@fuzdev/fuz_app 0.110.1 → 0.111.0
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/actions/action_rpc.d.ts +2 -2
- package/dist/auth/CLAUDE.md +24 -5
- package/dist/auth/account_action_specs.d.ts +0 -2
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_queries.d.ts +22 -0
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +49 -15
- package/dist/auth/account_schema.d.ts +7 -9
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +1 -2
- package/dist/auth/admin_action_specs.d.ts +0 -2
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/api_token_queries.d.ts +9 -4
- package/dist/auth/api_token_queries.d.ts.map +1 -1
- package/dist/auth/api_token_queries.js +29 -7
- package/dist/auth/app_settings_queries.d.ts +8 -0
- package/dist/auth/app_settings_queries.d.ts.map +1 -1
- package/dist/auth/app_settings_queries.js +14 -3
- package/dist/auth/audit_log_queries.d.ts +15 -3
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +32 -7
- package/dist/auth/audit_log_schema.d.ts +0 -1
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/auth_ddl.d.ts +6 -0
- package/dist/auth/auth_ddl.d.ts.map +1 -1
- package/dist/auth/auth_ddl.js +9 -0
- package/dist/auth/invite_queries.d.ts +10 -0
- package/dist/auth/invite_queries.d.ts.map +1 -1
- package/dist/auth/invite_queries.js +25 -6
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +10 -0
- package/dist/auth/role_grant_offer_ddl.d.ts +23 -2
- package/dist/auth/role_grant_offer_ddl.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_ddl.js +43 -2
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_queries.js +29 -48
- package/dist/auth/role_grant_queries.d.ts +18 -0
- package/dist/auth/role_grant_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.js +43 -17
- package/dist/auth/session_queries.d.ts +12 -3
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +21 -6
- package/dist/db/CLAUDE.md +15 -0
- package/dist/db/cell_audit_queries.d.ts.map +1 -1
- package/dist/db/cell_audit_queries.js +3 -1
- package/dist/db/cell_field_queries.d.ts +11 -2
- package/dist/db/cell_field_queries.d.ts.map +1 -1
- package/dist/db/cell_field_queries.js +23 -7
- package/dist/db/cell_grant_queries.d.ts +9 -1
- package/dist/db/cell_grant_queries.d.ts.map +1 -1
- package/dist/db/cell_grant_queries.js +25 -9
- package/dist/db/cell_item_queries.d.ts +12 -1
- package/dist/db/cell_item_queries.d.ts.map +1 -1
- package/dist/db/cell_item_queries.js +25 -7
- package/dist/db/cell_queries.d.ts +29 -0
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +53 -20
- package/dist/db/fact_queries.d.ts +7 -0
- package/dist/db/fact_queries.d.ts.map +1 -1
- package/dist/db/fact_queries.js +19 -2
- package/dist/db/schema_ready.d.ts +6 -1
- package/dist/db/schema_ready.d.ts.map +1 -1
- package/dist/db/schema_ready.js +6 -2
- package/dist/db/sql_columns.d.ts +67 -0
- package/dist/db/sql_columns.d.ts.map +1 -0
- package/dist/db/sql_columns.js +77 -0
- package/dist/db/sql_identifier.d.ts +6 -5
- package/dist/db/sql_identifier.d.ts.map +1 -1
- package/dist/db/sql_identifier.js +6 -5
- package/dist/http/CLAUDE.md +1 -1
- package/dist/http/route_spec.d.ts +2 -2
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +1 -2
- package/dist/testing/CLAUDE.md +2 -1
- package/dist/testing/cross_backend/expected_schema.json +1 -1
- package/dist/testing/db.d.ts +18 -0
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +24 -1
- package/dist/ui/AdminOverview.svelte +5 -10
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -12,7 +12,31 @@
|
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
14
|
import { assert_row } from '../db/assert_row.js';
|
|
15
|
+
import { columns_sql, qualify_columns } from '../db/sql_columns.js';
|
|
15
16
|
import { AUDIT_LOG_DEFAULT_LIMIT, builtin_audit_log_config } from './audit_log_schema.js';
|
|
17
|
+
/**
|
|
18
|
+
* The full `audit_log` column set, named explicitly so a row read fails loud
|
|
19
|
+
* on schema drift — audit rows ride the `audit_log_list` / role-grant-history
|
|
20
|
+
* RPC responses and the SSE broadcast raw, so a `SELECT *` would silently
|
|
21
|
+
* carry a dropped or leftover column into the strict-validated wire shapes
|
|
22
|
+
* (see `ACCOUNT_COLUMNS` in `auth/account_queries.ts` for the outage class;
|
|
23
|
+
* the Rust twin names columns at every audit site). Keep in sync with
|
|
24
|
+
* `AuditLogEvent` and the migration chain's end state. Exported for the
|
|
25
|
+
* per-cell timeline read in `db/cell_audit_queries.ts`.
|
|
26
|
+
*/
|
|
27
|
+
export const AUDIT_LOG_COLUMNS = [
|
|
28
|
+
'id',
|
|
29
|
+
'seq',
|
|
30
|
+
'event_type',
|
|
31
|
+
'outcome',
|
|
32
|
+
'actor_id',
|
|
33
|
+
'account_id',
|
|
34
|
+
'target_account_id',
|
|
35
|
+
'target_actor_id',
|
|
36
|
+
'ip',
|
|
37
|
+
'created_at',
|
|
38
|
+
'metadata'
|
|
39
|
+
];
|
|
16
40
|
/**
|
|
17
41
|
* Process-wide counter for audit metadata validation failures. `query_audit_log`
|
|
18
42
|
* increments on `safeParse` mismatch and writes the row anyway (fail-open —
|
|
@@ -47,9 +71,10 @@ export const reset_audit_unknown_event_type_failures = () => {
|
|
|
47
71
|
/**
|
|
48
72
|
* Insert an audit log entry.
|
|
49
73
|
*
|
|
50
|
-
* `
|
|
51
|
-
* `created_at`). Validates `metadata` against
|
|
52
|
-
* unknown `event_type` and metadata mismatches
|
|
74
|
+
* Returns the full row (`AUDIT_LOG_COLUMNS`) so callers receive DB-assigned
|
|
75
|
+
* fields (`id`, `seq`, `created_at`). Validates `metadata` against
|
|
76
|
+
* `config.metadata_schemas`; unknown `event_type` and metadata mismatches
|
|
77
|
+
* log + bump their counters
|
|
53
78
|
* but write the row anyway. Consumers extend the recognized set via
|
|
54
79
|
* `create_audit_log_config({extra_events})`.
|
|
55
80
|
*
|
|
@@ -83,7 +108,7 @@ export const query_audit_log = async (deps, input, config = builtin_audit_log_co
|
|
|
83
108
|
}
|
|
84
109
|
const rows = await deps.db.query(`INSERT INTO audit_log (event_type, outcome, actor_id, account_id, target_account_id, target_actor_id, ip, metadata)
|
|
85
110
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
86
|
-
RETURNING
|
|
111
|
+
RETURNING ${columns_sql(AUDIT_LOG_COLUMNS)}`, [
|
|
87
112
|
input.event_type,
|
|
88
113
|
input.outcome ?? 'success',
|
|
89
114
|
input.actor_id ?? null,
|
|
@@ -131,7 +156,7 @@ export const query_audit_log_list = async (deps, options) => {
|
|
|
131
156
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
132
157
|
const limit = options?.limit ?? AUDIT_LOG_DEFAULT_LIMIT;
|
|
133
158
|
const offset = options?.offset ?? 0;
|
|
134
|
-
return deps.db.query(`SELECT
|
|
159
|
+
return deps.db.query(`SELECT ${columns_sql(AUDIT_LOG_COLUMNS)} FROM audit_log ${where} ORDER BY seq DESC LIMIT $${param_index++} OFFSET $${param_index}`, [...params, limit, offset]);
|
|
135
160
|
};
|
|
136
161
|
/**
|
|
137
162
|
* List audit log entries with resolved usernames, newest first.
|
|
@@ -175,7 +200,7 @@ export const query_audit_log_list_with_usernames = async (deps, options) => {
|
|
|
175
200
|
// has no actor binding (admin password reset, session revoke, etc.).
|
|
176
201
|
// Under v1 1:1 the two branches resolve to the same username; the
|
|
177
202
|
// chain is forensic future-proofing for N:1 multi-actor.
|
|
178
|
-
return deps.db.query(`SELECT al
|
|
203
|
+
return deps.db.query(`SELECT ${qualify_columns(AUDIT_LOG_COLUMNS, 'al')},
|
|
179
204
|
COALESCE(origin_act_acc.username, origin_acc.username) AS username,
|
|
180
205
|
COALESCE(target_act_acc.username, target_acc.username) AS target_username
|
|
181
206
|
FROM audit_log al
|
|
@@ -199,7 +224,7 @@ export const query_audit_log_list_role_grant_history = async (deps, limit = AUDI
|
|
|
199
224
|
// Same actor-chained JOIN as `query_audit_log_list_with_usernames` —
|
|
200
225
|
// see the comment there for rationale (forensic future-proofing for
|
|
201
226
|
// N:1 multi-actor; v1 1:1 picks the same username via either branch).
|
|
202
|
-
return deps.db.query(`SELECT al
|
|
227
|
+
return deps.db.query(`SELECT ${qualify_columns(AUDIT_LOG_COLUMNS, 'al')},
|
|
203
228
|
COALESCE(origin_act_acc.username, origin_acc.username) AS username,
|
|
204
229
|
COALESCE(target_act_acc.username, target_acc.username) AS target_username
|
|
205
230
|
FROM audit_log al
|
|
@@ -475,7 +475,6 @@ export declare const AdminSessionJson: z.ZodObject<{
|
|
|
475
475
|
account_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
476
476
|
created_at: z.ZodString;
|
|
477
477
|
expires_at: z.ZodString;
|
|
478
|
-
last_seen_at: z.ZodString;
|
|
479
478
|
username: z.ZodString;
|
|
480
479
|
}, z.core.$strict>;
|
|
481
480
|
export type AdminSessionJson = z.infer<typeof AdminSessionJson>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/auth_ddl.d.ts
CHANGED
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
* (audit table) and `auth/role_grant_offer_ddl.ts` (offer table) — DDL lives
|
|
8
8
|
* in `*_ddl.ts`, Zod schemas in `*_schema.ts`.
|
|
9
9
|
*
|
|
10
|
+
* These strings are the body of the frozen `full_auth_schema` migration —
|
|
11
|
+
* never edit them (see the `auth/migrations.ts` module doc for the
|
|
12
|
+
* silent-outage rationale). Schema changes ship as new appended migrations,
|
|
13
|
+
* so a column retired later still appears below (created by v0, dropped
|
|
14
|
+
* downstream in the chain).
|
|
15
|
+
*
|
|
10
16
|
* @module
|
|
11
17
|
*/
|
|
12
18
|
export declare const ACCOUNT_SCHEMA = "\nCREATE TABLE IF NOT EXISTS account (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n username TEXT UNIQUE NOT NULL,\n email TEXT,\n email_verified BOOLEAN NOT NULL DEFAULT false,\n password_hash TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n created_by UUID,\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_by UUID,\n deleted_at TIMESTAMPTZ,\n deleted_by UUID\n)";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/auth_ddl.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/auth_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,eAAO,MAAM,cAAc,6ZAazB,CAAC;AAEH,eAAO,MAAM,YAAY,0ZAUvB,CAAC;AAEH,eAAO,MAAM,WAAW,wEAC0C,CAAC;AAEnE;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,8FACqD,CAAC;AAEzF,eAAO,MAAM,iBAAiB,2ZAU5B,CAAC;AAEH,eAAO,MAAM,kBAAkB,UAI9B,CAAC;AAKF,eAAO,MAAM,mBAAmB,0RAO9B,CAAC;AAEH,eAAO,MAAM,oBAAoB,UAGhC,CAAC;AAEF,eAAO,MAAM,gBAAgB,iUAU3B,CAAC;AAEH,eAAO,MAAM,mBAAmB,4GACsE,CAAC;AAEvG,eAAO,MAAM,yBAAyB,6FACiD,CAAC;AAExF,eAAO,MAAM,eAAe,gFAC8C,CAAC;AAE3E,eAAO,MAAM,qBAAqB,wJAIhC,CAAC;AAEH,6FAA6F;AAC7F,eAAO,MAAM,mBAAmB,yHAGP,CAAC;AAE1B,eAAO,MAAM,aAAa,6ZAUxB,CAAC;AAEH,eAAO,MAAM,cAAc,UAI1B,CAAC;AAEF,eAAO,MAAM,mBAAmB,oMAM9B,CAAC;AAEH,eAAO,MAAM,iBAAiB,sEACkC,CAAC"}
|
package/dist/auth/auth_ddl.js
CHANGED
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
* (audit table) and `auth/role_grant_offer_ddl.ts` (offer table) — DDL lives
|
|
8
8
|
* in `*_ddl.ts`, Zod schemas in `*_schema.ts`.
|
|
9
9
|
*
|
|
10
|
+
* These strings are the body of the frozen `full_auth_schema` migration —
|
|
11
|
+
* never edit them (see the `auth/migrations.ts` module doc for the
|
|
12
|
+
* silent-outage rationale). Schema changes ship as new appended migrations,
|
|
13
|
+
* so a column retired later still appears below (created by v0, dropped
|
|
14
|
+
* downstream in the chain).
|
|
15
|
+
*
|
|
10
16
|
* @module
|
|
11
17
|
*/
|
|
12
18
|
// `deleted_at` is the soft-delete tombstone (delete = soft, purge = hard).
|
|
@@ -64,6 +70,9 @@ export const ROLE_GRANT_INDEXES = [
|
|
|
64
70
|
`CREATE UNIQUE INDEX IF NOT EXISTS role_grant_actor_role_active_unique
|
|
65
71
|
ON role_grant (actor_id, role) WHERE revoked_at IS NULL`
|
|
66
72
|
];
|
|
73
|
+
// `last_seen_at` stays in this frozen v0 body — the appended
|
|
74
|
+
// `drop_session_last_seen_at` migration removes it later in the chain, so a
|
|
75
|
+
// fresh bootstrap and an old deployed DB converge on the same shape.
|
|
67
76
|
export const AUTH_SESSION_SCHEMA = `
|
|
68
77
|
CREATE TABLE IF NOT EXISTS auth_session (
|
|
69
78
|
id TEXT PRIMARY KEY,
|
|
@@ -8,6 +8,16 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { QueryDeps } from '../db/query_deps.js';
|
|
10
10
|
import type { Invite, CreateInviteInput, InviteWithUsernamesJson } from './invite_schema.js';
|
|
11
|
+
/**
|
|
12
|
+
* The full `invite` column set, named explicitly so a row read fails loud on
|
|
13
|
+
* schema drift — invite rows ride the `invite_create` / `invite_list` RPC
|
|
14
|
+
* responses raw, so a `SELECT *` would silently carry a dropped or leftover
|
|
15
|
+
* column into the strict-validated wire shapes (see `ACCOUNT_COLUMNS` in
|
|
16
|
+
* `auth/account_queries.ts` for the outage class; the Rust twin names
|
|
17
|
+
* columns at every invite site). Keep in sync with `Invite` and the
|
|
18
|
+
* migration chain's end state.
|
|
19
|
+
*/
|
|
20
|
+
export declare const INVITE_COLUMNS: readonly ["id", "email", "username", "claimed_by", "claimed_at", "created_at", "created_by"];
|
|
11
21
|
/**
|
|
12
22
|
* Create a new invite.
|
|
13
23
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invite_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"invite_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAE7F;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,8FAQqB,CAAC;AAEjD;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,OAAO,iBAAiB,KACtB,OAAO,CAAC,MAAM,CAQhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,OAAO,MAAM,KACX,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,4CAA4C,GACxD,MAAM,SAAS,EACf,OAAO,MAAM,GAAG,IAAI,EACpB,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAgB5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,WAAW,MAAM,EACjB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAQjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAIlF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,KACb,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAUxC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,IAAI,MAAM,KACR,OAAO,CAAC,OAAO,CAMjB,CAAC"}
|
|
@@ -7,6 +7,25 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import { assert_row } from '../db/assert_row.js';
|
|
10
|
+
import { columns_sql, qualify_columns } from '../db/sql_columns.js';
|
|
11
|
+
/**
|
|
12
|
+
* The full `invite` column set, named explicitly so a row read fails loud on
|
|
13
|
+
* schema drift — invite rows ride the `invite_create` / `invite_list` RPC
|
|
14
|
+
* responses raw, so a `SELECT *` would silently carry a dropped or leftover
|
|
15
|
+
* column into the strict-validated wire shapes (see `ACCOUNT_COLUMNS` in
|
|
16
|
+
* `auth/account_queries.ts` for the outage class; the Rust twin names
|
|
17
|
+
* columns at every invite site). Keep in sync with `Invite` and the
|
|
18
|
+
* migration chain's end state.
|
|
19
|
+
*/
|
|
20
|
+
export const INVITE_COLUMNS = [
|
|
21
|
+
'id',
|
|
22
|
+
'email',
|
|
23
|
+
'username',
|
|
24
|
+
'claimed_by',
|
|
25
|
+
'claimed_at',
|
|
26
|
+
'created_at',
|
|
27
|
+
'created_by'
|
|
28
|
+
];
|
|
10
29
|
/**
|
|
11
30
|
* Create a new invite.
|
|
12
31
|
*
|
|
@@ -18,20 +37,20 @@ import { assert_row } from '../db/assert_row.js';
|
|
|
18
37
|
export const query_create_invite = async (deps, input) => {
|
|
19
38
|
const row = await deps.db.query_one(`INSERT INTO invite (email, username, created_by)
|
|
20
39
|
VALUES ($1, $2, $3)
|
|
21
|
-
RETURNING
|
|
40
|
+
RETURNING ${columns_sql(INVITE_COLUMNS)}`, [input.email ?? null, input.username ?? null, input.created_by]);
|
|
22
41
|
return assert_row(row, 'INSERT INTO invite');
|
|
23
42
|
};
|
|
24
43
|
/**
|
|
25
44
|
* Find an unclaimed invite by email (case-insensitive).
|
|
26
45
|
*/
|
|
27
46
|
export const query_invite_find_unclaimed_by_email = async (deps, email) => {
|
|
28
|
-
return deps.db.query_one(`SELECT
|
|
47
|
+
return deps.db.query_one(`SELECT ${columns_sql(INVITE_COLUMNS)} FROM invite WHERE LOWER(email) = LOWER($1) AND claimed_at IS NULL`, [email]);
|
|
29
48
|
};
|
|
30
49
|
/**
|
|
31
50
|
* Find an unclaimed invite by username (case-insensitive).
|
|
32
51
|
*/
|
|
33
52
|
export const query_invite_find_unclaimed_by_username = async (deps, username) => {
|
|
34
|
-
return deps.db.query_one(`SELECT
|
|
53
|
+
return deps.db.query_one(`SELECT ${columns_sql(INVITE_COLUMNS)} FROM invite WHERE LOWER(username) = LOWER($1) AND claimed_at IS NULL`, [username]);
|
|
35
54
|
};
|
|
36
55
|
/**
|
|
37
56
|
* Find an unclaimed invite matching email and/or username, taking a
|
|
@@ -56,7 +75,7 @@ export const query_invite_find_unclaimed_by_username = async (deps, username) =>
|
|
|
56
75
|
* @returns the matching invite (locked), or `undefined`
|
|
57
76
|
*/
|
|
58
77
|
export const query_invite_find_unclaimed_match_for_update = async (deps, email, username) => {
|
|
59
|
-
return deps.db.query_one(`SELECT
|
|
78
|
+
return deps.db.query_one(`SELECT ${columns_sql(INVITE_COLUMNS)} FROM invite WHERE claimed_at IS NULL AND (
|
|
60
79
|
(email IS NOT NULL AND username IS NULL
|
|
61
80
|
AND $1::text IS NOT NULL AND LOWER(email) = LOWER($1::text))
|
|
62
81
|
OR
|
|
@@ -99,7 +118,7 @@ export const query_invite_claim_unscoped = async (deps, invite_id, account_id) =
|
|
|
99
118
|
* List all invites, newest first.
|
|
100
119
|
*/
|
|
101
120
|
export const query_invite_list_all = async (deps) => {
|
|
102
|
-
return deps.db.query(`SELECT
|
|
121
|
+
return deps.db.query(`SELECT ${columns_sql(INVITE_COLUMNS)} FROM invite ORDER BY created_at DESC`);
|
|
103
122
|
};
|
|
104
123
|
/**
|
|
105
124
|
* List all invites with resolved creator/claimer usernames, newest first.
|
|
@@ -108,7 +127,7 @@ export const query_invite_list_all = async (deps) => {
|
|
|
108
127
|
* @returns invites with `created_by_username` and `claimed_by_username`
|
|
109
128
|
*/
|
|
110
129
|
export const query_invite_list_all_with_usernames = async (deps) => {
|
|
111
|
-
return deps.db.query(`SELECT i
|
|
130
|
+
return deps.db.query(`SELECT ${qualify_columns(INVITE_COLUMNS, 'i')},
|
|
112
131
|
act.name AS created_by_username,
|
|
113
132
|
a.username AS claimed_by_username
|
|
114
133
|
FROM invite i
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AA+BH,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,wDAAwD;AACxD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,EAAE,aAAa,CAAC,MAAM,CAA8B,CAAC;AAE/F;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AA+BH,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,wDAAwD;AACxD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,EAAE,aAAa,CAAC,MAAM,CAA8B,CAAC;AAE/F;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,CA6K5C,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,kBAG/B,CAAC"}
|
package/dist/auth/migrations.js
CHANGED
|
@@ -227,6 +227,16 @@ export const auth_migrations = [
|
|
|
227
227
|
up: async (db) => {
|
|
228
228
|
await db.query('CREATE INDEX IF NOT EXISTS idx_audit_log_metadata ON audit_log USING gin (metadata jsonb_path_ops)');
|
|
229
229
|
}
|
|
230
|
+
},
|
|
231
|
+
// Drops `auth_session.last_seen_at`. The session lifetime is a hard cap
|
|
232
|
+
// set at mint — nothing updates the column post-mint on either spine, so
|
|
233
|
+
// it always equalled `created_at` (a decorative false signal the UIs
|
|
234
|
+
// stopped showing when the listings re-sorted by `created_at`).
|
|
235
|
+
{
|
|
236
|
+
name: 'drop_session_last_seen_at',
|
|
237
|
+
up: async (db) => {
|
|
238
|
+
await db.query('ALTER TABLE auth_session DROP COLUMN IF EXISTS last_seen_at');
|
|
239
|
+
}
|
|
230
240
|
}
|
|
231
241
|
];
|
|
232
242
|
/** Pre-composed migration namespace for auth tables. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Role grant offer DDL — `CREATE TABLE` + index statements
|
|
3
|
-
* sentinel constants the queries / migrations interpolate
|
|
2
|
+
* Role grant offer DDL — `CREATE TABLE` + index statements, the index-side
|
|
3
|
+
* sentinel constants the queries / migrations interpolate, and the table's
|
|
4
|
+
* `ROLE_GRANT_OFFER_COLUMNS` projection const.
|
|
4
5
|
*
|
|
5
6
|
* Separated from `auth/role_grant_offer_schema.ts` so the schema module stays
|
|
6
7
|
* Zod-only (paired with `auth/auth_ddl.ts` and `auth/audit_log_ddl.ts`).
|
|
@@ -22,6 +23,26 @@ export declare const ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID = "00000000-0000-0000-
|
|
|
22
23
|
* `scope_kind = NULL` and `scope_id = NULL` together encode the global case.
|
|
23
24
|
*/
|
|
24
25
|
export declare const ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN = "GLOBAL";
|
|
26
|
+
/**
|
|
27
|
+
* The full `role_grant_offer` column set, named explicitly so a row read
|
|
28
|
+
* fails loud on schema drift (see `ACCOUNT_COLUMNS` in
|
|
29
|
+
* `auth/account_queries.ts` for the outage class). Column order follows
|
|
30
|
+
* `RoleGrantOffer` and `ROLE_GRANT_OFFER_SCHEMA` below.
|
|
31
|
+
*
|
|
32
|
+
* Lives here rather than in `auth/role_grant_offer_queries.ts` because two
|
|
33
|
+
* query modules project this table — see the placement rule in
|
|
34
|
+
* `db/sql_columns.ts`.
|
|
35
|
+
*/
|
|
36
|
+
export declare const ROLE_GRANT_OFFER_COLUMNS: readonly ["id", "from_actor_id", "to_account_id", "to_actor_id", "role", "scope_kind", "scope_id", "message", "created_at", "expires_at", "accepted_at", "declined_at", "decline_reason", "retracted_at", "superseded_at", "resulting_role_grant_id"];
|
|
37
|
+
/**
|
|
38
|
+
* Outer `SELECT` over an `updated` CTE of `role_grant_offer` rows, joining
|
|
39
|
+
* the grantor's `account_id` on as `from_account_id` — the `SupersededOffer` /
|
|
40
|
+
* `DeclinedOffer` shape every offer-superseding cascade returns (the revoke
|
|
41
|
+
* cascades in `auth/role_grant_queries.ts`, decline + accept-supersede in
|
|
42
|
+
* `auth/role_grant_offer_queries.ts`). The CTE must be named `updated` and
|
|
43
|
+
* `RETURNING` the full offer row.
|
|
44
|
+
*/
|
|
45
|
+
export declare const ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT: string;
|
|
25
46
|
export declare const ROLE_GRANT_OFFER_SCHEMA = "\nCREATE TABLE IF NOT EXISTS role_grant_offer (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n from_actor_id UUID NOT NULL REFERENCES actor(id) ON DELETE CASCADE,\n to_account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,\n to_actor_id UUID NULL REFERENCES actor(id) ON DELETE CASCADE,\n role TEXT NOT NULL,\n scope_kind TEXT NULL,\n scope_id UUID NULL,\n message TEXT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n expires_at TIMESTAMPTZ NOT NULL,\n accepted_at TIMESTAMPTZ NULL,\n declined_at TIMESTAMPTZ NULL,\n decline_reason TEXT NULL,\n retracted_at TIMESTAMPTZ NULL,\n superseded_at TIMESTAMPTZ NULL,\n resulting_role_grant_id UUID NULL REFERENCES role_grant(id) ON DELETE SET NULL,\n CONSTRAINT role_grant_offer_single_terminal CHECK (\n (accepted_at IS NOT NULL)::int\n + (declined_at IS NOT NULL)::int\n + (retracted_at IS NOT NULL)::int\n + (superseded_at IS NOT NULL)::int\n <= 1\n ),\n CONSTRAINT role_grant_offer_role_grant_iff_accepted CHECK (\n (accepted_at IS NOT NULL) = (resulting_role_grant_id IS NOT NULL)\n ),\n CONSTRAINT role_grant_offer_reason_iff_declined CHECK (\n decline_reason IS NULL OR declined_at IS NOT NULL\n ),\n CONSTRAINT role_grant_offer_scope_kind_paired CHECK (\n (scope_kind IS NULL) = (scope_id IS NULL)\n )\n)";
|
|
26
47
|
/**
|
|
27
48
|
* At most one pending offer per (to_account, role, scope_kind, scope, from_actor).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_offer_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_ddl.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"role_grant_offer_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,mHAAmH;AACnH,eAAO,MAAM,oCAAoC,yCAAyC,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,WAAW,CAAC;AAEjE;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,uPAiBmB,CAAC;AAEzD;;;;;;;GAOG;AACH,eAAO,MAAM,oCAAoC,QAEI,CAAC;AAEtD,eAAO,MAAM,uBAAuB,qzCAkClC,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qCAAqC,2XAYpB,CAAC;AAE/B,+EAA+E;AAC/E,eAAO,MAAM,4BAA4B,kOAMX,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Role grant offer DDL — `CREATE TABLE` + index statements
|
|
3
|
-
* sentinel constants the queries / migrations interpolate
|
|
2
|
+
* Role grant offer DDL — `CREATE TABLE` + index statements, the index-side
|
|
3
|
+
* sentinel constants the queries / migrations interpolate, and the table's
|
|
4
|
+
* `ROLE_GRANT_OFFER_COLUMNS` projection const.
|
|
4
5
|
*
|
|
5
6
|
* Separated from `auth/role_grant_offer_schema.ts` so the schema module stays
|
|
6
7
|
* Zod-only (paired with `auth/auth_ddl.ts` and `auth/audit_log_ddl.ts`).
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
*
|
|
14
15
|
* @module
|
|
15
16
|
*/
|
|
17
|
+
import { qualify_columns } from '../db/sql_columns.js';
|
|
16
18
|
/** Sentinel UUID used inside the partial unique indexes to collapse `scope_id IS NULL` into a comparable value. */
|
|
17
19
|
export const ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID = '00000000-0000-0000-0000-000000000000';
|
|
18
20
|
/**
|
|
@@ -22,6 +24,45 @@ export const ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID = '00000000-0000-0000-0000-000
|
|
|
22
24
|
* `scope_kind = NULL` and `scope_id = NULL` together encode the global case.
|
|
23
25
|
*/
|
|
24
26
|
export const ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN = 'GLOBAL';
|
|
27
|
+
/**
|
|
28
|
+
* The full `role_grant_offer` column set, named explicitly so a row read
|
|
29
|
+
* fails loud on schema drift (see `ACCOUNT_COLUMNS` in
|
|
30
|
+
* `auth/account_queries.ts` for the outage class). Column order follows
|
|
31
|
+
* `RoleGrantOffer` and `ROLE_GRANT_OFFER_SCHEMA` below.
|
|
32
|
+
*
|
|
33
|
+
* Lives here rather than in `auth/role_grant_offer_queries.ts` because two
|
|
34
|
+
* query modules project this table — see the placement rule in
|
|
35
|
+
* `db/sql_columns.ts`.
|
|
36
|
+
*/
|
|
37
|
+
export const ROLE_GRANT_OFFER_COLUMNS = [
|
|
38
|
+
'id',
|
|
39
|
+
'from_actor_id',
|
|
40
|
+
'to_account_id',
|
|
41
|
+
'to_actor_id',
|
|
42
|
+
'role',
|
|
43
|
+
'scope_kind',
|
|
44
|
+
'scope_id',
|
|
45
|
+
'message',
|
|
46
|
+
'created_at',
|
|
47
|
+
'expires_at',
|
|
48
|
+
'accepted_at',
|
|
49
|
+
'declined_at',
|
|
50
|
+
'decline_reason',
|
|
51
|
+
'retracted_at',
|
|
52
|
+
'superseded_at',
|
|
53
|
+
'resulting_role_grant_id'
|
|
54
|
+
];
|
|
55
|
+
/**
|
|
56
|
+
* Outer `SELECT` over an `updated` CTE of `role_grant_offer` rows, joining
|
|
57
|
+
* the grantor's `account_id` on as `from_account_id` — the `SupersededOffer` /
|
|
58
|
+
* `DeclinedOffer` shape every offer-superseding cascade returns (the revoke
|
|
59
|
+
* cascades in `auth/role_grant_queries.ts`, decline + accept-supersede in
|
|
60
|
+
* `auth/role_grant_offer_queries.ts`). The CTE must be named `updated` and
|
|
61
|
+
* `RETURNING` the full offer row.
|
|
62
|
+
*/
|
|
63
|
+
export const ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT = `SELECT ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'u')}, grantor.account_id AS from_account_id
|
|
64
|
+
FROM updated u
|
|
65
|
+
JOIN actor grantor ON grantor.id = u.from_actor_id`;
|
|
25
66
|
export const ROLE_GRANT_OFFER_SCHEMA = `
|
|
26
67
|
CREATE TABLE IF NOT EXISTS role_grant_offer (
|
|
27
68
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_offer_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"role_grant_offer_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
14
|
import { assert_row } from '../db/assert_row.js';
|
|
15
|
-
import {
|
|
15
|
+
import { columns_sql, qualify_columns } from '../db/sql_columns.js';
|
|
16
|
+
import { ROLE_GRANT_OFFER_COLUMNS, ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN, ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID, ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT } from './role_grant_offer_ddl.js';
|
|
17
|
+
import { query_create_role_grant, query_role_grant_by_id } from './role_grant_queries.js';
|
|
16
18
|
import { query_audit_log } from './audit_log_queries.js';
|
|
17
19
|
/**
|
|
18
20
|
* Error thrown by offer-lifecycle queries when the offer is in a non-pending
|
|
@@ -152,7 +154,7 @@ export const query_role_grant_offer_create = async (deps, input) => {
|
|
|
152
154
|
to_actor_id = EXCLUDED.to_actor_id,
|
|
153
155
|
message = EXCLUDED.message,
|
|
154
156
|
expires_at = EXCLUDED.expires_at
|
|
155
|
-
RETURNING
|
|
157
|
+
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)}`, [
|
|
156
158
|
input.from_actor_id,
|
|
157
159
|
input.to_account_id,
|
|
158
160
|
input.to_actor_id ?? null,
|
|
@@ -191,11 +193,9 @@ export const query_role_grant_offer_decline = async (deps, offer_id, to_account_
|
|
|
191
193
|
AND declined_at IS NULL
|
|
192
194
|
AND retracted_at IS NULL
|
|
193
195
|
AND superseded_at IS NULL
|
|
194
|
-
RETURNING
|
|
196
|
+
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)}
|
|
195
197
|
)
|
|
196
|
-
|
|
197
|
-
FROM updated u
|
|
198
|
-
JOIN actor grantor ON grantor.id = u.from_actor_id`, [offer_id, to_account_id, reason ?? null]);
|
|
198
|
+
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [offer_id, to_account_id, reason ?? null]);
|
|
199
199
|
if (updated)
|
|
200
200
|
return updated;
|
|
201
201
|
return resolve_terminal_or_missing(deps, offer_id, { to_account_id });
|
|
@@ -220,7 +220,7 @@ export const query_role_grant_offer_retract = async (deps, offer_id, from_actor_
|
|
|
220
220
|
AND declined_at IS NULL
|
|
221
221
|
AND retracted_at IS NULL
|
|
222
222
|
AND superseded_at IS NULL
|
|
223
|
-
RETURNING
|
|
223
|
+
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)}`, [offer_id, from_actor_id]);
|
|
224
224
|
if (updated)
|
|
225
225
|
return updated;
|
|
226
226
|
return resolve_terminal_or_missing(deps, offer_id, { from_actor_id });
|
|
@@ -238,7 +238,7 @@ const resolve_terminal_or_missing = async (deps, offer_id, scope) => {
|
|
|
238
238
|
conditions.push(`from_actor_id = $${idx++}`);
|
|
239
239
|
params.push(scope.from_actor_id);
|
|
240
240
|
}
|
|
241
|
-
const row = await deps.db.query_one(`SELECT
|
|
241
|
+
const row = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)} FROM role_grant_offer WHERE ${conditions.join(' AND ')}`, params);
|
|
242
242
|
if (!row)
|
|
243
243
|
return null;
|
|
244
244
|
if (row.accepted_at || row.declined_at || row.retracted_at || row.superseded_at) {
|
|
@@ -254,7 +254,7 @@ const resolve_terminal_or_missing = async (deps, offer_id, scope) => {
|
|
|
254
254
|
* sweep (`query_role_grant_offer_sweep_expired`) handles audit tombstoning.
|
|
255
255
|
*/
|
|
256
256
|
export const query_role_grant_offer_list = async (deps, to_account_id) => {
|
|
257
|
-
return deps.db.query(`SELECT
|
|
257
|
+
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)} FROM role_grant_offer
|
|
258
258
|
WHERE to_account_id = $1
|
|
259
259
|
AND accepted_at IS NULL
|
|
260
260
|
AND declined_at IS NULL
|
|
@@ -269,7 +269,7 @@ export const query_role_grant_offer_list = async (deps, to_account_id) => {
|
|
|
269
269
|
* Includes terminal offers — used by the grantor-side admin / history view.
|
|
270
270
|
*/
|
|
271
271
|
export const query_role_grant_offer_history_for_account = async (deps, account_id, limit = 100, offset = 0) => {
|
|
272
|
-
return deps.db.query(`SELECT o
|
|
272
|
+
return deps.db.query(`SELECT ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'o')} FROM role_grant_offer o
|
|
273
273
|
LEFT JOIN actor a ON a.id = o.from_actor_id
|
|
274
274
|
WHERE o.to_account_id = $1 OR a.account_id = $1
|
|
275
275
|
ORDER BY o.created_at DESC
|
|
@@ -280,7 +280,7 @@ export const query_role_grant_offer_history_for_account = async (deps, account_i
|
|
|
280
280
|
* expired (server-side filter), or missing.
|
|
281
281
|
*/
|
|
282
282
|
export const query_role_grant_offer_find_pending = async (deps, offer_id) => {
|
|
283
|
-
const row = await deps.db.query_one(`SELECT
|
|
283
|
+
const row = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)} FROM role_grant_offer
|
|
284
284
|
WHERE id = $1
|
|
285
285
|
AND accepted_at IS NULL
|
|
286
286
|
AND declined_at IS NULL
|
|
@@ -298,7 +298,7 @@ export const query_role_grant_offer_find_pending = async (deps, offer_id) => {
|
|
|
298
298
|
* already exists for the offer id).
|
|
299
299
|
*/
|
|
300
300
|
export const query_role_grant_offer_sweep_expired = async (deps) => {
|
|
301
|
-
return deps.db.query(`SELECT
|
|
301
|
+
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)} FROM role_grant_offer
|
|
302
302
|
WHERE accepted_at IS NULL
|
|
303
303
|
AND declined_at IS NULL
|
|
304
304
|
AND retracted_at IS NULL
|
|
@@ -346,7 +346,7 @@ export const query_accept_offer = async (deps, input) => {
|
|
|
346
346
|
// We defer writing `accepted_at` until the role_grant row exists — the
|
|
347
347
|
// `role_grant_offer_role_grant_iff_accepted` CHECK constraint demands both be set
|
|
348
348
|
// (or neither) at row-visibility time.
|
|
349
|
-
const locked = await deps.db.query_one(`SELECT
|
|
349
|
+
const locked = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)} FROM role_grant_offer
|
|
350
350
|
WHERE id = $1 AND to_account_id = $2
|
|
351
351
|
FOR UPDATE`, [offer_id, to_account_id]);
|
|
352
352
|
if (!locked) {
|
|
@@ -355,9 +355,7 @@ export const query_accept_offer = async (deps, input) => {
|
|
|
355
355
|
if (locked.accepted_at) {
|
|
356
356
|
// Race winner already committed; return the pre-existing role_grant.
|
|
357
357
|
// `role_grant_offer_role_grant_iff_accepted` CHECK guarantees resulting_role_grant_id is non-null.
|
|
358
|
-
const role_grant = assert_row(await deps.
|
|
359
|
-
locked.resulting_role_grant_id
|
|
360
|
-
]), 'resulting_role_grant lookup');
|
|
358
|
+
const role_grant = assert_row(await query_role_grant_by_id(deps, locked.resulting_role_grant_id), 'resulting_role_grant lookup');
|
|
361
359
|
// Multi-actor guard: two actors on the same recipient account may
|
|
362
360
|
// both race an account-grain offer — the loser must not silently
|
|
363
361
|
// receive the winner's role_grant (which would tell them "you got it"
|
|
@@ -411,37 +409,22 @@ export const query_accept_offer = async (deps, input) => {
|
|
|
411
409
|
if (!actor_check) {
|
|
412
410
|
throw new Error(`Accepting actor ${actor_id} does not belong to account ${to_account_id} (offer ${offer_id})`);
|
|
413
411
|
}
|
|
414
|
-
// Insert the role_grant
|
|
415
|
-
// code path already granted the same (actor, role, scope_kind, scope),
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
DO NOTHING
|
|
426
|
-
RETURNING *`, [actor_id, locked.role, locked.scope_kind, locked.scope_id, locked.from_actor_id, locked.id]);
|
|
427
|
-
let role_grant;
|
|
428
|
-
if (granted_role_grant) {
|
|
429
|
-
role_grant = granted_role_grant;
|
|
430
|
-
}
|
|
431
|
-
else {
|
|
432
|
-
const existing = await deps.db.query_one(`SELECT * FROM role_grant
|
|
433
|
-
WHERE actor_id = $1
|
|
434
|
-
AND role = $2
|
|
435
|
-
AND scope_kind IS NOT DISTINCT FROM $3
|
|
436
|
-
AND scope_id IS NOT DISTINCT FROM $4
|
|
437
|
-
AND revoked_at IS NULL`, [actor_id, locked.role, locked.scope_kind, locked.scope_id]);
|
|
438
|
-
role_grant = assert_row(existing, 'query_accept_offer idempotent role_grant lookup');
|
|
439
|
-
}
|
|
412
|
+
// Insert the role_grant through the shared idempotent grant — if another
|
|
413
|
+
// code path already granted the same (actor, role, scope_kind, scope), it
|
|
414
|
+
// returns that row. Stamps the grantor and the source offer on the row.
|
|
415
|
+
const role_grant = await query_create_role_grant(deps, {
|
|
416
|
+
actor_id,
|
|
417
|
+
role: locked.role,
|
|
418
|
+
scope_kind: locked.scope_kind,
|
|
419
|
+
scope_id: locked.scope_id,
|
|
420
|
+
granted_by: locked.from_actor_id,
|
|
421
|
+
source_offer_id: locked.id
|
|
422
|
+
});
|
|
440
423
|
// Single UPDATE sets both sides of the CHECK constraint at once.
|
|
441
424
|
const offer_accepted = await deps.db.query_one(`UPDATE role_grant_offer
|
|
442
425
|
SET accepted_at = NOW(), resulting_role_grant_id = $2
|
|
443
426
|
WHERE id = $1
|
|
444
|
-
RETURNING
|
|
427
|
+
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)}`, [locked.id, role_grant.id]);
|
|
445
428
|
const offer = assert_row(offer_accepted, 'mark offer accepted');
|
|
446
429
|
// Supersede sibling pending offers for the same (to_account, role, scope).
|
|
447
430
|
// Forecloses the "accept this other sibling later to get the role back
|
|
@@ -459,13 +442,11 @@ export const query_accept_offer = async (deps, input) => {
|
|
|
459
442
|
AND declined_at IS NULL
|
|
460
443
|
AND retracted_at IS NULL
|
|
461
444
|
AND superseded_at IS NULL
|
|
462
|
-
RETURNING
|
|
445
|
+
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)}
|
|
463
446
|
)
|
|
464
|
-
|
|
465
|
-
FROM updated u
|
|
466
|
-
JOIN actor grantor ON grantor.id = u.from_actor_id`, [to_account_id, offer.role, offer.scope_id, offer.id]);
|
|
447
|
+
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [to_account_id, offer.role, offer.scope_id, offer.id]);
|
|
467
448
|
// Emit audit events in-transaction (atomic with the role_grant insert).
|
|
468
|
-
// `RETURNING
|
|
449
|
+
// The `RETURNING` after the SET guarantees `offer.resulting_role_grant_id === role_grant.id`.
|
|
469
450
|
// Accept binds the actor deterministically — populate both target
|
|
470
451
|
// columns to mirror `role_grant_create` (the in-tx pair) so forensic
|
|
471
452
|
// queries don't have to split between the two events.
|
|
@@ -10,6 +10,15 @@ import type { Uuid } from '@fuzdev/fuz_util/id.ts';
|
|
|
10
10
|
import type { QueryDeps } from '../db/query_deps.js';
|
|
11
11
|
import type { RoleGrant, CreateRoleGrantInput } from './account_schema.js';
|
|
12
12
|
import type { SupersededOffer } from './role_grant_offer_schema.js';
|
|
13
|
+
/**
|
|
14
|
+
* The full `role_grant` column set, named explicitly so a row read fails loud
|
|
15
|
+
* on schema drift (see `ACCOUNT_COLUMNS` in `auth/account_queries.ts` for the
|
|
16
|
+
* outage class). Column order follows `RoleGrant` — the frozen `fuz_auth` v0
|
|
17
|
+
* body plus the appended `scope_kind` / `scope_id` / `revoked_reason` /
|
|
18
|
+
* `source_offer_id` columns. Keep in sync with `RoleGrant` and the
|
|
19
|
+
* `role_grant` migration chain in `auth/auth_ddl.ts`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ROLE_GRANT_COLUMNS: readonly ["id", "actor_id", "role", "scope_kind", "scope_id", "created_at", "expires_at", "revoked_at", "revoked_by", "revoked_reason", "granted_by", "source_offer_id"];
|
|
13
22
|
/**
|
|
14
23
|
* Grant a role_grant to an actor.
|
|
15
24
|
* Idempotent — if an active role_grant already exists for this actor, role, and
|
|
@@ -31,6 +40,15 @@ import type { SupersededOffer } from './role_grant_offer_schema.js';
|
|
|
31
40
|
* @mutates `role_grant` table - inserts a row when no active role_grant matches `(actor_id, role, scope_kind, scope_id)`
|
|
32
41
|
*/
|
|
33
42
|
export declare const query_create_role_grant: (deps: QueryDeps, input: CreateRoleGrantInput) => Promise<RoleGrant>;
|
|
43
|
+
/**
|
|
44
|
+
* Find a role_grant by id, regardless of revoked / expired state.
|
|
45
|
+
*
|
|
46
|
+
* Unscoped by design — the one caller is `query_accept_offer`'s race-loser
|
|
47
|
+
* path, which resolves an already-accepted offer's `resulting_role_grant_id`
|
|
48
|
+
* (a row the offer's own IDOR guard has already vouched for). Authority
|
|
49
|
+
* checks stay at the call site.
|
|
50
|
+
*/
|
|
51
|
+
export declare const query_role_grant_by_id: (deps: QueryDeps, id: Uuid) => Promise<RoleGrant | undefined>;
|
|
34
52
|
/**
|
|
35
53
|
* Look up the role of an active role_grant (constrained to a specific
|
|
36
54
|
* actor) plus the actor's `account_id`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"role_grant_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_queries.ts"],"names":[],"mappings":"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"}
|