my own indieAuth provider! indiko.dunkirk.sh/docs
indieauth oauth2-server
at main 156 lines 4.0 kB view raw
1import { authenticate } from "ldap-authentication"; 2import { db } from "./db"; 3 4interface LdapUser { 5 username: string; 6 id: number; 7 status: string; 8 created_at: number; 9} 10 11interface AuditResult { 12 total: number; 13 active: number; 14 orphaned: number; 15 errors: number; 16 orphanedUsers: Array<{ 17 username: string; 18 id: number; 19 status: string; 20 createdAt: number; 21 }>; 22} 23 24export async function checkLdapUser(username: string): Promise<boolean> { 25 try { 26 const user = await authenticate({ 27 ldapOpts: { 28 url: process.env.LDAP_URL || "ldap://localhost:389", 29 }, 30 adminDn: process.env.LDAP_ADMIN_DN || "", 31 adminPassword: process.env.LDAP_ADMIN_PASSWORD || "", 32 userSearchBase: process.env.LDAP_USER_SEARCH_BASE || "dc=example,dc=com", 33 usernameAttribute: process.env.LDAP_USERNAME_ATTRIBUTE || "uid", 34 username: username, 35 verifyUserExists: true, 36 }); 37 return !!user; 38 } catch (error) { 39 // User not found or invalid credentials (expected for non-existence check) 40 return false; 41 } 42} 43 44export async function checkLdapGroupMembership( 45 username: string, 46 userDn: string, 47): Promise<boolean> { 48 if (!process.env.LDAP_GROUP_DN) { 49 return true; // No group restriction configured 50 } 51 52 try { 53 const groupDn = process.env.LDAP_GROUP_DN; 54 const groupClass = process.env.LDAP_GROUP_CLASS || "groupOfUniqueNames"; 55 const memberAttribute = 56 process.env.LDAP_GROUP_MEMBER_ATTRIBUTE || "uniqueMember"; 57 58 const user = await authenticate({ 59 ldapOpts: { 60 url: process.env.LDAP_URL || "ldap://localhost:389", 61 }, 62 userDn: userDn, 63 userSearchBase: process.env.LDAP_USER_SEARCH_BASE || "dc=example,dc=com", 64 usernameAttribute: process.env.LDAP_USERNAME_ATTRIBUTE || "uid", 65 username: username, 66 verifyUserExists: true, 67 groupsSearchBase: groupDn, 68 groupClass: groupClass, 69 groupMemberAttribute: memberAttribute, 70 }); 71 72 // If user was found and authenticate returns it, groups are available 73 return !!user; 74 } catch (error) { 75 console.error("LDAP group membership check failed:", error); 76 return false; 77 } 78} 79 80export async function getLdapAccounts(): Promise<AuditResult> { 81 console.log("🔍 Starting LDAP orphan account audit...\n"); 82 83 // Get all LDAP-provisioned users 84 const ldapUsers = db 85 .query( 86 "SELECT id, username, status, created_at FROM users WHERE provisioned_via_ldap = 1", 87 ) 88 .all() as LdapUser[]; 89 90 const result: AuditResult = { 91 total: ldapUsers.length, 92 active: 0, 93 orphaned: 0, 94 errors: 0, 95 orphanedUsers: [], 96 }; 97 98 console.log(`Found ${result.total} LDAP-provisioned accounts\n`); 99 100 // Check each user against LDAP 101 for (const user of ldapUsers) { 102 process.stdout.write(`Checking ${user.username}... `); 103 104 try { 105 const existsInLdap = await checkLdapUser(user.username); 106 107 if (existsInLdap) { 108 console.log("✅ Found in LDAP"); 109 result.active++; 110 } else { 111 console.log("❌ NOT FOUND in LDAP"); 112 result.orphaned++; 113 result.orphanedUsers.push({ 114 username: user.username, 115 id: user.id, 116 status: user.status, 117 createdAt: user.created_at, 118 }); 119 } 120 } catch (error) { 121 console.log("⚠️ Error checking LDAP"); 122 result.errors++; 123 console.error( 124 ` Error: ${error instanceof Error ? error.message : String(error)}`, 125 ); 126 } 127 } 128 129 return result; 130} 131 132export async function updateOrphanedAccounts( 133 result: AuditResult, 134 action: "suspend" | "deactivate" | "remove", 135): Promise<void> { 136 const newStatus = action === "suspend" ? "suspended" : "inactive"; 137 138 console.log( 139 `\n📝 Updating ${result.orphaned} orphaned account(s) to status: '${newStatus}'`, 140 ); 141 142 for (const user of result.orphanedUsers) { 143 if (action === "remove") { 144 db.query("DELETE FROM users WHERE id = ?").run(user.id); 145 console.log(` Removed: ${user.username}`); 146 continue; 147 } 148 db.query("UPDATE users SET status = ? WHERE id = ?").run( 149 newStatus, 150 user.id, 151 ); 152 console.log(` Updated: ${user.username}`); 153 } 154 155 console.log(`\n✅ Updated ${result.orphaned} account(s)`); 156}