@lunora/auth 1.0.0-alpha.90 → 1.0.0-alpha.91

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/audit.d.mts CHANGED
@@ -82,21 +82,6 @@ interface ReadAuthAuditOptions {
82
82
  /** Return only events with `seq` strictly greater than this (forward paging). */
83
83
  sinceSeq?: number;
84
84
  }
85
- /**
86
- * Create the `__lunora_auth_audit__` table. `seq` is an `AUTOINCREMENT` primary
87
- * key giving the database a monotonic cursor the Security page pages through.
88
- * Idempotent, so read and write paths can call it defensively.
89
- *
90
- * `target_email` is added via a guarded `ALTER TABLE` rather than baked only
91
- * into the `CREATE`, mirroring `@lunora/observability`'s
92
- * `ensureRequestLogTable`/`ensureFunctionMetricsTables` — `CREATE TABLE IF NOT
93
- * EXISTS` only helps a table that doesn't exist yet, so a database whose audit
94
- * table predates this column needs the `ALTER` to gain it. SQLite has no `ADD
95
- * COLUMN IF NOT EXISTS`; the duplicate-column error from a re-run (or from the
96
- * column already existing on the freshly-created schema above) is swallowed —
97
- * anything else re-throws, so a genuinely broken executor is not silently
98
- * papered over.
99
- */
100
85
  declare const ensureAuthAuditTable: (executor: SqlExecutor) => Promise<void>;
101
86
  /**
102
87
  * Append one auth event, redacting its `detail` payload (unless disabled) and —
package/dist/audit.d.ts CHANGED
@@ -82,21 +82,6 @@ interface ReadAuthAuditOptions {
82
82
  /** Return only events with `seq` strictly greater than this (forward paging). */
83
83
  sinceSeq?: number;
84
84
  }
85
- /**
86
- * Create the `__lunora_auth_audit__` table. `seq` is an `AUTOINCREMENT` primary
87
- * key giving the database a monotonic cursor the Security page pages through.
88
- * Idempotent, so read and write paths can call it defensively.
89
- *
90
- * `target_email` is added via a guarded `ALTER TABLE` rather than baked only
91
- * into the `CREATE`, mirroring `@lunora/observability`'s
92
- * `ensureRequestLogTable`/`ensureFunctionMetricsTables` — `CREATE TABLE IF NOT
93
- * EXISTS` only helps a table that doesn't exist yet, so a database whose audit
94
- * table predates this column needs the `ALTER` to gain it. SQLite has no `ADD
95
- * COLUMN IF NOT EXISTS`; the duplicate-column error from a re-run (or from the
96
- * column already existing on the freshly-created schema above) is swallowed —
97
- * anything else re-throws, so a genuinely broken executor is not silently
98
- * papered over.
99
- */
100
85
  declare const ensureAuthAuditTable: (executor: SqlExecutor) => Promise<void>;
