@puzzle-ui/fullstack 0.0.0-stage → 0.5.0

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.
Files changed (80) hide show
  1. package/README.md +558 -2
  2. package/dist/LICENSE +21 -0
  3. package/dist/auth/client/auth.css +206 -0
  4. package/dist/auth/client/experience.cjs +340 -0
  5. package/dist/auth/client/experience.cjs.map +1 -0
  6. package/dist/auth/client/experience.d.cts +67 -0
  7. package/dist/auth/client/experience.d.ts +67 -0
  8. package/dist/auth/client/experience.js +340 -0
  9. package/dist/auth/client/experience.js.map +1 -0
  10. package/dist/auth/client/index.cjs +234 -0
  11. package/dist/auth/client/index.cjs.map +1 -0
  12. package/dist/auth/client/index.d.cts +48 -0
  13. package/dist/auth/client/index.d.ts +48 -0
  14. package/dist/auth/client/index.js +216 -0
  15. package/dist/auth/client/index.js.map +1 -0
  16. package/dist/auth/client/messages.cjs +80 -0
  17. package/dist/auth/client/messages.cjs.map +1 -0
  18. package/dist/auth/client/messages.d.cts +92 -0
  19. package/dist/auth/client/messages.d.ts +92 -0
  20. package/dist/auth/client/messages.js +79 -0
  21. package/dist/auth/client/messages.js.map +1 -0
  22. package/dist/auth/client/passkeys.cjs +43 -0
  23. package/dist/auth/client/passkeys.cjs.map +1 -0
  24. package/dist/auth/client/passkeys.d.cts +23 -0
  25. package/dist/auth/client/passkeys.d.ts +23 -0
  26. package/dist/auth/client/passkeys.js +40 -0
  27. package/dist/auth/client/passkeys.js.map +1 -0
  28. package/dist/auth/client/react.cjs +859 -0
  29. package/dist/auth/client/react.cjs.map +1 -0
  30. package/dist/auth/client/react.d.cts +72 -0
  31. package/dist/auth/client/react.d.ts +72 -0
  32. package/dist/auth/client/react.js +850 -0
  33. package/dist/auth/client/react.js.map +1 -0
  34. package/dist/auth/next/index.cjs +40 -0
  35. package/dist/auth/next/index.cjs.map +1 -0
  36. package/dist/auth/next/index.d.cts +12 -0
  37. package/dist/auth/next/index.d.ts +12 -0
  38. package/dist/auth/next/index.js +38 -0
  39. package/dist/auth/next/index.js.map +1 -0
  40. package/dist/auth/server/health.cjs +51 -0
  41. package/dist/auth/server/health.cjs.map +1 -0
  42. package/dist/auth/server/health.d.cts +15 -0
  43. package/dist/auth/server/health.d.ts +15 -0
  44. package/dist/auth/server/health.js +51 -0
  45. package/dist/auth/server/health.js.map +1 -0
  46. package/dist/auth/server/index.cjs +108 -0
  47. package/dist/auth/server/index.cjs.map +1 -0
  48. package/dist/auth/server/index.d.cts +22 -0
  49. package/dist/auth/server/index.d.ts +22 -0
  50. package/dist/auth/server/index.js +88 -0
  51. package/dist/auth/server/index.js.map +1 -0
  52. package/dist/auth/server/lifecycle.cjs +126 -0
  53. package/dist/auth/server/lifecycle.cjs.map +1 -0
  54. package/dist/auth/server/lifecycle.d.cts +20 -0
  55. package/dist/auth/server/lifecycle.d.ts +20 -0
  56. package/dist/auth/server/lifecycle.js +126 -0
  57. package/dist/auth/server/lifecycle.js.map +1 -0
  58. package/dist/auth/server/mfa.cjs +682 -0
  59. package/dist/auth/server/mfa.cjs.map +1 -0
  60. package/dist/auth/server/mfa.d.cts +90 -0
  61. package/dist/auth/server/mfa.d.ts +90 -0
  62. package/dist/auth/server/mfa.js +673 -0
  63. package/dist/auth/server/mfa.js.map +1 -0
  64. package/dist/auth/server/session.cjs +58 -0
  65. package/dist/auth/server/session.cjs.map +1 -0
  66. package/dist/auth/server/session.d.cts +14 -0
  67. package/dist/auth/server/session.d.ts +14 -0
  68. package/dist/auth/server/session.js +53 -0
  69. package/dist/auth/server/session.js.map +1 -0
  70. package/dist/auth/shared/index.cjs +0 -0
  71. package/dist/auth/shared/index.d.cts +64 -0
  72. package/dist/auth/shared/index.d.ts +64 -0
  73. package/dist/auth/shared/index.js +1 -0
  74. package/dist/auth/tanstack/index.cjs +34 -0
  75. package/dist/auth/tanstack/index.cjs.map +1 -0
  76. package/dist/auth/tanstack/index.d.cts +7 -0
  77. package/dist/auth/tanstack/index.d.ts +7 -0
  78. package/dist/auth/tanstack/index.js +32 -0
  79. package/dist/auth/tanstack/index.js.map +1 -0
  80. package/package.json +108 -4
