@lalternative/auth 0.10.5 → 0.11.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.
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createAuthClient } from 'better-auth/react';
2
- import { d as AuthClientSurface, j as MagicLinkClientSurface, P as PlatformAuthClientConfig } from './types-CxpBvjn9.js';
2
+ import { f as AuthClientSurface, l as MagicLinkClientSurface, d as AdminClientSurface, T as TwoFactorClientSurface, P as PlatformAuthClientConfig } from './types-Ct0Kt36S.js';
3
3
  import 'better-auth';
4
4
 
5
5
  /**
@@ -13,10 +13,16 @@ import 'better-auth';
13
13
  * signIn carries both halves: the client always mounts magicLinkClient, since
14
14
  * which methods exist client-side costs nothing — whether the route answers is
15
15
  * decided server-side by passing `magicLink` to createPlatformAuth.
16
+ *
17
+ * admin is optional on AuthClientSurface, whose job is to type the prop the
18
+ * forms take, and required here: this client always mounts adminClient(), so a
19
+ * back-office calling admin.listUsers() off it must not have to widen the type.
16
20
  */
17
- type PlatformAuthClient = Omit<AuthClientSurface, "signIn"> & {
21
+ type PlatformAuthClient = Omit<AuthClientSurface, "signIn" | "admin"> & {
18
22
  signIn: AuthClientSurface["signIn"] & MagicLinkClientSurface["signIn"];
19
- } & Omit<ReturnType<typeof createAuthClient>, keyof AuthClientSurface | keyof MagicLinkClientSurface>;
23
+ admin: AdminClientSurface;
24
+ twoFactor: TwoFactorClientSurface;
25
+ } & Omit<ReturnType<typeof createAuthClient>, keyof AuthClientSurface | keyof MagicLinkClientSurface | "twoFactor">;
20
26
  /**
21
27
  * Creates a Better Auth client for React usage.
22
28
  * Provides useSession() and the email-OTP / magic-link / admin plugin methods.
package/dist/client.js CHANGED
@@ -3,7 +3,8 @@ import { createAuthClient } from "better-auth/react";
3
3
  import {
4
4
  emailOTPClient,
5
5
  adminClient,
6
- magicLinkClient
6
+ magicLinkClient,
7
+ twoFactorClient
7
8
  } from "better-auth/client/plugins";
8
9
  function createPlatformAuthClient(config) {
9
10
  return createAuthClient({
@@ -12,6 +13,7 @@ function createPlatformAuthClient(config) {
12
13
  emailOTPClient(),
13
14
  magicLinkClient(),
14
15
  adminClient(),
16
+ twoFactorClient(),
15
17
  ...config?.plugins ?? []
16
18
  ]
17
19
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { createAuthClient } from \"better-auth/react\"\nimport {\n emailOTPClient,\n adminClient,\n magicLinkClient,\n} from \"better-auth/client/plugins\"\nimport type {\n AuthClientSurface,\n MagicLinkClientSurface,\n PlatformAuthClientConfig,\n} from \"./types\"\n\n/**\n * A Better Auth React client carrying the platform plugins.\n *\n * The concrete inferred type cannot be named in a published .d.ts (TS2742 — it\n * reaches into zod's internals), so the surface the auth screens call is\n * declared by hand in AuthClientSurface and intersected with the rest of the\n * client. Keep it in sync with the plugins enabled below.\n *\n * signIn carries both halves: the client always mounts magicLinkClient, since\n * which methods exist client-side costs nothing — whether the route answers is\n * decided server-side by passing `magicLink` to createPlatformAuth.\n */\nexport type PlatformAuthClient = Omit<AuthClientSurface, \"signIn\"> & {\n signIn: AuthClientSurface[\"signIn\"] & MagicLinkClientSurface[\"signIn\"]\n} & Omit<\n ReturnType<typeof createAuthClient>,\n keyof AuthClientSurface | keyof MagicLinkClientSurface\n >\n\n/**\n * Creates a Better Auth client for React usage.\n * Provides useSession() and the email-OTP / magic-link / admin plugin methods.\n */\nexport function createPlatformAuthClient(\n config?: PlatformAuthClientConfig,\n): PlatformAuthClient {\n return createAuthClient({\n baseURL:\n config?.baseURL ??\n (typeof window !== \"undefined\"\n ? window.location.origin\n : \"http://localhost:3000\"),\n plugins: [\n emailOTPClient(),\n magicLinkClient(),\n adminClient(),\n ...(config?.plugins ?? []),\n ],\n }) as unknown as PlatformAuthClient\n}\n"],"mappings":";AAAA,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA8BA,SAAS,yBACd,QACoB;AACpB,SAAO,iBAAiB;AAAA,IACtB,SACE,QAAQ,YACP,OAAO,WAAW,cACf,OAAO,SAAS,SAChB;AAAA,IACN,SAAS;AAAA,MACP,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,YAAY;AAAA,MACZ,GAAI,QAAQ,WAAW,CAAC;AAAA,IAC1B;AAAA,EACF,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { createAuthClient } from \"better-auth/react\"\nimport {\n emailOTPClient,\n adminClient,\n magicLinkClient,\n twoFactorClient,\n} from \"better-auth/client/plugins\"\nimport type {\n AdminClientSurface,\n AuthClientSurface,\n MagicLinkClientSurface,\n PlatformAuthClientConfig,\n TwoFactorClientSurface,\n} from \"./types\"\n\n/**\n * A Better Auth React client carrying the platform plugins.\n *\n * The concrete inferred type cannot be named in a published .d.ts (TS2742 — it\n * reaches into zod's internals), so the surface the auth screens call is\n * declared by hand in AuthClientSurface and intersected with the rest of the\n * client. Keep it in sync with the plugins enabled below.\n *\n * signIn carries both halves: the client always mounts magicLinkClient, since\n * which methods exist client-side costs nothing — whether the route answers is\n * decided server-side by passing `magicLink` to createPlatformAuth.\n *\n * admin is optional on AuthClientSurface, whose job is to type the prop the\n * forms take, and required here: this client always mounts adminClient(), so a\n * back-office calling admin.listUsers() off it must not have to widen the type.\n */\nexport type PlatformAuthClient = Omit<AuthClientSurface, \"signIn\" | \"admin\"> & {\n signIn: AuthClientSurface[\"signIn\"] & MagicLinkClientSurface[\"signIn\"]\n admin: AdminClientSurface\n twoFactor: TwoFactorClientSurface\n} & Omit<\n ReturnType<typeof createAuthClient>,\n | keyof AuthClientSurface\n | keyof MagicLinkClientSurface\n | \"twoFactor\"\n >\n\n/**\n * Creates a Better Auth client for React usage.\n * Provides useSession() and the email-OTP / magic-link / admin plugin methods.\n */\nexport function createPlatformAuthClient(\n config?: PlatformAuthClientConfig,\n): PlatformAuthClient {\n return createAuthClient({\n baseURL:\n config?.baseURL ??\n (typeof window !== \"undefined\"\n ? window.location.origin\n : \"http://localhost:3000\"),\n plugins: [\n emailOTPClient(),\n magicLinkClient(),\n adminClient(),\n twoFactorClient(),\n ...(config?.plugins ?? []),\n ],\n }) as unknown as PlatformAuthClient\n}\n"],"mappings":";AAAA,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAwCA,SAAS,yBACd,QACoB;AACpB,SAAO,iBAAiB;AAAA,IACtB,SACE,QAAQ,YACP,OAAO,WAAW,cACf,OAAO,SAAS,SAChB;AAAA,IACN,SAAS;AAAA,MACP,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,GAAI,QAAQ,WAAW,CAAC;AAAA,IAC1B;AAAA,EACF,CAAC;AACH;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { L as LoginFormProps, R as RegisterFormProps, V as VerifyEmailFormProps, F as ForgotPasswordFormProps, M as MagicLinkFormProps, a as ResetPasswordFormProps, A as AuthLayoutProps, I as InvitationNoticeProps, b as AuthClientResult, c as LinkComponent } from './types-CxpBvjn9.js';
2
- export { d as AuthClientSurface, e as AuthInviteProps, f as AuthNavProps, g as AuthThemeProps, h as InvitationFailure, i as LoginFormLabels, j as MagicLinkClientSurface, k as MagicLinkConfig, l as MagicLinkFormLabels, P as PlatformAuthClientConfig, m as PlatformAuthConfig, n as PlatformAuthMailer, o as PlatformAuthMailerArgs, p as PlatformAuthMailerType, q as PlatformSession, r as PlatformSessionData, s as PlatformUser, t as RegisterFormLabels } from './types-CxpBvjn9.js';
1
+ import { L as LoginFormProps, R as RegisterFormProps, V as VerifyEmailFormProps, F as ForgotPasswordFormProps, M as MagicLinkFormProps, a as ResetPasswordFormProps, A as AuthLayoutProps, I as InvitationNoticeProps, b as AuthClientResult, c as LinkComponent } from './types-Ct0Kt36S.js';
2
+ export { d as AdminClientSurface, e as AuthClientDataResult, f as AuthClientSurface, g as AuthInviteProps, h as AuthNavProps, i as AuthThemeProps, j as InvitationFailure, k as LoginFormLabels, l as MagicLinkClientSurface, m as MagicLinkConfig, n as MagicLinkFormLabels, P as PlatformAuthClientConfig, o as PlatformAuthConfig, p as PlatformAuthMailer, q as PlatformAuthMailerArgs, r as PlatformAuthMailerType, s as PlatformRateLimitConfig, t as PlatformRateLimitRule, u as PlatformSession, v as PlatformSessionData, w as PlatformTwoFactorConfig, x as PlatformUser, y as RegisterFormLabels, T as TwoFactorClientSurface } from './types-Ct0Kt36S.js';
3
3
  import * as better_auth_react from 'better-auth/react';
