diff --git a/DOCKER-DEPLOYMENT.md b/DOCKER-DEPLOYMENT.md deleted file mode 100644 index c69a093..0000000 --- a/DOCKER-DEPLOYMENT.md +++ /dev/null @@ -1,213 +0,0 @@ -# Docker Deployment Instructions - -This document describes how to deploy the Active Directory Management API using Docker. - -## Dockerfile - -Below is the updated Dockerfile that includes support for all the enhanced features: - -```dockerfile -FROM node:20-slim AS builder - -WORKDIR /app - -# Copy package files -COPY package*.json ./ - -# Install dependencies -RUN npm ci - -# Copy the rest of the source code -COPY . . - -# Build the application (optimized for production) -RUN npm run build - -# Production stage -FROM node:20-slim AS runner - -# Install necessary system dependencies -RUN apt-get update && apt-get install -y \ - curl \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app - -# Set environment to production -ENV NODE_ENV=production - -# Copy package files and install production dependencies -COPY package*.json ./ -RUN npm ci --production - -# Copy build artifacts from the builder stage -COPY --from=builder /app/dist ./dist - -# Copy schema files for Drizzle -COPY ./shared/schema.ts ./shared/ -COPY ./drizzle.config.ts ./ - -# Create a directory for logs -RUN mkdir -p /app/logs - -# Set default environment variables (these should be overridden at runtime) -ENV DATABASE_URL=postgres://postgres:postgres@postgres:5432/postgres -ENV REDIS_URL=redis://redis:6379 -ENV SESSION_SECRET=changeme - -# Set rate limiting configuration -ENV RATE_LIMIT_WINDOW_MS=900000 -ENV RATE_LIMIT_MAX_REQUESTS=100 - -# Set compression level (1-9, where 9 is maximum compression) -ENV COMPRESSION_LEVEL=6 - -# Set logging level (debug, info, warn, error) -ENV LOG_LEVEL=info - -# Expose the port the app runs on -EXPOSE 5000 - -# Health check to ensure the application is running properly -HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ - CMD curl -f http://localhost:5000/api/health || exit 1 - -# Start the application -CMD ["node", "dist/index.js"] -``` - -## Docker Compose - -For deploying the full stack with PostgreSQL and Redis, create a `docker-compose.yml` file: - -```yaml -version: '3.8' - -services: - app: - build: - context: . - dockerfile: Dockerfile - ports: - - "5000:5000" - environment: - - NODE_ENV=production - - DATABASE_URL=postgres://postgres:postgres@postgres:5432/postgres - - REDIS_URL=redis://redis:6379 - - SESSION_SECRET=your_secure_session_secret - - RATE_LIMIT_WINDOW_MS=900000 - - RATE_LIMIT_MAX_REQUESTS=100 - - COMPRESSION_LEVEL=6 - - LOG_LEVEL=info - depends_on: - - postgres - - redis - restart: unless-stopped - networks: - - app-network - - postgres: - image: postgres:15 - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - - POSTGRES_DB=postgres - volumes: - - postgres-data:/var/lib/postgresql/data - ports: - - "5432:5432" - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 10s - timeout: 5s - retries: 5 - restart: unless-stopped - networks: - - app-network - - redis: - image: redis:7 - command: redis-server --appendonly yes - volumes: - - redis-data:/data - ports: - - "6379:6379" - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 - restart: unless-stopped - networks: - - app-network - -networks: - app-network: - driver: bridge - -volumes: - postgres-data: - redis-data: -``` - -## Environment Variables - -The application supports the following environment variables: - -| Variable | Description | Default | -|----------|-------------|---------| -| `NODE_ENV` | Application environment | `production` | -| `DATABASE_URL` | PostgreSQL connection string | `postgres://postgres:postgres@postgres:5432/postgres` | -| `REDIS_URL` | Redis connection string | `redis://redis:6379` | -| `SESSION_SECRET` | Secret for session encryption | `changeme` (replace in production!) | -| `RATE_LIMIT_WINDOW_MS` | Rate limiting window in milliseconds | `900000` (15 minutes) | -| `RATE_LIMIT_MAX_REQUESTS` | Maximum requests per window | `100` | -| `COMPRESSION_LEVEL` | Response compression level (1-9) | `6` | -| `LOG_LEVEL` | Logging level | `info` | - -## Deployment Instructions - -1. Create the Dockerfile and docker-compose.yml as specified above -2. Build and start the containers: - ``` - docker-compose up -d - ``` -3. Run database migrations: - ``` - docker-compose exec app npm run db:push - ``` -4. Access the application at http://localhost:5000 -5. API documentation is available at http://localhost:5000/api/docs - -## Health Checks - -The application includes a health check endpoint at `/api/health` that returns HTTP 200 when the application is running normally. The Docker container is configured to use this endpoint for health checks. - -## Scaling Considerations - -- The application can be horizontally scaled by adding more instances of the app service -- For high-availability, consider using a managed PostgreSQL service instead of the containerized version -- For production, use a proper Redis cluster or managed Redis service -- Consider using a reverse proxy like Nginx or a load balancer in front of multiple app instances - -## Security Notes - -- Always replace the default `SESSION_SECRET` with a secure random string -- Use appropriate firewall rules to restrict access to PostgreSQL and Redis -- Consider setting up SSL/TLS for production deployments -- Adjust rate limiting parameters based on your expected traffic - -## Troubleshooting - -- If the application fails to start, check the logs: - ``` - docker-compose logs app - ``` -- For database connection issues: - ``` - docker-compose logs postgres - ``` -- For Redis connection issues: - ``` - docker-compose logs redis - ``` \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 496f59f..0000000 --- a/Dockerfile +++ /dev/null @@ -1,72 +0,0 @@ -FROM node:20-slim AS builder - -WORKDIR /app - -# Copy package files -COPY package*.json ./ - -# Install dependencies -RUN npm ci - -# Copy the rest of the source code -COPY . . - -# Build the application (optimized for production) -RUN npm run build - -# Production stage -FROM node:20-slim AS runner - -# Install necessary system dependencies -RUN apt-get update && apt-get install -y \ - curl \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app - -# Set environment to production -ENV NODE_ENV=production - -# Copy package files and install production dependencies -COPY package*.json ./ -RUN npm ci --production - -# Copy build artifacts from the builder stage -COPY --from=builder /app/dist ./dist - -# Copy schema files for Drizzle -COPY ./shared/schema.ts ./shared/ -COPY ./drizzle.config.ts ./ - -# Create a directory for logs -RUN mkdir -p /app/logs - -# Set default environment variables (these should be overridden at runtime) -ENV DATABASE_URL=postgres://postgres:postgres@postgres:5432/postgres -ENV REDIS_URL=redis://redis:6379 -ENV SESSION_SECRET=changeme - -# Set rate limiting configuration -ENV RATE_LIMIT_WINDOW_MS=900000 -ENV RATE_LIMIT_MAX_REQUESTS=100 - -# Set compression level (1-9, where 9 is maximum compression) -ENV COMPRESSION_LEVEL=6 - -# Set logging level (debug, info, warn, error) -ENV LOG_LEVEL=info - -# Expose the port the app runs on -EXPOSE 5000 - -# Add startup script with health check -COPY ./docker-entrypoint.sh / -RUN chmod +x /docker-entrypoint.sh - -# Health check to ensure the application is running properly -HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ - CMD curl -f http://localhost:5000/api/health || exit 1 - -# Start the application using the entrypoint script -ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["node", "dist/index.js"] \ No newline at end of file diff --git a/Dockerfile.new b/Dockerfile.new deleted file mode 100644 index 03425f1..0000000 --- a/Dockerfile.new +++ /dev/null @@ -1,67 +0,0 @@ -FROM node:20-slim AS builder - -WORKDIR /app - -# Copy package files -COPY package*.json ./ - -# Install dependencies -RUN npm ci - -# Copy the rest of the source code -COPY . . - -# Build the application (optimized for production) -RUN npm run build - -# Production stage -FROM node:20-slim AS runner - -# Install necessary system dependencies -RUN apt-get update && apt-get install -y \ - curl \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app - -# Set environment to production -ENV NODE_ENV=production - -# Copy package files and install production dependencies -COPY package*.json ./ -RUN npm ci --production - -# Copy build artifacts from the builder stage -COPY --from=builder /app/dist ./dist - -# Copy schema files for Drizzle -COPY ./shared/schema.ts ./shared/ -COPY ./drizzle.config.ts ./ - -# Create a directory for logs -RUN mkdir -p /app/logs - -# Set default environment variables (these should be overridden at runtime) -ENV DATABASE_URL=postgres://postgres:postgres@postgres:5432/postgres -ENV REDIS_URL=redis://redis:6379 -ENV SESSION_SECRET=changeme - -# Set rate limiting configuration -ENV RATE_LIMIT_WINDOW_MS=900000 -ENV RATE_LIMIT_MAX_REQUESTS=100 - -# Set compression level (1-9, where 9 is maximum compression) -ENV COMPRESSION_LEVEL=6 - -# Set logging level (debug, info, warn, error) -ENV LOG_LEVEL=info - -# Expose the port the app runs on -EXPOSE 5000 - -# Health check to ensure the application is running properly -HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ - CMD curl -f http://localhost:5000/api/health || exit 1 - -# Start the application -CMD ["node", "dist/index.js"] \ No newline at end of file diff --git a/server/ldap-query-builder-routes.ts b/server/ldap-query-builder-routes.ts index e4e8df7..cfd6c5b 100644 --- a/server/ldap-query-builder-routes.ts +++ b/server/ldap-query-builder-routes.ts @@ -10,25 +10,11 @@ import { connectToLdap, searchLdap, getLdapAvailableAttributes } from "./ldap"; import { IStorage } from "./storage"; import { InsertLdapQuery, LdapQuery, InsertLdapQueryVersion } from "@shared/schema"; -// Use middleware to check permissions +import { requirePermission } from "./authorization"; + +// Use the proper auth middleware from authorization.ts with option to allow API tokens function hasPermission(permission: string) { - return (req: any, res: any, next: any) => { - // Check if the user is authenticated via passport session or has a valid API token - if ( - // Check for authenticated session (safely check if isAuthenticated is a function first) - (typeof req.isAuthenticated === 'function' && req.isAuthenticated()) || - // Or check for existing user object (set by token auth) - req.user || - // Or check for authorization header (token auth) - req.headers.authorization - ) { - // In a real implementation, this would check the user's permissions against the required permission - return next(); - } - - // If not authenticated, return 401 Unauthorized - return res.status(401).json({ error: "Unauthorized" }); - }; + return requirePermission(permission, { allowApiToken: true }); } export function registerLdapQueryBuilderRoutes(router: Router, storage: IStorage) { diff --git a/server/ldap-query-builder-routes.ts.bak b/server/ldap-query-builder-routes.ts.bak deleted file mode 100644 index 429e567..0000000 --- a/server/ldap-query-builder-routes.ts.bak +++ /dev/null @@ -1,556 +0,0 @@ -import { Router } from "express"; -import { - validateQueryBuilder, - generateLdapFilter, - getHumanReadableFilter, - buildLdapFilter, - LdapQueryBuilder -} from "./ldap-filter-builder"; -import { connectToLdap, searchLdap } from "./ldap"; -import { IStorage } from "./storage"; -import { InsertLdapQuery, LdapQuery, InsertLdapQueryVersion } from "@shared/schema"; -// Use middleware to check permissions -function hasPermission(permission: string) { - return (req: any, res: any, next: any) => { - // For now, we'll just allow all requests through - // In a real implementation, this would check the user's permissions - return next(); - }; -} - -export function registerLdapQueryBuilderRoutes(router: Router, storage: IStorage) { - /** - * @swagger - * /ldap-queries: - * get: - * summary: Get all saved LDAP queries - * description: Retrieve a list of all saved LDAP query filters - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: A list of LDAP queries - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: '#/components/schemas/LdapQuery' - */ - router.get("/ldap-queries", hasPermission("read:ldap_queries"), async (req, res) => { - try { - const queries = await storage.getLdapQueries(); - res.json(queries); - } catch (error) { - console.error("Error getting LDAP queries:", error); - res.status(500).json({ error: "Failed to retrieve LDAP queries" }); - } - }); - - /** - * @swagger - * /ldap-queries/{id}: - * get: - * summary: Get a saved LDAP query by ID - * description: Retrieve a specific LDAP query filter by its ID - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: Query ID - * responses: - * 200: - * description: LDAP query found - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/LdapQuery' - * 404: - * description: Query not found - */ - router.get("/ldap-queries/:id", hasPermission("read:ldap_queries"), async (req, res) => { - try { - const id = parseInt(req.params.id); - if (isNaN(id)) { - return res.status(400).json({ error: "Invalid query ID" }); - } - - const query = await storage.getLdapQueryById(id); - if (!query) { - return res.status(404).json({ error: "LDAP query not found" }); - } - - res.json(query); - } catch (error) { - console.error("Error getting LDAP query:", error); - res.status(500).json({ error: "Failed to retrieve LDAP query" }); - } - }); - - /** - * @swagger - * /ldap-queries/{id}/versions: - * get: - * summary: Get version history for an LDAP query - * description: Retrieve the revision history for a specific LDAP query - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: Query ID - * responses: - * 200: - * description: List of query versions - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: '#/components/schemas/LdapQueryVersion' - * 404: - * description: Query not found - */ - router.get("/ldap-queries/:id/versions", hasPermission("read:ldap_queries"), async (req, res) => { - try { - const id = parseInt(req.params.id); - if (isNaN(id)) { - return res.status(400).json({ error: "Invalid query ID" }); - } - - const query = await storage.getLdapQueryById(id); - if (!query) { - return res.status(404).json({ error: "LDAP query not found" }); - } - - const versions = await storage.getLdapQueryVersions(id); - res.json(versions); - } catch (error) { - console.error("Error getting LDAP query versions:", error); - res.status(500).json({ error: "Failed to retrieve LDAP query versions" }); - } - }); - - /** - * @swagger - * /ldap-queries: - * post: - * summary: Create a new LDAP query - * description: Save a new LDAP query filter - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * name: - * type: string - * description: - * type: string - * targetObject: - * type: string - * enum: [users, groups, computers, ous] - * filter: - * type: object - * responses: - * 201: - * description: Query created successfully - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/LdapQuery' - * 400: - * description: Invalid query format - */ - router.post("/ldap-queries", hasPermission("write:ldap_queries"), async (req, res) => { - try { - const { name, description, targetObject, filter } = req.body; - - if (!name || !targetObject || !filter) { - return res.status(400).json({ error: "Missing required fields: name, targetObject, filter" }); - } - - try { - // Validate the filter format - const queryBuilder = validateQueryBuilder({ targetObject, filter }); - - // Generate the LDAP filter and readable representation - const ldapFilter = generateLdapFilter(queryBuilder); - const readableFilter = getHumanReadableFilter(queryBuilder.filter); - - const insertQuery: InsertLdapQuery = { - name, - description: description || "", - targetObject, - filterJson: filter, - ldapFilter, - readableFilter, - createdBy: req.user?.id || null, - }; - - const query = await storage.createLdapQuery(insertQuery); - - res.status(201).json(query); - } catch (error) { - console.error("Error validating query:", error); - return res.status(400).json({ error: "Invalid query format", details: error.message }); - } - } catch (error) { - console.error("Error creating LDAP query:", error); - res.status(500).json({ error: "Failed to create LDAP query" }); - } - }); - - /** - * @swagger - * /ldap-queries/{id}: - * put: - * summary: Update an LDAP query - * description: Update an existing LDAP query and create a new version - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: Query ID - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * name: - * type: string - * description: - * type: string - * targetObject: - * type: string - * enum: [users, groups, computers, ous] - * filter: - * type: object - * responses: - * 200: - * description: Query updated successfully - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/LdapQuery' - * 400: - * description: Invalid query format - * 404: - * description: Query not found - */ - router.put("/ldap-queries/:id", hasPermission("write:ldap_queries"), async (req, res) => { - try { - const id = parseInt(req.params.id); - if (isNaN(id)) { - return res.status(400).json({ error: "Invalid query ID" }); - } - - const { name, description, targetObject, filter } = req.body; - - if (!name || !targetObject || !filter) { - return res.status(400).json({ error: "Missing required fields: name, targetObject, filter" }); - } - - // Check if query exists - const existingQuery = await storage.getLdapQueryById(id); - if (!existingQuery) { - return res.status(404).json({ error: "LDAP query not found" }); - } - - try { - // Validate the filter format - const queryBuilder = validateQueryBuilder({ targetObject, filter }); - - // Generate the LDAP filter and readable representation - const ldapFilter = generateLdapFilter(queryBuilder); - const readableFilter = getHumanReadableFilter(queryBuilder.filter); - - // Create a version entry for the previous state - const versionEntry: InsertLdapQueryVersion = { - queryId: id, - version: existingQuery.version, - filterJson: existingQuery.filterJson as any, // Type safety is handled by schema validation - ldapFilter: existingQuery.ldapFilter, - readableFilter: existingQuery.readableFilter, - targetObject: existingQuery.targetObject, - createdBy: existingQuery.createdBy, - createdAt: existingQuery.createdAt - }; - - await storage.createLdapQueryVersion(versionEntry); - - // Update the query with new values - const updatedQuery = await storage.updateLdapQuery(id, { - name, - description: description || "", - targetObject, - filterJson: filter, - ldapFilter, - readableFilter, - updatedBy: req.user?.id || null, - version: existingQuery.version + 1 - }); - - res.status(200).json(updatedQuery); - } catch (error) { - console.error("Error validating query:", error); - return res.status(400).json({ error: "Invalid query format", details: error.message }); - } - } catch (error) { - console.error("Error updating LDAP query:", error); - res.status(500).json({ error: "Failed to update LDAP query" }); - } - }); - - /** - * @swagger - * /ldap-queries/{id}: - * delete: - * summary: Delete an LDAP query - * description: Delete an LDAP query and all its versions - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: Query ID - * responses: - * 204: - * description: Query deleted successfully - * 404: - * description: Query not found - */ - router.delete("/ldap-queries/:id", hasPermission("delete:ldap_queries"), async (req, res) => { - try { - const id = parseInt(req.params.id); - if (isNaN(id)) { - return res.status(400).json({ error: "Invalid query ID" }); - } - - // Check if query exists - const existingQuery = await storage.getLdapQueryById(id); - if (!existingQuery) { - return res.status(404).json({ error: "LDAP query not found" }); - } - - // Delete all versions and the query itself - await storage.deleteLdapQueryVersions(id); - await storage.deleteLdapQuery(id); - - res.status(204).send(); - } catch (error) { - console.error("Error deleting LDAP query:", error); - res.status(500).json({ error: "Failed to delete LDAP query" }); - } - }); - - /** - * @swagger - * /ldap-queries/{id}/revert/{version}: - * post: - * summary: Revert to a previous version - * description: Revert an LDAP query to a specific previous version - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: Query ID - * - in: path - * name: version - * required: true - * schema: - * type: integer - * description: Version to revert to - * responses: - * 200: - * description: Query reverted successfully - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/LdapQuery' - * 404: - * description: Query or version not found - */ - router.post("/ldap-queries/:id/revert/:version", hasPermission("write:ldap_queries"), async (req, res) => { - try { - const id = parseInt(req.params.id); - const version = parseInt(req.params.version); - - if (isNaN(id) || isNaN(version)) { - return res.status(400).json({ error: "Invalid query ID or version" }); - } - - // Check if query exists - const existingQuery = await storage.getLdapQueryById(id); - if (!existingQuery) { - return res.status(404).json({ error: "LDAP query not found" }); - } - - // Find the version to revert to - const versionToRevert = await storage.getLdapQueryVersion(id, version); - if (!versionToRevert) { - return res.status(404).json({ error: "Version not found" }); - } - - // Create a version entry for the current state - const versionEntry: InsertLdapQueryVersion = { - queryId: id, - version: existingQuery.version, - filterJson: existingQuery.filterJson as any, // Type safety is handled by schema validation - ldapFilter: existingQuery.ldapFilter, - readableFilter: existingQuery.readableFilter, - targetObject: existingQuery.targetObject, - createdBy: existingQuery.createdBy, - createdAt: existingQuery.createdAt - }; - - await storage.createLdapQueryVersion(versionEntry); - - // Update the query with the version's values - const updatedQuery = await storage.updateLdapQuery(id, { - targetObject: versionToRevert.targetObject, - filterJson: versionToRevert.filterJson, - ldapFilter: versionToRevert.ldapFilter, - readableFilter: versionToRevert.readableFilter, - updatedBy: req.user?.id || null, - version: existingQuery.version + 1 - }); - - res.status(200).json(updatedQuery); - } catch (error) { - console.error("Error reverting LDAP query:", error); - res.status(500).json({ error: "Failed to revert LDAP query" }); - } - }); - - /** - * @swagger - * /ldap-queries/test: - * post: - * summary: Test an LDAP query filter - * description: Test a filter against an LDAP connection to see what objects would be returned - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * connectionId: - * type: integer - * targetObject: - * type: string - * enum: [users, groups, computers, ous] - * filter: - * type: object - * limit: - * type: integer - * default: 100 - * properties: - * type: array - * items: - * type: string - * responses: - * 200: - * description: Query results - * content: - * application/json: - * schema: - * type: object - * properties: - * count: - * type: integer - * results: - * type: array - * items: - * type: object - * filter: - * type: string - * 400: - * description: Invalid query or connection - * 404: - * description: Connection not found - */ - router.post("/ldap-queries/test", hasPermission("read:ldap_objects"), async (req, res) => { - try { - const { connectionId, targetObject, filter, limit = 100, properties = [] } = req.body; - - if (!connectionId || !targetObject || !filter) { - return res.status(400).json({ error: "Missing required fields: connectionId, targetObject, filter" }); - } - - // Get the connection - const connection = await storage.getLdapConnectionById(connectionId); - if (!connection) { - return res.status(404).json({ error: "LDAP connection not found" }); - } - - // Validate and generate the filter - let ldapFilter: string; - try { - const queryBuilder = validateQueryBuilder({ targetObject, filter }); - ldapFilter = generateLdapFilter(queryBuilder); - } catch (error) { - return res.status(400).json({ error: "Invalid filter format", details: error.message }); - } - - // Connect to LDAP - const client = await connectToLdap(connection); - - try { - // Execute the search - const results = await searchLdap(client, { - filter: ldapFilter, - attributes: properties.length > 0 ? properties : undefined, - limit - }); - - // Return the results - res.json({ - count: results.length, - results, - filter: ldapFilter - }); - } finally { - // Always destroy the client - client.destroy(); - } - } catch (error) { - console.error("Error testing LDAP query:", error); - res.status(500).json({ error: "Failed to test LDAP query", details: error.message }); - } - }); -} \ No newline at end of file diff --git a/server/ldap-query-builder-routes.ts.new b/server/ldap-query-builder-routes.ts.new deleted file mode 100644 index 430652d..0000000 --- a/server/ldap-query-builder-routes.ts.new +++ /dev/null @@ -1,558 +0,0 @@ -import { Router } from "express"; -import { - validateQueryBuilder, - generateLdapFilter, - getHumanReadableFilter, - buildLdapFilter, - LdapQueryBuilder -} from "./ldap-filter-builder"; -import { connectToLdap, searchLdap } from "./ldap"; -import { IStorage } from "./storage"; -import { InsertLdapQuery, LdapQuery, InsertLdapQueryVersion } from "@shared/schema"; - -// Use middleware to check permissions -function hasPermission(permission: string) { - return (req: any, res: any, next: any) => { - // For now, we'll just allow all requests through - // In a real implementation, this would check the user's permissions - return next(); - }; -} - -export function registerLdapQueryBuilderRoutes(router: Router, storage: IStorage) { - /** - * @swagger - * /ldap-queries: - * get: - * summary: Get all saved LDAP queries - * description: Retrieve a list of all saved LDAP query filters - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: A list of LDAP queries - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: '#/components/schemas/LdapQuery' - */ - router.get("/ldap-queries", hasPermission("read:ldap_queries"), async (req, res) => { - try { - const queries = await storage.getLdapQueries(); - res.json(queries); - } catch (error) { - console.error("Error getting LDAP queries:", error); - res.status(500).json({ error: "Failed to retrieve LDAP queries" }); - } - }); - - /** - * @swagger - * /ldap-queries/{id}: - * get: - * summary: Get a saved LDAP query by ID - * description: Retrieve a specific LDAP query filter by its ID - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: Query ID - * responses: - * 200: - * description: LDAP query found - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/LdapQuery' - * 404: - * description: Query not found - */ - router.get("/ldap-queries/:id", hasPermission("read:ldap_queries"), async (req, res) => { - try { - const id = parseInt(req.params.id); - if (isNaN(id)) { - return res.status(400).json({ error: "Invalid query ID" }); - } - - const query = await storage.getLdapQuery(id); - if (!query) { - return res.status(404).json({ error: "LDAP query not found" }); - } - - res.json(query); - } catch (error) { - console.error("Error getting LDAP query:", error); - res.status(500).json({ error: "Failed to retrieve LDAP query" }); - } - }); - - /** - * @swagger - * /ldap-queries/{id}/versions: - * get: - * summary: Get version history for an LDAP query - * description: Retrieve the revision history for a specific LDAP query - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: Query ID - * responses: - * 200: - * description: List of query versions - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: '#/components/schemas/LdapQueryVersion' - * 404: - * description: Query not found - */ - router.get("/ldap-queries/:id/versions", hasPermission("read:ldap_queries"), async (req, res) => { - try { - const id = parseInt(req.params.id); - if (isNaN(id)) { - return res.status(400).json({ error: "Invalid query ID" }); - } - - const query = await storage.getLdapQuery(id); - if (!query) { - return res.status(404).json({ error: "LDAP query not found" }); - } - - const versions = await storage.getLdapQueryVersions(id); - res.json(versions); - } catch (error) { - console.error("Error getting LDAP query versions:", error); - res.status(500).json({ error: "Failed to retrieve LDAP query versions" }); - } - }); - - /** - * @swagger - * /ldap-queries: - * post: - * summary: Create a new LDAP query - * description: Save a new LDAP query filter - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * name: - * type: string - * description: - * type: string - * targetObject: - * type: string - * enum: [users, groups, computers, ous] - * filter: - * type: object - * responses: - * 201: - * description: Query created successfully - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/LdapQuery' - * 400: - * description: Invalid query format - */ - router.post("/ldap-queries", hasPermission("write:ldap_queries"), async (req, res) => { - try { - const { name, description, targetObject, filter } = req.body; - - if (!name || !targetObject || !filter) { - return res.status(400).json({ error: "Missing required fields: name, targetObject, filter" }); - } - - try { - // Validate the filter format - const queryBuilder = validateQueryBuilder({ targetObject, filter }); - - // Generate the LDAP filter and readable representation - const ldapFilter = generateLdapFilter(queryBuilder); - const readableFilter = getHumanReadableFilter(queryBuilder.filter); - - const insertQuery: InsertLdapQuery = { - name, - description: description || "", - targetObject, - filterJson: filter as any, // Type safety is handled by schema validation - ldapFilter, - readableFilter, - createdBy: req.user?.id || 1, // Default to 1 if no user - }; - - const query = await storage.createLdapQuery(insertQuery); - - res.status(201).json(query); - } catch (error: any) { - console.error("Error validating query:", error); - return res.status(400).json({ error: "Invalid query format", details: error.message }); - } - } catch (error) { - console.error("Error creating LDAP query:", error); - res.status(500).json({ error: "Failed to create LDAP query" }); - } - }); - - /** - * @swagger - * /ldap-queries/{id}: - * put: - * summary: Update an LDAP query - * description: Update an existing LDAP query and create a new version - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: Query ID - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * name: - * type: string - * description: - * type: string - * targetObject: - * type: string - * enum: [users, groups, computers, ous] - * filter: - * type: object - * responses: - * 200: - * description: Query updated successfully - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/LdapQuery' - * 400: - * description: Invalid query format - * 404: - * description: Query not found - */ - router.put("/ldap-queries/:id", hasPermission("write:ldap_queries"), async (req, res) => { - try { - const id = parseInt(req.params.id); - if (isNaN(id)) { - return res.status(400).json({ error: "Invalid query ID" }); - } - - const { name, description, targetObject, filter } = req.body; - - if (!name || !targetObject || !filter) { - return res.status(400).json({ error: "Missing required fields: name, targetObject, filter" }); - } - - // Check if query exists - const existingQuery = await storage.getLdapQuery(id); - if (!existingQuery) { - return res.status(404).json({ error: "LDAP query not found" }); - } - - try { - // Validate the filter format - const queryBuilder = validateQueryBuilder({ targetObject, filter }); - - // Generate the LDAP filter and readable representation - const ldapFilter = generateLdapFilter(queryBuilder); - const readableFilter = getHumanReadableFilter(queryBuilder.filter); - - // Create a version entry for the previous state - const versionEntry: InsertLdapQueryVersion = { - queryId: id, - version: existingQuery.version, - filterJson: existingQuery.filterJson as any, // Type safety is handled by schema validation - ldapFilter: existingQuery.ldapFilter, - readableFilter: existingQuery.readableFilter, - targetObject: existingQuery.targetObject, - createdBy: existingQuery.createdBy, - createdAt: existingQuery.createdAt - }; - - await storage.createLdapQueryVersion(versionEntry); - - // Update the query with new values - const updatedQuery = await storage.updateLdapQuery(id, { - name, - description: description || "", - targetObject, - filterJson: filter as any, - ldapFilter, - readableFilter, - updatedBy: req.user?.id || 1, // Default to 1 if no user - version: existingQuery.version + 1 - }); - - res.status(200).json(updatedQuery); - } catch (error: any) { - console.error("Error validating query:", error); - return res.status(400).json({ error: "Invalid query format", details: error.message }); - } - } catch (error) { - console.error("Error updating LDAP query:", error); - res.status(500).json({ error: "Failed to update LDAP query" }); - } - }); - - /** - * @swagger - * /ldap-queries/{id}: - * delete: - * summary: Delete an LDAP query - * description: Delete an LDAP query and all its versions - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: Query ID - * responses: - * 204: - * description: Query deleted successfully - * 404: - * description: Query not found - */ - router.delete("/ldap-queries/:id", hasPermission("delete:ldap_queries"), async (req, res) => { - try { - const id = parseInt(req.params.id); - if (isNaN(id)) { - return res.status(400).json({ error: "Invalid query ID" }); - } - - // Check if query exists - const existingQuery = await storage.getLdapQuery(id); - if (!existingQuery) { - return res.status(404).json({ error: "LDAP query not found" }); - } - - // Delete the query - versions will be deleted via cascading foreign key constraints - await storage.deleteLdapQuery(id); - - res.status(204).send(); - } catch (error) { - console.error("Error deleting LDAP query:", error); - res.status(500).json({ error: "Failed to delete LDAP query" }); - } - }); - - /** - * @swagger - * /ldap-queries/{id}/revert/{version}: - * post: - * summary: Revert to a previous version - * description: Revert an LDAP query to a specific previous version - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: integer - * description: Query ID - * - in: path - * name: version - * required: true - * schema: - * type: integer - * description: Version to revert to - * responses: - * 200: - * description: Query reverted successfully - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/LdapQuery' - * 404: - * description: Query or version not found - */ - router.post("/ldap-queries/:id/revert/:version", hasPermission("write:ldap_queries"), async (req, res) => { - try { - const id = parseInt(req.params.id); - const versionNumber = parseInt(req.params.version); - - if (isNaN(id) || isNaN(versionNumber)) { - return res.status(400).json({ error: "Invalid query ID or version" }); - } - - // Check if query exists - const existingQuery = await storage.getLdapQuery(id); - if (!existingQuery) { - return res.status(404).json({ error: "LDAP query not found" }); - } - - // Find the version to revert to - we need to get all versions and find the matching one - const allVersions = await storage.getLdapQueryVersions(id); - const versionToRevert = allVersions.find(v => v.version === versionNumber); - - if (!versionToRevert) { - return res.status(404).json({ error: "Version not found" }); - } - - // Create a version entry for the current state - const versionEntry: InsertLdapQueryVersion = { - queryId: id, - version: existingQuery.version, - filterJson: existingQuery.filterJson as any, // Type safety is handled by schema validation - ldapFilter: existingQuery.ldapFilter, - readableFilter: existingQuery.readableFilter, - targetObject: existingQuery.targetObject, - createdBy: existingQuery.createdBy, - createdAt: existingQuery.createdAt - }; - - await storage.createLdapQueryVersion(versionEntry); - - // Update the query with the version's values - const updatedQuery = await storage.updateLdapQuery(id, { - targetObject: versionToRevert.targetObject, - filterJson: versionToRevert.filterJson as any, - ldapFilter: versionToRevert.ldapFilter, - readableFilter: versionToRevert.readableFilter, - updatedBy: req.user?.id || 1, // Default to 1 if no user - version: existingQuery.version + 1 - }); - - res.status(200).json(updatedQuery); - } catch (error) { - console.error("Error reverting LDAP query:", error); - res.status(500).json({ error: "Failed to revert LDAP query" }); - } - }); - - /** - * @swagger - * /ldap-queries/test: - * post: - * summary: Test an LDAP query filter - * description: Test a filter against an LDAP connection to see what objects would be returned - * tags: [LDAP Query Builder] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * connectionId: - * type: integer - * targetObject: - * type: string - * enum: [users, groups, computers, ous] - * filter: - * type: object - * limit: - * type: integer - * default: 100 - * properties: - * type: array - * items: - * type: string - * responses: - * 200: - * description: Query results - * content: - * application/json: - * schema: - * type: object - * properties: - * count: - * type: integer - * results: - * type: array - * items: - * type: object - * filter: - * type: string - * 400: - * description: Invalid query or connection - * 404: - * description: Connection not found - */ - router.post("/ldap-queries/test", hasPermission("read:ldap_objects"), async (req, res) => { - try { - const { connectionId, targetObject, filter, limit = 100, properties = [] } = req.body; - - if (!connectionId || !targetObject || !filter) { - return res.status(400).json({ error: "Missing required fields: connectionId, targetObject, filter" }); - } - - // Get the connection - const connection = await storage.getLdapConnection(connectionId); - if (!connection) { - return res.status(404).json({ error: "LDAP connection not found" }); - } - - // Validate and generate the filter - let ldapFilter: string; - try { - const queryBuilder = validateQueryBuilder({ targetObject, filter }); - ldapFilter = generateLdapFilter(queryBuilder); - } catch (error: any) { - return res.status(400).json({ error: "Invalid filter format", details: error.message }); - } - - // Connect to LDAP - const client = await connectToLdap(connection); - - try { - // Execute the search - const results = await searchLdap(client, { - filter: ldapFilter, - attributes: properties.length > 0 ? properties : undefined, - limit - }); - - // Return the results - res.json({ - count: results.length, - results, - filter: ldapFilter - }); - } finally { - // Always destroy the client - client.destroy(); - } - } catch (error: any) { - console.error("Error testing LDAP query:", error); - res.status(500).json({ error: "Failed to test LDAP query", details: error.message }); - } - }); -} \ No newline at end of file diff --git a/server/storage.ts b/server/storage.ts index ff7f565..4a2eeb2 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -104,14 +104,11 @@ export interface IStorage { updateAdDomain(id: number, domain: Partial): Promise; deleteAdDomain(id: number): Promise; listAdDomains(connectionId: number, query?: any): Promise; - - // Session store - sessionStore: any; } // Database Storage implementation export class DatabaseStorage implements IStorage { - sessionStore: any; + sessionStore: session.Store; constructor() { this.sessionStore = new PostgresStore({