@lunora/auth 1.0.0-alpha.37 → 1.0.0-alpha.38

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 (35) hide show
  1. package/dist/adapter.mjs +1 -48
  2. package/dist/audit.mjs +2 -102
  3. package/dist/email-guard.mjs +1 -71
  4. package/dist/index.d.mts +2 -2
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.mjs +1 -16
  7. package/dist/middleware.d.mts +1 -1
  8. package/dist/middleware.d.ts +1 -1
  9. package/dist/middleware.mjs +1 -56
  10. package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-kIwlEt8i.mjs +1 -0
  11. package/dist/packem_shared/LunoraAuthAdminError-BiNYZM9j.mjs +1 -0
  12. package/dist/packem_shared/authAuditHook-Dx3sqf3G.mjs +1 -0
  13. package/dist/packem_shared/compileMigrationsSql-ChiudSmt.mjs +1 -0
  14. package/dist/packem_shared/{create-auth.d-COcIS_KU.d.mts → create-auth.d-De6IOirt.d.mts} +1 -1
  15. package/dist/packem_shared/{create-auth.d-COcIS_KU.d.ts → create-auth.d-De6IOirt.d.ts} +1 -1
  16. package/dist/packem_shared/createAuth-DS6PL8Mb.mjs +1 -0
  17. package/dist/packem_shared/emailGateDatabaseHooks-DzBD1Qoq.mjs +1 -0
  18. package/dist/packem_shared/sessionPresets-DpEFjXKV.mjs +1 -0
  19. package/dist/plugins-client.mjs +1 -2
  20. package/dist/plugins.mjs +1 -22
  21. package/dist/schema.d.mts +1 -1
  22. package/dist/schema.d.ts +1 -1
  23. package/dist/schema.mjs +1 -62
  24. package/dist/sql-store.mjs +1 -184
  25. package/dist/store.mjs +1 -183
  26. package/dist/turnstile-middleware.mjs +1 -34
  27. package/dist/turnstile.mjs +1 -59
  28. package/package.json +2 -2
  29. package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-DjcUWEQl.mjs +0 -11
  30. package/dist/packem_shared/LunoraAuthAdminError-CReJPMkx.mjs +0 -513
  31. package/dist/packem_shared/authAuditHook-3OJKhpQV.mjs +0 -119
  32. package/dist/packem_shared/compileMigrationsSql-Dl5N8z5q.mjs +0 -29
  33. package/dist/packem_shared/createAuth-s4i7WhAh.mjs +0 -72
  34. package/dist/packem_shared/emailGateDatabaseHooks-BGS4uJM9.mjs +0 -64
  35. package/dist/packem_shared/sessionPresets-Dwwd74_J.mjs +0 -38
