UNPKG

@kitiumai/auth-mongo

Version:

Enterprise-grade MongoDB storage adapter for @kitiumai/auth with full support for users, sessions, OAuth links, API keys, 2FA, RBAC, and SSO

1,360 lines (1,358 loc) 45.5 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; // src/index.ts var src_exports = {}; __export(src_exports, { MongoStorageAdapter: () => MongoStorageAdapter }); module.exports = __toCommonJS(src_exports); // src/mongo.ts var import_mongodb = require("mongodb"); var import_logger = require("@kitiumai/logger"); var import_error = require("@kitiumai/error"); var import_node_crypto = require("crypto"); var import_promises = require("timers/promises"); var generateId = () => { return (0, import_node_crypto.randomBytes)(16).toString("hex"); }; var generateApiKey = (prefix) => { const secret = (0, import_node_crypto.randomBytes)(32).toString("hex"); return `${prefix}_${secret}`; }; var hashApiKey = (key, algorithm, salt, scryptParams = { N: 16384, r: 8, p: 1 }) => { if (algorithm === "scrypt") { const result = (0, import_node_crypto.scryptSync)(key, salt ?? Buffer.alloc(0), 32, scryptParams); return result.toString("hex"); } return (0, import_node_crypto.createHash)("sha256").update(key).digest("hex"); }; var MongoStorageAdapter = class { constructor(connectionString, options) { __publicField(this, "client"); __publicField(this, "db", null); __publicField(this, "logger", (0, import_logger.getLogger)()); __publicField(this, "defaultRetries"); __publicField(this, "databaseName"); __publicField(this, "operationTimeoutMS"); __publicField(this, "apiKeyHashAlgorithm"); __publicField(this, "apiKeySalt"); const { operationTimeoutMS, maxRetries, databaseName, apiKeyHashAlgorithm, apiKeySalt, ...clientOptions } = options ?? {}; this.client = new import_mongodb.MongoClient(connectionString, { maxPoolSize: 10, minPoolSize: 2, maxIdleTimeMS: 3e4, serverSelectionTimeoutMS: 5e3, ...clientOptions }); this.defaultRetries = maxRetries ?? 2; this.databaseName = databaseName ?? "auth"; this.operationTimeoutMS = operationTimeoutMS ?? null; this.apiKeyHashAlgorithm = apiKeyHashAlgorithm ?? "sha256"; if (apiKeySalt !== void 0) { this.apiKeySalt = apiKeySalt; } } async connect() { try { await this.client.connect(); this.db = this.client.db(this.databaseName); await this.runMigrations(); this.logger.info("MongoDB adapter connected successfully"); } catch (error) { this.logger.error("Failed to connect to MongoDB", { error }); throw new import_error.InternalError({ code: "auth-mongo/connection_failed", message: "Failed to connect to MongoDB", severity: "error", retryable: true, cause: error }); } } async disconnect() { try { await this.client.close(); this.logger.info("MongoDB adapter disconnected"); } catch (error) { this.logger.error("Error disconnecting from MongoDB", { error }); throw new import_error.InternalError({ code: "auth-mongo/disconnect_failed", message: "Failed to disconnect from MongoDB", severity: "error", retryable: false, cause: error }); } } getDatabase() { if (!this.db) { throw new import_error.InternalError({ code: "auth-mongo/not_connected", message: "MongoDB client not connected", severity: "error", retryable: false }); } return this.db; } getCollection(name) { return this.getDatabase().collection(name); } async withRetry(operation, operationName) { let lastError; for (let attempt = 0; attempt <= this.defaultRetries; attempt += 1) { try { const start = Date.now(); let timeoutHandle; const timeoutPromise = this.operationTimeoutMS !== null ? new Promise((_, reject) => { timeoutHandle = setTimeout(() => { reject( new import_error.InternalError({ code: "auth-mongo/operation_timeout", message: `MongoDB operation exceeded ${this.operationTimeoutMS}ms: ${operationName}`, severity: "error", retryable: false }) ); }, this.operationTimeoutMS); }) : null; const result = timeoutPromise ? await Promise.race([operation(), timeoutPromise]) : await operation(); if (timeoutHandle) { clearTimeout(timeoutHandle); } const durationMs = Date.now() - start; this.logger.debug("mongo.operation", { operation: operationName, durationMs }); return result; } catch (error) { lastError = error; const retryable = attempt < this.defaultRetries; this.logger.warn("MongoDB operation failed", { operation: operationName, attempt, retryable, error }); if (!retryable) { if (error instanceof import_error.InternalError && error.code === "auth-mongo/operation_timeout") { throw error; } throw new import_error.InternalError({ code: "auth-mongo/operation_failed", message: `Failed to execute MongoDB operation: ${operationName}`, severity: "error", retryable: false, cause: error }); } await (0, import_promises.setTimeout)(50 * 2 ** attempt); } } throw lastError; } async healthCheck() { const start = Date.now(); try { await this.getDatabase().admin().ping(); return { status: "ok", latencyMs: Date.now() - start }; } catch (error) { this.logger.error("MongoDB health check failed", { error }); return { status: "error", latencyMs: Date.now() - start }; } } async runMigrations() { const migrationsCollection = this.getCollection( "auth_migrations" ); const migrationId = "0001_initial_schema_v2"; const existing = await migrationsCollection.findOne({ id: migrationId }); if (existing) { return; } await this.createCollections(); await this.createIndexes(); await migrationsCollection.insertOne({ id: migrationId, appliedAt: /* @__PURE__ */ new Date() }); } async createCollections() { const db = this.getDatabase(); const collections = [ "users", "api_keys", "sessions", "organizations", "email_verification_tokens", "email_verification_token_attempts", "auth_events", "roles", "user_roles", "sso_providers", "sso_links", "sso_sessions", "twofa_devices", "twofa_backup_codes", "twofa_sessions" ]; const existingCollections = await db.listCollections().toArray(); const existingNames = new Set(existingCollections.map((c) => c.name)); for (const collectionName of collections) { if (!existingNames.has(collectionName)) { await db.createCollection(collectionName); } } } async createIndexes() { await this.getCollection("users").createIndexes([ { key: { email: 1 }, unique: true, sparse: true }, { key: { createdAt: 1 } } ]); await this.getCollection("api_keys").createIndexes([ { key: { principalId: 1 } }, { key: { hash: 1 }, unique: true }, { key: { prefix: 1, lastFour: 1 } }, { key: { expiresAt: 1 }, sparse: true } ]); await this.getCollection("sessions").createIndexes([ { key: { userId: 1 } }, { key: { expiresAt: 1 }, expireAfterSeconds: 0 } // TTL index ]); await this.getCollection("organizations").createIndexes([ { key: { plan: 1 } }, { key: { createdAt: 1 } } ]); await this.getCollection("email_verification_tokens").createIndexes([ { key: { email: 1 } }, { key: { type: 1 } }, { key: { codeHash: 1 } }, { key: { expiresAt: 1 }, expireAfterSeconds: 0 } // TTL index ]); await this.getCollection("auth_events").createIndexes([ { key: { principalId: 1 } }, { key: { type: 1 } }, { key: { timestamp: 1 } } ]); await this.getCollection("roles").createIndexes([ { key: { orgId: 1 } }, { key: { createdAt: 1 } } ]); await this.getCollection("user_roles").createIndexes([ { key: { userId: 1 } }, { key: { roleId: 1 } }, { key: { orgId: 1 } } ]); await this.getCollection("sso_providers").createIndexes([ { key: { orgId: 1 } }, { key: { type: 1 } } ]); await this.getCollection("sso_links").createIndexes([ { key: { userId: 1 } }, { key: { providerId: 1 } }, { key: { providerType: 1, providerSubject: 1 } } ]); await this.getCollection("sso_sessions").createIndexes([ { key: { userId: 1 } }, { key: { providerId: 1 } }, { key: { expiresAt: 1 }, expireAfterSeconds: 0 } // TTL index ]); await this.getCollection("twofa_devices").createIndexes([ { key: { userId: 1 } }, { key: { createdAt: 1 } } ]); await this.getCollection("twofa_backup_codes").createIndexes([ { key: { userId: 1 } }, { key: { used: 1 } } ]); await this.getCollection("twofa_sessions").createIndexes([ { key: { userId: 1 } }, { key: { sessionId: 1 } }, { key: { expiresAt: 1 }, expireAfterSeconds: 0 } // TTL index ]); } /** * Create an API key with plaintext secret (convenience method) */ async createApiKeyWithSecret(principalId, scopes, prefix = "api") { const key = generateApiKey(prefix); const hash = hashApiKey(key, this.apiKeyHashAlgorithm, this.apiKeySalt); const parts = key.split("_"); const lastFour = parts[parts.length - 1].slice(-4); const record = await this.createApiKey({ principalId, hash, prefix, lastFour, scopes }); return { record, key }; } hashApiKeySecret(secret) { return hashApiKey(secret, this.apiKeyHashAlgorithm, this.apiKeySalt); } async verifyApiKeySecret(rawKey) { const parts = rawKey.split("_"); if (parts.length < 2) { return null; } const prefix = parts[0]; const lastFour = parts[parts.length - 1].slice(-4); const candidates = await this.getApiKeysByPrefixAndLastFour(prefix, lastFour); if (!candidates.length) { return null; } const hashed = this.hashApiKeySecret(rawKey); const hashedBuffer = Buffer.from(hashed, "hex"); for (const candidate of candidates) { const candidateHash = Buffer.from(candidate.hash, "hex"); if (candidateHash.length === hashedBuffer.length && (0, import_node_crypto.timingSafeEqual)(candidateHash, hashedBuffer)) { return candidate; } } return null; } async rotateApiKey(id, options) { const existing = await this.getApiKey(id); if (!existing) { throw new import_error.InternalError({ code: "auth-mongo/api_key_not_found", message: "API key not found for rotation", severity: "error", retryable: false }); } const rotationExpiry = options?.expiresOldKeysAt ?? /* @__PURE__ */ new Date(); await this.updateApiKey(id, { expiresAt: rotationExpiry }); return this.createApiKeyWithSecret( existing.principalId, options?.scopes ?? existing.scopes, existing.prefix ); } // API Key methods async createApiKey(data) { return this.withRetry(async () => { const id = generateId(); const now = /* @__PURE__ */ new Date(); const doc = { _id: id, principalId: data.principalId, hash: data.hash, prefix: data.prefix, lastFour: data.lastFour, scopes: data.scopes, metadata: data.metadata ?? {}, expiresAt: data.expiresAt ?? null, createdAt: now, updatedAt: now }; await this.getCollection("api_keys").insertOne(doc); return this.mapApiKeyRecord(doc); }, "createApiKey"); } async getApiKey(id) { return this.withRetry(async () => { const doc = await this.getCollection("api_keys").findOne({ _id: id }); return doc ? this.mapApiKeyRecord(doc) : null; }, "getApiKey"); } async getApiKeyByHash(hash) { return this.withRetry(async () => { const doc = await this.getCollection("api_keys").findOne({ hash }); return doc ? this.mapApiKeyRecord(doc) : null; }, "getApiKeyByHash"); } async getApiKeysByPrefixAndLastFour(prefix, lastFour) { return this.withRetry(async () => { const docs = await this.getCollection("api_keys").find({ prefix, lastFour }).toArray(); return docs.map((doc) => this.mapApiKeyRecord(doc)); }, "getApiKeysByPrefixAndLastFour"); } async updateApiKey(id, data) { return this.withRetry(async () => { const updateDoc = { $set: { updatedAt: /* @__PURE__ */ new Date() } }; const setFields = updateDoc.$set; for (const [key, value] of Object.entries(data)) { if (key === "id" || key === "createdAt") { continue; } setFields[key] = value; } const result = await this.getCollection("api_keys").findOneAndUpdate({ _id: id }, updateDoc, { returnDocument: "after" }); if (!result) { throw new import_error.InternalError({ code: "auth-mongo/update_failed", message: "Failed to update API key", severity: "error", retryable: false }); } return this.mapApiKeyRecord(result); }, "updateApiKey"); } async deleteApiKey(id) { return this.withRetry(async () => { await this.getCollection("api_keys").deleteOne({ _id: id }); }, "deleteApiKey"); } async listApiKeys(principalId) { return this.withRetry(async () => { const docs = await this.getCollection("api_keys").find({ principalId }).sort({ createdAt: -1 }).toArray(); return docs.map((doc) => this.mapApiKeyRecord(doc)); }, "listApiKeys"); } // Session methods async createSession(data) { return this.withRetry(async () => { const id = generateId(); const now = /* @__PURE__ */ new Date(); const doc = { _id: id, userId: data.userId, orgId: data.orgId || null, plan: data.plan || null, entitlements: data.entitlements || [], expiresAt: data.expiresAt, metadata: data.metadata || {}, createdAt: now, updatedAt: now }; await this.getCollection("sessions").insertOne(doc); return this.mapSessionRecord(doc); }, "createSession"); } async getSession(id) { return this.withRetry(async () => { const doc = await this.getCollection("sessions").findOne({ _id: id }); return doc ? this.mapSessionRecord(doc) : null; }, "getSession"); } async updateSession(id, data) { return this.withRetry(async () => { const updateDoc = { $set: { updatedAt: /* @__PURE__ */ new Date() } }; const setFields = updateDoc.$set; for (const [key, value] of Object.entries(data)) { if (key === "id" || key === "createdAt") { continue; } setFields[key] = value; } const result = await this.getCollection("sessions").findOneAndUpdate({ _id: id }, updateDoc, { returnDocument: "after" }); if (!result) { throw new import_error.InternalError({ code: "auth-mongo/update_failed", message: "Failed to update session", severity: "error", retryable: false }); } return this.mapSessionRecord(result); }, "updateSession"); } async deleteSession(id) { return this.withRetry(async () => { await this.getCollection("sessions").deleteOne({ _id: id }); }, "deleteSession"); } // Organization methods async createOrganization(data) { return this.withRetry(async () => { const id = generateId(); const now = /* @__PURE__ */ new Date(); const doc = { _id: id, name: data.name, plan: data.plan, seats: data.seats, members: data.members, metadata: data.metadata || {}, createdAt: now, updatedAt: now }; await this.getCollection("organizations").insertOne(doc); return this.mapOrganizationRecord(doc); }, "createOrganization"); } async getOrganization(id) { return this.withRetry(async () => { const doc = await this.getCollection("organizations").findOne({ _id: id }); return doc ? this.mapOrganizationRecord(doc) : null; }, "getOrganization"); } async updateOrganization(id, data) { return this.withRetry(async () => { const updateDoc = { $set: { updatedAt: /* @__PURE__ */ new Date() } }; const setFields = updateDoc.$set; for (const [key, value] of Object.entries(data)) { if (key === "id" || key === "createdAt") { continue; } setFields[key] = value; } const result = await this.getCollection("organizations").findOneAndUpdate( { _id: id }, updateDoc, { returnDocument: "after" } ); if (!result) { throw new import_error.InternalError({ code: "auth-mongo/update_failed", message: "Failed to update organization", severity: "error", retryable: false }); } return this.mapOrganizationRecord(result); }, "updateOrganization"); } async deleteOrganization(id) { return this.withRetry(async () => { await this.getCollection("organizations").deleteOne({ _id: id }); }, "deleteOrganization"); } // User methods async createUser(data) { return this.withRetry(async () => { const id = generateId(); const now = /* @__PURE__ */ new Date(); const doc = { _id: id, email: data.email || null, name: data.name || null, picture: data.picture || null, plan: data.plan || "free", entitlements: data.entitlements || [], oauth: {}, metadata: data.metadata || {}, createdAt: now, updatedAt: now }; await this.getCollection("users").insertOne(doc); return this.mapUserRecord(doc); }, "createUser"); } async getUser(id) { return this.withRetry(async () => { const doc = await this.getCollection("users").findOne({ _id: id }); return doc ? this.mapUserRecord(doc) : null; }, "getUser"); } async getUserByEmail(email) { return this.withRetry(async () => { const doc = await this.getCollection("users").findOne({ email }); return doc ? this.mapUserRecord(doc) : null; }, "getUserByEmail"); } async getUserByOAuth(provider, sub) { return this.withRetry(async () => { const filter = {}; filter[`oauth.${provider}.sub`] = sub; const doc = await this.getCollection("users").findOne(filter); return doc ? this.mapUserRecord(doc) : null; }, "getUserByOAuth"); } async updateUser(id, data) { return this.withRetry(async () => { const updateDoc = { $set: { updatedAt: /* @__PURE__ */ new Date() } }; const setFields = updateDoc.$set; for (const [key, value] of Object.entries(data)) { setFields[key] = value; } const result = await this.getCollection("users").findOneAndUpdate({ _id: id }, updateDoc, { returnDocument: "after" }); if (!result) { throw new import_error.InternalError({ code: "auth-mongo/update_failed", message: "Failed to update user", severity: "error", retryable: false }); } return this.mapUserRecord(result); }, "updateUser"); } async deleteUser(id) { return this.withRetry(async () => { await this.getCollection("users").deleteOne({ _id: id }); }, "deleteUser"); } async linkOAuthAccount(userId, provider, oauthLink) { return this.withRetry(async () => { const updateDoc = { $set: { updatedAt: /* @__PURE__ */ new Date() } }; const setFields = updateDoc.$set; setFields[`oauth.${provider}`] = { provider: oauthLink.provider, sub: oauthLink.sub, email: oauthLink.email, name: oauthLink.name, linkedAt: oauthLink.linkedAt }; const result = await this.getCollection("users").findOneAndUpdate( { _id: userId }, updateDoc, { returnDocument: "after" } ); if (!result) { throw new import_error.InternalError({ code: "auth-mongo/link_failed", message: "Failed to link OAuth account", severity: "error", retryable: false }); } return this.mapUserRecord(result); }, "linkOAuthAccount"); } // Email Verification Token methods async createEmailVerificationToken(data) { return this.withRetry(async () => { const id = generateId(); const doc = { _id: id, email: data.email, code: data.code, codeHash: data.codeHash, type: data.type, userId: data.userId || null, metadata: data.metadata || {}, expiresAt: data.expiresAt, usedAt: null, createdAt: /* @__PURE__ */ new Date() }; await this.getCollection("email_verification_tokens").insertOne(doc); return this.mapEmailVerificationToken(doc); }, "createEmailVerificationToken"); } async getEmailVerificationTokens(email, type) { return this.withRetry(async () => { const filter = { email }; if (type) { filter["type"] = type; } const docs = await this.getCollection("email_verification_tokens").find(filter).sort({ createdAt: -1 }).toArray(); return docs.map((doc) => this.mapEmailVerificationToken(doc)); }, "getEmailVerificationTokens"); } async getEmailVerificationTokenById(id) { return this.withRetry(async () => { const doc = await this.getCollection("email_verification_tokens").findOne({ _id: id }); return doc ? this.mapEmailVerificationToken(doc) : null; }, "getEmailVerificationTokenById"); } async markEmailVerificationTokenAsUsed(id) { return this.withRetry(async () => { const result = await this.getCollection("email_verification_tokens").findOneAndUpdate( { _id: id }, { $set: { usedAt: /* @__PURE__ */ new Date() } }, { returnDocument: "after" } ); if (!result) { throw new import_error.InternalError({ code: "auth-mongo/update_failed", message: "Failed to mark email verification token as used", severity: "error", retryable: false }); } return this.mapEmailVerificationToken(result); }, "markEmailVerificationTokenAsUsed"); } async deleteExpiredEmailVerificationTokens() { return this.withRetry(async () => { const result = await this.getCollection("email_verification_tokens").deleteMany({ expiresAt: { $lt: /* @__PURE__ */ new Date() } }); return result.deletedCount; }, "deleteExpiredEmailVerificationTokens"); } async getEmailVerificationTokenAttempts(tokenId) { return this.withRetry(async () => { const doc = await this.getCollection("email_verification_token_attempts").findOne({ _id: tokenId }); return doc?.["attempts"] || 0; }, "getEmailVerificationTokenAttempts"); } async incrementEmailVerificationTokenAttempts(tokenId) { return this.withRetry(async () => { const collection = this.getCollection("email_verification_token_attempts"); const incrementValue = 1; await collection.updateOne( { _id: tokenId }, // eslint-disable-next-line @typescript-eslint/no-explicit-any { $inc: { attempts: incrementValue } }, { upsert: true } ); const doc = await collection.findOne({ _id: tokenId }); return doc?.["attempts"] || 1; }, "incrementEmailVerificationTokenAttempts"); } // Event methods async emitEvent(event) { return this.withRetry(async () => { const doc = { type: event.type, principalId: event.principalId, orgId: event.orgId, data: event.data, timestamp: event.timestamp }; await this.getCollection("auth_events").insertOne(doc); }, "emitEvent"); } // RBAC methods async createRole(data) { return this.withRetry(async () => { const id = generateId(); const now = /* @__PURE__ */ new Date(); const doc = { _id: id, orgId: data.orgId, name: data.name, description: data.description || null, isSystem: data.isSystem || false, permissions: data.permissions || [], metadata: data.metadata || {}, createdAt: now, updatedAt: now }; await this.getCollection("roles").insertOne(doc); return this.mapRoleRecord(doc); }, "createRole"); } async getRole(roleId) { return this.withRetry(async () => { const doc = await this.getCollection("roles").findOne({ _id: roleId }); return doc ? this.mapRoleRecord(doc) : null; }, "getRole"); } async updateRole(roleId, data) { return this.withRetry(async () => { const updateDoc = { $set: { updatedAt: /* @__PURE__ */ new Date() } }; const setFields = updateDoc.$set; for (const [key, value] of Object.entries(data)) { if (key === "id" || key === "createdAt") { continue; } setFields[key] = value; } const result = await this.getCollection("roles").findOneAndUpdate( { _id: roleId }, updateDoc, { returnDocument: "after" } ); if (!result) { throw new import_error.InternalError({ code: "auth-mongo/update_failed", message: "Failed to update role", severity: "error", retryable: false }); } return this.mapRoleRecord(result); }, "updateRole"); } async deleteRole(roleId) { return this.withRetry(async () => { await this.getCollection("roles").deleteOne({ _id: roleId }); }, "deleteRole"); } async listRoles(orgId) { return this.withRetry(async () => { const docs = await this.getCollection("roles").find({ orgId }).sort({ createdAt: -1 }).toArray(); return docs.map((doc) => this.mapRoleRecord(doc)); }, "listRoles"); } async assignRoleToUser(userId, roleId, orgId) { return this.withRetry(async () => { const id = generateId(); const doc = { _id: id, userId, roleId, orgId, assignedAt: /* @__PURE__ */ new Date() }; await this.getCollection("user_roles").insertOne(doc); const role = await this.getRole(roleId); if (!role) { throw new import_error.InternalError({ code: "auth-mongo/role_not_found", message: "Role not found", severity: "error", retryable: false, context: { roleId } }); } return role; }, "assignRoleToUser"); } async revokeRoleFromUser(userId, roleId, orgId) { return this.withRetry(async () => { await this.getCollection("user_roles").deleteOne({ userId, roleId, orgId }); }, "revokeRoleFromUser"); } async getUserRoles(userId, orgId) { return this.withRetry(async () => { const userRoles = await this.getCollection("user_roles").find({ userId, orgId }).toArray(); const roleIds = userRoles.map((ur) => ur["roleId"]); const roles = await this.getCollection("roles").find({ _id: { $in: roleIds } }).sort({ createdAt: -1 }).toArray(); return roles.map((doc) => this.mapRoleRecord(doc)); }, "getUserRoles"); } // SSO methods async createSSOProvider(data) { return this.withRetry(async () => { const id = generateId(); const now = /* @__PURE__ */ new Date(); const doc = { _id: id, type: data["type"], name: data["name"], orgId: data["orgId"] || null, metadataUrl: data["metadataUrl"] || data["metadata_url"] || null, clientId: data["clientId"] || data["client_id"] || null, clientSecret: data["clientSecret"] || data["client_secret"] || null, tokenEndpointAuthMethod: data["tokenEndpointAuthMethod"] || data["token_endpoint_auth_method"] || null, idpEntityId: data["idpEntityId"] || data["idp_entity_id"] || null, idpSsoUrl: data["idpSsoUrl"] || data["idp_sso_url"] || null, idpSloUrl: data["idpSloUrl"] || data["idp_slo_url"] || null, idpCertificate: data["idpCertificate"] || data["idp_certificate"] || null, spEntityId: data["spEntityId"] || data["sp_entity_id"] || null, spAcsUrl: data["spAcsUrl"] || data["sp_acs_url"] || null, spSloUrl: data["spSloUrl"] || data["sp_slo_url"] || null, signingCert: data["signingCert"] || data["signing_cert"] || null, signingKey: data["signingKey"] || data["signing_key"] || null, encryptionEnabled: data["encryptionEnabled"] || data["encryption_enabled"] || false, forceAuthn: data["forceAuthn"] || data["force_authn"] || false, scopes: data["scopes"] || [], redirectUris: data["redirectUris"] || data["redirect_uris"] || [], claimMapping: data["claimMapping"] || data["claim_mapping"] || {}, attributeMapping: data["attributeMapping"] || data["attribute_mapping"] || {}, metadata: data["metadata"] || {}, createdAt: now, updatedAt: now }; await this.getCollection("sso_providers").insertOne(doc); return this.mapSSOProviderRecord(doc); }, "createSSOProvider"); } async getSSOProvider(providerId) { return this.withRetry(async () => { const doc = await this.getCollection("sso_providers").findOne({ _id: providerId }); return doc ? this.mapSSOProviderRecord(doc) : null; }, "getSSOProvider"); } async updateSSOProvider(providerId, data) { return this.withRetry(async () => { const updateDoc = { $set: { updatedAt: /* @__PURE__ */ new Date() } }; const setFields = updateDoc.$set; for (const [key, value] of Object.entries(data)) { if (key === "id" || key === "createdAt") { continue; } setFields[key] = value; } const result = await this.getCollection("sso_providers").findOneAndUpdate( { _id: providerId }, updateDoc, { returnDocument: "after" } ); if (!result) { throw new import_error.InternalError({ code: "auth-mongo/update_failed", message: "Failed to update SSO provider", severity: "error", retryable: false }); } return this.mapSSOProviderRecord(result); }, "updateSSOProvider"); } async deleteSSOProvider(providerId) { return this.withRetry(async () => { await this.getCollection("sso_providers").deleteOne({ _id: providerId }); }, "deleteSSOProvider"); } async listSSOProviders(orgId) { return this.withRetry(async () => { const filter = {}; if (orgId) { filter.$or = [{ orgId }, { orgId: null }]; } const docs = await this.getCollection("sso_providers").find(filter).sort({ createdAt: -1 }).toArray(); return docs.map((doc) => this.mapSSOProviderRecord(doc)); }, "listSSOProviders"); } async createSSOLink(data) { return this.withRetry(async () => { const id = generateId(); const doc = { _id: id, userId: data.userId, providerId: data.providerId, providerType: data.providerType, providerSubject: data.providerSubject, providerEmail: data.providerEmail || null, autoProvisioned: data.autoProvisioned || false, metadata: data.metadata || {}, linkedAt: /* @__PURE__ */ new Date(), lastAuthAt: data.lastAuthAt || /* @__PURE__ */ new Date() }; await this.getCollection("sso_links").insertOne(doc); return this.mapSSOLinkRecord(doc); }, "createSSOLink"); } async getSSOLink(linkId) { return this.withRetry(async () => { const doc = await this.getCollection("sso_links").findOne({ _id: linkId }); return doc ? this.mapSSOLinkRecord(doc) : null; }, "getSSOLink"); } async getUserSSOLinks(userId) { return this.withRetry(async () => { const docs = await this.getCollection("sso_links").find({ userId }).sort({ linkedAt: -1 }).toArray(); return docs.map((doc) => this.mapSSOLinkRecord(doc)); }, "getUserSSOLinks"); } async deleteSSOLink(linkId) { return this.withRetry(async () => { await this.getCollection("sso_links").deleteOne({ _id: linkId }); }, "deleteSSOLink"); } async createSSOSession(data) { return this.withRetry(async () => { const id = generateId(); const doc = { _id: id, userId: data.userId, providerId: data.providerId, providerType: data.providerType, providerSubject: data.providerSubject, sessionToken: data.sessionToken || null, expiresAt: data.expiresAt, linkedAt: /* @__PURE__ */ new Date(), lastAuthAt: data.lastAuthAt || /* @__PURE__ */ new Date() }; await this.getCollection("sso_sessions").insertOne(doc); return this.mapSSOSessionRecord(doc); }, "createSSOSession"); } async getSSOSession(sessionId) { return this.withRetry(async () => { const doc = await this.getCollection("sso_sessions").findOne({ _id: sessionId }); return doc ? this.mapSSOSessionRecord(doc) : null; }, "getSSOSession"); } // 2FA methods async createTwoFactorDevice(data) { return this.withRetry(async () => { const id = generateId(); const now = /* @__PURE__ */ new Date(); const doc = { _id: id, userId: data.userId, method: data.method, name: data.name || null, verified: data.verified || false, phoneNumber: data.phoneNumber || null, secret: data.secret || null, lastUsedAt: null, metadata: data.metadata || {}, createdAt: now, updatedAt: now }; await this.getCollection("twofa_devices").insertOne(doc); return this.mapTwoFactorDeviceRecord(doc); }, "createTwoFactorDevice"); } async getTwoFactorDevice(deviceId) { return this.withRetry(async () => { const doc = await this.getCollection("twofa_devices").findOne({ _id: deviceId }); return doc ? this.mapTwoFactorDeviceRecord(doc) : null; }, "getTwoFactorDevice"); } async listTwoFactorDevices(userId) { return this.withRetry(async () => { const docs = await this.getCollection("twofa_devices").find({ userId }).sort({ createdAt: -1 }).toArray(); return docs.map((doc) => this.mapTwoFactorDeviceRecord(doc)); }, "listTwoFactorDevices"); } async updateTwoFactorDevice(deviceId, data) { return this.withRetry(async () => { const updateDoc = { $set: { updatedAt: /* @__PURE__ */ new Date() } }; const setFields = updateDoc.$set; for (const [key, value] of Object.entries(data)) { if (key === "id" || key === "createdAt") { continue; } setFields[key] = value; } const result = await this.getCollection("twofa_devices").findOneAndUpdate( { _id: deviceId }, updateDoc, { returnDocument: "after" } ); if (!result) { throw new import_error.InternalError({ code: "auth-mongo/update_failed", message: "Failed to update two-factor device", severity: "error", retryable: false }); } return this.mapTwoFactorDeviceRecord(result); }, "updateTwoFactorDevice"); } async deleteTwoFactorDevice(deviceId) { return this.withRetry(async () => { await this.getCollection("twofa_devices").deleteOne({ _id: deviceId }); }, "deleteTwoFactorDevice"); } async createBackupCodes(userId, codes) { return this.withRetry(async () => { const createdCodes = []; for (const codeData of codes) { const id = generateId(); const codeValue = typeof codeData === "string" ? codeData : codeData["code"] || ""; const doc = { _id: id, userId, code: codeValue, used: false, usedAt: null, createdAt: /* @__PURE__ */ new Date() }; await this.getCollection("twofa_backup_codes").insertOne(doc); createdCodes.push(this.mapBackupCodeRecord(doc)); } return createdCodes; }, "createBackupCodes"); } async getBackupCodes(userId) { return this.withRetry(async () => { const docs = await this.getCollection("twofa_backup_codes").find({ userId }).sort({ createdAt: -1 }).toArray(); return docs.map((doc) => this.mapBackupCodeRecord(doc)); }, "getBackupCodes"); } async markBackupCodeUsed(codeId) { return this.withRetry(async () => { await this.getCollection("twofa_backup_codes").updateOne( { _id: codeId }, { $set: { used: true, usedAt: /* @__PURE__ */ new Date() } } ); }, "markBackupCodeUsed"); } async createTwoFactorSession(data) { return this.withRetry(async () => { const id = generateId(); const doc = { _id: id, userId: data.userId, sessionId: data.sessionId, deviceId: data.deviceId, method: data.method, verificationCode: data.verificationCode || null, attemptCount: data.attemptCount || 0, maxAttempts: data.maxAttempts || 5, expiresAt: data.expiresAt, completedAt: null, createdAt: /* @__PURE__ */ new Date() }; await this.getCollection("twofa_sessions").insertOne(doc); return this.mapTwoFactorSessionRecord(doc); }, "createTwoFactorSession"); } async getTwoFactorSession(sessionId) { return this.withRetry(async () => { const doc = await this.getCollection("twofa_sessions").findOne({ _id: sessionId }); return doc ? this.mapTwoFactorSessionRecord(doc) : null; }, "getTwoFactorSession"); } async completeTwoFactorSession(sessionId) { return this.withRetry(async () => { await this.getCollection("twofa_sessions").updateOne( { _id: sessionId }, { $set: { completedAt: /* @__PURE__ */ new Date() } } ); }, "completeTwoFactorSession"); } // Helper mapping methods mapApiKeyRecord(doc) { const result = { id: doc["_id"], principalId: doc["principalId"], hash: doc["hash"], prefix: doc["prefix"], lastFour: doc["lastFour"], scopes: doc["scopes"] || [], metadata: doc["metadata"] || {}, createdAt: new Date(doc["createdAt"]), updatedAt: new Date(doc["updatedAt"]) }; if (doc["expiresAt"]) { result.expiresAt = new Date(doc["expiresAt"]); } return result; } mapSessionRecord(doc) { const result = { id: doc["_id"], userId: doc["userId"], entitlements: doc["entitlements"] || [], expiresAt: new Date(doc["expiresAt"]), metadata: doc["metadata"] || {}, createdAt: new Date(doc["createdAt"]), updatedAt: new Date(doc["updatedAt"]) }; if (doc["orgId"]) { result.orgId = doc["orgId"]; } if (doc["plan"]) { result.plan = doc["plan"]; } return result; } mapOrganizationRecord(doc) { return { id: doc["_id"], name: doc["name"], plan: doc["plan"], seats: doc["seats"], members: doc["members"] || [], metadata: doc["metadata"] || {}, createdAt: new Date(doc["createdAt"]), updatedAt: new Date(doc["updatedAt"]) }; } mapUserRecord(doc) { const result = { id: doc["_id"], entitlements: doc["entitlements"] || [], oauth: doc["oauth"] || {}, metadata: doc["metadata"] || {}, createdAt: new Date(doc["createdAt"]), updatedAt: new Date(doc["updatedAt"]) }; if (doc["email"]) { result.email = doc["email"]; } if (doc["name"]) { result.name = doc["name"]; } if (doc["picture"]) { result.picture = doc["picture"]; } if (doc["plan"]) { result.plan = doc["plan"]; } return result; } mapEmailVerificationToken(doc) { const result = { id: doc["_id"], email: doc["email"], code: doc["code"], codeHash: doc["codeHash"], type: doc["type"], metadata: doc["metadata"] || {}, expiresAt: new Date(doc["expiresAt"]), createdAt: new Date(doc["createdAt"]) }; if (doc["userId"]) { result.userId = doc["userId"]; } if (doc["usedAt"]) { result.usedAt = new Date(doc["usedAt"]); } return result; } mapRoleRecord(doc) { return { id: doc["_id"], orgId: doc["orgId"], name: doc["name"], description: doc["description"], isSystem: doc["isSystem"], permissions: (doc["permissions"] || []).map((p) => p), metadata: doc["metadata"] || {}, createdAt: doc["createdAt"], updatedAt: doc["updatedAt"] }; } mapSSOProviderRecord(doc) { return { id: doc["_id"], type: doc["type"], name: doc["name"], orgId: doc["orgId"], metadataUrl: doc["metadataUrl"], clientId: doc["clientId"], clientSecret: doc["clientSecret"], tokenEndpointAuthMethod: doc["tokenEndpointAuthMethod"], idpEntityId: doc["idpEntityId"], idpSsoUrl: doc["idpSsoUrl"], idpSloUrl: doc["idpSloUrl"], idpCertificate: doc["idpCertificate"], spEntityId: doc["spEntityId"], spAcsUrl: doc["spAcsUrl"], spSloUrl: doc["spSloUrl"], signingCert: doc["signingCert"], signingKey: doc["signingKey"], encryptionEnabled: doc["encryptionEnabled"], forceAuthn: doc["forceAuthn"], scopes: doc["scopes"] || [], redirectUris: doc["redirectUris"] || [], claimMapping: doc["claimMapping"] || {}, attributeMapping: doc["attributeMapping"] || {}, metadata: doc["metadata"] || {}, createdAt: doc["createdAt"], updatedAt: doc["updatedAt"] }; } mapSSOLinkRecord(doc) { return { id: doc["_id"], userId: doc["userId"], providerId: doc["providerId"], providerType: doc["providerType"], providerSubject: doc["providerSubject"], providerEmail: doc["providerEmail"], autoProvisioned: doc["autoProvisioned"], metadata: doc["metadata"] || {}, linkedAt: doc["linkedAt"], lastAuthAt: doc["lastAuthAt"] }; } mapSSOSessionRecord(doc) { return { id: doc["_id"], userId: doc["userId"], providerId: doc["providerId"], providerType: doc["providerType"], providerSubject: doc["providerSubject"], sessionToken: doc["sessionToken"], expiresAt: doc["expiresAt"], linkedAt: doc["linkedAt"], lastAuthAt: doc["lastAuthAt"] }; } mapTwoFactorDeviceRecord(doc) { return { id: doc["_id"], userId: doc["userId"], method: doc["method"], name: doc["name"], verified: doc["verified"], phoneNumber: doc["phoneNumber"], secret: doc["secret"], lastUsedAt: doc["lastUsedAt"], metadata: doc["metadata"] || {}, createdAt: doc["createdAt"], updatedAt: doc["updatedAt"] }; } mapBackupCodeRecord(doc) { return { id: doc["_id"], userId: doc["userId"], code: doc["code"], used: doc["used"], usedAt: doc["usedAt"], createdAt: doc["createdAt"] }; } mapTwoFactorSessionRecord(doc) { return { id: doc["_id"], userId: doc["userId"], sessionId: doc["sessionId"], deviceId: doc["deviceId"], method: doc["method"], verificationCode: doc["verificationCode"], attemptCount: doc["attemptCount"], maxAttempts: doc["maxAttempts"], expiresAt: doc["expiresAt"], completedAt: doc["completedAt"], createdAt: doc["createdAt"] }; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { MongoStorageAdapter }); //# sourceMappingURL=index.cjs.map