101
86
  /**
102
87
  * Append one auth event, redacting its `detail` payload (unless disabled) and —
package/dist/audit.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import{standardRules as A,piiRules as L,redact as f}from"@visulima/redact";const n="__lunora_auth_audit__",g=[...A,...L],R=1e3,N=1e4,s=null,u=e=>{if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="bigint"||typeof e=="boolean")return String(e)},l=async e=>{await e.run(`CREATE TABLE IF NOT EXISTS "${n}" (
1
+ import{standardRules as L,piiRules as f,redact as g}from"@visulima/redact";const o="__lunora_auth_audit__",R=[...L,...f],p=1e3,N=1e4,n=null,l=e=>{if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="bigint"||typeof e=="boolean")return String(e)},h=async e=>{await e.run(`CREATE TABLE IF NOT EXISTS "${o}" (
2
2
  seq INTEGER PRIMARY KEY AUTOINCREMENT,
3
3
  ts REAL NOT NULL,
4
4
  event TEXT NOT NULL,
@@ -9,4 +9,4 @@ import{standardRules as A,piiRules as L,redact as f}from"@visulima/redact";const
9
9
  ip TEXT,
10
10
  user_agent TEXT,
11
11
  detail TEXT
12
- )`,[]);try{await e.run(`ALTER TABLE "${n}" ADD COLUMN target_email TEXT`,[])}catch(t){if(!(t instanceof Error?t.message.toLowerCase():String(t).toLowerCase()).includes("duplicate column"))throw t}},S=async(e,t,i={})=>{await l(e);let a;return t.detail!==void 0&&(a=i.redactDetail===!1?t.detail:f(t.detail,g)),await e.run(`INSERT INTO "${n}" (ts, event, outcome, actor_id, actor_email, target_email, ip, user_agent, detail) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,[t.ts,t.event,t.outcome,t.actorId??s,t.actorEmail??s,t.targetEmail??s,t.ip??s,t.userAgent??s,a===void 0?s:JSON.stringify(a)]),typeof i.retention=="number"&&i.retention>0&&await e.run(`DELETE FROM "${n}" WHERE seq <= (SELECT MAX(seq) - ? FROM "${n}")`,[i.retention]),{...t,detail:a}},I=async(e,t={})=>{await l(e);const i=Number.isFinite(t.limit)?Math.max(1,Math.min(t.limit,N)):R,a=["seq > ?"],o=[t.sinceSeq??0];return t.actorId!==void 0&&(a.push("actor_id = ?"),o.push(t.actorId)),t.event!==void 0&&(a.push("event = ?"),o.push(t.event)),o.push(i),(await e.all(`SELECT seq, ts, event, outcome, actor_id, actor_email, target_email, ip, user_agent, detail FROM "${n}" WHERE ${a.join(" AND ")} ORDER BY seq DESC LIMIT ?`,o)).map(r=>{const c={event:u(r.event)??"",outcome:r.outcome==="failure"?"failure":"success",seq:Number(r.seq),ts:Number(r.ts)},d=[["actorId","actor_id"],["actorEmail","actor_email"],["targetEmail","target_email"],["ip","ip"],["userAgent","user_agent"]];for(const[m,_]of d){const T=u(r[_]);T!==void 0&&(c[m]=T)}const E=u(r.detail);return E!==void 0&&(c.detail=JSON.parse(E)),c})},D=e=>({read:t=>I(e,t)});export{n as AUTH_AUDIT_TABLE,S as appendAuthAuditEntry,D as createAuthAuditReader,l as ensureAuthAuditTable,I as readAuthAuditLog};
12
+ )`,[]);try{await e.run(`ALTER TABLE "${o}" ADD COLUMN target_email TEXT`,[])}catch(t){if(!(t instanceof Error?t.message.toLowerCase():String(t).toLowerCase()).includes("duplicate column"))throw t}},E=new WeakMap,m=e=>{const t=E.get(e);if(t)return t;const a=h(e);return E.set(e,a),a.catch(()=>E.delete(e)),a},D=async(e,t,a={})=>{await m(e);let i;return t.detail!==void 0&&(i=a.redactDetail===!1?t.detail:g(t.detail,R)),await e.run(`INSERT INTO "${o}" (ts, event, outcome, actor_id, actor_email, target_email, ip, user_agent, detail) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,[t.ts,t.event,t.outcome,t.actorId??n,t.actorEmail??n,t.targetEmail??n,t.ip??n,t.userAgent??n,i===void 0?n:JSON.stringify(i)]),typeof a.retention=="number"&&a.retention>0&&await e.run(`DELETE FROM "${o}" WHERE seq <= (SELECT MAX(seq) - ? FROM "${o}")`,[a.retention]),{...t,detail:i}},I=async(e,t={})=>{await m(e);const a=Number.isFinite(t.limit)?Math.max(1,Math.min(t.limit,N)):p,i=["seq > ?"],c=[t.sinceSeq??0];return t.actorId!==void 0&&(i.push("actor_id = ?"),c.push(t.actorId)),t.event!==void 0&&(i.push("event = ?"),c.push(t.event)),c.push(a),(await e.all(`SELECT seq, ts, event, outcome, actor_id, actor_email, target_email, ip, user_agent, detail FROM "${o}" WHERE ${i.join(" AND ")} ORDER BY seq DESC LIMIT ?`,c)).map(r=>{const u={event:l(r.event)??"",outcome:r.outcome==="failure"?"failure":"success",seq:Number(r.seq),ts:Number(r.ts)},A=[["actorId","actor_id"],["actorEmail","actor_email"],["targetEmail","target_email"],["ip","ip"],["userAgent","user_agent"]];for(const[s,_]of A){const T=l(r[_]);T!==void 0&&(u[s]=T)}const d=l(r.detail);if(d!==void 0)try{const s=JSON.parse(d);typeof s=="object"&&s!==null&&!Array.isArray(s)&&(u.detail=s)}catch{}return u})},O=e=>({read:t=>I(e,t)});export{o as AUTH_AUDIT_TABLE,D as appendAuthAuditEntry,O as createAuthAuditReader,m as ensureAuthAuditTable,I as readAuthAuditLog};
package/dist/index.d.mts CHANGED
@@ -860,7 +860,7 @@ declare const withEmailGate: (options: BetterAuthOptions, config?: EmailGateHook
860
860
  declare const DEFAULT_AUTH_BASE_PATH: string;
861
861
  /**
862
862
  * Route an inbound `Request` to better-auth if the path falls under
863
- * `basePath`; otherwise return `null` so the caller can continue dispatching.
863
+ * `basePath`; otherwise return `undefined` so the caller can continue dispatching.
864
864
  *
865
865
  * Better-auth handles arbitrarily nested paths (`/api/auth/sign-in/email`,
866
866
  * `/api/auth/callback/github`, …), so we use prefix matching instead of the
package/dist/index.d.ts CHANGED
@@ -860,7 +860,7 @@ declare const withEmailGate: (options: BetterAuthOptions, config?: EmailGateHook
860
860
  declare const DEFAULT_AUTH_BASE_PATH: string;
861
861
  /**
862
862
  * Route an inbound `Request` to better-auth if the path falls under
863
- * `basePath`; otherwise return `null` so the caller can continue dispatching.
863
+ * `basePath`; otherwise return `undefined` so the caller can continue dispatching.
864
864
  *
865
865
  * Better-auth handles arbitrarily nested paths (`/api/auth/sign-in/email`,
866
866
  * `/api/auth/callback/github`, …), so we use prefix matching instead of the
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{l as r,a as o,b as a}from"./packem_shared/adapter-DA8DdALX.mjs";import{LunoraAuthAdminError as u,createAuthAdmin as i}from"./packem_shared/LunoraAuthAdminError-gdM6RSXW.mjs";import{AUTH_AUDIT_TABLE as m,appendAuthAuditEntry as l,createAuthAuditReader as d,ensureAuthAuditTable as h,readAuthAuditLog as E}from"./audit.mjs";import{authAuditHook as p,buildAuditEntry as T,eventForPath as f,withAuthAudit as _}from"./packem_shared/authAuditHook-DOd5YOTL.mjs";import{READ_AUDIT_PATH as D,INTERNAL_SECRET_HEADER as S,RESOLVE_SESSION_PATH as H,LunoraAuthDO as c}from"./packem_shared/AUTH_DO_AUDIT_PATH-WJbI0Bpc.mjs";import{createAuth as L,resolveAuthOptions as P}from"./packem_shared/createAuth-DYFhkI1U.mjs";import{authDoColumnAdditions as I,authDoSchemaStatements as O}from"./packem_shared/authDoColumnAdditions-BCSs7qaN.mjs";import{createDoAuthWiring as N}from"./packem_shared/createDoAuthWiring-C7uxkces.mjs";import{emailGateDatabaseHooks as b,withEmailGate as g}from"./packem_shared/emailGateDatabaseHooks-CAygR3iq.mjs";import{assertEmailAllowed as M,classifyEmail as q,emailGateMiddleware as C,loadEmailDomainLists as F}from"./email-guard.mjs";import{DEFAULT_AUTH_BASE_PATH as k,handleAuthRequest as B}from"./packem_shared/DEFAULT_AUTH_BASE_PATH-C7moXiBA.mjs";import{LunoraAuthHeadersError as W,withAuthPlugins as Y}from"./middleware.mjs";import{compileMigrationsSql as z,ensureMigrated as J}from"./packem_shared/compileMigrationsSql-Y1YB3maO.mjs";import{default as Q}from"./schema.mjs";import{sessionPresets as Z,validateSessionPolicy as $}from"./packem_shared/sessionPresets-C867Mlo4.mjs";import{createSqlAuthStore as te,d1Executor as re}from"./sql-store.mjs";import{createMemoryAuthStore as ae,matchesWhere as Ae}from"./store.mjs";import{TURNSTILE_VERIFY_ENDPOINT as ie,verifyTurnstile as se}from"./turnstile.mjs";import{verifyTurnstileMiddleware as le}from"./turnstile-middleware.mjs";export{m as AUTH_AUDIT_TABLE,D as AUTH_DO_AUDIT_PATH,S as AUTH_DO_SECRET_HEADER,H as AUTH_DO_SESSION_PATH,k as DEFAULT_AUTH_BASE_PATH,u as LunoraAuthAdminError,c as LunoraAuthDO,W as LunoraAuthHeadersError,ie as TURNSTILE_VERIFY_ENDPOINT,l as appendAuthAuditEntry,M as assertEmailAllowed,p as authAuditHook,I as authDoColumnAdditions,O as authDoSchemaStatements,Q as authTables,T as buildAuditEntry,q as classifyEmail,z as compileMigrationsSql,L as createAuth,i as createAuthAdmin,d as createAuthAuditReader,N as createDoAuthWiring,ae as createMemoryAuthStore,te as createSqlAuthStore,re as d1Executor,b as emailGateDatabaseHooks,C as emailGateMiddleware,h as ensureAuthAuditTable,J as ensureMigrated,f as eventForPath,B as handleAuthRequest,F as loadEmailDomainLists,r as lunoraAuthAdapter,o as lunoraD1Adapter,a as lunoraDoAdapter,Ae as matchesWhere,E as readAuthAuditLog,P as resolveAuthOptions,Z as sessionPresets,$ as validateSessionPolicy,se as verifyTurnstile,le as verifyTurnstileMiddleware,_ as withAuthAudit,Y as withAuthPlugins,g as withEmailGate};
1
+ import{l as r,a as o,b as a}from"./packem_shared/adapter-DA8DdALX.mjs";import{LunoraAuthAdminError as u,createAuthAdmin as i}from"./packem_shared/LunoraAuthAdminError-DtAKa22S.mjs";import{AUTH_AUDIT_TABLE as m,appendAuthAuditEntry as l,createAuthAuditReader as d,ensureAuthAuditTable as h,readAuthAuditLog as E}from"./audit.mjs";import{authAuditHook as p,buildAuditEntry as T,eventForPath as f,withAuthAudit as _}from"./packem_shared/authAuditHook-tG8gdR77.mjs";import{READ_AUDIT_PATH as D,INTERNAL_SECRET_HEADER as S,RESOLVE_SESSION_PATH as H,LunoraAuthDO as c}from"./packem_shared/AUTH_DO_AUDIT_PATH-D-RB2XYs.mjs";import{createAuth as L,resolveAuthOptions as P}from"./packem_shared/createAuth-DYFhkI1U.mjs";import{authDoColumnAdditions as I,authDoSchemaStatements as O}from"./packem_shared/authDoColumnAdditions-BCSs7qaN.mjs";import{createDoAuthWiring as N}from"./packem_shared/createDoAuthWiring-C8H_SGCq.mjs";import{emailGateDatabaseHooks as b,withEmailGate as g}from"./packem_shared/emailGateDatabaseHooks-CAygR3iq.mjs";import{assertEmailAllowed as M,classifyEmail as q,emailGateMiddleware as C,loadEmailDomainLists as F}from"./email-guard.mjs";import{DEFAULT_AUTH_BASE_PATH as k,handleAuthRequest as B}from"./packem_shared/DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs";import{LunoraAuthHeadersError as W,withAuthPlugins as Y}from"./middleware.mjs";import{compileMigrationsSql as z,ensureMigrated as J}from"./packem_shared/compileMigrationsSql-Y1YB3maO.mjs";import{default as Q}from"./schema.mjs";import{sessionPresets as Z,validateSessionPolicy as $}from"./packem_shared/sessionPresets-C867Mlo4.mjs";import{createSqlAuthStore as te,d1Executor as re}from"./sql-store.mjs";import{createMemoryAuthStore as ae,matchesWhere as Ae}from"./store.mjs";import{TURNSTILE_VERIFY_ENDPOINT as ie,verifyTurnstile as se}from"./turnstile.mjs";import{verifyTurnstileMiddleware as le}from"./turnstile-middleware.mjs";export{m as AUTH_AUDIT_TABLE,D as AUTH_DO_AUDIT_PATH,S as AUTH_DO_SECRET_HEADER,H as AUTH_DO_SESSION_PATH,k as DEFAULT_AUTH_BASE_PATH,u as LunoraAuthAdminError,c as LunoraAuthDO,W as LunoraAuthHeadersError,ie as TURNSTILE_VERIFY_ENDPOINT,l as appendAuthAuditEntry,M as assertEmailAllowed,p as authAuditHook,I as authDoColumnAdditions,O as authDoSchemaStatements,Q as authTables,T as buildAuditEntry,q as classifyEmail,z as compileMigrationsSql,L as createAuth,i as createAuthAdmin,d as createAuthAuditReader,N as createDoAuthWiring,ae as createMemoryAuthStore,te as createSqlAuthStore,re as d1Executor,b as emailGateDatabaseHooks,C as emailGateMiddleware,h as ensureAuthAuditTable,J as ensureMigrated,f as eventForPath,B as handleAuthRequest,F as loadEmailDomainLists,r as lunoraAuthAdapter,o as lunoraD1Adapter,a as lunoraDoAdapter,Ae as matchesWhere,E as readAuthAuditLog,P as resolveAuthOptions,Z as sessionPresets,$ as validateSessionPolicy,se as verifyTurnstile,le as verifyTurnstileMiddleware,_ as withAuthAudit,Y as withAuthPlugins,g as withEmailGate};
@@ -1 +1 @@
1
- import{b as i,d as u}from"./adapter-DA8DdALX.mjs";import{ensureAuthAuditTable as h,createAuthAuditReader as c}from"../audit.mjs";import{resolveAuthOptions as l,createAuth as d}from"./createAuth-DYFhkI1U.mjs";import{authDoSchemaStatements as f,authDoColumnAdditions as p}from"./authDoColumnAdditions-BCSs7qaN.mjs";import{handleAuthRequest as m}from"./DEFAULT_AUTH_BASE_PATH-C7moXiBA.mjs";const A=(o,t)=>{const e=Math.max(o.length,t.length);let s=o.length^t.length;for(let r=0;r<e;r+=1){const n=r<o.length?o.charCodeAt(r):0,a=r<t.length?t.charCodeAt(r):0;s|=n^a}return s===0},R="/__lunora/auth/session",y="/__lunora/auth/audit",g="x-lunora-auth-do-secret",S=o=>{if(o===null||typeof o!="object"||Array.isArray(o))return{error:"body must be a JSON object"};const t={};for(const[e,s]of Object.entries(o))if(e==="limit"||e==="sinceSeq"){if(typeof s!="number"||!Number.isFinite(s))return{error:`"${e}" must be a finite number`};t[e]=s}else if(e==="actorId"||e==="event"){if(typeof s!="string")return{error:`"${e}" must be a string`};t[e]=s}else return{error:`unknown audit read option "${e}"`};return{options:t}};class v{#s;#r;#t;#e;#o=!1;constructor(t,e,s={}){this.#t=t.storage,this.#r=e,this.#s=s}#n(){if(this.#e!==void 0)return this.#e;const t=this.#r();if(!this.#o){const e=l(t);for(const s of f(e))[...this.#t.sql.exec(s)];for(const s of p(e,r=>this.#i(r)))[...this.#t.sql.exec(s)];this.#o=!0}return this.#e=d({...t,database:i(this.#t)}),this.#e}#i(t){return[...this.#t.sql.exec("SELECT name FROM pragma_table_info(?)",t)].map(s=>String(s.name))}async#u(t){if(!this.#a(t))return Response.json({error:"unauthorized"},{status:401});let e;try{e=await t.json()}catch{return Response.json({error:"invalid body"},{status:400})}const s=S(e??{});if("error"in s)return Response.json({error:s.error},{status:400});const r=u(this.#t);await h(r);const n=await c(r).read(s.options);return Response.json({entries:n})}#a(t){const{internalSecret:e}=this.#s;if(e===void 0||e==="")return!1;const s=t.headers.get(g);return s!==null&&A(s,e)}async#h(t){if(!this.#a(t))return Response.json({error:"unauthorized"},{status:401});const r=(await this.#n().api.getSession({headers:t.headers}))?.user.id;return Response.json(r===void 0?{}:{userId:r})}async fetch(t){const e=new URL(t.url);if(e.pathname===R)return this.#h(t);if(e.pathname===y)return this.#u(t);const s=this.#n();return await m(s,t,this.#s.basePath)??Response.json({error:"not an auth route"},{status:404})}}export{g as INTERNAL_SECRET_HEADER,v as LunoraAuthDO,y as READ_AUDIT_PATH,R as RESOLVE_SESSION_PATH};
1
+ import{b as i,d as u}from"./adapter-DA8DdALX.mjs";import{ensureAuthAuditTable as h,createAuthAuditReader as c}from"../audit.mjs";import{resolveAuthOptions as l,createAuth as d}from"./createAuth-DYFhkI1U.mjs";import{authDoSchemaStatements as f,authDoColumnAdditions as p}from"./authDoColumnAdditions-BCSs7qaN.mjs";import{handleAuthRequest as m}from"./DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs";const A=(o,t)=>{const e=Math.max(o.length,t.length);let s=o.length^t.length;for(let r=0;r<e;r+=1){const n=r<o.length?o.charCodeAt(r):0,a=r<t.length?t.charCodeAt(r):0;s|=n^a}return s===0},R="/__lunora/auth/session",y="/__lunora/auth/audit",g="x-lunora-auth-do-secret",S=o=>{if(o===null||typeof o!="object"||Array.isArray(o))return{error:"body must be a JSON object"};const t={};for(const[e,s]of Object.entries(o))if(e==="limit"||e==="sinceSeq"){if(typeof s!="number"||!Number.isFinite(s))return{error:`"${e}" must be a finite number`};t[e]=s}else if(e==="actorId"||e==="event"){if(typeof s!="string")return{error:`"${e}" must be a string`};t[e]=s}else return{error:`unknown audit read option "${e}"`};return{options:t}};class v{#s;#r;#t;#e;#o=!1;constructor(t,e,s={}){this.#t=t.storage,this.#r=e,this.#s=s}#n(){if(this.#e!==void 0)return this.#e;const t=this.#r();if(!this.#o){const e=l(t);for(const s of f(e))[...this.#t.sql.exec(s)];for(const s of p(e,r=>this.#i(r)))[...this.#t.sql.exec(s)];this.#o=!0}return this.#e=d({...t,database:i(this.#t)}),this.#e}#i(t){return[...this.#t.sql.exec("SELECT name FROM pragma_table_info(?)",t)].map(s=>String(s.name))}async#u(t){if(!this.#a(t))return Response.json({error:"unauthorized"},{status:401});let e;try{e=await t.json()}catch{return Response.json({error:"invalid body"},{status:400})}const s=S(e??{});if("error"in s)return Response.json({error:s.error},{status:400});const r=u(this.#t);await h(r);const n=await c(r).read(s.options);return Response.json({entries:n})}#a(t){const{internalSecret:e}=this.#s;if(e===void 0||e==="")return!1;const s=t.headers.get(g);return s!==null&&A(s,e)}async#h(t){if(!this.#a(t))return Response.json({error:"unauthorized"},{status:401});const r=(await this.#n().api.getSession({headers:t.headers}))?.user.id;return Response.json(r===void 0?{}:{userId:r})}async fetch(t){const e=new URL(t.url);if(e.pathname===R)return this.#h(t);if(e.pathname===y)return this.#u(t);const s=this.#n();return await m(s,t,this.#s.basePath)??Response.json({error:"not an auth route"},{status:404})}}export{g as INTERNAL_SECRET_HEADER,v as LunoraAuthDO,y as READ_AUDIT_PATH,R as RESOLVE_SESSION_PATH};
@@ -0,0 +1 @@
1
+ const e="/api/auth",u=(n,t)=>{const r=t.endsWith("/")?t.slice(0,-1):t;return n===r||n.startsWith(`${r}/`)},o=async(n,t,r=e)=>{const s=new URL(t.url);if(u(s.pathname,r))return n.handler(t)};export{e as DEFAULT_AUTH_BASE_PATH,o as handleAuthRequest,u as isAuthRoutePath};
@@ -0,0 +1 @@
1
+ import{createLocalAccountIssuer as I}from"@better-auth/core/db";import{LunoraError as R}from"@lunora/errors";import{getAuthTables as b}from"better-auth/db";class p extends R{constructor(m,f){super(f,m,{name:"LunoraAuthAdminError"})}}const N=50,E=500,M=3600,D=M*24,U=100*365*24*60*60,T=2880*60*1e3,z=[{field:"userId",model:"member"},{field:"userId",model:"teamMember"},{field:"userId",model:"passkey"},{field:"userId",model:"twoFactor"},{field:"userId",model:"oauthAccessToken"},{field:"userId",model:"oauthRefreshToken"},{field:"userId",model:"oauthConsent"},{field:"userId",model:"deviceCode"},{field:"userId",model:"walletAddress"},{field:"inviterId",model:"invitation"}],L=new Set(["accessToken","backupCodes","idToken","password","publicKey","refreshToken","secret","token"]),k=d=>Math.min(Math.max(Math.trunc(d??N),1),E),C=d=>Math.max(0,Math.trunc(d??0)),u=d=>{const m={};for(const[f,l]of Object.entries(d))L.has(f)||(m[f]=l instanceof Date?l.getTime():l);return m},g=d=>Array.isArray(d)?d.join(","):d,S=d=>d.toLowerCase().replaceAll(/[^\da-z]+/g,"-").replaceAll(/^-|-$/g,""),P=new Set(["banExpires","banned","banReason","createdAt","email","emailVerified","id","name","role","updatedAt"]),B={displayUsername:"username",phoneNumber:"phone-number",phoneNumberVerified:"phone-number",username:"username"},F=d=>d==="boolean"||d==="date"||d==="number"?d:"string",G=d=>{const m=[];for(const[f,l]of Object.entries(d))l.input===!1||l.references!==void 0||P.has(f)||m.push({name:f,plugin:B[f],required:l.required===!0,type:F(l.type),unique:l.unique===!0});return m},O=d=>typeof d=="string"&&d.includes("owner"),V=async(d,m)=>{const l=(await d.adapter.findMany({model:"member",where:[{field:"userId",value:m}]})).filter(w=>O(w.role)&&typeof w.organizationId=="string").map(w=>w.organizationId);if(l.length===0)return;const y=await Promise.all(l.map(async w=>d.adapter.findMany({model:"member",where:[{field:"organizationId",value:w}]}))),r=l.find((w,v)=>!y[v]?.some(a=>a.userId!==m&&O(a.role)));if(r!==void 0)throw new p(`user is the last owner of organization ${r} — transfer ownership or delete the organization first`,"LAST_ORGANIZATION_OWNER")},_=d=>{if(d instanceof p)return d;const m=d,f=m?.body?.code??m?.code??"AUTH_ADMIN_ERROR",l=m?.body?.message??m?.message??"auth admin operation failed";return new p(l,f)},$=(d,m={})=>{const f=d.$context,l=m.features??{},y=a=>{const e=new Set((a.plugins??[]).map(i=>i.id)),t=i=>e.has(i);return{accounts:l.accounts??!0,admin:l.admin??t("admin"),organization:l.organization??t("organization"),passkey:l.passkey??t("passkey"),twoFactor:l.twoFactor??t("two-factor")}},r=async a=>{try{return await a(await f)}catch(e){throw _(e)}},w=a=>u(a),v=async(a,e,t)=>{const i=t.where&&t.where.length>0?t.where:void 0,[n,s]=await Promise.all([a.adapter.findMany({limit:k(t.limit),model:e,offset:C(t.offset),sortBy:t.sortBy,where:i}),a.adapter.count({model:e,where:i})]);return{rows:n.map(o=>u(o)),total:s}};return{banUser:({expiresInSeconds:a,reason:e,userId:t})=>r(async i=>{let n=null;if(a!==void 0){if(!Number.isInteger(a)||a<=0)throw new p("expiresInSeconds must be a positive finite integer","INVALID_BAN_SECONDS");const o=Math.min(a,U);n=new Date(Date.now()+o*1e3)}const s=await i.internalAdapter.updateUser(t,{banExpires:n,banned:!0,banReason:e??"No reason"});return await i.internalAdapter.deleteUserSessions(t),w(s)}),cancelInvitation:({invitationId:a})=>r(async e=>{await e.adapter.delete({model:"invitation",where:[{field:"id",value:a}]})}),capabilities:()=>r(a=>Promise.resolve(y(a.options))),addMember:({organizationId:a,role:e,userId:t})=>r(async i=>{const n=await i.adapter.create({data:{createdAt:new Date,organizationId:a,role:e===void 0||e===""?"member":e,userId:t},model:"member"});return u(n)}),addTeamMember:({teamId:a,userId:e})=>r(async t=>{const i=await t.adapter.create({data:{createdAt:new Date,teamId:a,userId:e},model:"teamMember"});return u(i)}),config:()=>r(a=>{const e=a.options,t=y(e),i=new Set((e.plugins??[]).map(c=>c.id)),n=b(e),s=e.session??{},o=e.rateLimit??{};return Promise.resolve({capabilities:t,emailAndPassword:e.emailAndPassword?.enabled??!1,organization:{enabled:t.organization,roles:!!n.organizationRole,teams:!!n.team},plugins:[...i].toSorted((c,h)=>c.localeCompare(h)),rateLimit:{enabled:o.enabled??!1,max:o.max,window:o.window},session:{cookieCache:s.cookieCache?.enabled,expiresIn:s.expiresIn,freshAge:s.freshAge,updateAge:s.updateAge},socialProviders:Object.keys(e.socialProviders??{}).toSorted((c,h)=>c.localeCompare(h)),userFields:G(n.user?.fields??{})})}),createOrganization:({logo:a,metadata:e,name:t,ownerId:i,slug:n})=>r(async s=>{const o=S(n!==void 0&&n!==""?n:t);if(o==="")throw new p("could not derive a slug from the organization name","ORG_SLUG_INVALID");if(await s.adapter.findOne({model:"organization",where:[{field:"slug",value:o}]}))throw new p("an organization with this slug already exists","ORG_SLUG_TAKEN");const h=await s.adapter.create({data:{createdAt:new Date,logo:a===void 0||a===""?void 0:a,metadata:e===void 0?void 0:JSON.stringify(e),name:t,slug:o},model:"organization"});return i!==void 0&&i!==""&&await s.adapter.create({data:{createdAt:new Date,organizationId:h.id,role:"owner",userId:i},model:"member"}),u(h)}),createOrgRole:({organizationId:a,permission:e,role:t})=>r(async i=>{const n=await i.adapter.create({data:{createdAt:new Date,organizationId:a,permission:JSON.stringify(e),role:t},model:"organizationRole"});return u(n)}),createTeam:({name:a,organizationId:e})=>r(async t=>{const i=await t.adapter.create({data:{createdAt:new Date,name:a,organizationId:e},model:"team"});return u(i)}),deleteOrganization:({organizationId:a})=>r(async e=>{const t=b(e.options);if(await e.adapter.deleteMany({model:"member",where:[{field:"organizationId",value:a}]}),await e.adapter.deleteMany({model:"invitation",where:[{field:"organizationId",value:a}]}),t.team){const i=await e.adapter.findMany({model:"team",where:[{field:"organizationId",value:a}]});for(const n of i)await e.adapter.deleteMany({model:"teamMember",where:[{field:"teamId",value:n.id}]});await e.adapter.deleteMany({model:"team",where:[{field:"organizationId",value:a}]})}t.organizationRole&&await e.adapter.deleteMany({model:"organizationRole",where:[{field:"organizationId",value:a}]}),await e.adapter.delete({model:"organization",where:[{field:"id",value:a}]})}),deleteOrgRole:({roleId:a})=>r(async e=>{await e.adapter.delete({model:"organizationRole",where:[{field:"id",value:a}]})}),inviteMember:({email:a,inviterId:e,organizationId:t,role:i})=>r(async n=>{let s=e;if(s===void 0||s===""){const c=await n.adapter.findMany({model:"member",where:[{field:"organizationId",value:t}]});s=(c.find(A=>typeof A.role=="string"&&A.role.includes("owner"))??c[0])?.userId}if(s===void 0||s==="")throw new p("provide an inviter — the organization has no members to attribute the invitation to","INVITER_REQUIRED");const o=await n.adapter.create({data:{createdAt:new Date,email:a.toLowerCase(),expiresAt:new Date(Date.now()+T),inviterId:s,organizationId:t,role:i===void 0||i===""?"member":i,status:"pending"},model:"invitation"});return u(o)}),listOrgRoles:({limit:a,offset:e,organizationId:t})=>r(i=>v(i,"organizationRole",{limit:a,offset:e,sortBy:{direction:"desc",field:"createdAt"},where:[{field:"organizationId",value:t}]})),listTeamMembers:({limit:a,offset:e,teamId:t})=>r(i=>v(i,"teamMember",{limit:a,offset:e,where:[{field:"teamId",value:t}]})),listTeams:({limit:a,offset:e,organizationId:t})=>r(i=>v(i,"team",{limit:a,offset:e,sortBy:{direction:"desc",field:"createdAt"},where:[{field:"organizationId",value:t}]})),removeTeam:({teamId:a})=>r(async e=>{await e.adapter.deleteMany({model:"teamMember",where:[{field:"teamId",value:a}]}),await e.adapter.delete({model:"team",where:[{field:"id",value:a}]})}),removeTeamMember:({teamMemberId:a})=>r(async e=>{await e.adapter.delete({model:"teamMember",where:[{field:"id",value:a}]})}),updateMemberRole:({memberId:a,role:e})=>r(async t=>{const i=await t.adapter.update({model:"member",update:{role:g(e)},where:[{field:"id",value:a}]});return u(i??{id:a,role:g(e)})}),updateOrganization:({logo:a,metadata:e,name:t,organizationId:i,slug:n})=>r(async s=>{const o={};if(t!==void 0&&(o.name=t),n!==void 0&&n!==""&&(o.slug=S(n)),a!==void 0&&(o.logo=a===""?void 0:a),e!==void 0&&(o.metadata=JSON.stringify(e)),Object.keys(o).length===0)return u({id:i});const c=await s.adapter.update({model:"organization",update:o,where:[{field:"id",value:i}]});return u(c??{id:i})}),updateOrgRole:({permission:a,roleId:e})=>r(async t=>{const i=await t.adapter.update({model:"organizationRole",update:{permission:JSON.stringify(a),updatedAt:new Date},where:[{field:"id",value:e}]});return u(i??{id:e,permission:JSON.stringify(a)})}),updateTeam:({name:a,teamId:e})=>r(async t=>{const i=await t.adapter.update({model:"team",update:{name:a,updatedAt:new Date},where:[{field:"id",value:e}]});return u(i??{id:e,name:a})}),createUser:({data:a,email:e,name:t,password:i,role:n})=>r(async s=>{const o=e.toLowerCase();if(await s.internalAdapter.findUserByEmail(o))throw new p("a user with this email already exists","USER_ALREADY_EXISTS");const c=await s.internalAdapter.createUser({email:o,name:t,role:n===void 0?void 0:g(n),...a},{method:"admin"});if(i!==void 0&&i!==""){const h=await s.password.hash(i);await s.internalAdapter.linkAccount({accountId:c.id,issuer:I("credential"),password:h,providerId:"credential",userId:c.id})}return w(c)}),deletePasskey:({passkeyId:a})=>r(async e=>{await e.adapter.delete({model:"passkey",where:[{field:"id",value:a}]})}),disableTwoFactor:({userId:a})=>r(async e=>{await e.adapter.deleteMany({model:"twoFactor",where:[{field:"userId",value:a}]}),await e.internalAdapter.updateUser(a,{twoFactorEnabled:!1})}),impersonateUser:({userId:a})=>r(async e=>{const t=await e.internalAdapter.findUserById(a);if(!t)throw new p("user not found","USER_NOT_FOUND");const i=m.impersonationSeconds;let n=M;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,D)}const s=new Date(Date.now()+n*1e3),o=await e.internalAdapter.createSession(a,!0,{expiresAt:s,impersonatedBy:m.impersonatedBy??a},!0);return{expiresAt:o.expiresAt instanceof Date?o.expiresAt.getTime():s.getTime(),token:o.token,user:w(t)}}),listAccounts:({userId:a})=>r(async e=>(await e.adapter.findMany({model:"account",where:[{field:"userId",value:a}]})).map(i=>u(i))),listInvitations:({limit:a,offset:e,organizationId:t})=>r(i=>v(i,"invitation",{limit:a,offset:e,where:[{field:"organizationId",value:t}]})),listMembers:({limit:a,offset:e,organizationId:t})=>r(i=>v(i,"member",{limit:a,offset:e,sortBy:{direction:"desc",field:"createdAt"},where:[{field:"organizationId",value:t}]})),listOrganizations:({limit:a,offset:e})=>r(t=>v(t,"organization",{limit:a,offset:e,sortBy:{direction:"desc",field:"createdAt"}})),listPasskeys:({userId:a})=>r(async e=>(await e.adapter.findMany({model:"passkey",where:[{field:"userId",value:a}]})).map(i=>u(i))),listSessions:({limit:a,offset:e,userId:t})=>r(i=>v(i,"session",{limit:a,offset:e,sortBy:{direction:"desc",field:"createdAt"},where:t===void 0||t===""?void 0:[{field:"userId",value:t}]})),listUsers:({filterField:a,filterValue:e,limit:t,offset:i,search:n,searchField:s,sortBy:o,sortDirection:c})=>r(h=>{const A=[];return n!==void 0&&n!==""&&A.push({field:s??"email",operator:"contains",value:n}),e!==void 0&&A.push({field:a??"email",operator:"eq",value:e}),v(h,"user",{limit:t,offset:i,sortBy:{direction:c??"desc",field:o??"createdAt"},where:A})}),removeMember:({memberId:a})=>r(async e=>{await e.adapter.delete({model:"member",where:[{field:"id",value:a}]})}),removeUser:({userId:a})=>r(async e=>{const t=b(e.options);t.member&&await V(e,a),await Promise.all(z.filter(({model:i})=>t[i]).map(({field:i,model:n})=>e.adapter.deleteMany({model:n,where:[{field:i,value:a}]}))),await e.internalAdapter.deleteUserSessions(a),await e.internalAdapter.deleteUser(a)}),revokeUserSession:({sessionId:a})=>r(async e=>{const t=await e.adapter.findOne({model:"session",where:[{field:"id",value:a}]});t?.token&&await e.internalAdapter.deleteSession(t.token)}),revokeUserSessions:({userId:a})=>r(async e=>{await e.internalAdapter.deleteUserSessions(a)}),setRole:({role:a,userId:e})=>r(async t=>{const i=await t.internalAdapter.updateUser(e,{role:g(a)});return w(i)}),setUserPassword:({newPassword:a,userId:e})=>r(async t=>{const i=t.password.config.minPasswordLength,n=t.password.config.maxPasswordLength;if(a.length<i)throw new p(`password must be at least ${i.toString()} characters`,"PASSWORD_TOO_SHORT");if(a.length>n)throw new p(`password must be at most ${n.toString()} characters`,"PASSWORD_TOO_LONG");if(!await t.internalAdapter.findUserById(e))throw new p("user not found","USER_NOT_FOUND");const o=(await t.internalAdapter.findAccounts(e)).some(h=>h.providerId==="credential");if(!o&&t.options.emailAndPassword?.enabled!==!0)throw new p("email/password sign-in is disabled for this deployment","EMAIL_PASSWORD_DISABLED");const c=await t.password.hash(a);o?await t.internalAdapter.updatePassword(e,c):await t.internalAdapter.linkAccount({accountId:e,issuer:I("credential"),password:c,providerId:"credential",userId:e})}),unbanUser:({userId:a})=>r(async e=>{const t=await e.internalAdapter.updateUser(a,{banExpires:null,banned:!1,banReason:null});return w(t)}),unlinkAccount:({accountId:a,userId:e})=>r(async t=>{await t.adapter.delete({model:"account",where:[{field:"id",value:a},{connector:"AND",field:"userId",value:e}]})}),updateUser:({data:a,userId:e})=>r(async t=>{const i=await t.internalAdapter.updateUser(e,a);return w(i)})}};export{p as LunoraAuthAdminError,$ as createAuthAdmin};
@@ -1 +1 @@
1
- import{createAuthMiddleware as d}from"better-auth/api";import{appendAuthAuditEntry as c}from"../audit.mjs";const l=r=>{const t=r.toLowerCase(),e=i=>t===i||t.endsWith(i);if(e("/sign-up/email")||e("/sign-up"))return"sign-up";if(e("/sign-in/social")||e("/sign-in/magic-link"))return"sign-in-initiated";if(t.includes("/sign-in/")||t.includes("/callback/")||e("/magic-link/verify")||t.includes("/two-factor/verify-"))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"},o=(r,t)=>r.headers?.get(t)??r.request?.headers.get(t)??void 0,f=(r,t)=>{const e=o(r,"cf-connecting-ip");if(e!==void 0)return e;if(t===!0)return o(r,"x-forwarded-for")?.split(",")[0]?.trim()},v=r=>{const t=r.context?.newSession??r.context?.session,e=t?.user?.id??t?.session?.userId,i=t?.user?.email;return{...e===void 0?{}:{actorId:e},...i===void 0?{}:{actorEmail:i}}},h=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"},u=320,p=(r,t)=>{if(t!=="sign-in"&&t!=="sign-in-initiated")return;const e=r.body?.email??r.body?.username;if(!(typeof e!="string"||e.length===0))return e.length>u?e.slice(0,u):e},g=(r,{now:t=Date.now(),trustProxyHeaders:e}={})=>{const i=r.path===void 0?void 0:l(r.path);if(i===void 0)return;const s=f(r,e),n=o(r,"user-agent"),a=p(r,i);return{...v(r),event:i,outcome:h(r),ts:t,...s===void 0?{}:{ip:s},...a===void 0?{}:{targetEmail:a},...n===void 0?{}:{userAgent:n},detail:{path:r.path}}},m=r=>d(async t=>{try{const e=g(t,{trustProxyHeaders:r.trustProxyHeaders});if(e!==void 0){const i=await c(r.executor,e,{redactDetail:r.redactDetail,retention:r.retention});r.onRecord!==void 0&&await r.onRecord(i)}}catch(e){console.error("@lunora/auth: audit hook failed to record event",e)}}),y=(r,t)=>{const e=m(t),i=r.hooks?.after,s=i?async n=>(await i(n),e(n)):e;return{...r,hooks:{...r.hooks,after:s}}};export{m as authAuditHook,g as buildAuditEntry,l as eventForPath,y as withAuthAudit};
1
+ import{createAuthMiddleware as d}from"better-auth/api";import{appendAuthAuditEntry as c}from"../audit.mjs";const l=r=>{const t=r.toLowerCase(),e=i=>t===i||t.endsWith(i);if(e("/sign-up/email")||e("/sign-up"))return"sign-up";if(e("/sign-in/social")||e("/sign-in/magic-link"))return"sign-in-initiated";if(t.includes("/sign-in/")||t.includes("/callback/")||e("/magic-link/verify")||t.includes("/two-factor/verify-"))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"},a=(r,t)=>r.headers?.get(t)??r.request?.headers.get(t)??void 0,f=(r,t)=>{const e=a(r,"cf-connecting-ip");if(e!==void 0)return e;if(t===!0)return a(r,"x-forwarded-for")?.split(",")[0]?.trim()},v=r=>{const t=r.context?.newSession??r.context?.session,e=t?.user?.id??t?.session?.userId,i=t?.user?.email;return{...e===void 0?{}:{actorId:e},...i===void 0?{}:{actorEmail:i}}},h=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"},u=320,p=(r,t)=>{if(t!=="sign-in"&&t!=="sign-in-initiated")return;const e=r.body?.email??r.body?.username;if(!(typeof e!="string"||e.length===0))return e.length>u?e.slice(0,u):e},g=(r,{now:t=Date.now(),trustProxyHeaders:e}={})=>{const i=r.path===void 0?void 0:l(r.path);if(i===void 0)return;const s=f(r,e),n=a(r,"user-agent"),o=p(r,i);return{...v(r),event:i,outcome:h(r),ts:t,...s===void 0?{}:{ip:s},...o===void 0?{}:{targetEmail:o},...n===void 0?{}:{userAgent:n},detail:{path:r.path}}},m=r=>d(async t=>{try{const e=g(t,{trustProxyHeaders:r.trustProxyHeaders});if(e!==void 0){const i=await c(r.executor,e,{redactDetail:r.redactDetail,retention:r.retention});r.onRecord!==void 0&&await r.onRecord(i)}}catch(e){console.error("@lunora/auth: audit hook failed to record event",e)}}),y=(r,t)=>{const e=m(t),i=r.hooks?.after,s=i?async n=>{const o=await i(n);return await e(n),o}:e;return{...r,hooks:{...r.hooks,after:s}}};export{m as authAuditHook,g as buildAuditEntry,l as eventForPath,y as withAuthAudit};
@@ -0,0 +1 @@
1
+ import{INTERNAL_SECRET_HEADER as u,RESOLVE_SESSION_PATH as f,READ_AUDIT_PATH as p}from"./AUTH_DO_AUDIT_PATH-D-RB2XYs.mjs";import{DEFAULT_AUTH_BASE_PATH as A,isAuthRoutePath as R}from"./DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs";const E=c=>{const{basePath:d=A,internalSecret:o,namespace:s,objectName:l="auth"}=c,i=()=>{if(s)return s.get(s.idFromName(l))},h=async(e,t,n)=>{if(!o)return;const r=i();if(!r)return;const a=await r.fetch(new Request(new URL(e,t),{body:JSON.stringify(n),headers:{"content-type":"application/json",[u]:o},method:"POST"}));return a.ok?a:void 0};return{auditReader:{read:async e=>{const t=await h(p,"https://auth-do.invalid",e);return t?(await t.json())?.entries??[]:[]}},authHandler:async e=>{if(R(new URL(e.url).pathname,d))return i()?.fetch(e)},resolveIdentity:async e=>{if(!o)return null;const t=i();if(!t)return null;const n=new Headers(e.headers);n.set(u,o);const r=await t.fetch(new Request(new URL(f,e.url),{headers:n}));if(!r.ok)return null;const a=await r.json();return a?.userId?{userId:a.userId}:null}}};export{E as createDoAuthWiring};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/auth",
3
- "version": "1.0.0-alpha.90",
3
+ "version": "1.0.0-alpha.91",
4
4
  "description": "Auth for Lunora — a thin better-auth wrapper: email/password, OAuth, plugins, D1-backed",
5
5
  "keywords": [
6
6
  "auth",
@@ -104,7 +104,6 @@
104
104
  "@better-auth/passkey": "1.7.1",
105
105
  "@better-auth/scim": "1.7.1",
106
106
  "@lunora/errors": "1.0.0-alpha.22",
107
- "@lunora/server": "1.0.0-alpha.80",
108
107
  "@lunora/values": "1.0.0-alpha.27",
109
108
  "@visulima/disposable-email-domains": "1.1.0",
110
109
  "@visulima/email-verifier": "1.0.2",
@@ -114,6 +113,7 @@
114
113
  "peerDependencies": {
115
114
  "@better-auth/core": ">=1.7.1 <2.0.0-0",
116
115
  "@better-auth/sso": ">=1.7.1 <2.0.0-0",
116
+ "@lunora/server": ">=1.0.0-alpha.24 <2.0.0-0",
117
117
  "better-auth": ">=1.7.1 <2.0.0-0"
118
118
  },
119
119
  "peerDependenciesMeta": {
@@ -1 +0,0 @@
1
- const a="/api/auth",c=async(s,t,n=a)=>{const e=new URL(t.url),r=n.endsWith("/")?n.slice(0,-1):n;if(!(e.pathname!==r&&!e.pathname.startsWith(`${r}/`)))return s.handler(t)};export{a as DEFAULT_AUTH_BASE_PATH,c as handleAuthRequest};
@@ -1 +0,0 @@
1
- import{createLocalAccountIssuer as M}from"@better-auth/core/db";import{LunoraError as I}from"@lunora/errors";import{getAuthTables as S}from"better-auth/db";class f extends I{constructor(m,w){super(w,m,{name:"LunoraAuthAdminError"})}}const N=50,R=500,O=3600,U=O*24,D=100*365*24*60*60,E=2880*60*1e3,T=new Set(["accessToken","backupCodes","idToken","password","publicKey","refreshToken","secret","token"]),z=d=>Math.min(Math.max(Math.trunc(d??N),1),R),L=d=>Math.max(0,Math.trunc(d??0)),u=d=>{const m={};for(const[w,l]of Object.entries(d))T.has(w)||(m[w]=l instanceof Date?l.getTime():l);return m},A=d=>Array.isArray(d)?d.join(","):d,g=d=>d.toLowerCase().replaceAll(/[^\da-z]+/g,"-").replaceAll(/^-|-$/g,""),k=new Set(["banExpires","banned","banReason","createdAt","email","emailVerified","id","name","role","updatedAt"]),P={displayUsername:"username",phoneNumber:"phone-number",phoneNumberVerified:"phone-number",username:"username"},B=d=>d==="boolean"||d==="date"||d==="number"?d:"string",C=d=>{const m=[];for(const[w,l]of Object.entries(d))l.input===!1||l.references!==void 0||k.has(w)||m.push({name:w,plugin:P[w],required:l.required===!0,type:B(l.type),unique:l.unique===!0});return m},F=d=>{if(d instanceof f)return d;const m=d,w=m?.body?.code??m?.code??"AUTH_ADMIN_ERROR",l=m?.body?.message??m?.message??"auth admin operation failed";return new f(l,w)},j=(d,m={})=>{const w=d.$context,l=m.features??{},b=e=>{const a=new Set((e.plugins??[]).map(i=>i.id)),t=i=>a.has(i);return{accounts:l.accounts??!0,admin:l.admin??t("admin"),organization:l.organization??t("organization"),passkey:l.passkey??t("passkey"),twoFactor:l.twoFactor??t("two-factor")}},r=async e=>{try{return await e(await w)}catch(a){throw F(a)}},v=e=>u(e),h=async(e,a,t)=>{const i=t.where&&t.where.length>0?t.where:void 0,[n,s]=await Promise.all([e.adapter.findMany({limit:z(t.limit),model:a,offset:L(t.offset),sortBy:t.sortBy,where:i}),e.adapter.count({model:a,where:i})]);return{rows:n.map(o=>u(o)),total:s}};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 f("expiresInSeconds must be a positive finite integer","INVALID_BAN_SECONDS");const o=Math.min(e,D);n=new Date(Date.now()+o*1e3)}const s=await i.internalAdapter.updateUser(t,{banExpires:n,banned:!0,banReason:a??"No reason"});return await i.internalAdapter.deleteUserSessions(t),v(s)}),cancelInvitation:({invitationId:e})=>r(async a=>{await a.adapter.delete({model:"invitation",where:[{field:"id",value:e}]})}),capabilities:()=>r(e=>Promise.resolve(b(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 u(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 u(i)}),config:()=>r(e=>{const a=e.options,t=b(a),i=new Set((a.plugins??[]).map(c=>c.id)),n=S(a),s=a.session??{},o=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((c,p)=>c.localeCompare(p)),rateLimit:{enabled:o.enabled??!1,max:o.max,window:o.window},session:{cookieCache:s.cookieCache?.enabled,expiresIn:s.expiresIn,freshAge:s.freshAge,updateAge:s.updateAge},socialProviders:Object.keys(a.socialProviders??{}).toSorted((c,p)=>c.localeCompare(p)),userFields:C(n.user?.fields??{})})}),createOrganization:({logo:e,metadata:a,name:t,ownerId:i,slug:n})=>r(async s=>{const o=g(n!==void 0&&n!==""?n:t);if(o==="")throw new f("could not derive a slug from the organization name","ORG_SLUG_INVALID");if(await s.adapter.findOne({model:"organization",where:[{field:"slug",value:o}]}))throw new f("an organization with this slug already exists","ORG_SLUG_TAKEN");const p=await s.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:o},model:"organization"});return i!==void 0&&i!==""&&await s.adapter.create({data:{createdAt:new Date,organizationId:p.id,role:"owner",userId:i},model:"member"}),u(p)}),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 u(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 u(i)}),deleteOrganization:({organizationId:e})=>r(async a=>{const t=S(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 s=a;if(s===void 0||s===""){const c=await n.adapter.findMany({model:"member",where:[{field:"organizationId",value:t}]});s=(c.find(y=>typeof y.role=="string"&&y.role.includes("owner"))??c[0])?.userId}if(s===void 0||s==="")throw new f("provide an inviter — the organization has no members to attribute the invitation to","INVITER_REQUIRED");const o=await n.adapter.create({data:{createdAt:new Date,email:e.toLowerCase(),expiresAt:new Date(Date.now()+E),inviterId:s,organizationId:t,role:i===void 0||i===""?"member":i,status:"pending"},model:"invitation"});return u(o)}),listOrgRoles:({limit:e,offset:a,organizationId:t})=>r(i=>h(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=>h(i,"teamMember",{limit:e,offset:a,where:[{field:"teamId",value:t}]})),listTeams:({limit:e,offset:a,organizationId:t})=>r(i=>h(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:A(a)},where:[{field:"id",value:e}]});return u(i??{id:e,role:A(a)})}),updateOrganization:({logo:e,metadata:a,name:t,organizationId:i,slug:n})=>r(async s=>{const o={};if(t!==void 0&&(o.name=t),n!==void 0&&n!==""&&(o.slug=g(n)),e!==void 0&&(o.logo=e===""?void 0:e),a!==void 0&&(o.metadata=JSON.stringify(a)),Object.keys(o).length===0)return u({id:i});const c=await s.adapter.update({model:"organization",update:o,where:[{field:"id",value:i}]});return u(c??{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 u(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 u(i??{id:a,name:e})}),createUser:({data:e,email:a,name:t,password:i,role:n})=>r(async s=>{const o=a.toLowerCase();if(await s.internalAdapter.findUserByEmail(o))throw new f("a user with this email already exists","USER_ALREADY_EXISTS");const c=await s.internalAdapter.createUser({email:o,name:t,role:n===void 0?void 0:A(n),...e},{method:"admin"});if(i!==void 0&&i!==""){const p=await s.password.hash(i);await s.internalAdapter.linkAccount({accountId:c.id,issuer:M("credential"),password:p,providerId:"credential",userId:c.id})}return v(c)}),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 f("user not found","USER_NOT_FOUND");const i=m.impersonationSeconds;let n=O;if(i!==void 0){if(!Number.isInteger(i)||!Number.isFinite(i)||i<=0)throw new f("impersonationSeconds must be a positive finite integer","INVALID_IMPERSONATION_SECONDS");n=Math.min(i,U)}const s=new Date(Date.now()+n*1e3),o=await a.internalAdapter.createSession(e,!0,{expiresAt:s,impersonatedBy:m.impersonatedBy??e},!0);return{expiresAt:o.expiresAt instanceof Date?o.expiresAt.getTime():s.getTime(),token:o.token,user:v(t)}}),listAccounts:({userId:e})=>r(async a=>(await a.adapter.findMany({model:"account",where:[{field:"userId",value:e}]})).map(i=>u(i))),listInvitations:({limit:e,offset:a,organizationId:t})=>r(i=>h(i,"invitation",{limit:e,offset:a,where:[{field:"organizationId",value:t}]})),listMembers:({limit:e,offset:a,organizationId:t})=>r(i=>h(i,"member",{limit:e,offset:a,sortBy:{direction:"desc",field:"createdAt"},where:[{field:"organizationId",value:t}]})),listOrganizations:({limit:e,offset:a})=>r(t=>h(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(i=>u(i))),listSessions:({limit:e,offset:a,userId:t})=>r(i=>h(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:s,sortBy:o,sortDirection:c})=>r(p=>{const y=[];return n!==void 0&&n!==""&&y.push({field:s??"email",operator:"contains",value:n}),a!==void 0&&y.push({field:e??"email",operator:"eq",value:a}),h(p,"user",{limit:t,offset:i,sortBy:{direction:c??"desc",field:o??"createdAt"},where:y})}),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:A(e)});return v(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 f(`password must be at least ${i.toString()} characters`,"PASSWORD_TOO_SHORT");if(e.length>n)throw new f(`password must be at most ${n.toString()} characters`,"PASSWORD_TOO_LONG");const s=await t.password.hash(e);await t.internalAdapter.updatePassword(a,s)}),unbanUser:({userId:e})=>r(async a=>{const t=await a.internalAdapter.updateUser(e,{banExpires:null,banned:!1,banReason:null});return v(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 v(i)})}};export{f as LunoraAuthAdminError,j as createAuthAdmin};
@@ -1 +0,0 @@
1
- import{INTERNAL_SECRET_HEADER as u,RESOLVE_SESSION_PATH as f,READ_AUDIT_PATH as p}from"./AUTH_DO_AUDIT_PATH-WJbI0Bpc.mjs";import{DEFAULT_AUTH_BASE_PATH as w}from"./DEFAULT_AUTH_BASE_PATH-C7moXiBA.mjs";const E=c=>{const{basePath:d=w,internalSecret:o,namespace:s,objectName:l="auth"}=c,i=()=>{if(s)return s.get(s.idFromName(l))},h=async(e,t,n)=>{if(!o)return;const r=i();if(!r)return;const a=await r.fetch(new Request(new URL(e,t),{body:JSON.stringify(n),headers:{"content-type":"application/json",[u]:o},method:"POST"}));return a.ok?a:void 0};return{auditReader:{read:async e=>{const t=await h(p,"https://auth-do.invalid",e);return t?(await t.json())?.entries??[]:[]}},authHandler:async e=>{if(new URL(e.url).pathname.startsWith(d))return i()?.fetch(e)},resolveIdentity:async e=>{if(!o)return null;const t=i();if(!t)return null;const n=new Headers(e.headers);n.set(u,o);const r=await t.fetch(new Request(new URL(f,e.url),{headers:n}));if(!r.ok)return null;const a=await r.json();return a?.userId?{userId:a.userId}:null}}};export{E as createDoAuthWiring};