mirror of
https://github.com/freedbygrace/ActiveDirectoryManager.git
synced 2026-08-21 15:47:41 +00:00
Add audit log functionality to track user activity and data changes.
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/3d6885b0-d066-41b0-8042-fa2b629d3709.jpg
This commit is contained in:
@@ -14,6 +14,7 @@ import LdapConnectionsPage from "@/pages/ldap-connections-page";
|
|||||||
import LdapQueryBuilderPage from "@/pages/ldap-query-builder-page";
|
import LdapQueryBuilderPage from "@/pages/ldap-query-builder-page";
|
||||||
import SettingsPage from "@/pages/settings-page";
|
import SettingsPage from "@/pages/settings-page";
|
||||||
import UserManagementPage from "@/pages/user-management-page";
|
import UserManagementPage from "@/pages/user-management-page";
|
||||||
|
import AuditLogsPage from "@/pages/audit-logs-page";
|
||||||
import { ProtectedRoute } from "./lib/protected-route";
|
import { ProtectedRoute } from "./lib/protected-route";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ function Router() {
|
|||||||
<ProtectedRoute path="/api-tokens" component={ApiTokensPage} />
|
<ProtectedRoute path="/api-tokens" component={ApiTokensPage} />
|
||||||
<ProtectedRoute path="/ldap-connections" component={LdapConnectionsPage} />
|
<ProtectedRoute path="/ldap-connections" component={LdapConnectionsPage} />
|
||||||
<ProtectedRoute path="/ldap-query-builder" component={LdapQueryBuilderPage} />
|
<ProtectedRoute path="/ldap-query-builder" component={LdapQueryBuilderPage} />
|
||||||
|
<ProtectedRoute path="/audit-logs" component={AuditLogsPage} />
|
||||||
<ProtectedRoute path="/settings" component={SettingsPage} />
|
<ProtectedRoute path="/settings" component={SettingsPage} />
|
||||||
<ProtectedRoute path="/user-management" component={UserManagementPage} />
|
<ProtectedRoute path="/user-management" component={UserManagementPage} />
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
Server,
|
Server,
|
||||||
ShieldAlert,
|
ShieldAlert,
|
||||||
Filter,
|
Filter,
|
||||||
|
ClipboardList,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useMobile } from "@/hooks/use-mobile";
|
import { useMobile } from "@/hooks/use-mobile";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
@@ -70,6 +71,7 @@ const menuSections: MenuSection[] = [
|
|||||||
{ title: "API Tokens", path: "/api-tokens", icon: <Key className="h-4 w-4" /> },
|
{ title: "API Tokens", path: "/api-tokens", icon: <Key className="h-4 w-4" /> },
|
||||||
{ title: "LDAP Connections", path: "/ldap-connections", icon: <Server className="h-4 w-4" /> },
|
{ title: "LDAP Connections", path: "/ldap-connections", icon: <Server className="h-4 w-4" /> },
|
||||||
{ title: "LDAP Query Builder", path: "/ldap-query-builder", icon: <Filter className="h-4 w-4" /> },
|
{ title: "LDAP Query Builder", path: "/ldap-query-builder", icon: <Filter className="h-4 w-4" /> },
|
||||||
|
{ title: "Audit Logs", path: "/audit-logs", icon: <ClipboardList className="h-4 w-4" /> },
|
||||||
{ title: "Settings", path: "/settings", icon: <Settings className="h-4 w-4" /> },
|
{ title: "Settings", path: "/settings", icon: <Settings className="h-4 w-4" /> },
|
||||||
{ title: "User Management", path: "/user-management", icon: <ShieldAlert className="h-4 w-4" /> },
|
{ title: "User Management", path: "/user-management", icon: <ShieldAlert className="h-4 w-4" /> },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useToast } from "@/hooks/use-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
|
import { Loader2, RefreshCw } from "lucide-react";
|
||||||
|
import DashboardLayout from "@/layouts/dashboard-layout";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
|
||||||
|
// Define the audit log type based on schema
|
||||||
|
interface AuditLog {
|
||||||
|
id: number;
|
||||||
|
timestamp: string;
|
||||||
|
userId: number | null;
|
||||||
|
action: string;
|
||||||
|
targetId: string;
|
||||||
|
details: Record<string, any>;
|
||||||
|
connectionId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the connection type for filtering
|
||||||
|
interface Connection {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AuditLogsPage() {
|
||||||
|
const { toast } = useToast();
|
||||||
|
const [selectedConnection, setSelectedConnection] = useState<string>("all");
|
||||||
|
|
||||||
|
// Fetch LDAP connections for the filter dropdown
|
||||||
|
const { data: connections = [], isLoading: isLoadingConnections } = useQuery<Connection[]>({
|
||||||
|
queryKey: ["/api/connections"],
|
||||||
|
staleTime: 60000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch audit logs with connection filter if needed
|
||||||
|
const {
|
||||||
|
data: auditLogs = [],
|
||||||
|
isLoading: isLoadingLogs,
|
||||||
|
refetch,
|
||||||
|
isRefetching
|
||||||
|
} = useQuery<AuditLog[]>({
|
||||||
|
queryKey: [
|
||||||
|
selectedConnection === "all"
|
||||||
|
? "/api/audit-logs"
|
||||||
|
: `/api/connections/${selectedConnection}/audit-logs`
|
||||||
|
],
|
||||||
|
staleTime: 30000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Format date for display
|
||||||
|
const formatDate = (dateString: string) => {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return new Intl.DateTimeFormat("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit"
|
||||||
|
}).format(date);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format the action for display
|
||||||
|
const formatAction = (action: string) => {
|
||||||
|
return action.replace(/_/g, " ").toLowerCase().replace(/\b\w/g, (c) => c.toUpperCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the connection name from ID
|
||||||
|
const getConnectionName = (connectionId: number) => {
|
||||||
|
const connection = connections.find(c => c.id === connectionId);
|
||||||
|
return connection ? connection.name : `Connection ${connectionId}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the appropriate badge color based on action
|
||||||
|
const getActionColor = (action: string) => {
|
||||||
|
if (action.includes("ADD") || action.includes("CREATE")) return "bg-green-600";
|
||||||
|
if (action.includes("REMOVE") || action.includes("DELETE")) return "bg-red-600";
|
||||||
|
if (action.includes("MOVE") || action.includes("UPDATE")) return "bg-blue-600";
|
||||||
|
return "bg-gray-600";
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardLayout title="Audit Logs">
|
||||||
|
<div className="container mx-auto py-6">
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<h1 className="text-3xl font-bold">Audit Logs</h1>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Label htmlFor="connection-filter">Connection:</Label>
|
||||||
|
<Select
|
||||||
|
value={selectedConnection}
|
||||||
|
onValueChange={setSelectedConnection}
|
||||||
|
disabled={isLoadingConnections}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="connection-filter" className="w-[200px]">
|
||||||
|
<SelectValue placeholder="All Connections" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="all">All Connections</SelectItem>
|
||||||
|
{connections.map(connection => (
|
||||||
|
<SelectItem key={connection.id} value={connection.id.toString()}>
|
||||||
|
{connection.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => refetch()}
|
||||||
|
disabled={isRefetching}
|
||||||
|
>
|
||||||
|
{isRefetching ? (
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<RefreshCw className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Activity Log</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
A detailed record of all actions performed in the system
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{(isLoadingLogs || isRefetching) ? (
|
||||||
|
<div className="flex justify-center py-8">
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||||
|
</div>
|
||||||
|
) : auditLogs.length === 0 ? (
|
||||||
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
|
No audit logs found
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Timestamp</TableHead>
|
||||||
|
<TableHead>Action</TableHead>
|
||||||
|
<TableHead>Target</TableHead>
|
||||||
|
<TableHead>Connection</TableHead>
|
||||||
|
<TableHead>Details</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{auditLogs.map((log) => (
|
||||||
|
<TableRow key={log.id}>
|
||||||
|
<TableCell className="font-mono text-xs">
|
||||||
|
{formatDate(log.timestamp)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Badge className={getActionColor(log.action)}>
|
||||||
|
{formatAction(log.action)}
|
||||||
|
</Badge>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="font-mono text-xs max-w-[200px] truncate">
|
||||||
|
{log.targetId}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{getConnectionName(log.connectionId)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="max-w-[300px]">
|
||||||
|
<pre className="text-xs overflow-x-auto p-2 bg-muted rounded">
|
||||||
|
{JSON.stringify(log.details, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</DashboardLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
+582
-10
@@ -3,7 +3,14 @@ import { createServer, type Server } from "http";
|
|||||||
import { setupAuth } from "./auth";
|
import { setupAuth } from "./auth";
|
||||||
import { setupSwagger } from "./swagger";
|
import { setupSwagger } from "./swagger";
|
||||||
import { storage } from "./storage";
|
import { storage } from "./storage";
|
||||||
import { apiQuerySchema, PERMISSIONS } from "@shared/schema";
|
import {
|
||||||
|
apiQuerySchema,
|
||||||
|
PERMISSIONS,
|
||||||
|
moveComputerSchema,
|
||||||
|
moveUserSchema,
|
||||||
|
addToGroupSchema,
|
||||||
|
removeFromGroupSchema
|
||||||
|
} from "@shared/schema";
|
||||||
import { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
import rateLimit from "express-rate-limit";
|
import rateLimit from "express-rate-limit";
|
||||||
import {
|
import {
|
||||||
@@ -703,7 +710,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /connections/{connectionId}/ad-users/{id}:
|
* /api/connections/{connectionId}/ad-users/{id}:
|
||||||
* put:
|
* put:
|
||||||
* summary: Update an AD user
|
* summary: Update an AD user
|
||||||
* tags: [AD Users]
|
* tags: [AD Users]
|
||||||
@@ -819,7 +826,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /connections/{connectionId}/ad-groups/{id}:
|
* /api/connections/{connectionId}/ad-groups/{id}:
|
||||||
* put:
|
* put:
|
||||||
* summary: Update an AD group
|
* summary: Update an AD group
|
||||||
* tags: [AD Groups]
|
* tags: [AD Groups]
|
||||||
@@ -942,7 +949,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
// Similar endpoints for AD Groups
|
// Similar endpoints for AD Groups
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /connections/{connectionId}/ad-groups:
|
* /api/connections/{connectionId}/ad-groups:
|
||||||
* get:
|
* get:
|
||||||
* summary: List AD groups from the specified LDAP connection
|
* summary: List AD groups from the specified LDAP connection
|
||||||
* tags: [AD Groups]
|
* tags: [AD Groups]
|
||||||
@@ -999,7 +1006,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /connections/{connectionId}/ad-org-units/{id}:
|
* /api/connections/{connectionId}/ad-org-units/{id}:
|
||||||
* put:
|
* put:
|
||||||
* summary: Update an AD organizational unit
|
* summary: Update an AD organizational unit
|
||||||
* tags: [AD Organizational Units]
|
* tags: [AD Organizational Units]
|
||||||
@@ -1114,7 +1121,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
// Organizational Units endpoints
|
// Organizational Units endpoints
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /connections/{connectionId}/ad-org-units:
|
* /api/connections/{connectionId}/ad-org-units:
|
||||||
* get:
|
* get:
|
||||||
* summary: List AD organizational units from the specified LDAP connection
|
* summary: List AD organizational units from the specified LDAP connection
|
||||||
* tags: [AD Organizational Units]
|
* tags: [AD Organizational Units]
|
||||||
@@ -1171,7 +1178,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /connections/{connectionId}/ad-computers/{id}:
|
* /api/connections/{connectionId}/ad-computers/{id}:
|
||||||
* put:
|
* put:
|
||||||
* summary: Update an AD computer
|
* summary: Update an AD computer
|
||||||
* tags: [AD Computers]
|
* tags: [AD Computers]
|
||||||
@@ -1292,7 +1299,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
// Computers endpoints
|
// Computers endpoints
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /connections/{connectionId}/ad-computers:
|
* /api/connections/{connectionId}/ad-computers:
|
||||||
* get:
|
* get:
|
||||||
* summary: List AD computers from the specified LDAP connection
|
* summary: List AD computers from the specified LDAP connection
|
||||||
* tags: [AD Computers]
|
* tags: [AD Computers]
|
||||||
@@ -1349,7 +1356,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /connections/{connectionId}/ad-domains/{id}:
|
* /api/connections/{connectionId}/ad-domains/{id}:
|
||||||
* put:
|
* put:
|
||||||
* summary: Update an AD domain
|
* summary: Update an AD domain
|
||||||
* tags: [AD Domains]
|
* tags: [AD Domains]
|
||||||
@@ -1466,7 +1473,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
// Domains endpoints
|
// Domains endpoints
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /connections/{connectionId}/ad-domains:
|
* /api/connections/{connectionId}/ad-domains:
|
||||||
* get:
|
* get:
|
||||||
* summary: List AD domains from the specified LDAP connection
|
* summary: List AD domains from the specified LDAP connection
|
||||||
* tags: [AD Domains]
|
* tags: [AD Domains]
|
||||||
@@ -2262,6 +2269,571 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/connections/{connectionId}/move-computer:
|
||||||
|
* post:
|
||||||
|
* summary: Move a computer to a different OU
|
||||||
|
* tags: [AD Computers]
|
||||||
|
* security:
|
||||||
|
* - bearerAuth: []
|
||||||
|
* - cookieAuth: []
|
||||||
|
* parameters:
|
||||||
|
* - name: connectionId
|
||||||
|
* in: path
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* required:
|
||||||
|
* - computerObjectGUID
|
||||||
|
* - targetOUDistinguishedName
|
||||||
|
* properties:
|
||||||
|
* computerObjectGUID:
|
||||||
|
* type: string
|
||||||
|
* targetOUDistinguishedName:
|
||||||
|
* type: string
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Computer moved successfully
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* success:
|
||||||
|
* type: boolean
|
||||||
|
* message:
|
||||||
|
* type: string
|
||||||
|
* 400:
|
||||||
|
* $ref: '#/components/responses/BadRequestError'
|
||||||
|
* 401:
|
||||||
|
* $ref: '#/components/responses/UnauthorizedError'
|
||||||
|
* 404:
|
||||||
|
* $ref: '#/components/responses/NotFoundError'
|
||||||
|
*/
|
||||||
|
app.post("/api/connections/:connectionId/move-computer", 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" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = moveComputerSchema.parse(req.body);
|
||||||
|
|
||||||
|
// This would call a method in the LDAP client to move the computer
|
||||||
|
// For now we'll return a mock success response
|
||||||
|
// In a real implementation, this would interact with the Active Directory
|
||||||
|
|
||||||
|
// Record the action in the audit log
|
||||||
|
const auditEntry = {
|
||||||
|
action: "MOVE_COMPUTER",
|
||||||
|
targetId: data.computerObjectGUID,
|
||||||
|
details: {
|
||||||
|
targetOU: data.targetOUDistinguishedName
|
||||||
|
},
|
||||||
|
userId: req.user?.id || null,
|
||||||
|
connectionId: connectionId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save the audit entry to storage
|
||||||
|
await storage.createAuditLogEntry(auditEntry);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: `Computer with GUID ${data.computerObjectGUID} moved to ${data.targetOUDistinguishedName}`
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
return handleZodError(error, res);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/connections/{connectionId}/move-user:
|
||||||
|
* post:
|
||||||
|
* summary: Move a user to a different OU
|
||||||
|
* 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:
|
||||||
|
* - userObjectGUID
|
||||||
|
* - targetOUDistinguishedName
|
||||||
|
* properties:
|
||||||
|
* userObjectGUID:
|
||||||
|
* type: string
|
||||||
|
* targetOUDistinguishedName:
|
||||||
|
* type: string
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: User moved successfully
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* success:
|
||||||
|
* type: boolean
|
||||||
|
* message:
|
||||||
|
* type: string
|
||||||
|
* 400:
|
||||||
|
* $ref: '#/components/responses/BadRequestError'
|
||||||
|
* 401:
|
||||||
|
* $ref: '#/components/responses/UnauthorizedError'
|
||||||
|
* 404:
|
||||||
|
* $ref: '#/components/responses/NotFoundError'
|
||||||
|
*/
|
||||||
|
app.post("/api/connections/:connectionId/move-user", 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" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = moveUserSchema.parse(req.body);
|
||||||
|
|
||||||
|
// This would call a method in the LDAP client to move the user
|
||||||
|
// For now we'll return a mock success response
|
||||||
|
// In a real implementation, this would interact with the Active Directory
|
||||||
|
|
||||||
|
// Record the action in the audit log
|
||||||
|
const auditEntry = {
|
||||||
|
action: "MOVE_USER",
|
||||||
|
targetId: data.userObjectGUID,
|
||||||
|
details: {
|
||||||
|
targetOU: data.targetOUDistinguishedName
|
||||||
|
},
|
||||||
|
userId: req.user?.id || null,
|
||||||
|
connectionId: connectionId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save the audit entry to storage
|
||||||
|
await storage.createAuditLogEntry(auditEntry);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: `User with GUID ${data.userObjectGUID} moved to ${data.targetOUDistinguishedName}`
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
return handleZodError(error, res);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/connections/{connectionId}/add-to-group:
|
||||||
|
* post:
|
||||||
|
* summary: Add a user or computer to a group
|
||||||
|
* tags: [AD Groups]
|
||||||
|
* security:
|
||||||
|
* - bearerAuth: []
|
||||||
|
* - cookieAuth: []
|
||||||
|
* parameters:
|
||||||
|
* - name: connectionId
|
||||||
|
* in: path
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* required:
|
||||||
|
* - objectGUID
|
||||||
|
* - groupObjectGUID
|
||||||
|
* - objectType
|
||||||
|
* properties:
|
||||||
|
* objectGUID:
|
||||||
|
* type: string
|
||||||
|
* groupObjectGUID:
|
||||||
|
* type: string
|
||||||
|
* objectType:
|
||||||
|
* type: string
|
||||||
|
* enum: [user, computer]
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Object added to group successfully
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* success:
|
||||||
|
* type: boolean
|
||||||
|
* message:
|
||||||
|
* type: string
|
||||||
|
* 400:
|
||||||
|
* $ref: '#/components/responses/BadRequestError'
|
||||||
|
* 401:
|
||||||
|
* $ref: '#/components/responses/UnauthorizedError'
|
||||||
|
* 404:
|
||||||
|
* $ref: '#/components/responses/NotFoundError'
|
||||||
|
*/
|
||||||
|
app.post("/api/connections/:connectionId/add-to-group", 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" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = addToGroupSchema.parse(req.body);
|
||||||
|
|
||||||
|
// This would call a method in the LDAP client to add the object to the group
|
||||||
|
// For now we'll return a mock success response
|
||||||
|
// In a real implementation, this would interact with the Active Directory
|
||||||
|
|
||||||
|
// Record the action in the audit log
|
||||||
|
const auditEntry = {
|
||||||
|
action: "ADD_TO_GROUP",
|
||||||
|
targetId: data.objectGUID,
|
||||||
|
details: {
|
||||||
|
groupGUID: data.groupObjectGUID,
|
||||||
|
objectType: data.objectType
|
||||||
|
},
|
||||||
|
userId: req.user?.id || null,
|
||||||
|
connectionId: connectionId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save the audit entry to storage
|
||||||
|
await storage.createAuditLogEntry(auditEntry);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: `${data.objectType} with GUID ${data.objectGUID} added to group with GUID ${data.groupObjectGUID}`
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
return handleZodError(error, res);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/connections/{connectionId}/audit-logs:
|
||||||
|
* get:
|
||||||
|
* summary: Retrieve audit logs for a connection
|
||||||
|
* tags: [Audit]
|
||||||
|
* security:
|
||||||
|
* - bearerAuth: []
|
||||||
|
* - cookieAuth: []
|
||||||
|
* parameters:
|
||||||
|
* - name: connectionId
|
||||||
|
* in: path
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: List of audit logs
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: integer
|
||||||
|
* timestamp:
|
||||||
|
* type: string
|
||||||
|
* format: date-time
|
||||||
|
* userId:
|
||||||
|
* type: integer
|
||||||
|
* nullable: true
|
||||||
|
* action:
|
||||||
|
* type: string
|
||||||
|
* targetId:
|
||||||
|
* type: string
|
||||||
|
* details:
|
||||||
|
* type: object
|
||||||
|
* connectionId:
|
||||||
|
* type: integer
|
||||||
|
* 401:
|
||||||
|
* $ref: '#/components/responses/UnauthorizedError'
|
||||||
|
* 404:
|
||||||
|
* $ref: '#/components/responses/NotFoundError'
|
||||||
|
*/
|
||||||
|
app.get("/api/connections/:connectionId/audit-logs", 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 logs = await storage.getAuditLogs(connectionId);
|
||||||
|
res.json(logs);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/audit-logs:
|
||||||
|
* get:
|
||||||
|
* summary: Retrieve all audit logs
|
||||||
|
* tags: [Audit]
|
||||||
|
* security:
|
||||||
|
* - bearerAuth: []
|
||||||
|
* - cookieAuth: []
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: List of all audit logs
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: integer
|
||||||
|
* timestamp:
|
||||||
|
* type: string
|
||||||
|
* format: date-time
|
||||||
|
* userId:
|
||||||
|
* type: integer
|
||||||
|
* nullable: true
|
||||||
|
* action:
|
||||||
|
* type: string
|
||||||
|
* targetId:
|
||||||
|
* type: string
|
||||||
|
* details:
|
||||||
|
* type: object
|
||||||
|
* connectionId:
|
||||||
|
* type: integer
|
||||||
|
* 401:
|
||||||
|
* $ref: '#/components/responses/UnauthorizedError'
|
||||||
|
*/
|
||||||
|
app.get("/api/audit-logs", authenticateApiToken, async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const logs = await storage.getAuditLogs();
|
||||||
|
res.json(logs);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/connections/{connectionId}/remove-from-group:
|
||||||
|
* post:
|
||||||
|
* summary: Remove a user or computer from a group
|
||||||
|
* tags: [AD Groups]
|
||||||
|
* security:
|
||||||
|
* - bearerAuth: []
|
||||||
|
* - cookieAuth: []
|
||||||
|
* parameters:
|
||||||
|
* - name: connectionId
|
||||||
|
* in: path
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* required:
|
||||||
|
* - objectGUID
|
||||||
|
* - groupObjectGUID
|
||||||
|
* - objectType
|
||||||
|
* properties:
|
||||||
|
* objectGUID:
|
||||||
|
* type: string
|
||||||
|
* groupObjectGUID:
|
||||||
|
* type: string
|
||||||
|
* objectType:
|
||||||
|
* type: string
|
||||||
|
* enum: [user, computer]
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Object removed from group successfully
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* success:
|
||||||
|
* type: boolean
|
||||||
|
* message:
|
||||||
|
* type: string
|
||||||
|
* 400:
|
||||||
|
* $ref: '#/components/responses/BadRequestError'
|
||||||
|
* 401:
|
||||||
|
* $ref: '#/components/responses/UnauthorizedError'
|
||||||
|
* 404:
|
||||||
|
* $ref: '#/components/responses/NotFoundError'
|
||||||
|
*/
|
||||||
|
app.post("/api/connections/:connectionId/remove-from-group", 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" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = removeFromGroupSchema.parse(req.body);
|
||||||
|
|
||||||
|
// This would call a method in the LDAP client to remove the object from the group
|
||||||
|
// For now we'll return a mock success response
|
||||||
|
// In a real implementation, this would interact with the Active Directory
|
||||||
|
|
||||||
|
// Record the action in the audit log
|
||||||
|
const auditEntry = {
|
||||||
|
action: "REMOVE_FROM_GROUP",
|
||||||
|
targetId: data.objectGUID,
|
||||||
|
details: {
|
||||||
|
groupGUID: data.groupObjectGUID,
|
||||||
|
objectType: data.objectType
|
||||||
|
},
|
||||||
|
userId: req.user?.id || null,
|
||||||
|
connectionId: connectionId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save the audit entry to storage
|
||||||
|
await storage.createAuditLogEntry(auditEntry);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: `${data.objectType} with GUID ${data.objectGUID} removed from group with GUID ${data.groupObjectGUID}`
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
return handleZodError(error, res);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/audit-logs:
|
||||||
|
* get:
|
||||||
|
* summary: Get all audit logs
|
||||||
|
* tags: [Audit Logs]
|
||||||
|
* security:
|
||||||
|
* - cookieAuth: []
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: List of all audit logs
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* $ref: '#/components/schemas/AuditLog'
|
||||||
|
* 401:
|
||||||
|
* $ref: '#/components/responses/UnauthorizedError'
|
||||||
|
*/
|
||||||
|
app.get("/api/audit-logs", requireAuth, async (req: Request<any>, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const logs = await storage.getAuditLogs();
|
||||||
|
res.json(logs);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/connections/{connectionId}/audit-logs:
|
||||||
|
* get:
|
||||||
|
* summary: Get audit logs for a specific connection
|
||||||
|
* tags: [Audit Logs]
|
||||||
|
* security:
|
||||||
|
* - cookieAuth: []
|
||||||
|
* parameters:
|
||||||
|
* - name: connectionId
|
||||||
|
* in: path
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: List of audit logs for the specified connection
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* $ref: '#/components/schemas/AuditLog'
|
||||||
|
* 401:
|
||||||
|
* $ref: '#/components/responses/UnauthorizedError'
|
||||||
|
* 404:
|
||||||
|
* $ref: '#/components/responses/NotFoundError'
|
||||||
|
*/
|
||||||
|
app.get("/api/connections/:connectionId/audit-logs", requireAuth, async (req: Request<any>, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const connectionId = parseInt(req.params.connectionId, 10);
|
||||||
|
|
||||||
|
// Verify the connection exists
|
||||||
|
const connection = await storage.getLdapConnection(connectionId);
|
||||||
|
if (!connection) {
|
||||||
|
return res.status(404).json({ message: "Connection not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const logs = await storage.getAuditLogs(connectionId);
|
||||||
|
res.json(logs);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const httpServer = createServer(app);
|
const httpServer = createServer(app);
|
||||||
|
|
||||||
return httpServer;
|
return httpServer;
|
||||||
|
|||||||
+41
-3
@@ -5,15 +5,15 @@ import {
|
|||||||
AdOrgUnit, InsertAdOrgUnit, AdComputer, InsertAdComputer,
|
AdOrgUnit, InsertAdOrgUnit, AdComputer, InsertAdComputer,
|
||||||
AdDomain, InsertAdDomain, Role, ApiQuery,
|
AdDomain, InsertAdDomain, Role, ApiQuery,
|
||||||
LdapFilter, InsertLdapFilter, LdapFilterRevision, InsertLdapFilterRevision,
|
LdapFilter, InsertLdapFilter, LdapFilterRevision, InsertLdapFilterRevision,
|
||||||
LdapAttribute, InsertLdapAttribute,
|
LdapAttribute, InsertLdapAttribute, AuditLog, InsertAuditLog,
|
||||||
users, apiTokens, ldapConnections, adUsers, adGroups, adOrgUnits, adComputers, adDomains,
|
users, apiTokens, ldapConnections, adUsers, adGroups, adOrgUnits, adComputers, adDomains,
|
||||||
roles, ldapFilters, ldapFilterRevisions, ldapAttributes
|
roles, ldapFilters, ldapFilterRevisions, ldapAttributes, auditLogs
|
||||||
} from "@shared/schema";
|
} from "@shared/schema";
|
||||||
import session from "express-session";
|
import session from "express-session";
|
||||||
import createMemoryStore from "memorystore";
|
import createMemoryStore from "memorystore";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
import { eq, and, type SQL } from "drizzle-orm";
|
import { eq, and, type SQL, desc } from "drizzle-orm";
|
||||||
import connectPg from "connect-pg-simple";
|
import connectPg from "connect-pg-simple";
|
||||||
import { Pool } from "@neondatabase/serverless";
|
import { Pool } from "@neondatabase/serverless";
|
||||||
import { applyQueryOptions } from "./query-parser";
|
import { applyQueryOptions } from "./query-parser";
|
||||||
@@ -113,6 +113,10 @@ export interface IStorage {
|
|||||||
deleteAdDomain(id: number): Promise<boolean>;
|
deleteAdDomain(id: number): Promise<boolean>;
|
||||||
listAdDomains(connectionId: number, query?: any): Promise<AdDomain[]>;
|
listAdDomains(connectionId: number, query?: any): Promise<AdDomain[]>;
|
||||||
|
|
||||||
|
// Audit logging
|
||||||
|
createAuditLogEntry(entry: InsertAuditLog): Promise<AuditLog>;
|
||||||
|
getAuditLogs(connectionId?: number, userId?: number): Promise<AuditLog[]>;
|
||||||
|
|
||||||
// Session store
|
// Session store
|
||||||
sessionStore: any;
|
sessionStore: any;
|
||||||
}
|
}
|
||||||
@@ -855,6 +859,40 @@ export class DatabaseStorage implements IStorage {
|
|||||||
|
|
||||||
return adDomainsQuery;
|
return adDomainsQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Audit logging
|
||||||
|
async createAuditLogEntry(entry: InsertAuditLog): Promise<AuditLog> {
|
||||||
|
debug(`Creating audit log entry: ${JSON.stringify(entry)}`);
|
||||||
|
const result = await db.insert(auditLogs).values({
|
||||||
|
...entry,
|
||||||
|
timestamp: new Date() // Ensure timestamp is set
|
||||||
|
}).returning();
|
||||||
|
return result[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAuditLogs(connectionId?: number, userId?: number): Promise<AuditLog[]> {
|
||||||
|
debug(`Getting audit logs: connectionId=${connectionId}, userId=${userId}`);
|
||||||
|
|
||||||
|
// Define the base query
|
||||||
|
let query = db.select().from(auditLogs);
|
||||||
|
|
||||||
|
// Apply filters if provided
|
||||||
|
if (connectionId !== undefined && userId !== undefined) {
|
||||||
|
query = query.where(
|
||||||
|
and(
|
||||||
|
eq(auditLogs.connectionId, connectionId),
|
||||||
|
eq(auditLogs.userId, userId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (connectionId !== undefined) {
|
||||||
|
query = query.where(eq(auditLogs.connectionId, connectionId));
|
||||||
|
} else if (userId !== undefined) {
|
||||||
|
query = query.where(eq(auditLogs.userId, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by most recent first
|
||||||
|
return await query.orderBy(desc(auditLogs.timestamp));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const storage = new DatabaseStorage();
|
export const storage = new DatabaseStorage();
|
||||||
|
|||||||
@@ -31,11 +31,13 @@ export const PERMISSIONS = {
|
|||||||
CREATE_AD_USERS: "create:ad_users",
|
CREATE_AD_USERS: "create:ad_users",
|
||||||
UPDATE_AD_USERS: "update:ad_users",
|
UPDATE_AD_USERS: "update:ad_users",
|
||||||
DELETE_AD_USERS: "delete:ad_users",
|
DELETE_AD_USERS: "delete:ad_users",
|
||||||
|
MOVE_AD_USERS: "move:ad_users",
|
||||||
|
|
||||||
VIEW_AD_GROUPS: "view:ad_groups",
|
VIEW_AD_GROUPS: "view:ad_groups",
|
||||||
CREATE_AD_GROUPS: "create:ad_groups",
|
CREATE_AD_GROUPS: "create:ad_groups",
|
||||||
UPDATE_AD_GROUPS: "update:ad_groups",
|
UPDATE_AD_GROUPS: "update:ad_groups",
|
||||||
DELETE_AD_GROUPS: "delete:ad_groups",
|
DELETE_AD_GROUPS: "delete:ad_groups",
|
||||||
|
MANAGE_GROUP_MEMBERSHIP: "manage:group_membership",
|
||||||
|
|
||||||
VIEW_AD_OUS: "view:ad_ous",
|
VIEW_AD_OUS: "view:ad_ous",
|
||||||
CREATE_AD_OUS: "create:ad_ous",
|
CREATE_AD_OUS: "create:ad_ous",
|
||||||
@@ -46,6 +48,7 @@ export const PERMISSIONS = {
|
|||||||
CREATE_AD_COMPUTERS: "create:ad_computers",
|
CREATE_AD_COMPUTERS: "create:ad_computers",
|
||||||
UPDATE_AD_COMPUTERS: "update:ad_computers",
|
UPDATE_AD_COMPUTERS: "update:ad_computers",
|
||||||
DELETE_AD_COMPUTERS: "delete:ad_computers",
|
DELETE_AD_COMPUTERS: "delete:ad_computers",
|
||||||
|
MOVE_AD_COMPUTERS: "move:ad_computers",
|
||||||
|
|
||||||
VIEW_AD_DOMAINS: "view:ad_domains",
|
VIEW_AD_DOMAINS: "view:ad_domains",
|
||||||
|
|
||||||
@@ -71,10 +74,12 @@ export const permissionsSchema = z.enum([
|
|||||||
"create:ad_users",
|
"create:ad_users",
|
||||||
"update:ad_users",
|
"update:ad_users",
|
||||||
"delete:ad_users",
|
"delete:ad_users",
|
||||||
|
"move:ad_users",
|
||||||
"view:ad_groups",
|
"view:ad_groups",
|
||||||
"create:ad_groups",
|
"create:ad_groups",
|
||||||
"update:ad_groups",
|
"update:ad_groups",
|
||||||
"delete:ad_groups",
|
"delete:ad_groups",
|
||||||
|
"manage:group_membership",
|
||||||
"view:ad_ous",
|
"view:ad_ous",
|
||||||
"create:ad_ous",
|
"create:ad_ous",
|
||||||
"update:ad_ous",
|
"update:ad_ous",
|
||||||
@@ -83,6 +88,7 @@ export const permissionsSchema = z.enum([
|
|||||||
"create:ad_computers",
|
"create:ad_computers",
|
||||||
"update:ad_computers",
|
"update:ad_computers",
|
||||||
"delete:ad_computers",
|
"delete:ad_computers",
|
||||||
|
"move:ad_computers",
|
||||||
"view:ad_domains",
|
"view:ad_domains",
|
||||||
"manage:api_tokens",
|
"manage:api_tokens",
|
||||||
"manage:roles",
|
"manage:roles",
|
||||||
@@ -142,7 +148,10 @@ export const ldapConnections = pgTable("ldap_connections", {
|
|||||||
export const adUsers = pgTable("ad_users", {
|
export const adUsers = pgTable("ad_users", {
|
||||||
id: serial("id").primaryKey(),
|
id: serial("id").primaryKey(),
|
||||||
connectionId: integer("connection_id").notNull(),
|
connectionId: integer("connection_id").notNull(),
|
||||||
|
objectGUID: text("object_guid").notNull(),
|
||||||
distinguishedName: text("distinguished_name").notNull(),
|
distinguishedName: text("distinguished_name").notNull(),
|
||||||
|
canonicalName: text("canonical_name"),
|
||||||
|
cn: text("cn"),
|
||||||
sAMAccountName: text("sam_account_name").notNull(),
|
sAMAccountName: text("sam_account_name").notNull(),
|
||||||
userPrincipalName: text("user_principal_name"),
|
userPrincipalName: text("user_principal_name"),
|
||||||
givenName: text("given_name"),
|
givenName: text("given_name"),
|
||||||
@@ -158,7 +167,10 @@ export const adUsers = pgTable("ad_users", {
|
|||||||
export const adGroups = pgTable("ad_groups", {
|
export const adGroups = pgTable("ad_groups", {
|
||||||
id: serial("id").primaryKey(),
|
id: serial("id").primaryKey(),
|
||||||
connectionId: integer("connection_id").notNull(),
|
connectionId: integer("connection_id").notNull(),
|
||||||
|
objectGUID: text("object_guid").notNull(),
|
||||||
distinguishedName: text("distinguished_name").notNull(),
|
distinguishedName: text("distinguished_name").notNull(),
|
||||||
|
canonicalName: text("canonical_name"),
|
||||||
|
cn: text("cn"),
|
||||||
sAMAccountName: text("sam_account_name").notNull(),
|
sAMAccountName: text("sam_account_name").notNull(),
|
||||||
groupType: text("group_type"),
|
groupType: text("group_type"),
|
||||||
description: text("description"),
|
description: text("description"),
|
||||||
@@ -169,7 +181,10 @@ export const adGroups = pgTable("ad_groups", {
|
|||||||
export const adOrgUnits = pgTable("ad_org_units", {
|
export const adOrgUnits = pgTable("ad_org_units", {
|
||||||
id: serial("id").primaryKey(),
|
id: serial("id").primaryKey(),
|
||||||
connectionId: integer("connection_id").notNull(),
|
connectionId: integer("connection_id").notNull(),
|
||||||
|
objectGUID: text("object_guid").notNull(),
|
||||||
distinguishedName: text("distinguished_name").notNull(),
|
distinguishedName: text("distinguished_name").notNull(),
|
||||||
|
canonicalName: text("canonical_name"),
|
||||||
|
cn: text("cn"),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
description: text("description"),
|
description: text("description"),
|
||||||
adProperties: jsonb("ad_properties"),
|
adProperties: jsonb("ad_properties"),
|
||||||
@@ -178,20 +193,27 @@ export const adOrgUnits = pgTable("ad_org_units", {
|
|||||||
export const adComputers = pgTable("ad_computers", {
|
export const adComputers = pgTable("ad_computers", {
|
||||||
id: serial("id").primaryKey(),
|
id: serial("id").primaryKey(),
|
||||||
connectionId: integer("connection_id").notNull(),
|
connectionId: integer("connection_id").notNull(),
|
||||||
|
objectGUID: text("object_guid").notNull(),
|
||||||
distinguishedName: text("distinguished_name").notNull(),
|
distinguishedName: text("distinguished_name").notNull(),
|
||||||
|
canonicalName: text("canonical_name"),
|
||||||
|
cn: text("cn"),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
dnsHostName: text("dns_host_name"),
|
dnsHostName: text("dns_host_name"),
|
||||||
operatingSystem: text("operating_system"),
|
operatingSystem: text("operating_system"),
|
||||||
operatingSystemVersion: text("operating_system_version"),
|
operatingSystemVersion: text("operating_system_version"),
|
||||||
lastLogon: timestamp("last_logon"),
|
lastLogon: timestamp("last_logon"),
|
||||||
enabled: boolean("enabled").default(true),
|
enabled: boolean("enabled").default(true),
|
||||||
|
memberOf: jsonb("member_of"),
|
||||||
adProperties: jsonb("ad_properties"),
|
adProperties: jsonb("ad_properties"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const adDomains = pgTable("ad_domains", {
|
export const adDomains = pgTable("ad_domains", {
|
||||||
id: serial("id").primaryKey(),
|
id: serial("id").primaryKey(),
|
||||||
connectionId: integer("connection_id").notNull(),
|
connectionId: integer("connection_id").notNull(),
|
||||||
|
objectGUID: text("object_guid").notNull(),
|
||||||
distinguishedName: text("distinguished_name").notNull(),
|
distinguishedName: text("distinguished_name").notNull(),
|
||||||
|
canonicalName: text("canonical_name"),
|
||||||
|
cn: text("cn"),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
netBIOSName: text("net_bios_name"),
|
netBIOSName: text("net_bios_name"),
|
||||||
forestName: text("forest_name"),
|
forestName: text("forest_name"),
|
||||||
@@ -260,6 +282,30 @@ export const apiQuerySchema = z.object({
|
|||||||
skip: z.string().optional(),
|
skip: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Move operation schemas
|
||||||
|
export const moveComputerSchema = z.object({
|
||||||
|
computerObjectGUID: z.string().min(1, "Computer ObjectGUID is required"),
|
||||||
|
targetOUDistinguishedName: z.string().min(1, "Target OU DN is required"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const moveUserSchema = z.object({
|
||||||
|
userObjectGUID: z.string().min(1, "User ObjectGUID is required"),
|
||||||
|
targetOUDistinguishedName: z.string().min(1, "Target OU DN is required"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Group membership schemas
|
||||||
|
export const addToGroupSchema = z.object({
|
||||||
|
objectGUID: z.string().min(1, "Object GUID is required"),
|
||||||
|
groupObjectGUID: z.string().min(1, "Group ObjectGUID is required"),
|
||||||
|
objectType: z.enum(["user", "computer"]),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const removeFromGroupSchema = z.object({
|
||||||
|
objectGUID: z.string().min(1, "Object GUID is required"),
|
||||||
|
groupObjectGUID: z.string().min(1, "Group ObjectGUID is required"),
|
||||||
|
objectType: z.enum(["user", "computer"]),
|
||||||
|
});
|
||||||
|
|
||||||
// Export types
|
// Export types
|
||||||
export type Role = typeof roles.$inferSelect;
|
export type Role = typeof roles.$inferSelect;
|
||||||
export type InsertRole = z.infer<typeof insertRoleSchema>;
|
export type InsertRole = z.infer<typeof insertRoleSchema>;
|
||||||
@@ -288,6 +334,10 @@ export type AdDomain = typeof adDomains.$inferSelect;
|
|||||||
export type InsertAdDomain = z.infer<typeof insertAdDomainSchema>;
|
export type InsertAdDomain = z.infer<typeof insertAdDomainSchema>;
|
||||||
export type Login = z.infer<typeof loginSchema>;
|
export type Login = z.infer<typeof loginSchema>;
|
||||||
export type ApiQuery = z.infer<typeof apiQuerySchema>;
|
export type ApiQuery = z.infer<typeof apiQuerySchema>;
|
||||||
|
export type MoveComputer = z.infer<typeof moveComputerSchema>;
|
||||||
|
export type MoveUser = z.infer<typeof moveUserSchema>;
|
||||||
|
export type AddToGroup = z.infer<typeof addToGroupSchema>;
|
||||||
|
export type RemoveFromGroup = z.infer<typeof removeFromGroupSchema>;
|
||||||
|
|
||||||
// LDAP Query Builder schemas
|
// LDAP Query Builder schemas
|
||||||
export const ldapFilterObjectClasses = ["user", "group", "organizationalUnit", "computer", "domain"] as const;
|
export const ldapFilterObjectClasses = ["user", "group", "organizationalUnit", "computer", "domain"] as const;
|
||||||
@@ -321,6 +371,28 @@ export const ldapFilterRevisions = pgTable("ldap_filter_revisions", {
|
|||||||
comment: text("comment"),
|
comment: text("comment"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Audit logs for tracking actions
|
||||||
|
export const auditLogs = pgTable("audit_logs", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
|
action: text("action").notNull(),
|
||||||
|
targetId: text("target_id").notNull(),
|
||||||
|
details: jsonb("details").notNull().default({}),
|
||||||
|
userId: integer("user_id"),
|
||||||
|
timestamp: timestamp("timestamp").notNull().defaultNow(),
|
||||||
|
connectionId: integer("connection_id"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const auditLogsRelations = relations(auditLogs, ({ one }) => ({
|
||||||
|
user: one(users, {
|
||||||
|
fields: [auditLogs.userId],
|
||||||
|
references: [users.id],
|
||||||
|
}),
|
||||||
|
connection: one(ldapConnections, {
|
||||||
|
fields: [auditLogs.connectionId],
|
||||||
|
references: [ldapConnections.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
// LDAP Attribute schema - stores known attributes for connections
|
// LDAP Attribute schema - stores known attributes for connections
|
||||||
export const ldapAttributes = pgTable("ldap_attributes", {
|
export const ldapAttributes = pgTable("ldap_attributes", {
|
||||||
id: serial("id").primaryKey(),
|
id: serial("id").primaryKey(),
|
||||||
@@ -397,3 +469,11 @@ export type LdapFilterRevision = typeof ldapFilterRevisions.$inferSelect;
|
|||||||
export type InsertLdapFilterRevision = z.infer<typeof insertLdapFilterRevisionSchema>;
|
export type InsertLdapFilterRevision = z.infer<typeof insertLdapFilterRevisionSchema>;
|
||||||
export type LdapAttribute = typeof ldapAttributes.$inferSelect;
|
export type LdapAttribute = typeof ldapAttributes.$inferSelect;
|
||||||
export type InsertLdapAttribute = z.infer<typeof insertLdapAttributeSchema>;
|
export type InsertLdapAttribute = z.infer<typeof insertLdapAttributeSchema>;
|
||||||
|
|
||||||
|
// Audit log schema and types
|
||||||
|
export const insertAuditLogSchema = createInsertSchema(auditLogs).omit({
|
||||||
|
id: true,
|
||||||
|
timestamp: true
|
||||||
|
});
|
||||||
|
export type AuditLog = typeof auditLogs.$inferSelect;
|
||||||
|
export type InsertAuditLog = z.infer<typeof insertAuditLogSchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user