@bitbaum/accountkit 0.0.0-stage → 0.8.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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -2
  3. package/dist/AccountMenu.d.ts +76 -0
  4. package/dist/AccountMenu.d.ts.map +1 -0
  5. package/dist/AccountMenu.js +156 -0
  6. package/dist/AccountMenu.js.map +1 -0
  7. package/dist/SignInError.d.ts +50 -0
  8. package/dist/SignInError.d.ts.map +1 -0
  9. package/dist/SignInError.js +34 -0
  10. package/dist/SignInError.js.map +1 -0
  11. package/dist/auth/client.d.ts +19 -0
  12. package/dist/auth/client.d.ts.map +1 -0
  13. package/dist/auth/client.js +17 -0
  14. package/dist/auth/client.js.map +1 -0
  15. package/dist/auth/factor.d.ts +9 -0
  16. package/dist/auth/factor.d.ts.map +1 -0
  17. package/dist/auth/factor.js +107 -0
  18. package/dist/auth/factor.js.map +1 -0
  19. package/dist/auth/index.d.ts +6 -0
  20. package/dist/auth/index.d.ts.map +1 -0
  21. package/dist/auth/index.js +6 -0
  22. package/dist/auth/index.js.map +1 -0
  23. package/dist/auth/labels.d.ts +126 -0
  24. package/dist/auth/labels.d.ts.map +1 -0
  25. package/dist/auth/labels.js +143 -0
  26. package/dist/auth/labels.js.map +1 -0
  27. package/dist/auth/limits.d.ts +28 -0
  28. package/dist/auth/limits.d.ts.map +1 -0
  29. package/dist/auth/limits.js +90 -0
  30. package/dist/auth/limits.js.map +1 -0
  31. package/dist/auth/mail.d.ts +10 -0
  32. package/dist/auth/mail.d.ts.map +1 -0
  33. package/dist/auth/mail.js +45 -0
  34. package/dist/auth/mail.js.map +1 -0
  35. package/dist/auth/methods.d.ts +71 -0
  36. package/dist/auth/methods.d.ts.map +1 -0
  37. package/dist/auth/methods.js +92 -0
  38. package/dist/auth/methods.js.map +1 -0
  39. package/dist/auth/next.d.ts +73 -0
  40. package/dist/auth/next.d.ts.map +1 -0
  41. package/dist/auth/next.js +131 -0
  42. package/dist/auth/next.js.map +1 -0
  43. package/dist/auth/react.d.ts +178 -0
  44. package/dist/auth/react.d.ts.map +1 -0
  45. package/dist/auth/react.js +405 -0
  46. package/dist/auth/react.js.map +1 -0
  47. package/dist/auth/recovery.d.ts +17 -0
  48. package/dist/auth/recovery.d.ts.map +1 -0
  49. package/dist/auth/recovery.js +85 -0
  50. package/dist/auth/recovery.js.map +1 -0
  51. package/dist/auth/roles.d.ts +19 -0
  52. package/dist/auth/roles.d.ts.map +1 -0
  53. package/dist/auth/roles.js +29 -0
  54. package/dist/auth/roles.js.map +1 -0
  55. package/dist/auth/schema.d.ts +1923 -0
  56. package/dist/auth/schema.d.ts.map +1 -0
  57. package/dist/auth/schema.js +120 -0
  58. package/dist/auth/schema.js.map +1 -0
  59. package/dist/auth/server.d.ts +56 -0
  60. package/dist/auth/server.d.ts.map +1 -0
  61. package/dist/auth/server.js +230 -0
  62. package/dist/auth/server.js.map +1 -0
  63. package/dist/auth/translations.d.ts +9 -0
  64. package/dist/auth/translations.d.ts.map +1 -0
  65. package/dist/auth/translations.js +629 -0
  66. package/dist/auth/translations.js.map +1 -0
  67. package/dist/identity.d.ts +50 -0
  68. package/dist/identity.d.ts.map +1 -0
  69. package/dist/identity.js +76 -0
  70. package/dist/identity.js.map +1 -0
  71. package/dist/index.d.ts +4 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +4 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/orangecat.d.ts +333 -0
  76. package/dist/orangecat.d.ts.map +1 -0
  77. package/dist/orangecat.js +508 -0
  78. package/dist/orangecat.js.map +1 -0
  79. package/dist/paths.d.ts +3 -0
  80. package/dist/paths.d.ts.map +1 -0
  81. package/dist/paths.js +11 -0
  82. package/dist/paths.js.map +1 -0
  83. package/package.json +145 -4
  84. package/styles.css +408 -0