4
4
  import * as better_auth from 'better-auth';
5
5
  import { PlatformAuthClient } from './client.js';
6
6
  import * as react from 'react';
7
7
  import { InputHTMLAttributes, ReactNode } from 'react';
8
- export { C as ClaimOutcome, i as isInvitationFailure } from './invitation-DQL5hv6t.js';
8
+ export { C as ClaimOutcome, i as isInvitationFailure } from './invitation-miBKXoxz.js';
9
9
 
10
10
  /**
11
11
  * Returns a useSession hook bound to the given auth client.
@@ -1,4 +1,4 @@
1
- import { h as InvitationFailure } from './types-CxpBvjn9.js';
1
+ import { j as InvitationFailure } from './types-Ct0Kt36S.js';
2
2
 
3
3
  /**
4
4
  * What became of a claim, in terms the invitee can be told.
package/dist/server.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Auth, BetterAuthOptions } from 'better-auth';
2
- import { m as PlatformAuthConfig } from './types-CxpBvjn9.js';
3
- export { q as PlatformSession, r as PlatformSessionData, s as PlatformUser } from './types-CxpBvjn9.js';
4
- export { a as ClaimInvitationOptions, C as ClaimOutcome, c as claimInvitation, b as completesSignup, h as holdInviteTokenCookie, d as invitationOutcomeCookie, e as inviteTokenFrom, i as isInvitationFailure, p as pinInviteToken, r as releaseInviteTokenCookie } from './invitation-DQL5hv6t.js';
2
+ import { o as PlatformAuthConfig } from './types-Ct0Kt36S.js';
3
+ export { s as PlatformRateLimitConfig, t as PlatformRateLimitRule, u as PlatformSession, v as PlatformSessionData, w as PlatformTwoFactorConfig, x as PlatformUser } from './types-Ct0Kt36S.js';
4
+ export { a as ClaimInvitationOptions, C as ClaimOutcome, c as claimInvitation, b as completesSignup, h as holdInviteTokenCookie, d as invitationOutcomeCookie, e as inviteTokenFrom, i as isInvitationFailure, p as pinInviteToken, r as releaseInviteTokenCookie } from './invitation-miBKXoxz.js';
5
5
 
6
6
  /**
7
7
  * Creates a Better Auth instance with platform defaults.
package/dist/server.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
 
13
13
  // src/server.ts
14
14
  import { betterAuth, APIError } from "better-auth";
15
- import { emailOTP, admin, magicLink } from "better-auth/plugins";
15
+ import { emailOTP, admin, magicLink, twoFactor } from "better-auth/plugins";
16
16
 
17
17
  // src/google-defaults.ts
18
18
  function withGoogleDefaults(google) {
@@ -24,6 +24,32 @@ function withGoogleDefaults(google) {
24
24
  };
25
25
  }
26
26
 
27
+ // src/rate-limit.ts
28
+ var PLATFORM_RULES = {
29
+ "/sign-in/email": { window: 60, max: 5 },
30
+ "/sign-up/email": { window: 60, max: 5 },
31
+ "/email-otp/send-verification-otp": { window: 60, max: 3 },
32
+ "/email-otp/verify-email": { window: 60, max: 5 },
33
+ "/email-otp/reset-password": { window: 60, max: 5 },
34
+ "/forget-password": { window: 60, max: 3 },
35
+ "/reset-password": { window: 60, max: 5 },
36
+ "/sign-in/magic-link": { window: 60, max: 3 },
37
+ "/two-factor/verify-totp": { window: 60, max: 5 },
38
+ "/two-factor/verify-otp": { window: 60, max: 5 },
39
+ "/two-factor/verify-backup-code": { window: 60, max: 5 },
40
+ "/two-factor/send-otp": { window: 60, max: 3 }
41
+ };
42
+ function resolveRateLimit(config) {
43
+ return {
44
+ enabled: config?.enabled ?? true,
45
+ window: config?.window ?? 10,
46
+ max: config?.max ?? 100,
47
+ ...config?.storage ? { storage: config.storage } : {},
48
+ ...config?.modelName ? { modelName: config.modelName } : {},
49
+ customRules: { ...PLATFORM_RULES, ...config?.customRules }
50
+ };
51
+ }
52
+
27
53
  // src/server.ts
28
54
  var DEFAULT_EMAIL_SUBJECTS = {
29
55
  "email-verification": "Verify your account",
@@ -68,7 +94,10 @@ function createPlatformAuth(config) {
68
94
  isInvited,
69
95
  emailSubjects,
70
96
  renderOtpEmail,
71
- magicLink: magicLinkConfig
97
+ magicLink: magicLinkConfig,
98
+ rateLimit,
99
+ twoFactor: twoFactorConfig,
100
+ trustedOrigins
72
101
  } = config;
73
102
  const subjects = { ...DEFAULT_EMAIL_SUBJECTS, ...emailSubjects };
74
103
  const renderEmail = renderOtpEmail ?? defaultRenderOtpEmail;
@@ -76,6 +105,8 @@ function createPlatformAuth(config) {
76
105
  database,
77
106
  baseURL,
78
107
  secret,
108
+ ...trustedOrigins ? { trustedOrigins } : {},
109
+ rateLimit: resolveRateLimit(rateLimit),
79
110
  emailAndPassword: {
80
111
  enabled: true,
81
112
  requireEmailVerification: true
@@ -179,6 +210,12 @@ function createPlatformAuth(config) {
179
210
  })
180
211
  ] : [],
181
212
  admin(),
213
+ ...twoFactorConfig?.enabled ? [
214
+ twoFactor({
215
+ issuer: twoFactorConfig.issuer ?? appName,
216
+ skipVerificationOnEnable: twoFactorConfig.skipVerificationOnEnable ?? false
217
+ })
218
+ ] : [],
182
219
  ...plugins
183
220
  // app-specific plugins (e.g. tanstackStartCookies)
184
221
  ],
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/google-defaults.ts"],"sourcesContent":["import { betterAuth, APIError, type Auth, type BetterAuthOptions } from \"better-auth\"\nimport { emailOTP, admin, magicLink } from \"better-auth/plugins\"\nimport type { PlatformAuthConfig, PlatformAuthMailerType } from \"./types\"\nimport { withGoogleDefaults } from \"./google-defaults\"\nimport { withSignUpName } from \"./signup-name\"\n\nconst DEFAULT_EMAIL_SUBJECTS: Record<string, string> = {\n \"email-verification\": \"Verify your account\",\n \"forget-password\": \"Reset your password\",\n \"sign-in\": \"Your sign-in code\",\n}\n\nfunction defaultRenderOtpEmail(otp: string): string {\n return `\n <div style=\"font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px\">\n <h2 style=\"font-size:20px;font-weight:600;margin-bottom:16px\">Your verification code</h2>\n <p style=\"color:#555;margin-bottom:24px\">Use the code below to continue. It expires in 5 minutes.</p>\n <div style=\"background:#f5f5f5;border-radius:8px;padding:24px;text-align:center;letter-spacing:8px;font-size:32px;font-weight:700\">\n ${otp}\n </div>\n <p style=\"color:#999;font-size:12px;margin-top:24px\">If you didn't request this, you can safely ignore this email.</p>\n </div>\n `\n}\n\nfunction defaultRenderMagicLinkEmail(url: string): string {\n return `\n <div style=\"font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px\">\n <h2 style=\"font-size:20px;font-weight:600;margin-bottom:16px\">Your sign-in link</h2>\n <p style=\"color:#555;margin-bottom:24px\">Click the button below to sign in. The link expires in 5 minutes and works once.</p>\n <a href=\"${url}\" style=\"display:inline-block;background:#111;color:#fff;text-decoration:none;border-radius:8px;padding:14px 28px;font-weight:600\">Sign in</a>\n <p style=\"color:#999;font-size:12px;margin-top:24px;word-break:break-all\">Or paste this address into your browser:<br>${url}</p>\n <p style=\"color:#999;font-size:12px;margin-top:16px\">If you didn't request this, you can safely ignore this email.</p>\n </div>\n `\n}\n\n/**\n * Creates a Better Auth instance with platform defaults.\n * Each app calls this with its own config (DB, secret, providers, plugins).\n */\nexport function createPlatformAuth(\n config: PlatformAuthConfig,\n): Auth<BetterAuthOptions> {\n const {\n database,\n baseURL,\n secret,\n appName,\n mailer,\n google,\n github,\n plugins = [],\n databaseHooks,\n betaMode = false,\n isInvited,\n emailSubjects,\n renderOtpEmail,\n magicLink: magicLinkConfig,\n } = config\n\n const subjects = { ...DEFAULT_EMAIL_SUBJECTS, ...emailSubjects }\n const renderEmail = renderOtpEmail ?? defaultRenderOtpEmail\n\n // The concrete instance type (with email-otp/admin plugins) is widened to\n // the base Auth type so the published .d.ts stays portable (inferring the\n // full plugin type triggers TS2742 — it can't be named without a zod ref).\n // The admin() plugin's user.role field is re-exposed via module augmentation\n // below, so consumers (e.g. transcript-web me.ts) still see session.user.role.\n return betterAuth({\n database,\n baseURL,\n secret,\n emailAndPassword: {\n enabled: true,\n requireEmailVerification: true,\n },\n // Never auto-merge a social identity into an existing account by matching\n // email. Better Auth links by default (email-verified providers are trusted),\n // so signing in with Google/GitHub on an email already registered would fold\n // that identity into the existing account. We keep each sign-in method its\n // own account: a social login on a taken email is refused, not linked.\n account: {\n accountLinking: {\n enabled: false,\n },\n },\n // Naming happens here rather than on /sign-up/email so that every way in\n // is covered: a magic link that signs up bypasses the endpoint entirely\n // and calls createUser straight, with `name: name || \"\"`.\n databaseHooks: {\n ...databaseHooks,\n user: {\n ...databaseHooks?.user,\n create: {\n ...databaseHooks?.user?.create,\n before: async (user: Record<string, unknown>, ctx: unknown) => {\n const named = withSignUpName(user)\n const appHook = databaseHooks?.user?.create?.before\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const applied = await appHook?.(named as any, ctx as any)\n if (applied === false) return false\n if (applied && typeof applied === \"object\" && \"data\" in applied) {\n return { data: withSignUpName(applied.data) }\n }\n return { data: named }\n },\n },\n },\n },\n hooks: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n before: async (ctx: any) => {\n if (!betaMode) return\n if (ctx.path !== \"/sign-up/email\") return\n const body = ctx.body as { email?: string; inviteToken?: string } | undefined\n const email = body?.email\n const inviteToken = body?.inviteToken\n if (email && inviteToken && isInvited) {\n const ok = await isInvited(email, inviteToken)\n if (ok) return\n }\n throw new APIError(\"FORBIDDEN\", {\n message: \"Registration is invite-only during the private beta.\",\n })\n },\n },\n plugins: [\n emailOTP({\n async sendVerificationOTP({ email, otp, type }) {\n const subject = subjects[type]\n ? `${subjects[type]} - ${appName}`\n : `Your ${appName} code`\n const html = renderEmail(otp, type as PlatformAuthMailerType)\n\n if (mailer) {\n await mailer({\n to: email,\n subject,\n html,\n type: type as PlatformAuthMailerType,\n otp,\n })\n return\n }\n\n console.warn(\n `[EMAIL] No mailer configured — logging OTP to stdout for ${email} (${type}): ${otp}`,\n )\n },\n otpLength: 6,\n expiresIn: 300,\n overrideDefaultEmailVerification: true,\n }),\n ...(magicLinkConfig\n ? [\n magicLink({\n expiresIn: magicLinkConfig.expiresIn ?? 300,\n // A magic link that signs up walks past both gates the platform\n // puts on the front door: requireEmailVerification, and the\n // invite-only hook, which only guards /sign-up/email.\n disableSignUp: !magicLinkConfig.allowSignUp,\n async sendMagicLink({ email, url }) {\n const subject =\n magicLinkConfig.subject ?? `Your sign-in link - ${appName}`\n const html = (\n magicLinkConfig.render ?? defaultRenderMagicLinkEmail\n )(url, email)\n\n if (mailer) {\n await mailer({\n to: email,\n subject,\n html,\n type: \"magic-link\",\n url,\n })\n return\n }\n\n console.warn(\n `[EMAIL] No mailer configured — logging magic link to stdout for ${email}: ${url}`,\n )\n },\n }),\n ]\n : []),\n admin(),\n ...plugins, // app-specific plugins (e.g. tanstackStartCookies)\n ],\n socialProviders: {\n // Spread as given rather than rebuilt field by field: anything Better\n // Auth accepts belongs to the app, and a config silently dropped on the\n // way through is how an app ends up writing a plugin to put it back.\n //\n // The two defaults below are the platform's, not Google's: without\n // accessType 'offline' Google never mints a refresh token, and without\n // 'consent' it stops minting one for an account that already consented.\n // A NULL refreshToken means deleting an account can revoke the access\n // token but cannot remove the app from myaccount.google.com/permissions,\n // so the grant outlives the account it belonged to.\n ...(google ? { google: withGoogleDefaults(google) } : {}),\n ...(github ? { github } : {}),\n },\n }) as unknown as Auth<BetterAuthOptions>\n}\n\nexport type PlatformAuth = ReturnType<typeof createPlatformAuth>\n\n// Re-export the session contract from /server so consumers that import the\n// auth factory can type api.getSession() without a second import path.\nexport type {\n PlatformUser,\n PlatformSession,\n PlatformSessionData,\n} from \"./types\"\n\n// Invitation claiming runs on the auth callback, where the session is\n// established — the one place every sign-up flow passes through.\nexport {\n claimInvitation,\n completesSignup,\n holdInviteTokenCookie,\n invitationOutcomeCookie,\n inviteTokenFrom,\n isInvitationFailure,\n pinInviteToken,\n releaseInviteTokenCookie,\n} from \"./invitation\"\nexport type { ClaimOutcome, ClaimInvitationOptions } from \"./invitation\"\n","import type { PlatformAuthConfig } from \"./types\"\n\n/**\n * Applies the platform's Google defaults without touching what the app set.\n *\n * Better Auth also accepts a function returning the options, which cannot be\n * amended without calling it — such a config is passed straight through and\n * owns its own defaults.\n */\nexport function withGoogleDefaults(\n google: NonNullable<PlatformAuthConfig[\"google\"]>,\n): NonNullable<PlatformAuthConfig[\"google\"]> {\n if (typeof google === \"function\") return google\n return {\n ...google,\n accessType: google.accessType ?? \"offline\",\n prompt: google.prompt ?? \"select_account consent\",\n }\n}\n\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/server.ts","../src/google-defaults.ts","../src/rate-limit.ts"],"sourcesContent":["import { betterAuth, APIError, type Auth, type BetterAuthOptions } from \"better-auth\"\nimport { emailOTP, admin, magicLink, twoFactor } from \"better-auth/plugins\"\nimport type { PlatformAuthConfig, PlatformAuthMailerType } from \"./types\"\nimport { withGoogleDefaults } from \"./google-defaults\"\nimport { withSignUpName } from \"./signup-name\"\nimport { resolveRateLimit } from \"./rate-limit\"\n\nconst DEFAULT_EMAIL_SUBJECTS: Record<string, string> = {\n \"email-verification\": \"Verify your account\",\n \"forget-password\": \"Reset your password\",\n \"sign-in\": \"Your sign-in code\",\n}\n\nfunction defaultRenderOtpEmail(otp: string): string {\n return `\n <div style=\"font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px\">\n <h2 style=\"font-size:20px;font-weight:600;margin-bottom:16px\">Your verification code</h2>\n <p style=\"color:#555;margin-bottom:24px\">Use the code below to continue. It expires in 5 minutes.</p>\n <div style=\"background:#f5f5f5;border-radius:8px;padding:24px;text-align:center;letter-spacing:8px;font-size:32px;font-weight:700\">\n ${otp}\n </div>\n <p style=\"color:#999;font-size:12px;margin-top:24px\">If you didn't request this, you can safely ignore this email.</p>\n </div>\n `\n}\n\nfunction defaultRenderMagicLinkEmail(url: string): string {\n return `\n <div style=\"font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px\">\n <h2 style=\"font-size:20px;font-weight:600;margin-bottom:16px\">Your sign-in link</h2>\n <p style=\"color:#555;margin-bottom:24px\">Click the button below to sign in. The link expires in 5 minutes and works once.</p>\n <a href=\"${url}\" style=\"display:inline-block;background:#111;color:#fff;text-decoration:none;border-radius:8px;padding:14px 28px;font-weight:600\">Sign in</a>\n <p style=\"color:#999;font-size:12px;margin-top:24px;word-break:break-all\">Or paste this address into your browser:<br>${url}</p>\n <p style=\"color:#999;font-size:12px;margin-top:16px\">If you didn't request this, you can safely ignore this email.</p>\n </div>\n `\n}\n\n/**\n * Creates a Better Auth instance with platform defaults.\n * Each app calls this with its own config (DB, secret, providers, plugins).\n */\nexport function createPlatformAuth(\n config: PlatformAuthConfig,\n): Auth<BetterAuthOptions> {\n const {\n database,\n baseURL,\n secret,\n appName,\n mailer,\n google,\n github,\n plugins = [],\n databaseHooks,\n betaMode = false,\n isInvited,\n emailSubjects,\n renderOtpEmail,\n magicLink: magicLinkConfig,\n rateLimit,\n twoFactor: twoFactorConfig,\n trustedOrigins,\n } = config\n\n const subjects = { ...DEFAULT_EMAIL_SUBJECTS, ...emailSubjects }\n const renderEmail = renderOtpEmail ?? defaultRenderOtpEmail\n\n // The concrete instance type (with email-otp/admin plugins) is widened to\n // the base Auth type so the published .d.ts stays portable (inferring the\n // full plugin type triggers TS2742 — it can't be named without a zod ref).\n // The admin() plugin's user.role field is re-exposed via module augmentation\n // below, so consumers (e.g. transcript-web me.ts) still see session.user.role.\n return betterAuth({\n database,\n baseURL,\n secret,\n ...(trustedOrigins ? { trustedOrigins } : {}),\n rateLimit: resolveRateLimit(rateLimit),\n emailAndPassword: {\n enabled: true,\n requireEmailVerification: true,\n },\n // Never auto-merge a social identity into an existing account by matching\n // email. Better Auth links by default (email-verified providers are trusted),\n // so signing in with Google/GitHub on an email already registered would fold\n // that identity into the existing account. We keep each sign-in method its\n // own account: a social login on a taken email is refused, not linked.\n account: {\n accountLinking: {\n enabled: false,\n },\n },\n // Naming happens here rather than on /sign-up/email so that every way in\n // is covered: a magic link that signs up bypasses the endpoint entirely\n // and calls createUser straight, with `name: name || \"\"`.\n databaseHooks: {\n ...databaseHooks,\n user: {\n ...databaseHooks?.user,\n create: {\n ...databaseHooks?.user?.create,\n before: async (user: Record<string, unknown>, ctx: unknown) => {\n const named = withSignUpName(user)\n const appHook = databaseHooks?.user?.create?.before\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const applied = await appHook?.(named as any, ctx as any)\n if (applied === false) return false\n if (applied && typeof applied === \"object\" && \"data\" in applied) {\n return { data: withSignUpName(applied.data) }\n }\n return { data: named }\n },\n },\n },\n },\n hooks: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n before: async (ctx: any) => {\n if (!betaMode) return\n if (ctx.path !== \"/sign-up/email\") return\n const body = ctx.body as { email?: string; inviteToken?: string } | undefined\n const email = body?.email\n const inviteToken = body?.inviteToken\n if (email && inviteToken && isInvited) {\n const ok = await isInvited(email, inviteToken)\n if (ok) return\n }\n throw new APIError(\"FORBIDDEN\", {\n message: \"Registration is invite-only during the private beta.\",\n })\n },\n },\n plugins: [\n emailOTP({\n async sendVerificationOTP({ email, otp, type }) {\n const subject = subjects[type]\n ? `${subjects[type]} - ${appName}`\n : `Your ${appName} code`\n const html = renderEmail(otp, type as PlatformAuthMailerType)\n\n if (mailer) {\n await mailer({\n to: email,\n subject,\n html,\n type: type as PlatformAuthMailerType,\n otp,\n })\n return\n }\n\n console.warn(\n `[EMAIL] No mailer configured — logging OTP to stdout for ${email} (${type}): ${otp}`,\n )\n },\n otpLength: 6,\n expiresIn: 300,\n overrideDefaultEmailVerification: true,\n }),\n ...(magicLinkConfig\n ? [\n magicLink({\n expiresIn: magicLinkConfig.expiresIn ?? 300,\n // A magic link that signs up walks past both gates the platform\n // puts on the front door: requireEmailVerification, and the\n // invite-only hook, which only guards /sign-up/email.\n disableSignUp: !magicLinkConfig.allowSignUp,\n async sendMagicLink({ email, url }) {\n const subject =\n magicLinkConfig.subject ?? `Your sign-in link - ${appName}`\n const html = (\n magicLinkConfig.render ?? defaultRenderMagicLinkEmail\n )(url, email)\n\n if (mailer) {\n await mailer({\n to: email,\n subject,\n html,\n type: \"magic-link\",\n url,\n })\n return\n }\n\n console.warn(\n `[EMAIL] No mailer configured — logging magic link to stdout for ${email}: ${url}`,\n )\n },\n }),\n ]\n : []),\n admin(),\n ...(twoFactorConfig?.enabled\n ? [\n twoFactor({\n issuer: twoFactorConfig.issuer ?? appName,\n skipVerificationOnEnable:\n twoFactorConfig.skipVerificationOnEnable ?? false,\n }),\n ]\n : []),\n ...plugins, // app-specific plugins (e.g. tanstackStartCookies)\n ],\n socialProviders: {\n // Spread as given rather than rebuilt field by field: anything Better\n // Auth accepts belongs to the app, and a config silently dropped on the\n // way through is how an app ends up writing a plugin to put it back.\n //\n // The two defaults below are the platform's, not Google's: without\n // accessType 'offline' Google never mints a refresh token, and without\n // 'consent' it stops minting one for an account that already consented.\n // A NULL refreshToken means deleting an account can revoke the access\n // token but cannot remove the app from myaccount.google.com/permissions,\n // so the grant outlives the account it belonged to.\n ...(google ? { google: withGoogleDefaults(google) } : {}),\n ...(github ? { github } : {}),\n },\n }) as unknown as Auth<BetterAuthOptions>\n}\n\nexport type PlatformAuth = ReturnType<typeof createPlatformAuth>\n\n// Re-export the session contract from /server so consumers that import the\n// auth factory can type api.getSession() without a second import path.\nexport type {\n PlatformUser,\n PlatformSession,\n PlatformSessionData,\n PlatformRateLimitConfig,\n PlatformRateLimitRule,\n PlatformTwoFactorConfig,\n} from \"./types\"\n\n// Invitation claiming runs on the auth callback, where the session is\n// established — the one place every sign-up flow passes through.\nexport {\n claimInvitation,\n completesSignup,\n holdInviteTokenCookie,\n invitationOutcomeCookie,\n inviteTokenFrom,\n isInvitationFailure,\n pinInviteToken,\n releaseInviteTokenCookie,\n} from \"./invitation\"\nexport type { ClaimOutcome, ClaimInvitationOptions } from \"./invitation\"\n","import type { PlatformAuthConfig } from \"./types\"\n\n/**\n * Applies the platform's Google defaults without touching what the app set.\n *\n * Better Auth also accepts a function returning the options, which cannot be\n * amended without calling it — such a config is passed straight through and\n * owns its own defaults.\n */\nexport function withGoogleDefaults(\n google: NonNullable<PlatformAuthConfig[\"google\"]>,\n): NonNullable<PlatformAuthConfig[\"google\"]> {\n if (typeof google === \"function\") return google\n return {\n ...google,\n accessType: google.accessType ?? \"offline\",\n prompt: google.prompt ?? \"select_account consent\",\n }\n}\n\n","import type { PlatformRateLimitConfig, PlatformRateLimitRule } from \"./types\"\n\n// Better Auth keys its own rate limiter on NODE_ENV === \"production\", so a\n// deployment that forgets the variable serves sign-in with no brute-force\n// protection and nothing reports it. These are the platform's rules, applied\n// regardless of the environment.\nconst PLATFORM_RULES: Record<string, PlatformRateLimitRule> = {\n \"/sign-in/email\": { window: 60, max: 5 },\n \"/sign-up/email\": { window: 60, max: 5 },\n \"/email-otp/send-verification-otp\": { window: 60, max: 3 },\n \"/email-otp/verify-email\": { window: 60, max: 5 },\n \"/email-otp/reset-password\": { window: 60, max: 5 },\n \"/forget-password\": { window: 60, max: 3 },\n \"/reset-password\": { window: 60, max: 5 },\n \"/sign-in/magic-link\": { window: 60, max: 3 },\n \"/two-factor/verify-totp\": { window: 60, max: 5 },\n \"/two-factor/verify-otp\": { window: 60, max: 5 },\n \"/two-factor/verify-backup-code\": { window: 60, max: 5 },\n \"/two-factor/send-otp\": { window: 60, max: 3 },\n}\n\nexport interface ResolvedRateLimit {\n enabled: boolean\n window: number\n max: number\n storage?: \"memory\" | \"database\" | \"secondary-storage\"\n modelName?: string\n customRules: Record<string, PlatformRateLimitRule>\n}\n\nexport function resolveRateLimit(\n config?: PlatformRateLimitConfig,\n): ResolvedRateLimit {\n return {\n enabled: config?.enabled ?? true,\n window: config?.window ?? 10,\n max: config?.max ?? 100,\n ...(config?.storage ? { storage: config.storage } : {}),\n ...(config?.modelName ? { modelName: config.modelName } : {}),\n customRules: { ...PLATFORM_RULES, ...config?.customRules },\n }\n}\n\nexport { PLATFORM_RULES }\n"],"mappings":"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","names":[]}
@@ -25,6 +25,8 @@ interface PlatformUser {
25
25
  role?: string | null;
26
26
  /** From the admin() plugin. */
27
27
  banned?: boolean | null;
28
+ /** From the twoFactor() plugin, when enabled. */
29
+ twoFactorEnabled?: boolean | null;
28
30
  }
29
31
  interface PlatformSessionData {
30
32
  id: string;
@@ -82,6 +84,51 @@ interface MagicLinkConfig {
82
84
  */
83
85
  render?: (url: string, email: string) => string;
84
86
  }
87
+ interface PlatformRateLimitRule {
88
+ /** Length of the window, in seconds. */
89
+ window: number;
90
+ /** Requests allowed per window, per client. */
91
+ max: number;
92
+ }
93
+ interface PlatformRateLimitConfig {
94
+ /**
95
+ * Off only when an app is certain something in front of it already limits
96
+ * the auth endpoints. Defaults to true, deliberately: Better Auth keys its
97
+ * own default on NODE_ENV, so a deployment missing the variable would serve
98
+ * sign-in with no brute-force protection and nothing would say so.
99
+ */
100
+ enabled?: boolean;
101
+ /** Global window in seconds for paths without a rule. Defaults to 10. */
102
+ window?: number;
103
+ /** Global max per window for paths without a rule. Defaults to 100. */
104
+ max?: number;
105
+ /**
106
+ * Where counters live. "memory" (the default) is per-process, so it does not
107
+ * hold across replicas — an app running more than one must pass "database"
108
+ * or wire secondaryStorage.
109
+ */
110
+ storage?: "memory" | "database" | "secondary-storage";
111
+ /** Table name when storage is "database". */
112
+ modelName?: string;
113
+ /**
114
+ * Per-path overrides, merged over the platform's. The platform already
115
+ * tightens sign-in, sign-up, OTP send/verify, password reset, magic link
116
+ * and two-factor verification; use this to go further, not to loosen.
117
+ */
118
+ customRules?: Record<string, PlatformRateLimitRule>;
119
+ }
120
+ interface PlatformTwoFactorConfig {
121
+ /** Mounts the two-factor endpoints. Off unless set. */
122
+ enabled: boolean;
123
+ /** Label shown in the authenticator app. Defaults to appName. */
124
+ issuer?: string;
125
+ /**
126
+ * Trust an enrolment before a first code is verified. Leave false: enabling
127
+ * on an unverified secret locks the account out when the authenticator was
128
+ * mis-scanned.
129
+ */
130
+ skipVerificationOnEnable?: boolean;
131
+ }
85
132
  interface PlatformAuthConfig {
86
133
  /** PostgreSQL connection pool or connection string */
87
134
  database: BetterAuthOptions["database"];
@@ -143,6 +190,22 @@ interface PlatformAuthConfig {
143
190
  betaMode?: boolean;
144
191
  /** Check if an email+token pair has been invited (required when betaMode is true) */
145
192
  isInvited?: (email: string, inviteToken: string) => Promise<boolean>;
193
+ /**
194
+ * Brute-force limits on the auth endpoints. The platform enables them with
195
+ * tightened per-path rules; pass this only to adjust.
196
+ */
197
+ rateLimit?: PlatformRateLimitConfig;
198
+ /**
199
+ * TOTP second factor with backup codes. Requires the `twoFactor` table and
200
+ * the `user.twoFactorEnabled` column.
201
+ */
202
+ twoFactor?: PlatformTwoFactorConfig;
203
+ /**
204
+ * Origins allowed to drive the auth endpoints, beyond baseURL. Better Auth
205
+ * checks Origin against this list, so it is what stands between a session
206
+ * cookie and a cross-site request that spends it.
207
+ */
208
+ trustedOrigins?: string[];
146
209
  }
147
210
  interface PlatformAuthClientConfig {
148
211
  /** Base URL override (defaults to window.location.origin in browser) */
@@ -271,6 +334,36 @@ interface AuthClientResult {
271
334
  status?: number;
272
335
  } | null;
273
336
  }
337
+ interface AuthClientDataResult<T> extends AuthClientResult {
338
+ data?: T | null;
339
+ }
340
+ /**
341
+ * The admin() plugin's client half. Part of {@link AuthClientSurface} rather
342
+ * than kept apart like {@link MagicLinkClientSurface}: createPlatformAuthClient
343
+ * always mounts adminClient(), so a client built by it always answers here.
344
+ *
345
+ * The user shape is left open — each app stores its own columns, and this
346
+ * surface exists to type the calls, not the rows they return.
347
+ */
348
+ interface AdminClientSurface {
349
+ listUsers(input: {
350
+ query?: Record<string, unknown>;
351
+ }): Promise<AuthClientDataResult<{
352
+ users?: unknown[];
353
+ total?: number;
354
+ }>>;
355
+ banUser(input: {
356
+ userId: string;
357
+ banReason?: string;
358
+ }): Promise<AuthClientResult>;
359
+ unbanUser(input: {
360
+ userId: string;
361
+ }): Promise<AuthClientResult>;
362
+ setRole(input: {
363
+ userId: string;
364
+ role: "admin" | "user";
365
+ }): Promise<AuthClientResult>;
366
+ }
274
367
  interface AuthClientSurface {
275
368
  signIn: {
276
369
  email(input: {
@@ -289,6 +382,7 @@ interface AuthClientSurface {
289
382
  name?: string;
290
383
  email: string;
291
384
  password: string;
385
+ callbackURL?: string;
292
386
  }): Promise<AuthClientResult>;
293
387
  };
294
388
  emailOtp: {
@@ -306,6 +400,12 @@ interface AuthClientSurface {
306
400
  password: string;
307
401
  }): Promise<AuthClientResult>;
308
402
  };
403
+ /**
404
+ * Optional: the forms take an AuthClientSurface as a prop, and a client built
405
+ * by hand for a login screen has no reason to mount adminClient(). Required
406
+ * on PlatformAuthClient, which always does.
407
+ */
408
+ admin?: AdminClientSurface;
309
409
  }
310
410
  /**
311
411
  * The magic-link half of the client surface, kept apart from
@@ -322,6 +422,45 @@ interface MagicLinkClientSurface {
322
422
  }): Promise<AuthClientResult>;
323
423
  };
324
424
  }
425
+ /**
426
+ * The twoFactor() plugin's client half. Always mounted by
427
+ * createPlatformAuthClient — which methods exist client-side costs nothing;
428
+ * whether the routes answer is decided server-side by passing `twoFactor` to
429
+ * createPlatformAuth.
430
+ *
431
+ * A sign-in against an account with 2FA on returns `twoFactorRedirect: true`
432
+ * instead of a session, and the second factor is what completes it.
433
+ */
434
+ interface TwoFactorClientSurface {
435
+ enable(input: {
436
+ password: string;
437
+ issuer?: string;
438
+ }): Promise<AuthClientDataResult<{
439
+ totpURI: string;
440
+ backupCodes: string[];
441
+ }>>;
442
+ disable(input: {
443
+ password: string;
444
+ }): Promise<AuthClientResult>;
445
+ getTotpUri(input: {
446
+ password: string;
447
+ }): Promise<AuthClientDataResult<{
448
+ totpURI: string;
449
+ }>>;
450
+ verifyTotp(input: {
451
+ code: string;
452
+ trustDevice?: boolean;
453
+ }): Promise<AuthClientResult>;
454
+ verifyBackupCode(input: {
455
+ code: string;
456
+ trustDevice?: boolean;
457
+ }): Promise<AuthClientResult>;
458
+ generateBackupCodes(input: {
459
+ password: string;
460
+ }): Promise<AuthClientDataResult<{
461
+ backupCodes: string[];
462
+ }>>;
463
+ }
325
464
  interface AuthThemeProps {
326
465
  /** Replaces the submit button's `bg-primary text-primary-foreground` */
327
466
  submitClassName?: string;
@@ -563,4 +702,4 @@ interface AuthLayoutProps extends AuthHeadingProps {
563
702
  footer?: React.ReactNode;
564
703
  }
565
704
 
566
- export type { AuthLayoutProps as A, ForgotPasswordFormProps as F, InvitationNoticeProps as I, LoginFormProps as L, MagicLinkFormProps as M, PlatformAuthClientConfig as P, RegisterFormProps as R, VerifyEmailFormProps as V, ResetPasswordFormProps as a, AuthClientResult as b, LinkComponent as c, AuthClientSurface as d, AuthInviteProps as e, AuthNavProps as f, AuthThemeProps as g, InvitationFailure as h, LoginFormLabels as i, MagicLinkClientSurface as j, MagicLinkConfig as k, MagicLinkFormLabels as l, PlatformAuthConfig as m, PlatformAuthMailer as n, PlatformAuthMailerArgs as o, PlatformAuthMailerType as p, PlatformSession as q, PlatformSessionData as r, PlatformUser as s, RegisterFormLabels as t };
705
+ export type { AuthLayoutProps as A, ForgotPasswordFormProps as F, InvitationNoticeProps as I, LoginFormProps as L, MagicLinkFormProps as M, PlatformAuthClientConfig as P, RegisterFormProps as R, TwoFactorClientSurface as T, VerifyEmailFormProps as V, ResetPasswordFormProps as a, AuthClientResult as b, LinkComponent as c, AdminClientSurface as d, AuthClientDataResult as e, AuthClientSurface as f, AuthInviteProps as g, AuthNavProps as h, AuthThemeProps as i, InvitationFailure as j, LoginFormLabels as k, MagicLinkClientSurface as l, MagicLinkConfig as m, MagicLinkFormLabels as n, PlatformAuthConfig as o, PlatformAuthMailer as p, PlatformAuthMailerArgs as q, PlatformAuthMailerType as r, PlatformRateLimitConfig as s, PlatformRateLimitRule as t, PlatformSession as u, PlatformSessionData as v, PlatformTwoFactorConfig as w, PlatformUser as x, RegisterFormLabels as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lalternative/auth",
3
- "version": "0.10.5",
3
+ "version": "0.11.0",
4
4
  "description": "Shared Better Auth wrapper for L'Alternative apps (server + React client + auth UI)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",