Add user authentication and Active Directory management features. Includes a new admin UI and Swagger API documentation.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 705f2157-ef97-4fbd-89e4-8c7f2ecaea90
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7ed01c5f-a82d-405a-b728-b2e3d127c60c/e9f0a10f-e323-455c-82c9-1da4a687f20e.jpg
This commit is contained in:
alphaeusmote
2025-04-08 01:55:16 +00:00
parent e871381332
commit 3c7d1c1f9c
36 changed files with 6333 additions and 535 deletions
+115 -80
View File
@@ -1,12 +1,13 @@
import passport from "passport";
import { Strategy as LocalStrategy } from "passport-local";
import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt";
import { Express } from "express";
import session from "express-session";
import { scrypt, randomBytes, timingSafeEqual } from "crypto";
import { promisify } from "util";
import { storage } from "./storage";
import { User as SelectUser } from "@shared/schema";
import jwt from "jsonwebtoken";
import { storage } from "./storage";
import { User as SelectUser, loginSchema } from "@shared/schema";
declare global {
namespace Express {
@@ -15,14 +16,16 @@ declare global {
}
const scryptAsync = promisify(scrypt);
const JWT_SECRET = process.env.JWT_SECRET || "super-secret-key-change-in-production";
const SESSION_SECRET = process.env.SESSION_SECRET || "session-secret-change-in-production";
export async function hashPassword(password: string) {
async function hashPassword(password: string) {
const salt = randomBytes(16).toString("hex");
const buf = (await scryptAsync(password, salt, 64)) as Buffer;
return `${buf.toString("hex")}.${salt}`;
}
export async function comparePasswords(supplied: string, stored: string) {
async function comparePasswords(supplied: string, stored: string) {
const [hashed, salt] = stored.split(".");
const hashedBuf = Buffer.from(hashed, "hex");
const suppliedBuf = (await scryptAsync(supplied, salt, 64)) as Buffer;
@@ -30,18 +33,15 @@ export async function comparePasswords(supplied: string, stored: string) {
}
export function setupAuth(app: Express) {
const jwtSecret = process.env.JWT_SECRET || 'default_jwt_secret_key_change_in_production';
const sessionSecret = process.env.SESSION_SECRET || 'default_session_secret_key_change_in_production';
const sessionSettings: session.SessionOptions = {
secret: sessionSecret,
secret: SESSION_SECRET,
resave: false,
saveUninitialized: false,
store: storage.sessionStore,
cookie: {
secure: process.env.NODE_ENV === 'production',
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
maxAge: 24 * 60 * 60 * 1000, // 24 hours
secure: process.env.NODE_ENV === "production",
},
};
app.set("trust proxy", 1);
@@ -49,21 +49,42 @@ export function setupAuth(app: Express) {
app.use(passport.initialize());
app.use(passport.session());
// Local strategy for username/password authentication
passport.use(
new LocalStrategy(async (username, password, done) => {
try {
const user = await storage.getUserByUsername(username);
if (!user || !(await comparePasswords(password, user.password))) {
return done(null, false);
} else {
return done(null, user);
return done(null, false, { message: "Invalid username or password" });
}
return done(null, user);
} catch (error) {
return done(error);
}
}),
);
// JWT strategy for API token authentication
passport.use(
new JwtStrategy(
{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: JWT_SECRET,
},
async (payload, done) => {
try {
const user = await storage.getUser(payload.sub);
if (!user) {
return done(null, false, { message: "User not found" });
}
return done(null, user);
} catch (error) {
return done(error);
}
}
)
);
passport.serializeUser((user, done) => done(null, user.id));
passport.deserializeUser(async (id: number, done) => {
try {
@@ -74,102 +95,116 @@ export function setupAuth(app: Express) {
}
});
// Registration endpoint
app.post("/api/register", async (req, res, next) => {
try {
const existingUser = await storage.getUserByUsername(req.body.username);
const validationResult = loginSchema.safeParse(req.body);
if (!validationResult.success) {
return res.status(400).json({ message: "Invalid input", errors: validationResult.error.errors });
}
const { username, password } = req.body;
const existingUser = await storage.getUserByUsername(username);
if (existingUser) {
return res.status(400).json({ message: "Username already exists" });
}
const hashedPassword = await hashPassword(req.body.password);
const hashedPassword = await hashPassword(password);
const user = await storage.createUser({
...req.body,
username,
password: hashedPassword,
email: req.body.email,
fullName: req.body.fullName,
role: req.body.role || "user",
});
// Log this activity
await storage.createActivityLog({
action: 'Create',
resource: user.username,
resourceType: 'User',
userId: null,
username: 'System',
status: 'Success',
details: { id: user.id }
});
// Remove password from response
const userResponse = { ...user, password: undefined };
req.login(user, (err) => {
if (err) return next(err);
// Don't send password back
const { password, ...userWithoutPassword } = user;
res.status(201).json(userWithoutPassword);
res.status(201).json(userResponse);
});
} catch (error) {
next(error);
}
});
app.post("/api/login", passport.authenticate("local"), (req, res) => {
// Don't send password back
const { password, ...userWithoutPassword } = req.user as SelectUser;
res.status(200).json(userWithoutPassword);
// Login endpoint
app.post("/api/login", (req, res, next) => {
passport.authenticate("local", (err, user, info) => {
if (err) return next(err);
if (!user) {
return res.status(401).json({ message: info?.message || "Authentication failed" });
}
req.login(user, (loginErr) => {
if (loginErr) return next(loginErr);
// Remove password from response
const userResponse = { ...user, password: undefined };
res.json(userResponse);
});
})(req, res, next);
});
// Logout endpoint
app.post("/api/logout", (req, res, next) => {
req.logout((err) => {
if (err) return next(err);
res.sendStatus(200);
req.session.destroy((sessionErr) => {
if (sessionErr) return next(sessionErr);
res.clearCookie("connect.sid");
res.status(200).json({ message: "Logged out successfully" });
});
});
});
// Get current user endpoint
app.get("/api/user", (req, res) => {
if (!req.isAuthenticated()) return res.sendStatus(401);
// Don't send password back
const { password, ...userWithoutPassword } = req.user as SelectUser;
res.json(userWithoutPassword);
if (!req.isAuthenticated()) {
return res.status(401).json({ message: "Not authenticated" });
}
// Remove password from response
const userResponse = { ...req.user, password: undefined };
res.json(userResponse);
});
// Middleware to verify API token
const verifyApiToken = async (req: any, res: any, next: any) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ message: 'API token is required' });
// Generate API token endpoint
app.post("/api/tokens", (req, res, next) => {
if (!req.isAuthenticated()) {
return res.status(401).json({ message: "Not authenticated" });
}
try {
// Check if token exists in storage
const apiToken = await storage.getApiTokenByToken(token);
if (!apiToken) {
return res.status(401).json({ message: 'Invalid API token' });
}
// Check if token is expired
if (apiToken.expiresAt && new Date(apiToken.expiresAt) < new Date()) {
return res.status(401).json({ message: 'API token has expired' });
}
// Update the last used timestamp
await storage.updateApiToken(apiToken.id, { lastUsedAt: new Date() });
// Get the user associated with this token
const user = await storage.getUser(apiToken.userId);
if (!user) {
return res.status(401).json({ message: 'User associated with token not found' });
}
// Set the user in the request
req.user = user;
next();
} catch (error) {
return res.status(500).json({ message: 'Error verifying API token' });
}
};
return { verifyApiToken };
try {
const { name, expiresAt, permissions } = req.body;
if (!name) {
return res.status(400).json({ message: "Token name is required" });
}
const token = jwt.sign(
{
sub: req.user.id,
permissions
},
JWT_SECRET,
{ expiresAt: expiresAt ? new Date(expiresAt) : undefined }
);
const apiToken = storage.createApiToken({
name,
token,
userId: req.user.id,
permissions: permissions || {},
expiresAt: expiresAt ? new Date(expiresAt) : null,
});
res.status(201).json(apiToken);
} catch (error) {
next(error);
}
});
// Middleware to check API token authentication
const authenticateApiToken = passport.authenticate("jwt", { session: false });
return { authenticateApiToken };
}
+816 -5
View File
@@ -1,13 +1,824 @@
import type { Express } from "express";
import type { Express, Request, Response, NextFunction } from "express";
import { createServer, type Server } from "http";
import { setupAuth } from "./auth";
import { setupSwagger } from "./swagger";
import { storage } from "./storage";
import { apiQuerySchema } from "@shared/schema";
import { ZodError } from "zod";
export async function registerRoutes(app: Express): Promise<Server> {
// put application routes here
// prefix all routes with /api
// Setup authentication
const { authenticateApiToken } = setupAuth(app);
// use storage to perform CRUD operations on the storage interface
// e.g. storage.insertUser(user) or storage.getUserByUsername(username)
// Setup Swagger documentation
setupSwagger(app);
// Error handler for Zod validation errors
const handleZodError = (err: ZodError, res: Response) => {
return res.status(400).json({
message: "Validation error",
errors: err.errors,
});
};
// Parse query parameters
const parseQueryParams = (req: Request) => {
try {
return apiQuerySchema.parse(req.query);
} catch (err) {
if (err instanceof ZodError) {
return null;
}
throw err;
}
};
// Middleware to check admin role
const requireAdmin = (req: Request, res: Response, next: NextFunction) => {
if (!req.isAuthenticated() || req.user.role !== "admin") {
return res.status(403).json({ message: "Access denied: Admin role required" });
}
next();
};
/**
* @swagger
* /api/ldap-connections:
* get:
* summary: List all LDAP connections
* tags: [LDAP Connections]
* security:
* - cookieAuth: []
* responses:
* 200:
* description: A list of LDAP connections
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/LdapConnection'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
app.get("/api/ldap-connections", async (req, res, next) => {
try {
if (!req.isAuthenticated()) {
return res.status(401).json({ message: "Not authenticated" });
}
const connections = await storage.listLdapConnections();
// Hide sensitive fields like password
const safeConnections = connections.map(conn => {
const { password, ...safeConn } = conn;
return safeConn;
});
res.json(safeConnections);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/ldap-connections:
* post:
* summary: Create a new LDAP connection
* tags: [LDAP Connections]
* security:
* - cookieAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - server
* - domain
* - username
* - password
* properties:
* name:
* type: string
* server:
* type: string
* domain:
* type: string
* port:
* type: integer
* default: 389
* useSSL:
* type: boolean
* default: true
* username:
* type: string
* password:
* type: string
* responses:
* 201:
* description: Connection created successfully
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/LdapConnection'
* 400:
* $ref: '#/components/responses/BadRequestError'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
app.post("/api/ldap-connections", requireAdmin, async (req, res, next) => {
try {
const connection = await storage.createLdapConnection(req.body);
// Hide password in response
const { password, ...safeConn } = connection;
res.status(201).json(safeConn);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/ldap-connections/{id}:
* get:
* summary: Get a specific LDAP connection
* tags: [LDAP Connections]
* security:
* - cookieAuth: []
* parameters:
* - name: id
* in: path
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: LDAP connection details
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/LdapConnection'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.get("/api/ldap-connections/:id", async (req, res, next) => {
try {
if (!req.isAuthenticated()) {
return res.status(401).json({ message: "Not authenticated" });
}
const connection = await storage.getLdapConnection(parseInt(req.params.id));
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
// Hide password in response
const { password, ...safeConn } = connection;
res.json(safeConn);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/ldap-connections/{id}:
* put:
* summary: Update a LDAP connection
* tags: [LDAP Connections]
* security:
* - cookieAuth: []
* parameters:
* - name: id
* in: path
* required: true
* schema:
* type: integer
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* server:
* type: string
* domain:
* type: string
* port:
* type: integer
* useSSL:
* type: boolean
* username:
* type: string
* password:
* type: string
* responses:
* 200:
* description: Connection updated successfully
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/LdapConnection'
* 400:
* $ref: '#/components/responses/BadRequestError'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.put("/api/ldap-connections/:id", requireAdmin, async (req, res, next) => {
try {
const updatedConnection = await storage.updateLdapConnection(parseInt(req.params.id), req.body);
if (!updatedConnection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
// Hide password in response
const { password, ...safeConn } = updatedConnection;
res.json(safeConn);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/ldap-connections/{id}:
* delete:
* summary: Delete a LDAP connection
* tags: [LDAP Connections]
* security:
* - cookieAuth: []
* parameters:
* - name: id
* in: path
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Connection deleted successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.delete("/api/ldap-connections/:id", requireAdmin, async (req, res, next) => {
try {
const deleted = await storage.deleteLdapConnection(parseInt(req.params.id));
if (!deleted) {
return res.status(404).json({ message: "LDAP connection not found" });
}
res.json({ success: true });
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/tokens:
* get:
* summary: List all API tokens for current user
* tags: [API Tokens]
* security:
* - cookieAuth: []
* responses:
* 200:
* description: A list of API tokens
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/ApiToken'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
app.get("/api/tokens", async (req, res, next) => {
try {
if (!req.isAuthenticated()) {
return res.status(401).json({ message: "Not authenticated" });
}
const tokens = await storage.listApiTokensByUserId(req.user.id);
res.json(tokens);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/tokens/{id}:
* delete:
* summary: Delete an API token
* tags: [API Tokens]
* security:
* - cookieAuth: []
* parameters:
* - name: id
* in: path
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Token deleted successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.delete("/api/tokens/:id", async (req, res, next) => {
try {
if (!req.isAuthenticated()) {
return res.status(401).json({ message: "Not authenticated" });
}
const token = await storage.getApiToken(parseInt(req.params.id));
if (!token) {
return res.status(404).json({ message: "Token not found" });
}
// Only allow users to delete their own tokens unless they're admin
if (token.userId !== req.user.id && req.user.role !== "admin") {
return res.status(403).json({ message: "Forbidden: You cannot delete tokens that don't belong to you" });
}
const deleted = await storage.deleteApiToken(parseInt(req.params.id));
if (!deleted) {
return res.status(404).json({ message: "Token not found" });
}
res.json({ success: true });
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/users:
* get:
* summary: List all users (admin only)
* tags: [Users]
* security:
* - cookieAuth: []
* responses:
* 200:
* description: A list of users
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/User'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* description: Forbidden - admin access required
*/
app.get("/api/users", requireAdmin, async (req, res, next) => {
try {
const users = await storage.listUsers();
// Remove passwords from response
const safeUsers = users.map(user => {
const { password, ...safeUser } = user;
return safeUser;
});
res.json(safeUsers);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/connections/{connectionId}/ad-users:
* get:
* summary: List AD users from the specified LDAP connection
* tags: [AD Users]
* security:
* - bearerAuth: []
* - cookieAuth: []
* parameters:
* - name: connectionId
* in: path
* required: true
* schema:
* type: integer
* - $ref: '#/components/parameters/filterParam'
* - $ref: '#/components/parameters/selectParam'
* - $ref: '#/components/parameters/expandParam'
* - $ref: '#/components/parameters/orderByParam'
* - $ref: '#/components/parameters/topParam'
* - $ref: '#/components/parameters/skipParam'
* responses:
* 200:
* description: A list of AD users
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/AdUser'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
app.get("/api/connections/:connectionId/ad-users", async (req, res, next) => {
try {
if (!req.isAuthenticated() && !req.headers.authorization) {
return res.status(401).json({ message: "Authentication required" });
}
const connectionId = parseInt(req.params.connectionId);
const connection = await storage.getLdapConnection(connectionId);
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
const query = parseQueryParams(req);
const users = await storage.listAdUsers(connectionId, query);
res.json(users);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/connections/{connectionId}/ad-users:
* post:
* summary: Create a new AD user
* tags: [AD Users]
* security:
* - bearerAuth: []
* - cookieAuth: []
* parameters:
* - name: connectionId
* in: path
* required: true
* schema:
* type: integer
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - distinguishedName
* - sAMAccountName
* properties:
* distinguishedName:
* type: string
* sAMAccountName:
* type: string
* userPrincipalName:
* type: string
* givenName:
* type: string
* surname:
* type: string
* displayName:
* type: string
* email:
* type: string
* enabled:
* type: boolean
* adProperties:
* type: object
* responses:
* 201:
* description: User created successfully
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/AdUser'
* 400:
* $ref: '#/components/responses/BadRequestError'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
app.post("/api/connections/:connectionId/ad-users", authenticateApiToken, async (req, res, next) => {
try {
const connectionId = parseInt(req.params.connectionId);
const connection = await storage.getLdapConnection(connectionId);
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
const userData = { ...req.body, connectionId };
const user = await storage.createAdUser(userData);
res.status(201).json(user);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/connections/{connectionId}/ad-users/{id}:
* get:
* summary: Get a specific AD user
* tags: [AD Users]
* security:
* - bearerAuth: []
* - cookieAuth: []
* parameters:
* - name: connectionId
* in: path
* required: true
* schema:
* type: integer
* - name: id
* in: path
* required: true
* schema:
* type: integer
* - $ref: '#/components/parameters/selectParam'
* responses:
* 200:
* description: AD user details
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/AdUser'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.get("/api/connections/:connectionId/ad-users/:id", async (req, res, next) => {
try {
if (!req.isAuthenticated() && !req.headers.authorization) {
return res.status(401).json({ message: "Authentication required" });
}
const user = await storage.getAdUser(parseInt(req.params.id));
if (!user || user.connectionId !== parseInt(req.params.connectionId)) {
return res.status(404).json({ message: "AD user not found" });
}
// Apply property selection if specified
let result = user;
if (req.query.select) {
const properties = (req.query.select as string).split(',');
const selectedUser: any = { id: user.id };
properties.forEach(prop => {
if ((user as any)[prop] !== undefined) {
selectedUser[prop] = (user as any)[prop];
}
});
result = selectedUser as typeof user;
}
res.json(result);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/connections/{connectionId}/ad-users/{id}:
* put:
* summary: Update an AD user
* tags: [AD Users]
* security:
* - bearerAuth: []
* - cookieAuth: []
* parameters:
* - name: connectionId
* in: path
* required: true
* schema:
* type: integer
* - name: id
* in: path
* required: true
* schema:
* type: integer
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* distinguishedName:
* type: string
* sAMAccountName:
* type: string
* userPrincipalName:
* type: string
* givenName:
* type: string
* surname:
* type: string
* displayName:
* type: string
* email:
* type: string
* enabled:
* type: boolean
* adProperties:
* type: object
* responses:
* 200:
* description: User updated successfully
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/AdUser'
* 400:
* $ref: '#/components/responses/BadRequestError'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.put("/api/connections/:connectionId/ad-users/:id", authenticateApiToken, async (req, res, next) => {
try {
const user = await storage.getAdUser(parseInt(req.params.id));
if (!user || user.connectionId !== parseInt(req.params.connectionId)) {
return res.status(404).json({ message: "AD user not found" });
}
const updatedUser = await storage.updateAdUser(parseInt(req.params.id), req.body);
res.json(updatedUser);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/connections/{connectionId}/ad-users/{id}:
* delete:
* summary: Delete an AD user
* tags: [AD Users]
* security:
* - bearerAuth: []
* - cookieAuth: []
* parameters:
* - name: connectionId
* in: path
* required: true
* schema:
* type: integer
* - name: id
* in: path
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: User deleted successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.delete("/api/connections/:connectionId/ad-users/:id", authenticateApiToken, async (req, res, next) => {
try {
const user = await storage.getAdUser(parseInt(req.params.id));
if (!user || user.connectionId !== parseInt(req.params.connectionId)) {
return res.status(404).json({ message: "AD user not found" });
}
const deleted = await storage.deleteAdUser(parseInt(req.params.id));
if (!deleted) {
return res.status(404).json({ message: "AD user not found" });
}
res.json({ success: true });
} catch (error) {
next(error);
}
});
// Similar endpoints for AD Groups
app.get("/api/connections/:connectionId/ad-groups", async (req, res, next) => {
try {
if (!req.isAuthenticated() && !req.headers.authorization) {
return res.status(401).json({ message: "Authentication required" });
}
const connectionId = parseInt(req.params.connectionId);
const connection = await storage.getLdapConnection(connectionId);
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
const query = parseQueryParams(req);
const groups = await storage.listAdGroups(connectionId, query);
res.json(groups);
} catch (error) {
next(error);
}
});
// Organizational Units endpoints
app.get("/api/connections/:connectionId/ad-org-units", async (req, res, next) => {
try {
if (!req.isAuthenticated() && !req.headers.authorization) {
return res.status(401).json({ message: "Authentication required" });
}
const connectionId = parseInt(req.params.connectionId);
const connection = await storage.getLdapConnection(connectionId);
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
const query = parseQueryParams(req);
const orgUnits = await storage.listAdOrgUnits(connectionId, query);
res.json(orgUnits);
} catch (error) {
next(error);
}
});
// Computers endpoints
app.get("/api/connections/:connectionId/ad-computers", async (req, res, next) => {
try {
if (!req.isAuthenticated() && !req.headers.authorization) {
return res.status(401).json({ message: "Authentication required" });
}
const connectionId = parseInt(req.params.connectionId);
const connection = await storage.getLdapConnection(connectionId);
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
const query = parseQueryParams(req);
const computers = await storage.listAdComputers(connectionId, query);
res.json(computers);
} catch (error) {
next(error);
}
});
// Domains endpoints
app.get("/api/connections/:connectionId/ad-domains", async (req, res, next) => {
try {
if (!req.isAuthenticated() && !req.headers.authorization) {
return res.status(401).json({ message: "Authentication required" });
}
const connectionId = parseInt(req.params.connectionId);
const connection = await storage.getLdapConnection(connectionId);
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
const query = parseQueryParams(req);
const domains = await storage.listAdDomains(connectionId, query);
res.json(domains);
} catch (error) {
next(error);
}
});
const httpServer = createServer(app);
+371 -99
View File
@@ -1,44 +1,76 @@
import { users, apiTokens, ldapConnections, activityLogs } from "@shared/schema";
import type {
User, InsertUser,
ApiToken, InsertApiToken,
import {
User, InsertUser, ApiToken, InsertApiToken,
LdapConnection, InsertLdapConnection,
ActivityLog, InsertActivityLog
AdUser, InsertAdUser, AdGroup, InsertAdGroup,
AdOrgUnit, InsertAdOrgUnit, AdComputer, InsertAdComputer,
AdDomain, InsertAdDomain
} from "@shared/schema";
import session from "express-session";
import createMemoryStore from "memorystore";
import crypto from "crypto";
// Memory store for sessions
const MemoryStore = createMemoryStore(session);
export interface IStorage {
// Users
// User management
getUser(id: number): Promise<User | undefined>;
getUserByUsername(username: string): Promise<User | undefined>;
createUser(user: InsertUser): Promise<User>;
updateUser(id: number, user: Partial<User>): Promise<User | undefined>;
deleteUser(id: number): Promise<boolean>;
listUsers(filters?: any): Promise<User[]>;
listUsers(): Promise<User[]>;
// API Tokens
// API Token management
getApiToken(id: number): Promise<ApiToken | undefined>;
getApiTokenByToken(token: string): Promise<ApiToken | undefined>;
createApiToken(token: InsertApiToken & { token: string }): Promise<ApiToken>;
updateApiToken(id: number, token: Partial<ApiToken>): Promise<ApiToken | undefined>;
createApiToken(token: InsertApiToken): Promise<ApiToken>;
deleteApiToken(id: number): Promise<boolean>;
listApiTokens(userId?: number): Promise<ApiToken[]>;
listApiTokensByUserId(userId: number): Promise<ApiToken[]>;
// LDAP Connections
// LDAP Connection management
getLdapConnection(id: number): Promise<LdapConnection | undefined>;
createLdapConnection(connection: InsertLdapConnection): Promise<LdapConnection>;
updateLdapConnection(id: number, connection: Partial<LdapConnection>): Promise<LdapConnection | undefined>;
deleteLdapConnection(id: number): Promise<boolean>;
listLdapConnections(): Promise<LdapConnection[]>;
// Activity Logs
createActivityLog(log: InsertActivityLog): Promise<ActivityLog>;
listActivityLogs(limit?: number): Promise<ActivityLog[]>;
// AD Users
getAdUser(id: number): Promise<AdUser | undefined>;
createAdUser(user: InsertAdUser): Promise<AdUser>;
updateAdUser(id: number, user: Partial<AdUser>): Promise<AdUser | undefined>;
deleteAdUser(id: number): Promise<boolean>;
listAdUsers(connectionId: number, query?: any): Promise<AdUser[]>;
// Session Store
// AD Groups
getAdGroup(id: number): Promise<AdGroup | undefined>;
createAdGroup(group: InsertAdGroup): Promise<AdGroup>;
updateAdGroup(id: number, group: Partial<AdGroup>): Promise<AdGroup | undefined>;
deleteAdGroup(id: number): Promise<boolean>;
listAdGroups(connectionId: number, query?: any): Promise<AdGroup[]>;
// AD Organizational Units
getAdOrgUnit(id: number): Promise<AdOrgUnit | undefined>;
createAdOrgUnit(ou: InsertAdOrgUnit): Promise<AdOrgUnit>;
updateAdOrgUnit(id: number, ou: Partial<AdOrgUnit>): Promise<AdOrgUnit | undefined>;
deleteAdOrgUnit(id: number): Promise<boolean>;
listAdOrgUnits(connectionId: number, query?: any): Promise<AdOrgUnit[]>;
// AD Computers
getAdComputer(id: number): Promise<AdComputer | undefined>;
createAdComputer(computer: InsertAdComputer): Promise<AdComputer>;
updateAdComputer(id: number, computer: Partial<AdComputer>): Promise<AdComputer | undefined>;
deleteAdComputer(id: number): Promise<boolean>;
listAdComputers(connectionId: number, query?: any): Promise<AdComputer[]>;
// AD Domains
getAdDomain(id: number): Promise<AdDomain | undefined>;
createAdDomain(domain: InsertAdDomain): Promise<AdDomain>;
updateAdDomain(id: number, domain: Partial<AdDomain>): Promise<AdDomain | undefined>;
deleteAdDomain(id: number): Promise<boolean>;
listAdDomains(connectionId: number, query?: any): Promise<AdDomain[]>;
// Session store
sessionStore: session.SessionStore;
}
@@ -46,31 +78,47 @@ export class MemStorage implements IStorage {
private users: Map<number, User>;
private apiTokens: Map<number, ApiToken>;
private ldapConnections: Map<number, LdapConnection>;
private activityLogs: ActivityLog[];
currentUserId: number;
currentTokenId: number;
currentConnectionId: number;
currentLogId: number;
private adUsers: Map<number, AdUser>;
private adGroups: Map<number, AdGroup>;
private adOrgUnits: Map<number, AdOrgUnit>;
private adComputers: Map<number, AdComputer>;
private adDomains: Map<number, AdDomain>;
sessionStore: session.SessionStore;
private userCurrentId: number;
private tokenCurrentId: number;
private connectionCurrentId: number;
private adUserCurrentId: number;
private adGroupCurrentId: number;
private adOrgUnitCurrentId: number;
private adComputerCurrentId: number;
private adDomainCurrentId: number;
constructor() {
this.users = new Map();
this.apiTokens = new Map();
this.ldapConnections = new Map();
this.activityLogs = [];
this.currentUserId = 1;
this.currentTokenId = 1;
this.currentConnectionId = 1;
this.currentLogId = 1;
this.adUsers = new Map();
this.adGroups = new Map();
this.adOrgUnits = new Map();
this.adComputers = new Map();
this.adDomains = new Map();
this.userCurrentId = 1;
this.tokenCurrentId = 1;
this.connectionCurrentId = 1;
this.adUserCurrentId = 1;
this.adGroupCurrentId = 1;
this.adOrgUnitCurrentId = 1;
this.adComputerCurrentId = 1;
this.adDomainCurrentId = 1;
this.sessionStore = new MemoryStore({
checkPeriod: 86400000, // 24 hours
checkPeriod: 86400000 // 24h
});
}
// Users
// User management
async getUser(id: number): Promise<User | undefined> {
return this.users.get(id);
}
@@ -82,18 +130,23 @@ export class MemStorage implements IStorage {
}
async createUser(insertUser: InsertUser): Promise<User> {
const id = this.currentUserId++;
const createdAt = new Date();
const user: User = { ...insertUser, id, createdAt };
const id = this.userCurrentId++;
const now = new Date();
const user: User = {
...insertUser,
id,
createdAt: now,
role: insertUser.role || "user"
};
this.users.set(id, user);
return user;
}
async updateUser(id: number, updates: Partial<User>): Promise<User | undefined> {
const user = this.users.get(id);
async updateUser(id: number, userData: Partial<User>): Promise<User | undefined> {
const user = await this.getUser(id);
if (!user) return undefined;
const updatedUser = { ...user, ...updates };
const updatedUser = { ...user, ...userData };
this.users.set(id, updatedUser);
return updatedUser;
}
@@ -102,22 +155,11 @@ export class MemStorage implements IStorage {
return this.users.delete(id);
}
async listUsers(filters?: any): Promise<User[]> {
const users = Array.from(this.users.values());
if (!filters) return users;
// Apply filters if provided
return users.filter(user => {
for (const [key, value] of Object.entries(filters)) {
if (user[key as keyof User] !== value) {
return false;
}
}
return true;
});
async listUsers(): Promise<User[]> {
return Array.from(this.users.values());
}
// API Tokens
// API Token management
async getApiToken(id: number): Promise<ApiToken | undefined> {
return this.apiTokens.get(id);
}
@@ -128,61 +170,48 @@ export class MemStorage implements IStorage {
);
}
async createApiToken(tokenData: InsertApiToken & { token: string }): Promise<ApiToken> {
const id = this.currentTokenId++;
const createdAt = new Date();
const apiToken: ApiToken = {
...tokenData,
id,
createdAt,
lastUsedAt: null
};
this.apiTokens.set(id, apiToken);
return apiToken;
}
async updateApiToken(id: number, updates: Partial<ApiToken>): Promise<ApiToken | undefined> {
const token = this.apiTokens.get(id);
if (!token) return undefined;
const updatedToken = { ...token, ...updates };
this.apiTokens.set(id, updatedToken);
return updatedToken;
async createApiToken(insertToken: InsertApiToken): Promise<ApiToken> {
const id = this.tokenCurrentId++;
const now = new Date();
const token: ApiToken = { ...insertToken, id, createdAt: now };
this.apiTokens.set(id, token);
return token;
}
async deleteApiToken(id: number): Promise<boolean> {
return this.apiTokens.delete(id);
}
async listApiTokens(userId?: number): Promise<ApiToken[]> {
const tokens = Array.from(this.apiTokens.values());
if (userId === undefined) return tokens;
return tokens.filter(token => token.userId === userId);
async listApiTokensByUserId(userId: number): Promise<ApiToken[]> {
return Array.from(this.apiTokens.values()).filter(
(token) => token.userId === userId,
);
}
// LDAP Connections
// LDAP Connection management
async getLdapConnection(id: number): Promise<LdapConnection | undefined> {
return this.ldapConnections.get(id);
}
async createLdapConnection(connectionData: InsertLdapConnection): Promise<LdapConnection> {
const id = this.currentConnectionId++;
const ldapConnection: LdapConnection = {
...connectionData,
async createLdapConnection(insertConnection: InsertLdapConnection): Promise<LdapConnection> {
const id = this.connectionCurrentId++;
const now = new Date();
const connection: LdapConnection = {
...insertConnection,
id,
status: "disconnected",
lastConnected: null
createdAt: now,
lastConnected: null,
status: "disconnected"
};
this.ldapConnections.set(id, ldapConnection);
return ldapConnection;
this.ldapConnections.set(id, connection);
return connection;
}
async updateLdapConnection(id: number, updates: Partial<LdapConnection>): Promise<LdapConnection | undefined> {
const connection = this.ldapConnections.get(id);
async updateLdapConnection(id: number, connectionData: Partial<LdapConnection>): Promise<LdapConnection | undefined> {
const connection = await this.getLdapConnection(id);
if (!connection) return undefined;
const updatedConnection = { ...connection, ...updates };
const updatedConnection = { ...connection, ...connectionData };
this.ldapConnections.set(id, updatedConnection);
return updatedConnection;
}
@@ -195,20 +224,263 @@ export class MemStorage implements IStorage {
return Array.from(this.ldapConnections.values());
}
// Activity Logs
async createActivityLog(logData: InsertActivityLog): Promise<ActivityLog> {
const id = this.currentLogId++;
const timestamp = new Date();
const log: ActivityLog = { ...logData, id, timestamp };
this.activityLogs.push(log);
return log;
// AD Users
async getAdUser(id: number): Promise<AdUser | undefined> {
return this.adUsers.get(id);
}
async listActivityLogs(limit = 10): Promise<ActivityLog[]> {
// Sort by timestamp descending (newest first)
return [...this.activityLogs]
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
.slice(0, limit);
async createAdUser(user: InsertAdUser): Promise<AdUser> {
const id = this.adUserCurrentId++;
const adUser: AdUser = { ...user, id };
this.adUsers.set(id, adUser);
return adUser;
}
async updateAdUser(id: number, userData: Partial<AdUser>): Promise<AdUser | undefined> {
const user = await this.getAdUser(id);
if (!user) return undefined;
const updatedUser = { ...user, ...userData };
this.adUsers.set(id, updatedUser);
return updatedUser;
}
async deleteAdUser(id: number): Promise<boolean> {
return this.adUsers.delete(id);
}
async listAdUsers(connectionId: number, query?: any): Promise<AdUser[]> {
let users = Array.from(this.adUsers.values()).filter(
(user) => user.connectionId === connectionId,
);
// Apply filtering logic based on query
if (query) {
if (query.filter) {
// Simple filter implementation - can be expanded
const filterParts = query.filter.split(' ');
if (filterParts.length === 3) {
const [property, operator, value] = filterParts;
const unquotedValue = value.replace(/^'|'$/g, '');
if (operator === 'eq') {
users = users.filter(user => (user as any)[property] === unquotedValue);
}
}
}
// Apply property selection
if (query.select) {
const properties = query.select.split(',');
users = users.map(user => {
const result: any = { id: user.id };
properties.forEach(prop => {
if ((user as any)[prop] !== undefined) {
result[prop] = (user as any)[prop];
}
});
return result as AdUser;
});
}
}
return users;
}
// AD Groups
async getAdGroup(id: number): Promise<AdGroup | undefined> {
return this.adGroups.get(id);
}
async createAdGroup(group: InsertAdGroup): Promise<AdGroup> {
const id = this.adGroupCurrentId++;
const adGroup: AdGroup = { ...group, id };
this.adGroups.set(id, adGroup);
return adGroup;
}
async updateAdGroup(id: number, groupData: Partial<AdGroup>): Promise<AdGroup | undefined> {
const group = await this.getAdGroup(id);
if (!group) return undefined;
const updatedGroup = { ...group, ...groupData };
this.adGroups.set(id, updatedGroup);
return updatedGroup;
}
async deleteAdGroup(id: number): Promise<boolean> {
return this.adGroups.delete(id);
}
async listAdGroups(connectionId: number, query?: any): Promise<AdGroup[]> {
let groups = Array.from(this.adGroups.values()).filter(
(group) => group.connectionId === connectionId,
);
// Apply filtering logic
if (query) {
if (query.select) {
const properties = query.select.split(',');
groups = groups.map(group => {
const result: any = { id: group.id };
properties.forEach(prop => {
if ((group as any)[prop] !== undefined) {
result[prop] = (group as any)[prop];
}
});
return result as AdGroup;
});
}
}
return groups;
}
// AD Organizational Units
async getAdOrgUnit(id: number): Promise<AdOrgUnit | undefined> {
return this.adOrgUnits.get(id);
}
async createAdOrgUnit(ou: InsertAdOrgUnit): Promise<AdOrgUnit> {
const id = this.adOrgUnitCurrentId++;
const adOrgUnit: AdOrgUnit = { ...ou, id };
this.adOrgUnits.set(id, adOrgUnit);
return adOrgUnit;
}
async updateAdOrgUnit(id: number, ouData: Partial<AdOrgUnit>): Promise<AdOrgUnit | undefined> {
const ou = await this.getAdOrgUnit(id);
if (!ou) return undefined;
const updatedOu = { ...ou, ...ouData };
this.adOrgUnits.set(id, updatedOu);
return updatedOu;
}
async deleteAdOrgUnit(id: number): Promise<boolean> {
return this.adOrgUnits.delete(id);
}
async listAdOrgUnits(connectionId: number, query?: any): Promise<AdOrgUnit[]> {
let orgUnits = Array.from(this.adOrgUnits.values()).filter(
(ou) => ou.connectionId === connectionId,
);
// Apply filtering logic
if (query) {
if (query.select) {
const properties = query.select.split(',');
orgUnits = orgUnits.map(ou => {
const result: any = { id: ou.id };
properties.forEach(prop => {
if ((ou as any)[prop] !== undefined) {
result[prop] = (ou as any)[prop];
}
});
return result as AdOrgUnit;
});
}
}
return orgUnits;
}
// AD Computers
async getAdComputer(id: number): Promise<AdComputer | undefined> {
return this.adComputers.get(id);
}
async createAdComputer(computer: InsertAdComputer): Promise<AdComputer> {
const id = this.adComputerCurrentId++;
const adComputer: AdComputer = { ...computer, id };
this.adComputers.set(id, adComputer);
return adComputer;
}
async updateAdComputer(id: number, computerData: Partial<AdComputer>): Promise<AdComputer | undefined> {
const computer = await this.getAdComputer(id);
if (!computer) return undefined;
const updatedComputer = { ...computer, ...computerData };
this.adComputers.set(id, updatedComputer);
return updatedComputer;
}
async deleteAdComputer(id: number): Promise<boolean> {
return this.adComputers.delete(id);
}
async listAdComputers(connectionId: number, query?: any): Promise<AdComputer[]> {
let computers = Array.from(this.adComputers.values()).filter(
(computer) => computer.connectionId === connectionId,
);
// Apply filtering logic
if (query) {
if (query.select) {
const properties = query.select.split(',');
computers = computers.map(computer => {
const result: any = { id: computer.id };
properties.forEach(prop => {
if ((computer as any)[prop] !== undefined) {
result[prop] = (computer as any)[prop];
}
});
return result as AdComputer;
});
}
}
return computers;
}
// AD Domains
async getAdDomain(id: number): Promise<AdDomain | undefined> {
return this.adDomains.get(id);
}
async createAdDomain(domain: InsertAdDomain): Promise<AdDomain> {
const id = this.adDomainCurrentId++;
const adDomain: AdDomain = { ...domain, id };
this.adDomains.set(id, adDomain);
return adDomain;
}
async updateAdDomain(id: number, domainData: Partial<AdDomain>): Promise<AdDomain | undefined> {
const domain = await this.getAdDomain(id);
if (!domain) return undefined;
const updatedDomain = { ...domain, ...domainData };
this.adDomains.set(id, updatedDomain);
return updatedDomain;
}
async deleteAdDomain(id: number): Promise<boolean> {
return this.adDomains.delete(id);
}
async listAdDomains(connectionId: number, query?: any): Promise<AdDomain[]> {
let domains = Array.from(this.adDomains.values()).filter(
(domain) => domain.connectionId === connectionId,
);
// Apply filtering logic
if (query) {
if (query.select) {
const properties = query.select.split(',');
domains = domains.map(domain => {
const result: any = { id: domain.id };
properties.forEach(prop => {
if ((domain as any)[prop] !== undefined) {
result[prop] = (domain as any)[prop];
}
});
return result as AdDomain;
});
}
}
return domains;
}
}
+242 -277
View File
@@ -1,283 +1,248 @@
import swaggerJsdoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';
import { Express } from 'express';
import swaggerJsdoc from "swagger-jsdoc";
import swaggerUi from "swagger-ui-express";
import { Express } from "express";
export const setupSwagger = (app: Express) => {
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'Active Directory Management API',
version: '1.0.0',
description: 'RESTful API for managing Active Directory resources',
contact: {
name: 'API Support',
email: 'support@example.com'
}
// 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",
contact: {
name: "API Support",
email: "support@example.com",
},
servers: [
{
url: '/api',
description: 'API Server'
}
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
},
schemas: {
User: {
type: 'object',
properties: {
id: {
type: 'integer',
description: 'User ID'
},
username: {
type: 'string',
description: 'Username'
},
email: {
type: 'string',
description: 'Email address'
},
isAdmin: {
type: 'boolean',
description: 'Admin status'
},
createdAt: {
type: 'string',
format: 'date-time',
description: 'Creation date'
}
}
},
ApiToken: {
type: 'object',
properties: {
id: {
type: 'integer',
description: 'Token ID'
},
name: {
type: 'string',
description: 'Token name'
},
token: {
type: 'string',
description: 'The actual token'
},
userId: {
type: 'integer',
description: 'User ID that owns the token'
},
createdAt: {
type: 'string',
format: 'date-time',
description: 'Creation date'
},
lastUsedAt: {
type: 'string',
format: 'date-time',
description: 'Last used date'
},
expiresAt: {
type: 'string',
format: 'date-time',
description: 'Expiration date'
}
}
},
LdapConnection: {
type: 'object',
properties: {
id: {
type: 'integer',
description: 'Connection ID'
},
name: {
type: 'string',
description: 'Connection name'
},
server: {
type: 'string',
description: 'Server hostname/IP'
},
port: {
type: 'integer',
description: 'Server port'
},
authType: {
type: 'string',
description: 'Authentication type'
},
username: {
type: 'string',
description: 'Username'
},
baseDN: {
type: 'string',
description: 'Base Distinguished Name'
},
useTLS: {
type: 'boolean',
description: 'Use TLS/SSL'
},
status: {
type: 'string',
description: 'Connection status'
},
lastConnected: {
type: 'string',
format: 'date-time',
description: 'Last connected timestamp'
}
}
},
ActivityLog: {
type: 'object',
properties: {
id: {
type: 'integer',
description: 'Log ID'
},
action: {
type: 'string',
description: 'Action performed'
},
resource: {
type: 'string',
description: 'Resource name'
},
resourceType: {
type: 'string',
description: 'Resource type'
},
userId: {
type: 'integer',
description: 'User ID'
},
username: {
type: 'string',
description: 'Username'
},
status: {
type: 'string',
description: 'Status'
},
details: {
type: 'object',
description: 'Additional details'
},
timestamp: {
type: 'string',
format: 'date-time',
description: 'Timestamp'
}
}
},
LdapUser: {
type: 'object',
properties: {
cn: {
type: 'string',
description: 'Common Name'
},
sAMAccountName: {
type: 'string',
description: 'SAM Account Name'
},
mail: {
type: 'string',
description: 'Email address'
},
distinguishedName: {
type: 'string',
description: 'Distinguished Name'
}
}
},
LdapGroup: {
type: 'object',
properties: {
cn: {
type: 'string',
description: 'Common Name'
},
distinguishedName: {
type: 'string',
description: 'Distinguished Name'
},
member: {
type: 'array',
items: {
type: 'string'
},
description: 'Group members'
}
}
},
LdapOU: {
type: 'object',
properties: {
ou: {
type: 'string',
description: 'Organizational Unit name'
},
distinguishedName: {
type: 'string',
description: 'Distinguished Name'
}
}
},
LdapComputer: {
type: 'object',
properties: {
cn: {
type: 'string',
description: 'Computer name'
},
distinguishedName: {
type: 'string',
description: 'Distinguished Name'
},
operatingSystem: {
type: 'string',
description: 'Operating System'
}
}
},
Error: {
type: 'object',
properties: {
message: {
type: 'string',
description: 'Error message'
}
}
}
}
},
security: [
{
bearerAuth: []
}
]
},
apis: ['./server/api.ts']
};
servers: [
{
url: "/api",
description: "API base URL",
},
],
components: {
securitySchemes: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
},
cookieAuth: {
type: "apiKey",
in: "cookie",
name: "connect.sid",
},
},
schemas: {
User: {
type: "object",
properties: {
id: { type: "integer" },
username: { type: "string" },
email: { type: "string" },
fullName: { type: "string" },
role: { type: "string" },
createdAt: { type: "string", format: "date-time" },
},
},
ApiToken: {
type: "object",
properties: {
id: { type: "integer" },
name: { type: "string" },
token: { type: "string" },
userId: { type: "integer" },
permissions: { type: "object" },
expiresAt: { type: "string", format: "date-time" },
createdAt: { type: "string", format: "date-time" },
},
},
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" },
distinguishedName: { 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" } },
adProperties: { type: "object" },
},
},
AdGroup: {
type: "object",
properties: {
id: { type: "integer" },
connectionId: { type: "integer" },
distinguishedName: { type: "string" },
sAMAccountName: { type: "string" },
groupType: { type: "string" },
description: { type: "string" },
members: { type: "array", items: { type: "string" } },
adProperties: { type: "object" },
},
},
AdOrgUnit: {
type: "object",
properties: {
id: { type: "integer" },
connectionId: { type: "integer" },
distinguishedName: { type: "string" },
name: { type: "string" },
description: { type: "string" },
adProperties: { type: "object" },
},
},
AdComputer: {
type: "object",
properties: {
id: { type: "integer" },
connectionId: { type: "integer" },
distinguishedName: { type: "string" },
name: { type: "string" },
dnsHostName: { type: "string" },
operatingSystem: { type: "string" },
operatingSystemVersion: { type: "string" },
lastLogon: { type: "string", format: "date-time" },
enabled: { type: "boolean" },
adProperties: { type: "object" },
},
},
AdDomain: {
type: "object",
properties: {
id: { type: "integer" },
connectionId: { type: "integer" },
distinguishedName: { type: "string" },
name: { type: "string" },
netBIOSName: { type: "string" },
forestName: { type: "string" },
domainFunctionality: { type: "string" },
adProperties: { type: "object" },
},
},
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" },
},
},
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" },
},
},
},
},
},
security: [
{
bearerAuth: [],
},
{
cookieAuth: [],
},
],
},
apis: ["./server/routes.ts"], // Path to the API routes
};
const swaggerSpec = swaggerJsdoc(options);
app.use('/swagger-ui', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
// Serve the OpenAPI spec as JSON
app.get('/swagger.json', (req, res) => {
res.setHeader('Content-Type', 'application/json');
const swaggerSpec = swaggerJsdoc(swaggerOptions);
export function setupSwagger(app: Express) {
app.use("/api/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
app.get("/api/swagger.json", (req, res) => {
res.setHeader("Content-Type", "application/json");
res.send(swaggerSpec);
});
};
}