@@ -0,0 +1,9 @@
1
+ import { twoFactor } from "better-auth/plugins";
2
+ /** Resolve only app-local challenge UI and return URLs; never carry bearer values. */
3
+ export declare function factorDestination(origin: string, screen: unknown, next: unknown): string;
4
+ /** Reuse BetterAuth's challenge/trusted-device policy for every primary method. */
5
+ export declare function factorForAllMethods(site: {
6
+ title: string;
7
+ url: string;
8
+ }): ReturnType<typeof twoFactor>;
9
+ //# sourceMappingURL=factor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factor.d.ts","sourceRoot":"","sources":["../../src/auth/factor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAIhD,sFAAsF;AACtF,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,OAAO,GACZ,MAAM,CA0BR;AAED,mFAAmF;AACnF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAqG/B"}
@@ -0,0 +1,107 @@
1
+ import { createAuthMiddleware, getOAuthState } from "better-auth/api";
2
+ import { twoFactor } from "better-auth/plugins";
3
+ import { deleteSessionCookie } from "better-auth/cookies";
4
+ import { returnPath } from "../paths.js";
5
+ /** Resolve only app-local challenge UI and return URLs; never carry bearer values. */
6
+ export function factorDestination(origin, screen, next) {
7
+ const local = (value, fallback) => {
8
+ try {
9
+ const url = new URL(typeof value === "string" ? value : fallback, origin);
10
+ if (url.origin !== new URL(origin).origin)
11
+ return new URL(fallback, origin);
12
+ return new URL(returnPath(url.pathname + url.search + url.hash, fallback), origin);
13
+ }
14
+ catch {
15
+ return new URL(fallback, origin);
16
+ }
17
+ };
18
+ const target = local(screen, "/sign-in");
19
+ const destination = local(next, "/");
20
+ for (const key of ["error", "error_description", "token", "code", "state"])
21
+ target.searchParams.delete(key);
22
+ target.searchParams.set("twoFactor", "1");
23
+ target.searchParams.set("next", destination.pathname === target.pathname
24
+ ? "/"
25
+ : destination.pathname + destination.search + destination.hash);
26
+ return target.toString();
27
+ }
28
+ /** Reuse BetterAuth's challenge/trusted-device policy for every primary method. */
29
+ export function factorForAllMethods(site) {
30
+ const plugin = twoFactor({ issuer: site.title, allowPasswordless: true });
31
+ const native = plugin.hooks.after[0];
32
+ return {
33
+ ...plugin,
34
+ hooks: {
35
+ ...plugin.hooks,
36
+ after: [
37
+ ...plugin.hooks.after,
38
+ {
39
+ matcher: ({ path }) => path === "/magic-link/verify" ||
40
+ path === "/verify-email" ||
41
+ path === "/passkey/verify-authentication" ||
42
+ /^\/(?:oauth2\/)?callback\/[^/]+$/.test(path ?? ""),
43
+ handler: createAuthMiddleware(async (ctx) => {
44
+ const pending = ctx.context.newSession;
45
+ if (!pending ||
46
+ !("twoFactorEnabled" in pending.user) ||
47
+ !pending.user.twoFactorEnabled)
48
+ return;
49
+ // Delegate cookie revocation, signed challenge creation, expiry,
50
+ // attempt limits and trusted-device validation to the native plugin.
51
+ let delegated;
52
+ try {
53
+ // BetterCall's runtime supports returnHeaders even where the
54
+ // middleware declaration exposes only its body. Validate it.
55
+ const result = await native.handler({
56
+ ...ctx,
57
+ returnHeaders: true,
58
+ });
59
+ if (!result ||
60
+ typeof result !== "object" ||
61
+ !("headers" in result) ||
62
+ !(result.headers instanceof Headers) ||
63
+ !("response" in result))
64
+ throw new Error("Unsupported BetterAuth factor middleware envelope");
65
+ const body = result.response;
66
+ if (body !== undefined &&
67
+ (!body ||
68
+ typeof body !== "object" ||
69
+ !("twoFactorRedirect" in body) ||
70
+ body.twoFactorRedirect !== true ||
71
+ !("twoFactorMethods" in body) ||
72
+ !Array.isArray(body.twoFactorMethods) ||
73
+ !body.twoFactorMethods.every((method) => typeof method === "string")))
74
+ throw new Error("Unsupported BetterAuth factor challenge body");
75
+ delegated = {
76
+ headers: result.headers,
77
+ response: body,
78
+ };
79
+ }
80
+ catch (error) {
81
+ // Never leave the primary session usable on an SDK failure.
82
+ await ctx.context.internalAdapter.deleteSession(pending.session.token);
83
+ ctx.context.setNewSession(null);
84
+ deleteSessionCookie(ctx, true);
85
+ throw error;
86
+ }
87
+ // Middleware invocations return their own headers and body. Keep
88
+ // each Set-Cookie separate, including session revocation + challenge.
89
+ for (const [name, value] of delegated.headers) {
90
+ if (name !== "set-cookie")
91
+ ctx.responseHeaders.set(name, value);
92
+ }
93
+ for (const cookie of delegated.headers.getSetCookie())
94
+ ctx.responseHeaders.append("set-cookie", cookie);
95
+ if (ctx.context.newSession ||
96
+ ctx.path === "/passkey/verify-authentication")
97
+ return delegated.response;
98
+ const state = await getOAuthState();
99
+ const target = factorDestination(site.url, ctx.query?.errorCallbackURL ?? state?.errorURL, ctx.query?.callbackURL ?? state?.callbackURL);
100
+ throw ctx.redirect(target);
101
+ }),
102
+ },
103
+ ],
104
+ },
105
+ };
106
+ }
107
+ //# sourceMappingURL=factor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factor.js","sourceRoot":"","sources":["../../src/auth/factor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,sFAAsF;AACtF,MAAM,UAAU,iBAAiB,CAC/B,MAAc,EACd,MAAe,EACf,IAAa;IAEb,MAAM,KAAK,GAAG,CAAC,KAAc,EAAE,QAAgB,EAAE,EAAE;QACjD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAC1E,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM;gBACvC,OAAO,IAAI,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YACnC,OAAO,IAAI,GAAG,CACZ,UAAU,CAAC,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,EAC1D,MAAM,CACP,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACnC,CAAC;IACH,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACzC,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACrC,KAAK,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC;QACxE,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IAC1C,MAAM,CAAC,YAAY,CAAC,GAAG,CACrB,MAAM,EACN,WAAW,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ;QACtC,CAAC,CAAC,GAAG;QACL,CAAC,CAAC,WAAW,CAAC,QAAQ,GAAG,WAAW,CAAC,MAAM,GAAG,WAAW,CAAC,IAAI,CACjE,CAAC;IACF,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;AAC3B,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,mBAAmB,CAAC,IAGnC;IACC,MAAM,MAAM,GAAG,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACrC,OAAO;QACL,GAAG,MAAM;QACT,KAAK,EAAE;YACL,GAAG,MAAM,CAAC,KAAK;YACf,KAAK,EAAE;gBACL,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK;gBACrB;oBACE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CACpB,IAAI,KAAK,oBAAoB;wBAC7B,IAAI,KAAK,eAAe;wBACxB,IAAI,KAAK,gCAAgC;wBACzC,kCAAkC,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;oBACrD,OAAO,EAAE,oBAAoB,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;wBAC1C,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC;wBACvC,IACE,CAAC,OAAO;4BACR,CAAC,CAAC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;4BACrC,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB;4BAE9B,OAAO;wBACT,iEAAiE;wBACjE,qEAAqE;wBACrE,IAAI,SAKH,CAAC;wBACF,IAAI,CAAC;4BACH,6DAA6D;4BAC7D,6DAA6D;4BAC7D,MAAM,MAAM,GAAY,MAAM,MAAM,CAAC,OAAO,CAAC;gCAC3C,GAAG,GAAG;gCACN,aAAa,EAAE,IAAI;6BACpB,CAAC,CAAC;4BACH,IACE,CAAC,MAAM;gCACP,OAAO,MAAM,KAAK,QAAQ;gCAC1B,CAAC,CAAC,SAAS,IAAI,MAAM,CAAC;gCACtB,CAAC,CAAC,MAAM,CAAC,OAAO,YAAY,OAAO,CAAC;gCACpC,CAAC,CAAC,UAAU,IAAI,MAAM,CAAC;gCAEvB,MAAM,IAAI,KAAK,CACb,mDAAmD,CACpD,CAAC;4BACJ,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC;4BAC7B,IACE,IAAI,KAAK,SAAS;gCAClB,CAAC,CAAC,IAAI;oCACJ,OAAO,IAAI,KAAK,QAAQ;oCACxB,CAAC,CAAC,mBAAmB,IAAI,IAAI,CAAC;oCAC9B,IAAI,CAAC,iBAAiB,KAAK,IAAI;oCAC/B,CAAC,CAAC,kBAAkB,IAAI,IAAI,CAAC;oCAC7B,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC;oCACrC,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAC1B,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,CACvC,CAAC;gCAEJ,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;4BAClE,SAAS,GAAG;gCACV,OAAO,EAAE,MAAM,CAAC,OAAO;gCACvB,QAAQ,EAAE,IAEG;6BACd,CAAC;wBACJ,CAAC;wBAAC,OAAO,KAAK,EAAE,CAAC;4BACf,4DAA4D;4BAC5D,MAAM,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,aAAa,CAC7C,OAAO,CAAC,OAAO,CAAC,KAAK,CACtB,CAAC;4BACF,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;4BAChC,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;4BAC/B,MAAM,KAAK,CAAC;wBACd,CAAC;wBACD,iEAAiE;wBACjE,sEAAsE;wBACtE,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;4BAC9C,IAAI,IAAI,KAAK,YAAY;gCAAE,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;wBAClE,CAAC;wBACD,KAAK,MAAM,MAAM,IAAI,SAAS,CAAC,OAAO,CAAC,YAAY,EAAE;4BACnD,GAAG,CAAC,eAAe,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;wBACnD,IACE,GAAG,CAAC,OAAO,CAAC,UAAU;4BACtB,GAAG,CAAC,IAAI,KAAK,gCAAgC;4BAE7C,OAAO,SAAS,CAAC,QAAQ,CAAC;wBAC5B,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC;wBACpC,MAAM,MAAM,GAAG,iBAAiB,CAC9B,IAAI,CAAC,GAAG,EACR,GAAG,CAAC,KAAK,EAAE,gBAAgB,IAAI,KAAK,EAAE,QAAQ,EAC9C,GAAG,CAAC,KAAK,EAAE,WAAW,IAAI,KAAK,EAAE,WAAW,CAC7C,CAAC;wBACF,MAAM,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;oBAC7B,CAAC,CAAC;iBACH;aACF;SACF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ export * from "./methods.js";
2
+ export * from "./roles.js";
3
+ export { authBucket, authLimits, type AuthBucket } from "./limits.js";
4
+ export { DEFAULT_AUTH_LABELS, DEFAULT_RECOVERY_LABELS, authErrorText, fill, type AuthError, type AuthLabels, type RecoveryLabels, } from "./labels.js";
5
+ export { AUTH_LABEL_LOCALES, authLabelsFor } from "./translations.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,aAAa,EACb,IAAI,EACJ,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export * from "./methods.js";
2
+ export * from "./roles.js";
3
+ export { authBucket, authLimits } from "./limits.js";
4
+ export { DEFAULT_AUTH_LABELS, DEFAULT_RECOVERY_LABELS, authErrorText, fill, } from "./labels.js";
5
+ export { AUTH_LABEL_LOCALES, authLabelsFor } from "./translations.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAmB,MAAM,aAAa,CAAC;AACtE,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,aAAa,EACb,IAAI,GAIL,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Every word the account screens show, so each product can say them in its
3
+ * own language. Placeholders are `{email}`, `{method}` and `{word}`; a product
4
+ * on next-intl passes `t.raw(...)`, which keeps them as written.
5
+ */
6
+ /**
7
+ * The one password rule: a length. No character classes, no symbols — a
8
+ * longer password is the person's choice, not a gate (NIST SP 800-63B).
9
+ */
10
+ export declare const MIN_PASSWORD_LENGTH = 8;
11
+ export interface RecoveryLabels {
12
+ forgot: string;
13
+ send: string;
14
+ sent: string;
15
+ reset: string;
16
+ newPassword: string;
17
+ confirmPassword: string;
18
+ mismatch: string;
19
+ invalidToken: string;
20
+ success: string;
21
+ }
22
+ export declare const DEFAULT_RECOVERY_LABELS: RecoveryLabels;
23
+ export interface AuthLabels {
24
+ /** Optional for compatibility with existing product label overrides. */
25
+ passwordVisibility?: {
26
+ show: string;
27
+ hide: string;
28
+ };
29
+ /** Optional for compatibility with existing product label overrides. */
30
+ recovery?: RecoveryLabels;
31
+ signIn: string;
32
+ createAccount: string;
33
+ withSignin: string;
34
+ withOrangecat: string;
35
+ withPasskey: string;
36
+ withLink: string;
37
+ signUpWithLink: string;
38
+ orEmail: string;
39
+ email: string;
40
+ password: string;
41
+ passwordHint: string;
42
+ /** @deprecated Sign-up no longer asks for a name; the account page still uses displayName. */
43
+ displayName: string;
44
+ /** @deprecated Unused since sign-up stopped asking for a name. */
45
+ displayNameHint: string;
46
+ checkInbox: string;
47
+ confirmInbox: string;
48
+ back: string;
49
+ totpPrompt: string;
50
+ backupPrompt: string;
51
+ totpCode: string;
52
+ backupCode: string;
53
+ trustDevice: string;
54
+ verify: string;
55
+ useTotp: string;
56
+ useBackup: string;
57
+ resendVerification: string;
58
+ noAccount: string;
59
+ haveAccount: string;
60
+ errors: {
61
+ rateLimited: string;
62
+ unavailable: string;
63
+ generic: string;
64
+ emailFirst: string;
65
+ INVALID_EMAIL_OR_PASSWORD: string;
66
+ EMAIL_NOT_VERIFIED: string;
67
+ USER_ALREADY_EXISTS: string;
68
+ USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL: string;
69
+ PASSWORD_TOO_SHORT: string;
70
+ PASSWORD_TOO_LONG: string;
71
+ INVALID_EMAIL: string;
72
+ INVALID_CODE: string;
73
+ INVALID_TWO_FACTOR_CODE: string;
74
+ SESSION_EXPIRED: string;
75
+ INVALID_PASSWORD: string;
76
+ };
77
+ account: {
78
+ profile: string;
79
+ noEmail: string;
80
+ unverified: string;
81
+ role: string;
82
+ roles: Record<string, string>;
83
+ save: string;
84
+ saved: string;
85
+ methods: string;
86
+ methodPassword: string;
87
+ link: string;
88
+ passkeys: string;
89
+ passkeysLede: string;
90
+ passkeyDefaultName: string;
91
+ remove: string;
92
+ addPasskey: string;
93
+ twoFactor: string;
94
+ twoFactorOn: string;
95
+ twoFactorOff: string;
96
+ currentPassword: string;
97
+ currentPasswordHint: string;
98
+ scan: string;
99
+ openAuthenticator: string;
100
+ backupCodes: string;
101
+ turnOn: string;
102
+ turnOff: string;
103
+ setUp: string;
104
+ leave: string;
105
+ signOut: string;
106
+ deleteConfirm: string;
107
+ deleteWord: string;
108
+ delete: string;
109
+ };
110
+ }
111
+ export declare const DEFAULT_AUTH_LABELS: AuthLabels;
112
+ /** Put values into a label: `fill("Hi {name}", { name: "Ada" })`. */
113
+ export declare function fill(template: string, values: Record<string, string>): string;
114
+ /** The error shape better-auth's client returns, plus our own guard's answers. */
115
+ export type AuthError = {
116
+ code?: string;
117
+ status?: number;
118
+ message?: string;
119
+ } | null | undefined;
120
+ /**
121
+ * A failure in words, never a raw code: what happened and what to do next.
122
+ * Unknown codes get the generic sentence — a provider's message can carry
123
+ * details the person should not be shown.
124
+ */
125
+ export declare function authErrorText(labels: AuthLabels, error: AuthError): string | null;
126
+ //# sourceMappingURL=labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../../src/auth/labels.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,uBAAuB,EAAE,cAYrC,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,wEAAwE;IACxE,kBAAkB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD,wEAAwE;IACxE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,8FAA8F;IAC9F,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE;QACN,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,yBAAyB,EAAE,MAAM,CAAC;QAClC,kBAAkB,EAAE,MAAM,CAAC;QAC3B,mBAAmB,EAAE,MAAM,CAAC;QAC5B,qCAAqC,EAAE,MAAM,CAAC;QAC9C,kBAAkB,EAAE,MAAM,CAAC;QAC3B,iBAAiB,EAAE,MAAM,CAAC;QAC1B,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,uBAAuB,EAAE,MAAM,CAAC;QAChC,eAAe,EAAE,MAAM,CAAC;QACxB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IACF,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC9B,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,cAAc,EAAE,MAAM,CAAC;QACvB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,MAAM,CAAC;QACxB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,IAAI,EAAE,MAAM,CAAC;QACb,iBAAiB,EAAE,MAAM,CAAC;QAC1B,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,eAAO,MAAM,mBAAmB,EAAE,UAqGjC,CAAC;AAEF,qEAAqE;AACrE,wBAAgB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAK7E;AAED,kFAAkF;AAClF,MAAM,MAAM,SAAS,GACnB;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,CAAC;AAgB1E;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,SAAS,GACf,MAAM,GAAG,IAAI,CAQf"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Every word the account screens show, so each product can say them in its
3
+ * own language. Placeholders are `{email}`, `{method}` and `{word}`; a product
4
+ * on next-intl passes `t.raw(...)`, which keeps them as written.
5
+ */
6
+ /**
7
+ * The one password rule: a length. No character classes, no symbols — a
8
+ * longer password is the person's choice, not a gate (NIST SP 800-63B).
9
+ */
10
+ export const MIN_PASSWORD_LENGTH = 8;
11
+ export const DEFAULT_RECOVERY_LABELS = {
12
+ forgot: "Forgot your password?",
13
+ send: "Email me a reset link",
14
+ sent: "If an account can be recovered at that address, a reset link will arrive shortly. Check your inbox and spam folder.",
15
+ reset: "Reset password",
16
+ newPassword: "New password",
17
+ confirmPassword: "Repeat new password",
18
+ mismatch: "The passwords do not match. Enter the same password in both fields.",
19
+ invalidToken: "This reset link is invalid or has expired. Request a new link.",
20
+ success: "Your password has been changed. Sign in with your new password.",
21
+ };
22
+ export const DEFAULT_AUTH_LABELS = {
23
+ passwordVisibility: { show: "Show password", hide: "Hide password" },
24
+ recovery: DEFAULT_RECOVERY_LABELS,
25
+ signIn: "Sign in",
26
+ createAccount: "Create an account",
27
+ withSignin: "Continue with Google, Apple, GitHub, X or Facebook",
28
+ withOrangecat: "Continue with OrangeCat",
29
+ withPasskey: "Sign in with a passkey",
30
+ withLink: "Email me a sign-in link instead",
31
+ signUpWithLink: "Email me a link instead",
32
+ orEmail: "or with email",
33
+ email: "Email",
34
+ password: "Password",
35
+ passwordHint: "At least 8 characters.",
36
+ displayName: "Display name",
37
+ displayNameHint: "Optional. Anything you like — it does not have to be your real name.",
38
+ checkInbox: "We sent a link to {email}. Open it on this device to continue. It works for 10 minutes.",
39
+ confirmInbox: "We sent a link to {email}. Open it to confirm your address and finish creating your account.",
40
+ back: "Back",
41
+ totpPrompt: "Enter the 6-digit code from your authenticator app.",
42
+ backupPrompt: "Enter one of the backup codes you saved when you set up two-factor sign-in.",
43
+ totpCode: "Code",
44
+ backupCode: "Backup code",
45
+ trustDevice: "Trust this device for 30 days",
46
+ verify: "Verify",
47
+ useTotp: "Use the authenticator app instead",
48
+ useBackup: "Use a backup code instead",
49
+ resendVerification: "Send the confirmation link again",
50
+ noAccount: "No account yet?",
51
+ haveAccount: "Already have an account?",
52
+ errors: {
53
+ rateLimited: "Too many attempts. Wait a few minutes, then try again.",
54
+ unavailable: "Accounts are not available right now. Try again in a little while.",
55
+ generic: "That did not work. Try again, or choose another way to sign in.",
56
+ emailFirst: "Enter your email address first.",
57
+ INVALID_EMAIL_OR_PASSWORD: "That email and password do not match. Check both, or sign in with a link instead.",
58
+ EMAIL_NOT_VERIFIED: "Confirm your email address first — the link is in your inbox.",
59
+ USER_ALREADY_EXISTS: "There is already an account with this address. Sign in instead.",
60
+ USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL: "There is already an account with this address. Sign in instead.",
61
+ PASSWORD_TOO_SHORT: "The password needs at least 8 characters.",
62
+ PASSWORD_TOO_LONG: "The password can have at most 128 characters.",
63
+ INVALID_EMAIL: "That does not look like an email address.",
64
+ INVALID_CODE: "That code is not right, or it has expired. Try the newest one.",
65
+ INVALID_TWO_FACTOR_CODE: "That code is not right, or it has expired. Try the newest one.",
66
+ SESSION_EXPIRED: "For your safety, sign in again before changing this.",
67
+ INVALID_PASSWORD: "That password is not right.",
68
+ },
69
+ account: {
70
+ profile: "Profile",
71
+ noEmail: "none — add a password sign-in to use email",
72
+ unverified: "not confirmed yet",
73
+ role: "Role",
74
+ roles: {
75
+ customer: "Customer",
76
+ helper: "Helper",
77
+ professional: "Professional",
78
+ staff: "Staff",
79
+ admin: "Administrator",
80
+ },
81
+ save: "Save",
82
+ saved: "Saved.",
83
+ methods: "Ways to sign in",
84
+ methodPassword: "Email and password",
85
+ link: "Add {method}",
86
+ passkeys: "Passkeys",
87
+ passkeysLede: "Sign in with your fingerprint, face or device PIN instead of a password.",
88
+ passkeyDefaultName: "Passkey",
89
+ remove: "Remove",
90
+ addPasskey: "Add a passkey",
91
+ twoFactor: "Two-factor sign-in",
92
+ twoFactorOn: "On. Signing in asks for a code from your authenticator app.",
93
+ twoFactorOff: "Off. Turn it on to ask for a code from an authenticator app at every sign-in.",
94
+ currentPassword: "Current password",
95
+ currentPasswordHint: "Needed if your account has a password. Without one, sign in again first.",
96
+ scan: "Add this account to your authenticator app, then enter the code it shows.",
97
+ openAuthenticator: "Open in your authenticator app",
98
+ backupCodes: "Keep these backup codes somewhere safe. Each one works once if you lose your phone.",
99
+ turnOn: "Turn on",
100
+ turnOff: "Turn off",
101
+ setUp: "Set up",
102
+ leave: "Sign out or leave",
103
+ signOut: "Sign out",
104
+ deleteConfirm: "To delete your account and everything in it, type {word}",
105
+ deleteWord: "delete",
106
+ delete: "Delete my account",
107
+ },
108
+ };
109
+ /** Put values into a label: `fill("Hi {name}", { name: "Ada" })`. */
110
+ export function fill(template, values) {
111
+ return template.replace(/\{(\w+)\}/g, (whole, key) => values[key] ?? whole);
112
+ }
113
+ const KNOWN = new Set([
114
+ "INVALID_EMAIL_OR_PASSWORD",
115
+ "EMAIL_NOT_VERIFIED",
116
+ "USER_ALREADY_EXISTS",
117
+ "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL",
118
+ "PASSWORD_TOO_SHORT",
119
+ "PASSWORD_TOO_LONG",
120
+ "INVALID_EMAIL",
121
+ "INVALID_CODE",
122
+ "INVALID_TWO_FACTOR_CODE",
123
+ "SESSION_EXPIRED",
124
+ "INVALID_PASSWORD",
125
+ ]);
126
+ /**
127
+ * A failure in words, never a raw code: what happened and what to do next.
128
+ * Unknown codes get the generic sentence — a provider's message can carry
129
+ * details the person should not be shown.
130
+ */
131
+ export function authErrorText(labels, error) {
132
+ if (!error)
133
+ return null;
134
+ if (error.status === 429)
135
+ return labels.errors.rateLimited;
136
+ if (error.status === 503)
137
+ return labels.errors.unavailable;
138
+ if (error.code && KNOWN.has(error.code)) {
139
+ return labels.errors[error.code];
140
+ }
141
+ return labels.errors.generic;
142
+ }
143
+ //# sourceMappingURL=labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.js","sourceRoot":"","sources":["../../src/auth/labels.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { type LimitResult } from "limitkit";
2
+ /**
3
+ * Rate limits for the account endpoints, the same in every product.
4
+ *
5
+ * KEYED ON THE PROXY'S HOP, NEVER THE FIRST X-Forwarded-For ENTRY. limitkit's
6
+ * `clientIp()` reads the LAST hop, the one Caddy appended, which the caller
7
+ * cannot choose. `trustedProxies: 1` is Caddy on the box.
8
+ */
9
+ export declare const authLimits: {
10
+ /** Guessing a password, a code or a passkey assertion. */
11
+ credential: import("limitkit").Limiter;
12
+ /** Anything that sends an email: protects inboxes and the shared quota. */
13
+ mail: import("limitkit").Limiter;
14
+ /** The same, per recipient, so rotating IPs cannot flood one inbox. */
15
+ mailTo: import("limitkit").Limiter;
16
+ /** Everything else under /api/auth (session reads, sign-out, listing). */
17
+ auth: import("limitkit").Limiter;
18
+ };
19
+ export type AuthBucket = "credential" | "mail" | "auth";
20
+ /** Which bucket a better-auth path (after /api/auth) spends from. */
21
+ export declare function authBucket(path: string): AuthBucket;
22
+ export declare function callerKey(headers: Headers, scope: string): string;
23
+ /** A recipient as a bucket key: hashed, so the limiter never holds an address. */
24
+ export declare function recipientKey(email: string): string;
25
+ export declare function tooMany(result: LimitResult): Response;
26
+ /** Returns a Response to send INSTEAD of handling the request, or null to go on. */
27
+ export declare function rateLimitAuthRequest(request: Request): Promise<Response | null>;
28
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/auth/limits.ts"],"names":[],"mappings":"AACA,OAAO,EAAsC,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAEhF;;;;;;GAMG;AACH,eAAO,MAAM,UAAU;IACrB,0DAA0D;;IAE1D,2EAA2E;;IAE3E,uEAAuE;;IAEvE,0EAA0E;;CAE3E,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,MAAM,GAAG,MAAM,CAAC;AAwBxD,qEAAqE;AACrE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAKnD;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,CAQrD;AAeD,oFAAoF;AACpF,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAgB1B"}
@@ -0,0 +1,90 @@
1
+ import { createHash } from "node:crypto";
2
+ import { clientIp, slidingWindow, toHeaders } from "limitkit";
3
+ /**
4
+ * Rate limits for the account endpoints, the same in every product.
5
+ *
6
+ * KEYED ON THE PROXY'S HOP, NEVER THE FIRST X-Forwarded-For ENTRY. limitkit's
7
+ * `clientIp()` reads the LAST hop, the one Caddy appended, which the caller
8
+ * cannot choose. `trustedProxies: 1` is Caddy on the box.
9
+ */
10
+ export const authLimits = {
11
+ /** Guessing a password, a code or a passkey assertion. */
12
+ credential: slidingWindow({ limit: 10, windowMs: 15 * 60_000 }),
13
+ /** Anything that sends an email: protects inboxes and the shared quota. */
14
+ mail: slidingWindow({ limit: 5, windowMs: 60 * 60_000 }),
15
+ /** The same, per recipient, so rotating IPs cannot flood one inbox. */
16
+ mailTo: slidingWindow({ limit: 3, windowMs: 60 * 60_000 }),
17
+ /** Everything else under /api/auth (session reads, sign-out, listing). */
18
+ auth: slidingWindow({ limit: 300, windowMs: 10 * 60_000 }),
19
+ };
20
+ const MAIL = new Set([
21
+ "/sign-up/email",
22
+ "/sign-in/magic-link",
23
+ "/send-verification-email",
24
+ "/request-password-reset",
25
+ "/forget-password",
26
+ "/change-email",
27
+ ]);
28
+ const CREDENTIAL_PREFIXES = [
29
+ "/sign-in/email",
30
+ "/sign-in/social",
31
+ "/reset-password",
32
+ "/change-password",
33
+ "/verify-password",
34
+ "/two-factor/",
35
+ "/passkey/verify-authentication",
36
+ "/passkey/generate-authenticate-options",
37
+ "/magic-link/verify",
38
+ "/delete-user",
39
+ ];
40
+ /** Which bucket a better-auth path (after /api/auth) spends from. */
41
+ export function authBucket(path) {
42
+ if (MAIL.has(path))
43
+ return "mail";
44
+ if (CREDENTIAL_PREFIXES.some((p) => path === p || path.startsWith(p)))
45
+ return "credential";
46
+ return "auth";
47
+ }
48
+ export function callerKey(headers, scope) {
49
+ return `${scope}:${clientIp(headers, { trustedProxies: 1 })}`;
50
+ }
51
+ /** A recipient as a bucket key: hashed, so the limiter never holds an address. */
52
+ export function recipientKey(email) {
53
+ return `mail-to:${createHash("sha256").update(email.trim().toLowerCase()).digest("hex")}`;
54
+ }
55
+ export function tooMany(result) {
56
+ return Response.json({ error: "rate_limited", retryAfterSeconds: result.retryAfterSeconds }, {
57
+ status: 429,
58
+ headers: { ...toHeaders(result), "Cache-Control": "no-store" },
59
+ });
60
+ }
61
+ /** The address a mail-sending request would write to, read from a CLONE of its body. */
62
+ async function recipientOf(request) {
63
+ try {
64
+ const body = await request.clone().json();
65
+ const fields = body ?? {};
66
+ const value = typeof fields.email === "string" ? fields.email : fields.newEmail;
67
+ return typeof value === "string" && value.includes("@") ? value : null;
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ /** Returns a Response to send INSTEAD of handling the request, or null to go on. */
74
+ export async function rateLimitAuthRequest(request) {
75
+ const path = new URL(request.url).pathname.replace(/^\/api\/auth/, "") || "/";
76
+ const bucket = request.method === "GET" ? "auth" : authBucket(path);
77
+ const byCaller = authLimits[bucket].check(callerKey(request.headers, `auth-${bucket}`));
78
+ if (!byCaller.allowed)
79
+ return tooMany(byCaller);
80
+ if (bucket === "mail") {
81
+ const email = await recipientOf(request);
82
+ if (email) {
83
+ const byRecipient = authLimits.mailTo.check(recipientKey(email));
84
+ if (!byRecipient.allowed)
85
+ return tooMany(byRecipient);
86
+ }
87
+ }
88
+ return null;
89
+ }
90
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/auth/limits.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { AuthMailer, AuthMailKind, AuthSite } from "./server.js";
2
+ export interface AuthMailOptions {
3
+ site: Pick<AuthSite, "title" | "url">;
4
+ /** Read lazily so a build never captures deployment credentials. */
5
+ env?: () => Record<string, string | undefined>;
6
+ subjects?: Partial<Record<AuthMailKind, string>>;
7
+ }
8
+ /** Shared account-mail adapter; a no-op sender is never reported as configured. */
9
+ export declare function createAuthMailer(options: AuthMailOptions): AuthMailer;
10
+ //# sourceMappingURL=mail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail.d.ts","sourceRoot":"","sources":["../../src/auth/mail.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,KAAK,CAAC,CAAC;IACtC,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;CAClD;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,UAAU,CAgDrE"}
@@ -0,0 +1,45 @@
1
+ import { conventionalFrom, isMailConfigured, sendMail, } from "@bitbaum/mail-kit";
2
+ /** Shared account-mail adapter; a no-op sender is never reported as configured. */
3
+ export function createAuthMailer(options) {
4
+ const env = () => {
5
+ const values = options.env?.() ?? process.env;
6
+ return {
7
+ ...values,
8
+ APP_URL: options.site.url,
9
+ RESEND_FROM: values.RESEND_FROM || conventionalFrom(options.site.title),
10
+ };
11
+ };
12
+ const subjects = {
13
+ verify: `Confirm your email for ${options.site.title}`,
14
+ magicLink: `Sign in to ${options.site.title}`,
15
+ reset: `Reset your ${options.site.title} password`,
16
+ ...options.subjects,
17
+ };
18
+ return {
19
+ get configured() {
20
+ return isMailConfigured(env());
21
+ },
22
+ async send(kind, to, url) {
23
+ // Refuse unexpected origins before sending a bearer credential by email.
24
+ let sameOrigin = false;
25
+ try {
26
+ sameOrigin = new URL(url).origin === new URL(options.site.url).origin;
27
+ }
28
+ catch {
29
+ /* Invalid links must never reach a recipient. */
30
+ }
31
+ if (!sameOrigin) {
32
+ console.error("[auth] refusing account email with an unexpected origin");
33
+ return;
34
+ }
35
+ const result = await sendMail({
36
+ to,
37
+ subject: subjects[kind],
38
+ text: `${subjects[kind]}\n\n${url}\n\nIf you did not request this, you can ignore this email.`,
39
+ }, { env: env() });
40
+ if (!result.sent)
41
+ console.error("[auth] account email delivery failed", result.status ?? "transport");
42
+ },
43
+ };
44
+ }
45
+ //# sourceMappingURL=mail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail.js","sourceRoot":"","sources":["../../src/auth/mail.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,QAAQ,GACT,MAAM,mBAAmB,CAAC;AAU3B,mFAAmF;AACnF,MAAM,UAAU,gBAAgB,CAAC,OAAwB;IACvD,MAAM,GAAG,GAAG,GAAG,EAAE;QACf,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC;QAC9C,OAAO;YACL,GAAG,MAAM;YACT,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG;YACzB,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;SACxE,CAAC;IACJ,CAAC,CAAC;IACF,MAAM,QAAQ,GAAiC;QAC7C,MAAM,EAAE,0BAA0B,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE;QACtD,SAAS,EAAE,cAAc,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE;QAC7C,KAAK,EAAE,cAAc,OAAO,CAAC,IAAI,CAAC,KAAK,WAAW;QAClD,GAAG,OAAO,CAAC,QAAQ;KACpB,CAAC;IACF,OAAO;QACL,IAAI,UAAU;YACZ,OAAO,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC;QACjC,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG;YACtB,yEAAyE;YACzE,IAAI,UAAU,GAAG,KAAK,CAAC;YACvB,IAAI,CAAC;gBACH,UAAU,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;YACxE,CAAC;YAAC,MAAM,CAAC;gBACP,iDAAiD;YACnD,CAAC;YACD,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,OAAO,CAAC,KAAK,CACX,yDAAyD,CAC1D,CAAC;gBACF,OAAO;YACT,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAC3B;gBACE,EAAE;gBACF,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC;gBACvB,IAAI,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,GAAG,6DAA6D;aAC/F,EACD,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,CACf,CAAC;YACF,IAAI,CAAC,MAAM,CAAC,IAAI;gBACd,OAAO,CAAC,KAAK,CACX,sCAAsC,EACtC,MAAM,CAAC,MAAM,IAAI,WAAW,CAC7B,CAAC;QACN,CAAC;KACF,CAAC;AACJ,CAAC"}