@lunora/auth 1.0.0-alpha.36 → 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.
- package/dist/adapter.mjs +1 -48
- package/dist/audit.mjs +2 -102
- package/dist/email-guard.mjs +1 -71
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +1 -16
- package/dist/middleware.d.mts +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.mjs +1 -56
- package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-kIwlEt8i.mjs +1 -0
- package/dist/packem_shared/LunoraAuthAdminError-BiNYZM9j.mjs +1 -0
- package/dist/packem_shared/authAuditHook-Dx3sqf3G.mjs +1 -0
- package/dist/packem_shared/compileMigrationsSql-ChiudSmt.mjs +1 -0
- package/dist/packem_shared/{create-auth.d-COcIS_KU.d.mts → create-auth.d-De6IOirt.d.mts} +1 -1
- package/dist/packem_shared/{create-auth.d-COcIS_KU.d.ts → create-auth.d-De6IOirt.d.ts} +1 -1
- package/dist/packem_shared/createAuth-DS6PL8Mb.mjs +1 -0
- package/dist/packem_shared/emailGateDatabaseHooks-DzBD1Qoq.mjs +1 -0
- package/dist/packem_shared/sessionPresets-DpEFjXKV.mjs +1 -0
- package/dist/plugins-client.mjs +1 -2
- package/dist/plugins.mjs +1 -22
- package/dist/schema.d.mts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.mjs +1 -62
- package/dist/sql-store.mjs +1 -184
- package/dist/store.mjs +1 -183
- package/dist/turnstile-middleware.mjs +1 -34
- package/dist/turnstile.mjs +1 -59
- package/package.json +4 -4
- package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-DjcUWEQl.mjs +0 -11
- package/dist/packem_shared/LunoraAuthAdminError-CReJPMkx.mjs +0 -513
- package/dist/packem_shared/authAuditHook-3OJKhpQV.mjs +0 -119
- package/dist/packem_shared/compileMigrationsSql-Dl5N8z5q.mjs +0 -29
- package/dist/packem_shared/createAuth-s4i7WhAh.mjs +0 -72
- package/dist/packem_shared/emailGateDatabaseHooks-BGS4uJM9.mjs +0 -64
- package/dist/packem_shared/sessionPresets-Dwwd74_J.mjs +0 -38
package/dist/adapter.mjs
CHANGED
|
@@ -1,48 +1 @@
|
|
|
1
|
-
import
|
|
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
|
|
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};
|
package/dist/email-guard.mjs
CHANGED
|
@@ -1,71 +1 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
4
|
-
export { c as createAuth, r as resolveAuthOptions } from "./packem_shared/create-auth.d-
|
|
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-
|
|
4
|
-
export { c as createAuth, r as resolveAuthOptions } from "./packem_shared/create-auth.d-
|
|
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
|
-
|
|
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};
|
package/dist/middleware.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
import { L as LunoraAuth } from "./packem_shared/create-auth.d-
|
|
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
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
import { L as LunoraAuth } from "./packem_shared/create-auth.d-
|
|
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
|
package/dist/middleware.mjs
CHANGED
|
@@ -1,56 +1 @@
|
|
|
1
|
-
import
|
|
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.<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 `"
|
|
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 `"
|
|
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};
|
package/dist/plugins-client.mjs
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
|
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};
|