@fuzdev/fuz_app 0.110.1 → 0.112.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/CLAUDE.md +15 -11
- package/dist/actions/action_rpc.d.ts +3 -3
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +4 -6
- package/dist/actions/peer_ping.d.ts +8 -0
- package/dist/actions/peer_ping.d.ts.map +1 -1
- package/dist/actions/peer_ping.js +8 -0
- package/dist/actions/peer_request.d.ts +1 -1
- package/dist/actions/peer_request.js +1 -1
- package/dist/actions/perform_action.d.ts +16 -9
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +30 -21
- package/dist/actions/register_action_ws.d.ts +1 -1
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +88 -21
- package/dist/auth/CLAUDE.md +59 -10
- 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_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +10 -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 +70 -16
- package/dist/auth/account_schema.d.ts +27 -9
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +23 -3
- package/dist/auth/actor_search_actions.d.ts +3 -3
- package/dist/auth/actor_search_actions.d.ts.map +1 -1
- package/dist/auth/actor_search_actions.js +10 -6
- 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 +36 -8
- 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 +16 -3
- package/dist/auth/audit_log_queries.d.ts +21 -3
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +38 -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/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +3 -8
- package/dist/auth/cell_audit_actions.d.ts.map +1 -1
- package/dist/auth/cell_audit_actions.js +1 -1
- package/dist/auth/cell_field_actions.js +1 -1
- package/dist/auth/cell_grant_actions.js +1 -1
- package/dist/auth/cell_item_actions.js +1 -1
- 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 +28 -7
- 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 +34 -2
- package/dist/auth/role_grant_offer_ddl.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_ddl.js +61 -2
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_queries.js +48 -53
- 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 +58 -18
- 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 +26 -6
- package/dist/db/CLAUDE.md +34 -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 +12 -3
- package/dist/db/cell_field_queries.d.ts.map +1 -1
- package/dist/db/cell_field_queries.js +25 -7
- package/dist/db/cell_grant_queries.d.ts +10 -2
- package/dist/db/cell_grant_queries.d.ts.map +1 -1
- package/dist/db/cell_grant_queries.js +29 -11
- package/dist/db/cell_item_queries.d.ts +13 -2
- package/dist/db/cell_item_queries.d.ts.map +1 -1
- package/dist/db/cell_item_queries.js +27 -7
- package/dist/db/cell_queries.d.ts +32 -3
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +57 -22
- 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 +27 -4
- 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 +144 -0
- package/dist/db/sql_columns.d.ts.map +1 -0
- package/dist/db/sql_columns.js +164 -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 +4 -2
- package/dist/http/jsonrpc_helpers.d.ts +13 -0
- package/dist/http/jsonrpc_helpers.d.ts.map +1 -1
- package/dist/http/jsonrpc_helpers.js +13 -0
- 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/server/serve_fact_route.d.ts.map +1 -1
- package/dist/server/serve_fact_route.js +7 -2
- package/dist/testing/CLAUDE.md +55 -28
- package/dist/testing/admin_integration.d.ts +0 -3
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +0 -1
- package/dist/testing/adversarial_404.d.ts.map +1 -1
- package/dist/testing/adversarial_404.js +4 -1
- package/dist/testing/adversarial_input.d.ts.map +1 -1
- package/dist/testing/adversarial_input.js +5 -4
- package/dist/testing/app_server.d.ts +4 -3
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +11 -5
- package/dist/testing/attack_surface.d.ts +16 -0
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +36 -13
- package/dist/testing/audit_completeness.d.ts +0 -3
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +0 -1
- package/dist/testing/auth_apps.d.ts +9 -2
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +9 -2
- package/dist/testing/cross_backend/account_lifecycle.d.ts +5 -4
- package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/account_lifecycle.js +7 -0
- package/dist/testing/cross_backend/action_manifest.d.ts +19 -3
- package/dist/testing/cross_backend/action_manifest.d.ts.map +1 -1
- package/dist/testing/cross_backend/action_manifest.js +10 -4
- package/dist/testing/cross_backend/action_manifest_parity.d.ts +9 -3
- package/dist/testing/cross_backend/action_manifest_parity.d.ts.map +1 -1
- package/dist/testing/cross_backend/action_manifest_parity.js +6 -0
- package/dist/testing/cross_backend/actor_lookup.d.ts +3 -2
- package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.d.ts +4 -2
- package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.js +1 -1
- package/dist/testing/cross_backend/cell_crud.d.ts +2 -2
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +10 -0
- package/dist/testing/cross_backend/cell_gated_create.d.ts +3 -3
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_grant_role.d.ts +2 -2
- package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_relations.d.ts +2 -2
- package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
- package/dist/testing/cross_backend/expected_schema.json +1 -1
- package/dist/testing/cross_backend/fact_serving.d.ts +2 -2
- package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
- package/dist/testing/cross_backend/make_cross_backend_project.d.ts +17 -2
- package/dist/testing/cross_backend/make_cross_backend_project.d.ts.map +1 -1
- package/dist/testing/cross_backend/make_cross_backend_project.js +23 -3
- package/dist/testing/cross_backend/origin.d.ts +4 -2
- package/dist/testing/cross_backend/origin.d.ts.map +1 -1
- package/dist/testing/cross_backend/origin.js +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.js +11 -7
- package/dist/testing/cross_backend/query_shape.d.ts +6 -5
- package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
- package/dist/testing/cross_backend/ready.d.ts +2 -2
- package/dist/testing/cross_backend/ready.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_enumeration.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_enumeration.js +7 -1
- package/dist/testing/cross_backend/setup.d.ts +26 -6
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/standard.d.ts +5 -1
- package/dist/testing/cross_backend/standard.d.ts.map +1 -1
- package/dist/testing/cross_backend/standard.js +1 -5
- package/dist/testing/cross_backend/testing_backdoor.js +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +5 -0
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_lifetime.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_lifetime.js +6 -0
- package/dist/testing/cross_backend/token_scope_surface.d.ts +2 -2
- package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/wire_shapes.d.ts +22 -0
- package/dist/testing/cross_backend/wire_shapes.d.ts.map +1 -0
- package/dist/testing/cross_backend/wire_shapes.js +41 -0
- package/dist/testing/data_exposure.d.ts +0 -3
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +0 -1
- package/dist/testing/db.d.ts +62 -7
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +86 -28
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +15 -2
- package/dist/testing/round_trip.d.ts +0 -3
- package/dist/testing/round_trip.d.ts.map +1 -1
- package/dist/testing/round_trip.js +0 -4
- package/dist/testing/rpc_attack_surface.js +2 -2
- package/dist/testing/rpc_round_trip.d.ts +0 -3
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +0 -1
- package/dist/testing/schema_generators.d.ts +15 -0
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +45 -11
- package/dist/testing/standard.d.ts +5 -1
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +2 -7
- package/dist/timestamp.d.ts +58 -0
- package/dist/timestamp.d.ts.map +1 -0
- package/dist/timestamp.js +57 -0
- package/dist/ui/AdminOverview.svelte +5 -10
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/RoleGrantOfferHistory.svelte +3 -1
- package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -1
- package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
- package/dist/ui/role_grant_offers_state.svelte.js +6 -2
- package/package.json +1 -1
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,37 @@ 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
|
+
* The `role_grant_offer` timestamp override, by row qualifier (`''` bare,
|
|
39
|
+
* `o` for the history JOIN, `u` for the `updated` CTE's outer select). Lives
|
|
40
|
+
* beside the const for the same reason the const does — both query modules
|
|
41
|
+
* that project this table need it.
|
|
42
|
+
*
|
|
43
|
+
* A cascade's inner `RETURNING` deliberately projects the *raw* columns: the
|
|
44
|
+
* outer select over the `updated` CTE applies this, and `to_char` over an
|
|
45
|
+
* already-formatted text column would fail.
|
|
46
|
+
*/
|
|
47
|
+
export declare const role_grant_offer_expr: (alias: string) => import("../db/sql_columns.js").ColumnExpr;
|
|
48
|
+
/**
|
|
49
|
+
* Outer `SELECT` over an `updated` CTE of `role_grant_offer` rows, joining
|
|
50
|
+
* the grantor's `account_id` on as `from_account_id` — the `SupersededOffer` /
|
|
51
|
+
* `DeclinedOffer` shape every offer-superseding cascade returns (the revoke
|
|
52
|
+
* cascades in `auth/role_grant_queries.ts`, decline + accept-supersede in
|
|
53
|
+
* `auth/role_grant_offer_queries.ts`). The CTE must be named `updated` and
|
|
54
|
+
* `RETURNING` the full offer row.
|
|
55
|
+
*/
|
|
56
|
+
export declare const ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT: string;
|
|
25
57
|
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
58
|
/**
|
|
27
59
|
* 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;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,8DAOhC,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,oCAAoC,QAMI,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 { iso8601_timestamp_expr, 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,63 @@ 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
|
+
* The `role_grant_offer` timestamp override, by row qualifier (`''` bare,
|
|
57
|
+
* `o` for the history JOIN, `u` for the `updated` CTE's outer select). Lives
|
|
58
|
+
* beside the const for the same reason the const does — both query modules
|
|
59
|
+
* that project this table need it.
|
|
60
|
+
*
|
|
61
|
+
* A cascade's inner `RETURNING` deliberately projects the *raw* columns: the
|
|
62
|
+
* outer select over the `updated` CTE applies this, and `to_char` over an
|
|
63
|
+
* already-formatted text column would fail.
|
|
64
|
+
*/
|
|
65
|
+
export const role_grant_offer_expr = iso8601_timestamp_expr(ROLE_GRANT_OFFER_COLUMNS, [
|
|
66
|
+
'created_at',
|
|
67
|
+
'expires_at',
|
|
68
|
+
'accepted_at',
|
|
69
|
+
'declined_at',
|
|
70
|
+
'retracted_at',
|
|
71
|
+
'superseded_at'
|
|
72
|
+
]);
|
|
73
|
+
/**
|
|
74
|
+
* Outer `SELECT` over an `updated` CTE of `role_grant_offer` rows, joining
|
|
75
|
+
* the grantor's `account_id` on as `from_account_id` — the `SupersededOffer` /
|
|
76
|
+
* `DeclinedOffer` shape every offer-superseding cascade returns (the revoke
|
|
77
|
+
* cascades in `auth/role_grant_queries.ts`, decline + accept-supersede in
|
|
78
|
+
* `auth/role_grant_offer_queries.ts`). The CTE must be named `updated` and
|
|
79
|
+
* `RETURNING` the full offer row.
|
|
80
|
+
*/
|
|
81
|
+
export const ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT = `SELECT ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'u', role_grant_offer_expr('u'))}, grantor.account_id AS from_account_id
|
|
82
|
+
FROM updated u
|
|
83
|
+
JOIN actor grantor ON grantor.id = u.from_actor_id`;
|
|
25
84
|
export const ROLE_GRANT_OFFER_SCHEMA = `
|
|
26
85
|
CREATE TABLE IF NOT EXISTS role_grant_offer (
|
|
27
86
|
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_expr, 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, role_grant_offer_expr(''))}`, [
|
|
156
158
|
input.from_actor_id,
|
|
157
159
|
input.to_account_id,
|
|
158
160
|
input.to_actor_id ?? null,
|
|
@@ -191,11 +193,10 @@ 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
|
-
|
|
196
|
+
-- raw columns; the outer grantor select formats them
|
|
197
|
+
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)}
|
|
195
198
|
)
|
|
196
|
-
|
|
197
|
-
FROM updated u
|
|
198
|
-
JOIN actor grantor ON grantor.id = u.from_actor_id`, [offer_id, to_account_id, reason ?? null]);
|
|
199
|
+
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [offer_id, to_account_id, reason ?? null]);
|
|
199
200
|
if (updated)
|
|
200
201
|
return updated;
|
|
201
202
|
return resolve_terminal_or_missing(deps, offer_id, { to_account_id });
|
|
@@ -220,7 +221,7 @@ export const query_role_grant_offer_retract = async (deps, offer_id, from_actor_
|
|
|
220
221
|
AND declined_at IS NULL
|
|
221
222
|
AND retracted_at IS NULL
|
|
222
223
|
AND superseded_at IS NULL
|
|
223
|
-
RETURNING
|
|
224
|
+
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))}`, [offer_id, from_actor_id]);
|
|
224
225
|
if (updated)
|
|
225
226
|
return updated;
|
|
226
227
|
return resolve_terminal_or_missing(deps, offer_id, { from_actor_id });
|
|
@@ -238,7 +239,7 @@ const resolve_terminal_or_missing = async (deps, offer_id, scope) => {
|
|
|
238
239
|
conditions.push(`from_actor_id = $${idx++}`);
|
|
239
240
|
params.push(scope.from_actor_id);
|
|
240
241
|
}
|
|
241
|
-
const row = await deps.db.query_one(`SELECT
|
|
242
|
+
const row = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))} FROM role_grant_offer WHERE ${conditions.join(' AND ')}`, params);
|
|
242
243
|
if (!row)
|
|
243
244
|
return null;
|
|
244
245
|
if (row.accepted_at || row.declined_at || row.retracted_at || row.superseded_at) {
|
|
@@ -254,14 +255,14 @@ const resolve_terminal_or_missing = async (deps, offer_id, scope) => {
|
|
|
254
255
|
* sweep (`query_role_grant_offer_sweep_expired`) handles audit tombstoning.
|
|
255
256
|
*/
|
|
256
257
|
export const query_role_grant_offer_list = async (deps, to_account_id) => {
|
|
257
|
-
return deps.db.query(`SELECT
|
|
258
|
+
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))} FROM role_grant_offer
|
|
258
259
|
WHERE to_account_id = $1
|
|
259
260
|
AND accepted_at IS NULL
|
|
260
261
|
AND declined_at IS NULL
|
|
261
262
|
AND retracted_at IS NULL
|
|
262
263
|
AND superseded_at IS NULL
|
|
263
264
|
AND expires_at > NOW()
|
|
264
|
-
ORDER BY expires_at ASC`, [to_account_id]);
|
|
265
|
+
ORDER BY role_grant_offer.expires_at ASC`, [to_account_id]);
|
|
265
266
|
};
|
|
266
267
|
/**
|
|
267
268
|
* List every offer involving an account (either direction), newest first.
|
|
@@ -269,7 +270,7 @@ export const query_role_grant_offer_list = async (deps, to_account_id) => {
|
|
|
269
270
|
* Includes terminal offers — used by the grantor-side admin / history view.
|
|
270
271
|
*/
|
|
271
272
|
export const query_role_grant_offer_history_for_account = async (deps, account_id, limit = 100, offset = 0) => {
|
|
272
|
-
return deps.db.query(`SELECT o
|
|
273
|
+
return deps.db.query(`SELECT ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'o', role_grant_offer_expr('o'))} FROM role_grant_offer o
|
|
273
274
|
LEFT JOIN actor a ON a.id = o.from_actor_id
|
|
274
275
|
WHERE o.to_account_id = $1 OR a.account_id = $1
|
|
275
276
|
ORDER BY o.created_at DESC
|
|
@@ -280,7 +281,7 @@ export const query_role_grant_offer_history_for_account = async (deps, account_i
|
|
|
280
281
|
* expired (server-side filter), or missing.
|
|
281
282
|
*/
|
|
282
283
|
export const query_role_grant_offer_find_pending = async (deps, offer_id) => {
|
|
283
|
-
const row = await deps.db.query_one(`SELECT
|
|
284
|
+
const row = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))} FROM role_grant_offer
|
|
284
285
|
WHERE id = $1
|
|
285
286
|
AND accepted_at IS NULL
|
|
286
287
|
AND declined_at IS NULL
|
|
@@ -298,13 +299,13 @@ export const query_role_grant_offer_find_pending = async (deps, offer_id) => {
|
|
|
298
299
|
* already exists for the offer id).
|
|
299
300
|
*/
|
|
300
301
|
export const query_role_grant_offer_sweep_expired = async (deps) => {
|
|
301
|
-
return deps.db.query(`SELECT
|
|
302
|
+
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))} FROM role_grant_offer
|
|
302
303
|
WHERE accepted_at IS NULL
|
|
303
304
|
AND declined_at IS NULL
|
|
304
305
|
AND retracted_at IS NULL
|
|
305
306
|
AND superseded_at IS NULL
|
|
306
307
|
AND expires_at <= NOW()
|
|
307
|
-
ORDER BY expires_at ASC`);
|
|
308
|
+
ORDER BY role_grant_offer.expires_at ASC`);
|
|
308
309
|
};
|
|
309
310
|
/**
|
|
310
311
|
* Accept an offer atomically: mark accepted, insert the role_grant, stamp
|
|
@@ -346,18 +347,27 @@ export const query_accept_offer = async (deps, input) => {
|
|
|
346
347
|
// We defer writing `accepted_at` until the role_grant row exists — the
|
|
347
348
|
// `role_grant_offer_role_grant_iff_accepted` CHECK constraint demands both be set
|
|
348
349
|
// (or neither) at row-visibility time.
|
|
349
|
-
|
|
350
|
+
//
|
|
351
|
+
// Trailing `expires_at > NOW() AS still_valid` rides on the FOR UPDATE row
|
|
352
|
+
// so the expiry branch below compares the **raw** column on Postgres' clock
|
|
353
|
+
// — the projected `expires_at` is floored to the second, so comparing it in
|
|
354
|
+
// JS would report expiry up to a second early. Twin of the Rust
|
|
355
|
+
// `query_accept_offer`'s `o.expires_at > NOW() AS still_valid`.
|
|
356
|
+
const locked_row = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))},
|
|
357
|
+
role_grant_offer.expires_at > NOW() AS still_valid
|
|
358
|
+
FROM role_grant_offer
|
|
350
359
|
WHERE id = $1 AND to_account_id = $2
|
|
351
360
|
FOR UPDATE`, [offer_id, to_account_id]);
|
|
352
|
-
if (!
|
|
361
|
+
if (!locked_row) {
|
|
353
362
|
throw new RoleGrantOfferNotFoundError(offer_id);
|
|
354
363
|
}
|
|
364
|
+
// `still_valid` is the comparison result, not a column of the offer — split
|
|
365
|
+
// it off so `locked` stays exactly `ROLE_GRANT_OFFER_COLUMNS`.
|
|
366
|
+
const { still_valid, ...locked } = locked_row;
|
|
355
367
|
if (locked.accepted_at) {
|
|
356
368
|
// Race winner already committed; return the pre-existing role_grant.
|
|
357
369
|
// `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');
|
|
370
|
+
const role_grant = assert_row(await query_role_grant_by_id(deps, locked.resulting_role_grant_id), 'resulting_role_grant lookup');
|
|
361
371
|
// Multi-actor guard: two actors on the same recipient account may
|
|
362
372
|
// both race an account-grain offer — the loser must not silently
|
|
363
373
|
// receive the winner's role_grant (which would tell them "you got it"
|
|
@@ -379,8 +389,9 @@ export const query_accept_offer = async (deps, input) => {
|
|
|
379
389
|
}
|
|
380
390
|
// Expiry check AFTER the accepted-path: a validly-accepted offer past its
|
|
381
391
|
// expires_at still returns the role_grant idempotently. Only pending offers
|
|
382
|
-
// past expiry reach this branch.
|
|
383
|
-
|
|
392
|
+
// past expiry reach this branch. `still_valid` rode on the FOR UPDATE row
|
|
393
|
+
// above, so the comparison stays on the raw column and Postgres' clock.
|
|
394
|
+
if (!still_valid) {
|
|
384
395
|
throw new RoleGrantOfferExpiredError(offer_id);
|
|
385
396
|
}
|
|
386
397
|
// Actor-targeted offer gate. When the offer is account-grain
|
|
@@ -411,37 +422,22 @@ export const query_accept_offer = async (deps, input) => {
|
|
|
411
422
|
if (!actor_check) {
|
|
412
423
|
throw new Error(`Accepting actor ${actor_id} does not belong to account ${to_account_id} (offer ${offer_id})`);
|
|
413
424
|
}
|
|
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
|
-
}
|
|
425
|
+
// Insert the role_grant through the shared idempotent grant — if another
|
|
426
|
+
// code path already granted the same (actor, role, scope_kind, scope), it
|
|
427
|
+
// returns that row. Stamps the grantor and the source offer on the row.
|
|
428
|
+
const role_grant = await query_create_role_grant(deps, {
|
|
429
|
+
actor_id,
|
|
430
|
+
role: locked.role,
|
|
431
|
+
scope_kind: locked.scope_kind,
|
|
432
|
+
scope_id: locked.scope_id,
|
|
433
|
+
granted_by: locked.from_actor_id,
|
|
434
|
+
source_offer_id: locked.id
|
|
435
|
+
});
|
|
440
436
|
// Single UPDATE sets both sides of the CHECK constraint at once.
|
|
441
437
|
const offer_accepted = await deps.db.query_one(`UPDATE role_grant_offer
|
|
442
438
|
SET accepted_at = NOW(), resulting_role_grant_id = $2
|
|
443
439
|
WHERE id = $1
|
|
444
|
-
RETURNING
|
|
440
|
+
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))}`, [locked.id, role_grant.id]);
|
|
445
441
|
const offer = assert_row(offer_accepted, 'mark offer accepted');
|
|
446
442
|
// Supersede sibling pending offers for the same (to_account, role, scope).
|
|
447
443
|
// Forecloses the "accept this other sibling later to get the role back
|
|
@@ -459,13 +455,12 @@ export const query_accept_offer = async (deps, input) => {
|
|
|
459
455
|
AND declined_at IS NULL
|
|
460
456
|
AND retracted_at IS NULL
|
|
461
457
|
AND superseded_at IS NULL
|
|
462
|
-
|
|
458
|
+
-- raw columns; the outer grantor select formats them
|
|
459
|
+
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)}
|
|
463
460
|
)
|
|
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]);
|
|
461
|
+
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [to_account_id, offer.role, offer.scope_id, offer.id]);
|
|
467
462
|
// Emit audit events in-transaction (atomic with the role_grant insert).
|
|
468
|
-
// `RETURNING
|
|
463
|
+
// The `RETURNING` after the SET guarantees `offer.resulting_role_grant_id === role_grant.id`.
|
|
469
464
|
// Accept binds the actor deterministically — populate both target
|
|
470
465
|
// columns to mirror `role_grant_create` (the in-tx pair) so forensic
|
|
471
466
|
// 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":"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;AAS3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,0KAaoB,CAAC;AAcpD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,OAAO,oBAAoB,KACzB,OAAO,CAAC,SAAS,CAmCnB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,SAAS,EACf,IAAI,IAAI,KACN,OAAO,CAAC,SAAS,GAAG,SAAS,CAI9B,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,2CAA2C,GACvD,MAAM,SAAS,EACf,eAAe,MAAM,EACrB,UAAU,MAAM,KACd,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,IAAI,CAAA;CAAE,GAAG,IAAI,CASnD,CAAC;AAEF,qHAAqH;AACrH,MAAM,WAAW,qBAAqB;IACrC,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB;;;;;;;;OAQG;IACH,iBAAiB,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,eAAe,IAAI,EACnB,UAAU,IAAI,EACd,YAAY,IAAI,GAAG,IAAI,EACvB,SAAS,MAAM,GAAG,IAAI,KACpB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CA8CtC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sCAAsC,GAClD,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAS1B,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,SAAS,EACf,UAAU,MAAM,EAChB,MAAM,MAAM,EACZ,WAAW,MAAM,GAAG,IAAI,KACtB,OAAO,CAAC,OAAO,CAajB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,MAAM,MAAM,KACV,OAAO,CAAC,OAAO,CAajB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,MAAM,MAAM,KACV,OAAO,CAAC,OAAO,CAiBjB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4CAA4C,GACxD,MAAM,SAAS,EACf,MAAM,MAAM,KACV,OAAO,CAAC,MAAM,CAehB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,+BAA+B,GAC3C,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAK1B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yCAAyC,GACrD,MAAM,SAAS,EACf,MAAM,MAAM,KACV,OAAO,CAAC,MAAM,GAAG,IAAI,CAgBvB,CAAC;AAEF,8IAA8I;AAC9I,MAAM,WAAW,oBAAoB;IACpC;;;;;;;;OAQG;IACH,OAAO,EAAE,KAAK,CAAC;QACd,aAAa,EAAE,IAAI,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,QAAQ,EAAE,IAAI,CAAC;QACf,QAAQ,EAAE,IAAI,CAAC;QACf,UAAU,EAAE,IAAI,CAAC;KACjB,CAAC,CAAC;IACH;;;;;;;;;;OAUG;IACH,iBAAiB,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,iCAAiC,GAC7C,MAAM,SAAS,EACf,UAAU,IAAI,EACd,YAAY,IAAI,GAAG,IAAI,EACvB,SAAS,MAAM,GAAG,IAAI,KACpB,OAAO,CAAC,oBAAoB,CA6C9B,CAAC;AAEF,iIAAiI;AACjI,MAAM,WAAW,gBAAgB;IAChC;;;;OAIG;IACH,OAAO,EAAE,KAAK,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;KACnB,CAAC,CAAC;IACH;;;;;OAKG;IACH,iBAAiB,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,SAAS,EACf,UAAU,MAAM,EAChB,MAAM,MAAM,EACZ,YAAY,MAAM,GAAG,IAAI,EACzB,SAAS,MAAM,GAAG,IAAI,KACpB,OAAO,CAAC,gBAAgB,CA2C1B,CAAC"}
|
|
@@ -7,7 +7,41 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import { assert_row } from '../db/assert_row.js';
|
|
10
|
-
import {
|
|
10
|
+
import { columns_sql, iso8601_timestamp_expr, qualify_columns } from '../db/sql_columns.js';
|
|
11
|
+
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';
|
|
12
|
+
/**
|
|
13
|
+
* The full `role_grant` column set, named explicitly so a row read fails loud
|
|
14
|
+
* on schema drift (see `ACCOUNT_COLUMNS` in `auth/account_queries.ts` for the
|
|
15
|
+
* outage class). Column order follows `RoleGrant` — the frozen `fuz_auth` v0
|
|
16
|
+
* body plus the appended `scope_kind` / `scope_id` / `revoked_reason` /
|
|
17
|
+
* `source_offer_id` columns. Keep in sync with `RoleGrant` and the
|
|
18
|
+
* `role_grant` migration chain in `auth/auth_ddl.ts`.
|
|
19
|
+
*/
|
|
20
|
+
export const ROLE_GRANT_COLUMNS = [
|
|
21
|
+
'id',
|
|
22
|
+
'actor_id',
|
|
23
|
+
'role',
|
|
24
|
+
'scope_kind',
|
|
25
|
+
'scope_id',
|
|
26
|
+
'created_at',
|
|
27
|
+
'expires_at',
|
|
28
|
+
'revoked_at',
|
|
29
|
+
'revoked_by',
|
|
30
|
+
'revoked_reason',
|
|
31
|
+
'granted_by',
|
|
32
|
+
'source_offer_id'
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* The `role_grant` timestamp override; every read here is on the bare table.
|
|
36
|
+
* `created_at` / `expires_at` ride `RoleGrantSummaryJson` (twin of the
|
|
37
|
+
* spine's `role_grant_summary_columns`); `revoked_at` is what
|
|
38
|
+
* `is_role_grant_active` reads.
|
|
39
|
+
*/
|
|
40
|
+
const role_grant_expr = iso8601_timestamp_expr(ROLE_GRANT_COLUMNS, [
|
|
41
|
+
'created_at',
|
|
42
|
+
'expires_at',
|
|
43
|
+
'revoked_at'
|
|
44
|
+
])('');
|
|
11
45
|
/**
|
|
12
46
|
* Grant a role_grant to an actor.
|
|
13
47
|
* Idempotent — if an active role_grant already exists for this actor, role, and
|
|
@@ -39,7 +73,7 @@ export const query_create_role_grant = async (deps, input) => {
|
|
|
39
73
|
)
|
|
40
74
|
WHERE revoked_at IS NULL
|
|
41
75
|
DO NOTHING
|
|
42
|
-
RETURNING
|
|
76
|
+
RETURNING ${columns_sql(ROLE_GRANT_COLUMNS, role_grant_expr)}`, [
|
|
43
77
|
input.actor_id,
|
|
44
78
|
input.role,
|
|
45
79
|
input.scope_kind ?? null,
|
|
@@ -51,7 +85,7 @@ export const query_create_role_grant = async (deps, input) => {
|
|
|
51
85
|
if (inserted)
|
|
52
86
|
return inserted;
|
|
53
87
|
// Active role_grant already exists — return it (idempotent grant).
|
|
54
|
-
const existing = await deps.db.query_one(`SELECT
|
|
88
|
+
const existing = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS, role_grant_expr)} FROM role_grant
|
|
55
89
|
WHERE actor_id = $1
|
|
56
90
|
AND role = $2
|
|
57
91
|
AND scope_kind IS NOT DISTINCT FROM $3
|
|
@@ -59,6 +93,15 @@ export const query_create_role_grant = async (deps, input) => {
|
|
|
59
93
|
AND revoked_at IS NULL`, [input.actor_id, input.role, input.scope_kind ?? null, input.scope_id ?? null]);
|
|
60
94
|
return assert_row(existing, 'idempotent role_grant grant');
|
|
61
95
|
};
|
|
96
|
+
/**
|
|
97
|
+
* Find a role_grant by id, regardless of revoked / expired state.
|
|
98
|
+
*
|
|
99
|
+
* Unscoped by design — the one caller is `query_accept_offer`'s race-loser
|
|
100
|
+
* path, which resolves an already-accepted offer's `resulting_role_grant_id`
|
|
101
|
+
* (a row the offer's own IDOR guard has already vouched for). Authority
|
|
102
|
+
* checks stay at the call site.
|
|
103
|
+
*/
|
|
104
|
+
export const query_role_grant_by_id = async (deps, id) => deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS, role_grant_expr)} FROM role_grant WHERE id = $1`, [id]);
|
|
62
105
|
/**
|
|
63
106
|
* Look up the role of an active role_grant (constrained to a specific
|
|
64
107
|
* actor) plus the actor's `account_id`.
|
|
@@ -136,11 +179,10 @@ export const query_revoke_role_grant = async (deps, role_grant_id, actor_id, rev
|
|
|
136
179
|
AND o.declined_at IS NULL
|
|
137
180
|
AND o.retracted_at IS NULL
|
|
138
181
|
AND o.superseded_at IS NULL
|
|
139
|
-
|
|
182
|
+
-- raw columns; the outer grantor select formats them
|
|
183
|
+
RETURNING ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'o')}
|
|
140
184
|
)
|
|
141
|
-
|
|
142
|
-
FROM updated u
|
|
143
|
-
JOIN actor grantor ON grantor.id = u.from_actor_id`, [actor_id, revoked.role, revoked.scope_id]);
|
|
185
|
+
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [actor_id, revoked.role, revoked.scope_id]);
|
|
144
186
|
return {
|
|
145
187
|
id: revoked.id,
|
|
146
188
|
role: revoked.role,
|
|
@@ -153,11 +195,11 @@ export const query_revoke_role_grant = async (deps, role_grant_id, actor_id, rev
|
|
|
153
195
|
* Find all active (non-revoked, non-expired) role_grants for an actor.
|
|
154
196
|
*/
|
|
155
197
|
export const query_role_grant_find_active_for_actor = async (deps, actor_id) => {
|
|
156
|
-
return deps.db.query(`SELECT
|
|
198
|
+
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS, role_grant_expr)} FROM role_grant
|
|
157
199
|
WHERE actor_id = $1
|
|
158
200
|
AND revoked_at IS NULL
|
|
159
201
|
AND (expires_at IS NULL OR expires_at > NOW())
|
|
160
|
-
ORDER BY created_at`, [actor_id]);
|
|
202
|
+
ORDER BY role_grant.created_at`, [actor_id]);
|
|
161
203
|
};
|
|
162
204
|
/**
|
|
163
205
|
* Check if an actor has an active role_grant for a given role.
|
|
@@ -284,7 +326,7 @@ export const query_count_active_accounts_with_global_role = async (deps, role) =
|
|
|
284
326
|
* List all role_grants for an actor (including revoked/expired).
|
|
285
327
|
*/
|
|
286
328
|
export const query_role_grant_list_for_actor = async (deps, actor_id) => {
|
|
287
|
-
return deps.db.query(`SELECT
|
|
329
|
+
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS, role_grant_expr)} FROM role_grant WHERE actor_id = $1 ORDER BY role_grant.created_at DESC`, [actor_id]);
|
|
288
330
|
};
|
|
289
331
|
/**
|
|
290
332
|
* Find the account ID of an account that holds an active role_grant for a given role.
|
|
@@ -366,11 +408,10 @@ export const query_role_grant_revoke_for_scope = async (deps, scope_id, revoked_
|
|
|
366
408
|
AND o.declined_at IS NULL
|
|
367
409
|
AND o.retracted_at IS NULL
|
|
368
410
|
AND o.superseded_at IS NULL
|
|
369
|
-
|
|
411
|
+
-- raw columns; the outer grantor select formats them
|
|
412
|
+
RETURNING ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'o')}
|
|
370
413
|
)
|
|
371
|
-
|
|
372
|
-
FROM updated u
|
|
373
|
-
JOIN actor grantor ON grantor.id = u.from_actor_id`, [scope_id]);
|
|
414
|
+
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [scope_id]);
|
|
374
415
|
return { revoked, superseded_offers };
|
|
375
416
|
};
|
|
376
417
|
/**
|
|
@@ -420,10 +461,9 @@ export const query_role_grant_revoke_role = async (deps, actor_id, role, revoked
|
|
|
420
461
|
AND o.declined_at IS NULL
|
|
421
462
|
AND o.retracted_at IS NULL
|
|
422
463
|
AND o.superseded_at IS NULL
|
|
423
|
-
|
|
464
|
+
-- raw columns; the outer grantor select formats them
|
|
465
|
+
RETURNING ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'o')}
|
|
424
466
|
)
|
|
425
|
-
|
|
426
|
-
FROM updated u
|
|
427
|
-
JOIN actor grantor ON grantor.id = u.from_actor_id`, [actor_id, role]);
|
|
467
|
+
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [actor_id, role]);
|
|
428
468
|
return { revoked, superseded_offers };
|
|
429
469
|
};
|
|
@@ -43,6 +43,17 @@ export declare const generate_session_token: () => string;
|
|
|
43
43
|
* @mutates `auth_session` table - inserts a row keyed by `token_hash`
|
|
44
44
|
*/
|
|
45
45
|
export declare const query_create_session: (deps: QueryDeps, token_hash: string, account_id: string, expires_at: Date) => Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* The full `auth_session` column set, named explicitly so a row read fails
|
|
48
|
+
* loud on schema drift — `SELECT *` would silently carry a dropped or
|
|
49
|
+
* leftover column into the strict-validated wire shapes (see
|
|
50
|
+
* `ACCOUNT_COLUMNS` in `auth/account_queries.ts` for the outage class this
|
|
51
|
+
* discipline exists to prevent; the Rust twin already names columns at every
|
|
52
|
+
* session site). Keep in sync with `AuthSession` and the migration chain's
|
|
53
|
+
* end state — not the frozen v0 DDL in `auth/auth_ddl.ts`, which still
|
|
54
|
+
* creates `last_seen_at` for the appended drop migration to remove.
|
|
55
|
+
*/
|
|
56
|
+
export declare const AUTH_SESSION_COLUMNS: readonly ["id", "account_id", "created_at", "expires_at"];
|
|
46
57
|
/**
|
|
47
58
|
* Get a session if it exists, is not expired, and has not been revoked.
|
|
48
59
|
*
|
|
@@ -132,9 +143,7 @@ export declare const query_session_enforce_limit: (deps: QueryDeps, account_id:
|
|
|
132
143
|
* List all active sessions across all accounts with usernames.
|
|
133
144
|
*
|
|
134
145
|
* Ordered by `created_at DESC` (newest session first), matching the
|
|
135
|
-
* per-account listing.
|
|
136
|
-
* but is decorative — nothing updates it post-mint (it always equals
|
|
137
|
-
* `created_at`); it is slated for removal on its own twin migration.
|
|
146
|
+
* per-account listing.
|
|
138
147
|
*
|
|
139
148
|
* @param deps - query dependencies
|
|
140
149
|
* @param limit - maximum entries to return
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAElE;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAEjE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,SAElD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAEzC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,EAClB,YAAY,IAAI,KACd,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,2DAKoB,CAAC;AAQtD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,WAAW,GAAG,SAAS,CAKjC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAU,KACR,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAK5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAc,MAAM,KAClB,OAAO,CAAC,MAAM,CAYhB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,cAAW,KACT,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CASnD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,CAKnF,CAAC"}
|