@lunora/auth 1.0.0-alpha.7 → 1.0.0-alpha.71

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 (69) hide show
  1. package/LICENSE.md +38 -0
  2. package/README.md +56 -1
  3. package/dist/adapter.d.mts +4 -43
  4. package/dist/adapter.d.ts +4 -43
  5. package/dist/adapter.mjs +1 -47
  6. package/dist/audit.d.mts +146 -0
  7. package/dist/audit.d.ts +146 -0
  8. package/dist/audit.mjs +12 -0
  9. package/dist/email-guard.d.mts +122 -0
  10. package/dist/email-guard.d.ts +122 -0
  11. package/dist/email-guard.mjs +1 -0
  12. package/dist/index.d.mts +698 -147
  13. package/dist/index.d.ts +698 -147
  14. package/dist/index.mjs +1 -12
  15. package/dist/middleware.d.mts +157 -156
  16. package/dist/middleware.d.ts +157 -156
  17. package/dist/middleware.mjs +1 -53
  18. package/dist/packem_shared/AUTH_DO_AUDIT_PATH-C4897amZ.mjs +1 -0
  19. package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-kIwlEt8i.mjs +1 -0
  20. package/dist/packem_shared/LunoraAuthAdminError-CiHsF1qZ.mjs +1 -0
  21. package/dist/packem_shared/adapter-RvDcm0Zy.mjs +1 -0
  22. package/dist/packem_shared/adapter.d-2a61HAY2.d.ts +76 -0
  23. package/dist/packem_shared/adapter.d-DrD3bb1u.d.mts +76 -0
  24. package/dist/packem_shared/authAuditHook-DG_ZNO53.mjs +1 -0
  25. package/dist/packem_shared/authDoColumnAdditions-B8BRbdzn.mjs +1 -0
  26. package/dist/packem_shared/compileMigrationsSql-BcvcHAqo.mjs +1 -0
  27. package/dist/packem_shared/create-auth.d-De6IOirt.d.mts +128 -0
  28. package/dist/packem_shared/create-auth.d-De6IOirt.d.ts +128 -0
  29. package/dist/packem_shared/createAuth-DRtd4q6u.mjs +1 -0
  30. package/dist/packem_shared/createDoAuthWiring-acnXUGZr.mjs +1 -0
  31. package/dist/packem_shared/createLunoraAuthClient-CedinxXU.mjs +1 -0
  32. package/dist/packem_shared/emailGateDatabaseHooks-DzBD1Qoq.mjs +1 -0
  33. package/dist/packem_shared/sessionPresets-DpEFjXKV.mjs +1 -0
  34. package/dist/packem_shared/uiConfig-BrYEFK3O.mjs +1 -0
  35. package/dist/plugins-client.d.mts +68 -1
  36. package/dist/plugins-client.d.ts +68 -1
  37. package/dist/plugins-client.mjs +1 -2
  38. package/dist/plugins-enterprise-client.d.mts +9 -0
  39. package/dist/plugins-enterprise-client.d.ts +9 -0
  40. package/dist/plugins-enterprise-client.mjs +1 -0
  41. package/dist/plugins-enterprise.d.mts +1576 -0
  42. package/dist/plugins-enterprise.d.ts +1576 -0
  43. package/dist/plugins-enterprise.mjs +1 -0
  44. package/dist/plugins.d.mts +83 -2
  45. package/dist/plugins.d.ts +83 -2
  46. package/dist/plugins.mjs +1 -22
  47. package/dist/schema.d.mts +39 -39
  48. package/dist/schema.d.ts +39 -39
  49. package/dist/schema.mjs +1 -62
  50. package/dist/sql-store.d.mts +28 -28
  51. package/dist/sql-store.d.ts +28 -28
  52. package/dist/sql-store.mjs +1 -162
  53. package/dist/store.d.mts +49 -31
  54. package/dist/store.d.ts +49 -31
  55. package/dist/store.mjs +1 -170
  56. package/dist/turnstile-middleware.d.mts +55 -55
  57. package/dist/turnstile-middleware.d.ts +55 -55
  58. package/dist/turnstile-middleware.mjs +1 -45
  59. package/dist/turnstile.d.mts +42 -59
  60. package/dist/turnstile.d.ts +42 -59
  61. package/dist/turnstile.mjs +1 -61
  62. package/package.json +38 -5
  63. package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-DjcUWEQl.mjs +0 -11
  64. package/dist/packem_shared/LunoraAuthAdminError-BxrfEeA_.mjs +0 -249
  65. package/dist/packem_shared/compileMigrationsSql-wZH3oXDu.mjs +0 -28
  66. package/dist/packem_shared/create-auth.d-M36jwG_Y.d.mts +0 -58
  67. package/dist/packem_shared/create-auth.d-M36jwG_Y.d.ts +0 -58
  68. package/dist/packem_shared/createAuth-B-tvsvQU.mjs +0 -56
  69. package/dist/packem_shared/sessionPresets-B95rXrd8.mjs +0 -35
