diff --git a/client/src/pages/settings-page.tsx b/client/src/pages/settings-page.tsx index c7cf6d2..903e346 100644 --- a/client/src/pages/settings-page.tsx +++ b/client/src/pages/settings-page.tsx @@ -82,6 +82,7 @@ export default function SettingsPage() { General Security API + Authentication Notifications @@ -272,6 +273,182 @@ export default function SettingsPage() { + +
+ + + Authentication Methods + + Configure login methods for your application + + + +
+
+ +
+ Enable username and password login +
+
+ +
+ +
+
+ +
+ Allow new users to register accounts +
+
+ +
+ + + +
+

LDAP Authentication

+ +
+
+ +
+ Allow users to login via LDAP +
+
+ +
+ +
+ + +

+ Name shown to users on the login screen +

+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +

+ Use {{username}} as a placeholder for the user's input +

+
+ +
+
+ +
+ Require secure connection +
+
+ +
+
+ + + +
+

OpenID Connect Authentication

+ +
+
+ +
+ Allow users to login via OpenID Connect +
+
+ +
+ +
+ + +

+ Name shown to users on the login screen +

+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +

+ Must match the redirect URI configured with your OIDC provider +

+
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+
diff --git a/server/routes.ts b/server/routes.ts index f055e2b..a080949 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -57,6 +57,25 @@ export async function registerRoutes(app: Express): Promise { // Initialize Role Based Access Control system 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 const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes diff --git a/shared/schema.ts b/shared/schema.ts index 2752423..ca92a9b 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -146,6 +146,40 @@ export const ldapConnections = pgTable("ldap_connections", { 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 export const adUsers = pgTable("ad_users", { 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 export const insertRoleSchema = createInsertSchema(roles).omit({ id: true, createdAt: true }); 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 insertAdSiteSchema = createInsertSchema(adSites).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 export const loginSchema = z.object({