package/dist/adapter.mjs CHANGED
@@ -1,48 +1 @@
1
- import { createAdapterFactory } from 'better-auth/adapters';
2
- import { createSqlAuthStore, d1Executor } from './sql-store.mjs';
3
-
4
- const asRow = (row) => row;
5
- const asRowOrNull = (row) => row ?? null;
6
- const asRows = (rows) => rows;
7
- const lunoraAuthAdapter = (store) => createAdapterFactory({
8
- adapter: () => {
9
- return {
10
- consumeOne: async ({ model, where }) => asRowOrNull(await store.consumeOne(model, where)),
11
- count: async ({ model, where }) => store.count(model, where ?? []),
12
- create: async ({ data, model }) => asRow(await store.create(model, data)),
13
- delete: async ({ model, where }) => {
14
- await store.remove(model, where);
15
- },
16
- deleteMany: async ({ model, where }) => store.remove(model, where),
17
- findMany: async ({ limit, model, offset, sortBy, where }) => asRows(await store.read(model, { limit, offset, sortBy, where: where ?? [] })),
18
- findOne: async ({ model, where }) => {
19
- const [row] = await store.read(model, { limit: 1, where });
20
- return asRowOrNull(row);
21
- },
22
- incrementOne: async ({ increment, model, set, where }) => asRowOrNull(await store.incrementOne(model, where, increment, set)),
23
- update: async ({ model, update, where }) => {
24
- const [row] = await store.update(model, where, update);
25
- return asRowOrNull(row);
26
- },
27
- updateMany: async ({ model, update, where }) => {
28
- const updated = await store.update(model, where, update);
29
- return updated.length;
30
- }
31
- };
32
- },
33
- config: {
34
- adapterId: "lunora",
35
- adapterName: "Lunora Adapter",
36
- // Conservative flags so the adapter is store-agnostic: better-auth
37
- // serializes dates/booleans/json to primitives (string/number) before a
38
- // write and parses them back after a read, so a store — in-memory or
39
- // SQL — only ever handles primitives, never schema-aware codecs.
40
- supportsBooleans: false,
41
- supportsDates: false,
42
- supportsJSON: false,
43
- supportsNumericIds: false
44
- }
45
- });
46
- const lunoraD1Adapter = (d1) => lunoraAuthAdapter(createSqlAuthStore(d1Executor(d1)));
47
-
48
- export { lunoraAuthAdapter, lunoraD1Adapter };
1
+ import{createAdapterFactory as d}from"better-auth/adapters";import{createSqlAuthStore as c,d1Executor as l}from"./sql-store.mjs";const m=a=>a,o=a=>a??null,u=a=>a,i=a=>d({adapter:()=>({consumeOne:async({model:e,where:t})=>o(await a.consumeOne(e,t)),count:async({model:e,where:t})=>a.count(e,t??[]),create:async({data:e,model:t})=>m(await a.create(t,e)),delete:async({model:e,where:t})=>{await a.remove(e,t)},deleteMany:async({model:e,where:t})=>a.remove(e,t),findMany:async({limit:e,model:t,offset:r,sortBy:n,where:s})=>u(await a.read(t,{limit:e,offset:r,sortBy:n,where:s??[]})),findOne:async({model:e,where:t})=>{const[r]=await a.read(e,{limit:1,where:t});return o(r)},incrementOne:async({increment:e,model:t,set:r,where:n})=>o(await a.incrementOne(t,n,e,r)),update:async({model:e,update:t,where:r})=>{const[n]=await a.update(e,r,t);return o(n)},updateMany:async({model:e,update:t,where:r})=>(await a.update(e,r,t)).length}),config:{adapterId:"lunora",adapterName:"Lunora Adapter",supportsBooleans:!1,supportsDates:!1,supportsJSON:!1,supportsNumericIds:!1}}),y=a=>i(c(l(a)));export{i as lunoraAuthAdapter,y as lunoraD1Adapter};
package/dist/audit.mjs CHANGED
@@ -1,22 +1,4 @@
1
- import { redact, standardRules, piiRules } from '@visulima/redact';
2
-
3
- const AUTH_AUDIT_TABLE = "__lunora_auth_audit__";
4
- const AUDIT_REDACT_RULES = [...standardRules, ...piiRules];
5
- const DEFAULT_READ_LIMIT = 1e3;
6
- const MAX_READ_LIMIT = 1e4;
7
- const SQL_NULL = null;
8
- const text = (value) => {
9
- if (typeof value === "string") {
10
- return value;
11
- }
12
- if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
13
- return String(value);
14
- }
15
- return void 0;
16
- };
17
- const ensureAuthAuditTable = async (executor) => {
18
- await executor.run(
19
- `CREATE TABLE IF NOT EXISTS "${AUTH_AUDIT_TABLE}" (
1
+ import{redact as A,standardRules as _,piiRules as v}from"@visulima/redact";const n="__lunora_auth_audit__",R=[..._,...v],N=1e3,L=1e4,u=null,s=t=>{if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="bigint"||typeof t=="boolean")return String(t)},m=async t=>{await t.run(`CREATE TABLE IF NOT EXISTS "${n}" (
20
2
  seq INTEGER PRIMARY KEY AUTOINCREMENT,
21
3
  ts REAL NOT NULL,
22
4
  event TEXT NOT NULL,
@@ -26,86 +8,4 @@ const ensureAuthAuditTable = async (executor) => {
26
8
  ip TEXT,
27
9
  user_agent TEXT,
28
10
  detail TEXT
29
- )`,
30
- []
31
- );
32
- };
33
- const appendAuthAuditEntry = async (executor, entry, options = {}) => {
34
- await ensureAuthAuditTable(executor);
35
- let detail;
36
- if (entry.detail !== void 0) {
37
- detail = options.redactDetail === false ? entry.detail : redact(entry.detail, AUDIT_REDACT_RULES);
38
- }
39
- await executor.run(
40
- `INSERT INTO "${AUTH_AUDIT_TABLE}" (ts, event, outcome, actor_id, actor_email, ip, user_agent, detail) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
41
- [
42
- entry.ts,
43
- entry.event,
44
- entry.outcome,
45
- entry.actorId ?? SQL_NULL,
46
- entry.actorEmail ?? SQL_NULL,
47
- entry.ip ?? SQL_NULL,
48
- entry.userAgent ?? SQL_NULL,
49
- detail === void 0 ? SQL_NULL : JSON.stringify(detail)
50
- ]
51
- );
52
- if (typeof options.retention === "number" && options.retention > 0) {
53
- await executor.run(`DELETE FROM "${AUTH_AUDIT_TABLE}" WHERE seq <= (SELECT MAX(seq) - ? FROM "${AUTH_AUDIT_TABLE}")`, [options.retention]);
54
- }
55
- return { ...entry, detail };
56
- };
57
- const readAuthAuditLog = async (executor, options = {}) => {
58
- await ensureAuthAuditTable(executor);
59
- const limit = Math.max(1, Math.min(options.limit ?? DEFAULT_READ_LIMIT, MAX_READ_LIMIT));
60
- const clauses = ["seq > ?"];
61
- const parameters = [options.sinceSeq ?? 0];
62
- if (options.actorId !== void 0) {
63
- clauses.push("actor_id = ?");
64
- parameters.push(options.actorId);
65
- }
66
- if (options.event !== void 0) {
67
- clauses.push("event = ?");
68
- parameters.push(options.event);
69
- }
70
- parameters.push(limit);
71
- const rows = await executor.all(
72
- `SELECT seq, ts, event, outcome, actor_id, actor_email, ip, user_agent, detail FROM "${AUTH_AUDIT_TABLE}" WHERE ${clauses.join(" AND ")} ORDER BY seq DESC LIMIT ?`,
73
- parameters
74
- );
75
- return rows.map((row) => {
76
- const base = {
77
- event: text(row["event"]) ?? "",
78
- outcome: row["outcome"] === "failure" ? "failure" : "success",
79
- seq: Number(row["seq"]),
80
- ts: Number(row["ts"])
81
- };
82
- const actorId = text(row["actor_id"]);
83
- const actorEmail = text(row["actor_email"]);
84
- const ip = text(row["ip"]);
85
- const userAgent = text(row["user_agent"]);
86
- const detail = text(row["detail"]);
87
- if (actorId !== void 0) {
88
- base.actorId = actorId;
89
- }
90
- if (actorEmail !== void 0) {
91
- base.actorEmail = actorEmail;
92
- }
93
- if (ip !== void 0) {
94
- base.ip = ip;
95
- }
96
- if (userAgent !== void 0) {
97
- base.userAgent = userAgent;
98
- }
99
- if (detail !== void 0) {
100
- base.detail = JSON.parse(detail);
101
- }
102
- return base;
103
- });
104
- };
105
- const createAuthAuditReader = (executor) => {
106
- return {
107
- read: (options) => readAuthAuditLog(executor, options)
108
- };
109
- };
110
-
111
- export { AUTH_AUDIT_TABLE, appendAuthAuditEntry, createAuthAuditReader, ensureAuthAuditTable, readAuthAuditLog };
11
+ )`,[])},O=async(t,e,r={})=>{await m(t);let i;return e.detail!==void 0&&(i=r.redactDetail===!1?e.detail:A(e.detail,R)),await t.run(`INSERT INTO "${n}" (ts, event, outcome, actor_id, actor_email, ip, user_agent, detail) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,[e.ts,e.event,e.outcome,e.actorId??u,e.actorEmail??u,e.ip??u,e.userAgent??u,i===void 0?u:JSON.stringify(i)]),typeof r.retention=="number"&&r.retention>0&&await t.run(`DELETE FROM "${n}" WHERE seq <= (SELECT MAX(seq) - ? FROM "${n}")`,[r.retention]),{...e,detail:i}},I=async(t,e={})=>{await m(t);const r=Math.max(1,Math.min(e.limit??N,L)),i=["seq > ?"],d=[e.sinceSeq??0];return e.actorId!==void 0&&(i.push("actor_id = ?"),d.push(e.actorId)),e.event!==void 0&&(i.push("event = ?"),d.push(e.event)),d.push(r),(await t.all(`SELECT seq, ts, event, outcome, actor_id, actor_email, ip, user_agent, detail FROM "${n}" WHERE ${i.join(" AND ")} ORDER BY seq DESC LIMIT ?`,d)).map(a=>{const o={event:s(a.event)??"",outcome:a.outcome==="failure"?"failure":"success",seq:Number(a.seq),ts:Number(a.ts)},E=s(a.actor_id),c=s(a.actor_email),T=s(a.ip),l=s(a.user_agent),p=s(a.detail);return E!==void 0&&(o.actorId=E),c!==void 0&&(o.actorEmail=c),T!==void 0&&(o.ip=T),l!==void 0&&(o.userAgent=l),p!==void 0&&(o.detail=JSON.parse(p)),o})},h=t=>({read:e=>I(t,e)});export{n as AUTH_AUDIT_TABLE,O as appendAuthAuditEntry,h as createAuthAuditReader,m as ensureAuthAuditTable,I as readAuthAuditLog};
@@ -1,71 +1 @@
1
- import { LunoraError } from '@lunora/errors';
2
- import { isDisposableDomain, setDomains } from '@visulima/disposable-email-domains';
3
- import { extractDomain, isFreeDomain, setDomains as setDomains$1 } from '@visulima/free-email-domains';
4
-
5
- const listFromModule = (module) => {
6
- const value = module.default ?? module;
7
- return Array.isArray(value) ? value : [];
8
- };
9
- let listsPromise;
10
- const loadEmailDomainLists = async () => {
11
- listsPromise ??= (async () => {
12
- const [disposable, free] = await Promise.all([import('@visulima/disposable-email-domains/domains'), import('@visulima/free-email-domains/domains')]);
13
- setDomains(listFromModule(disposable));
14
- setDomains$1(listFromModule(free));
15
- })();
16
- return listsPromise;
17
- };
18
- const toDomainSet = (domains) => domains && domains.length > 0 ? new Set(domains.map((domain) => domain.toLowerCase())) : void 0;
19
- const classifyEmail = (email, config = {}) => {
20
- const domain = extractDomain(email);
21
- if (domain === void 0) {
22
- return { domain: void 0, emailClass: "business" };
23
- }
24
- const allowDomains = toDomainSet(config.allowDomains);
25
- if (isDisposableDomain(domain, { allowDomains, customDomains: toDomainSet(config.denyDomains) })) {
26
- return { domain, emailClass: "disposable" };
27
- }
28
- if (isFreeDomain(domain, { allowDomains })) {
29
- return { domain, emailClass: "free" };
30
- }
31
- return { domain, emailClass: "business" };
32
- };
33
- const verifyMx = async (domain) => {
34
- const { checkMxRecords } = await import('@visulima/email-verifier/checks/mx');
35
- const result = await checkMxRecords(domain);
36
- return result.valid;
37
- };
38
- const assertEmailAllowed = async (email, config = {}) => {
39
- if (config.requireValidSyntax !== false) {
40
- const { validateSyntax } = await import('@visulima/email-verifier/checks/syntax');
41
- if (!validateSyntax(email)) {
42
- throw new LunoraError("VALIDATION_ERROR", `@lunora/auth: "${email}" is not a valid email address.`);
43
- }
44
- }
45
- await loadEmailDomainLists();
46
- const classification = classifyEmail(email, config);
47
- if (classification.emailClass === "disposable" && config.blockDisposable !== false) {
48
- throw new LunoraError(
49
- "EMAIL_DOMAIN_BLOCKED",
50
- `@lunora/auth: signups from the disposable/throwaway domain "${classification.domain ?? email}" are not allowed.`
51
- );
52
- }
53
- if (config.mx === true && classification.domain !== void 0 && !await verifyMx(classification.domain)) {
54
- throw new LunoraError(
55
- "EMAIL_UNDELIVERABLE",
56
- `@lunora/auth: the domain "${classification.domain}" publishes no MX records, so mail to it cannot be delivered.`
57
- );
58
- }
59
- return classification;
60
- };
61
- const emailGateMiddleware = (options) => async ({ ctx, next }) => {
62
- const email = options.email(ctx);
63
- if (email === void 0 || email === "") {
64
- throw new LunoraError("VALIDATION_ERROR", "@lunora/auth: emailGateMiddleware received no email to check.");
65
- }
66
- const classification = await assertEmailAllowed(email, options);
67
- options.onClassify?.(classification, ctx);
68
- return next();
69
- };
70
-
71
- export { assertEmailAllowed, classifyEmail, emailGateMiddleware, loadEmailDomainLists };
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};
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { lunoraAuthAdapter, lunoraD1Adapter } from "./adapter.mjs";
2
2
  import { LunoraError } from '@lunora/errors';
