@ingram-tech/nk-auth 0.19.3 → 0.19.5

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/chain.d.ts CHANGED
@@ -32,8 +32,11 @@ export interface AuthChainCheckOptions {
32
32
  * Returns the runner's status when it is, or `null` when the database has no
33
33
  * nk-auth journal table at all: a site that owns Better Auth's tables in its
34
34
  * own chain (a squashed baseline) never records this one, and that is not a
35
- * deployment mistake. Throws {@link AuthChainNotAppliedError} when the journal
36
- * exists but lags the installed package.
35
+ * deployment mistake. Also `null` when this role cannot read the journal (a
36
+ * least-privilege auth role with no access to the `drizzle` schema): it could
37
+ * not have applied the chain either, so there is nothing to verify through it.
38
+ * Throws {@link AuthChainNotAppliedError} when the journal exists but lags the
39
+ * installed package.
37
40
  *
38
41
  * `createAuthHelpers` runs this once per process on the first session read
39
42
  * when the instance's `database` is a `pg` Pool; call it yourself from
@@ -1 +1 @@
1
- {"version":3,"file":"chain.d.ts","sourceRoot":"","sources":["../src/chain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAGrF,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE/B;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,QAAO,MAOtC,CAAC;AAEH,wEAAwE;AACxE,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAS3D;;;;;;GAMG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IAClD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,YAAY,OAAO,EAAE,SAAS,MAAM,EAAE,EAMrC;CACD;AAED,MAAM,WAAW,qBAAqB;IACrC,sFAAsF;IACtF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qDAAqD;IACrD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB,SAC5B,IAAI,YACD,qBAAqB,KAC5B,OAAO,CAAC,eAAe,GAAG,IAAI,CAkBhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,aAChB,OAAO,YACR,qBAAqB,KAC5B,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAOtB,CAAC"}
1
+ {"version":3,"file":"chain.d.ts","sourceRoot":"","sources":["../src/chain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAGrF,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAG/B;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,QAAO,MAOtC,CAAC;AAEH,wEAAwE;AACxE,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAS3D;;;;;;GAMG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IAClD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,YAAY,OAAO,EAAE,SAAS,MAAM,EAAE,EAMrC;CACD;AAED,MAAM,WAAW,qBAAqB;IACrC,sFAAsF;IACtF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qDAAqD;IACrD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAsDD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,SAC5B,IAAI,YACD,qBAAqB,KAC5B,OAAO,CAAC,eAAe,GAAG,IAAI,CAahC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,aAChB,OAAO,YACR,qBAAqB,KAC5B,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAOtB,CAAC"}
package/dist/chain.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { inspectMigrations } from "@ingram-tech/nk-db/migrate";
2
2
  import { createRequire } from "node:module";
3
3
  import { dirname } from "node:path";
4
+ import { SHIPPED_AUTH_CHAIN } from "./shipped-chain.js";
4
5
  /**
5
6
  * The migration chain this package ships: `migrations/` in the installed
6
7
  * package. Resolved through the package manifest, at call time, so a bundler
@@ -30,24 +31,62 @@ export class AuthChainNotAppliedError extends Error {
30
31
  this.pending = pending;
31
32
  }
32
33
  }
34
+ const journalOf = (options) => ({
35
+ schema: options.migrationsSchema ?? "drizzle",
36
+ table: options.migrationsTable ?? AUTH_MIGRATIONS_TABLE,
37
+ });
38
+ /**
39
+ * Whether this role can read the journal table. Looked up in pg_catalog, which
40
+ * every role can read: `to_regclass` raises "permission denied" instead of
41
+ * returning null when the role lacks USAGE on the schema, and that broke every
42
+ * session read on a site whose auth role has no business in `drizzle`.
43
+ */
44
+ const journalReadable = async (pool, schema, table) => {
45
+ const { rows } = await pool.query(`select has_schema_privilege(n.oid, 'USAGE')
46
+ and has_table_privilege(c.oid, 'SELECT') as readable
47
+ from pg_catalog.pg_class c
48
+ join pg_catalog.pg_namespace n on n.oid = c.relnamespace
49
+ where n.nspname = $1 and c.relname = $2`, [schema, table]);
50
+ return rows[0]?.readable === true;
51
+ };
52
+ const quoteIdent = (name) => `"${name.replaceAll('"', '""')}"`;
53
+ /**
54
+ * The same verdict as {@link assertAuthChainApplied}, decided against
55
+ * {@link SHIPPED_AUTH_CHAIN} instead of the `migrations/` folder: a shipped
56
+ * file is pending when its hash is not recorded (the set difference nk-db's
57
+ * runner uses). It never touches the filesystem, so it is safe inside a
58
+ * site's server bundle, where it runs on the first session read.
59
+ */
60
+ const assertShippedChainRecorded = async (pool, options) => {
61
+ const { schema, table } = journalOf(options);
62
+ if (!(await journalReadable(pool, schema, table)))
63
+ return;
64
+ const { rows } = await pool.query(`select hash from ${quoteIdent(schema)}.${quoteIdent(table)}`);
65
+ const recorded = new Set(rows.map((row) => row.hash));
66
+ const pending = SHIPPED_AUTH_CHAIN.filter((file) => !recorded.has(file.hash));
67
+ if (pending.length > 0) {
68
+ throw new AuthChainNotAppliedError(pending.map((file) => file.tag));
69
+ }
70
+ };
33
71
  /**
34
72
  * Verify the shipped auth chain is fully applied to the database behind `pool`.
35
73
  *
36
74
  * Returns the runner's status when it is, or `null` when the database has no
37
75
  * nk-auth journal table at all: a site that owns Better Auth's tables in its
38
76
  * own chain (a squashed baseline) never records this one, and that is not a
39
- * deployment mistake. Throws {@link AuthChainNotAppliedError} when the journal
40
- * exists but lags the installed package.
77
+ * deployment mistake. Also `null` when this role cannot read the journal (a
78
+ * least-privilege auth role with no access to the `drizzle` schema): it could
79
+ * not have applied the chain either, so there is nothing to verify through it.
80
+ * Throws {@link AuthChainNotAppliedError} when the journal exists but lags the
81
+ * installed package.
41
82
  *
42
83
  * `createAuthHelpers` runs this once per process on the first session read
43
84
  * when the instance's `database` is a `pg` Pool; call it yourself from
44
85
  * `instrumentation.ts` or a health check to fail a deploy earlier.
45
86
  */
46
87
  export const assertAuthChainApplied = async (pool, options = {}) => {
47
- const schema = options.migrationsSchema ?? "drizzle";
48
- const table = options.migrationsTable ?? AUTH_MIGRATIONS_TABLE;
49
- const { rows } = await pool.query("select to_regclass($1) as present", [`"${schema}"."${table}"`]);
50
- if (rows[0]?.present === null || rows[0]?.present === undefined)
88
+ const { schema, table } = journalOf(options);
89
+ if (!(await journalReadable(pool, schema, table)))
51
90
  return null;
52
91
  const status = await inspectMigrations({
53
92
  pool,
@@ -72,7 +111,7 @@ export const authChainCheck = (database, options = {}) => {
72
111
  return async () => { };
73
112
  let checked;
74
113
  return () => {
75
- checked ??= assertAuthChainApplied(database, options).then(() => undefined);
114
+ checked ??= assertShippedChainRecorded(database, options);
76
115
  return checked;
77
116
  };
78
117
  };
package/dist/chain.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"chain.js","sourceRoot":"","sources":["../src/chain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAwB,MAAM,4BAA4B,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAW,EAAE,CAChD,OAAO,CACN,OAAO,CACN,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CACrC,oDAAoD,CACpD,CACD,CACD,CAAC;AAEH,wEAAwE;AACxE,MAAM,CAAC,MAAM,qBAAqB,GAAG,qBAAqB,CAAC;AAE3D,mEAAmE;AACnE,MAAM,MAAM,GAAG,CAAC,KAAc,EAAiB,EAAE,CAChD,OAAO,KAAK,KAAK,QAAQ;IACzB,KAAK,KAAK,IAAI;IACd,OAAQ,KAA6B,CAAC,KAAK,KAAK,UAAU;IAC1D,OAAQ,KAA+B,CAAC,OAAO,KAAK,UAAU,CAAC;AAEhE;;;;;;GAMG;AACH,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACzC,OAAO,CAAoB;IACpC,YAAY,OAA0B;QACrC,KAAK,CACJ,yBAAyB,OAAO,CAAC,MAAM,6EAA6E,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,0GAA0G,qBAAqB,kCAAkC,CACvS,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;CACD;AASD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,EAC1C,IAAU,EACV,OAAO,GAA0B,EAAE,EACD,EAAE;IACpC,MAAM,MAAM,GAAG,OAAO,CAAC,gBAAgB,IAAI,SAAS,CAAC;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,eAAe,IAAI,qBAAqB,CAAC;IAC/D,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAChC,mCAAmC,EACnC,CAAC,IAAI,MAAM,MAAM,KAAK,GAAG,CAAC,CAC1B,CAAC;IACF,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC7E,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC;QACtC,IAAI;QACJ,gBAAgB,EAAE,oBAAoB,EAAE;QACxC,gBAAgB,EAAE,MAAM;QACxB,eAAe,EAAE,KAAK;KACtB,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,wBAAwB,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAC7B,QAAiB,EACjB,OAAO,GAA0B,EAAE,EACX,EAAE;IAC1B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,IAAI,EAAE,GAAE,CAAC,CAAC;IAC7C,IAAI,OAAkC,CAAC;IACvC,OAAO,GAAG,EAAE;QACX,OAAO,KAAK,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC5E,OAAO,OAAO,CAAC;IAChB,CAAC,CAAC;AACH,CAAC,CAAC"}
1
+ {"version":3,"file":"chain.js","sourceRoot":"","sources":["../src/chain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The migration chain this package ships, as data: each `migrations/*.sql`
3
+ * file's tag and the sha256 nk-db's runner records for it.
4
+ *
5
+ * The per-request chain check runs inside the site's server bundle, where the
6
+ * package's files are not reliably on disk: Next rewrites `require.resolve`
7
+ * to a numeric module id, so resolving `migrations/` there made every session
8
+ * read throw. Carrying the chain in code keeps the check off the filesystem.
9
+ * `chain.test.ts` fails if this list drifts from `migrations/`, so a new
10
+ * migration must be added here in the same change.
11
+ */
12
+ export declare const SHIPPED_AUTH_CHAIN: ReadonlyArray<{
13
+ tag: string;
14
+ hash: string;
15
+ }>;
16
+ //# sourceMappingURL=shipped-chain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shipped-chain.d.ts","sourceRoot":"","sources":["../src/shipped-chain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAAa,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAa3E,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The migration chain this package ships, as data: each `migrations/*.sql`
3
+ * file's tag and the sha256 nk-db's runner records for it.
4
+ *
5
+ * The per-request chain check runs inside the site's server bundle, where the
6
+ * package's files are not reliably on disk: Next rewrites `require.resolve`
7
+ * to a numeric module id, so resolving `migrations/` there made every session
8
+ * read throw. Carrying the chain in code keeps the check off the filesystem.
9
+ * `chain.test.ts` fails if this list drifts from `migrations/`, so a new
10
+ * migration must be added here in the same change.
11
+ */
12
+ export const SHIPPED_AUTH_CHAIN = [
13
+ {
14
+ tag: "0001_better_auth",
15
+ hash: "5f44f3294805908dc7a946ac4fb2032d868da5f04e98f496ddfe76e8c0f8cddc",
16
+ },
17
+ {
18
+ tag: "0002_better_auth_1_7",
19
+ hash: "fa64a63e86ea862aff393fef6ecc705fb478d10c7bfc9e390ed475fa990aedea",
20
+ },
21
+ {
22
+ tag: "0003_better_auth_1_7_3",
23
+ hash: "45152bfe9401ace12ac860ccfc4ae49b6a3ab69cf2460947a964c5f231aa4919",
24
+ },
25
+ ];
26
+ //# sourceMappingURL=shipped-chain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shipped-chain.js","sourceRoot":"","sources":["../src/shipped-chain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAiD;IAC/E;QACC,GAAG,EAAE,kBAAkB;QACvB,IAAI,EAAE,kEAAkE;KACxE;IACD;QACC,GAAG,EAAE,sBAAsB;QAC3B,IAAI,EAAE,kEAAkE;KACxE;IACD;QACC,GAAG,EAAE,wBAAwB;QAC7B,IAAI,EAAE,kEAAkE;KACxE;CACD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingram-tech/nk-auth",
3
- "version": "0.19.3",
3
+ "version": "0.19.5",
4
4
  "description": "The Ingram Better Auth foundation: composable presets (org, dual-shape JWT, active-org hooks, pg pool) for Next.js sites.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/chain.ts CHANGED
@@ -2,6 +2,7 @@ import { inspectMigrations, type MigrationStatus } from "@ingram-tech/nk-db/migr
2
2
  import { createRequire } from "node:module";
3
3
  import { dirname } from "node:path";
4
4
  import type { Pool } from "pg";
5
+ import { SHIPPED_AUTH_CHAIN } from "./shipped-chain.js";
5
6
 
6
7
  /**
7
8
  * The migration chain this package ships: `migrations/` in the installed
@@ -53,14 +54,69 @@ export interface AuthChainCheckOptions {
53
54
  migrationsSchema?: string;
54
55
  }
55
56
 
57
+ const journalOf = (options: AuthChainCheckOptions) => ({
58
+ schema: options.migrationsSchema ?? "drizzle",
59
+ table: options.migrationsTable ?? AUTH_MIGRATIONS_TABLE,
60
+ });
61
+
62
+ /**
63
+ * Whether this role can read the journal table. Looked up in pg_catalog, which
64
+ * every role can read: `to_regclass` raises "permission denied" instead of
65
+ * returning null when the role lacks USAGE on the schema, and that broke every
66
+ * session read on a site whose auth role has no business in `drizzle`.
67
+ */
68
+ const journalReadable = async (
69
+ pool: Pool,
70
+ schema: string,
71
+ table: string,
72
+ ): Promise<boolean> => {
73
+ const { rows } = await pool.query<{ readable: boolean }>(
74
+ `select has_schema_privilege(n.oid, 'USAGE')
75
+ and has_table_privilege(c.oid, 'SELECT') as readable
76
+ from pg_catalog.pg_class c
77
+ join pg_catalog.pg_namespace n on n.oid = c.relnamespace
78
+ where n.nspname = $1 and c.relname = $2`,
79
+ [schema, table],
80
+ );
81
+ return rows[0]?.readable === true;
82
+ };
83
+
84
+ const quoteIdent = (name: string): string => `"${name.replaceAll('"', '""')}"`;
85
+
86
+ /**
87
+ * The same verdict as {@link assertAuthChainApplied}, decided against
88
+ * {@link SHIPPED_AUTH_CHAIN} instead of the `migrations/` folder: a shipped
89
+ * file is pending when its hash is not recorded (the set difference nk-db's
90
+ * runner uses). It never touches the filesystem, so it is safe inside a
91
+ * site's server bundle, where it runs on the first session read.
92
+ */
93
+ const assertShippedChainRecorded = async (
94
+ pool: Pool,
95
+ options: AuthChainCheckOptions,
96
+ ): Promise<void> => {
97
+ const { schema, table } = journalOf(options);
98
+ if (!(await journalReadable(pool, schema, table))) return;
99
+ const { rows } = await pool.query<{ hash: string }>(
100
+ `select hash from ${quoteIdent(schema)}.${quoteIdent(table)}`,
101
+ );
102
+ const recorded = new Set(rows.map((row) => row.hash));
103
+ const pending = SHIPPED_AUTH_CHAIN.filter((file) => !recorded.has(file.hash));
104
+ if (pending.length > 0) {
105
+ throw new AuthChainNotAppliedError(pending.map((file) => file.tag));
106
+ }
107
+ };
108
+
56
109
  /**
57
110
  * Verify the shipped auth chain is fully applied to the database behind `pool`.
58
111
  *
59
112
  * Returns the runner's status when it is, or `null` when the database has no
60
113
  * nk-auth journal table at all: a site that owns Better Auth's tables in its
61
114
  * own chain (a squashed baseline) never records this one, and that is not a
62
- * deployment mistake. Throws {@link AuthChainNotAppliedError} when the journal
63
- * exists but lags the installed package.
115
+ * deployment mistake. Also `null` when this role cannot read the journal (a
116
+ * least-privilege auth role with no access to the `drizzle` schema): it could
117
+ * not have applied the chain either, so there is nothing to verify through it.
118
+ * Throws {@link AuthChainNotAppliedError} when the journal exists but lags the
119
+ * installed package.
64
120
  *
65
121
  * `createAuthHelpers` runs this once per process on the first session read
66
122
  * when the instance's `database` is a `pg` Pool; call it yourself from
@@ -70,13 +126,8 @@ export const assertAuthChainApplied = async (
70
126
  pool: Pool,
71
127
  options: AuthChainCheckOptions = {},
72
128
  ): Promise<MigrationStatus | null> => {
73
- const schema = options.migrationsSchema ?? "drizzle";
74
- const table = options.migrationsTable ?? AUTH_MIGRATIONS_TABLE;
75
- const { rows } = await pool.query<{ present: string | null }>(
76
- "select to_regclass($1) as present",
77
- [`"${schema}"."${table}"`],
78
- );
79
- if (rows[0]?.present === null || rows[0]?.present === undefined) return null;
129
+ const { schema, table } = journalOf(options);
130
+ if (!(await journalReadable(pool, schema, table))) return null;
80
131
  const status = await inspectMigrations({
81
132
  pool,
82
133
  migrationsFolder: authMigrationsFolder(),
@@ -103,7 +154,7 @@ export const authChainCheck = (
103
154
  if (!isPool(database)) return async () => {};
104
155
  let checked: Promise<void> | undefined;
105
156
  return () => {
106
- checked ??= assertAuthChainApplied(database, options).then(() => undefined);
157
+ checked ??= assertShippedChainRecorded(database, options);
107
158
  return checked;
108
159
  };
109
160
  };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The migration chain this package ships, as data: each `migrations/*.sql`
3
+ * file's tag and the sha256 nk-db's runner records for it.
4
+ *
5
+ * The per-request chain check runs inside the site's server bundle, where the
6
+ * package's files are not reliably on disk: Next rewrites `require.resolve`
7
+ * to a numeric module id, so resolving `migrations/` there made every session
8
+ * read throw. Carrying the chain in code keeps the check off the filesystem.
9
+ * `chain.test.ts` fails if this list drifts from `migrations/`, so a new
10
+ * migration must be added here in the same change.
11
+ */
12
+ export const SHIPPED_AUTH_CHAIN: ReadonlyArray<{ tag: string; hash: string }> = [
13
+ {
14
+ tag: "0001_better_auth",
15
+ hash: "5f44f3294805908dc7a946ac4fb2032d868da5f04e98f496ddfe76e8c0f8cddc",
16
+ },
17
+ {
18
+ tag: "0002_better_auth_1_7",
19
+ hash: "fa64a63e86ea862aff393fef6ecc705fb478d10c7bfc9e390ed475fa990aedea",
20
+ },
21
+ {
22
+ tag: "0003_better_auth_1_7_3",
23
+ hash: "45152bfe9401ace12ac860ccfc4ae49b6a3ab69cf2460947a964c5f231aa4919",
24
+ },
25
+ ];