@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,337 lines (1,336 loc) • 44.2 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
// src/mongo.ts
import {
MongoClient
} from "mongodb";
import { getLogger } from "@kitiumai/logger";
import { InternalError } from "@kitiumai/error";
import { randomBytes, createHash, scryptSync, timingSafeEqual } from "crypto";
import { setTimeout as delay } from "timers/promises";
var generateId = () => {
return randomBytes(16).toString("hex");
};
var generateApiKey = (prefix) => {
const secret = randomBytes(32).toString("hex");
return `${prefix}_${secret}`;
};
var hashApiKey = (key, algorithm, salt, scryptParams = { N: 16384, r: 8, p: 1 }) => {
if (algorithm === "scrypt") {
const result = scryptSync(key, salt ?? Buffer.alloc(0), 32, scryptParams);
return result.toString("hex");
}
return createHash("sha256").update(key).digest("hex");
};
var MongoStorageAdapter = class {
constructor(connectionString, options) {
__publicField(this, "client");
__publicField(this, "db", null);
__publicField(this, "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 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 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 InternalError({
code: "auth-mongo/disconnect_failed",
message: "Failed to disconnect from MongoDB",
severity: "error",
retryable: false,
cause: error
});
}
}
getDatabase() {
if (!this.db) {
throw new 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 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 InternalError && error.code === "auth-mongo/operation_timeout") {
throw error;
}
throw new InternalError({
code: "auth-mongo/operation_failed",
message: `Failed to execute MongoDB operation: ${operationName}`,
severity: "error",
retryable: false,
cause: error
});
}
await delay(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 && timingSafeEqual(candidateHash, hashedBuffer)) {
return candidate;
}
}
return null;
}
async rotateApiKey(id, options) {
const existing = await this.getApiKey(id);
if (!existing) {
throw new 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 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 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 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 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 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 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 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 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 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 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"]
};
}
};
export {
MongoStorageAdapter
};
//# sourceMappingURL=index.js.map