{"version":3,"file":"options.mjs","names":[],"sources":["../../../../../../src/astro/routes/api/auth/passkey/options.ts"],"sourcesContent":["/**\n * POST /_emdash/api/auth/passkey/options\n *\n * Get authentication options for passkey login.\n *\n * Rate limited: 10 requests per minute per IP.\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { createKyselyAdapter } from \"@premium-cms/auth/adapters/kysely\";\nimport { generateAuthenticationOptions } 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 { passkeyOptionsBody } from \"#api/schemas.js\";\nimport { createChallengeStore, cleanupExpiredChallenges } from \"#auth/challenge-store.js\";\nimport { getPasskeyConfig } from \"#auth/passkey-config.js\";\nimport { checkRateLimit, getClientIp, rateLimitResponse } from \"#auth/rate-limit.js\";\nimport { getTrustedProxyHeaders } from \"#auth/trusted-proxy.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\t// Fire-and-forget cleanup of expired challenges -- prevents accumulation\n\t\tvoid cleanupExpiredChallenges(emdash.db).catch(() => {});\n\n\t\t// Parse body before rate limiting so malformed requests don't consume slots\n\t\tconst body = await parseOptionalBody(request, passkeyOptionsBody, {});\n\t\tif (isParseError(body)) return body;\n\n\t\t// Rate limit: 10 requests per 60 seconds per IP\n\t\tconst ip = getClientIp(request, getTrustedProxyHeaders(emdash.config));\n\t\tconst rateLimit = await checkRateLimit(emdash.db, ip, \"passkey/options\", 10, 60);\n\t\tif (!rateLimit.allowed) {\n\t\t\treturn rateLimitResponse(60);\n\t\t}\n\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\n\t\t// Get credentials to allow\n\t\tlet credentials: Awaited<ReturnType<typeof adapter.getCredentialsByUserId>> = [];\n\n\t\tif (body.email) {\n\t\t\t// Get credentials for specific user\n\t\t\tconst user = await adapter.getUserByEmail(body.email);\n\t\t\tif (user) {\n\t\t\t\tcredentials = await adapter.getCredentialsByUserId(user.id);\n\t\t\t}\n\t\t\t// Don't reveal if user exists - just return empty allowCredentials\n\t\t}\n\t\t// If no email provided, allowCredentials will be undefined (allow any discoverable credential)\n\n\t\t// Get passkey config\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 authentication options\n\t\tconst challengeStore = createChallengeStore(emdash.db);\n\t\tconst authOptions = await generateAuthenticationOptions(\n\t\t\tpasskeyConfig,\n\t\t\tcredentials,\n\t\t\tchallengeStore,\n\t\t);\n\n\t\treturn apiSuccess({\n\t\t\tsuccess: true,\n\t\t\toptions: authOptions,\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to generate passkey options\", \"PASSKEY_OPTIONS_ERROR\");\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;AAEH,EAAK,yBAAyB,OAAO,GAAG,CAAC,YAAY,GAAG;EAGxD,MAAM,OAAO,MAAM,kBAAkB,SAAS,oBAAoB,EAAE,CAAC;AACrE,MAAI,aAAa,KAAK,CAAE,QAAO;EAG/B,MAAM,KAAK,YAAY,SAAS,uBAAuB,OAAO,OAAO,CAAC;AAEtE,MAAI,EADc,MAAM,eAAe,OAAO,IAAI,IAAI,mBAAmB,IAAI,GAAG,EACjE,QACd,QAAO,kBAAkB,GAAG;EAG7B,MAAM,UAAU,oBAAoB,OAAO,GAAG;EAG9C,IAAI,cAA0E,EAAE;AAEhF,MAAI,KAAK,OAAO;GAEf,MAAM,OAAO,MAAM,QAAQ,eAAe,KAAK,MAAM;AACrD,OAAI,KACH,eAAc,MAAM,QAAQ,uBAAuB,KAAK,GAAG;;EAO7D,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;AAOtD,SAAO,WAAW;GACjB,SAAS;GACT,SARmB,MAAM,8BACzB,eACA,aACA,eACA;GAKA,CAAC;UACM,OAAO;AACf,SAAO,YAAY,OAAO,sCAAsC,wBAAwB"}