{"version":3,"file":"register-options.mjs","names":[],"sources":["../../../../../../src/astro/routes/api/auth/invite/register-options.ts"],"sourcesContent":["/**\n * POST /_emdash/api/auth/invite/register-options\n *\n * Generate WebAuthn registration options for an invited user.\n * Validates the invite token and creates a temporary user identity\n * for the passkey registration flow.\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { validateInvite, InviteError } from \"@premium-cms/auth\";\nimport { createKyselyAdapter } from \"@premium-cms/auth/adapters/kysely\";\nimport { generateRegistrationOptions } from \"@premium-cms/auth/passkey\";\nimport { ulid } from \"ulidx\";\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { inviteRegisterOptionsBody } from \"#api/schemas.js\";\nimport { createChallengeStore } from \"#auth/challenge-store.js\";\nimport { getPasskeyConfig } from \"#auth/passkey-config.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\n\nexport const POST: APIRoute = async ({ request, locals }) => {\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\tconst body = await parseBody(request, inviteRegisterOptionsBody);\n\t\tif (isParseError(body)) return body;\n\n\t\t// Validate the invite token to get the email\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\tconst invite = await validateInvite(adapter, body.token);\n\n\t\t// Get passkey config\n\t\t// Resolve siteUrl from config / EMDASH_SITE_URL so the RP ID matches the\n\t\t// browser-facing origin when running behind a TLS-terminating reverse proxy\n\t\t// (e.g. Traefik, nginx). Without this, getPasskeyConfig falls back to\n\t\t// url.hostname — the internal hostname like \"localhost\" — and the browser\n\t\t// rejects the WebAuthn registration. See issue #994.\n\t\tconst url = new URL(request.url);\n\t\tconst options = new OptionsRepository(emdash.db);\n\t\tconst siteName = (await options.get<string>(\"emdash:site_title\")) ?? undefined;\n\t\tconst siteUrl = getPublicOrigin(url, emdash?.config);\n\t\tconst passkeyConfig = getPasskeyConfig(url, siteName, siteUrl);\n\n\t\t// Generate registration options with a temporary user identity\n\t\tconst challengeStore = createChallengeStore(emdash.db);\n\t\tconst tempUser = {\n\t\t\tid: ulid(),\n\t\t\temail: invite.email,\n\t\t\tname: body.name || null,\n\t\t};\n\n\t\tconst registrationOptions = await generateRegistrationOptions(\n\t\t\tpasskeyConfig,\n\t\t\ttempUser,\n\t\t\t[],\n\t\t\tchallengeStore,\n\t\t);\n\n\t\treturn apiSuccess({ options: registrationOptions });\n\t} catch (error) {\n\t\tif (error instanceof InviteError) {\n\t\t\tconst statusMap: Record<string, number> = {\n\t\t\t\tinvalid_token: 404,\n\t\t\t\ttoken_expired: 410,\n\t\t\t\tuser_exists: 409,\n\t\t\t};\n\t\t\treturn apiError(error.code.toUpperCase(), error.message, statusMap[error.code] ?? 400);\n\t\t}\n\n\t\treturn handleError(\n\t\t\terror,\n\t\t\t\"Failed to generate registration options\",\n\t\t\t\"INVITE_REGISTER_OPTIONS_ERROR\",\n\t\t);\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAUA,MAAa,YAAY;AAezB,MAAa,OAAiB,OAAO,EAAE,SAAS,aAAa;CAC5D,MAAM,EAAE,WAAW;AAEnB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAGpE,KAAI;EACH,MAAM,OAAO,MAAM,UAAU,SAAS,0BAA0B;AAChE,MAAI,aAAa,KAAK,CAAE,QAAO;EAI/B,MAAM,SAAS,MAAM,eADL,oBAAoB,OAAO,GAAG,EACD,KAAK,MAAM;EAQxD,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;EAIhC,MAAM,gBAAgB,iBAAiB,KAFrB,MADF,IAAI,kBAAkB,OAAO,GAAG,CAChB,IAAY,oBAAoB,IAAK,QACrD,gBAAgB,KAAK,QAAQ,OAAO,CACU;EAG9D,MAAM,iBAAiB,qBAAqB,OAAO,GAAG;AActD,SAAO,WAAW,EAAE,SAPQ,MAAM,4BACjC,eAPgB;GAChB,IAAI,MAAM;GACV,OAAO,OAAO;GACd,MAAM,KAAK,QAAQ;GACnB,EAKA,EAAE,EACF,eACA,EAEiD,CAAC;UAC3C,OAAO;AACf,MAAI,iBAAiB,YAMpB,QAAO,SAAS,MAAM,KAAK,aAAa,EAAE,MAAM,SALN;GACzC,eAAe;GACf,eAAe;GACf,aAAa;GACb,CACkE,MAAM,SAAS,IAAI;AAGvF,SAAO,YACN,OACA,2CACA,gCACA"}