@happyvertical/smrt-profiles 0.40.69 → 0.40.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -4
- package/dist/chunks/{OidcIdentity-CFbUlB_Z.js → OidcIdentity-Bd68oFpZ.js} +3 -3
- package/dist/chunks/{OidcIdentity-CFbUlB_Z.js.map → OidcIdentity-Bd68oFpZ.js.map} +1 -1
- package/dist/chunks/{OidcIdentityCollection-BLxSaqJT.js → OidcIdentityCollection-DmllRnCw.js} +2 -2
- package/dist/chunks/{OidcIdentityCollection-BLxSaqJT.js.map → OidcIdentityCollection-DmllRnCw.js.map} +1 -1
- package/dist/chunks/{ProfileAssetCollection-FhW8q7_1.js → ProfileAssetCollection-DS2Nh2IQ.js} +2 -2
- package/dist/chunks/{ProfileAssetCollection-FhW8q7_1.js.map → ProfileAssetCollection-DS2Nh2IQ.js.map} +1 -1
- package/dist/chunks/{ProfileCollection-DdJ3W4GG.js → ProfileCollection-BOwbNCgG.js} +7 -14
- package/dist/chunks/ProfileCollection-BOwbNCgG.js.map +1 -0
- package/dist/chunks/{oidcProvisioningPrimitives-C9XSQNZj.js → oidcProvisioningPrimitives-BWHPpJde.js} +3 -3
- package/dist/chunks/{oidcProvisioningPrimitives-C9XSQNZj.js.map → oidcProvisioningPrimitives-BWHPpJde.js.map} +1 -1
- package/dist/chunks/{resolveIdentity-BpkxPkz7.js → resolveIdentity-F6aErDyh.js} +15 -15
- package/dist/chunks/{resolveIdentity-BpkxPkz7.js.map → resolveIdentity-F6aErDyh.js.map} +1 -1
- package/dist/index.js +10 -10
- package/dist/internal/oidc-provisioning.js +2 -2
- package/dist/manifest.json +61 -1
- package/dist/oidc-provisioning.d.ts +13 -1
- package/dist/smrt-knowledge.json +5 -5
- package/dist/utils.js +1 -1
- package/package.json +6 -6
- package/dist/chunks/ProfileCollection-DdJ3W4GG.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { _ as verifyAuthEvent, t as NostrIdentity, y as __exportAll } from "./NostrIdentity-DF_a_WZk.js";
|
|
2
|
-
import { d as coordinateOidcProvisioning, h as normalizeIdentityEmail, p as isOidcProvisioningRaceConflict } from "./ProfileCollection-
|
|
2
|
+
import { d as coordinateOidcProvisioning, h as normalizeIdentityEmail, p as isOidcProvisioningRaceConflict } from "./ProfileCollection-BOwbNCgG.js";
|
|
3
3
|
import { t as ApiKey } from "./ApiKey-Br9NGBnr.js";
|
|
4
|
-
import { t as OidcIdentity } from "./OidcIdentity-
|
|
5
|
-
import { n as insertOidcProvisioningIdentity, t as createOidcProvisioningPerson } from "./oidcProvisioningPrimitives-
|
|
4
|
+
import { t as OidcIdentity } from "./OidcIdentity-Bd68oFpZ.js";
|
|
5
|
+
import { n as insertOidcProvisioningIdentity, t as createOidcProvisioningPerson } from "./oidcProvisioningPrimitives-BWHPpJde.js";
|
|
6
6
|
import { resolveDatabase } from "@happyvertical/smrt-core";
|
|
7
7
|
import { withSystemContext } from "@happyvertical/smrt-tenancy";
|
|
8
8
|
//#region src/auth/resolveIdentity.ts
|
|
@@ -65,13 +65,13 @@ async function resolveIdentity(context) {
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
async function findProfileByExternalId(provider, externalId, options) {
|
|
68
|
-
const { OidcIdentityCollection } = await import("./OidcIdentityCollection-
|
|
68
|
+
const { OidcIdentityCollection } = await import("./OidcIdentityCollection-DmllRnCw.js").then((n) => n.r);
|
|
69
69
|
const identities = await (await OidcIdentityCollection.create(options)).findByProvider(provider);
|
|
70
70
|
for (const identity of identities) if (identity.subject === externalId || identity.email?.includes(externalId)) {
|
|
71
71
|
const profile = await identity.getProfile();
|
|
72
72
|
if (profile) return profile;
|
|
73
73
|
}
|
|
74
|
-
const { ProfileCollection } = await import("./ProfileCollection-
|
|
74
|
+
const { ProfileCollection } = await import("./ProfileCollection-BOwbNCgG.js").then((n) => n.r);
|
|
75
75
|
const profiles = await (await ProfileCollection.create(options)).list({
|
|
76
76
|
where: { email: `${externalId}@users.noreply.github.com` },
|
|
77
77
|
limit: 1
|
|
@@ -92,7 +92,7 @@ async function reuseExistingOidcIdentityForProfile(profileId, claims, provider,
|
|
|
92
92
|
createConcurrencyError: (cause) => new Error("Concurrent exact OIDC identity reuse did not converge.", cause === void 0 ? void 0 : { cause }),
|
|
93
93
|
rebindRootResult: rebindOidcProfileResult,
|
|
94
94
|
provision: (tx) => withSystemContext(async () => {
|
|
95
|
-
const { OidcIdentityCollection } = await import("./OidcIdentityCollection-
|
|
95
|
+
const { OidcIdentityCollection } = await import("./OidcIdentityCollection-DmllRnCw.js").then((n) => n.r);
|
|
96
96
|
const identity = await (await OidcIdentityCollection.create({ db: tx })).findBySubject(normalizedClaims.iss, normalizedClaims.sub);
|
|
97
97
|
if (!identity) throw new Error("OidcIdentity.findOrCreate() no longer creates authentication links; use UserCollection.getOrCreateFromOidc() or createProfileFromOidc().");
|
|
98
98
|
if (identity.profileId !== profileId) throw new Error(`OIDC identity ${normalizedClaims.iss} subject ${normalizedClaims.sub} belongs to a different Profile.`);
|
|
@@ -126,7 +126,7 @@ async function coordinateProfileOidcProvisioning(claims, provider, options) {
|
|
|
126
126
|
});
|
|
127
127
|
}
|
|
128
128
|
async function provisionOidcProfile(claims, provider, options) {
|
|
129
|
-
const { OidcIdentityCollection } = await import("./OidcIdentityCollection-
|
|
129
|
+
const { OidcIdentityCollection } = await import("./OidcIdentityCollection-DmllRnCw.js").then((n) => n.r);
|
|
130
130
|
const existingIdentity = await (await OidcIdentityCollection.create(options)).findBySubject(claims.iss, claims.sub);
|
|
131
131
|
if (existingIdentity) {
|
|
132
132
|
const profile2 = await existingIdentity.getProfile();
|
|
@@ -140,7 +140,7 @@ async function provisionOidcProfile(claims, provider, options) {
|
|
|
140
140
|
created: false
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
|
-
const { ProfileCollection } = await import("./ProfileCollection-
|
|
143
|
+
const { ProfileCollection } = await import("./ProfileCollection-BOwbNCgG.js").then((n) => n.r);
|
|
144
144
|
const profileCollection = await ProfileCollection.create(options);
|
|
145
145
|
if (claims.email) {
|
|
146
146
|
if (await profileCollection.findUniqueGlobalPersonByEmail(claims.email)) {
|
|
@@ -167,14 +167,14 @@ async function provisionOidcProfile(claims, provider, options) {
|
|
|
167
167
|
};
|
|
168
168
|
}
|
|
169
169
|
async function rebindOidcProfileResult(result, rootDb) {
|
|
170
|
-
const { OidcIdentityCollection } = await import("./OidcIdentityCollection-
|
|
171
|
-
const { ProfileCollection } = await import("./ProfileCollection-
|
|
170
|
+
const { OidcIdentityCollection } = await import("./OidcIdentityCollection-DmllRnCw.js").then((n) => n.r);
|
|
171
|
+
const { ProfileCollection } = await import("./ProfileCollection-BOwbNCgG.js").then((n) => n.r);
|
|
172
172
|
const profileId = requireOidcProfileId(result.profile.id);
|
|
173
173
|
const identityId = requireOidcProfileId(result.oidcIdentity.id);
|
|
174
174
|
const [profile, oidcIdentity] = await withSystemContext(async () => {
|
|
175
175
|
const profiles = await ProfileCollection.create({ db: rootDb });
|
|
176
176
|
const identities = await OidcIdentityCollection.create({ db: rootDb });
|
|
177
|
-
return
|
|
177
|
+
return [await profiles.get({ id: profileId }), await identities.get({ id: identityId })];
|
|
178
178
|
});
|
|
179
179
|
if (!profile || !oidcIdentity) throw new Error("Committed OIDC Profile provisioning result was not found.");
|
|
180
180
|
return {
|
|
@@ -214,11 +214,11 @@ async function createProfileFromNostr(email, nostrData, options) {
|
|
|
214
214
|
created: false
|
|
215
215
|
};
|
|
216
216
|
}
|
|
217
|
-
const { Person } = await import("./oidcProvisioningPrimitives-
|
|
218
|
-
const { ProfileTypeCollection } = await import("./oidcProvisioningPrimitives-
|
|
217
|
+
const { Person } = await import("./oidcProvisioningPrimitives-BWHPpJde.js").then((n) => n.o);
|
|
218
|
+
const { ProfileTypeCollection } = await import("./oidcProvisioningPrimitives-BWHPpJde.js").then((n) => n.c);
|
|
219
219
|
let personType = await (await ProfileTypeCollection.create(options)).getBySlug("person");
|
|
220
220
|
if (!personType) {
|
|
221
|
-
const { ProfileType } = await import("./ProfileCollection-
|
|
221
|
+
const { ProfileType } = await import("./ProfileCollection-BOwbNCgG.js").then((n) => n.s);
|
|
222
222
|
personType = new ProfileType({
|
|
223
223
|
...options,
|
|
224
224
|
slug: "person",
|
|
@@ -257,4 +257,4 @@ async function createProfileFromNostr(email, nostrData, options) {
|
|
|
257
257
|
//#endregion
|
|
258
258
|
export { resolveIdentity_exports as i, createProfileFromOidc as n, resolveIdentity as r, createProfileFromNostr as t };
|
|
259
259
|
|
|
260
|
-
//# sourceMappingURL=resolveIdentity-
|
|
260
|
+
//# sourceMappingURL=resolveIdentity-F6aErDyh.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveIdentity-BpkxPkz7.js","names":["profile"],"sources":["../../src/auth/resolveIdentity.ts"],"sourcesContent":["/**\n * resolveIdentity - Resolves authentication context to a Profile\n *\n * Used by apps (blindmanpress.com) to resolve incoming auth to a Profile.\n * Modules (aedile, praeco) should receive the resolved profile from the app.\n *\n * Resolution order:\n * 1. API key header → ApiKey → Profile\n * 2. OIDC session → OidcIdentity → Profile\n * 3. Nostr auth → NostrIdentity → Profile\n * 4. Actor header (CI pass-through) → Profile lookup\n */\n\nimport {\n resolveDatabase,\n type SmrtObjectOptions,\n} from '@happyvertical/smrt-core';\nimport { withSystemContext } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { ApiKey } from '../models/ApiKey';\nimport { NostrIdentity } from '../models/NostrIdentity';\nimport { OidcIdentity } from '../models/OidcIdentity';\nimport type { Profile } from '../models/Profile';\nimport { normalizeIdentityEmail } from './normalizeIdentityEmail';\nimport { type NostrEvent, verifyAuthEvent } from './nostrCrypto';\nimport {\n coordinateOidcProvisioning,\n isOidcProvisioningRaceConflict,\n} from './oidcProvisioningCoordinator';\nimport {\n createOidcProvisioningPerson,\n insertOidcProvisioningIdentity,\n} from './oidcProvisioningPrimitives';\n\n/**\n * Context provided to resolveIdentity\n */\nexport interface AuthContext {\n /**\n * API key from X-API-Key header or similar\n */\n apiKey?: string | null;\n\n /**\n * OIDC session data (from @auth/sveltekit or similar)\n */\n oidcSession?: {\n sub?: string;\n iss?: string;\n email?: string;\n name?: string;\n } | null;\n\n /**\n * Actor identifier for CI pass-through identity\n * Usually the GitHub actor (username) who triggered the workflow\n */\n actor?: string | null;\n\n /**\n * Nostr authentication data (NIP-42 style)\n */\n nostrAuth?: {\n /** Signed Nostr event for authentication */\n event: NostrEvent;\n /** Expected challenge (to prevent replay attacks) */\n challenge: string;\n } | null;\n\n /**\n * Database/persistence options\n */\n db?: SmrtObjectOptions['db'];\n}\n\n/**\n * Result of identity resolution\n */\nexport interface ResolveIdentityResult {\n /**\n * The resolved profile, or null if not authenticated\n */\n profile: Profile | null;\n\n /**\n * How the identity was resolved\n */\n source: 'api_key' | 'oidc' | 'nostr' | 'actor' | 'none';\n\n /**\n * The API key record if authenticated via API key\n */\n apiKey?: ApiKey;\n\n /**\n * The OIDC identity record if authenticated via OIDC\n */\n oidcIdentity?: OidcIdentity;\n\n /**\n * The Nostr identity record if authenticated via Nostr\n */\n nostrIdentity?: NostrIdentity;\n}\n\n/**\n * Resolve authentication context to a Profile\n *\n * @param context - The authentication context from the request\n * @returns The resolved profile and metadata\n *\n * @example\n * ```typescript\n * // In SvelteKit hooks.server.ts\n * import { resolveIdentity } from '@happyvertical/smrt-profiles';\n *\n * const identityMiddleware: Handle = async ({ event, resolve }) => {\n * const session = await event.locals.auth();\n *\n * const { profile, source } = await resolveIdentity({\n * oidcSession: session,\n * apiKey: event.request.headers.get('X-API-Key'),\n * actor: event.request.headers.get('X-Actor'),\n * db: { type: 'postgres', url: DATABASE_URL },\n * });\n *\n * event.locals.profile = profile;\n * event.locals.authSource = source;\n *\n * return resolve(event);\n * };\n * ```\n */\nexport async function resolveIdentity(\n context: AuthContext,\n): Promise<ResolveIdentityResult> {\n const options: SmrtObjectOptions = { db: context.db };\n\n // 1. Check API key header first (highest priority for programmatic access)\n if (context.apiKey) {\n const apiKey = await ApiKey.verify(context.apiKey, options);\n if (apiKey) {\n const profile = await apiKey.getProfile();\n if (profile) {\n return {\n profile,\n source: 'api_key',\n apiKey,\n };\n }\n }\n }\n\n // 2. Check OIDC session (web users)\n if (context.oidcSession?.sub && context.oidcSession?.iss) {\n const oidcIdentity = await OidcIdentity.findBySubject(\n context.oidcSession.iss,\n context.oidcSession.sub,\n options,\n );\n\n if (oidcIdentity) {\n // Record usage\n await oidcIdentity.recordUsage();\n\n const profile = await oidcIdentity.getProfile();\n if (profile) {\n return {\n profile,\n source: 'oidc',\n oidcIdentity,\n };\n }\n }\n }\n\n // 3. Check Nostr auth (NIP-42 style signed event)\n if (context.nostrAuth?.event && context.nostrAuth?.challenge) {\n const { event, challenge } = context.nostrAuth;\n\n // Verify the auth event\n const verifyResult = verifyAuthEvent(event, challenge);\n if (verifyResult.valid) {\n // Look up identity by public key\n const nostrIdentity = await NostrIdentity.findByPubkey(\n event.pubkey,\n options,\n );\n\n if (nostrIdentity) {\n // Record usage\n await nostrIdentity.recordUsage();\n\n const profile = await nostrIdentity.getProfile();\n if (profile) {\n return {\n profile,\n source: 'nostr',\n nostrIdentity,\n };\n }\n }\n }\n }\n\n // 4. Check actor for CI pass-through (look up by metadata)\n if (context.actor) {\n const profile = await findProfileByExternalId(\n 'github',\n context.actor,\n options,\n );\n if (profile) {\n return {\n profile,\n source: 'actor',\n };\n }\n }\n\n // No authentication found\n return {\n profile: null,\n source: 'none',\n };\n}\n\n/**\n * Find a profile by external ID (e.g., GitHub username)\n *\n * This looks up profiles by their linked external identities or metadata.\n *\n * @param provider - The external provider (e.g., 'github')\n * @param externalId - The external identifier (e.g., GitHub username)\n * @param options - Database options\n * @returns The profile or null\n */\nasync function findProfileByExternalId(\n provider: string,\n externalId: string,\n options: SmrtObjectOptions,\n): Promise<Profile | null> {\n // First check OIDC identities (if they use the provider)\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n\n const oidcCollection = await OidcIdentityCollection.create(options);\n const identities = await oidcCollection.findByProvider(provider);\n\n for (const identity of identities) {\n // Check if the subject matches the external ID\n if (\n identity.subject === externalId ||\n identity.email?.includes(externalId)\n ) {\n const profile = await identity.getProfile();\n if (profile) return profile;\n }\n }\n\n // Could also check profile metadata for external IDs\n // This would require a standardized metadata field like 'github_username'\n const { ProfileCollection } = await import(\n '../collections/ProfileCollection'\n );\n const profileCollection = await ProfileCollection.create(options);\n\n // Try to find by email that looks like a GitHub noreply\n const profiles = await profileCollection.list({\n where: {\n email: `${externalId}@users.noreply.github.com`,\n },\n limit: 1,\n });\n\n if (profiles.length > 0) {\n return profiles[0];\n }\n\n return null;\n}\n\n/**\n * Create a profile from OIDC claims if it doesn't exist\n *\n * Resolution order:\n * 1. If OIDC identity (iss + sub) already exists → return linked profile\n * 2. If the email matches an existing Profile, fail closed because this\n * package cannot prove whether a User owns it\n * 3. Otherwise, create new profile + identity\n *\n * Security considerations:\n * - Existing issuer/subject links always keep their already-linked Profile,\n * including legacy tenant-scoped or non-Person Profiles, and refresh the\n * cached identity email. This exact-link compatibility does not perform\n * email-based canonical reuse.\n * - New identities never attach to an existing email match through this\n * Profile-only helper because Profile ownership belongs to the users package.\n * Use `UserCollection.getOrCreateFromOidc()` for owner-aware verified-email\n * reuse and its supported pre-provision resolver hook.\n * - This function trusts the OIDC provider to assert correct email_verified status.\n * Only use with trusted providers.\n *\n * @param claims - OIDC token claims\n * @param provider - Provider name (e.g., 'keycloak', 'google', 'github')\n * @param options - Database options\n * @returns The created or existing profile with linked OIDC identity\n */\nexport async function createProfileFromOidc(\n claims: {\n sub: string;\n iss: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n },\n provider: string,\n options: SmrtObjectOptions,\n): Promise<{ profile: Profile; oidcIdentity: OidcIdentity; created: boolean }> {\n return coordinateProfileOidcProvisioning(claims, provider, options);\n}\n\n/**\n * @internal Transactionally reuse one exact legacy issuer/subject link.\n *\n * This path cannot create or rebind authority. It intentionally preserves\n * existing tenant-scoped and non-Person Profile links; canonical global Person\n * validation remains mandatory for every path that creates a User/session.\n */\nexport async function reuseExistingOidcIdentityForProfile(\n profileId: string,\n claims: {\n sub: string;\n iss: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n },\n provider: string,\n options: SmrtObjectOptions,\n): Promise<{ profile: Profile; oidcIdentity: OidcIdentity; created: false }> {\n const normalizedClaims = normalizeOidcProfileClaims(claims);\n const db = await resolveOidcProfileDatabase(options.db);\n return coordinateOidcProvisioning({\n db,\n lockKeys: [\n `identity:${OidcIdentity.buildIdentityKey(\n normalizedClaims.iss,\n normalizedClaims.sub,\n )}`,\n ],\n isRaceConflict: isOidcProvisioningRaceConflict,\n createTransactionError: (message, cause) =>\n new Error(message, cause === undefined ? undefined : { cause }),\n createConcurrencyError: (cause) =>\n new Error(\n 'Concurrent exact OIDC identity reuse did not converge.',\n cause === undefined ? undefined : { cause },\n ),\n rebindRootResult: rebindOidcProfileResult,\n provision: (tx) =>\n withSystemContext(async () => {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const identity = await (\n await OidcIdentityCollection.create({ db: tx })\n ).findBySubject(normalizedClaims.iss, normalizedClaims.sub);\n if (!identity) {\n throw new Error(\n 'OidcIdentity.findOrCreate() no longer creates authentication links; use UserCollection.getOrCreateFromOidc() or createProfileFromOidc().',\n );\n }\n if (identity.profileId !== profileId) {\n throw new Error(\n `OIDC identity ${normalizedClaims.iss} subject ${normalizedClaims.sub} belongs to a different Profile.`,\n );\n }\n const profile = await identity.getProfile();\n if (!profile) {\n throw new Error('The exact OIDC identity has no linked Profile.');\n }\n identity.provider = provider;\n if (normalizedClaims.email) identity.email = normalizedClaims.email;\n identity.lastUsedAt = new Date();\n await identity.save();\n return { profile, oidcIdentity: identity, created: false as const };\n }),\n });\n}\n\nasync function coordinateProfileOidcProvisioning(\n claims: {\n sub: string;\n iss: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n },\n provider: string,\n options: SmrtObjectOptions,\n): Promise<{ profile: Profile; oidcIdentity: OidcIdentity; created: boolean }> {\n const normalizedClaims = normalizeOidcProfileClaims(claims);\n const db = await resolveOidcProfileDatabase(options.db);\n return coordinateOidcProvisioning({\n db,\n lockKeys: [\n `identity:${OidcIdentity.buildIdentityKey(\n normalizedClaims.iss,\n normalizedClaims.sub,\n )}`,\n ...(normalizedClaims.email ? [`email:${normalizedClaims.email}`] : []),\n ],\n isRaceConflict: isOidcProvisioningRaceConflict,\n createTransactionError: (message, cause) =>\n new Error(message, cause === undefined ? undefined : { cause }),\n createConcurrencyError: (cause) =>\n new Error(\n 'Concurrent OIDC Profile provisioning did not converge.',\n cause === undefined ? undefined : { cause },\n ),\n rebindRootResult: rebindOidcProfileResult,\n provision: (tx) =>\n withSystemContext(() =>\n provisionOidcProfile(normalizedClaims, provider, {\n ...options,\n db: tx,\n }),\n ),\n });\n}\n\nasync function provisionOidcProfile(\n claims: {\n sub: string;\n iss: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n },\n provider: string,\n options: SmrtObjectOptions & { db: DatabaseInterface },\n): Promise<{ profile: Profile; oidcIdentity: OidcIdentity; created: boolean }> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const identityCollection = await OidcIdentityCollection.create(options);\n const existingIdentity = await identityCollection.findBySubject(\n claims.iss,\n claims.sub,\n );\n if (existingIdentity) {\n const profile = await existingIdentity.getProfile();\n if (!profile) {\n throw new Error('The exact OIDC identity has no linked Profile.');\n }\n if (claims.email) existingIdentity.email = claims.email;\n existingIdentity.lastUsedAt = new Date();\n await existingIdentity.save();\n return { profile, oidcIdentity: existingIdentity, created: false };\n }\n\n const { ProfileCollection } = await import(\n '../collections/ProfileCollection'\n );\n const profileCollection = await ProfileCollection.create(options);\n if (claims.email) {\n const collision = await profileCollection.findUniqueGlobalPersonByEmail(\n claims.email,\n );\n if (collision) {\n if (claims.email_verified !== true) {\n throw new Error(\n `OIDC email ${claims.email} is not verified and already belongs to a Profile.`,\n );\n }\n throw new Error(\n `OIDC email ${claims.email} already belongs to a Profile; createProfileFromOidc() cannot prove that Profile is unowned. Use UserCollection.getOrCreateFromOidc() for owner-aware linking.`,\n );\n }\n }\n\n const profile = await createOidcProvisioningPerson(options.db, {\n email: claims.email ?? '',\n name: claims.name,\n preferredUsername: claims.preferred_username,\n subject: claims.sub,\n });\n const oidcIdentity = await insertOidcProvisioningIdentity(options.db, {\n email: claims.email,\n issuer: claims.iss,\n profileId: requireOidcProfileId(profile.id),\n provider,\n subject: claims.sub,\n });\n return { profile, oidcIdentity, created: true };\n}\n\nasync function rebindOidcProfileResult<\n T extends {\n profile: Profile;\n oidcIdentity: OidcIdentity;\n created: boolean;\n },\n>(result: T, rootDb: DatabaseInterface): Promise<T> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const { ProfileCollection } = await import(\n '../collections/ProfileCollection'\n );\n const profileId = requireOidcProfileId(result.profile.id);\n const identityId = requireOidcProfileId(result.oidcIdentity.id);\n const [profile, oidcIdentity] = await withSystemContext(async () => {\n const profiles = await ProfileCollection.create({ db: rootDb });\n const identities = await OidcIdentityCollection.create({ db: rootDb });\n return Promise.all([\n profiles.get({ id: profileId }),\n identities.get({ id: identityId }),\n ]);\n });\n if (!profile || !oidcIdentity) {\n throw new Error(\n 'Committed OIDC Profile provisioning result was not found.',\n );\n }\n return { ...result, profile, oidcIdentity };\n}\n\nfunction normalizeOidcProfileClaims(claims: {\n sub: string;\n iss: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n}) {\n const sub = claims?.sub;\n const iss = claims?.iss;\n if (\n typeof sub !== 'string' ||\n sub.trim().length === 0 ||\n typeof iss !== 'string' ||\n iss.trim().length === 0\n ) {\n throw new Error(\n 'Invalid OIDC claims: both \"sub\" and \"iss\" must be non-empty strings.',\n );\n }\n const suppliedEmail = claims.email;\n return {\n ...claims,\n sub,\n iss,\n email:\n typeof suppliedEmail === 'string' && suppliedEmail.trim()\n ? normalizeIdentityEmail(suppliedEmail)\n : undefined,\n };\n}\n\nfunction requireOidcProfileId(value: unknown): string {\n if (typeof value !== 'string' || !value) {\n throw new Error('OIDC Profile provisioning did not produce an id.');\n }\n return value;\n}\n\nasync function resolveOidcProfileDatabase(\n db: SmrtObjectOptions['db'],\n): Promise<DatabaseInterface> {\n if (!db) {\n throw new Error(\n 'OIDC Profile provisioning requires an initialized database.',\n );\n }\n return resolveDatabase(db as Parameters<typeof resolveDatabase>[0]);\n}\n\n/**\n * Create a profile from Nostr identity (used by magic link service)\n *\n * This is typically called internally by the magic link service,\n * but can be used directly if needed.\n *\n * @param email - Email address for the profile\n * @param nostrData - Encrypted Nostr keypair data\n * @param options - Database options\n * @returns The created profile with linked Nostr identity\n */\nexport async function createProfileFromNostr(\n email: string,\n nostrData: {\n pubkey: string;\n encryptedPrivkey: string;\n encryptionIv: string;\n encryptionTag: string;\n nip05Username?: string;\n },\n options: SmrtObjectOptions,\n): Promise<{\n profile: Profile;\n nostrIdentity: NostrIdentity;\n created: boolean;\n}> {\n const normalizedEmail = normalizeIdentityEmail(email);\n\n // Check if Nostr identity already exists\n const existingIdentity = await NostrIdentity.findByEmail(\n normalizedEmail,\n options,\n );\n\n if (existingIdentity) {\n const profile = await existingIdentity.getProfile();\n if (profile) {\n return {\n profile,\n nostrIdentity: existingIdentity,\n created: false,\n };\n }\n }\n\n // Create new profile\n const { Person } = await import('../models/ProfileTypes');\n const { ProfileTypeCollection } = await import(\n '../collections/ProfileTypeCollection'\n );\n\n // Get or create the 'person' type\n const typeCollection = await ProfileTypeCollection.create(options);\n let personType = await typeCollection.getBySlug('person');\n\n if (!personType) {\n const { ProfileType } = await import('../models/ProfileType');\n personType = new ProfileType({\n ...options,\n slug: 'person',\n name: 'Person',\n description: 'Individual person profile',\n });\n await personType.initialize();\n await personType.save();\n }\n\n const profile = new Person({\n ...options,\n typeId: personType.id as string,\n email: normalizedEmail,\n name: normalizedEmail.split('@')[0], // Default name from email\n });\n await profile.initialize();\n await profile.save();\n\n // Create Nostr identity\n const nostrIdentity = new NostrIdentity({\n ...options,\n profileId: profile.id as string,\n pubkey: nostrData.pubkey,\n encryptedPrivkey: nostrData.encryptedPrivkey,\n encryptionIv: nostrData.encryptionIv,\n encryptionTag: nostrData.encryptionTag,\n email: normalizedEmail,\n nip05Username:\n nostrData.nip05Username?.toLowerCase() || normalizedEmail.split('@')[0],\n });\n await nostrIdentity.initialize();\n await nostrIdentity.save();\n\n return {\n profile,\n nostrIdentity,\n created: true,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resolveIdentity-F6aErDyh.js","names":["profile"],"sources":["../../src/auth/resolveIdentity.ts"],"sourcesContent":["/**\n * resolveIdentity - Resolves authentication context to a Profile\n *\n * Used by apps (blindmanpress.com) to resolve incoming auth to a Profile.\n * Modules (aedile, praeco) should receive the resolved profile from the app.\n *\n * Resolution order:\n * 1. API key header → ApiKey → Profile\n * 2. OIDC session → OidcIdentity → Profile\n * 3. Nostr auth → NostrIdentity → Profile\n * 4. Actor header (CI pass-through) → Profile lookup\n */\n\nimport {\n resolveDatabase,\n type SmrtObjectOptions,\n} from '@happyvertical/smrt-core';\nimport { withSystemContext } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { ApiKey } from '../models/ApiKey';\nimport { NostrIdentity } from '../models/NostrIdentity';\nimport { OidcIdentity } from '../models/OidcIdentity';\nimport type { Profile } from '../models/Profile';\nimport { normalizeIdentityEmail } from './normalizeIdentityEmail';\nimport { type NostrEvent, verifyAuthEvent } from './nostrCrypto';\nimport {\n coordinateOidcProvisioning,\n isOidcProvisioningRaceConflict,\n} from './oidcProvisioningCoordinator';\nimport {\n createOidcProvisioningPerson,\n insertOidcProvisioningIdentity,\n} from './oidcProvisioningPrimitives';\n\n/**\n * Context provided to resolveIdentity\n */\nexport interface AuthContext {\n /**\n * API key from X-API-Key header or similar\n */\n apiKey?: string | null;\n\n /**\n * OIDC session data (from @auth/sveltekit or similar)\n */\n oidcSession?: {\n sub?: string;\n iss?: string;\n email?: string;\n name?: string;\n } | null;\n\n /**\n * Actor identifier for CI pass-through identity\n * Usually the GitHub actor (username) who triggered the workflow\n */\n actor?: string | null;\n\n /**\n * Nostr authentication data (NIP-42 style)\n */\n nostrAuth?: {\n /** Signed Nostr event for authentication */\n event: NostrEvent;\n /** Expected challenge (to prevent replay attacks) */\n challenge: string;\n } | null;\n\n /**\n * Database/persistence options\n */\n db?: SmrtObjectOptions['db'];\n}\n\n/**\n * Result of identity resolution\n */\nexport interface ResolveIdentityResult {\n /**\n * The resolved profile, or null if not authenticated\n */\n profile: Profile | null;\n\n /**\n * How the identity was resolved\n */\n source: 'api_key' | 'oidc' | 'nostr' | 'actor' | 'none';\n\n /**\n * The API key record if authenticated via API key\n */\n apiKey?: ApiKey;\n\n /**\n * The OIDC identity record if authenticated via OIDC\n */\n oidcIdentity?: OidcIdentity;\n\n /**\n * The Nostr identity record if authenticated via Nostr\n */\n nostrIdentity?: NostrIdentity;\n}\n\n/**\n * Resolve authentication context to a Profile\n *\n * @param context - The authentication context from the request\n * @returns The resolved profile and metadata\n *\n * @example\n * ```typescript\n * // In SvelteKit hooks.server.ts\n * import { resolveIdentity } from '@happyvertical/smrt-profiles';\n *\n * const identityMiddleware: Handle = async ({ event, resolve }) => {\n * const session = await event.locals.auth();\n *\n * const { profile, source } = await resolveIdentity({\n * oidcSession: session,\n * apiKey: event.request.headers.get('X-API-Key'),\n * actor: event.request.headers.get('X-Actor'),\n * db: { type: 'postgres', url: DATABASE_URL },\n * });\n *\n * event.locals.profile = profile;\n * event.locals.authSource = source;\n *\n * return resolve(event);\n * };\n * ```\n */\nexport async function resolveIdentity(\n context: AuthContext,\n): Promise<ResolveIdentityResult> {\n const options: SmrtObjectOptions = { db: context.db };\n\n // 1. Check API key header first (highest priority for programmatic access)\n if (context.apiKey) {\n const apiKey = await ApiKey.verify(context.apiKey, options);\n if (apiKey) {\n const profile = await apiKey.getProfile();\n if (profile) {\n return {\n profile,\n source: 'api_key',\n apiKey,\n };\n }\n }\n }\n\n // 2. Check OIDC session (web users)\n if (context.oidcSession?.sub && context.oidcSession?.iss) {\n const oidcIdentity = await OidcIdentity.findBySubject(\n context.oidcSession.iss,\n context.oidcSession.sub,\n options,\n );\n\n if (oidcIdentity) {\n // Record usage\n await oidcIdentity.recordUsage();\n\n const profile = await oidcIdentity.getProfile();\n if (profile) {\n return {\n profile,\n source: 'oidc',\n oidcIdentity,\n };\n }\n }\n }\n\n // 3. Check Nostr auth (NIP-42 style signed event)\n if (context.nostrAuth?.event && context.nostrAuth?.challenge) {\n const { event, challenge } = context.nostrAuth;\n\n // Verify the auth event\n const verifyResult = verifyAuthEvent(event, challenge);\n if (verifyResult.valid) {\n // Look up identity by public key\n const nostrIdentity = await NostrIdentity.findByPubkey(\n event.pubkey,\n options,\n );\n\n if (nostrIdentity) {\n // Record usage\n await nostrIdentity.recordUsage();\n\n const profile = await nostrIdentity.getProfile();\n if (profile) {\n return {\n profile,\n source: 'nostr',\n nostrIdentity,\n };\n }\n }\n }\n }\n\n // 4. Check actor for CI pass-through (look up by metadata)\n if (context.actor) {\n const profile = await findProfileByExternalId(\n 'github',\n context.actor,\n options,\n );\n if (profile) {\n return {\n profile,\n source: 'actor',\n };\n }\n }\n\n // No authentication found\n return {\n profile: null,\n source: 'none',\n };\n}\n\n/**\n * Find a profile by external ID (e.g., GitHub username)\n *\n * This looks up profiles by their linked external identities or metadata.\n *\n * @param provider - The external provider (e.g., 'github')\n * @param externalId - The external identifier (e.g., GitHub username)\n * @param options - Database options\n * @returns The profile or null\n */\nasync function findProfileByExternalId(\n provider: string,\n externalId: string,\n options: SmrtObjectOptions,\n): Promise<Profile | null> {\n // First check OIDC identities (if they use the provider)\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n\n const oidcCollection = await OidcIdentityCollection.create(options);\n const identities = await oidcCollection.findByProvider(provider);\n\n for (const identity of identities) {\n // Check if the subject matches the external ID\n if (\n identity.subject === externalId ||\n identity.email?.includes(externalId)\n ) {\n const profile = await identity.getProfile();\n if (profile) return profile;\n }\n }\n\n // Could also check profile metadata for external IDs\n // This would require a standardized metadata field like 'github_username'\n const { ProfileCollection } = await import(\n '../collections/ProfileCollection'\n );\n const profileCollection = await ProfileCollection.create(options);\n\n // Try to find by email that looks like a GitHub noreply\n const profiles = await profileCollection.list({\n where: {\n email: `${externalId}@users.noreply.github.com`,\n },\n limit: 1,\n });\n\n if (profiles.length > 0) {\n return profiles[0];\n }\n\n return null;\n}\n\n/**\n * Create a profile from OIDC claims if it doesn't exist\n *\n * Resolution order:\n * 1. If OIDC identity (iss + sub) already exists → return linked profile\n * 2. If the email matches an existing Profile, fail closed because this\n * package cannot prove whether a User owns it\n * 3. Otherwise, create new profile + identity\n *\n * Security considerations:\n * - Existing issuer/subject links always keep their already-linked Profile,\n * including legacy tenant-scoped or non-Person Profiles, and refresh the\n * cached identity email. This exact-link compatibility does not perform\n * email-based canonical reuse.\n * - New identities never attach to an existing email match through this\n * Profile-only helper because Profile ownership belongs to the users package.\n * Use `UserCollection.getOrCreateFromOidc()` for owner-aware verified-email\n * reuse and its supported pre-provision resolver hook.\n * - This function trusts the OIDC provider to assert correct email_verified status.\n * Only use with trusted providers.\n *\n * @param claims - OIDC token claims\n * @param provider - Provider name (e.g., 'keycloak', 'google', 'github')\n * @param options - Database options\n * @returns The created or existing profile with linked OIDC identity\n */\nexport async function createProfileFromOidc(\n claims: {\n sub: string;\n iss: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n },\n provider: string,\n options: SmrtObjectOptions,\n): Promise<{ profile: Profile; oidcIdentity: OidcIdentity; created: boolean }> {\n return coordinateProfileOidcProvisioning(claims, provider, options);\n}\n\n/**\n * @internal Transactionally reuse one exact legacy issuer/subject link.\n *\n * This path cannot create or rebind authority. It intentionally preserves\n * existing tenant-scoped and non-Person Profile links; canonical global Person\n * validation remains mandatory for every path that creates a User/session.\n */\nexport async function reuseExistingOidcIdentityForProfile(\n profileId: string,\n claims: {\n sub: string;\n iss: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n },\n provider: string,\n options: SmrtObjectOptions,\n): Promise<{ profile: Profile; oidcIdentity: OidcIdentity; created: false }> {\n const normalizedClaims = normalizeOidcProfileClaims(claims);\n const db = await resolveOidcProfileDatabase(options.db);\n return coordinateOidcProvisioning({\n db,\n lockKeys: [\n `identity:${OidcIdentity.buildIdentityKey(\n normalizedClaims.iss,\n normalizedClaims.sub,\n )}`,\n ],\n isRaceConflict: isOidcProvisioningRaceConflict,\n createTransactionError: (message, cause) =>\n new Error(message, cause === undefined ? undefined : { cause }),\n createConcurrencyError: (cause) =>\n new Error(\n 'Concurrent exact OIDC identity reuse did not converge.',\n cause === undefined ? undefined : { cause },\n ),\n rebindRootResult: rebindOidcProfileResult,\n provision: (tx) =>\n withSystemContext(async () => {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const identity = await (\n await OidcIdentityCollection.create({ db: tx })\n ).findBySubject(normalizedClaims.iss, normalizedClaims.sub);\n if (!identity) {\n throw new Error(\n 'OidcIdentity.findOrCreate() no longer creates authentication links; use UserCollection.getOrCreateFromOidc() or createProfileFromOidc().',\n );\n }\n if (identity.profileId !== profileId) {\n throw new Error(\n `OIDC identity ${normalizedClaims.iss} subject ${normalizedClaims.sub} belongs to a different Profile.`,\n );\n }\n const profile = await identity.getProfile();\n if (!profile) {\n throw new Error('The exact OIDC identity has no linked Profile.');\n }\n identity.provider = provider;\n if (normalizedClaims.email) identity.email = normalizedClaims.email;\n identity.lastUsedAt = new Date();\n await identity.save();\n return { profile, oidcIdentity: identity, created: false as const };\n }),\n });\n}\n\nasync function coordinateProfileOidcProvisioning(\n claims: {\n sub: string;\n iss: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n },\n provider: string,\n options: SmrtObjectOptions,\n): Promise<{ profile: Profile; oidcIdentity: OidcIdentity; created: boolean }> {\n const normalizedClaims = normalizeOidcProfileClaims(claims);\n const db = await resolveOidcProfileDatabase(options.db);\n return coordinateOidcProvisioning({\n db,\n lockKeys: [\n `identity:${OidcIdentity.buildIdentityKey(\n normalizedClaims.iss,\n normalizedClaims.sub,\n )}`,\n ...(normalizedClaims.email ? [`email:${normalizedClaims.email}`] : []),\n ],\n isRaceConflict: isOidcProvisioningRaceConflict,\n createTransactionError: (message, cause) =>\n new Error(message, cause === undefined ? undefined : { cause }),\n createConcurrencyError: (cause) =>\n new Error(\n 'Concurrent OIDC Profile provisioning did not converge.',\n cause === undefined ? undefined : { cause },\n ),\n rebindRootResult: rebindOidcProfileResult,\n provision: (tx) =>\n withSystemContext(() =>\n provisionOidcProfile(normalizedClaims, provider, {\n ...options,\n db: tx,\n }),\n ),\n });\n}\n\nasync function provisionOidcProfile(\n claims: {\n sub: string;\n iss: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n },\n provider: string,\n options: SmrtObjectOptions & { db: DatabaseInterface },\n): Promise<{ profile: Profile; oidcIdentity: OidcIdentity; created: boolean }> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const identityCollection = await OidcIdentityCollection.create(options);\n const existingIdentity = await identityCollection.findBySubject(\n claims.iss,\n claims.sub,\n );\n if (existingIdentity) {\n const profile = await existingIdentity.getProfile();\n if (!profile) {\n throw new Error('The exact OIDC identity has no linked Profile.');\n }\n if (claims.email) existingIdentity.email = claims.email;\n existingIdentity.lastUsedAt = new Date();\n await existingIdentity.save();\n return { profile, oidcIdentity: existingIdentity, created: false };\n }\n\n const { ProfileCollection } = await import(\n '../collections/ProfileCollection'\n );\n const profileCollection = await ProfileCollection.create(options);\n if (claims.email) {\n const collision = await profileCollection.findUniqueGlobalPersonByEmail(\n claims.email,\n );\n if (collision) {\n if (claims.email_verified !== true) {\n throw new Error(\n `OIDC email ${claims.email} is not verified and already belongs to a Profile.`,\n );\n }\n throw new Error(\n `OIDC email ${claims.email} already belongs to a Profile; createProfileFromOidc() cannot prove that Profile is unowned. Use UserCollection.getOrCreateFromOidc() for owner-aware linking.`,\n );\n }\n }\n\n const profile = await createOidcProvisioningPerson(options.db, {\n email: claims.email ?? '',\n name: claims.name,\n preferredUsername: claims.preferred_username,\n subject: claims.sub,\n });\n const oidcIdentity = await insertOidcProvisioningIdentity(options.db, {\n email: claims.email,\n issuer: claims.iss,\n profileId: requireOidcProfileId(profile.id),\n provider,\n subject: claims.sub,\n });\n return { profile, oidcIdentity, created: true };\n}\n\nasync function rebindOidcProfileResult<\n T extends {\n profile: Profile;\n oidcIdentity: OidcIdentity;\n created: boolean;\n },\n>(result: T, rootDb: DatabaseInterface): Promise<T> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const { ProfileCollection } = await import(\n '../collections/ProfileCollection'\n );\n const profileId = requireOidcProfileId(result.profile.id);\n const identityId = requireOidcProfileId(result.oidcIdentity.id);\n const [profile, oidcIdentity] = await withSystemContext(async () => {\n const profiles = await ProfileCollection.create({ db: rootDb });\n const identities = await OidcIdentityCollection.create({ db: rootDb });\n // Read one statement at a time. SQLite and DuckDB handles multiplex a\n // single native connection, so overlapping statements on it fail the\n // losing prepared statement or abort the process outright (#2352). These\n // are primary-key reads, so the sequential cost is one extra round trip.\n const rebound = await profiles.get({ id: profileId });\n const reboundIdentity = await identities.get({ id: identityId });\n return [rebound, reboundIdentity] as const;\n });\n if (!profile || !oidcIdentity) {\n throw new Error(\n 'Committed OIDC Profile provisioning result was not found.',\n );\n }\n return { ...result, profile, oidcIdentity };\n}\n\nfunction normalizeOidcProfileClaims(claims: {\n sub: string;\n iss: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n}) {\n const sub = claims?.sub;\n const iss = claims?.iss;\n if (\n typeof sub !== 'string' ||\n sub.trim().length === 0 ||\n typeof iss !== 'string' ||\n iss.trim().length === 0\n ) {\n throw new Error(\n 'Invalid OIDC claims: both \"sub\" and \"iss\" must be non-empty strings.',\n );\n }\n const suppliedEmail = claims.email;\n return {\n ...claims,\n sub,\n iss,\n email:\n typeof suppliedEmail === 'string' && suppliedEmail.trim()\n ? normalizeIdentityEmail(suppliedEmail)\n : undefined,\n };\n}\n\nfunction requireOidcProfileId(value: unknown): string {\n if (typeof value !== 'string' || !value) {\n throw new Error('OIDC Profile provisioning did not produce an id.');\n }\n return value;\n}\n\nasync function resolveOidcProfileDatabase(\n db: SmrtObjectOptions['db'],\n): Promise<DatabaseInterface> {\n if (!db) {\n throw new Error(\n 'OIDC Profile provisioning requires an initialized database.',\n );\n }\n return resolveDatabase(db as Parameters<typeof resolveDatabase>[0]);\n}\n\n/**\n * Create a profile from Nostr identity (used by magic link service)\n *\n * This is typically called internally by the magic link service,\n * but can be used directly if needed.\n *\n * @param email - Email address for the profile\n * @param nostrData - Encrypted Nostr keypair data\n * @param options - Database options\n * @returns The created profile with linked Nostr identity\n */\nexport async function createProfileFromNostr(\n email: string,\n nostrData: {\n pubkey: string;\n encryptedPrivkey: string;\n encryptionIv: string;\n encryptionTag: string;\n nip05Username?: string;\n },\n options: SmrtObjectOptions,\n): Promise<{\n profile: Profile;\n nostrIdentity: NostrIdentity;\n created: boolean;\n}> {\n const normalizedEmail = normalizeIdentityEmail(email);\n\n // Check if Nostr identity already exists\n const existingIdentity = await NostrIdentity.findByEmail(\n normalizedEmail,\n options,\n );\n\n if (existingIdentity) {\n const profile = await existingIdentity.getProfile();\n if (profile) {\n return {\n profile,\n nostrIdentity: existingIdentity,\n created: false,\n };\n }\n }\n\n // Create new profile\n const { Person } = await import('../models/ProfileTypes');\n const { ProfileTypeCollection } = await import(\n '../collections/ProfileTypeCollection'\n );\n\n // Get or create the 'person' type\n const typeCollection = await ProfileTypeCollection.create(options);\n let personType = await typeCollection.getBySlug('person');\n\n if (!personType) {\n const { ProfileType } = await import('../models/ProfileType');\n personType = new ProfileType({\n ...options,\n slug: 'person',\n name: 'Person',\n description: 'Individual person profile',\n });\n await personType.initialize();\n await personType.save();\n }\n\n const profile = new Person({\n ...options,\n typeId: personType.id as string,\n email: normalizedEmail,\n name: normalizedEmail.split('@')[0], // Default name from email\n });\n await profile.initialize();\n await profile.save();\n\n // Create Nostr identity\n const nostrIdentity = new NostrIdentity({\n ...options,\n profileId: profile.id as string,\n pubkey: nostrData.pubkey,\n encryptedPrivkey: nostrData.encryptedPrivkey,\n encryptionIv: nostrData.encryptionIv,\n encryptionTag: nostrData.encryptionTag,\n email: normalizedEmail,\n nip05Username:\n nostrData.nip05Username?.toLowerCase() || normalizedEmail.split('@')[0],\n });\n await nostrIdentity.initialize();\n await nostrIdentity.save();\n\n return {\n profile,\n nostrIdentity,\n created: true,\n };\n}\n"],"mappings":"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"}
|