@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 +5 -2
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +46 -7
- package/dist/chain.js.map +1 -1
- package/dist/shipped-chain.d.ts +16 -0
- package/dist/shipped-chain.d.ts.map +1 -0
- package/dist/shipped-chain.js +26 -0
- package/dist/shipped-chain.js.map +1 -0
- package/package.json +1 -1
- package/src/chain.ts +61 -10
- package/src/shipped-chain.ts +25 -0
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.
|
|
36
|
-
*
|
|
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
|
package/dist/chain.d.ts.map
CHANGED
|
@@ -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;
|
|
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.
|
|
40
|
-
*
|
|
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
|
|
48
|
-
|
|
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 ??=
|
|
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;
|
|
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
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.
|
|
63
|
-
*
|
|
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
|
|
74
|
-
|
|
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 ??=
|
|
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
|
+
];
|