@@ -0,0 +1,88 @@
1
+ import { AuthStepUpRequiredError, MFA_CHALLENGE_COOKIE_NAME, createAuthMfaHandlers, createMfaChallengeResponse, createMfaPendingResponse, hasRecentStepUp, readMfaChallengeCookie, requireRecentStepUp, serializeClearedMfaChallengeCookie, serializeMfaChallengeCookie } from "./mfa.js";
2
+ import { createAuthLifecycleHandlers } from "./lifecycle.js";
3
+ import { clearSessionResponse, createSessionResponse, getRequestSession, logoutAllRequest, logoutRequest, protectAuthenticatedRequest } from "./session.js";
4
+ import { createAuthReadinessHandler } from "./health.js";
5
+ // src/auth/server/index.ts
6
+ const createInitializationErrorResponse = () => new Response(JSON.stringify({ error: {
7
+ code: "AUTH_INITIALIZATION_FAILED",
8
+ message: "Authentication is temporarily unavailable."
9
+ } }), {
10
+ status: 503,
11
+ headers: {
12
+ "Content-Type": "application/json; charset=utf-8",
13
+ "Cache-Control": "no-store",
14
+ Pragma: "no-cache"
15
+ }
16
+ });
17
+ const createAuthServer = (options) => {
18
+ let initializationPromise = null;
19
+ const initialize = () => {
20
+ if (!initializationPromise) initializationPromise = (async () => {
21
+ await options.mfa.initialize();
22
+ await options.auth.initialize();
23
+ await options.stepUp.initialize();
24
+ if (options.lifecycle) await options.lifecycle.lifecycle.initialize();
25
+ })().catch((error) => {
26
+ initializationPromise = null;
27
+ throw error;
28
+ });
29
+ return initializationPromise;
30
+ };
31
+ const rawHandlers = {
32
+ ...createAuthMfaHandlers(options),
33
+ ...options.lifecycle ? createAuthLifecycleHandlers(options.lifecycle) : {}
34
+ };
35
+ return {
36
+ options,
37
+ handlers: Object.fromEntries(Object.entries(rawHandlers).map(([name, handler]) => [name, async (request) => {
38
+ try {
39
+ await initialize();
40
+ } catch {
41
+ return createInitializationErrorResponse();
42
+ }
43
+ return handler(request);
44
+ }])),
45
+ initialize
46
+ };
47
+ };
48
+ const createAuthRouteHandlers = (server, basePath = "/api/auth") => {
49
+ const prefix = basePath.replace(/\/+$/, "");
50
+ const route = (path) => `${prefix}${path}`;
51
+ const handlers = server.handlers;
52
+ return {
53
+ [route("/register")]: { POST: handlers.register },
54
+ [route("/sign-in")]: { POST: handlers.signIn },
55
+ [route("/state")]: { GET: handlers.getAuthState },
56
+ [route("/sign-out")]: { POST: handlers.signOut },
57
+ [route("/mfa/sign-in/totp")]: { POST: handlers.verifySignInTotp },
58
+ [route("/mfa/sign-in/recovery-code")]: { POST: handlers.verifySignInRecoveryCode },
59
+ [route("/mfa/passkey/sign-in/start")]: { POST: handlers.beginPasskeySignIn },
60
+ [route("/mfa/passkey/sign-in/finish")]: { POST: handlers.finishPasskeySignIn },
61
+ [route("/mfa/passkey/challenge/start")]: { POST: handlers.beginPasskeyMfa },
62
+ [route("/mfa/passkey/challenge/finish")]: { POST: handlers.finishPasskeyMfa },
63
+ [route("/mfa/totp/enrollment/start")]: { POST: handlers.beginTotpEnrollment },
64
+ [route("/mfa/totp/enrollment/finish")]: { POST: handlers.verifyTotpEnrollment },
65
+ [route("/mfa/passkey/enrollment/start")]: { POST: handlers.beginPasskeyRegistration },
66
+ [route("/mfa/passkey/enrollment/finish")]: { POST: handlers.finishPasskeyRegistration },
67
+ [route("/mfa/factors")]: { GET: handlers.listMfaFactors },
68
+ [route("/mfa/factors/remove")]: { POST: handlers.removeMfaFactor },
69
+ [route("/mfa/recovery-codes/regenerate")]: { POST: handlers.regenerateRecoveryCodes },
70
+ [route("/mfa/step-up/start")]: { POST: handlers.beginStepUp },
71
+ [route("/mfa/step-up/totp")]: { POST: handlers.verifyStepUpTotp },
72
+ [route("/mfa/step-up/recovery-code")]: { POST: handlers.verifyStepUpRecoveryCode },
73
+ [route("/mfa/step-up/passkey/start")]: { POST: handlers.beginStepUpPasskey },
74
+ [route("/mfa/step-up/passkey/finish")]: { POST: handlers.finishStepUpPasskey },
75
+ [route("/mfa/enrollment/required/totp/start")]: { POST: handlers.beginRequiredTotpEnrollment },
76
+ [route("/mfa/enrollment/required/totp/finish")]: { POST: handlers.verifyRequiredTotpEnrollment },
77
+ [route("/mfa/enrollment/required/passkey/start")]: { POST: handlers.beginRequiredPasskeyEnrollment },
78
+ [route("/mfa/enrollment/required/passkey/finish")]: { POST: handlers.finishRequiredPasskeyEnrollment },
79
+ ...handlers.requestEmailVerification ? { [route("/email/verification/request")]: { POST: handlers.requestEmailVerification } } : {},
80
+ ...handlers.verifyEmail ? { [route("/email/verification/verify")]: { POST: handlers.verifyEmail } } : {},
81
+ ...handlers.requestPasswordReset ? { [route("/password/reset/request")]: { POST: handlers.requestPasswordReset } } : {},
82
+ ...handlers.resetPassword ? { [route("/password/reset/finish")]: { POST: handlers.resetPassword } } : {},
83
+ ...handlers.changePassword ? { [route("/password/change")]: { POST: handlers.changePassword } } : {}
84
+ };
85
+ };
86
+ export { AuthStepUpRequiredError, MFA_CHALLENGE_COOKIE_NAME, clearSessionResponse, createAuthLifecycleHandlers, createAuthMfaHandlers, createAuthReadinessHandler, createAuthRouteHandlers, createAuthServer, createMfaChallengeResponse, createMfaPendingResponse, createSessionResponse, getRequestSession, hasRecentStepUp, logoutAllRequest, logoutRequest, protectAuthenticatedRequest, readMfaChallengeCookie, requireRecentStepUp, serializeClearedMfaChallengeCookie, serializeMfaChallengeCookie };
87
+
88
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/auth/server/index.ts"],"sourcesContent":["import type {\n EmailProvider,\n PasswordHasher,\n RateLimitProvider,\n} from \"@puzzle-ui/auth\";\n\nimport {\n createAuthMfaHandlers,\n type AuthMfaHandlers,\n type AuthMfaHandlersOptions,\n} from \"./mfa\";\n\nimport {\n createAuthLifecycleHandlers,\n type AuthLifecycleHandlers,\n type AuthLifecycleHandlersOptions,\n} from \"./lifecycle\";\n\nexport type AuthServerOptions = AuthMfaHandlersOptions & {\n readonly passwordHasher?: PasswordHasher;\n readonly emailProvider?: EmailProvider;\n readonly rateLimitProvider?: RateLimitProvider;\n readonly lifecycle?: AuthLifecycleHandlersOptions;\n};\n\nexport type AuthServerHandlers = AuthMfaHandlers &\n Partial<AuthLifecycleHandlers>;\n\nexport type AuthServer = {\n readonly options: AuthServerOptions;\n readonly handlers: AuthServerHandlers;\n initialize(): Promise<void>;\n};\n\nexport type AuthHttpMethod = \"GET\" | \"POST\";\n\nexport type AuthRouteHandlerMap = Readonly<\n Record<\n string,\n Readonly<\n Partial<Record<AuthHttpMethod, (request: Request) => Promise<Response>>>\n >\n >\n>;\n\nconst createInitializationErrorResponse = (): Response =>\n new Response(\n JSON.stringify({\n error: {\n code: \"AUTH_INITIALIZATION_FAILED\",\n message: \"Authentication is temporarily unavailable.\",\n },\n }),\n {\n status: 503,\n headers: {\n \"Content-Type\": \"application/json; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n Pragma: \"no-cache\",\n },\n },\n );\n\nexport const createAuthServer = (options: AuthServerOptions): AuthServer => {\n let initializationPromise: Promise<void> | null = null;\n\n const initialize = (): Promise<void> => {\n if (!initializationPromise) {\n const initialization = (async () => {\n await options.mfa.initialize();\n await options.auth.initialize();\n await options.stepUp.initialize();\n\n if (options.lifecycle) {\n await options.lifecycle.lifecycle.initialize();\n }\n })();\n\n initializationPromise = initialization.catch((error) => {\n initializationPromise = null;\n throw error;\n });\n }\n\n return initializationPromise;\n };\n\n const rawHandlers: AuthServerHandlers = {\n ...createAuthMfaHandlers(options),\n ...(options.lifecycle\n ? createAuthLifecycleHandlers(options.lifecycle)\n : {}),\n };\n\n const handlers = Object.fromEntries(\n Object.entries(rawHandlers).map(([name, handler]) => [\n name,\n async (request: Request): Promise<Response> => {\n try {\n await initialize();\n } catch {\n return createInitializationErrorResponse();\n }\n\n return handler!(request);\n },\n ]),\n ) as AuthServerHandlers;\n\n return {\n options,\n handlers,\n initialize,\n };\n};\n\nexport const createAuthRouteHandlers = (\n server: AuthServer,\n basePath = \"/api/auth\",\n): AuthRouteHandlerMap => {\n const prefix = basePath.replace(/\\/+$/, \"\");\n\n const route = (path: string): string => `${prefix}${path}`;\n\n const handlers = server.handlers;\n\n return {\n [route(\"/register\")]: {\n POST: handlers.register,\n },\n\n [route(\"/sign-in\")]: {\n POST: handlers.signIn,\n },\n\n [route(\"/state\")]: {\n GET: handlers.getAuthState,\n },\n\n [route(\"/sign-out\")]: {\n POST: handlers.signOut,\n },\n\n [route(\"/mfa/sign-in/totp\")]: {\n POST: handlers.verifySignInTotp,\n },\n\n [route(\"/mfa/sign-in/recovery-code\")]: {\n POST: handlers.verifySignInRecoveryCode,\n },\n\n [route(\"/mfa/passkey/sign-in/start\")]: {\n POST: handlers.beginPasskeySignIn,\n },\n\n [route(\"/mfa/passkey/sign-in/finish\")]: {\n POST: handlers.finishPasskeySignIn,\n },\n\n [route(\"/mfa/passkey/challenge/start\")]: {\n POST: handlers.beginPasskeyMfa,\n },\n\n [route(\"/mfa/passkey/challenge/finish\")]: {\n POST: handlers.finishPasskeyMfa,\n },\n\n [route(\"/mfa/totp/enrollment/start\")]: {\n POST: handlers.beginTotpEnrollment,\n },\n\n [route(\"/mfa/totp/enrollment/finish\")]: {\n POST: handlers.verifyTotpEnrollment,\n },\n\n [route(\"/mfa/passkey/enrollment/start\")]: {\n POST: handlers.beginPasskeyRegistration,\n },\n\n [route(\"/mfa/passkey/enrollment/finish\")]: {\n POST: handlers.finishPasskeyRegistration,\n },\n\n [route(\"/mfa/factors\")]: {\n GET: handlers.listMfaFactors,\n },\n\n [route(\"/mfa/factors/remove\")]: {\n POST: handlers.removeMfaFactor,\n },\n\n [route(\"/mfa/recovery-codes/regenerate\")]: {\n POST: handlers.regenerateRecoveryCodes,\n },\n\n [route(\"/mfa/step-up/start\")]: {\n POST: handlers.beginStepUp,\n },\n\n [route(\"/mfa/step-up/totp\")]: {\n POST: handlers.verifyStepUpTotp,\n },\n\n [route(\"/mfa/step-up/recovery-code\")]: {\n POST: handlers.verifyStepUpRecoveryCode,\n },\n\n [route(\"/mfa/step-up/passkey/start\")]: {\n POST: handlers.beginStepUpPasskey,\n },\n\n [route(\"/mfa/step-up/passkey/finish\")]: {\n POST: handlers.finishStepUpPasskey,\n },\n\n [route(\"/mfa/enrollment/required/totp/start\")]: {\n POST: handlers.beginRequiredTotpEnrollment,\n },\n\n [route(\"/mfa/enrollment/required/totp/finish\")]: {\n POST: handlers.verifyRequiredTotpEnrollment,\n },\n\n [route(\"/mfa/enrollment/required/passkey/start\")]: {\n POST: handlers.beginRequiredPasskeyEnrollment,\n },\n\n [route(\"/mfa/enrollment/required/passkey/finish\")]: {\n POST: handlers.finishRequiredPasskeyEnrollment,\n },\n\n ...(handlers.requestEmailVerification\n ? {\n [route(\"/email/verification/request\")]: {\n POST: handlers.requestEmailVerification,\n },\n }\n : {}),\n\n ...(handlers.verifyEmail\n ? {\n [route(\"/email/verification/verify\")]: {\n POST: handlers.verifyEmail,\n },\n }\n : {}),\n\n ...(handlers.requestPasswordReset\n ? {\n [route(\"/password/reset/request\")]: {\n POST: handlers.requestPasswordReset,\n },\n }\n : {}),\n\n ...(handlers.resetPassword\n ? {\n [route(\"/password/reset/finish\")]: {\n POST: handlers.resetPassword,\n },\n }\n : {}),\n\n ...(handlers.changePassword\n ? {\n [route(\"/password/change\")]: {\n POST: handlers.changePassword,\n },\n }\n : {}),\n };\n};\n\nexport {\n getRequestSession,\n protectAuthenticatedRequest,\n createSessionResponse,\n clearSessionResponse,\n logoutRequest,\n logoutAllRequest,\n} from \"./session\";\n\nexport type { SessionRequestOptions, SessionResponseOptions } from \"./session\";\n\nexport {\n MFA_CHALLENGE_COOKIE_NAME,\n serializeMfaChallengeCookie,\n serializeClearedMfaChallengeCookie,\n readMfaChallengeCookie,\n createMfaPendingResponse,\n createMfaChallengeResponse,\n createAuthMfaHandlers,\n hasRecentStepUp,\n requireRecentStepUp,\n AuthStepUpRequiredError,\n} from \"./mfa\";\n\nexport type {\n AuthMfaHandlersOptions,\n AuthMfaHandlers,\n AuthMfaPendingResponse,\n AuthClientUserResponse,\n} from \"./mfa\";\n\nexport { createAuthLifecycleHandlers } from \"./lifecycle\";\n\nexport type {\n AuthLifecycleHandlers,\n AuthLifecycleHandlersOptions,\n} from \"./lifecycle\";\n\nexport { createAuthReadinessHandler } from \"./health\";\n\nexport type {\n AuthReadinessCheck,\n AuthReadinessOptions,\n AuthReadinessResult,\n} from \"./health\";\n"],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ const require_session = require("./session.cjs");
2
+ let _puzzle_ui_auth = require("@puzzle-ui/auth");
3
+ let _puzzle_ui_session = require("@puzzle-ui/session");
4
+ // src/auth/server/lifecycle.ts
5
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6
+ const jsonResponse = (body, status = 200) => new Response(JSON.stringify(body), {
7
+ status,
8
+ headers: {
9
+ "Content-Type": "application/json; charset=utf-8",
10
+ "Cache-Control": "no-store",
11
+ Pragma: "no-cache"
12
+ }
13
+ });
14
+ const readBody = async (request) => {
15
+ let body;
16
+ try {
17
+ body = await request.json();
18
+ } catch {
19
+ throw new Error("INVALID_REQUEST");
20
+ }
21
+ if (!isRecord(body)) throw new Error("INVALID_REQUEST");
22
+ return body;
23
+ };
24
+ const requiredString = (body, field, maximumLength = 2048) => {
25
+ const value = body[field];
26
+ if (typeof value !== "string" || !value.trim() || value.length > maximumLength) throw new Error("INVALID_REQUEST");
27
+ return value;
28
+ };
29
+ const getErrorResponse = (error) => {
30
+ if (error instanceof _puzzle_ui_auth.AuthError) {
31
+ const status = error.code === "RATE_LIMITED" ? 429 : error.code === "PROVIDER" ? 503 : error.code === "SESSION" ? 401 : 400;
32
+ return jsonResponse({ error: {
33
+ code: error.code,
34
+ message: error.message
35
+ } }, status);
36
+ }
37
+ if (error instanceof Error && error.message === "INVALID_REQUEST") return jsonResponse({ error: {
38
+ code: "INVALID_REQUEST",
39
+ message: "The request is invalid."
40
+ } }, 400);
41
+ return jsonResponse({ error: {
42
+ code: "INTERNAL_AUTH_ERROR",
43
+ message: "An authentication error occurred."
44
+ } }, 500);
45
+ };
46
+ const endpoint = (handler) => async (request) => {
47
+ try {
48
+ if (request.method !== "POST") return jsonResponse({ error: {
49
+ code: "METHOD_NOT_ALLOWED",
50
+ message: "Only POST requests are supported."
51
+ } }, 405);
52
+ return await handler(request);
53
+ } catch (error) {
54
+ return getErrorResponse(error);
55
+ }
56
+ };
57
+ const getSessionToken = (request, cookie) => (0, _puzzle_ui_session.readSessionCookie)(request.headers.get("cookie"), cookie.name);
58
+ const createAuthLifecycleHandlers = (options) => {
59
+ const sessionOptions = {
60
+ sessions: options.sessions,
61
+ cookie: options.cookie
62
+ };
63
+ const protect = (request) => {
64
+ (0, _puzzle_ui_session.protectStateChangingRequest)(request, options.csrf);
65
+ };
66
+ return {
67
+ requestEmailVerification: endpoint(async (request) => {
68
+ protect(request);
69
+ const session = await require_session.getRequestSession(request, sessionOptions);
70
+ if (!session || session.kind !== "authenticated" || !session.userId) return jsonResponse({ error: {
71
+ code: "UNAUTHENTICATED",
72
+ message: "Authentication is required."
73
+ } }, 401);
74
+ await options.lifecycle.sendEmailVerification({
75
+ userId: session.userId,
76
+ verificationUrl: options.verificationUrl(request),
77
+ ipAddress: options.resolveClientIp(request)
78
+ });
79
+ return jsonResponse({ status: "verification-requested" });
80
+ }),
81
+ verifyEmail: endpoint(async (request) => {
82
+ protect(request);
83
+ const body = await readBody(request);
84
+ const token = requiredString(body, "token");
85
+ await options.lifecycle.verifyEmail(token);
86
+ return jsonResponse({ status: "email-verified" });
87
+ }),
88
+ requestPasswordReset: endpoint(async (request) => {
89
+ protect(request);
90
+ const body = await readBody(request);
91
+ await options.lifecycle.requestPasswordReset({
92
+ email: requiredString(body, "email", 320),
93
+ resetUrl: options.passwordResetUrl(request),
94
+ ipAddress: options.resolveClientIp(request)
95
+ });
96
+ return jsonResponse({ status: "password-reset-requested" });
97
+ }),
98
+ resetPassword: endpoint(async (request) => {
99
+ protect(request);
100
+ const body = await readBody(request);
101
+ await options.lifecycle.resetPassword({
102
+ token: requiredString(body, "token"),
103
+ newPassword: requiredString(body, "newPassword")
104
+ });
105
+ return jsonResponse({ status: "password-reset" });
106
+ }),
107
+ changePassword: endpoint(async (request) => {
108
+ protect(request);
109
+ const sessionToken = getSessionToken(request, options.cookie);
110
+ if (!sessionToken) return jsonResponse({ error: {
111
+ code: "UNAUTHENTICATED",
112
+ message: "Authentication is required."
113
+ } }, 401);
114
+ const body = await readBody(request);
115
+ await options.lifecycle.changePassword({
116
+ sessionToken,
117
+ currentPassword: requiredString(body, "currentPassword"),
118
+ newPassword: requiredString(body, "newPassword")
119
+ });
120
+ return jsonResponse({ status: "password-changed" });
121
+ })
122
+ };
123
+ };
124
+ exports.createAuthLifecycleHandlers = createAuthLifecycleHandlers;
125
+
126
+ //# sourceMappingURL=lifecycle.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.cjs","names":["AuthError","readSessionCookie","getRequestSession"],"sources":["../../../src/auth/server/lifecycle.ts"],"sourcesContent":["import { AuthError, type StorageAuthLifecycleManager } from \"@puzzle-ui/auth\";\n\nimport {\n readSessionCookie,\n protectStateChangingRequest,\n type CsrfOptions,\n type SessionCookieOptions,\n type SessionManager,\n} from \"@puzzle-ui/session\";\n\nimport { getRequestSession, type SessionRequestOptions } from \"./session\";\n\nexport type AuthLifecycleHandlersOptions = {\n readonly lifecycle: StorageAuthLifecycleManager;\n readonly sessions: SessionManager;\n readonly cookie: SessionCookieOptions;\n readonly csrf: CsrfOptions;\n readonly resolveClientIp: (request: Request) => string;\n readonly verificationUrl: (request: Request) => string;\n readonly passwordResetUrl: (request: Request) => string;\n};\n\nexport type AuthLifecycleHandlers = {\n readonly requestEmailVerification: (request: Request) => Promise<Response>;\n readonly verifyEmail: (request: Request) => Promise<Response>;\n readonly requestPasswordReset: (request: Request) => Promise<Response>;\n readonly resetPassword: (request: Request) => Promise<Response>;\n readonly changePassword: (request: Request) => Promise<Response>;\n};\n\ntype JsonObject = Record<string, unknown>;\n\nconst isRecord = (value: unknown): value is JsonObject =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst jsonResponse = (body: unknown, status = 200): Response =>\n new Response(JSON.stringify(body), {\n status,\n headers: {\n \"Content-Type\": \"application/json; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n Pragma: \"no-cache\",\n },\n });\n\nconst readBody = async (request: Request): Promise<JsonObject> => {\n let body: unknown;\n\n try {\n body = await request.json();\n } catch {\n throw new Error(\"INVALID_REQUEST\");\n }\n\n if (!isRecord(body)) {\n throw new Error(\"INVALID_REQUEST\");\n }\n\n return body;\n};\n\nconst requiredString = (\n body: JsonObject,\n field: string,\n maximumLength = 2048,\n): string => {\n const value = body[field];\n\n if (\n typeof value !== \"string\" ||\n !value.trim() ||\n value.length > maximumLength\n ) {\n throw new Error(\"INVALID_REQUEST\");\n }\n\n return value;\n};\n\nconst getErrorResponse = (error: unknown): Response => {\n if (error instanceof AuthError) {\n const status =\n error.code === \"RATE_LIMITED\"\n ? 429\n : error.code === \"PROVIDER\"\n ? 503\n : error.code === \"SESSION\"\n ? 401\n : 400;\n\n return jsonResponse(\n {\n error: {\n code: error.code,\n message: error.message,\n },\n },\n status,\n );\n }\n\n if (error instanceof Error && error.message === \"INVALID_REQUEST\") {\n return jsonResponse(\n {\n error: {\n code: \"INVALID_REQUEST\",\n message: \"The request is invalid.\",\n },\n },\n 400,\n );\n }\n\n return jsonResponse(\n {\n error: {\n code: \"INTERNAL_AUTH_ERROR\",\n message: \"An authentication error occurred.\",\n },\n },\n 500,\n );\n};\n\nconst endpoint =\n (handler: (request: Request) => Promise<Response>) =>\n async (request: Request): Promise<Response> => {\n try {\n if (request.method !== \"POST\") {\n return jsonResponse(\n {\n error: {\n code: \"METHOD_NOT_ALLOWED\",\n message: \"Only POST requests are supported.\",\n },\n },\n 405,\n );\n }\n\n return await handler(request);\n } catch (error) {\n return getErrorResponse(error);\n }\n };\n\nconst getSessionToken = (\n request: Request,\n cookie: SessionCookieOptions,\n): string | null =>\n readSessionCookie(request.headers.get(\"cookie\"), cookie.name);\n\nconst createAuthLifecycleHandlers = (\n options: AuthLifecycleHandlersOptions,\n): AuthLifecycleHandlers => {\n const sessionOptions: SessionRequestOptions = {\n sessions: options.sessions,\n cookie: options.cookie,\n };\n\n const protect = (request: Request) => {\n protectStateChangingRequest(request, options.csrf);\n };\n\n const requestEmailVerification = endpoint(async (request) => {\n protect(request);\n\n const session = await getRequestSession(request, sessionOptions);\n\n if (!session || session.kind !== \"authenticated\" || !session.userId) {\n return jsonResponse(\n {\n error: {\n code: \"UNAUTHENTICATED\",\n message: \"Authentication is required.\",\n },\n },\n 401,\n );\n }\n\n await options.lifecycle.sendEmailVerification({\n userId: session.userId,\n verificationUrl: options.verificationUrl(request),\n ipAddress: options.resolveClientIp(request),\n });\n\n return jsonResponse({\n status: \"verification-requested\",\n });\n });\n\n const verifyEmail = endpoint(async (request) => {\n protect(request);\n\n const body = await readBody(request);\n const token = requiredString(body, \"token\");\n\n await options.lifecycle.verifyEmail(token);\n\n return jsonResponse({\n status: \"email-verified\",\n });\n });\n\n const requestPasswordReset = endpoint(async (request) => {\n protect(request);\n\n const body = await readBody(request);\n\n await options.lifecycle.requestPasswordReset({\n email: requiredString(body, \"email\", 320),\n resetUrl: options.passwordResetUrl(request),\n ipAddress: options.resolveClientIp(request),\n });\n\n // The public result must never reveal whether an account exists.\n return jsonResponse({\n status: \"password-reset-requested\",\n });\n });\n\n const resetPassword = endpoint(async (request) => {\n protect(request);\n\n const body = await readBody(request);\n\n await options.lifecycle.resetPassword({\n token: requiredString(body, \"token\"),\n newPassword: requiredString(body, \"newPassword\"),\n });\n\n return jsonResponse({\n status: \"password-reset\",\n });\n });\n\n const changePassword = endpoint(async (request) => {\n protect(request);\n\n const sessionToken = getSessionToken(request, options.cookie);\n\n if (!sessionToken) {\n return jsonResponse(\n {\n error: {\n code: \"UNAUTHENTICATED\",\n message: \"Authentication is required.\",\n },\n },\n 401,\n );\n }\n\n const body = await readBody(request);\n\n await options.lifecycle.changePassword({\n sessionToken,\n currentPassword: requiredString(body, \"currentPassword\"),\n newPassword: requiredString(body, \"newPassword\"),\n });\n\n return jsonResponse({\n status: \"password-changed\",\n });\n });\n\n return {\n requestEmailVerification,\n verifyEmail,\n requestPasswordReset,\n resetPassword,\n changePassword,\n };\n};\n\nexport { createAuthLifecycleHandlers };\n"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { StorageAuthLifecycleManager } from "@puzzle-ui/auth";
2
+ import { CsrfOptions, SessionCookieOptions, SessionManager } from "@puzzle-ui/session";
3
+ export type AuthLifecycleHandlersOptions = {
4
+ readonly lifecycle: StorageAuthLifecycleManager;
5
+ readonly sessions: SessionManager;
6
+ readonly cookie: SessionCookieOptions;
7
+ readonly csrf: CsrfOptions;
8
+ readonly resolveClientIp: (request: Request) => string;
9
+ readonly verificationUrl: (request: Request) => string;
10
+ readonly passwordResetUrl: (request: Request) => string;
11
+ };
12
+ export type AuthLifecycleHandlers = {
13
+ readonly requestEmailVerification: (request: Request) => Promise<Response>;
14
+ readonly verifyEmail: (request: Request) => Promise<Response>;
15
+ readonly requestPasswordReset: (request: Request) => Promise<Response>;
16
+ readonly resetPassword: (request: Request) => Promise<Response>;
17
+ readonly changePassword: (request: Request) => Promise<Response>;
18
+ };
19
+ declare const createAuthLifecycleHandlers: (options: AuthLifecycleHandlersOptions) => AuthLifecycleHandlers;
20
+ export { createAuthLifecycleHandlers };
@@ -0,0 +1,20 @@
1
+ import { StorageAuthLifecycleManager } from "@puzzle-ui/auth";
2
+ import { CsrfOptions, SessionCookieOptions, SessionManager } from "@puzzle-ui/session";
3
+ export type AuthLifecycleHandlersOptions = {
4
+ readonly lifecycle: StorageAuthLifecycleManager;
5
+ readonly sessions: SessionManager;
6
+ readonly cookie: SessionCookieOptions;
7
+ readonly csrf: CsrfOptions;
8
+ readonly resolveClientIp: (request: Request) => string;
9
+ readonly verificationUrl: (request: Request) => string;
10
+ readonly passwordResetUrl: (request: Request) => string;
11
+ };
12
+ export type AuthLifecycleHandlers = {
13
+ readonly requestEmailVerification: (request: Request) => Promise<Response>;
14
+ readonly verifyEmail: (request: Request) => Promise<Response>;
15
+ readonly requestPasswordReset: (request: Request) => Promise<Response>;
16
+ readonly resetPassword: (request: Request) => Promise<Response>;
17
+ readonly changePassword: (request: Request) => Promise<Response>;
18
+ };
19
+ declare const createAuthLifecycleHandlers: (options: AuthLifecycleHandlersOptions) => AuthLifecycleHandlers;
20
+ export { createAuthLifecycleHandlers };
@@ -0,0 +1,126 @@
1
+ import { getRequestSession } from "./session.js";
2
+ import { AuthError } from "@puzzle-ui/auth";
3
+ import { protectStateChangingRequest, readSessionCookie } from "@puzzle-ui/session";
4
+ // src/auth/server/lifecycle.ts
5
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6
+ const jsonResponse = (body, status = 200) => new Response(JSON.stringify(body), {
7
+ status,
8
+ headers: {
9
+ "Content-Type": "application/json; charset=utf-8",
10
+ "Cache-Control": "no-store",
11
+ Pragma: "no-cache"
12
+ }
13
+ });
14
+ const readBody = async (request) => {
15
+ let body;
16
+ try {
17
+ body = await request.json();
18
+ } catch {
19
+ throw new Error("INVALID_REQUEST");
20
+ }
21
+ if (!isRecord(body)) throw new Error("INVALID_REQUEST");
22
+ return body;
23
+ };
24
+ const requiredString = (body, field, maximumLength = 2048) => {
25
+ const value = body[field];
26
+ if (typeof value !== "string" || !value.trim() || value.length > maximumLength) throw new Error("INVALID_REQUEST");
27
+ return value;
28
+ };
29
+ const getErrorResponse = (error) => {
30
+ if (error instanceof AuthError) {
31
+ const status = error.code === "RATE_LIMITED" ? 429 : error.code === "PROVIDER" ? 503 : error.code === "SESSION" ? 401 : 400;
32
+ return jsonResponse({ error: {
33
+ code: error.code,
34
+ message: error.message
35
+ } }, status);
36
+ }
37
+ if (error instanceof Error && error.message === "INVALID_REQUEST") return jsonResponse({ error: {
38
+ code: "INVALID_REQUEST",
39
+ message: "The request is invalid."
40
+ } }, 400);
41
+ return jsonResponse({ error: {
42
+ code: "INTERNAL_AUTH_ERROR",
43
+ message: "An authentication error occurred."
44
+ } }, 500);
45
+ };
46
+ const endpoint = (handler) => async (request) => {
47
+ try {
48
+ if (request.method !== "POST") return jsonResponse({ error: {
49
+ code: "METHOD_NOT_ALLOWED",
50
+ message: "Only POST requests are supported."
51
+ } }, 405);
52
+ return await handler(request);
53
+ } catch (error) {
54
+ return getErrorResponse(error);
55
+ }
56
+ };
57
+ const getSessionToken = (request, cookie) => readSessionCookie(request.headers.get("cookie"), cookie.name);
58
+ const createAuthLifecycleHandlers = (options) => {
59
+ const sessionOptions = {
60
+ sessions: options.sessions,
61
+ cookie: options.cookie
62
+ };
63
+ const protect = (request) => {
64
+ protectStateChangingRequest(request, options.csrf);
65
+ };
66
+ return {
67
+ requestEmailVerification: endpoint(async (request) => {
68
+ protect(request);
69
+ const session = await getRequestSession(request, sessionOptions);
70
+ if (!session || session.kind !== "authenticated" || !session.userId) return jsonResponse({ error: {
71
+ code: "UNAUTHENTICATED",
72
+ message: "Authentication is required."
73
+ } }, 401);
74
+ await options.lifecycle.sendEmailVerification({
75
+ userId: session.userId,
76
+ verificationUrl: options.verificationUrl(request),
77
+ ipAddress: options.resolveClientIp(request)
78
+ });
79
+ return jsonResponse({ status: "verification-requested" });
80
+ }),
81
+ verifyEmail: endpoint(async (request) => {
82
+ protect(request);
83
+ const body = await readBody(request);
84
+ const token = requiredString(body, "token");
85
+ await options.lifecycle.verifyEmail(token);
86
+ return jsonResponse({ status: "email-verified" });
87
+ }),
88
+ requestPasswordReset: endpoint(async (request) => {
89
+ protect(request);
90
+ const body = await readBody(request);
91
+ await options.lifecycle.requestPasswordReset({
92
+ email: requiredString(body, "email", 320),
93
+ resetUrl: options.passwordResetUrl(request),
94
+ ipAddress: options.resolveClientIp(request)
95
+ });
96
+ return jsonResponse({ status: "password-reset-requested" });
97
+ }),
98
+ resetPassword: endpoint(async (request) => {
99
+ protect(request);
100
+ const body = await readBody(request);
101
+ await options.lifecycle.resetPassword({
102
+ token: requiredString(body, "token"),
103
+ newPassword: requiredString(body, "newPassword")
104
+ });
105
+ return jsonResponse({ status: "password-reset" });
106
+ }),
107
+ changePassword: endpoint(async (request) => {
108
+ protect(request);
109
+ const sessionToken = getSessionToken(request, options.cookie);
110
+ if (!sessionToken) return jsonResponse({ error: {
111
+ code: "UNAUTHENTICATED",
112
+ message: "Authentication is required."
113
+ } }, 401);
114
+ const body = await readBody(request);
115
+ await options.lifecycle.changePassword({
116
+ sessionToken,
117
+ currentPassword: requiredString(body, "currentPassword"),
118
+ newPassword: requiredString(body, "newPassword")
119
+ });
120
+ return jsonResponse({ status: "password-changed" });
121
+ })
122
+ };
123
+ };
124
+ export { createAuthLifecycleHandlers };
125
+
126
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","names":[],"sources":["../../../src/auth/server/lifecycle.ts"],"sourcesContent":["import { AuthError, type StorageAuthLifecycleManager } from \"@puzzle-ui/auth\";\n\nimport {\n readSessionCookie,\n protectStateChangingRequest,\n type CsrfOptions,\n type SessionCookieOptions,\n type SessionManager,\n} from \"@puzzle-ui/session\";\n\nimport { getRequestSession, type SessionRequestOptions } from \"./session\";\n\nexport type AuthLifecycleHandlersOptions = {\n readonly lifecycle: StorageAuthLifecycleManager;\n readonly sessions: SessionManager;\n readonly cookie: SessionCookieOptions;\n readonly csrf: CsrfOptions;\n readonly resolveClientIp: (request: Request) => string;\n readonly verificationUrl: (request: Request) => string;\n readonly passwordResetUrl: (request: Request) => string;\n};\n\nexport type AuthLifecycleHandlers = {\n readonly requestEmailVerification: (request: Request) => Promise<Response>;\n readonly verifyEmail: (request: Request) => Promise<Response>;\n readonly requestPasswordReset: (request: Request) => Promise<Response>;\n readonly resetPassword: (request: Request) => Promise<Response>;\n readonly changePassword: (request: Request) => Promise<Response>;\n};\n\ntype JsonObject = Record<string, unknown>;\n\nconst isRecord = (value: unknown): value is JsonObject =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst jsonResponse = (body: unknown, status = 200): Response =>\n new Response(JSON.stringify(body), {\n status,\n headers: {\n \"Content-Type\": \"application/json; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n Pragma: \"no-cache\",\n },\n });\n\nconst readBody = async (request: Request): Promise<JsonObject> => {\n let body: unknown;\n\n try {\n body = await request.json();\n } catch {\n throw new Error(\"INVALID_REQUEST\");\n }\n\n if (!isRecord(body)) {\n throw new Error(\"INVALID_REQUEST\");\n }\n\n return body;\n};\n\nconst requiredString = (\n body: JsonObject,\n field: string,\n maximumLength = 2048,\n): string => {\n const value = body[field];\n\n if (\n typeof value !== \"string\" ||\n !value.trim() ||\n value.length > maximumLength\n ) {\n throw new Error(\"INVALID_REQUEST\");\n }\n\n return value;\n};\n\nconst getErrorResponse = (error: unknown): Response => {\n if (error instanceof AuthError) {\n const status =\n error.code === \"RATE_LIMITED\"\n ? 429\n : error.code === \"PROVIDER\"\n ? 503\n : error.code === \"SESSION\"\n ? 401\n : 400;\n\n return jsonResponse(\n {\n error: {\n code: error.code,\n message: error.message,\n },\n },\n status,\n );\n }\n\n if (error instanceof Error && error.message === \"INVALID_REQUEST\") {\n return jsonResponse(\n {\n error: {\n code: \"INVALID_REQUEST\",\n message: \"The request is invalid.\",\n },\n },\n 400,\n );\n }\n\n return jsonResponse(\n {\n error: {\n code: \"INTERNAL_AUTH_ERROR\",\n message: \"An authentication error occurred.\",\n },\n },\n 500,\n );\n};\n\nconst endpoint =\n (handler: (request: Request) => Promise<Response>) =>\n async (request: Request): Promise<Response> => {\n try {\n if (request.method !== \"POST\") {\n return jsonResponse(\n {\n error: {\n code: \"METHOD_NOT_ALLOWED\",\n message: \"Only POST requests are supported.\",\n },\n },\n 405,\n );\n }\n\n return await handler(request);\n } catch (error) {\n return getErrorResponse(error);\n }\n };\n\nconst getSessionToken = (\n request: Request,\n cookie: SessionCookieOptions,\n): string | null =>\n readSessionCookie(request.headers.get(\"cookie\"), cookie.name);\n\nconst createAuthLifecycleHandlers = (\n options: AuthLifecycleHandlersOptions,\n): AuthLifecycleHandlers => {\n const sessionOptions: SessionRequestOptions = {\n sessions: options.sessions,\n cookie: options.cookie,\n };\n\n const protect = (request: Request) => {\n protectStateChangingRequest(request, options.csrf);\n };\n\n const requestEmailVerification = endpoint(async (request) => {\n protect(request);\n\n const session = await getRequestSession(request, sessionOptions);\n\n if (!session || session.kind !== \"authenticated\" || !session.userId) {\n return jsonResponse(\n {\n error: {\n code: \"UNAUTHENTICATED\",\n message: \"Authentication is required.\",\n },\n },\n 401,\n );\n }\n\n await options.lifecycle.sendEmailVerification({\n userId: session.userId,\n verificationUrl: options.verificationUrl(request),\n ipAddress: options.resolveClientIp(request),\n });\n\n return jsonResponse({\n status: \"verification-requested\",\n });\n });\n\n const verifyEmail = endpoint(async (request) => {\n protect(request);\n\n const body = await readBody(request);\n const token = requiredString(body, \"token\");\n\n await options.lifecycle.verifyEmail(token);\n\n return jsonResponse({\n status: \"email-verified\",\n });\n });\n\n const requestPasswordReset = endpoint(async (request) => {\n protect(request);\n\n const body = await readBody(request);\n\n await options.lifecycle.requestPasswordReset({\n email: requiredString(body, \"email\", 320),\n resetUrl: options.passwordResetUrl(request),\n ipAddress: options.resolveClientIp(request),\n });\n\n // The public result must never reveal whether an account exists.\n return jsonResponse({\n status: \"password-reset-requested\",\n });\n });\n\n const resetPassword = endpoint(async (request) => {\n protect(request);\n\n const body = await readBody(request);\n\n await options.lifecycle.resetPassword({\n token: requiredString(body, \"token\"),\n newPassword: requiredString(body, \"newPassword\"),\n });\n\n return jsonResponse({\n status: \"password-reset\",\n });\n });\n\n const changePassword = endpoint(async (request) => {\n protect(request);\n\n const sessionToken = getSessionToken(request, options.cookie);\n\n if (!sessionToken) {\n return jsonResponse(\n {\n error: {\n code: \"UNAUTHENTICATED\",\n message: \"Authentication is required.\",\n },\n },\n 401,\n );\n }\n\n const body = await readBody(request);\n\n await options.lifecycle.changePassword({\n sessionToken,\n currentPassword: requiredString(body, \"currentPassword\"),\n newPassword: requiredString(body, \"newPassword\"),\n });\n\n return jsonResponse({\n status: \"password-changed\",\n });\n });\n\n return {\n requestEmailVerification,\n verifyEmail,\n requestPasswordReset,\n resetPassword,\n changePassword,\n };\n};\n\nexport { createAuthLifecycleHandlers };\n"],"mappings":"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"}