3
- import { L as LunoraAuth, a as LunoraAuthOptions } from "./packem_shared/create-auth.d-COcIS_KU.mjs";
4
- export { c as createAuth, r as resolveAuthOptions } from "./packem_shared/create-auth.d-COcIS_KU.mjs";
3
+ import { L as LunoraAuth, a as LunoraAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.mjs";
4
+ export { c as createAuth, r as resolveAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.mjs";
5
5
  import { AppendAuthAuditEntry, AppendAuthAuditOptions, AuthAuditEvent } from "./audit.mjs";
6
6
  export { AUTH_AUDIT_TABLE, type AuthAuditEntry, type AuthAuditOutcome, type AuthAuditReader, type ReadAuthAuditOptions, appendAuthAuditEntry, createAuthAuditReader, ensureAuthAuditTable, readAuthAuditLog } from "./audit.mjs";
7
7
  import { createAuthMiddleware } from 'better-auth/api';
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { lunoraAuthAdapter, lunoraD1Adapter } from "./adapter.js";
2
2
  import { LunoraError } from '@lunora/errors';
3
- import { L as LunoraAuth, a as LunoraAuthOptions } from "./packem_shared/create-auth.d-COcIS_KU.js";
4
- export { c as createAuth, r as resolveAuthOptions } from "./packem_shared/create-auth.d-COcIS_KU.js";
3
+ import { L as LunoraAuth, a as LunoraAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.js";
4
+ export { c as createAuth, r as resolveAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.js";
5
5
  import { AppendAuthAuditEntry, AppendAuthAuditOptions, AuthAuditEvent } from "./audit.js";
6
6
  export { AUTH_AUDIT_TABLE, type AuthAuditEntry, type AuthAuditOutcome, type AuthAuditReader, type ReadAuthAuditOptions, appendAuthAuditEntry, createAuthAuditReader, ensureAuthAuditTable, readAuthAuditLog } from "./audit.js";
7
7
  import { createAuthMiddleware } from 'better-auth/api';
package/dist/index.mjs CHANGED
@@ -1,16 +1 @@
1
- export { lunoraAuthAdapter, lunoraD1Adapter } from './adapter.mjs';
2
- export { LunoraAuthAdminError, createAuthAdmin } from './packem_shared/LunoraAuthAdminError-CReJPMkx.mjs';
3
- export { AUTH_AUDIT_TABLE, appendAuthAuditEntry, createAuthAuditReader, ensureAuthAuditTable, readAuthAuditLog } from './audit.mjs';
4
- export { authAuditHook, buildAuditEntry, eventForPath, withAuthAudit } from './packem_shared/authAuditHook-3OJKhpQV.mjs';
5
- export { createAuth, resolveAuthOptions } from './packem_shared/createAuth-s4i7WhAh.mjs';
6
- export { emailGateDatabaseHooks, withEmailGate } from './packem_shared/emailGateDatabaseHooks-BGS4uJM9.mjs';
7
- export { assertEmailAllowed, classifyEmail, emailGateMiddleware, loadEmailDomainLists } from './email-guard.mjs';
8
- export { DEFAULT_AUTH_BASE_PATH, handleAuthRequest } from './packem_shared/DEFAULT_AUTH_BASE_PATH-DjcUWEQl.mjs';
9
- export { LunoraAuthHeadersError, withAuthPlugins } from './middleware.mjs';
10
- export { compileMigrationsSql, ensureMigrated } from './packem_shared/compileMigrationsSql-Dl5N8z5q.mjs';
11
- export { default as authTables } from './schema.mjs';
12
- export { sessionPresets, validateSessionPolicy } from './packem_shared/sessionPresets-Dwwd74_J.mjs';
13
- export { createSqlAuthStore, d1Executor } from './sql-store.mjs';
14
- export { createMemoryAuthStore, matchesWhere } from './store.mjs';
15
- export { TURNSTILE_VERIFY_ENDPOINT, verifyTurnstile } from './turnstile.mjs';
16
- export { verifyTurnstileMiddleware } from './turnstile-middleware.mjs';
1
+ import{lunoraAuthAdapter as t,lunoraD1Adapter as o}from"./adapter.mjs";import{LunoraAuthAdminError as i,createAuthAdmin as u}from"./packem_shared/LunoraAuthAdminError-BiNYZM9j.mjs";import{AUTH_AUDIT_TABLE as l,appendAuthAuditEntry as m,createAuthAuditReader as s,ensureAuthAuditTable as d,readAuthAuditLog as h}from"./audit.mjs";import{authAuditHook as n,buildAuditEntry as f,eventForPath as x,withAuthAudit as E}from"./packem_shared/authAuditHook-Dx3sqf3G.mjs";import{createAuth as c,resolveAuthOptions as y}from"./packem_shared/createAuth-DS6PL8Mb.mjs";import{emailGateDatabaseHooks as S,withEmailGate as _}from"./packem_shared/emailGateDatabaseHooks-DzBD1Qoq.mjs";import{assertEmailAllowed as D,classifyEmail as H,emailGateMiddleware as P,loadEmailDomainLists as v}from"./email-guard.mjs";import{DEFAULT_AUTH_BASE_PATH as U,handleAuthRequest as b}from"./packem_shared/DEFAULT_AUTH_BASE_PATH-kIwlEt8i.mjs";import{LunoraAuthHeadersError as I,withAuthPlugins as R}from"./middleware.mjs";import{compileMigrationsSql as F,ensureMigrated as G}from"./packem_shared/compileMigrationsSql-ChiudSmt.mjs";import{default as k}from"./schema.mjs";import{sessionPresets as O,validateSessionPolicy as V}from"./packem_shared/sessionPresets-DpEFjXKV.mjs";import{createSqlAuthStore as Y,d1Executor as j}from"./sql-store.mjs";import{createMemoryAuthStore as C,matchesWhere as J}from"./store.mjs";import{TURNSTILE_VERIFY_ENDPOINT as Q,verifyTurnstile as X}from"./turnstile.mjs";import{verifyTurnstileMiddleware as $}from"./turnstile-middleware.mjs";export{l as AUTH_AUDIT_TABLE,U as DEFAULT_AUTH_BASE_PATH,i as LunoraAuthAdminError,I as LunoraAuthHeadersError,Q as TURNSTILE_VERIFY_ENDPOINT,m as appendAuthAuditEntry,D as assertEmailAllowed,n as authAuditHook,k as authTables,f as buildAuditEntry,H as classifyEmail,F as compileMigrationsSql,c as createAuth,u as createAuthAdmin,s as createAuthAuditReader,C as createMemoryAuthStore,Y as createSqlAuthStore,j as d1Executor,S as emailGateDatabaseHooks,P as emailGateMiddleware,d as ensureAuthAuditTable,G as ensureMigrated,x as eventForPath,b as handleAuthRequest,v as loadEmailDomainLists,t as lunoraAuthAdapter,o as lunoraD1Adapter,J as matchesWhere,h as readAuthAuditLog,y as resolveAuthOptions,O as sessionPresets,V as validateSessionPolicy,X as verifyTurnstile,$ as verifyTurnstileMiddleware,E as withAuthAudit,R as withAuthPlugins,_ as withEmailGate};
@@ -1,5 +1,5 @@
1
1
  import { LunoraError } from '@lunora/errors';
2
- import { L as LunoraAuth } from "./packem_shared/create-auth.d-COcIS_KU.mjs";
2
+ import { L as LunoraAuth } from "./packem_shared/create-auth.d-De6IOirt.mjs";
3
3
  import 'better-auth';
4
4
  /**
5
5
  * Structural mirror of `@lunora/server`'s `MiddlewareNext` — the continuation
@@ -1,5 +1,5 @@
1
1
  import { LunoraError } from '@lunora/errors';
2
- import { L as LunoraAuth } from "./packem_shared/create-auth.d-COcIS_KU.js";
2
+ import { L as LunoraAuth } from "./packem_shared/create-auth.d-De6IOirt.js";
3
3
  import 'better-auth';
4
4
  /**
5
5
  * Structural mirror of `@lunora/server`'s `MiddlewareNext` — the continuation
@@ -1,56 +1 @@
1
- import { LunoraError } from '@lunora/errors';
2
-
3
- const callHasHeaders = (argument) => {
4
- if (argument === void 0) {
5
- return false;
6
- }
7
- if (typeof argument !== "object" || argument === null) {
8
- return true;
9
- }
10
- const { headers } = argument;
11
- return headers !== void 0 && headers !== null;
12
- };
13
- const guardAuthApi = (api) => {
14
- const withoutHeaders = () => api;
15
- return /* @__PURE__ */ new Proxy(api, {
16
- // eslint-disable-next-line sonarjs/function-return-type -- a Proxy `get` trap is intrinsically polymorphic: it returns the synthetic `withoutHeaders`, the guarded endpoint wrapper, or any passthrough property value
17
- get(target, property, receiver) {
18
- if (property === "withoutHeaders" && !(property in target)) {
19
- return withoutHeaders;
20
- }
21
- const value = Reflect.get(target, property, receiver);
22
- if (typeof value !== "function" || typeof property !== "string") {
23
- return value;
24
- }
25
- const method = property;
26
- return (...arguments_) => {
27
- if (!callHasHeaders(arguments_[0])) {
28
- return Promise.reject(new LunoraAuthHeadersError(method));
29
- }
30
- return Reflect.apply(value, target, arguments_);
31
- };
32
- }
33
- });
34
- };
35
- class LunoraAuthHeadersError extends LunoraError {
36
- /** The `ctx.authApi.&lt;method>` that was called without `headers`. */
37
- method;
38
- constructor(method) {
39
- super(
40
- "AUTH_HEADERS_MISSING",
41
- `@lunora/auth: ctx.authApi.${method}(…) was called without \`headers\`. better-auth treats a header-less call as a trusted server-to-server invocation and skips session authorization entirely — an authorization bypass. Pass the inbound request headers: ctx.authApi.${method}({ body, headers: request.headers }). If you genuinely intend an unauthenticated server-to-server call, opt out explicitly via ctx.authApi.withoutHeaders().<method>(…), or disable the guard for the whole middleware with withAuthPlugins(auth, { enforceHeaders: false }).`,
42
- { name: "LunoraAuthHeadersError" }
43
- );
44
- this.method = method;
45
- }
46
- }
47
- const withAuthPlugins = (auth, options = {}) => {
48
- const enforceHeaders = options.enforceHeaders ?? true;
49
- const authApi = enforceHeaders ? guardAuthApi(auth.api) : auth.api;
50
- return async ({ next }) => {
51
- const extended = await next({ ctx: { authApi } });
52
- return extended;
53
- };
54
- };
55
-
56
- export { LunoraAuthHeadersError, withAuthPlugins };
1
+ import{LunoraError as i}from"@lunora/errors";const h=e=>{if(e===void 0)return!1;if(typeof e!="object"||e===null)return!0;const{headers:t}=e;return t!=null},d=e=>{const t=()=>e;return new Proxy(e,{get(a,r,o){if(r==="withoutHeaders"&&!(r in a))return t;const s=Reflect.get(a,r,o);if(typeof s!="function"||typeof r!="string")return s;const u=r;return(...n)=>h(n[0])?Reflect.apply(s,a,n):Promise.reject(new c(u))}})};class c extends i{method;constructor(t){super("AUTH_HEADERS_MISSING",`@lunora/auth: ctx.authApi.${t}(…) was called without \`headers\`. better-auth treats a header-less call as a trusted server-to-server invocation and skips session authorization entirely — an authorization bypass. Pass the inbound request headers: ctx.authApi.${t}({ body, headers: request.headers }). If you genuinely intend an unauthenticated server-to-server call, opt out explicitly via ctx.authApi.withoutHeaders().<method>(…), or disable the guard for the whole middleware with withAuthPlugins(auth, { enforceHeaders: false }).`,{name:"LunoraAuthHeadersError"}),this.method=t}}const p=(e,t={})=>{const a=t.enforceHeaders??!0?d(e.api):e.api;return async({next:r})=>await r({ctx:{authApi:a}})};export{c as LunoraAuthHeadersError,p as withAuthPlugins};
@@ -0,0 +1 @@
1
+ const s="/api/auth",r=async(h,t,n=s)=>{const a=new URL(t.url),e=n.endsWith("/")?n.slice(0,-1):n;if(!(a.pathname!==e&&!a.pathname.startsWith(`${e}/`)))return h.handler(t)};export{s as DEFAULT_AUTH_BASE_PATH,r as handleAuthRequest};
@@ -0,0 +1 @@
1
+ import{LunoraError as S}from"@lunora/errors";import{getAuthTables as A}from"better-auth/db";class p extends S{constructor(u,w){super(w,u,{name:"LunoraAuthAdminError"})}}const O=50,M=500,z=3600,R=z*24,D=100*365*24*60*60,U=2880*60*1e3,N=new Set(["accessToken","backupCodes","idToken","password","publicKey","refreshToken","secret","token"]),k=s=>Math.min(Math.max(Math.trunc(s??O),1),M),T=s=>Math.max(0,Math.trunc(s??0)),c=s=>{const u={};for(const[w,m]of Object.entries(s))N.has(w)||(u[w]=m instanceof Date?m.getTime():m);return u},I=s=>Array.isArray(s)?s.join(","):s,b=s=>s.toLowerCase().replaceAll(/[^\da-z]+/g,"-").replaceAll(/^-|-$/g,""),x=new Set(["banExpires","banned","banReason","createdAt","email","emailVerified","id","name","role","updatedAt"]),E={displayUsername:"username",phoneNumber:"phone-number",phoneNumberVerified:"phone-number",username:"username"},_=s=>s==="boolean"?"boolean":s==="date"?"date":s==="number"?"number":"string",L=s=>{const u=[];for(const[w,m]of Object.entries(s))m.input===!1||m.references!==void 0||x.has(w)||u.push({name:w,plugin:E[w],required:m.required===!0,type:_(m.type),unique:m.unique===!0});return u},P=s=>{if(s instanceof p)return s;const u=s,w=u?.body?.code??u?.code??"AUTH_ADMIN_ERROR",m=u?.body?.message??u?.message??"auth admin operation failed";return new p(m,w)},F=(s,u={})=>{const w=s.$context,m=u.features??{},y=e=>{const a=new Set((e.plugins??[]).map(i=>i.id)),t=i=>a.has(i);return{accounts:m.accounts??!0,admin:m.admin??t("admin"),organization:m.organization??t("organization"),passkey:m.passkey??t("passkey"),twoFactor:m.twoFactor??t("two-factor")}},r=async e=>{try{return await e(await w)}catch(a){throw P(a)}},h=e=>c(e),g=async(e,a,t)=>{const i=t.where&&t.where.length>0?t.where:void 0,[n,o]=await Promise.all([e.adapter.findMany({limit:k(t.limit),model:a,offset:T(t.offset),sortBy:t.sortBy,where:i}),e.adapter.count({model:a,where:i})]);return{rows:n.map(d=>c(d)),total:o}};return{banUser:({expiresInSeconds:e,reason:a,userId:t})=>r(async i=>{let n=null;if(e!==void 0){if(!Number.isInteger(e)||e<=0)throw new p("expiresInSeconds must be a positive finite integer","INVALID_BAN_SECONDS");const d=Math.min(e,D);n=new Date(Date.now()+d*1e3)}const o=await i.internalAdapter.updateUser(t,{banExpires:n,banned:!0,banReason:a??"No reason"});return await i.internalAdapter.deleteUserSessions(t),h(o)}),cancelInvitation:({invitationId:e})=>r(async a=>{await a.adapter.delete({model:"invitation",where:[{field:"id",value:e}]})}),capabilities:()=>r(e=>Promise.resolve(y(e.options))),addMember:({organizationId:e,role:a,userId:t})=>r(async i=>{const n=await i.adapter.create({data:{createdAt:new Date,organizationId:e,role:a===void 0||a===""?"member":a,userId:t},model:"member"});return c(n)}),addTeamMember:({teamId:e,userId:a})=>r(async t=>{const i=await t.adapter.create({data:{createdAt:new Date,teamId:e,userId:a},model:"teamMember"});return c(i)}),config:()=>r(e=>{const a=e.options,t=y(a),i=new Set((a.plugins??[]).map(l=>l.id)),n=A(a),o=a.session??{},d=a.rateLimit??{};return Promise.resolve({capabilities:t,emailAndPassword:a.emailAndPassword?.enabled??!1,organization:{enabled:t.organization,roles:!!n.organizationRole,teams:!!n.team},plugins:[...i].toSorted((l,f)=>l.localeCompare(f)),rateLimit:{enabled:d.enabled??!1,max:d.max,window:d.window},session:{cookieCache:o.cookieCache?.enabled,expiresIn:o.expiresIn,freshAge:o.freshAge,updateAge:o.updateAge},socialProviders:Object.keys(a.socialProviders??{}).toSorted((l,f)=>l.localeCompare(f)),userFields:L(n.user?.fields??{})})}),createOrganization:({logo:e,metadata:a,name:t,ownerId:i,slug:n})=>r(async o=>{const d=b(n!==void 0&&n!==""?n:t);if(d==="")throw new p("could not derive a slug from the organization name","ORG_SLUG_INVALID");if(await o.adapter.findOne({model:"organization",where:[{field:"slug",value:d}]}))throw new p("an organization with this slug already exists","ORG_SLUG_TAKEN");const l=await o.adapter.create({data:{createdAt:new Date,logo:e===void 0||e===""?void 0:e,metadata:a===void 0?void 0:JSON.stringify(a),name:t,slug:d},model:"organization"});return i!==void 0&&i!==""&&await o.adapter.create({data:{createdAt:new Date,organizationId:l.id,role:"owner",userId:i},model:"member"}),c(l)}),createOrgRole:({organizationId:e,permission:a,role:t})=>r(async i=>{const n=await i.adapter.create({data:{createdAt:new Date,organizationId:e,permission:JSON.stringify(a),role:t},model:"organizationRole"});return c(n)}),createTeam:({name:e,organizationId:a})=>r(async t=>{const i=await t.adapter.create({data:{createdAt:new Date,name:e,organizationId:a},model:"team"});return c(i)}),deleteOrganization:({organizationId:e})=>r(async a=>{const t=A(a.options);if(await a.adapter.deleteMany({model:"member",where:[{field:"organizationId",value:e}]}),await a.adapter.deleteMany({model:"invitation",where:[{field:"organizationId",value:e}]}),t.team){const i=await a.adapter.findMany({model:"team",where:[{field:"organizationId",value:e}]});for(const n of i)await a.adapter.deleteMany({model:"teamMember",where:[{field:"teamId",value:n.id}]});await a.adapter.deleteMany({model:"team",where:[{field:"organizationId",value:e}]})}t.organizationRole&&await a.adapter.deleteMany({model:"organizationRole",where:[{field:"organizationId",value:e}]}),await a.adapter.delete({model:"organization",where:[{field:"id",value:e}]})}),deleteOrgRole:({roleId:e})=>r(async a=>{await a.adapter.delete({model:"organizationRole",where:[{field:"id",value:e}]})}),inviteMember:({email:e,inviterId:a,organizationId:t,role:i})=>r(async n=>{let o=a;if(o===void 0||o===""){const l=await n.adapter.findMany({model:"member",where:[{field:"organizationId",value:t}]});o=(l.find(f=>typeof f.role=="string"&&f.role.includes("owner"))??l[0])?.userId}if(o===void 0||o==="")throw new p("provide an inviter — the organization has no members to attribute the invitation to","INVITER_REQUIRED");const d=await n.adapter.create({data:{createdAt:new Date,email:e.toLowerCase(),expiresAt:new Date(Date.now()+U),inviterId:o,organizationId:t,role:i===void 0||i===""?"member":i,status:"pending"},model:"invitation"});return c(d)}),listOrgRoles:({limit:e,offset:a,organizationId:t})=>r(i=>g(i,"organizationRole",{limit:e,offset:a,sortBy:{direction:"desc",field:"createdAt"},where:[{field:"organizationId",value:t}]})),listTeamMembers:({limit:e,offset:a,teamId:t})=>r(i=>g(i,"teamMember",{limit:e,offset:a,where:[{field:"teamId",value:t}]})),listTeams:({limit:e,offset:a,organizationId:t})=>r(i=>g(i,"team",{limit:e,offset:a,sortBy:{direction:"desc",field:"createdAt"},where:[{field:"organizationId",value:t}]})),removeTeam:({teamId:e})=>r(async a=>{await a.adapter.deleteMany({model:"teamMember",where:[{field:"teamId",value:e}]}),await a.adapter.delete({model:"team",where:[{field:"id",value:e}]})}),removeTeamMember:({teamMemberId:e})=>r(async a=>{await a.adapter.delete({model:"teamMember",where:[{field:"id",value:e}]})}),updateMemberRole:({memberId:e,role:a})=>r(async t=>{const i=await t.adapter.update({model:"member",update:{role:I(a)},where:[{field:"id",value:e}]});return c(i??{id:e,role:I(a)})}),updateOrganization:({logo:e,metadata:a,name:t,organizationId:i,slug:n})=>r(async o=>{const d={};if(t!==void 0&&(d.name=t),n!==void 0&&n!==""&&(d.slug=b(n)),e!==void 0&&(d.logo=e===""?void 0:e),a!==void 0&&(d.metadata=JSON.stringify(a)),Object.keys(d).length===0)return c({id:i});const l=await o.adapter.update({model:"organization",update:d,where:[{field:"id",value:i}]});return c(l??{id:i})}),updateOrgRole:({permission:e,roleId:a})=>r(async t=>{const i=await t.adapter.update({model:"organizationRole",update:{permission:JSON.stringify(e),updatedAt:new Date},where:[{field:"id",value:a}]});return c(i??{id:a,permission:JSON.stringify(e)})}),updateTeam:({name:e,teamId:a})=>r(async t=>{const i=await t.adapter.update({model:"team",update:{name:e,updatedAt:new Date},where:[{field:"id",value:a}]});return c(i??{id:a,name:e})}),createUser:({data:e,email:a,name:t,password:i,role:n})=>r(async o=>{const d=a.toLowerCase();if(await o.internalAdapter.findUserByEmail(d))throw new p("a user with this email already exists","USER_ALREADY_EXISTS");const l=await o.internalAdapter.createUser({email:d,name:t,role:n===void 0?void 0:I(n),...e});if(i!==void 0&&i!==""){const f=await o.password.hash(i);await o.internalAdapter.linkAccount({accountId:l.id,password:f,providerId:"credential",userId:l.id})}return h(l)}),deletePasskey:({passkeyId:e})=>r(async a=>{await a.adapter.delete({model:"passkey",where:[{field:"id",value:e}]})}),disableTwoFactor:({userId:e})=>r(async a=>{await a.adapter.deleteMany({model:"twoFactor",where:[{field:"userId",value:e}]}),await a.internalAdapter.updateUser(e,{twoFactorEnabled:!1})}),impersonateUser:({userId:e})=>r(async a=>{const t=await a.internalAdapter.findUserById(e);if(!t)throw new p("user not found","USER_NOT_FOUND");const i=u.impersonationSeconds;let n=z;if(i!==void 0){if(!Number.isInteger(i)||!Number.isFinite(i)||i<=0)throw new p("impersonationSeconds must be a positive finite integer","INVALID_IMPERSONATION_SECONDS");n=Math.min(i,R)}const o=new Date(Date.now()+n*1e3),d=await a.internalAdapter.createSession(e,!0,{expiresAt:o,impersonatedBy:u.impersonatedBy??e},!0);return{expiresAt:d.expiresAt instanceof Date?d.expiresAt.getTime():o.getTime(),token:d.token,user:h(t)}}),listAccounts:({userId:e})=>r(async a=>(await a.adapter.findMany({model:"account",where:[{field:"userId",value:e}]})).map(t=>c(t))),listInvitations:({limit:e,offset:a,organizationId:t})=>r(i=>g(i,"invitation",{limit:e,offset:a,where:[{field:"organizationId",value:t}]})),listMembers:({limit:e,offset:a,organizationId:t})=>r(i=>g(i,"member",{limit:e,offset:a,sortBy:{direction:"desc",field:"createdAt"},where:[{field:"organizationId",value:t}]})),listOrganizations:({limit:e,offset:a})=>r(t=>g(t,"organization",{limit:e,offset:a,sortBy:{direction:"desc",field:"createdAt"}})),listPasskeys:({userId:e})=>r(async a=>(await a.adapter.findMany({model:"passkey",where:[{field:"userId",value:e}]})).map(t=>c(t))),listSessions:({limit:e,offset:a,userId:t})=>r(i=>g(i,"session",{limit:e,offset:a,sortBy:{direction:"desc",field:"createdAt"},where:t===void 0||t===""?void 0:[{field:"userId",value:t}]})),listUsers:({filterField:e,filterValue:a,limit:t,offset:i,search:n,searchField:o,sortBy:d,sortDirection:l})=>r(f=>{const v=[];return n!==void 0&&n!==""&&v.push({field:o??"email",operator:"contains",value:n}),a!==void 0&&v.push({field:e??"email",operator:"eq",value:a}),g(f,"user",{limit:t,offset:i,sortBy:{direction:l??"desc",field:d??"createdAt"},where:v})}),removeMember:({memberId:e})=>r(async a=>{await a.adapter.delete({model:"member",where:[{field:"id",value:e}]})}),removeUser:({userId:e})=>r(async a=>{await a.internalAdapter.deleteUserSessions(e),await a.internalAdapter.deleteUser(e)}),revokeUserSession:({sessionId:e})=>r(async a=>{const t=await a.adapter.findOne({model:"session",where:[{field:"id",value:e}]});t?.token&&await a.internalAdapter.deleteSession(t.token)}),revokeUserSessions:({userId:e})=>r(async a=>{await a.internalAdapter.deleteUserSessions(e)}),setRole:({role:e,userId:a})=>r(async t=>{const i=await t.internalAdapter.updateUser(a,{role:I(e)});return h(i)}),setUserPassword:({newPassword:e,userId:a})=>r(async t=>{const i=t.password.config.minPasswordLength,n=t.password.config.maxPasswordLength;if(e.length<i)throw new p(`password must be at least ${i.toString()} characters`,"PASSWORD_TOO_SHORT");if(e.length>n)throw new p(`password must be at most ${n.toString()} characters`,"PASSWORD_TOO_LONG");const o=await t.password.hash(e);await t.internalAdapter.updatePassword(a,o)}),unbanUser:({userId:e})=>r(async a=>{const t=await a.internalAdapter.updateUser(e,{banExpires:null,banned:!1,banReason:null});return h(t)}),unlinkAccount:({accountId:e,userId:a})=>r(async t=>{await t.adapter.delete({model:"account",where:[{field:"id",value:e},{connector:"AND",field:"userId",value:a}]})}),updateUser:({data:e,userId:a})=>r(async t=>{const i=await t.internalAdapter.updateUser(a,e);return h(i)})}};export{p as LunoraAuthAdminError,F as createAuthAdmin};
@@ -0,0 +1 @@
1
+ import{createAuthMiddleware as a}from"better-auth/api";import{appendAuthAuditEntry as u}from"../audit.mjs";const c=r=>{const t=r.toLowerCase(),e=n=>t===n||t.endsWith(n);if(e("/sign-up/email")||e("/sign-up"))return"sign-up";if(t.includes("/sign-in/"))return"sign-in";if(e("/sign-out"))return"sign-out";if(e("/change-password")||e("/set-password"))return"password-change";if(e("/reset-password")||e("/request-password-reset")||e("/forget-password"))return"password-reset";if(e("/verify-email"))return"email-verification";if(t.includes("/two-factor/enable")||t.includes("/totp/enable"))return"mfa-enable";if(t.includes("/two-factor/disable")||t.includes("/totp/disable"))return"mfa-disable";if(e("/refresh-token")||e("/token"))return"token-refresh";if(e("/revoke-session")||e("/revoke-sessions")||e("/revoke-other-sessions"))return"session-revoke";if(e("/link-social"))return"account-link";if(e("/unlink-account"))return"account-unlink"},s=(r,t)=>r.headers?.get(t)??r.request?.headers.get(t)??void 0,d=r=>{const t=s(r,"x-forwarded-for");return s(r,"cf-connecting-ip")??(t===void 0?void 0:t.split(",")[0]?.trim())??s(r,"x-real-ip")},f=r=>{const t=r.context?.newSession??r.context?.session,e=t?.user?.id??t?.session?.userId,n=t?.user?.email;return{...e===void 0?{}:{actorId:e},...n===void 0?{}:{actorEmail:n}}},l=r=>{const t=r.context?.returned;if(t instanceof Error)return"failure";if(typeof t=="object"&&t!==null&&"status"in t){const e=Number(t.status);if(Number.isFinite(e)&&e>=400)return"failure"}return"success"},p=(r,t=Date.now())=>{const e=r.path===void 0?void 0:c(r.path);if(e===void 0)return;const n=d(r),o=s(r,"user-agent");return{...f(r),event:e,outcome:l(r),ts:t,...n===void 0?{}:{ip:n},...o===void 0?{}:{userAgent:o},detail:{path:r.path}}},h=r=>a(async t=>{try{const e=p(t);if(e!==void 0){const n=await u(r.executor,e,{redactDetail:r.redactDetail,retention:r.retention});r.onRecord!==void 0&&await r.onRecord(n)}}catch(e){console.error("@lunora/auth: audit hook failed to record event",e)}return{}}),k=(r,t)=>{const e=h(t),n=r.hooks?.after,o=n?async i=>(await n(i),e(i)):e;return{...r,hooks:{...r.hooks,after:o}}};export{h as authAuditHook,p as buildAuditEntry,c as eventForPath,k as withAuthAudit};
@@ -0,0 +1 @@
1
+ import{getMigrations as e}from"better-auth/db/migration";import{resolveAuthOptions as s}from"./createAuth-DS6PL8Mb.mjs";const o=new WeakMap,g=async i=>{const{options:t}=i,n=o.get(t);if(n){await n;return}const r=(async()=>{const{runMigrations:a}=await e(t);await a()})();o.set(t,r);try{await r}catch(a){throw o.delete(t),a}},w=async i=>{const{compileMigrations:t}=await e(s(i));return t()};export{w as compileMigrationsSql,g as ensureMigrated};
@@ -66,7 +66,7 @@ type LunoraAuth = ReturnType<typeof betterAuth>;
66
66
  * Rate limiting is ON by default for `/api/auth/*`.
67
67
  *
68
68
  * better-auth's own default is `rateLimit.enabled ?? isProduction`, and its
69
- * `isProduction` is `"development" === "production"` resolved at
69
+ * `isProduction` is `"production" === "production"` resolved at
70
70
  * module-load time. On Cloudflare Workers that check is unreliable: the
71
71
  * runtime has no Node `process.env` (absent entirely without
72
72
  * `nodejs_compat`, and even with it `NODE_ENV` is rarely `"production"` at
@@ -66,7 +66,7 @@ type LunoraAuth = ReturnType<typeof betterAuth>;
66
66
  * Rate limiting is ON by default for `/api/auth/*`.
67
67
  *
68
68
  * better-auth's own default is `rateLimit.enabled ?? isProduction`, and its
69
- * `isProduction` is `"development" === "production"` resolved at
69
+ * `isProduction` is `"production" === "production"` resolved at
70
70
  * module-load time. On Cloudflare Workers that check is unreliable: the
71
71
  * runtime has no Node `process.env` (absent entirely without
72
72
  * `nodejs_compat`, and even with it `NODE_ENV` is rarely `"production"` at
@@ -0,0 +1 @@
1
+ import{LunoraError as a}from"@lunora/errors";import{betterAuth as n}from"better-auth";import{validateSessionPolicy as c}from"./sessionPresets-DpEFjXKV.mjs";const i=32,l=e=>{const t=typeof e=="string"?e.trim().length:0;return t>0&&t<i},s=e=>typeof e=="string"?e.toLowerCase().startsWith("http://"):e&&typeof e=="object"?e.protocol==="http"?!0:e.protocol==="https"?!1:typeof e.fallback=="string"&&e.fallback.toLowerCase().startsWith("http://"):!1,u=e=>{if(l(e.secret)){const r=`@lunora/auth: AUTH_SECRET is only ${String(e.secret?.trim().length)} characters. Use at least ${String(i)} for a brute-force-resistant secret — generate one with \`openssl rand -hex 32\`.`;if(!s(e.baseURL))throw new a("INTERNAL",r);console.warn(r)}const t=e.advanced??{};return{...e,advanced:{...t,defaultCookieAttributes:t.defaultCookieAttributes??{httpOnly:!0,path:"/",sameSite:"lax"},...t.useSecureCookies===void 0?{useSecureCookies:!s(e.baseURL)}:{}}}},h=e=>{const t=u(e),r=t.rateLimit?.enabled===void 0,o=t.rateLimit?.storage===void 0&&t.rateLimit?.enabled!==!1;return{...t,...r||o?{rateLimit:{...t.rateLimit,...r?{enabled:!0}:{},...o?{storage:"database"}:{}}}:{},...t.session?.cookieCache===void 0?{session:{...t.session,cookieCache:{enabled:!0,maxAge:60}}}:{}}},m=e=>{if(!e.secret||e.secret.trim()==="")throw new a("INTERNAL",'@lunora/auth: `secret` is required. Set AUTH_SECRET locally in .dev.vars (`lunora env set AUTH_SECRET "$(openssl rand -hex 32)"`), and in production with `wrangler secret put AUTH_SECRET`.');return e.session&&c(e.session),n(h(e))};export{m as createAuth,h as resolveAuthOptions};
@@ -0,0 +1 @@
1
+ import{LunoraError as i}from"@lunora/errors";import{APIError as u}from"better-auth/api";import{assertEmailAllowed as E}from"../email-guard.mjs";const m=a=>{switch(a){case 400:return"BAD_REQUEST";case 422:return"UNPROCESSABLE_ENTITY";case 429:return"TOO_MANY_REQUESTS";default:return"INTERNAL_SERVER_ERROR"}},n=a=>async(s,t)=>{const r=typeof s.email=="string"?s.email:void 0;if(r===void 0||r==="")return;let o;try{o=await E(r,a)}catch(e){throw e instanceof i?new u(m(e.status),{code:e.code,message:e.message}):e}a.onClassify?.(o,s,t)},b=(a={})=>({user:{create:{before:n(a)}}}),R=(a,s={})=>{const t=n(s),r=a.databaseHooks?.user?.create?.before,o=r?async(e,c)=>(await t(e,c),r(e,c)):t;return{...a,databaseHooks:{...a.databaseHooks,user:{...a.databaseHooks?.user,create:{...a.databaseHooks?.user?.create,before:o}}}}};export{b as emailGateDatabaseHooks,R as withEmailGate};
@@ -0,0 +1 @@
1
+ const r=s=>{const o=["expiresIn","updateAge","freshAge"];for(const n of o){const e=s[n];if(e!==void 0&&(typeof e!="number"||!Number.isFinite(e)||e<0))throw new TypeError(`@lunora/auth: \`session.${n}\` must be a non-negative, finite number of seconds`)}return s},i={longLived:{cookieCache:{enabled:!0,maxAge:60},expiresIn:2592e3,freshAge:86400,updateAge:86400},rolling:{cookieCache:{enabled:!0,maxAge:60},expiresIn:604800,freshAge:86400,updateAge:86400},strict:{cookieCache:{enabled:!1},expiresIn:3600,freshAge:300,updateAge:900}};export{i as sessionPresets,r as validateSessionPolicy};
@@ -1,2 +1 @@
1
- export { passkeyClient } from '@better-auth/passkey/client';
2
- export { adminClient, anonymousClient, customSessionClient, deviceAuthorizationClient, emailOTPClient, genericOAuthClient, inferAdditionalFields, inferOrgAdditionalFields, jwtClient, lastLoginMethodClient, magicLinkClient, multiSessionClient, oidcClient, oneTimeTokenClient, organizationClient, phoneNumberClient, siweClient, twoFactorClient, usernameClient } from 'better-auth/client/plugins';
1
+ import{passkeyClient as n}from"@better-auth/passkey/client";import{adminClient as l,anonymousClient as o,customSessionClient as C,deviceAuthorizationClient as a,emailOTPClient as r,genericOAuthClient as s,inferAdditionalFields as m,inferOrgAdditionalFields as d,jwtClient as u,lastLoginMethodClient as c,magicLinkClient as g,multiSessionClient as f,oidcClient as h,oneTimeTokenClient as p,organizationClient as A,phoneNumberClient as k,siweClient as w,twoFactorClient as F,usernameClient as O}from"better-auth/client/plugins";export{l as adminClient,o as anonymousClient,C as customSessionClient,a as deviceAuthorizationClient,r as emailOTPClient,s as genericOAuthClient,m as inferAdditionalFields,d as inferOrgAdditionalFields,u as jwtClient,c as lastLoginMethodClient,g as magicLinkClient,f as multiSessionClient,h as oidcClient,p as oneTimeTokenClient,A as organizationClient,n as passkeyClient,k as phoneNumberClient,w as siweClient,F as twoFactorClient,O as usernameClient};
package/dist/plugins.mjs CHANGED
@@ -1,22 +1 @@
1
- export { passkey } from '@better-auth/passkey';
2
- export { captcha, mcp, withMcpAuth } from 'better-auth/plugins';
3
- export { createAccessControl } from 'better-auth/plugins/access';
4
- export { admin } from 'better-auth/plugins/admin';
5
- export { anonymous } from 'better-auth/plugins/anonymous';
6
- export { bearer } from 'better-auth/plugins/bearer';
7
- export { customSession } from 'better-auth/plugins/custom-session';
8
- export { deviceAuthorization } from 'better-auth/plugins/device-authorization';
9
- export { emailOTP } from 'better-auth/plugins/email-otp';
10
- export { genericOAuth } from 'better-auth/plugins/generic-oauth';
11
- export { haveIBeenPwned } from 'better-auth/plugins/haveibeenpwned';
12
- export { jwt } from 'better-auth/plugins/jwt';
13
- export { magicLink } from 'better-auth/plugins/magic-link';
14
- export { multiSession } from 'better-auth/plugins/multi-session';
15
- export { oAuthProxy } from 'better-auth/plugins/oauth-proxy';
16
- export { oidcProvider } from 'better-auth/plugins/oidc-provider';
17
- export { oneTimeToken } from 'better-auth/plugins/one-time-token';
18
- export { organization } from 'better-auth/plugins/organization';
19
- export { phoneNumber } from 'better-auth/plugins/phone-number';
20
- export { siwe } from 'better-auth/plugins/siwe';
21
- export { twoFactor } from 'better-auth/plugins/two-factor';
22
- export { username } from 'better-auth/plugins/username';
1
+ import{passkey as e}from"@better-auth/passkey";import{captcha as m,mcp as p,withMcpAuth as x}from"better-auth/plugins";import{createAccessControl as i}from"better-auth/plugins/access";import{admin as a}from"better-auth/plugins/admin";import{anonymous as s}from"better-auth/plugins/anonymous";import{bearer as h}from"better-auth/plugins/bearer";import{customSession as w}from"better-auth/plugins/custom-session";import{deviceAuthorization as P}from"better-auth/plugins/device-authorization";import{emailOTP as k}from"better-auth/plugins/email-otp";import{genericOAuth as v}from"better-auth/plugins/generic-oauth";import{haveIBeenPwned as T}from"better-auth/plugins/haveibeenpwned";import{jwt as z}from"better-auth/plugins/jwt";import{magicLink as S}from"better-auth/plugins/magic-link";import{multiSession as B}from"better-auth/plugins/multi-session";import{oAuthProxy as F}from"better-auth/plugins/oauth-proxy";import{oidcProvider as L}from"better-auth/plugins/oidc-provider";import{oneTimeToken as N}from"better-auth/plugins/one-time-token";import{organization as D}from"better-auth/plugins/organization";import{phoneNumber as G}from"better-auth/plugins/phone-number";import{siwe as J}from"better-auth/plugins/siwe";import{twoFactor as Q}from"better-auth/plugins/two-factor";import{username as U}from"better-auth/plugins/username";export{a as admin,s as anonymous,h as bearer,m as captcha,i as createAccessControl,w as customSession,P as deviceAuthorization,k as emailOTP,v as genericOAuth,T as haveIBeenPwned,z as jwt,S as magicLink,p as mcp,B as multiSession,F as oAuthProxy,L as oidcProvider,N as oneTimeToken,D as organization,e as passkey,G as phoneNumber,J as siwe,Q as twoFactor,U as username,x as withMcpAuth};
package/dist/schema.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TableDefinition } from '@lunora/server';
2
- import { a as LunoraAuthOptions } from "./packem_shared/create-auth.d-COcIS_KU.mjs";
2
+ import { a as LunoraAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.mjs";
3
3
  import 'better-auth';
4
4
  /**
5
5
  * Derive Lunora table definitions from a better-auth config — the bridge that
package/dist/schema.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TableDefinition } from '@lunora/server';
2
- import { a as LunoraAuthOptions } from "./packem_shared/create-auth.d-COcIS_KU.js";
2
+ import { a as LunoraAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.js";
3
3
  import 'better-auth';
4
4
  /**
5
5
  * Derive Lunora table definitions from a better-auth config — the bridge that
package/dist/schema.mjs CHANGED
@@ -1,62 +1 @@
1
- import { defineTable } from '@lunora/server';
2
- import { v } from '@lunora/values';
3
- import { getAuthTables } from 'better-auth/db';
4
-
5
- const baseValidator = (attribute) => {
6
- if (attribute.references) {
7
- return v.id(attribute.references.model);
8
- }
9
- const { type } = attribute;
10
- if (Array.isArray(type)) {
11
- return v.string();
12
- }
13
- switch (type) {
14
- case "boolean": {
15
- return v.boolean();
16
- }
17
- case "date": {
18
- return v.date();
19
- }
20
- case "number": {
21
- return v.number();
22
- }
23
- case "number[]": {
24
- return v.array(v.number());
25
- }
26
- case "string": {
27
- return v.string();
28
- }
29
- case "string[]": {
30
- return v.array(v.string());
31
- }
32
- // "json" and anything unrecognised: keep the row shape permissive rather
33
- // than fail schema generation on a plugin's exotic column type.
34
- default: {
35
- return v.any();
36
- }
37
- }
38
- };
39
- const fieldValidator = (attribute) => {
40
- let validator = baseValidator(attribute);
41
- if (attribute.required === false) {
42
- validator = validator.nullable();
43
- }
44
- if (attribute.unique === true) {
45
- validator = validator.unique();
46
- }
47
- return validator;
48
- };
49
- const authTables = (options) => {
50
- const tables = getAuthTables(options);
51
- const schema = {};
52
- for (const table of Object.values(tables)) {
53
- const shape = {};
54
- for (const [fieldKey, attribute] of Object.entries(table.fields)) {
55
- shape[attribute.fieldName ?? fieldKey] = fieldValidator(attribute);
56
- }
57
- schema[table.modelName] = defineTable(shape).externallyManaged();
58
- }
59
- return schema;
60
- };
61
-
62
- export { authTables as default };
1
+ import{defineTable as i}from"@lunora/server";import{v as e}from"@lunora/values";import{getAuthTables as c}from"better-auth/db";const f=n=>{if(n.references)return e.id(n.references.model);const{type:r}=n;if(Array.isArray(r))return e.string();switch(r){case"boolean":return e.boolean();case"date":return e.date();case"number":return e.number();case"number[]":return e.array(e.number());case"string":return e.string();case"string[]":return e.array(e.string());default:return e.any()}},l=n=>{let r=f(n);return n.required===!1&&(r=r.nullable()),n.unique===!0&&(r=r.unique()),r},g=n=>{const r=c(n),t={};for(const a of Object.values(r)){const s={};for(const[o,u]of Object.entries(a.fields))s[u.fieldName??o]=l(u);t[a.modelName]=i(s).externallyManaged()}return t};export{g as default};