@lunora/auth 1.0.0-alpha.5 → 1.0.0-alpha.50
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/LICENSE.md +38 -0
- package/README.md +56 -1
- package/dist/adapter.d.mts +35 -35
- package/dist/adapter.d.ts +35 -35
- package/dist/adapter.mjs +1 -47
- package/dist/audit.d.mts +114 -0
- package/dist/audit.d.ts +114 -0
- package/dist/audit.mjs +11 -0
- package/dist/email-guard.d.mts +122 -0
- package/dist/email-guard.d.ts +122 -0
- package/dist/email-guard.mjs +1 -0
- package/dist/index.d.mts +460 -146
- package/dist/index.d.ts +460 -146
- package/dist/index.mjs +1 -12
- package/dist/middleware.d.mts +156 -155
- package/dist/middleware.d.ts +156 -155
- package/dist/middleware.mjs +1 -53
- package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-kIwlEt8i.mjs +1 -0
- package/dist/packem_shared/LunoraAuthAdminError-CiHsF1qZ.mjs +1 -0
- package/dist/packem_shared/authAuditHook-Dx3sqf3G.mjs +1 -0
- package/dist/packem_shared/compileMigrationsSql-C-2ALo6c.mjs +1 -0
- package/dist/packem_shared/create-auth.d-De6IOirt.d.mts +128 -0
- package/dist/packem_shared/create-auth.d-De6IOirt.d.ts +128 -0
- 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.d.mts +2 -1
- package/dist/plugins-client.d.ts +2 -1
- package/dist/plugins-client.mjs +1 -2
- package/dist/plugins-enterprise-client.d.mts +9 -0
- package/dist/plugins-enterprise-client.d.ts +9 -0
- package/dist/plugins-enterprise-client.mjs +1 -0
- package/dist/plugins-enterprise.d.mts +1576 -0
- package/dist/plugins-enterprise.d.ts +1576 -0
- package/dist/plugins-enterprise.mjs +1 -0
- package/dist/plugins.d.mts +4 -2
- package/dist/plugins.d.ts +4 -2
- package/dist/plugins.mjs +1 -22
- package/dist/schema.d.mts +39 -39
- package/dist/schema.d.ts +39 -39
- package/dist/schema.mjs +1 -62
- package/dist/sql-store.d.mts +28 -28
- package/dist/sql-store.d.ts +28 -28
- package/dist/sql-store.mjs +1 -162
- package/dist/store.d.mts +49 -31
- package/dist/store.d.ts +49 -31
- package/dist/store.mjs +1 -170
- package/dist/turnstile-middleware.d.mts +55 -55
- package/dist/turnstile-middleware.d.ts +55 -55
- package/dist/turnstile-middleware.mjs +1 -45
- package/dist/turnstile.d.mts +42 -59
- package/dist/turnstile.d.ts +42 -59
- package/dist/turnstile.mjs +1 -61
- package/package.json +39 -6
- package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-DjcUWEQl.mjs +0 -11
- package/dist/packem_shared/LunoraAuthAdminError-BxrfEeA_.mjs +0 -249
- package/dist/packem_shared/compileMigrationsSql-wZH3oXDu.mjs +0 -28
- package/dist/packem_shared/create-auth.d-M36jwG_Y.d.mts +0 -58
- package/dist/packem_shared/create-auth.d-M36jwG_Y.d.ts +0 -58
- package/dist/packem_shared/createAuth-B-tvsvQU.mjs +0 -56
- package/dist/packem_shared/sessionPresets-B95rXrd8.mjs +0 -35
package/dist/store.d.mts
CHANGED
|
@@ -2,10 +2,10 @@ import { CustomAdapter } from 'better-auth/adapters';
|
|
|
2
2
|
/** A stored auth row — an opaque bag of columns keyed by better-auth field name. */
|
|
3
3
|
type AuthRow = Record<string, unknown>;
|
|
4
4
|
/**
|
|
5
|
-
* One normalized better-auth where clause. Derived from {@link CustomAdapter}'s
|
|
6
|
-
* own method signature (rather than re-declared) so a better-auth change to the
|
|
7
|
-
* clause shape surfaces as a compile error here, not a silent mis-match.
|
|
8
|
-
*/
|
|
5
|
+
* One normalized better-auth where clause. Derived from {@link CustomAdapter}'s
|
|
6
|
+
* own method signature (rather than re-declared) so a better-auth change to the
|
|
7
|
+
* clause shape surfaces as a compile error here, not a silent mis-match.
|
|
8
|
+
*/
|
|
9
9
|
type AuthWhereClause = NonNullable<Parameters<CustomAdapter["findOne"]>[0]["where"]>[number];
|
|
10
10
|
/** Read query handed to {@link AuthStore.read}: a where filter plus optional sort/window. */
|
|
11
11
|
interface AuthQuery {
|
|
@@ -18,31 +18,49 @@ interface AuthQuery {
|
|
|
18
18
|
where: ReadonlyArray<AuthWhereClause>;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* The minimal table-addressed store the `lunoraAuthAdapter` drives. This is the
|
|
22
|
-
* seam a Lunora runtime binds to its ORM: back each method with `ctx.db` over
|
|
23
|
-
* the global (D1) auth tables that `authTables(...)` generates, and better-auth's
|
|
24
|
-
* reads/writes flow through Lunora's data layer (triggers, aggregates, OCC)
|
|
25
|
-
* instead of better-auth's own adapter. Field names and table (`model`) names are
|
|
26
|
-
* already the database names better-auth resolved from the schema, so a store
|
|
27
|
-
* passes them straight through.
|
|
28
|
-
*
|
|
29
|
-
* {@link createMemoryAuthStore} is a reference in-memory implementation (also
|
|
30
|
-
* what the tests run better-auth against); `createSqlAuthStore` is the SQL one.
|
|
31
|
-
*/
|
|
21
|
+
* The minimal table-addressed store the `lunoraAuthAdapter` drives. This is the
|
|
22
|
+
* seam a Lunora runtime binds to its ORM: back each method with `ctx.db` over
|
|
23
|
+
* the global (D1) auth tables that `authTables(...)` generates, and better-auth's
|
|
24
|
+
* reads/writes flow through Lunora's data layer (triggers, aggregates, OCC)
|
|
25
|
+
* instead of better-auth's own adapter. Field names and table (`model`) names are
|
|
26
|
+
* already the database names better-auth resolved from the schema, so a store
|
|
27
|
+
* passes them straight through.
|
|
28
|
+
*
|
|
29
|
+
* {@link createMemoryAuthStore} is a reference in-memory implementation (also
|
|
30
|
+
* what the tests run better-auth against); `createSqlAuthStore` is the SQL one.
|
|
31
|
+
*/
|
|
32
32
|
interface AuthStore {
|
|
33
33
|
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
* Atomically delete **at most one** row in `model` matching `where` and
|
|
35
|
+
* return it (or `undefined` if none matched). Backs better-auth's
|
|
36
|
+
* single-use-token consume (OTP / magic-link / email-verification /
|
|
37
|
+
* password-reset): implementing it natively — one round trip that finds and
|
|
38
|
+
* deletes in a single statement — closes the read-then-delete race the
|
|
39
|
+
* factory's `findMany` + `deleteMany` fallback would otherwise leave open.
|
|
40
|
+
*/
|
|
41
41
|
consumeOne: (model: string, where: ReadonlyArray<AuthWhereClause>) => Promise<AuthRow | undefined>;
|
|
42
42
|
/** Count rows in `model` matching `where` (empty `where` = all rows). */
|
|
43
43
|
count: (model: string, where: ReadonlyArray<AuthWhereClause>) => Promise<number>;
|
|
44
44
|
/** Insert `data` into `model`; return the stored row (the adapter pre-fills `id`). */
|
|
45
45
|
create: (model: string, data: AuthRow) => Promise<AuthRow>;
|
|
46
|
+
/**
|
|
47
|
+
* Atomically apply signed numeric deltas to **at most one** row in `model`
|
|
48
|
+
* matching `where`, then return the updated row (or `undefined` if the guard
|
|
49
|
+
* matched none). For each `increment` entry it applies `field = field + delta`
|
|
50
|
+
* (a negative delta decrements); the optional `set` map assigns absolute
|
|
51
|
+
* values in the same step. The `where` clause is both selector **and** guard
|
|
52
|
+
* — comparison operators are honoured, so a guard like
|
|
53
|
+
* `{ field: "count", operator: "lt", value: max }` only mutates the row while
|
|
54
|
+
* it still satisfies the predicate.
|
|
55
|
+
*
|
|
56
|
+
* Backs better-auth's durable (`storage: "database"`) rate limiter, whose
|
|
57
|
+
* counter rides these tables. Implementing it natively — one statement that
|
|
58
|
+
* guards, increments, and returns — gives the **one-winner-across-isolates**
|
|
59
|
+
* guarantee the read-then-update fallback cannot: on Workers two concurrent
|
|
60
|
+
* requests would otherwise both read `count=4` and both write `5`, letting a
|
|
61
|
+
* `max` of 5 pass 6+. Same race-closing rationale as {@link AuthStore.consumeOne}.
|
|
62
|
+
*/
|
|
63
|
+
incrementOne: (model: string, where: ReadonlyArray<AuthWhereClause>, increment: Record<string, number>, set?: AuthRow) => Promise<AuthRow | undefined>;
|
|
46
64
|
/** Read rows from `model` honouring the filter/sort/window in `query`. */
|
|
47
65
|
read: (model: string, query: AuthQuery) => Promise<AuthRow[]>;
|
|
48
66
|
/** Delete rows in `model` matching `where`; return how many were removed. */
|
|
@@ -51,16 +69,16 @@ interface AuthStore {
|
|
|
51
69
|
update: (model: string, where: ReadonlyArray<AuthWhereClause>, values: AuthRow) => Promise<AuthRow[]>;
|
|
52
70
|
}
|
|
53
71
|
/**
|
|
54
|
-
* Evaluate a better-auth where clause list against a row. Clauses fold
|
|
55
|
-
* left-to-right by their `connector` (`AND` by default, `OR` when set) — the
|
|
56
|
-
* same precedence better-auth's own adapters use. An empty list matches every
|
|
57
|
-
* row. Exported so any in-memory-style {@link AuthStore} can reuse it.
|
|
58
|
-
*/
|
|
72
|
+
* Evaluate a better-auth where clause list against a row. Clauses fold
|
|
73
|
+
* left-to-right by their `connector` (`AND` by default, `OR` when set) — the
|
|
74
|
+
* same precedence better-auth's own adapters use. An empty list matches every
|
|
75
|
+
* row. Exported so any in-memory-style {@link AuthStore} can reuse it.
|
|
76
|
+
*/
|
|
59
77
|
declare const matchesWhere: (row: AuthRow, where: ReadonlyArray<AuthWhereClause>) => boolean;
|
|
60
78
|
/**
|
|
61
|
-
* Reference in-memory {@link AuthStore} — used to run better-auth end to end in
|
|
62
|
-
* tests, and a worked example of the contract a Lunora-`ctx.db`-backed store
|
|
63
|
-
* fulfils. Not for production (state is per-instance and non-durable).
|
|
64
|
-
*/
|
|
79
|
+
* Reference in-memory {@link AuthStore} — used to run better-auth end to end in
|
|
80
|
+
* tests, and a worked example of the contract a Lunora-`ctx.db`-backed store
|
|
81
|
+
* fulfils. Not for production (state is per-instance and non-durable).
|
|
82
|
+
*/
|
|
65
83
|
declare const createMemoryAuthStore: () => AuthStore;
|
|
66
84
|
export { AuthQuery, type AuthRow, AuthStore, type AuthWhereClause, createMemoryAuthStore, matchesWhere };
|
package/dist/store.d.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { CustomAdapter } from 'better-auth/adapters';
|
|
|
2
2
|
/** A stored auth row — an opaque bag of columns keyed by better-auth field name. */
|
|
3
3
|
type AuthRow = Record<string, unknown>;
|
|
4
4
|
/**
|
|
5
|
-
* One normalized better-auth where clause. Derived from {@link CustomAdapter}'s
|
|
6
|
-
* own method signature (rather than re-declared) so a better-auth change to the
|
|
7
|
-
* clause shape surfaces as a compile error here, not a silent mis-match.
|
|
8
|
-
*/
|
|
5
|
+
* One normalized better-auth where clause. Derived from {@link CustomAdapter}'s
|
|
6
|
+
* own method signature (rather than re-declared) so a better-auth change to the
|
|
7
|
+
* clause shape surfaces as a compile error here, not a silent mis-match.
|
|
8
|
+
*/
|
|
9
9
|
type AuthWhereClause = NonNullable<Parameters<CustomAdapter["findOne"]>[0]["where"]>[number];
|
|
10
10
|
/** Read query handed to {@link AuthStore.read}: a where filter plus optional sort/window. */
|
|
11
11
|
interface AuthQuery {
|
|
@@ -18,31 +18,49 @@ interface AuthQuery {
|
|
|
18
18
|
where: ReadonlyArray<AuthWhereClause>;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* The minimal table-addressed store the `lunoraAuthAdapter` drives. This is the
|
|
22
|
-
* seam a Lunora runtime binds to its ORM: back each method with `ctx.db` over
|
|
23
|
-
* the global (D1) auth tables that `authTables(...)` generates, and better-auth's
|
|
24
|
-
* reads/writes flow through Lunora's data layer (triggers, aggregates, OCC)
|
|
25
|
-
* instead of better-auth's own adapter. Field names and table (`model`) names are
|
|
26
|
-
* already the database names better-auth resolved from the schema, so a store
|
|
27
|
-
* passes them straight through.
|
|
28
|
-
*
|
|
29
|
-
* {@link createMemoryAuthStore} is a reference in-memory implementation (also
|
|
30
|
-
* what the tests run better-auth against); `createSqlAuthStore` is the SQL one.
|
|
31
|
-
*/
|
|
21
|
+
* The minimal table-addressed store the `lunoraAuthAdapter` drives. This is the
|
|
22
|
+
* seam a Lunora runtime binds to its ORM: back each method with `ctx.db` over
|
|
23
|
+
* the global (D1) auth tables that `authTables(...)` generates, and better-auth's
|
|
24
|
+
* reads/writes flow through Lunora's data layer (triggers, aggregates, OCC)
|
|
25
|
+
* instead of better-auth's own adapter. Field names and table (`model`) names are
|
|
26
|
+
* already the database names better-auth resolved from the schema, so a store
|
|
27
|
+
* passes them straight through.
|
|
28
|
+
*
|
|
29
|
+
* {@link createMemoryAuthStore} is a reference in-memory implementation (also
|
|
30
|
+
* what the tests run better-auth against); `createSqlAuthStore` is the SQL one.
|
|
31
|
+
*/
|
|
32
32
|
interface AuthStore {
|
|
33
33
|
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
* Atomically delete **at most one** row in `model` matching `where` and
|
|
35
|
+
* return it (or `undefined` if none matched). Backs better-auth's
|
|
36
|
+
* single-use-token consume (OTP / magic-link / email-verification /
|
|
37
|
+
* password-reset): implementing it natively — one round trip that finds and
|
|
38
|
+
* deletes in a single statement — closes the read-then-delete race the
|
|
39
|
+
* factory's `findMany` + `deleteMany` fallback would otherwise leave open.
|
|
40
|
+
*/
|
|
41
41
|
consumeOne: (model: string, where: ReadonlyArray<AuthWhereClause>) => Promise<AuthRow | undefined>;
|
|
42
42
|
/** Count rows in `model` matching `where` (empty `where` = all rows). */
|
|
43
43
|
count: (model: string, where: ReadonlyArray<AuthWhereClause>) => Promise<number>;
|
|
44
44
|
/** Insert `data` into `model`; return the stored row (the adapter pre-fills `id`). */
|
|
45
45
|
create: (model: string, data: AuthRow) => Promise<AuthRow>;
|
|
46
|
+
/**
|
|
47
|
+
* Atomically apply signed numeric deltas to **at most one** row in `model`
|
|
48
|
+
* matching `where`, then return the updated row (or `undefined` if the guard
|
|
49
|
+
* matched none). For each `increment` entry it applies `field = field + delta`
|
|
50
|
+
* (a negative delta decrements); the optional `set` map assigns absolute
|
|
51
|
+
* values in the same step. The `where` clause is both selector **and** guard
|
|
52
|
+
* — comparison operators are honoured, so a guard like
|
|
53
|
+
* `{ field: "count", operator: "lt", value: max }` only mutates the row while
|
|
54
|
+
* it still satisfies the predicate.
|
|
55
|
+
*
|
|
56
|
+
* Backs better-auth's durable (`storage: "database"`) rate limiter, whose
|
|
57
|
+
* counter rides these tables. Implementing it natively — one statement that
|
|
58
|
+
* guards, increments, and returns — gives the **one-winner-across-isolates**
|
|
59
|
+
* guarantee the read-then-update fallback cannot: on Workers two concurrent
|
|
60
|
+
* requests would otherwise both read `count=4` and both write `5`, letting a
|
|
61
|
+
* `max` of 5 pass 6+. Same race-closing rationale as {@link AuthStore.consumeOne}.
|
|
62
|
+
*/
|
|
63
|
+
incrementOne: (model: string, where: ReadonlyArray<AuthWhereClause>, increment: Record<string, number>, set?: AuthRow) => Promise<AuthRow | undefined>;
|
|
46
64
|
/** Read rows from `model` honouring the filter/sort/window in `query`. */
|
|
47
65
|
read: (model: string, query: AuthQuery) => Promise<AuthRow[]>;
|
|
48
66
|
/** Delete rows in `model` matching `where`; return how many were removed. */
|
|
@@ -51,16 +69,16 @@ interface AuthStore {
|
|
|
51
69
|
update: (model: string, where: ReadonlyArray<AuthWhereClause>, values: AuthRow) => Promise<AuthRow[]>;
|
|
52
70
|
}
|
|
53
71
|
/**
|
|
54
|
-
* Evaluate a better-auth where clause list against a row. Clauses fold
|
|
55
|
-
* left-to-right by their `connector` (`AND` by default, `OR` when set) — the
|
|
56
|
-
* same precedence better-auth's own adapters use. An empty list matches every
|
|
57
|
-
* row. Exported so any in-memory-style {@link AuthStore} can reuse it.
|
|
58
|
-
*/
|
|
72
|
+
* Evaluate a better-auth where clause list against a row. Clauses fold
|
|
73
|
+
* left-to-right by their `connector` (`AND` by default, `OR` when set) — the
|
|
74
|
+
* same precedence better-auth's own adapters use. An empty list matches every
|
|
75
|
+
* row. Exported so any in-memory-style {@link AuthStore} can reuse it.
|
|
76
|
+
*/
|
|
59
77
|
declare const matchesWhere: (row: AuthRow, where: ReadonlyArray<AuthWhereClause>) => boolean;
|
|
60
78
|
/**
|
|
61
|
-
* Reference in-memory {@link AuthStore} — used to run better-auth end to end in
|
|
62
|
-
* tests, and a worked example of the contract a Lunora-`ctx.db`-backed store
|
|
63
|
-
* fulfils. Not for production (state is per-instance and non-durable).
|
|
64
|
-
*/
|
|
79
|
+
* Reference in-memory {@link AuthStore} — used to run better-auth end to end in
|
|
80
|
+
* tests, and a worked example of the contract a Lunora-`ctx.db`-backed store
|
|
81
|
+
* fulfils. Not for production (state is per-instance and non-durable).
|
|
82
|
+
*/
|
|
65
83
|
declare const createMemoryAuthStore: () => AuthStore;
|
|
66
84
|
export { AuthQuery, type AuthRow, AuthStore, type AuthWhereClause, createMemoryAuthStore, matchesWhere };
|
package/dist/store.mjs
CHANGED
|
@@ -1,170 +1 @@
|
|
|
1
|
-
const
|
|
2
|
-
const isNullish = (value) => value === void 0 || value === null;
|
|
3
|
-
const looseEquals = (left, right, insensitive) => {
|
|
4
|
-
if (insensitive) {
|
|
5
|
-
const a = asString(left);
|
|
6
|
-
const b = asString(right);
|
|
7
|
-
if (a !== void 0 && b !== void 0) {
|
|
8
|
-
return a.toLowerCase() === b.toLowerCase();
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
return left === right;
|
|
12
|
-
};
|
|
13
|
-
const matchesPattern = (cell, value, operator, insensitive) => {
|
|
14
|
-
const haystack = asString(cell);
|
|
15
|
-
const needle = asString(value);
|
|
16
|
-
if (haystack === void 0 || needle === void 0) {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
const [a, b] = insensitive ? [haystack.toLowerCase(), needle.toLowerCase()] : [haystack, needle];
|
|
20
|
-
if (operator === "contains") {
|
|
21
|
-
return a.includes(b);
|
|
22
|
-
}
|
|
23
|
-
return operator === "starts_with" ? a.startsWith(b) : a.endsWith(b);
|
|
24
|
-
};
|
|
25
|
-
const evaluateClause = (row, clause) => {
|
|
26
|
-
const { field, mode, operator, value } = clause;
|
|
27
|
-
const cell = row[field];
|
|
28
|
-
const insensitive = mode === "insensitive";
|
|
29
|
-
switch (operator) {
|
|
30
|
-
case "contains":
|
|
31
|
-
case "ends_with":
|
|
32
|
-
case "starts_with": {
|
|
33
|
-
return matchesPattern(cell, value, operator, insensitive);
|
|
34
|
-
}
|
|
35
|
-
case "gt": {
|
|
36
|
-
return !isNullish(value) && cell > value;
|
|
37
|
-
}
|
|
38
|
-
case "gte": {
|
|
39
|
-
return !isNullish(value) && cell >= value;
|
|
40
|
-
}
|
|
41
|
-
case "in": {
|
|
42
|
-
return Array.isArray(value) && value.some((entry) => looseEquals(cell, entry, insensitive));
|
|
43
|
-
}
|
|
44
|
-
case "lt": {
|
|
45
|
-
return !isNullish(value) && cell < value;
|
|
46
|
-
}
|
|
47
|
-
case "lte": {
|
|
48
|
-
return !isNullish(value) && cell <= value;
|
|
49
|
-
}
|
|
50
|
-
case "ne": {
|
|
51
|
-
return !looseEquals(cell, value, insensitive);
|
|
52
|
-
}
|
|
53
|
-
case "not_in": {
|
|
54
|
-
return Array.isArray(value) && !value.some((entry) => looseEquals(cell, entry, insensitive));
|
|
55
|
-
}
|
|
56
|
-
// "eq" and the default: null-aware equality.
|
|
57
|
-
default: {
|
|
58
|
-
return isNullish(value) ? isNullish(cell) : looseEquals(cell, value, insensitive);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
const compareCells = (a, b) => {
|
|
63
|
-
if (typeof a === "string" && typeof b === "string") {
|
|
64
|
-
return a.localeCompare(b);
|
|
65
|
-
}
|
|
66
|
-
const left = Number(a);
|
|
67
|
-
const right = Number(b);
|
|
68
|
-
if (left < right) {
|
|
69
|
-
return -1;
|
|
70
|
-
}
|
|
71
|
-
return left > right ? 1 : 0;
|
|
72
|
-
};
|
|
73
|
-
const sortRows = (rows, sortBy) => {
|
|
74
|
-
const direction = sortBy.direction === "asc" ? 1 : -1;
|
|
75
|
-
return rows.toSorted((left, right) => {
|
|
76
|
-
const a = left[sortBy.field];
|
|
77
|
-
const b = right[sortBy.field];
|
|
78
|
-
if (isNullish(a) && isNullish(b)) {
|
|
79
|
-
return 0;
|
|
80
|
-
}
|
|
81
|
-
if (isNullish(a)) {
|
|
82
|
-
return -direction;
|
|
83
|
-
}
|
|
84
|
-
if (isNullish(b)) {
|
|
85
|
-
return direction;
|
|
86
|
-
}
|
|
87
|
-
return compareCells(a, b) * direction;
|
|
88
|
-
});
|
|
89
|
-
};
|
|
90
|
-
const matchesWhere = (row, where) => {
|
|
91
|
-
let result = true;
|
|
92
|
-
for (const [index, clause] of where.entries()) {
|
|
93
|
-
const clauseResult = evaluateClause(row, clause);
|
|
94
|
-
if (index === 0) {
|
|
95
|
-
result = clauseResult;
|
|
96
|
-
} else if (clause.connector === "OR") {
|
|
97
|
-
result = result || clauseResult;
|
|
98
|
-
} else {
|
|
99
|
-
result = result && clauseResult;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return result;
|
|
103
|
-
};
|
|
104
|
-
const createMemoryAuthStore = () => {
|
|
105
|
-
const tables = /* @__PURE__ */ new Map();
|
|
106
|
-
const tableOf = (model) => {
|
|
107
|
-
const existing = tables.get(model);
|
|
108
|
-
if (existing) {
|
|
109
|
-
return existing;
|
|
110
|
-
}
|
|
111
|
-
const created = [];
|
|
112
|
-
tables.set(model, created);
|
|
113
|
-
return created;
|
|
114
|
-
};
|
|
115
|
-
return {
|
|
116
|
-
consumeOne: (model, where) => {
|
|
117
|
-
const table = tableOf(model);
|
|
118
|
-
const index = table.findIndex((row2) => matchesWhere(row2, where));
|
|
119
|
-
if (index === -1) {
|
|
120
|
-
return Promise.resolve(void 0);
|
|
121
|
-
}
|
|
122
|
-
const [row] = table.splice(index, 1);
|
|
123
|
-
return Promise.resolve(row ? { ...row } : void 0);
|
|
124
|
-
},
|
|
125
|
-
count: (model, where) => Promise.resolve(tableOf(model).filter((row) => matchesWhere(row, where)).length),
|
|
126
|
-
create: (model, data) => {
|
|
127
|
-
const row = { ...data };
|
|
128
|
-
tableOf(model).push(row);
|
|
129
|
-
return Promise.resolve({ ...row });
|
|
130
|
-
},
|
|
131
|
-
read: (model, query) => {
|
|
132
|
-
let rows = tableOf(model).filter((row) => matchesWhere(row, query.where));
|
|
133
|
-
if (query.sortBy) {
|
|
134
|
-
rows = sortRows(rows, query.sortBy);
|
|
135
|
-
}
|
|
136
|
-
if (query.offset) {
|
|
137
|
-
rows = rows.slice(query.offset);
|
|
138
|
-
}
|
|
139
|
-
if (query.limit !== void 0) {
|
|
140
|
-
rows = rows.slice(0, query.limit);
|
|
141
|
-
}
|
|
142
|
-
return Promise.resolve(
|
|
143
|
-
rows.map((row) => {
|
|
144
|
-
return { ...row };
|
|
145
|
-
})
|
|
146
|
-
);
|
|
147
|
-
},
|
|
148
|
-
remove: (model, where) => {
|
|
149
|
-
const table = tableOf(model);
|
|
150
|
-
const kept = table.filter((row) => !matchesWhere(row, where));
|
|
151
|
-
const removed = table.length - kept.length;
|
|
152
|
-
table.length = 0;
|
|
153
|
-
table.push(...kept);
|
|
154
|
-
return Promise.resolve(removed);
|
|
155
|
-
},
|
|
156
|
-
update: (model, where, values) => {
|
|
157
|
-
const matched = tableOf(model).filter((row) => matchesWhere(row, where));
|
|
158
|
-
for (const row of matched) {
|
|
159
|
-
Object.assign(row, values);
|
|
160
|
-
}
|
|
161
|
-
return Promise.resolve(
|
|
162
|
-
matched.map((row) => {
|
|
163
|
-
return { ...row };
|
|
164
|
-
})
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
export { createMemoryAuthStore, matchesWhere };
|
|
1
|
+
const m=i=>typeof i=="string"?i:void 0,a=i=>i==null,f=(i,n,r)=>{if(r){const o=m(i),t=m(n);if(o!==void 0&&t!==void 0)return o.toLowerCase()===t.toLowerCase()}return i===n},d=(i,n,r,o)=>{const t=m(i),e=m(n);if(t===void 0||e===void 0)return!1;const[s,c]=o?[t.toLowerCase(),e.toLowerCase()]:[t,e];return r==="contains"?s.includes(c):r==="starts_with"?s.startsWith(c):s.endsWith(c)},v=(i,n)=>{const{field:r,mode:o,operator:t,value:e}=n,s=i[r],c=o==="insensitive";switch(t){case"contains":case"ends_with":case"starts_with":return d(s,e,t,c);case"gt":return!a(e)&&s>e;case"gte":return!a(e)&&s>=e;case"in":return Array.isArray(e)&&e.some(l=>f(s,l,c));case"lt":return!a(e)&&s<e;case"lte":return!a(e)&&s<=e;case"ne":return!f(s,e,c);case"not_in":return Array.isArray(e)&&!e.some(l=>f(s,l,c));default:return a(e)?a(s):f(s,e,c)}},h=(i,n)=>{if(typeof i=="string"&&typeof n=="string")return i.localeCompare(n);const r=Number(i),o=Number(n);return r<o?-1:r>o?1:0},p=(i,n)=>{const r=n.direction==="asc"?1:-1;return i.toSorted((o,t)=>{const e=o[n.field],s=t[n.field];return a(e)&&a(s)?0:a(e)?-r:a(s)?r:h(e,s)*r})},u=(i,n)=>{let r=!0;for(const[o,t]of n.entries()){const e=v(i,t);o===0?r=e:t.connector==="OR"?r=r||e:r=r&&e}return r},g=()=>{const i=new Map,n=r=>{const o=i.get(r);if(o)return o;const t=[];return i.set(r,t),t};return{consumeOne:(r,o)=>{const t=n(r),e=t.findIndex(c=>u(c,o));if(e===-1)return Promise.resolve(void 0);const[s]=t.splice(e,1);return Promise.resolve(s?{...s}:void 0)},count:(r,o)=>Promise.resolve(n(r).filter(t=>u(t,o)).length),create:(r,o)=>{const t={...o};return n(r).push(t),Promise.resolve({...t})},incrementOne:(r,o,t,e)=>{const s=n(r).find(c=>u(c,o));if(!s)return Promise.resolve(void 0);for(const[c,l]of Object.entries(t))s[c]=(typeof s[c]=="number"?s[c]:0)+l;return e&&Object.assign(s,e),Promise.resolve({...s})},read:(r,o)=>{let t=n(r).filter(e=>u(e,o.where));return o.sortBy&&(t=p(t,o.sortBy)),o.offset&&(t=t.slice(o.offset)),o.limit!==void 0&&(t=t.slice(0,o.limit)),Promise.resolve(t.map(e=>({...e})))},remove:(r,o)=>{const t=n(r),e=t.filter(c=>!u(c,o)),s=t.length-e.length;return t.length=0,t.push(...e),Promise.resolve(s)},update:(r,o,t)=>{const e=n(r).filter(s=>u(s,o));for(const s of e)Object.assign(s,t);return Promise.resolve(e.map(s=>({...s})))}}};export{g as createMemoryAuthStore,u as matchesWhere};
|
|
@@ -2,79 +2,79 @@ import { Middleware } from '@lunora/server';
|
|
|
2
2
|
import { FetchLike, TurnstileVerifyResult } from "./turnstile.mjs";
|
|
3
3
|
interface VerifyTurnstileMiddlewareOptions<Context> {
|
|
4
4
|
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
* Assert the widget `action` the token was solved for (forwarded to
|
|
6
|
+
* {@link verifyTurnstile}). When set and the siteverify response's `action`
|
|
7
|
+
* does not match, the verdict is treated as a failure (403). Optional.
|
|
8
|
+
*/
|
|
9
9
|
expectedAction?: string;
|
|
10
10
|
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
* Assert the `hostname` the challenge was solved on (forwarded to
|
|
12
|
+
* {@link verifyTurnstile}). When set and the siteverify response's `hostname`
|
|
13
|
+
* does not match, the verdict is treated as a failure (403). Set this when a
|
|
14
|
+
* single secret/sitekey is shared across multiple domains to stop a token
|
|
15
|
+
* harvested on one origin being replayed against this procedure. Optional.
|
|
16
|
+
*/
|
|
17
17
|
expectedHostname?: string;
|
|
18
18
|
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
* Behavior when the siteverify call itself throws (network error, non-2xx).
|
|
20
|
+
* Defaults to `false` (**fail closed**: reject with 403). Set `true` only
|
|
21
|
+
* when degraded availability is preferable to denying traffic — a failing
|
|
22
|
+
* siteverify then admits every request. Mirrors `@lunora/ratelimit`'s
|
|
23
|
+
* `rateLimit` failure policy.
|
|
24
|
+
*/
|
|
25
25
|
failOpen?: boolean;
|
|
26
26
|
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
* Inject a `fetch` implementation (forwarded to {@link verifyTurnstile}).
|
|
28
|
+
* Primarily for tests.
|
|
29
|
+
*/
|
|
30
30
|
fetch?: FetchLike;
|
|
31
31
|
/** Override the error message thrown on a failed verdict. */
|
|
32
32
|
message?: string;
|
|
33
33
|
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
* Selector that pulls the visitor IP from `ctx` (the procedure context has
|
|
35
|
+
* no raw `Headers`, so this must come from `args`/ctx). Optional.
|
|
36
|
+
*/
|
|
37
37
|
remoteip?: (context: Context) => string | undefined;
|
|
38
38
|
/** Your Turnstile secret key (the `TURNSTILE_SECRET_KEY` env var). */
|
|
39
39
|
secret: string;
|
|
40
40
|
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
* Selector that pulls the `cf-turnstile-response` token from `ctx`. The
|
|
42
|
+
* procedure context carries only the resolved identity, **not** the raw
|
|
43
|
+
* inbound `Headers` (see `withAuthPlugins` in `./middleware`), so the token
|
|
44
|
+
* must travel in the function `args` and be read out here.
|
|
45
|
+
*/
|
|
46
46
|
token: (context: Context) => string | undefined;
|
|
47
47
|
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
* Extra predicate run on a `success: true` verdict (after the built-in
|
|
49
|
+
* `expectedHostname`/`expectedAction` checks). Return `false` to reject the
|
|
50
|
+
* request with 403 — use it for any custom replay/abuse guard that needs the
|
|
51
|
+
* full verdict (e.g. matching `cdata`, or one hostname out of an allow-list).
|
|
52
|
+
*/
|
|
53
53
|
validate?: (result: TurnstileVerifyResult) => boolean;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
* Procedure middleware that enforces a Turnstile (CAPTCHA) check before the
|
|
57
|
-
* handler runs. Attach it with `.use()`. It reads the token (and optional IP)
|
|
58
|
-
* from `ctx` via the provided selectors — because the procedure context does
|
|
59
|
-
* not carry raw request headers, the token must be passed through the function
|
|
60
|
-
* `args`. To gate the better-auth sign-in/sign-up flow itself, prefer
|
|
61
|
-
* better-auth's native `captcha` plugin (re-exported from `@lunora/auth/plugins`)
|
|
62
|
-
* — this middleware is for non-auth Lunora procedures.
|
|
63
|
-
*
|
|
64
|
-
* On a `success: false` verdict (or a missing token) it throws a structural
|
|
65
|
-
* `LunoraError` (`{ name: "LunoraError", code: "FORBIDDEN", status: 403 }`) —
|
|
66
|
-
* the runtime maps it to the matching RPC/HTTP status without any runtime
|
|
67
|
-
* import of `@lunora/server` (the `Middleware` import is type-only).
|
|
68
|
-
*
|
|
69
|
-
* **Failure policy:** if the siteverify call itself throws, the middleware
|
|
70
|
-
* **fails closed by default** (logs and rejects with 403). Pass
|
|
71
|
-
* `failOpen: true` to admit the request instead — mirrors `@lunora/ratelimit`.
|
|
72
|
-
*
|
|
73
|
-
* **Cross-origin replay:** a token solved on one origin can be replayed against
|
|
74
|
-
* a different endpoint when a single secret/sitekey is shared across multiple
|
|
75
|
-
* domains. Pass `expectedHostname` (and optionally `expectedAction`, or a custom
|
|
76
|
-
* `validate(result)` predicate) to assert the siteverify response's `hostname`
|
|
77
|
-
* /`action` and reject mismatches with 403.
|
|
78
|
-
*/
|
|
56
|
+
* Procedure middleware that enforces a Turnstile (CAPTCHA) check before the
|
|
57
|
+
* handler runs. Attach it with `.use()`. It reads the token (and optional IP)
|
|
58
|
+
* from `ctx` via the provided selectors — because the procedure context does
|
|
59
|
+
* not carry raw request headers, the token must be passed through the function
|
|
60
|
+
* `args`. To gate the better-auth sign-in/sign-up flow itself, prefer
|
|
61
|
+
* better-auth's native `captcha` plugin (re-exported from `@lunora/auth/plugins`)
|
|
62
|
+
* — this middleware is for non-auth Lunora procedures.
|
|
63
|
+
*
|
|
64
|
+
* On a `success: false` verdict (or a missing token) it throws a structural
|
|
65
|
+
* `LunoraError` (`{ name: "LunoraError", code: "FORBIDDEN", status: 403 }`) —
|
|
66
|
+
* the runtime maps it to the matching RPC/HTTP status without any runtime
|
|
67
|
+
* import of `@lunora/server` (the `Middleware` import is type-only).
|
|
68
|
+
*
|
|
69
|
+
* **Failure policy:** if the siteverify call itself throws, the middleware
|
|
70
|
+
* **fails closed by default** (logs and rejects with 403). Pass
|
|
71
|
+
* `failOpen: true` to admit the request instead — mirrors `@lunora/ratelimit`.
|
|
72
|
+
*
|
|
73
|
+
* **Cross-origin replay:** a token solved on one origin can be replayed against
|
|
74
|
+
* a different endpoint when a single secret/sitekey is shared across multiple
|
|
75
|
+
* domains. Pass `expectedHostname` (and optionally `expectedAction`, or a custom
|
|
76
|
+
* `validate(result)` predicate) to assert the siteverify response's `hostname`
|
|
77
|
+
* /`action` and reject mismatches with 403.
|
|
78
|
+
*/
|
|
79
79
|
declare const verifyTurnstileMiddleware: <Context>(options: VerifyTurnstileMiddlewareOptions<Context>) => Middleware<Context, Context>;
|
|
80
80
|
export { type VerifyTurnstileMiddlewareOptions, verifyTurnstileMiddleware };
|