import swaggerJsdoc from "swagger-jsdoc"; import swaggerUi from "swagger-ui-express"; import { Express, Request, Response } from "express"; import fs from "fs"; import path from "path"; import { getQueryParametersDocumentation } from "./query-parser"; import debugLib from "debug"; const debug = debugLib('api:swagger'); // Swagger definition const swaggerOptions = { definition: { openapi: "3.0.0", info: { title: "Active Directory Management API", version: "1.0.0", description: "REST API for managing Active Directory resources. This API provides comprehensive capabilities for managing Active Directory users, groups, organizational units, and more through a RESTful interface.", contact: { name: "API Support", email: "support@example.com", }, license: { name: "MIT", url: "https://opensource.org/licenses/MIT", } }, externalDocs: { description: "Find out more about this API", url: "/api/docs/more-info" }, servers: [ { url: "/api", description: "API base URL", }, ], tags: [ { name: "LDAP Connections", description: "Operations for managing LDAP connections" }, { name: "AD Users", description: "Operations for managing Active Directory users" }, { name: "AD Groups", description: "Operations for managing Active Directory groups" }, { name: "AD Computers", description: "Operations for managing Active Directory computers" }, { name: "AD Organizational Units", description: "Operations for managing Active Directory organizational units" }, { name: "Active Directory", description: "General Active Directory operations including password management and account status" } ], components: { securitySchemes: { bearerAuth: { type: "http", scheme: "bearer", bearerFormat: "JWT", }, cookieAuth: { type: "apiKey", in: "cookie", name: "connect.sid", }, }, schemas: { LdapConnection: { type: "object", properties: { id: { type: "integer" }, name: { type: "string" }, server: { type: "string" }, domain: { type: "string" }, port: { type: "integer" }, useSSL: { type: "boolean" }, username: { type: "string" }, status: { type: "string", enum: ["connected", "disconnected"] }, lastConnected: { type: "string", format: "date-time" }, createdAt: { type: "string", format: "date-time" }, }, }, AdUser: { type: "object", properties: { id: { type: "integer" }, connectionId: { type: "integer" }, objectGUID: { type: "string" }, distinguishedName: { type: "string" }, canonicalName: { type: "string" }, cn: { type: "string" }, sAMAccountName: { type: "string" }, userPrincipalName: { type: "string" }, givenName: { type: "string" }, surname: { type: "string" }, displayName: { type: "string" }, email: { type: "string" }, enabled: { type: "boolean" }, lastLogon: { type: "string", format: "date-time" }, memberOf: { type: "array", items: { type: "string" } }, managedBy: { type: "string", description: "Distinguished name of the user or group managing this object" }, adProperties: { type: "object" }, objectType: { type: "string", enum: ["user"] }, }, }, AdGroup: { type: "object", properties: { id: { type: "integer" }, connectionId: { type: "integer" }, objectGUID: { type: "string" }, distinguishedName: { type: "string" }, canonicalName: { type: "string" }, cn: { type: "string" }, sAMAccountName: { type: "string" }, groupType: { type: "string" }, description: { type: "string" }, members: { type: "array", items: { type: "string" } }, managedBy: { type: "string", description: "Distinguished name of the user or group managing this object" }, adProperties: { type: "object" }, objectType: { type: "string", enum: ["group"] }, }, }, AdOrgUnit: { type: "object", properties: { id: { type: "integer" }, connectionId: { type: "integer" }, objectGUID: { type: "string" }, distinguishedName: { type: "string" }, canonicalName: { type: "string" }, cn: { type: "string" }, name: { type: "string" }, description: { type: "string" }, managedBy: { type: "string", description: "Distinguished name of the user or group managing this object" }, adProperties: { type: "object" }, objectType: { type: "string", enum: ["organizationalUnit"] }, }, }, AdComputer: { type: "object", properties: { id: { type: "integer" }, connectionId: { type: "integer" }, objectGUID: { type: "string" }, distinguishedName: { type: "string" }, canonicalName: { type: "string" }, cn: { type: "string" }, name: { type: "string" }, dnsHostName: { type: "string" }, operatingSystem: { type: "string" }, operatingSystemVersion: { type: "string" }, lastLogon: { type: "string", format: "date-time" }, enabled: { type: "boolean" }, managedBy: { type: "string", description: "Distinguished name of the user or group managing this object" }, adProperties: { type: "object" }, objectType: { type: "string", enum: ["computer"] }, }, }, AdDomain: { type: "object", properties: { id: { type: "integer" }, connectionId: { type: "integer" }, objectGUID: { type: "string" }, distinguishedName: { type: "string" }, canonicalName: { type: "string" }, cn: { type: "string" }, name: { type: "string" }, netBIOSName: { type: "string" }, forestName: { type: "string" }, domainFunctionality: { type: "string" }, adProperties: { type: "object" }, objectType: { type: "string", enum: ["domain"] }, }, }, AdSite: { type: "object", properties: { id: { type: "integer" }, connectionId: { type: "integer" }, objectGUID: { type: "string" }, distinguishedName: { type: "string" }, cn: { type: "string" }, name: { type: "string" }, description: { type: "string" }, location: { type: "string" }, managedBy: { type: "string", description: "Distinguished name of the user or group managing this site" }, adProperties: { type: "object" }, objectType: { type: "string", enum: ["site"] }, }, }, AdSubnet: { type: "object", properties: { id: { type: "integer" }, connectionId: { type: "integer" }, objectGUID: { type: "string" }, distinguishedName: { type: "string" }, cn: { type: "string" }, name: { type: "string" }, description: { type: "string" }, siteObject: { type: "string", description: "Distinguished name of the site this subnet belongs to" }, location: { type: "string" }, networkAddress: { type: "string" }, networkMask: { type: "string" }, cidr: { type: "string", description: "CIDR notation for the subnet (e.g., 192.168.1.0/24)" }, managedBy: { type: "string", description: "Distinguished name of the user or group managing this subnet" }, adProperties: { type: "object" }, objectType: { type: "string", enum: ["subnet"] }, }, }, LdapAttribute: { type: "object", properties: { id: { type: "integer" }, connectionId: { type: "integer" }, name: { type: "string" }, displayName: { type: "string" }, description: { type: "string" }, type: { type: "string" }, multiValued: { type: "boolean" }, objectClass: { type: "string", enum: ["user", "group", "organizationalUnit", "computer", "domain"] }, isIndexed: { type: "boolean" }, createdAt: { type: "string", format: "date-time" }, updatedAt: { type: "string", format: "date-time" }, }, }, LdapFilter: { type: "object", properties: { id: { type: "integer" }, connectionId: { type: "integer" }, name: { type: "string" }, description: { type: "string" }, objectClass: { type: "string", enum: ["user", "group", "organizationalUnit", "computer", "domain"] }, filter: { type: "object" }, ldapFilter: { type: "string" }, createdBy: { type: "integer" }, modifiedBy: { type: "integer" }, createdAt: { type: "string", format: "date-time" }, updatedAt: { type: "string", format: "date-time" }, }, }, LdapFilterRevision: { type: "object", properties: { id: { type: "integer" }, filterId: { type: "integer" }, version: { type: "integer" }, filter: { type: "object" }, ldapFilter: { type: "string" }, createdBy: { type: "integer" }, createdAt: { type: "string", format: "date-time" }, comment: { type: "string" }, }, }, Error: { type: "object", properties: { message: { type: "string" }, errors: { type: "array", items: { type: "object", properties: { path: { type: "array", items: { type: "string" } }, message: { type: "string" }, }, }, }, }, }, }, parameters: { filterParam: { name: "filter", in: "query", description: "Filter expression (e.g. name eq 'John')", schema: { type: "string" }, }, selectParam: { name: "select", in: "query", description: "Properties to select (comma-separated)", schema: { type: "string" }, }, expandParam: { name: "expand", in: "query", description: "Related entities to expand (comma-separated)", schema: { type: "string" }, }, orderByParam: { name: "orderBy", in: "query", description: "Property to order by (e.g. name asc)", schema: { type: "string" }, }, topParam: { name: "top", in: "query", description: "Number of records to return", schema: { type: "integer" }, }, skipParam: { name: "skip", in: "query", description: "Number of records to skip", schema: { type: "integer" }, }, AuditLog: { type: "object", properties: { id: { type: "integer" }, action: { type: "string" }, targetId: { type: "string" }, details: { type: "object", additionalProperties: true }, userId: { type: "integer" }, timestamp: { type: "string", format: "date-time" }, connectionId: { type: "integer" } }, }, PasswordReset: { type: "object", required: ["userObjectGUID", "newPassword"], properties: { userObjectGUID: { type: "string", description: "The ObjectGUID of the user" }, newPassword: { type: "string", description: "The new password for the user" }, skipValidation: { type: "boolean", description: "Whether to skip password policy validation", default: false }, requirePasswordChangeAtNextLogon: { type: "boolean", description: "Whether to require the user to change their password at next logon", default: true } } }, EnableUserAccount: { type: "object", required: ["userObjectGUID", "enabled"], properties: { userObjectGUID: { type: "string", description: "The ObjectGUID of the user" }, enabled: { type: "boolean", description: "Whether to enable or disable the account" } } }, BulkEnableUserAccounts: { type: "object", required: ["userDNs", "enabled"], properties: { userDNs: { type: "array", items: { type: "string" }, description: "Array of user distinguished names" }, enabled: { type: "boolean", description: "Whether to enable or disable the accounts" } } }, }, responses: { UnauthorizedError: { description: "Authentication required", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" }, }, }, }, BadRequestError: { description: "Invalid request", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" }, }, }, }, NotFoundError: { description: "Resource not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" }, }, }, }, PaginatedResponse: { description: "Successful response with pagination metadata", content: { "application/json": { schema: { type: "object", properties: { data: { type: "array", items: { type: "object" } }, metadata: { type: "object", properties: { currentPage: { type: "integer" }, totalPages: { type: "integer" }, totalRecords: { type: "integer" }, nextPage: { type: "string", nullable: true }, previousPage: { type: "string", nullable: true } } } } } } } } }, }, security: [ { bearerAuth: [], }, { cookieAuth: [], }, ], }, apis: ["./server/routes.ts"], // Path to the API routes }; const swaggerSpec = swaggerJsdoc(swaggerOptions); export function setupSwagger(app: Express) { // Configure Swagger UI with additional options const swaggerUiOptions = { explorer: true, swaggerOptions: { persistAuthorization: true, docExpansion: 'none', tagsSorter: 'alpha', operationsSorter: 'alpha', filter: true, }, }; // Mount at both paths for backward compatibility app.use("/api/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec, swaggerUiOptions)); app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec, swaggerUiOptions)); // Provide the JSON spec at multiple paths app.get("/api/swagger.json", (req, res) => { res.setHeader("Content-Type", "application/json"); res.send(swaggerSpec); }); app.get("/api-docs/swagger.json", (req, res) => { res.setHeader("Content-Type", "application/json"); res.send(swaggerSpec); }); // Add download endpoint for the OpenAPI specification app.get("/api/docs/download", (req, res) => { const format = req.query.format?.toString().toLowerCase() ?? 'json'; if (format === 'json') { res.setHeader('Content-Disposition', 'attachment; filename=openapi-spec.json'); res.setHeader('Content-Type', 'application/json'); res.send(JSON.stringify(swaggerSpec, null, 2)); } else if (format === 'yaml' || format === 'yml') { try { // We'll need to import yaml dynamically or handle YAML conversion manually // For simplicity, just sending JSON if yaml not supported res.setHeader('Content-Disposition', 'attachment; filename=openapi-spec.json'); res.setHeader('Content-Type', 'application/json'); res.send(JSON.stringify(swaggerSpec, null, 2)); debug('YAML download requested but not implemented, sending JSON'); } catch (err) { debug('Error generating YAML:', err); res.setHeader('Content-Disposition', 'attachment; filename=openapi-spec.json'); res.setHeader('Content-Type', 'application/json'); res.send(JSON.stringify(swaggerSpec, null, 2)); } } else { res.status(400).send({ error: 'Invalid format. Supported formats: json, yaml' }); } }); // Add documentation for query parameters and filtering app.get("/api/docs/more-info", (req, res) => { const queryParamsInfo = getQueryParametersDocumentation(); res.send(` Active Directory Management API - Advanced Usage

Active Directory Management API - Advanced Usage Guide

API Documentation

The complete API documentation is available at /api/docs.

You can also download the OpenAPI specification:

Download OpenAPI Spec (JSON) Download OpenAPI Spec (YAML)

Query Parameters and Filtering

${queryParamsInfo}

Authentication

This API supports two authentication methods:

API Token Authentication

To authenticate using an API token, include the token in the Authorization header:

Authorization: Bearer YOUR_API_TOKEN

Rate Limiting

The API has rate limiting in place to prevent abuse. The current limits are:

Error Handling

The API returns consistent error responses with the following structure:

{
  "message": "Error message",
  "errors": [
    {
      "path": ["field", "subfield"],
      "message": "Specific error message"
    }
  ]
}

Example Usage

Filter Users by Name

GET /api/connections/1/ad-users?filter=displayName contains 'John'

Select Specific Fields

GET /api/connections/1/ad-users?select=id,displayName,email

Pagination

GET /api/connections/1/ad-users?top=10&skip=20

Combining Parameters

GET /api/connections/1/ad-users?filter=enabled eq true&select=id,displayName,email&orderBy=displayName asc&top=10

Return to API Documentation

`); }); }