mirror of
https://github.com/freedbygrace/ActiveDirectoryManager.git
synced 2026-07-26 11:59:14 +00:00
Update API documentation and UI to improve clarity and add role-based access control.
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/994ee92d-6a6d-4ca4-99c0-fe0dbc7f160b.jpg
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
||||
import pg from 'pg';
|
||||
import * as schema from './shared/schema';
|
||||
|
||||
const { Pool } = pg;
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
});
|
||||
|
||||
const db = drizzle(pool, { schema });
|
||||
|
||||
async function main() {
|
||||
console.log('Applying migrations...');
|
||||
|
||||
try {
|
||||
// Apply the migrations
|
||||
await migrate(db, { migrationsFolder: './migrations' });
|
||||
console.log('Migrations applied successfully');
|
||||
|
||||
// Insert default roles if they don't exist
|
||||
await db.transaction(async (tx) => {
|
||||
const adminRoleExists = await tx.query.roles.findFirst({
|
||||
where: (roles, { eq }) => eq(roles.name, 'admin')
|
||||
});
|
||||
|
||||
if (!adminRoleExists) {
|
||||
console.log('Creating admin role...');
|
||||
const [adminRole] = await tx.insert(schema.roles).values({
|
||||
name: 'admin',
|
||||
description: 'Administrator with full system access',
|
||||
isDefault: false
|
||||
}).returning();
|
||||
|
||||
// Add all permissions to admin role
|
||||
const permissions = Object.values(schema.PERMISSIONS);
|
||||
await Promise.all(permissions.map(permission =>
|
||||
tx.insert(schema.rolePermissions).values({
|
||||
roleId: adminRole.id,
|
||||
permission
|
||||
})
|
||||
));
|
||||
}
|
||||
|
||||
const userRoleExists = await tx.query.roles.findFirst({
|
||||
where: (roles, { eq }) => eq(roles.name, 'user')
|
||||
});
|
||||
|
||||
if (!userRoleExists) {
|
||||
console.log('Creating user role...');
|
||||
const [userRole] = await tx.insert(schema.roles).values({
|
||||
name: 'user',
|
||||
description: 'Regular user with limited access',
|
||||
isDefault: true
|
||||
}).returning();
|
||||
|
||||
// Add basic permissions to user role
|
||||
const basicPermissions = [
|
||||
schema.PERMISSIONS.VIEW_USERS,
|
||||
schema.PERMISSIONS.VIEW_LDAP_CONNECTIONS,
|
||||
schema.PERMISSIONS.VIEW_AD_USERS,
|
||||
schema.PERMISSIONS.VIEW_AD_GROUPS,
|
||||
schema.PERMISSIONS.VIEW_AD_OUS,
|
||||
schema.PERMISSIONS.VIEW_AD_COMPUTERS,
|
||||
schema.PERMISSIONS.VIEW_AD_DOMAINS
|
||||
];
|
||||
|
||||
await Promise.all(basicPermissions.map(permission =>
|
||||
tx.insert(schema.rolePermissions).values({
|
||||
roleId: userRole.id,
|
||||
permission
|
||||
})
|
||||
));
|
||||
}
|
||||
|
||||
const apiRoleExists = await tx.query.roles.findFirst({
|
||||
where: (roles, { eq }) => eq(roles.name, 'api')
|
||||
});
|
||||
|
||||
if (!apiRoleExists) {
|
||||
console.log('Creating api role...');
|
||||
await tx.insert(schema.roles).values({
|
||||
name: 'api',
|
||||
description: 'API access with customizable permissions',
|
||||
isDefault: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Setup completed successfully');
|
||||
} catch (error) {
|
||||
console.error('Migration failed:', error);
|
||||
} finally {
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -15,50 +15,50 @@ export default function ApiDocumentationCard() {
|
||||
<Button
|
||||
variant="link"
|
||||
className="p-0 h-auto font-medium text-sm text-primary"
|
||||
onClick={() => window.open("/api/docs", "_blank")}
|
||||
onClick={() => window.open("/api-docs", "_blank")}
|
||||
>
|
||||
View Full Documentation
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="p-4">
|
||||
<div className="bg-muted rounded-md p-4 font-mono text-sm overflow-x-auto">
|
||||
<pre className="text-xs whitespace-pre-wrap">
|
||||
# Active Directory Management API
|
||||
<div className="text-xs whitespace-pre-wrap">
|
||||
<p className="font-bold mb-2"># Active Directory Management API</p>
|
||||
|
||||
## User Endpoints
|
||||
<p className="font-bold mt-3 mb-2">## User Endpoints</p>
|
||||
|
||||
GET /api/users
|
||||
- Query Parameters:
|
||||
- filter: Filter users by property values (e.g. ?filter=name eq 'John')
|
||||
- select: Select specific properties to return (e.g. ?select=id,name,email)
|
||||
- expand: Include related entities (e.g. ?expand=groups)
|
||||
- orderBy: Order results (e.g. ?orderBy=name asc)
|
||||
- top: Limit number of results (e.g. ?top=10)
|
||||
- skip: Skip number of results (e.g. ?skip=10)
|
||||
<p className="mb-1">GET /api/users</p>
|
||||
<p className="mb-1">- Query Parameters:</p>
|
||||
<p className="ml-4 mb-1">- filter: Filter users by property values (e.g. ?filter=name eq 'John')</p>
|
||||
<p className="ml-4 mb-1">- select: Select specific properties to return (e.g. ?select=id,name,email)</p>
|
||||
<p className="ml-4 mb-1">- expand: Include related entities (e.g. ?expand=groups)</p>
|
||||
<p className="ml-4 mb-1">- orderBy: Order results (e.g. ?orderBy=name asc)</p>
|
||||
<p className="ml-4 mb-1">- top: Limit number of results (e.g. ?top=10)</p>
|
||||
<p className="ml-4 mb-1">- skip: Skip number of results (e.g. ?skip=10)</p>
|
||||
|
||||
POST /api/users
|
||||
- Create a new user in Active Directory
|
||||
- Request body: User object
|
||||
<p className="mt-3 mb-1">POST /api/users</p>
|
||||
<p className="mb-1">- Create a new user in Active Directory</p>
|
||||
<p className="mb-1">- Request body: User object</p>
|
||||
|
||||
GET /api/users/{id}
|
||||
- Get a specific user by ID
|
||||
- Query Parameters:
|
||||
- select: Select specific properties to return
|
||||
<p className="mt-3 mb-1">GET /api/users/{"{userId}"}</p>
|
||||
<p className="mb-1">- Get a specific user by ID</p>
|
||||
<p className="mb-1">- Query Parameters:</p>
|
||||
<p className="ml-4 mb-1">- select: Select specific properties to return</p>
|
||||
|
||||
PUT /api/users/{id}
|
||||
- Update a specific user
|
||||
- Request body: User object with updated properties
|
||||
<p className="mt-3 mb-1">PUT /api/users/{"{userId}"}</p>
|
||||
<p className="mb-1">- Update a specific user</p>
|
||||
<p className="mb-1">- Request body: User object with updated properties</p>
|
||||
|
||||
DELETE /api/users/{id}
|
||||
- Delete a specific user
|
||||
</pre>
|
||||
<p className="mt-3 mb-1">DELETE /api/users/{"{userId}"}</p>
|
||||
<p className="mb-1">- Delete a specific user</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex justify-end">
|
||||
<Button
|
||||
variant="link"
|
||||
className="p-0 h-auto font-medium text-sm text-primary"
|
||||
onClick={() => window.open("/api/docs", "_blank")}
|
||||
onClick={() => window.open("/api-docs", "_blank")}
|
||||
>
|
||||
Go to Swagger Documentation <ArrowRight className="ml-1 h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
@@ -2,6 +2,16 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.drawer-item {
|
||||
@apply text-gray-700 hover:bg-gray-100 hover:text-primary focus:outline-none transition-colors duration-200;
|
||||
}
|
||||
|
||||
.drawer-item.active {
|
||||
@apply bg-primary/10 text-primary font-medium;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 96%;
|
||||
|
||||
@@ -160,22 +160,21 @@ export function DashboardLayout({ children, title, description }: DashboardLayou
|
||||
} ${isMobile ? "absolute" : "relative"}`}
|
||||
>
|
||||
<div className="h-16 flex items-center px-4 border-b">
|
||||
<Link href="/">
|
||||
<a className="font-medium text-lg text-primary">AD Management API</a>
|
||||
<Link href="/" className="font-medium text-lg text-primary">
|
||||
AD Management API
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto h-[calc(100%-64px)]">
|
||||
<div className="px-2 pt-4">
|
||||
<Link href="/">
|
||||
<a
|
||||
className={`drawer-item px-4 py-2 flex items-center space-x-3 rounded cursor-pointer ${
|
||||
location === "/" ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
<Home className="h-4 w-4" />
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<Link
|
||||
href="/"
|
||||
className={`drawer-item px-4 py-2 flex items-center space-x-3 rounded cursor-pointer ${
|
||||
location === "/" ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
<Home className="h-4 w-4" />
|
||||
<span>Dashboard</span>
|
||||
</Link>
|
||||
|
||||
{menuSections.map((section, idx) => (
|
||||
@@ -185,15 +184,15 @@ export function DashboardLayout({ children, title, description }: DashboardLayou
|
||||
</div>
|
||||
|
||||
{section.items.map((item, itemIdx) => (
|
||||
<Link href={item.path} key={itemIdx}>
|
||||
<a
|
||||
className={`drawer-item px-4 py-2 flex items-center space-x-3 rounded cursor-pointer ${
|
||||
location === item.path ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
{item.icon}
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
<Link
|
||||
href={item.path}
|
||||
key={itemIdx}
|
||||
className={`drawer-item px-4 py-2 flex items-center space-x-3 rounded cursor-pointer ${
|
||||
location === item.path ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
{item.icon}
|
||||
<span>{item.title}</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -42,6 +42,8 @@ const registerSchema = insertUserSchema.extend({
|
||||
acceptTerms: z.boolean().refine(val => val === true, {
|
||||
message: "You must accept the terms and conditions",
|
||||
}),
|
||||
// Define roleId explicitly to match form values
|
||||
roleId: z.number().optional().nullable(),
|
||||
}).refine(data => data.password === data.confirmPassword, {
|
||||
message: "Passwords do not match",
|
||||
path: ["confirmPassword"],
|
||||
@@ -140,7 +142,7 @@ export default function AuthPage() {
|
||||
email: "",
|
||||
fullName: "",
|
||||
acceptTerms: false,
|
||||
role: "user",
|
||||
roleId: 2, // Assign the default "user" role ID
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
CREATE TABLE "ad_computers" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"connection_id" integer NOT NULL,
|
||||
"distinguished_name" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"dns_host_name" text,
|
||||
"operating_system" text,
|
||||
"operating_system_version" text,
|
||||
"last_logon" timestamp,
|
||||
"enabled" boolean DEFAULT true,
|
||||
"ad_properties" jsonb
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "ad_domains" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"connection_id" integer NOT NULL,
|
||||
"distinguished_name" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"net_bios_name" text,
|
||||
"forest_name" text,
|
||||
"domain_functionality" text,
|
||||
"ad_properties" jsonb
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "ad_groups" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"connection_id" integer NOT NULL,
|
||||
"distinguished_name" text NOT NULL,
|
||||
"sam_account_name" text NOT NULL,
|
||||
"group_type" text,
|
||||
"description" text,
|
||||
"members" jsonb,
|
||||
"ad_properties" jsonb
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "ad_org_units" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"connection_id" integer NOT NULL,
|
||||
"distinguished_name" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"ad_properties" jsonb
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "ad_users" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"connection_id" integer NOT NULL,
|
||||
"distinguished_name" text NOT NULL,
|
||||
"sam_account_name" text NOT NULL,
|
||||
"user_principal_name" text,
|
||||
"given_name" text,
|
||||
"surname" text,
|
||||
"display_name" text,
|
||||
"email" text,
|
||||
"enabled" boolean DEFAULT true,
|
||||
"last_logon" timestamp,
|
||||
"member_of" jsonb,
|
||||
"ad_properties" jsonb
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "api_tokens" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"token" text NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"role_id" integer,
|
||||
"custom_permissions" jsonb,
|
||||
"expires_at" timestamp,
|
||||
"created_at" timestamp DEFAULT now(),
|
||||
CONSTRAINT "api_tokens_token_unique" UNIQUE("token")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "ldap_connections" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"server" text NOT NULL,
|
||||
"domain" text NOT NULL,
|
||||
"port" integer DEFAULT 389,
|
||||
"use_ssl" boolean DEFAULT true,
|
||||
"username" text NOT NULL,
|
||||
"password" text NOT NULL,
|
||||
"status" text DEFAULT 'disconnected',
|
||||
"last_connected" timestamp,
|
||||
"created_at" timestamp DEFAULT now()
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "role_permissions" (
|
||||
"role_id" integer NOT NULL,
|
||||
"permission" text NOT NULL,
|
||||
CONSTRAINT "role_permissions_role_id_permission_pk" PRIMARY KEY("role_id","permission")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "roles" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"is_default" boolean DEFAULT false,
|
||||
"created_at" timestamp DEFAULT now(),
|
||||
CONSTRAINT "roles_name_unique" UNIQUE("name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"username" text NOT NULL,
|
||||
"password" text NOT NULL,
|
||||
"email" text,
|
||||
"full_name" text,
|
||||
"role_id" integer,
|
||||
"created_at" timestamp DEFAULT now(),
|
||||
CONSTRAINT "users_username_unique" UNIQUE("username")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "api_tokens" ADD CONSTRAINT "api_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "api_tokens" ADD CONSTRAINT "api_tokens_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "role_permissions" ADD CONSTRAINT "role_permissions_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "users" ADD CONSTRAINT "users_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE no action ON UPDATE no action;
|
||||
@@ -0,0 +1,714 @@
|
||||
{
|
||||
"id": "fbd1e34a-7d4a-41c2-9c99-cfda190d6502",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.ad_computers": {
|
||||
"name": "ad_computers",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"distinguished_name": {
|
||||
"name": "distinguished_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"dns_host_name": {
|
||||
"name": "dns_host_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"operating_system": {
|
||||
"name": "operating_system",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"operating_system_version": {
|
||||
"name": "operating_system_version",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_logon": {
|
||||
"name": "last_logon",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"enabled": {
|
||||
"name": "enabled",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": true
|
||||
},
|
||||
"ad_properties": {
|
||||
"name": "ad_properties",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ad_domains": {
|
||||
"name": "ad_domains",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"distinguished_name": {
|
||||
"name": "distinguished_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"net_bios_name": {
|
||||
"name": "net_bios_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"forest_name": {
|
||||
"name": "forest_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"domain_functionality": {
|
||||
"name": "domain_functionality",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"ad_properties": {
|
||||
"name": "ad_properties",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ad_groups": {
|
||||
"name": "ad_groups",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"distinguished_name": {
|
||||
"name": "distinguished_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"sam_account_name": {
|
||||
"name": "sam_account_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"group_type": {
|
||||
"name": "group_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"members": {
|
||||
"name": "members",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"ad_properties": {
|
||||
"name": "ad_properties",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ad_org_units": {
|
||||
"name": "ad_org_units",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"distinguished_name": {
|
||||
"name": "distinguished_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"ad_properties": {
|
||||
"name": "ad_properties",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ad_users": {
|
||||
"name": "ad_users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"distinguished_name": {
|
||||
"name": "distinguished_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"sam_account_name": {
|
||||
"name": "sam_account_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_principal_name": {
|
||||
"name": "user_principal_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"given_name": {
|
||||
"name": "given_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"surname": {
|
||||
"name": "surname",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"display_name": {
|
||||
"name": "display_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"enabled": {
|
||||
"name": "enabled",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": true
|
||||
},
|
||||
"last_logon": {
|
||||
"name": "last_logon",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"member_of": {
|
||||
"name": "member_of",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"ad_properties": {
|
||||
"name": "ad_properties",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.api_tokens": {
|
||||
"name": "api_tokens",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"token": {
|
||||
"name": "token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"role_id": {
|
||||
"name": "role_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"custom_permissions": {
|
||||
"name": "custom_permissions",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"api_tokens_user_id_users_id_fk": {
|
||||
"name": "api_tokens_user_id_users_id_fk",
|
||||
"tableFrom": "api_tokens",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"api_tokens_role_id_roles_id_fk": {
|
||||
"name": "api_tokens_role_id_roles_id_fk",
|
||||
"tableFrom": "api_tokens",
|
||||
"tableTo": "roles",
|
||||
"columnsFrom": [
|
||||
"role_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"api_tokens_token_unique": {
|
||||
"name": "api_tokens_token_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"token"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ldap_connections": {
|
||||
"name": "ldap_connections",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"server": {
|
||||
"name": "server",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"domain": {
|
||||
"name": "domain",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"port": {
|
||||
"name": "port",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": 389
|
||||
},
|
||||
"use_ssl": {
|
||||
"name": "use_ssl",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"password": {
|
||||
"name": "password",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'disconnected'"
|
||||
},
|
||||
"last_connected": {
|
||||
"name": "last_connected",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.role_permissions": {
|
||||
"name": "role_permissions",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"role_id": {
|
||||
"name": "role_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"permission": {
|
||||
"name": "permission",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"role_permissions_role_id_roles_id_fk": {
|
||||
"name": "role_permissions_role_id_roles_id_fk",
|
||||
"tableFrom": "role_permissions",
|
||||
"tableTo": "roles",
|
||||
"columnsFrom": [
|
||||
"role_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"role_permissions_role_id_permission_pk": {
|
||||
"name": "role_permissions_role_id_permission_pk",
|
||||
"columns": [
|
||||
"role_id",
|
||||
"permission"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.roles": {
|
||||
"name": "roles",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"is_default": {
|
||||
"name": "is_default",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"roles_name_unique": {
|
||||
"name": "roles_name_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"name"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"password": {
|
||||
"name": "password",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"full_name": {
|
||||
"name": "full_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"role_id": {
|
||||
"name": "role_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"users_role_id_roles_id_fk": {
|
||||
"name": "users_role_id_roles_id_fk",
|
||||
"tableFrom": "users",
|
||||
"tableTo": "roles",
|
||||
"columnsFrom": [
|
||||
"role_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"users_username_unique": {
|
||||
"name": "users_username_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"username"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1744078354259,
|
||||
"tag": "0000_fancy_blackheart",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
Generated
+58
-23
@@ -41,6 +41,10 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.3",
|
||||
"@replit/vite-plugin-shadcn-theme-json": "^0.0.4",
|
||||
"@tanstack/react-query": "^5.60.5",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/swagger-jsdoc": "^6.0.4",
|
||||
"@types/swagger-ui-express": "^4.1.8",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
@@ -59,6 +63,7 @@
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"pg": "^8.14.1",
|
||||
"react": "^18.3.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -3285,7 +3290,6 @@
|
||||
"version": "1.19.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
|
||||
"integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/connect": "*",
|
||||
@@ -3296,7 +3300,6 @@
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
||||
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
@@ -3388,7 +3391,6 @@
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
|
||||
"integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/body-parser": "*",
|
||||
@@ -3401,7 +3403,6 @@
|
||||
"version": "4.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz",
|
||||
"integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
@@ -3424,7 +3425,6 @@
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
|
||||
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
@@ -3433,11 +3433,26 @@
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/jsonwebtoken": {
|
||||
"version": "9.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz",
|
||||
"integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/ms": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
||||
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
||||
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
@@ -3453,12 +3468,21 @@
|
||||
"version": "1.0.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz",
|
||||
"integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/passport-jwt": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-4.0.1.tgz",
|
||||
"integrity": "sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/jsonwebtoken": "*",
|
||||
"@types/passport-strategy": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/passport-local": {
|
||||
"version": "1.0.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.38.tgz",
|
||||
@@ -3475,7 +3499,6 @@
|
||||
"version": "0.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz",
|
||||
"integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/express": "*",
|
||||
@@ -3504,14 +3527,12 @@
|
||||
"version": "6.9.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz",
|
||||
"integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/range-parser": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
|
||||
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
@@ -3539,7 +3560,6 @@
|
||||
"version": "0.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
|
||||
"integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mime": "^1",
|
||||
@@ -3550,7 +3570,6 @@
|
||||
"version": "1.15.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
|
||||
"integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/http-errors": "*",
|
||||
@@ -3558,6 +3577,22 @@
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/swagger-jsdoc": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz",
|
||||
"integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/swagger-ui-express": {
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz",
|
||||
"integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/express": "*",
|
||||
"@types/serve-static": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
|
||||
@@ -6436,14 +6471,14 @@
|
||||
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
||||
},
|
||||
"node_modules/pg": {
|
||||
"version": "8.13.1",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz",
|
||||
"integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==",
|
||||
"version": "8.14.1",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.14.1.tgz",
|
||||
"integrity": "sha512-0TdbqfjwIun9Fm/r89oB7RFQ0bLgduAhiIqIXOsyKoiC/L54DbuAAzIEN/9Op0f1Po9X7iCPXGoa/Ah+2aI8Xw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pg-connection-string": "^2.7.0",
|
||||
"pg-pool": "^3.7.0",
|
||||
"pg-protocol": "^1.7.0",
|
||||
"pg-pool": "^3.8.0",
|
||||
"pg-protocol": "^1.8.0",
|
||||
"pg-types": "^2.1.0",
|
||||
"pgpass": "1.x"
|
||||
},
|
||||
@@ -6494,18 +6529,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/pg-pool": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz",
|
||||
"integrity": "sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==",
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.8.0.tgz",
|
||||
"integrity": "sha512-VBw3jiVm6ZOdLBTIcXLNdSotb6Iy3uOCwDGFAksZCXmi10nyRvnP2v3jl4d+IsLYRyXf6o9hIm/ZtUzlByNUdw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"pg": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-protocol": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz",
|
||||
"integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==",
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.8.0.tgz",
|
||||
"integrity": "sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg-types": {
|
||||
|
||||
@@ -43,6 +43,10 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.3",
|
||||
"@replit/vite-plugin-shadcn-theme-json": "^0.0.4",
|
||||
"@tanstack/react-query": "^5.60.5",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/swagger-jsdoc": "^6.0.4",
|
||||
"@types/swagger-ui-express": "^4.1.8",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
@@ -61,6 +65,7 @@
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"pg": "^8.14.1",
|
||||
"react": "^18.3.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
||||
+18
-6
@@ -110,12 +110,19 @@ export function setupAuth(app: Express) {
|
||||
}
|
||||
|
||||
const hashedPassword = await hashPassword(password);
|
||||
// Get the default role if role ID isn't specified
|
||||
let roleId = req.body.roleId;
|
||||
if (!roleId) {
|
||||
const defaultRole = await storage.getDefaultRole();
|
||||
roleId = defaultRole?.id;
|
||||
}
|
||||
|
||||
const user = await storage.createUser({
|
||||
username,
|
||||
password: hashedPassword,
|
||||
email: req.body.email,
|
||||
fullName: req.body.fullName,
|
||||
role: req.body.role || "user",
|
||||
roleId: roleId,
|
||||
});
|
||||
|
||||
// Remove password from response
|
||||
@@ -132,7 +139,7 @@ export function setupAuth(app: Express) {
|
||||
|
||||
// Login endpoint
|
||||
app.post("/api/login", (req, res, next) => {
|
||||
passport.authenticate("local", (err, user, info) => {
|
||||
passport.authenticate("local", (err: any, user: any, info: any) => {
|
||||
if (err) return next(err);
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: info?.message || "Authentication failed" });
|
||||
@@ -175,25 +182,30 @@ export function setupAuth(app: Express) {
|
||||
}
|
||||
|
||||
try {
|
||||
const { name, expiresAt, permissions } = req.body;
|
||||
const { name, expiresAt, roleId, customPermissions } = req.body;
|
||||
if (!name) {
|
||||
return res.status(400).json({ message: "Token name is required" });
|
||||
}
|
||||
|
||||
// Create JWT token with user ID and optional permissions
|
||||
const token = jwt.sign(
|
||||
{
|
||||
sub: req.user.id,
|
||||
permissions
|
||||
customPermissions
|
||||
},
|
||||
JWT_SECRET,
|
||||
{ expiresAt: expiresAt ? new Date(expiresAt) : undefined }
|
||||
{
|
||||
expiresIn: expiresAt ? Math.floor((new Date(expiresAt).getTime() - Date.now()) / 1000) : '365d'
|
||||
}
|
||||
);
|
||||
|
||||
// Store the token in the database
|
||||
const apiToken = storage.createApiToken({
|
||||
name,
|
||||
token,
|
||||
userId: req.user.id,
|
||||
permissions: permissions || {},
|
||||
roleId: roleId || null,
|
||||
customPermissions: customPermissions || null,
|
||||
expiresAt: expiresAt ? new Date(expiresAt) : null,
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { db } from "./db";
|
||||
import { roles, rolePermissions, users, apiTokens } from "@shared/schema";
|
||||
import { eq, or, and, inArray } from "drizzle-orm";
|
||||
|
||||
// Types for role-based access control
|
||||
export type RequireAuthOptions = {
|
||||
allowApiToken?: boolean;
|
||||
};
|
||||
|
||||
export type RequirePermissionOptions = RequireAuthOptions & {
|
||||
anyOf?: string[];
|
||||
allOf?: string[];
|
||||
};
|
||||
|
||||
// Default middleware that verifies a user is authenticated
|
||||
export function requireAuth(options: RequireAuthOptions = {}) {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
// Check for session authentication
|
||||
if (req.isAuthenticated()) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Check for API token authentication if allowed
|
||||
if (options.allowApiToken) {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader && authHeader.startsWith("Bearer ")) {
|
||||
const token = authHeader.substring(7);
|
||||
|
||||
try {
|
||||
// Lookup the token
|
||||
const [apiToken] = await db.select()
|
||||
.from(apiTokens)
|
||||
.where(eq(apiTokens.token, token))
|
||||
.limit(1);
|
||||
|
||||
if (!apiToken) {
|
||||
return res.status(401).json({ message: "Invalid API token" });
|
||||
}
|
||||
|
||||
// Check if token has expired
|
||||
if (apiToken.expiresAt && new Date(apiToken.expiresAt) < new Date()) {
|
||||
return res.status(401).json({ message: "API token has expired" });
|
||||
}
|
||||
|
||||
// Set token info on the request
|
||||
req.user = {
|
||||
id: apiToken.userId,
|
||||
username: '',
|
||||
password: '',
|
||||
fullName: null,
|
||||
email: null,
|
||||
createdAt: null,
|
||||
// Add tokenId for identifying which token was used
|
||||
tokenId: apiToken.id,
|
||||
// Add roleId for permission checking
|
||||
roleId: apiToken.roleId,
|
||||
// Store custom permissions if available
|
||||
customPermissions: (Array.isArray(apiToken.customPermissions) ?
|
||||
apiToken.customPermissions :
|
||||
(apiToken.customPermissions ? JSON.parse(String(apiToken.customPermissions)) : [])) as string[]
|
||||
};
|
||||
|
||||
return next();
|
||||
} catch (error) {
|
||||
console.error("API token authentication error:", error);
|
||||
return res.status(500).json({ message: "Internal server error" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not authenticated through any method
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
};
|
||||
}
|
||||
|
||||
// Middleware for checking specific permissions
|
||||
export function requirePermission(permission: string, options: RequirePermissionOptions = {}) {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
// First ensure the user is authenticated
|
||||
const authMiddleware = requireAuth(options);
|
||||
|
||||
authMiddleware(req, res, async () => {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
// Get the permissions for the user's role
|
||||
let hasPermission = false;
|
||||
|
||||
// Check for API token custom permissions first if present
|
||||
if (req.user?.tokenId && Array.isArray(req.user?.customPermissions)) {
|
||||
// For API tokens with custom permissions
|
||||
const customPermissions = req.user.customPermissions;
|
||||
if (customPermissions.includes(permission) ||
|
||||
options.anyOf?.some(p => customPermissions.includes(p))) {
|
||||
hasPermission = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If no permission yet, check role-based permissions
|
||||
if (!hasPermission && req.user.roleId) {
|
||||
// Get permissions assigned to the role
|
||||
const rolePerms = await db.select()
|
||||
.from(rolePermissions)
|
||||
.where(eq(rolePermissions.roleId, req.user.roleId));
|
||||
|
||||
const userPermissions = rolePerms.map(rp => rp.permission);
|
||||
|
||||
// Check for the specific permission or any of the optional permissions
|
||||
if (userPermissions.includes(permission) ||
|
||||
options.anyOf?.some(p => userPermissions.includes(p))) {
|
||||
hasPermission = true;
|
||||
}
|
||||
|
||||
// If allOf is specified, ensure all required permissions are present
|
||||
if (options.allOf && options.allOf.length > 0) {
|
||||
hasPermission = options.allOf.every(p => userPermissions.includes(p));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user has the admin:system permission which grants all access
|
||||
if (!hasPermission && req.user.roleId) {
|
||||
const adminPerm = await db.select()
|
||||
.from(rolePermissions)
|
||||
.where(and(
|
||||
eq(rolePermissions.roleId, req.user.roleId),
|
||||
eq(rolePermissions.permission, "admin:system")
|
||||
))
|
||||
.limit(1);
|
||||
|
||||
if (adminPerm.length > 0) {
|
||||
hasPermission = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPermission) {
|
||||
return next();
|
||||
}
|
||||
|
||||
return res.status(403).json({ message: "Insufficient permissions" });
|
||||
} catch (error) {
|
||||
console.error("Permission check error:", error);
|
||||
return res.status(500).json({ message: "Internal server error" });
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Convenience middleware for requiring administrative access
|
||||
export function requireAdmin() {
|
||||
return requirePermission("admin:system");
|
||||
}
|
||||
|
||||
// Get permissions for a user
|
||||
export async function getUserPermissions(userId: number): Promise<string[]> {
|
||||
const [user] = await db.select()
|
||||
.from(users)
|
||||
.where(eq(users.id, userId))
|
||||
.limit(1);
|
||||
|
||||
if (!user || !user.roleId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const rolePerms = await db.select()
|
||||
.from(rolePermissions)
|
||||
.where(eq(rolePermissions.roleId, user.roleId));
|
||||
|
||||
return rolePerms.map(rp => rp.permission);
|
||||
}
|
||||
|
||||
// Initialize on server startup to ensure default roles
|
||||
export async function initializeRBAC() {
|
||||
try {
|
||||
// Check for existing data before doing anything
|
||||
const [adminRole] = await db.select()
|
||||
.from(roles)
|
||||
.where(eq(roles.name, "admin"))
|
||||
.limit(1);
|
||||
|
||||
if (adminRole) {
|
||||
console.log("RBAC already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log("RBAC initialization skipped - should be handled by updateSchema.ts");
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize RBAC:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+21
-16
@@ -3,8 +3,14 @@ import { createServer, type Server } from "http";
|
||||
import { setupAuth } from "./auth";
|
||||
import { setupSwagger } from "./swagger";
|
||||
import { storage } from "./storage";
|
||||
import { apiQuerySchema } from "@shared/schema";
|
||||
import { apiQuerySchema, PERMISSIONS } from "@shared/schema";
|
||||
import { ZodError } from "zod";
|
||||
import {
|
||||
requireAuth,
|
||||
requirePermission,
|
||||
requireAdmin,
|
||||
initializeRBAC
|
||||
} from "./authorization";
|
||||
|
||||
export async function registerRoutes(app: Express): Promise<Server> {
|
||||
// Setup authentication
|
||||
@@ -13,6 +19,9 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
// Setup Swagger documentation
|
||||
setupSwagger(app);
|
||||
|
||||
// Initialize Role Based Access Control system
|
||||
await initializeRBAC();
|
||||
|
||||
// Error handler for Zod validation errors
|
||||
const handleZodError = (err: ZodError, res: Response) => {
|
||||
return res.status(400).json({
|
||||
@@ -33,14 +42,6 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
};
|
||||
|
||||
// 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:
|
||||
@@ -49,6 +50,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
* tags: [LDAP Connections]
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of LDAP connections
|
||||
@@ -60,13 +62,11 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
* $ref: '#/components/schemas/LdapConnection'
|
||||
* 401:
|
||||
* $ref: '#/components/responses/UnauthorizedError'
|
||||
* 403:
|
||||
* $ref: '#/components/responses/ForbiddenError'
|
||||
*/
|
||||
app.get("/api/ldap-connections", async (req, res, next) => {
|
||||
app.get("/api/ldap-connections", requirePermission(PERMISSIONS.VIEW_LDAP_CONNECTIONS, { allowApiToken: true }), 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
|
||||
@@ -360,8 +360,13 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
|
||||
// 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" });
|
||||
if (token.userId !== req.user.id) {
|
||||
// Get the user's role
|
||||
const userRole = await storage.getRole(req.user.roleId!);
|
||||
|
||||
if (userRole?.name !== "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));
|
||||
|
||||
+24
-8
@@ -3,8 +3,9 @@ import {
|
||||
LdapConnection, InsertLdapConnection,
|
||||
AdUser, InsertAdUser, AdGroup, InsertAdGroup,
|
||||
AdOrgUnit, InsertAdOrgUnit, AdComputer, InsertAdComputer,
|
||||
AdDomain, InsertAdDomain,
|
||||
users, apiTokens, ldapConnections, adUsers, adGroups, adOrgUnits, adComputers, adDomains
|
||||
AdDomain, InsertAdDomain, Role,
|
||||
users, apiTokens, ldapConnections, adUsers, adGroups, adOrgUnits, adComputers, adDomains,
|
||||
roles
|
||||
} from "@shared/schema";
|
||||
import session from "express-session";
|
||||
import createMemoryStore from "memorystore";
|
||||
@@ -32,7 +33,11 @@ export interface IStorage {
|
||||
updateUser(id: number, user: Partial<User>): Promise<User | undefined>;
|
||||
deleteUser(id: number): Promise<boolean>;
|
||||
listUsers(): Promise<User[]>;
|
||||
|
||||
|
||||
// Role management
|
||||
getRole(id: number): Promise<Role | undefined>;
|
||||
getDefaultRole(): Promise<Role | undefined>;
|
||||
|
||||
// API Token management
|
||||
getApiToken(id: number): Promise<ApiToken | undefined>;
|
||||
getApiTokenByToken(token: string): Promise<ApiToken | undefined>;
|
||||
@@ -126,6 +131,17 @@ export class DatabaseStorage implements IStorage {
|
||||
async listUsers(): Promise<User[]> {
|
||||
return db.select().from(users);
|
||||
}
|
||||
|
||||
// Role management
|
||||
async getRole(id: number): Promise<Role | undefined> {
|
||||
const result = await db.select().from(roles).where(eq(roles.id, id));
|
||||
return result.length > 0 ? result[0] : undefined;
|
||||
}
|
||||
|
||||
async getDefaultRole(): Promise<Role | undefined> {
|
||||
const result = await db.select().from(roles).where(eq(roles.isDefault, true));
|
||||
return result.length > 0 ? result[0] : undefined;
|
||||
}
|
||||
|
||||
// API Token management
|
||||
async getApiToken(id: number): Promise<ApiToken | undefined> {
|
||||
@@ -210,7 +226,7 @@ export class DatabaseStorage implements IStorage {
|
||||
|
||||
return users.map(user => {
|
||||
const result: any = { id: user.id };
|
||||
properties.forEach(prop => {
|
||||
properties.forEach((prop: string) => {
|
||||
if ((user as any)[prop] !== undefined) {
|
||||
result[prop] = (user as any)[prop];
|
||||
}
|
||||
@@ -253,7 +269,7 @@ export class DatabaseStorage implements IStorage {
|
||||
|
||||
return groups.map(group => {
|
||||
const result: any = { id: group.id };
|
||||
properties.forEach(prop => {
|
||||
properties.forEach((prop: string) => {
|
||||
if ((group as any)[prop] !== undefined) {
|
||||
result[prop] = (group as any)[prop];
|
||||
}
|
||||
@@ -296,7 +312,7 @@ export class DatabaseStorage implements IStorage {
|
||||
|
||||
return orgUnits.map(ou => {
|
||||
const result: any = { id: ou.id };
|
||||
properties.forEach(prop => {
|
||||
properties.forEach((prop: string) => {
|
||||
if ((ou as any)[prop] !== undefined) {
|
||||
result[prop] = (ou as any)[prop];
|
||||
}
|
||||
@@ -339,7 +355,7 @@ export class DatabaseStorage implements IStorage {
|
||||
|
||||
return computers.map(computer => {
|
||||
const result: any = { id: computer.id };
|
||||
properties.forEach(prop => {
|
||||
properties.forEach((prop: string) => {
|
||||
if ((computer as any)[prop] !== undefined) {
|
||||
result[prop] = (computer as any)[prop];
|
||||
}
|
||||
@@ -382,7 +398,7 @@ export class DatabaseStorage implements IStorage {
|
||||
|
||||
return domains.map(domain => {
|
||||
const result: any = { id: domain.id };
|
||||
properties.forEach(prop => {
|
||||
properties.forEach((prop: string) => {
|
||||
if ((domain as any)[prop] !== undefined) {
|
||||
result[prop] = (domain as any)[prop];
|
||||
}
|
||||
|
||||
+10
-1
@@ -53,7 +53,7 @@ const swaggerOptions = {
|
||||
name: { type: "string" },
|
||||
token: { type: "string" },
|
||||
userId: { type: "integer" },
|
||||
permissions: { type: "object" },
|
||||
customPermissions: { type: "array", items: { type: "string" } },
|
||||
expiresAt: { type: "string", format: "date-time" },
|
||||
createdAt: { type: "string", format: "date-time" },
|
||||
},
|
||||
@@ -240,9 +240,18 @@ const swaggerOptions = {
|
||||
const swaggerSpec = swaggerJsdoc(swaggerOptions);
|
||||
|
||||
export function setupSwagger(app: Express) {
|
||||
// Mount at both paths for backward compatibility
|
||||
app.use("/api/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
|
||||
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
|
||||
|
||||
// Provide the JSON spec at multiple paths
|
||||
app.get("/api/swagger.json", (req, res) => {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.send(swaggerSpec);
|
||||
});
|
||||
|
||||
app.get("/api-docs/swagger.json", (req, res) => {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.send(swaggerSpec);
|
||||
});
|
||||
}
|
||||
|
||||
+148
-5
@@ -1,6 +1,104 @@
|
||||
import { pgTable, text, serial, integer, boolean, timestamp, jsonb } from "drizzle-orm/pg-core";
|
||||
import { pgTable, text, serial, integer, boolean, timestamp, jsonb, primaryKey } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { z } from "zod";
|
||||
import { relations } from "drizzle-orm";
|
||||
|
||||
// Role-based access control tables
|
||||
export const roles = pgTable("roles", {
|
||||
id: serial("id").primaryKey(),
|
||||
name: text("name").notNull().unique(),
|
||||
description: text("description"),
|
||||
isDefault: boolean("is_default").default(false),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
});
|
||||
|
||||
// Define available permissions
|
||||
export const PERMISSIONS = {
|
||||
// User management
|
||||
VIEW_USERS: "view:users",
|
||||
CREATE_USERS: "create:users",
|
||||
UPDATE_USERS: "update:users",
|
||||
DELETE_USERS: "delete:users",
|
||||
|
||||
// LDAP connections
|
||||
VIEW_LDAP_CONNECTIONS: "view:ldap_connections",
|
||||
CREATE_LDAP_CONNECTIONS: "create:ldap_connections",
|
||||
UPDATE_LDAP_CONNECTIONS: "update:ldap_connections",
|
||||
DELETE_LDAP_CONNECTIONS: "delete:ldap_connections",
|
||||
|
||||
// Active Directory management
|
||||
VIEW_AD_USERS: "view:ad_users",
|
||||
CREATE_AD_USERS: "create:ad_users",
|
||||
UPDATE_AD_USERS: "update:ad_users",
|
||||
DELETE_AD_USERS: "delete: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",
|
||||
|
||||
VIEW_AD_OUS: "view:ad_ous",
|
||||
CREATE_AD_OUS: "create:ad_ous",
|
||||
UPDATE_AD_OUS: "update:ad_ous",
|
||||
DELETE_AD_OUS: "delete:ad_ous",
|
||||
|
||||
VIEW_AD_COMPUTERS: "view:ad_computers",
|
||||
CREATE_AD_COMPUTERS: "create:ad_computers",
|
||||
UPDATE_AD_COMPUTERS: "update:ad_computers",
|
||||
DELETE_AD_COMPUTERS: "delete:ad_computers",
|
||||
|
||||
VIEW_AD_DOMAINS: "view:ad_domains",
|
||||
|
||||
// API Token management
|
||||
MANAGE_API_TOKENS: "manage:api_tokens",
|
||||
|
||||
// Administrative functions
|
||||
MANAGE_ROLES: "manage:roles",
|
||||
SYSTEM_ADMIN: "admin:system",
|
||||
} as const;
|
||||
|
||||
// Create a Zod schema for permissions
|
||||
export const permissionsSchema = z.enum([
|
||||
"view:users",
|
||||
"create:users",
|
||||
"update:users",
|
||||
"delete:users",
|
||||
"view:ldap_connections",
|
||||
"create:ldap_connections",
|
||||
"update:ldap_connections",
|
||||
"delete:ldap_connections",
|
||||
"view:ad_users",
|
||||
"create:ad_users",
|
||||
"update:ad_users",
|
||||
"delete:ad_users",
|
||||
"view:ad_groups",
|
||||
"create:ad_groups",
|
||||
"update:ad_groups",
|
||||
"delete:ad_groups",
|
||||
"view:ad_ous",
|
||||
"create:ad_ous",
|
||||
"update:ad_ous",
|
||||
"delete:ad_ous",
|
||||
"view:ad_computers",
|
||||
"create:ad_computers",
|
||||
"update:ad_computers",
|
||||
"delete:ad_computers",
|
||||
"view:ad_domains",
|
||||
"manage:api_tokens",
|
||||
"manage:roles",
|
||||
"admin:system"
|
||||
]);
|
||||
|
||||
export type Permission = z.infer<typeof permissionsSchema>;
|
||||
|
||||
export const rolePermissions = pgTable("role_permissions", {
|
||||
roleId: integer("role_id").notNull().references(() => roles.id, { onDelete: "cascade" }),
|
||||
permission: text("permission").notNull(),
|
||||
}, table => {
|
||||
return {
|
||||
pk: primaryKey({ columns: [table.roleId, table.permission] }),
|
||||
};
|
||||
});
|
||||
|
||||
// User schema for local authentication
|
||||
export const users = pgTable("users", {
|
||||
@@ -9,7 +107,7 @@ export const users = pgTable("users", {
|
||||
password: text("password").notNull(),
|
||||
email: text("email"),
|
||||
fullName: text("full_name"),
|
||||
role: text("role").default("user"),
|
||||
roleId: integer("role_id").references(() => roles.id),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
});
|
||||
|
||||
@@ -18,8 +116,9 @@ export const apiTokens = pgTable("api_tokens", {
|
||||
id: serial("id").primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
token: text("token").notNull().unique(),
|
||||
userId: integer("user_id").notNull(),
|
||||
permissions: jsonb("permissions").notNull(),
|
||||
userId: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||
roleId: integer("role_id").references(() => roles.id),
|
||||
customPermissions: jsonb("custom_permissions"),
|
||||
expiresAt: timestamp("expires_at"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
});
|
||||
@@ -100,7 +199,42 @@ export const adDomains = pgTable("ad_domains", {
|
||||
adProperties: jsonb("ad_properties"),
|
||||
});
|
||||
|
||||
// Define relations between tables
|
||||
export const rolesRelations = relations(roles, ({ many }) => ({
|
||||
permissions: many(rolePermissions),
|
||||
users: many(users),
|
||||
apiTokens: many(apiTokens),
|
||||
}));
|
||||
|
||||
export const rolePermissionsRelations = relations(rolePermissions, ({ one }) => ({
|
||||
role: one(roles, {
|
||||
fields: [rolePermissions.roleId],
|
||||
references: [roles.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const usersRelations = relations(users, ({ one, many }) => ({
|
||||
role: one(roles, {
|
||||
fields: [users.roleId],
|
||||
references: [roles.id],
|
||||
}),
|
||||
apiTokens: many(apiTokens),
|
||||
}));
|
||||
|
||||
export const apiTokensRelations = relations(apiTokens, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [apiTokens.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
role: one(roles, {
|
||||
fields: [apiTokens.roleId],
|
||||
references: [roles.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
// Generate insertion schemas
|
||||
export const insertRoleSchema = createInsertSchema(roles).omit({ id: true, createdAt: true });
|
||||
export const insertRolePermissionSchema = createInsertSchema(rolePermissions);
|
||||
export const insertUserSchema = createInsertSchema(users).omit({ id: true, createdAt: true });
|
||||
export const insertApiTokenSchema = createInsertSchema(apiTokens).omit({ id: true, createdAt: true });
|
||||
export const insertLdapConnectionSchema = createInsertSchema(ldapConnections).omit({ id: true, createdAt: true, lastConnected: true });
|
||||
@@ -127,7 +261,16 @@ export const apiQuerySchema = z.object({
|
||||
});
|
||||
|
||||
// Export types
|
||||
export type User = typeof users.$inferSelect;
|
||||
export type Role = typeof roles.$inferSelect;
|
||||
export type InsertRole = z.infer<typeof insertRoleSchema>;
|
||||
export type RolePermission = typeof rolePermissions.$inferSelect;
|
||||
export type InsertRolePermission = z.infer<typeof insertRolePermissionSchema>;
|
||||
export type User = typeof users.$inferSelect & {
|
||||
// For API token authentication
|
||||
tokenId?: number;
|
||||
customPermissions?: string[];
|
||||
role?: string;
|
||||
};
|
||||
export type InsertUser = z.infer<typeof insertUserSchema>;
|
||||
export type ApiToken = typeof apiTokens.$inferSelect;
|
||||
export type InsertApiToken = z.infer<typeof insertApiTokenSchema>;
|
||||
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
import pg from 'pg';
|
||||
import * as schema from './shared/schema';
|
||||
|
||||
const { Pool } = pg;
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
});
|
||||
|
||||
async function main() {
|
||||
console.log('Updating database schema...');
|
||||
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
// Create roles table if it doesn't exist
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS "roles" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"is_default" boolean DEFAULT false,
|
||||
"created_at" timestamp DEFAULT now(),
|
||||
CONSTRAINT "roles_name_unique" UNIQUE("name")
|
||||
);
|
||||
`);
|
||||
|
||||
// Create role_permissions table if it doesn't exist
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS "role_permissions" (
|
||||
"role_id" integer NOT NULL,
|
||||
"permission" text NOT NULL,
|
||||
CONSTRAINT "role_permissions_role_id_permission_pk" PRIMARY KEY("role_id","permission")
|
||||
);
|
||||
`);
|
||||
|
||||
// Add foreign key constraint to role_permissions table
|
||||
await client.query(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'role_permissions_role_id_roles_id_fk'
|
||||
) THEN
|
||||
ALTER TABLE "role_permissions"
|
||||
ADD CONSTRAINT "role_permissions_role_id_roles_id_fk"
|
||||
FOREIGN KEY ("role_id") REFERENCES "roles"("id") ON DELETE cascade;
|
||||
END IF;
|
||||
END $$;
|
||||
`);
|
||||
|
||||
// Update users table to add role_id column if it doesn't exist
|
||||
await client.query(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'users' AND column_name = 'role_id'
|
||||
) THEN
|
||||
ALTER TABLE "users" ADD COLUMN "role_id" integer;
|
||||
|
||||
ALTER TABLE "users"
|
||||
ADD CONSTRAINT "users_role_id_roles_id_fk"
|
||||
FOREIGN KEY ("role_id") REFERENCES "roles"("id");
|
||||
END IF;
|
||||
END $$;
|
||||
`);
|
||||
|
||||
// Update api_tokens table
|
||||
await client.query(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'api_tokens' AND column_name = 'role_id'
|
||||
) THEN
|
||||
ALTER TABLE "api_tokens" ADD COLUMN "role_id" integer;
|
||||
|
||||
ALTER TABLE "api_tokens"
|
||||
ADD CONSTRAINT "api_tokens_role_id_roles_id_fk"
|
||||
FOREIGN KEY ("role_id") REFERENCES "roles"("id");
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'api_tokens' AND column_name = 'permissions'
|
||||
) THEN
|
||||
ALTER TABLE "api_tokens" RENAME COLUMN "permissions" TO "custom_permissions";
|
||||
END IF;
|
||||
END $$;
|
||||
`);
|
||||
|
||||
await client.query('COMMIT');
|
||||
console.log('Schema update completed');
|
||||
|
||||
// Insert default roles if they don't exist
|
||||
await client.query('BEGIN');
|
||||
|
||||
const { rows: adminExists } = await client.query(`
|
||||
SELECT id FROM roles WHERE name = 'admin' LIMIT 1
|
||||
`);
|
||||
|
||||
if (adminExists.length === 0) {
|
||||
console.log('Creating admin role...');
|
||||
const { rows: adminRole } = await client.query(`
|
||||
INSERT INTO roles (name, description, is_default)
|
||||
VALUES ('admin', 'Administrator with full system access', false)
|
||||
RETURNING id
|
||||
`);
|
||||
|
||||
// Add all permissions to admin role
|
||||
for (const permission of Object.values(schema.PERMISSIONS)) {
|
||||
await client.query(`
|
||||
INSERT INTO role_permissions (role_id, permission)
|
||||
VALUES ($1, $2)
|
||||
`, [adminRole[0].id, permission]);
|
||||
}
|
||||
}
|
||||
|
||||
const { rows: userExists } = await client.query(`
|
||||
SELECT id FROM roles WHERE name = 'user' LIMIT 1
|
||||
`);
|
||||
|
||||
if (userExists.length === 0) {
|
||||
console.log('Creating user role...');
|
||||
const { rows: userRole } = await client.query(`
|
||||
INSERT INTO roles (name, description, is_default)
|
||||
VALUES ('user', 'Regular user with limited access', true)
|
||||
RETURNING id
|
||||
`);
|
||||
|
||||
// Add basic permissions to user role
|
||||
const basicPermissions = [
|
||||
schema.PERMISSIONS.VIEW_USERS,
|
||||
schema.PERMISSIONS.VIEW_LDAP_CONNECTIONS,
|
||||
schema.PERMISSIONS.VIEW_AD_USERS,
|
||||
schema.PERMISSIONS.VIEW_AD_GROUPS,
|
||||
schema.PERMISSIONS.VIEW_AD_OUS,
|
||||
schema.PERMISSIONS.VIEW_AD_COMPUTERS,
|
||||
schema.PERMISSIONS.VIEW_AD_DOMAINS
|
||||
];
|
||||
|
||||
for (const permission of basicPermissions) {
|
||||
await client.query(`
|
||||
INSERT INTO role_permissions (role_id, permission)
|
||||
VALUES ($1, $2)
|
||||
`, [userRole[0].id, permission]);
|
||||
}
|
||||
}
|
||||
|
||||
const { rows: apiExists } = await client.query(`
|
||||
SELECT id FROM roles WHERE name = 'api' LIMIT 1
|
||||
`);
|
||||
|
||||
if (apiExists.length === 0) {
|
||||
console.log('Creating api role...');
|
||||
await client.query(`
|
||||
INSERT INTO roles (name, description, is_default)
|
||||
VALUES ('api', 'API access with customizable permissions', false)
|
||||
`);
|
||||
}
|
||||
|
||||
// Update existing users to have the admin role if they have no role assigned
|
||||
await client.query(`
|
||||
DO $$
|
||||
DECLARE
|
||||
admin_role_id integer;
|
||||
BEGIN
|
||||
SELECT id INTO admin_role_id FROM roles WHERE name = 'admin' LIMIT 1;
|
||||
|
||||
IF admin_role_id IS NOT NULL THEN
|
||||
UPDATE users SET role_id = admin_role_id WHERE role_id IS NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`);
|
||||
|
||||
await client.query('COMMIT');
|
||||
console.log('Data migration completed successfully');
|
||||
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
console.error('Migration failed:', error);
|
||||
} finally {
|
||||
client.release();
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user