mirror of
https://github.com/freedbygrace/ActiveDirectoryManager.git
synced 2026-08-19 14:57:03 +00:00
Add LDAP authentication and dynamic group membership rules.
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/3dbf05fe-5071-4ef8-b5a9-67982811a801.jpg
This commit is contained in:
@@ -82,6 +82,7 @@ export default function SettingsPage() {
|
|||||||
<TabsTrigger value="general">General</TabsTrigger>
|
<TabsTrigger value="general">General</TabsTrigger>
|
||||||
<TabsTrigger value="security">Security</TabsTrigger>
|
<TabsTrigger value="security">Security</TabsTrigger>
|
||||||
<TabsTrigger value="api">API</TabsTrigger>
|
<TabsTrigger value="api">API</TabsTrigger>
|
||||||
|
<TabsTrigger value="authentication">Authentication</TabsTrigger>
|
||||||
<TabsTrigger value="notifications">Notifications</TabsTrigger>
|
<TabsTrigger value="notifications">Notifications</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
@@ -272,6 +273,182 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="authentication">
|
||||||
|
<div className="grid gap-6">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Authentication Methods</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Configure login methods for your application
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label>Local Authentication</Label>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Enable username and password login
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Switch defaultChecked />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label>Allow User Registration</Label>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Allow new users to register accounts
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Switch defaultChecked />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator className="my-4" />
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-sm font-medium">LDAP Authentication</h3>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label>Enable LDAP Login</Label>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Allow users to login via LDAP
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Switch id="enable-ldap" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="ldap-connection-name">Connection Display Name</Label>
|
||||||
|
<Input id="ldap-connection-name" placeholder="LDAP Authentication" />
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Name shown to users on the login screen
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="ldap-url">LDAP Server URL</Label>
|
||||||
|
<Input id="ldap-url" placeholder="ldap://ldap.example.com:389" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="ldap-bind-dn">Bind DN</Label>
|
||||||
|
<Input id="ldap-bind-dn" placeholder="cn=admin,dc=example,dc=com" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="ldap-bind-password">Bind Password</Label>
|
||||||
|
<Input id="ldap-bind-password" type="password" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="ldap-search-base">Search Base</Label>
|
||||||
|
<Input id="ldap-search-base" placeholder="ou=users,dc=example,dc=com" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="ldap-search-filter">Search Filter</Label>
|
||||||
|
<Input id="ldap-search-filter" placeholder="(uid={{username}})" />
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Use {{username}} as a placeholder for the user's input
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label>TLS/SSL</Label>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Require secure connection
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Switch id="ldap-tls" defaultChecked />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator className="my-4" />
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-sm font-medium">OpenID Connect Authentication</h3>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label>Enable OIDC Login</Label>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Allow users to login via OpenID Connect
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Switch id="enable-oidc" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="oidc-provider-name">Provider Display Name</Label>
|
||||||
|
<Input id="oidc-provider-name" placeholder="Single Sign-On" />
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Name shown to users on the login screen
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="oidc-issuer">Issuer</Label>
|
||||||
|
<Input id="oidc-issuer" placeholder="https://accounts.google.com" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="oidc-auth-url">Authorization URL</Label>
|
||||||
|
<Input id="oidc-auth-url" placeholder="https://accounts.google.com/o/oauth2/v2/auth" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="oidc-token-url">Token URL</Label>
|
||||||
|
<Input id="oidc-token-url" placeholder="https://oauth2.googleapis.com/token" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="oidc-userinfo-url">UserInfo URL</Label>
|
||||||
|
<Input id="oidc-userinfo-url" placeholder="https://openidconnect.googleapis.com/v1/userinfo" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="oidc-client-id">Client ID</Label>
|
||||||
|
<Input id="oidc-client-id" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="oidc-client-secret">Client Secret</Label>
|
||||||
|
<Input id="oidc-client-secret" type="password" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="oidc-callback-url">Callback URL</Label>
|
||||||
|
<Input id="oidc-callback-url" placeholder="http://localhost:3000/api/auth/oidc/callback" />
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Must match the redirect URI configured with your OIDC provider
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Requested Scopes</Label>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Switch id="scope-openid" defaultChecked disabled />
|
||||||
|
<Label htmlFor="scope-openid" className="text-sm font-normal">openid</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Switch id="scope-profile" defaultChecked />
|
||||||
|
<Label htmlFor="scope-profile" className="text-sm font-normal">profile</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Switch id="scope-email" defaultChecked />
|
||||||
|
<Label htmlFor="scope-email" className="text-sm font-normal">email</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="notifications">
|
<TabsContent value="notifications">
|
||||||
<div className="grid gap-6">
|
<div className="grid gap-6">
|
||||||
<Card>
|
<Card>
|
||||||
|
|||||||
@@ -57,6 +57,25 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
// Initialize Role Based Access Control system
|
// Initialize Role Based Access Control system
|
||||||
await initializeRBAC();
|
await initializeRBAC();
|
||||||
|
|
||||||
|
// Authentication info and providers
|
||||||
|
app.get("/api/auth/providers", (req, res) => {
|
||||||
|
const ldapEnabled = process.env.LDAP_ENABLED === "true";
|
||||||
|
const oidcEnabled = process.env.OIDC_ENABLED === "true";
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
ldap: {
|
||||||
|
enabled: ldapEnabled,
|
||||||
|
connectionName: ldapEnabled ? process.env.LDAP_CONNECTION_NAME || "LDAP Authentication" : null
|
||||||
|
},
|
||||||
|
oidc: {
|
||||||
|
enabled: oidcEnabled,
|
||||||
|
providerName: oidcEnabled ? process.env.OIDC_PROVIDER_NAME || "Single Sign-On" : null
|
||||||
|
},
|
||||||
|
localEnabled: process.env.DISABLE_LOCAL_AUTH !== "true",
|
||||||
|
registrationEnabled: process.env.DISABLE_REGISTRATION !== "true"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Apply rate limiting middleware for API routes
|
// Apply rate limiting middleware for API routes
|
||||||
const apiLimiter = rateLimit({
|
const apiLimiter = rateLimit({
|
||||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||||
|
|||||||
@@ -146,6 +146,40 @@ export const ldapConnections = pgTable("ldap_connections", {
|
|||||||
createdAt: timestamp("created_at").defaultNow(),
|
createdAt: timestamp("created_at").defaultNow(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Dynamic Group Membership Rules
|
||||||
|
export const dynamicGroupRules = pgTable("dynamic_group_rules", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
description: text("description"),
|
||||||
|
targetGroup: text("target_group").notNull(), // DN of the target AD group
|
||||||
|
enabled: boolean("enabled").default(true),
|
||||||
|
schedule: text("schedule").default("0 0 * * *"), // Cron format (default: daily at midnight)
|
||||||
|
createdAt: timestamp("created_at").defaultNow(),
|
||||||
|
updatedAt: timestamp("updated_at").defaultNow(),
|
||||||
|
lastRun: timestamp("last_run"),
|
||||||
|
lastRunStatus: text("last_run_status"),
|
||||||
|
variablePattern: text("variable_pattern"), // Pattern for dynamic group name, e.g. "{{department}}-Users"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Rule conditions (filter logic)
|
||||||
|
export const dynamicGroupConditions = pgTable("dynamic_group_conditions", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
|
ruleId: integer("rule_id").references(() => dynamicGroupRules.id, { onDelete: "cascade" }).notNull(),
|
||||||
|
parentId: integer("parent_id").references(() => dynamicGroupConditions.id, { onDelete: "cascade" }),
|
||||||
|
type: text("type").notNull(), // "condition", "group"
|
||||||
|
operator: text("operator"), // "and", "or", "not" for groups; "equals", "contains", "startsWith", etc. for conditions
|
||||||
|
attribute: text("attribute"), // LDAP attribute name (e.g., "department", "title")
|
||||||
|
value: text("value"), // Value to compare against
|
||||||
|
position: integer("position").default(0), // For ordering conditions within a group
|
||||||
|
});
|
||||||
|
|
||||||
|
// Many-to-many relationship between rules and connections
|
||||||
|
export const dynamicGroupRuleConnections = pgTable("dynamic_group_rule_connections", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
|
ruleId: integer("rule_id").references(() => dynamicGroupRules.id, { onDelete: "cascade" }).notNull(),
|
||||||
|
connectionId: integer("connection_id").references(() => ldapConnections.id, { onDelete: "cascade" }).notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
// Active Directory schemas
|
// Active Directory schemas
|
||||||
export const adUsers = pgTable("ad_users", {
|
export const adUsers = pgTable("ad_users", {
|
||||||
id: serial("id").primaryKey(),
|
id: serial("id").primaryKey(),
|
||||||
@@ -295,6 +329,37 @@ export const apiTokensRelations = relations(apiTokens, ({ one }) => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Dynamic Group Rules Relations
|
||||||
|
export const dynamicGroupRulesRelations = relations(dynamicGroupRules, ({ many }) => ({
|
||||||
|
conditions: many(dynamicGroupConditions),
|
||||||
|
connections: many(dynamicGroupRuleConnections),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const dynamicGroupConditionsRelations = relations(dynamicGroupConditions, ({ one, many }) => ({
|
||||||
|
rule: one(dynamicGroupRules, {
|
||||||
|
fields: [dynamicGroupConditions.ruleId],
|
||||||
|
references: [dynamicGroupRules.id],
|
||||||
|
}),
|
||||||
|
parent: one(dynamicGroupConditions, {
|
||||||
|
fields: [dynamicGroupConditions.parentId],
|
||||||
|
references: [dynamicGroupConditions.id],
|
||||||
|
}),
|
||||||
|
children: many(dynamicGroupConditions, {
|
||||||
|
relationName: 'parent-child-conditions'
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const dynamicGroupRuleConnectionsRelations = relations(dynamicGroupRuleConnections, ({ one }) => ({
|
||||||
|
rule: one(dynamicGroupRules, {
|
||||||
|
fields: [dynamicGroupRuleConnections.ruleId],
|
||||||
|
references: [dynamicGroupRules.id],
|
||||||
|
}),
|
||||||
|
connection: one(ldapConnections, {
|
||||||
|
fields: [dynamicGroupRuleConnections.connectionId],
|
||||||
|
references: [ldapConnections.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
// Generate insertion schemas
|
// Generate insertion schemas
|
||||||
export const insertRoleSchema = createInsertSchema(roles).omit({ id: true, createdAt: true });
|
export const insertRoleSchema = createInsertSchema(roles).omit({ id: true, createdAt: true });
|
||||||
export const insertRolePermissionSchema = createInsertSchema(rolePermissions);
|
export const insertRolePermissionSchema = createInsertSchema(rolePermissions);
|
||||||
@@ -308,6 +373,9 @@ export const insertAdComputerSchema = createInsertSchema(adComputers).omit({ id:
|
|||||||
export const insertAdDomainSchema = createInsertSchema(adDomains).omit({ id: true });
|
export const insertAdDomainSchema = createInsertSchema(adDomains).omit({ id: true });
|
||||||
export const insertAdSiteSchema = createInsertSchema(adSites).omit({ id: true });
|
export const insertAdSiteSchema = createInsertSchema(adSites).omit({ id: true });
|
||||||
export const insertAdSubnetSchema = createInsertSchema(adSubnets).omit({ id: true });
|
export const insertAdSubnetSchema = createInsertSchema(adSubnets).omit({ id: true });
|
||||||
|
export const insertDynamicGroupRuleSchema = createInsertSchema(dynamicGroupRules).omit({ id: true, createdAt: true, updatedAt: true, lastRun: true, lastRunStatus: true });
|
||||||
|
export const insertDynamicGroupConditionSchema = createInsertSchema(dynamicGroupConditions).omit({ id: true });
|
||||||
|
export const insertDynamicGroupRuleConnectionSchema = createInsertSchema(dynamicGroupRuleConnections).omit({ id: true });
|
||||||
|
|
||||||
// Login schema
|
// Login schema
|
||||||
export const loginSchema = z.object({
|
export const loginSchema = z.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user