@@ -0,0 +1,122 @@
1
+ import { Middleware } from '@lunora/server';
2
+ /**
3
+ * The resolved trust class of an email address' domain: `disposable` (a
4
+ * throwaway/temporary provider, or a caller `denyDomains` hit), `free` (a free
5
+ * consumer provider like Gmail — deliverable but not B2B), or `business`
6
+ * (anything else — a custom/company domain, or an `allowDomains` hit).
7
+ */
8
+ type EmailClass = "business" | "disposable" | "free";
9
+ /** Result of {@link classifyEmail} — the resolved class plus the normalized domain. */
10
+ interface EmailClassification {
11
+ /** The normalized (lowercased, trimmed) domain, or `undefined` for a structurally invalid address. */
12
+ domain: string | undefined;
13
+ /** The resolved trust class. A structurally invalid address resolves to `business` with `domain: undefined`. */
14
+ emailClass: EmailClass;
15
+ }
16
+ /** Configuration for {@link classifyEmail} / {@link assertEmailAllowed} / {@link emailGateMiddleware}. */
17
+ interface EmailGateConfig {
18
+ /**
19
+ * Domains that are always allowed and always classified `business`, even if
20
+ * they appear on the disposable or free lists. Wildcard/subdomain aware
21
+ * (`example.com` also allows `mail.example.com`). Your own hosted domains go
22
+ * here.
23
+ */
24
+ allowDomains?: ReadonlyArray<string>;
25
+ /**
26
+ * Reject disposable/throwaway signups. Defaults to `true` — the whole point
27
+ * of the gate. Set `false` to classify-only (surface `emailClass` without
28
+ * blocking).
29
+ */
30
+ blockDisposable?: boolean;
31
+ /**
32
+ * Extra domains to treat as disposable (blocked when `blockDisposable`), on
33
+ * top of the built-in list. Wildcard/subdomain aware.
34
+ */
35
+ denyDomains?: ReadonlyArray<string>;
36
+ /**
37
+ * Reserved for callers that branch on free-vs-business (e.g. gate a feature
38
+ * behind a business email). Purely advisory — {@link EmailClassification}'s
39
+ * `emailClass` already reports `free`, so this flag exists for symmetry/intent
40
+ * and never blocks. Defaults to `false`.
41
+ */
42
+ flagFreeEmail?: boolean;
43
+ /**
44
+ * Opt-in MX deliverability verification. Off by default because it needs DNS
45
+ * (`@visulima/email-verifier/checks/mx` → `node:dns`), which is not available
46
+ * on the default workerd path. When `true`, {@link assertEmailAllowed} rejects
47
+ * an address whose domain publishes no MX (or fallback A/AAAA) records with
48
+ * `EMAIL_UNDELIVERABLE`. The check is loaded via a dynamic import, so leaving
49
+ * this off keeps the DNS module out of the bundle entirely.
50
+ */
51
+ mx?: boolean;
52
+ /**
53
+ * Reject structurally invalid addresses up front (via
54
+ * `@visulima/email-verifier/checks/syntax`, pure-data/edge-safe). Defaults to
55
+ * `true`. When `false`, an unparseable address classifies as `business` with
56
+ * `domain: undefined` and is not rejected on syntax alone.
57
+ */
58
+ requireValidSyntax?: boolean;
59
+ }
60
+ /** Options for {@link emailGateMiddleware}: the base gate config plus how to read the email from `ctx`. */
61
+ interface EmailGateMiddlewareOptions<Context> extends EmailGateConfig {
62
+ /**
63
+ * Selector that pulls the signup email from `ctx`. The procedure context
64
+ * carries only the resolved identity, not the raw request body, so route the
65
+ * email through the function `args` and read it out here (mirrors
66
+ * `verifyTurnstileMiddleware`'s `token` selector).
67
+ */
68
+ email: (context: Context) => string | undefined;
69
+ /**
70
+ * Called with the resolved classification once the gate passes, so app policy
71
+ * can branch on `free` vs `business` (e.g. gate a plan behind a business
72
+ * email). Never fires when the gate rejects.
73
+ */
74
+ onClassify?: (classification: EmailClassification, context: Context) => void;
75
+ }
76
+ /**
77
+ * Inject the built-in disposable + free domain lists into the lookup packages,
78
+ * once. Call this at worker init on workerd (where the packages' `node:fs`
79
+ * loader is unavailable) so {@link classifyEmail} has data to match against;
80
+ * {@link assertEmailAllowed} / {@link emailGateMiddleware} await it for you, so
81
+ * the gating path is always edge-safe. Idempotent — repeat calls share one load.
82
+ */
83
+ declare const loadEmailDomainLists: () => Promise<void>;
84
+ /**
85
+ * Classify an email address' domain as `disposable` / `free` / `business`,
86
+ * pure-data and edge-safe (no DNS, no filesystem). A structurally invalid
87
+ * address resolves to `{ domain: undefined, emailClass: "business" }` — use
88
+ * {@link assertEmailAllowed} (which can reject on syntax) for the gating path.
89
+ * `allowDomains` wins over both lists; `denyDomains` adds to the disposable list.
90
+ *
91
+ * The lookup relies on the built-in domain lists being loaded. On Node they
92
+ * auto-load from disk; on workerd, `await loadEmailDomainLists()` first (or use
93
+ * {@link assertEmailAllowed} / {@link emailGateMiddleware}, which await it).
94
+ */
95
+ declare const classifyEmail: (email: string, config?: EmailGateConfig) => EmailClassification;
96
+ /**
97
+ * Classify `email` and enforce the gate, throwing a coded {@link LunoraError}
98
+ * when it fails: `VALIDATION_ERROR` (structurally invalid, only when
99
+ * `requireValidSyntax`, the default), `EMAIL_DOMAIN_BLOCKED` (disposable or
100
+ * deny-listed, only when `blockDisposable`, the default), or `EMAIL_UNDELIVERABLE`
101
+ * (no MX records, only when `mx: true`).
102
+ *
103
+ * Returns the {@link EmailClassification} on success so callers can branch on
104
+ * `free` vs `business`. Async only because of the opt-in MX step; with `mx` off
105
+ * it resolves without any network I/O.
106
+ */
107
+ declare const assertEmailAllowed: (email: string, config?: EmailGateConfig) => Promise<EmailClassification>;
108
+ /**
109
+ * Lunora procedure middleware that gates a non-auth, signup-shaped
110
+ * `mutation`/`action` on the email-domain policy. Attach it with `.use()`; it
111
+ * reads the email from `ctx` via the `email` selector (route it through `args`)
112
+ * and runs {@link assertEmailAllowed}, which throws a coded {@link LunoraError}
113
+ * (`EMAIL_DOMAIN_BLOCKED` / `EMAIL_UNDELIVERABLE` / `VALIDATION_ERROR`) the
114
+ * runtime maps to the matching status.
115
+ *
116
+ * To gate better-auth's native `/sign-up/email` endpoint instead, use
117
+ * `emailGateDatabaseHooks` / `withEmailGate` from `@lunora/auth` — those hook
118
+ * better-auth's own user-create path. The `Middleware` import is type-only, so
119
+ * this stays free of any runtime `@lunora/server` dependency.
120
+ */
121
+ declare const emailGateMiddleware: <Context>(options: EmailGateMiddlewareOptions<Context>) => Middleware<Context, Context>;
122
+ export { type EmailClass, type EmailClassification, type EmailGateConfig, type EmailGateMiddlewareOptions, assertEmailAllowed, classifyEmail, emailGateMiddleware, loadEmailDomainLists };
@@ -0,0 +1,122 @@
1
+ import { Middleware } from '@lunora/server';
2
+ /**
3
+ * The resolved trust class of an email address' domain: `disposable` (a
4
+ * throwaway/temporary provider, or a caller `denyDomains` hit), `free` (a free
5
+ * consumer provider like Gmail — deliverable but not B2B), or `business`
6
+ * (anything else — a custom/company domain, or an `allowDomains` hit).
7
+ */
8
+ type EmailClass = "business" | "disposable" | "free";
9
+ /** Result of {@link classifyEmail} — the resolved class plus the normalized domain. */
10
+ interface EmailClassification {
11
+ /** The normalized (lowercased, trimmed) domain, or `undefined` for a structurally invalid address. */
12
+ domain: string | undefined;
13
+ /** The resolved trust class. A structurally invalid address resolves to `business` with `domain: undefined`. */
14
+ emailClass: EmailClass;
15
+ }
16
+ /** Configuration for {@link classifyEmail} / {@link assertEmailAllowed} / {@link emailGateMiddleware}. */
17
+ interface EmailGateConfig {
18
+ /**
19
+ * Domains that are always allowed and always classified `business`, even if
20
+ * they appear on the disposable or free lists. Wildcard/subdomain aware
21
+ * (`example.com` also allows `mail.example.com`). Your own hosted domains go
22
+ * here.
23
+ */
24
+ allowDomains?: ReadonlyArray<string>;
25
+ /**
26
+ * Reject disposable/throwaway signups. Defaults to `true` — the whole point
27
+ * of the gate. Set `false` to classify-only (surface `emailClass` without
28
+ * blocking).
29
+ */
30
+ blockDisposable?: boolean;
31
+ /**
32
+ * Extra domains to treat as disposable (blocked when `blockDisposable`), on
33
+ * top of the built-in list. Wildcard/subdomain aware.
34
+ */
35
+ denyDomains?: ReadonlyArray<string>;
36
+ /**
37
+ * Reserved for callers that branch on free-vs-business (e.g. gate a feature
38
+ * behind a business email). Purely advisory — {@link EmailClassification}'s
39
+ * `emailClass` already reports `free`, so this flag exists for symmetry/intent
40
+ * and never blocks. Defaults to `false`.
41
+ */
42
+ flagFreeEmail?: boolean;
43
+ /**
44
+ * Opt-in MX deliverability verification. Off by default because it needs DNS
45
+ * (`@visulima/email-verifier/checks/mx` → `node:dns`), which is not available
46
+ * on the default workerd path. When `true`, {@link assertEmailAllowed} rejects
47
+ * an address whose domain publishes no MX (or fallback A/AAAA) records with
48
+ * `EMAIL_UNDELIVERABLE`. The check is loaded via a dynamic import, so leaving
49
+ * this off keeps the DNS module out of the bundle entirely.
50
+ */
51
+ mx?: boolean;
52
+ /**
53
+ * Reject structurally invalid addresses up front (via
54
+ * `@visulima/email-verifier/checks/syntax`, pure-data/edge-safe). Defaults to
55
+ * `true`. When `false`, an unparseable address classifies as `business` with
56
+ * `domain: undefined` and is not rejected on syntax alone.
57
+ */
58
+ requireValidSyntax?: boolean;
59
+ }
60
+ /** Options for {@link emailGateMiddleware}: the base gate config plus how to read the email from `ctx`. */
61
+ interface EmailGateMiddlewareOptions<Context> extends EmailGateConfig {
62
+ /**
63
+ * Selector that pulls the signup email from `ctx`. The procedure context
64
+ * carries only the resolved identity, not the raw request body, so route the
65
+ * email through the function `args` and read it out here (mirrors
66
+ * `verifyTurnstileMiddleware`'s `token` selector).
67
+ */
68
+ email: (context: Context) => string | undefined;
69
+ /**
70
+ * Called with the resolved classification once the gate passes, so app policy
71
+ * can branch on `free` vs `business` (e.g. gate a plan behind a business
72
+ * email). Never fires when the gate rejects.
73
+ */
74
+ onClassify?: (classification: EmailClassification, context: Context) => void;
75
+ }
76
+ /**
77
+ * Inject the built-in disposable + free domain lists into the lookup packages,
78
+ * once. Call this at worker init on workerd (where the packages' `node:fs`
79
+ * loader is unavailable) so {@link classifyEmail} has data to match against;
80
+ * {@link assertEmailAllowed} / {@link emailGateMiddleware} await it for you, so
81
+ * the gating path is always edge-safe. Idempotent — repeat calls share one load.
82
+ */
83
+ declare const loadEmailDomainLists: () => Promise<void>;
84
+ /**
85
+ * Classify an email address' domain as `disposable` / `free` / `business`,
86
+ * pure-data and edge-safe (no DNS, no filesystem). A structurally invalid
87
+ * address resolves to `{ domain: undefined, emailClass: "business" }` — use
88
+ * {@link assertEmailAllowed} (which can reject on syntax) for the gating path.
89
+ * `allowDomains` wins over both lists; `denyDomains` adds to the disposable list.
90
+ *
91
+ * The lookup relies on the built-in domain lists being loaded. On Node they
92
+ * auto-load from disk; on workerd, `await loadEmailDomainLists()` first (or use
93
+ * {@link assertEmailAllowed} / {@link emailGateMiddleware}, which await it).
94
+ */
95
+ declare const classifyEmail: (email: string, config?: EmailGateConfig) => EmailClassification;
96
+ /**
97
+ * Classify `email` and enforce the gate, throwing a coded {@link LunoraError}
98
+ * when it fails: `VALIDATION_ERROR` (structurally invalid, only when
99
+ * `requireValidSyntax`, the default), `EMAIL_DOMAIN_BLOCKED` (disposable or
100
+ * deny-listed, only when `blockDisposable`, the default), or `EMAIL_UNDELIVERABLE`
101
+ * (no MX records, only when `mx: true`).
102
+ *
103
+ * Returns the {@link EmailClassification} on success so callers can branch on
104
+ * `free` vs `business`. Async only because of the opt-in MX step; with `mx` off
105
+ * it resolves without any network I/O.
106
+ */
107
+ declare const assertEmailAllowed: (email: string, config?: EmailGateConfig) => Promise<EmailClassification>;
108
+ /**
109
+ * Lunora procedure middleware that gates a non-auth, signup-shaped
110
+ * `mutation`/`action` on the email-domain policy. Attach it with `.use()`; it
111
+ * reads the email from `ctx` via the `email` selector (route it through `args`)
112
+ * and runs {@link assertEmailAllowed}, which throws a coded {@link LunoraError}
113
+ * (`EMAIL_DOMAIN_BLOCKED` / `EMAIL_UNDELIVERABLE` / `VALIDATION_ERROR`) the
114
+ * runtime maps to the matching status.
115
+ *
116
+ * To gate better-auth's native `/sign-up/email` endpoint instead, use
117
+ * `emailGateDatabaseHooks` / `withEmailGate` from `@lunora/auth` — those hook
118
+ * better-auth's own user-create path. The `Middleware` import is type-only, so
119
+ * this stays free of any runtime `@lunora/server` dependency.
120
+ */
121
+ declare const emailGateMiddleware: <Context>(options: EmailGateMiddlewareOptions<Context>) => Middleware<Context, Context>;
122
+ export { type EmailClass, type EmailClassification, type EmailGateConfig, type EmailGateMiddlewareOptions, assertEmailAllowed, classifyEmail, emailGateMiddleware, loadEmailDomainLists };
@@ -0,0 +1 @@
1
+ import{LunoraError as e}from"@lunora/errors";import{isDisposableDomain as m,setDomains as d}from"@visulima/disposable-email-domains";import{extractDomain as c,isFreeDomain as w,setDomains as u}from"@visulima/free-email-domains";const t=a=>{const i=a.default??a;return Array.isArray(i)?i:[]};let n;const D=async()=>(n??=(async()=>{const[a,i]=await Promise.all([import("@visulima/disposable-email-domains/domains"),import("@visulima/free-email-domains/domains")]);d(t(a)),u(t(i))})(),n),r=a=>a&&a.length>0?new Set(a.map(i=>i.toLowerCase())):void 0,f=(a,i={})=>{const o=c(a);if(o===void 0)return{domain:void 0,emailClass:"business"};const s=r(i.allowDomains);return m(o,{allowDomains:s,customDomains:r(i.denyDomains)})?{domain:o,emailClass:"disposable"}:w(o,{allowDomains:s})?{domain:o,emailClass:"free"}:{domain:o,emailClass:"business"}},p=async a=>{const{checkMxRecords:i}=await import("@visulima/email-verifier/checks/mx");return(await i(a)).valid},h=async(a,i={})=>{if(i.requireValidSyntax!==!1){const{validateSyntax:s}=await import("@visulima/email-verifier/checks/syntax");if(!s(a))throw new e("VALIDATION_ERROR",`@lunora/auth: "${a}" is not a valid email address.`)}await D();const o=f(a,i);if(o.emailClass==="disposable"&&i.blockDisposable!==!1)throw new e("EMAIL_DOMAIN_BLOCKED",`@lunora/auth: signups from the disposable/throwaway domain "${o.domain??a}" are not allowed.`);if(i.mx===!0&&o.domain!==void 0&&!await p(o.domain))throw new e("EMAIL_UNDELIVERABLE",`@lunora/auth: the domain "${o.domain}" publishes no MX records, so mail to it cannot be delivered.`);return o},b=a=>async({ctx:i,next:o})=>{const s=a.email(i);if(s===void 0||s==="")throw new e("VALIDATION_ERROR","@lunora/auth: emailGateMiddleware received no email to check.");const l=await h(s,a);return a.onClassify?.(l,i),o()};export{h as assertEmailAllowed,f as classifyEmail,b as emailGateMiddleware,D as loadEmailDomainLists};