{"version":3,"file":"options.mjs","names":[],"sources":["../../../../../../../src/astro/routes/api/auth/passkey/register/options.ts"],"sourcesContent":["/**\n * POST /_emdash/api/auth/passkey/register/options\n *\n * Get WebAuthn registration options for adding a new passkey (authenticated user)\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { createKyselyAdapter } from \"@premium-cms/auth/adapters/kysely\";\nimport { generateRegistrationOptions } from \"@premium-cms/auth/passkey\";\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseOptionalBody } from \"#api/parse.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { passkeyRegisterOptionsBody } 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\nconst MAX_PASSKEYS = 10;\n\nexport const POST: APIRoute = async ({ request, locals }) => {\n\tconst { emdash, user } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\t// Require authentication\n\tif (!user) {\n\t\treturn apiError(\"NOT_AUTHENTICATED\", \"Not authenticated\", 401);\n\t}\n\n\ttry {\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\n\t\t// Check passkey limit\n\t\tconst count = await adapter.countCredentialsByUserId(user.id);\n\t\tif (count >= MAX_PASSKEYS) {\n\t\t\treturn apiError(\"PASSKEY_LIMIT\", `Maximum of ${MAX_PASSKEYS} passkeys allowed`, 400);\n\t\t}\n\n\t\t// Parse optional name from request\n\t\tconst body = await parseOptionalBody(request, passkeyRegisterOptionsBody, {});\n\t\tif (isParseError(body)) return body;\n\n\t\t// Get existing credentials for excludeCredentials\n\t\tconst existingCredentials = await adapter.getCredentialsByUserId(user.id);\n\n\t\t// Get passkey config\n\t\tconst url = new URL(request.url);\n\t\tconst optionsRepo = new OptionsRepository(emdash.db);\n\t\tconst siteName = (await optionsRepo.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\n\t\tconst challengeStore = createChallengeStore(emdash.db);\n\t\tconst registrationOptions = await generateRegistrationOptions(\n\t\t\tpasskeyConfig,\n\t\t\t{ id: user.id, email: user.email, name: user.name },\n\t\t\texistingCredentials,\n\t\t\tchallengeStore,\n\t\t);\n\n\t\t// Store the passkey name in the challenge metadata if provided\n\t\t// We'll retrieve it during verification\n\t\tif (body.name) {\n\t\t\t// Store name with challenge for later retrieval\n\t\t\t// The challenge store will need this when verifying\n\t\t\tawait optionsRepo.set(`emdash:passkey_pending:${user.id}`, {\n\t\t\t\tname: body.name,\n\t\t\t});\n\t\t}\n\n\t\treturn apiSuccess({\n\t\t\toptions: registrationOptions,\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(\n\t\t\terror,\n\t\t\t\"Failed to generate registration options\",\n\t\t\t\"PASSKEY_REGISTER_OPTIONS_ERROR\",\n\t\t);\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAQA,MAAa,YAAY;AAazB,MAAM,eAAe;AAErB,MAAa,OAAiB,OAAO,EAAE,SAAS,aAAa;CAC5D,MAAM,EAAE,QAAQ,SAAS;AAEzB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAIpE,KAAI,CAAC,KACJ,QAAO,SAAS,qBAAqB,qBAAqB,IAAI;AAG/D,KAAI;EACH,MAAM,UAAU,oBAAoB,OAAO,GAAG;AAI9C,MADc,MAAM,QAAQ,yBAAyB,KAAK,GAAG,IAChD,aACZ,QAAO,SAAS,iBAAiB,cAAc,aAAa,oBAAoB,IAAI;EAIrF,MAAM,OAAO,MAAM,kBAAkB,SAAS,4BAA4B,EAAE,CAAC;AAC7E,MAAI,aAAa,KAAK,CAAE,QAAO;EAG/B,MAAM,sBAAsB,MAAM,QAAQ,uBAAuB,KAAK,GAAG;EAGzE,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;EAChC,MAAM,cAAc,IAAI,kBAAkB,OAAO,GAAG;EAGpD,MAAM,gBAAgB,iBAAiB,KAFrB,MAAM,YAAY,IAAY,oBAAoB,IAAK,QACzD,gBAAgB,KAAK,QAAQ,OAAO,CACU;EAG9D,MAAM,iBAAiB,qBAAqB,OAAO,GAAG;EACtD,MAAM,sBAAsB,MAAM,4BACjC,eACA;GAAE,IAAI,KAAK;GAAI,OAAO,KAAK;GAAO,MAAM,KAAK;GAAM,EACnD,qBACA,eACA;AAID,MAAI,KAAK,KAGR,OAAM,YAAY,IAAI,0BAA0B,KAAK,MAAM,EAC1D,MAAM,KAAK,MACX,CAAC;AAGH,SAAO,WAAW,EACjB,SAAS,qBACT,CAAC;UACM,OAAO;AACf,SAAO,YACN,OACA,2CACA,iCACA"}