mirror of
https://github.com/freedbygrace/ActiveDirectoryManager.git
synced 2026-08-21 07:37:32 +00:00
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:
+115
-80
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user