@fuzdev/fuz_app 0.59.0 → 0.60.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 +5 -5
- package/dist/actions/action_codegen.d.ts +1 -1
- package/dist/actions/action_codegen.js +2 -2
- package/dist/actions/action_event_helpers.d.ts +3 -3
- package/dist/actions/action_event_helpers.js +8 -8
- package/dist/actions/action_event_types.d.ts +3 -3
- package/dist/actions/action_event_types.js +3 -3
- package/dist/actions/transports_ws_auth_guard.d.ts +2 -2
- package/dist/actions/transports_ws_auth_guard.js +3 -3
- package/dist/auth/CLAUDE.md +157 -15
- package/dist/auth/actor_lookup_action_specs.d.ts +127 -0
- package/dist/auth/actor_lookup_action_specs.d.ts.map +1 -0
- package/dist/auth/actor_lookup_action_specs.js +93 -0
- package/dist/auth/actor_lookup_actions.d.ts +19 -0
- package/dist/auth/actor_lookup_actions.d.ts.map +1 -0
- package/dist/auth/actor_lookup_actions.js +32 -0
- package/dist/auth/actor_lookup_queries.d.ts +44 -0
- package/dist/auth/actor_lookup_queries.d.ts.map +1 -0
- package/dist/auth/actor_lookup_queries.js +42 -0
- package/dist/auth/actor_search_action_specs.d.ts +166 -0
- package/dist/auth/actor_search_action_specs.d.ts.map +1 -0
- package/dist/auth/actor_search_action_specs.js +139 -0
- package/dist/auth/actor_search_actions.d.ts +31 -0
- package/dist/auth/actor_search_actions.d.ts.map +1 -0
- package/dist/auth/actor_search_actions.js +61 -0
- package/dist/auth/actor_search_queries.d.ts +75 -0
- package/dist/auth/actor_search_queries.d.ts.map +1 -0
- package/dist/auth/actor_search_queries.js +91 -0
- package/dist/auth/admin_actions.js +2 -2
- package/dist/auth/all_action_spec_registries.d.ts +55 -0
- package/dist/auth/all_action_spec_registries.d.ts.map +1 -0
- package/dist/auth/all_action_spec_registries.js +59 -0
- package/dist/auth/audit_emitter.d.ts +1 -1
- package/dist/auth/audit_emitter.js +2 -2
- package/dist/auth/audit_log_queries.d.ts +1 -1
- package/dist/auth/audit_log_queries.js +3 -3
- package/dist/auth/audit_log_routes.d.ts +1 -1
- package/dist/auth/audit_log_routes.js +1 -1
- package/dist/auth/audit_log_schema.d.ts +5 -5
- package/dist/auth/audit_log_schema.js +7 -7
- package/dist/auth/auth_ddl.d.ts +7 -0
- package/dist/auth/auth_ddl.d.ts.map +1 -1
- package/dist/auth/auth_ddl.js +8 -0
- package/dist/auth/credential_type_schema.d.ts +1 -1
- package/dist/auth/credential_type_schema.js +3 -3
- package/dist/auth/grant_path_schema.d.ts +1 -1
- package/dist/auth/grant_path_schema.js +3 -3
- package/dist/auth/migrations.d.ts +4 -4
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +7 -6
- package/dist/auth/role_grant_offer_actions.js +2 -2
- package/dist/auth/role_grant_offer_notifications.d.ts +2 -2
- package/dist/auth/role_grant_offer_notifications.js +2 -2
- package/dist/auth/role_grant_queries.d.ts +21 -0
- package/dist/auth/role_grant_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.js +31 -0
- package/dist/auth/role_schema.d.ts +2 -2
- package/dist/auth/role_schema.js +3 -3
- package/dist/auth/self_service_role_actions.d.ts +1 -1
- package/dist/auth/self_service_role_actions.js +2 -2
- package/dist/auth/session_cookie.d.ts +1 -1
- package/dist/auth/session_cookie.js +1 -1
- package/dist/auth/session_middleware.d.ts +1 -1
- package/dist/auth/session_middleware.js +5 -5
- package/dist/rate_limiter.d.ts +5 -5
- package/dist/rate_limiter.js +6 -6
- package/dist/realtime/sse_auth_guard.d.ts +3 -3
- package/dist/realtime/sse_auth_guard.js +4 -4
- package/dist/server/app_backend.d.ts +3 -3
- package/dist/server/app_backend.js +4 -4
- package/dist/server/app_server.d.ts +1 -1
- package/dist/server/app_server.js +10 -10
- package/dist/testing/CLAUDE.md +22 -12
- package/dist/testing/admin_integration.js +4 -4
- package/dist/testing/app_server.d.ts +1 -1
- package/dist/testing/app_server.js +2 -2
- package/dist/testing/attack_surface.d.ts +4 -4
- package/dist/testing/attack_surface.js +6 -6
- package/dist/testing/audit_completeness.js +4 -4
- package/dist/testing/data_exposure.d.ts +2 -2
- package/dist/testing/data_exposure.js +7 -7
- package/dist/testing/db.d.ts +8 -8
- package/dist/testing/db.js +11 -11
- package/dist/testing/integration.js +4 -4
- package/dist/testing/integration_helpers.d.ts +6 -6
- package/dist/testing/integration_helpers.js +7 -7
- package/dist/testing/rate_limiting.js +4 -4
- package/dist/testing/round_trip.js +2 -2
- package/dist/testing/rpc_round_trip.js +2 -2
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +23 -2
- package/dist/testing/sse_round_trip.js +2 -2
- package/dist/testing/surface_invariants.d.ts +4 -4
- package/dist/testing/surface_invariants.js +5 -5
- package/package.json +1 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prefix-based actor search.
|
|
3
|
+
*
|
|
4
|
+
* Sibling to `actor_lookup_queries.ts` — that resolves a batch of ids to
|
|
5
|
+
* labels; this resolves a partial name to candidate actors. Same row
|
|
6
|
+
* shape (`ActorLookupRow`) so the labels arc on the consumer side stays
|
|
7
|
+
* uniform.
|
|
8
|
+
*
|
|
9
|
+
* Case-insensitive LIKE-prefix on `actor.name` backed by the
|
|
10
|
+
* `idx_actor_name_lower` functional index. LIKE wildcards (`%`, `_`,
|
|
11
|
+
* `\`) in the query string are escaped at the JS layer so the
|
|
12
|
+
* prefix-only contract is enforceable — an unescaped `%xyz` would
|
|
13
|
+
* widen the surface to full-LIKE and defeat the per-call cap as a
|
|
14
|
+
* binding bound.
|
|
15
|
+
*
|
|
16
|
+
* ## Auth filtering — `scope_ids`
|
|
17
|
+
*
|
|
18
|
+
* When `scope_ids` is non-empty, the result is filtered to actors
|
|
19
|
+
* holding an **active** role_grant on one of the supplied scopes. Active
|
|
20
|
+
* means `revoked_at IS NULL AND (expires_at IS NULL OR expires_at > NOW())`.
|
|
21
|
+
* Stale (revoked / expired) role_grants do **not** confer membership for
|
|
22
|
+
* search-visibility purposes — otherwise a removed student would
|
|
23
|
+
* remain visible to teachers indefinitely.
|
|
24
|
+
*
|
|
25
|
+
* The `DISTINCT` on `actor.id` collapses the case where an actor holds
|
|
26
|
+
* multiple matching role_grants in the supplied scope set into one row.
|
|
27
|
+
*
|
|
28
|
+
* When `scope_ids` is omitted (admin-only global path; the handler
|
|
29
|
+
* gates), no role_grant join — every actor with a matching prefix is
|
|
30
|
+
* returned.
|
|
31
|
+
*
|
|
32
|
+
* ## Info-leak posture (see `actor_search_action_specs.ts` § audit)
|
|
33
|
+
*
|
|
34
|
+
* - Row shape **omits** `account_id` — the join is control-plane, not
|
|
35
|
+
* wire-visible. Identical to `actor_lookup_queries.ts`.
|
|
36
|
+
* - Hard-deleted actors (cascade-orphaned via `actor.account_id` FK)
|
|
37
|
+
* drop out silently.
|
|
38
|
+
* - No `created_at` / `updated_at` projected (timing-oracle avoidance).
|
|
39
|
+
* - Scope-membership uses ANY on the supplied `scope_ids` array but
|
|
40
|
+
* never surfaces "which scope matched" — the result row carries only
|
|
41
|
+
* the actor's wire shape. An attacker passing a random scope_id
|
|
42
|
+
* learns at most "this scope has at least one member matching X"
|
|
43
|
+
* if a match exists, indistinguishable from a no-match search; the
|
|
44
|
+
* caller-passes-scope_ids design (handler trusts the array as a
|
|
45
|
+
* filter, not as authority) means the attacker had to obtain the
|
|
46
|
+
* scope_id from somewhere else first.
|
|
47
|
+
*
|
|
48
|
+
* Caller bounds `limit` (the action-spec layer enforces
|
|
49
|
+
* `ACTOR_SEARCH_LIMIT_MAX`); SQL clamps to that cap on the call site
|
|
50
|
+
* before reaching this query.
|
|
51
|
+
*
|
|
52
|
+
* @module
|
|
53
|
+
*/
|
|
54
|
+
import type { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
55
|
+
import type { QueryDeps } from '../db/query_deps.js';
|
|
56
|
+
import type { ActorLookupRow } from './actor_lookup_queries.js';
|
|
57
|
+
/** Inputs for `query_actor_search`. */
|
|
58
|
+
export interface ActorSearchQueryInput {
|
|
59
|
+
/** Case-insensitive prefix string. Must be non-empty (action layer enforces `min(1)`). */
|
|
60
|
+
query: string;
|
|
61
|
+
/**
|
|
62
|
+
* When non-empty, restrict to actors holding an active role_grant on one
|
|
63
|
+
* of these scope ids. When empty / omitted, no scope filter is applied —
|
|
64
|
+
* the handler is responsible for the admin gate.
|
|
65
|
+
*/
|
|
66
|
+
scope_ids?: ReadonlyArray<Uuid>;
|
|
67
|
+
/** Maximum rows to return. The handler clamps to `ACTOR_SEARCH_LIMIT_MAX`. */
|
|
68
|
+
limit: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Search actors by case-insensitive prefix on `actor.name`, optionally
|
|
72
|
+
* filtered to those holding an active role_grant on one of `scope_ids`.
|
|
73
|
+
*/
|
|
74
|
+
export declare const query_actor_search: (deps: QueryDeps, input: ActorSearchQueryInput) => Promise<Array<ActorLookupRow>>;
|
|
75
|
+
//# sourceMappingURL=actor_search_queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actor_search_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/actor_search_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAE9D,uCAAuC;AACvC,MAAM,WAAW,qBAAqB;IACrC,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IAChC,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;CACd;AASD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC9B,MAAM,SAAS,EACf,OAAO,qBAAqB,KAC1B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAmC/B,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prefix-based actor search.
|
|
3
|
+
*
|
|
4
|
+
* Sibling to `actor_lookup_queries.ts` — that resolves a batch of ids to
|
|
5
|
+
* labels; this resolves a partial name to candidate actors. Same row
|
|
6
|
+
* shape (`ActorLookupRow`) so the labels arc on the consumer side stays
|
|
7
|
+
* uniform.
|
|
8
|
+
*
|
|
9
|
+
* Case-insensitive LIKE-prefix on `actor.name` backed by the
|
|
10
|
+
* `idx_actor_name_lower` functional index. LIKE wildcards (`%`, `_`,
|
|
11
|
+
* `\`) in the query string are escaped at the JS layer so the
|
|
12
|
+
* prefix-only contract is enforceable — an unescaped `%xyz` would
|
|
13
|
+
* widen the surface to full-LIKE and defeat the per-call cap as a
|
|
14
|
+
* binding bound.
|
|
15
|
+
*
|
|
16
|
+
* ## Auth filtering — `scope_ids`
|
|
17
|
+
*
|
|
18
|
+
* When `scope_ids` is non-empty, the result is filtered to actors
|
|
19
|
+
* holding an **active** role_grant on one of the supplied scopes. Active
|
|
20
|
+
* means `revoked_at IS NULL AND (expires_at IS NULL OR expires_at > NOW())`.
|
|
21
|
+
* Stale (revoked / expired) role_grants do **not** confer membership for
|
|
22
|
+
* search-visibility purposes — otherwise a removed student would
|
|
23
|
+
* remain visible to teachers indefinitely.
|
|
24
|
+
*
|
|
25
|
+
* The `DISTINCT` on `actor.id` collapses the case where an actor holds
|
|
26
|
+
* multiple matching role_grants in the supplied scope set into one row.
|
|
27
|
+
*
|
|
28
|
+
* When `scope_ids` is omitted (admin-only global path; the handler
|
|
29
|
+
* gates), no role_grant join — every actor with a matching prefix is
|
|
30
|
+
* returned.
|
|
31
|
+
*
|
|
32
|
+
* ## Info-leak posture (see `actor_search_action_specs.ts` § audit)
|
|
33
|
+
*
|
|
34
|
+
* - Row shape **omits** `account_id` — the join is control-plane, not
|
|
35
|
+
* wire-visible. Identical to `actor_lookup_queries.ts`.
|
|
36
|
+
* - Hard-deleted actors (cascade-orphaned via `actor.account_id` FK)
|
|
37
|
+
* drop out silently.
|
|
38
|
+
* - No `created_at` / `updated_at` projected (timing-oracle avoidance).
|
|
39
|
+
* - Scope-membership uses ANY on the supplied `scope_ids` array but
|
|
40
|
+
* never surfaces "which scope matched" — the result row carries only
|
|
41
|
+
* the actor's wire shape. An attacker passing a random scope_id
|
|
42
|
+
* learns at most "this scope has at least one member matching X"
|
|
43
|
+
* if a match exists, indistinguishable from a no-match search; the
|
|
44
|
+
* caller-passes-scope_ids design (handler trusts the array as a
|
|
45
|
+
* filter, not as authority) means the attacker had to obtain the
|
|
46
|
+
* scope_id from somewhere else first.
|
|
47
|
+
*
|
|
48
|
+
* Caller bounds `limit` (the action-spec layer enforces
|
|
49
|
+
* `ACTOR_SEARCH_LIMIT_MAX`); SQL clamps to that cap on the call site
|
|
50
|
+
* before reaching this query.
|
|
51
|
+
*
|
|
52
|
+
* @module
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* Escape LIKE wildcards in a user-supplied query string so the SQL
|
|
56
|
+
* prefix-match cannot be widened by user input. The `\` escape char is
|
|
57
|
+
* declared on the LIKE expression via `ESCAPE '\'`.
|
|
58
|
+
*/
|
|
59
|
+
const escape_like_pattern = (s) => s.replace(/[\\%_]/g, '\\$&');
|
|
60
|
+
/**
|
|
61
|
+
* Search actors by case-insensitive prefix on `actor.name`, optionally
|
|
62
|
+
* filtered to those holding an active role_grant on one of `scope_ids`.
|
|
63
|
+
*/
|
|
64
|
+
export const query_actor_search = async (deps, input) => {
|
|
65
|
+
const escaped_prefix = escape_like_pattern(input.query.toLowerCase());
|
|
66
|
+
const scope_ids = input.scope_ids ?? [];
|
|
67
|
+
if (scope_ids.length === 0) {
|
|
68
|
+
// Admin-global path — no role_grant join. Handler enforces admin.
|
|
69
|
+
return deps.db.query(`SELECT act.id, a.username, act.name AS display_name
|
|
70
|
+
FROM actor act
|
|
71
|
+
JOIN account a ON a.id = act.account_id
|
|
72
|
+
WHERE LOWER(act.name) LIKE $1 || '%' ESCAPE '\\'
|
|
73
|
+
ORDER BY display_name, id
|
|
74
|
+
LIMIT $2`, [escaped_prefix, input.limit]);
|
|
75
|
+
}
|
|
76
|
+
// Scoped path — filter to actors with an active role_grant on any of
|
|
77
|
+
// `scope_ids`. DISTINCT collapses actors holding multiple matching
|
|
78
|
+
// role_grants in the supplied scope set into one row. ORDER BY must
|
|
79
|
+
// reference SELECT-listed columns under DISTINCT, so we sort by the
|
|
80
|
+
// `display_name` alias rather than `LOWER(act.name)`.
|
|
81
|
+
return deps.db.query(`SELECT DISTINCT act.id, a.username, act.name AS display_name
|
|
82
|
+
FROM actor act
|
|
83
|
+
JOIN account a ON a.id = act.account_id
|
|
84
|
+
JOIN role_grant rg ON rg.actor_id = act.id
|
|
85
|
+
AND rg.scope_id = ANY($2)
|
|
86
|
+
AND rg.revoked_at IS NULL
|
|
87
|
+
AND (rg.expires_at IS NULL OR rg.expires_at > NOW())
|
|
88
|
+
WHERE LOWER(act.name) LIKE $1 || '%' ESCAPE '\\'
|
|
89
|
+
ORDER BY display_name, id
|
|
90
|
+
LIMIT $3`, [escaped_prefix, scope_ids, input.limit]);
|
|
91
|
+
};
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { rpc_action } from '../actions/action_rpc.js';
|
|
31
31
|
import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
|
|
32
|
-
import {
|
|
32
|
+
import { builtin_role_specs_by_name, list_roles_with_grant_path, } from './role_schema.js';
|
|
33
33
|
import { GRANT_PATH_ADMIN } from './grant_path_schema.js';
|
|
34
34
|
import { query_account_by_email, query_account_by_id, query_account_by_username, query_admin_account_list, } from './account_queries.js';
|
|
35
35
|
import { query_session_list_all_active, query_session_revoke_all_for_account, } from './session_queries.js';
|
|
@@ -54,7 +54,7 @@ import { admin_account_list_action_spec, admin_session_list_action_spec, admin_s
|
|
|
54
54
|
* @mutates `options.app_settings` ref - `app_settings_update` writes `open_signup`, `updated_at`, and `updated_by` so signup middleware reads without a DB round trip
|
|
55
55
|
*/
|
|
56
56
|
export const create_admin_actions = (deps, options = {}) => {
|
|
57
|
-
const role_specs = options.roles?.role_specs ??
|
|
57
|
+
const role_specs = options.roles?.role_specs ?? builtin_role_specs_by_name;
|
|
58
58
|
const grantable_roles = list_roles_with_grant_path(role_specs, GRANT_PATH_ADMIN);
|
|
59
59
|
const account_list_handler = async (input, ctx) => {
|
|
60
60
|
const accounts = await query_admin_account_list(ctx, {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical list of every fuz_auth action-spec registry — **for cross-cutting
|
|
3
|
+
* walkers and codegen only**. Not a mounting surface; consumers continue to
|
|
4
|
+
* import individual `all_*_action_specs` bundles (and `create_*_actions`
|
|
5
|
+
* factories) per registration site.
|
|
6
|
+
*
|
|
7
|
+
* The "one main bundle" alternative is an antipattern for mounting:
|
|
8
|
+
* `create_standard_rpc_actions` (admin + role_grant_offer + account) is the
|
|
9
|
+
* canonical surface, and opt-in registries (`self_service_role`,
|
|
10
|
+
* `actor_lookup`) are deliberately opt-in because their eligibility
|
|
11
|
+
* (`eligible_roles`) or coverage (byline labels) is app-specific. Spreading
|
|
12
|
+
* everything into a single mount would silently widen the dispatch surface
|
|
13
|
+
* the moment a new opt-in landed — the exact failure mode this module is
|
|
14
|
+
* built to detect, not propagate. See `./CLAUDE.md` §RPC actions
|
|
15
|
+
* (`standard_rpc_actions.ts`).
|
|
16
|
+
*
|
|
17
|
+
* Use cases for this registry:
|
|
18
|
+
*
|
|
19
|
+
* - Cross-registry walker tests (input-invariants, auth-shape
|
|
20
|
+
* biconditional) — iterate the spec arrays once, fail when a new
|
|
21
|
+
* registry slips by without an entry here.
|
|
22
|
+
* - Codegen that needs to see every fuz_auth surface at once
|
|
23
|
+
* (typed-client filters, attack-surface reports). For typed-client
|
|
24
|
+
* wiring of the standard surface, prefer `all_standard_action_specs`
|
|
25
|
+
* in `./standard_action_specs.ts` — it mirrors the
|
|
26
|
+
* `create_standard_rpc_actions` mount and stays narrower than this
|
|
27
|
+
* registry-of-registries (no opt-in bundles).
|
|
28
|
+
*
|
|
29
|
+
* `protocol_action_specs` (heartbeat / cancel) is **not** included —
|
|
30
|
+
* those are transport-level wire-protocol concerns shipped by fuz_app
|
|
31
|
+
* and spread by every consumer at registration via `protocol_actions`
|
|
32
|
+
* from `../actions/protocol.ts`. Walker tests that need protocol
|
|
33
|
+
* coverage spread `protocol_action_specs` separately.
|
|
34
|
+
*
|
|
35
|
+
* @module
|
|
36
|
+
*/
|
|
37
|
+
import type { RequestResponseActionSpec } from '../actions/action_spec.js';
|
|
38
|
+
/** One named entry in the registry-of-registries. */
|
|
39
|
+
export interface FuzAuthActionSpecRegistry {
|
|
40
|
+
/** Stable identifier matching the source bundle name (`'admin'`, `'role_grant_offer'`, etc.). */
|
|
41
|
+
name: string;
|
|
42
|
+
/** The bundle's spec array — kept readonly here even when the source declares it mutable. */
|
|
43
|
+
specs: ReadonlyArray<RequestResponseActionSpec>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Every fuz_auth action-spec registry, in dependency-stable order.
|
|
47
|
+
*
|
|
48
|
+
* Update this list when a new fuz_auth registry lands. The walker tests
|
|
49
|
+
* (`action_spec_input_invariants.test.ts`,
|
|
50
|
+
* `all_action_spec_registries.acting_biconditional.test.ts`) iterate
|
|
51
|
+
* over it — a missing entry silently skips coverage, which is the
|
|
52
|
+
* failure mode the registry-of-registries shape exists to prevent.
|
|
53
|
+
*/
|
|
54
|
+
export declare const all_fuz_auth_action_spec_registries: ReadonlyArray<FuzAuthActionSpecRegistry>;
|
|
55
|
+
//# sourceMappingURL=all_action_spec_registries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"all_action_spec_registries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/all_action_spec_registries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,2BAA2B,CAAC;AAQzE,qDAAqD;AACrD,MAAM,WAAW,yBAAyB;IACzC,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAC;IACb,6FAA6F;IAC7F,KAAK,EAAE,aAAa,CAAC,yBAAyB,CAAC,CAAC;CAChD;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,mCAAmC,EAAE,aAAa,CAAC,yBAAyB,CAOxF,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical list of every fuz_auth action-spec registry — **for cross-cutting
|
|
3
|
+
* walkers and codegen only**. Not a mounting surface; consumers continue to
|
|
4
|
+
* import individual `all_*_action_specs` bundles (and `create_*_actions`
|
|
5
|
+
* factories) per registration site.
|
|
6
|
+
*
|
|
7
|
+
* The "one main bundle" alternative is an antipattern for mounting:
|
|
8
|
+
* `create_standard_rpc_actions` (admin + role_grant_offer + account) is the
|
|
9
|
+
* canonical surface, and opt-in registries (`self_service_role`,
|
|
10
|
+
* `actor_lookup`) are deliberately opt-in because their eligibility
|
|
11
|
+
* (`eligible_roles`) or coverage (byline labels) is app-specific. Spreading
|
|
12
|
+
* everything into a single mount would silently widen the dispatch surface
|
|
13
|
+
* the moment a new opt-in landed — the exact failure mode this module is
|
|
14
|
+
* built to detect, not propagate. See `./CLAUDE.md` §RPC actions
|
|
15
|
+
* (`standard_rpc_actions.ts`).
|
|
16
|
+
*
|
|
17
|
+
* Use cases for this registry:
|
|
18
|
+
*
|
|
19
|
+
* - Cross-registry walker tests (input-invariants, auth-shape
|
|
20
|
+
* biconditional) — iterate the spec arrays once, fail when a new
|
|
21
|
+
* registry slips by without an entry here.
|
|
22
|
+
* - Codegen that needs to see every fuz_auth surface at once
|
|
23
|
+
* (typed-client filters, attack-surface reports). For typed-client
|
|
24
|
+
* wiring of the standard surface, prefer `all_standard_action_specs`
|
|
25
|
+
* in `./standard_action_specs.ts` — it mirrors the
|
|
26
|
+
* `create_standard_rpc_actions` mount and stays narrower than this
|
|
27
|
+
* registry-of-registries (no opt-in bundles).
|
|
28
|
+
*
|
|
29
|
+
* `protocol_action_specs` (heartbeat / cancel) is **not** included —
|
|
30
|
+
* those are transport-level wire-protocol concerns shipped by fuz_app
|
|
31
|
+
* and spread by every consumer at registration via `protocol_actions`
|
|
32
|
+
* from `../actions/protocol.ts`. Walker tests that need protocol
|
|
33
|
+
* coverage spread `protocol_action_specs` separately.
|
|
34
|
+
*
|
|
35
|
+
* @module
|
|
36
|
+
*/
|
|
37
|
+
import { all_admin_action_specs } from './admin_action_specs.js';
|
|
38
|
+
import { all_role_grant_offer_action_specs } from './role_grant_offer_action_specs.js';
|
|
39
|
+
import { all_account_action_specs } from './account_action_specs.js';
|
|
40
|
+
import { all_self_service_role_action_specs } from './self_service_role_action_specs.js';
|
|
41
|
+
import { all_actor_lookup_action_specs } from './actor_lookup_action_specs.js';
|
|
42
|
+
import { all_actor_search_action_specs } from './actor_search_action_specs.js';
|
|
43
|
+
/**
|
|
44
|
+
* Every fuz_auth action-spec registry, in dependency-stable order.
|
|
45
|
+
*
|
|
46
|
+
* Update this list when a new fuz_auth registry lands. The walker tests
|
|
47
|
+
* (`action_spec_input_invariants.test.ts`,
|
|
48
|
+
* `all_action_spec_registries.acting_biconditional.test.ts`) iterate
|
|
49
|
+
* over it — a missing entry silently skips coverage, which is the
|
|
50
|
+
* failure mode the registry-of-registries shape exists to prevent.
|
|
51
|
+
*/
|
|
52
|
+
export const all_fuz_auth_action_spec_registries = [
|
|
53
|
+
{ name: 'admin', specs: all_admin_action_specs },
|
|
54
|
+
{ name: 'role_grant_offer', specs: all_role_grant_offer_action_specs },
|
|
55
|
+
{ name: 'account', specs: all_account_action_specs },
|
|
56
|
+
{ name: 'self_service_role', specs: all_self_service_role_action_specs },
|
|
57
|
+
{ name: 'actor_lookup', specs: all_actor_lookup_action_specs },
|
|
58
|
+
{ name: 'actor_search', specs: all_actor_search_action_specs },
|
|
59
|
+
];
|
|
@@ -144,7 +144,7 @@ export interface CreateAuditEmitterOptions {
|
|
|
144
144
|
*/
|
|
145
145
|
on_audit_event?: ((event: AuditLogEvent) => void) | null;
|
|
146
146
|
/**
|
|
147
|
-
* Audit-log config. Defaults to `
|
|
147
|
+
* Audit-log config. Defaults to `builtin_audit_log_config`. Consumer-
|
|
148
148
|
* extended configs from `create_audit_log_config({extra_events})` get
|
|
149
149
|
* registered here once at backend assembly.
|
|
150
150
|
*/
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* @module
|
|
34
34
|
*/
|
|
35
35
|
import { query_audit_log } from './audit_log_queries.js';
|
|
36
|
-
import {
|
|
36
|
+
import { builtin_audit_log_config, } from './audit_log_schema.js';
|
|
37
37
|
/**
|
|
38
38
|
* Build a bound `AuditEmitter`. Called once at `create_app_backend` time.
|
|
39
39
|
*
|
|
@@ -41,7 +41,7 @@ import { BUILTIN_AUDIT_LOG_CONFIG, } from './audit_log_schema.js';
|
|
|
41
41
|
* @returns the bound emitter; closes over the pool + config + listener chain
|
|
42
42
|
*/
|
|
43
43
|
export const create_audit_emitter = (options) => {
|
|
44
|
-
const { db, log, audit_log_config =
|
|
44
|
+
const { db, log, audit_log_config = builtin_audit_log_config } = options;
|
|
45
45
|
const on_event_chain = [];
|
|
46
46
|
if (options.on_audit_event)
|
|
47
47
|
on_event_chain.push(options.on_audit_event);
|
|
@@ -38,7 +38,7 @@ export declare const reset_audit_unknown_event_type_failures: () => void;
|
|
|
38
38
|
*
|
|
39
39
|
* @param deps - query dependencies
|
|
40
40
|
* @param input - the audit event to record
|
|
41
|
-
* @param config - audit-log config. Defaults to `
|
|
41
|
+
* @param config - audit-log config. Defaults to `builtin_audit_log_config`.
|
|
42
42
|
* @returns the inserted audit log row
|
|
43
43
|
* @mutates `audit_log` table - inserts the new row
|
|
44
44
|
* @mutates drift counters - bumps `audit_unknown_event_type_failures` and/or `audit_metadata_validation_failures` on mismatch
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
14
|
import { assert_row } from '../db/assert_row.js';
|
|
15
|
-
import { AUDIT_LOG_DEFAULT_LIMIT,
|
|
15
|
+
import { AUDIT_LOG_DEFAULT_LIMIT, builtin_audit_log_config, } from './audit_log_schema.js';
|
|
16
16
|
/**
|
|
17
17
|
* Process-wide counter for audit metadata validation failures. `query_audit_log`
|
|
18
18
|
* increments on `safeParse` mismatch and writes the row anyway (fail-open —
|
|
@@ -61,12 +61,12 @@ export const reset_audit_unknown_event_type_failures = () => {
|
|
|
61
61
|
*
|
|
62
62
|
* @param deps - query dependencies
|
|
63
63
|
* @param input - the audit event to record
|
|
64
|
-
* @param config - audit-log config. Defaults to `
|
|
64
|
+
* @param config - audit-log config. Defaults to `builtin_audit_log_config`.
|
|
65
65
|
* @returns the inserted audit log row
|
|
66
66
|
* @mutates `audit_log` table - inserts the new row
|
|
67
67
|
* @mutates drift counters - bumps `audit_unknown_event_type_failures` and/or `audit_metadata_validation_failures` on mismatch
|
|
68
68
|
*/
|
|
69
|
-
export const query_audit_log = async (deps, input, config =
|
|
69
|
+
export const query_audit_log = async (deps, input, config = builtin_audit_log_config) => {
|
|
70
70
|
if (!config.event_types.includes(input.event_type)) {
|
|
71
71
|
audit_unknown_event_type_failures++;
|
|
72
72
|
console.error(`[audit_log] unknown event_type '${input.event_type}' — register via create_audit_log_config({extra_events})`);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* `admin_session_list` on the same file. What remains here is the optional
|
|
7
7
|
* `GET /audit/stream` SSE route — streams aren't an action-kind, so they
|
|
8
8
|
* stay on REST. The event payload broadcast on the stream surfaces via
|
|
9
|
-
* `
|
|
9
|
+
* `audit_log_event_specs` (one `EventSpec` per audit event type) declared
|
|
10
10
|
* alongside the broadcaster in `../realtime/sse_auth_guard.ts`.
|
|
11
11
|
*
|
|
12
12
|
* @module
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* `admin_session_list` on the same file. What remains here is the optional
|
|
7
7
|
* `GET /audit/stream` SSE route — streams aren't an action-kind, so they
|
|
8
8
|
* stay on REST. The event payload broadcast on the stream surfaces via
|
|
9
|
-
* `
|
|
9
|
+
* `audit_log_event_specs` (one `EventSpec` per audit event type) declared
|
|
10
10
|
* alongside the broadcaster in `../realtime/sse_auth_guard.ts`.
|
|
11
11
|
*
|
|
12
12
|
* @module
|
|
@@ -64,7 +64,7 @@ export type AuditOutcome = z.infer<typeof AuditOutcome>;
|
|
|
64
64
|
* stub schema); the Zod schemas themselves are reachable and mutable —
|
|
65
65
|
* freeze isn't a security boundary.
|
|
66
66
|
*/
|
|
67
|
-
export declare const
|
|
67
|
+
export declare const audit_metadata_schemas: Readonly<{
|
|
68
68
|
login: z.ZodNullable<z.ZodObject<{
|
|
69
69
|
username: z.ZodString;
|
|
70
70
|
}, z.core.$loose>>;
|
|
@@ -175,7 +175,7 @@ export declare const AUDIT_METADATA_SCHEMAS: Readonly<{
|
|
|
175
175
|
}>;
|
|
176
176
|
/** Mapped type of metadata shapes per event type, derived from Zod schemas. */
|
|
177
177
|
export type AuditMetadataMap = {
|
|
178
|
-
[K in AuditEventType]: z.infer<(typeof
|
|
178
|
+
[K in AuditEventType]: z.infer<(typeof audit_metadata_schemas)[K]>;
|
|
179
179
|
};
|
|
180
180
|
/** Audit log row from the database. See `AuditLogEventJson` for `event_type` widening rationale. */
|
|
181
181
|
export interface AuditLogEvent {
|
|
@@ -270,7 +270,7 @@ export interface AuditLogInput<T extends string = AuditEventType> {
|
|
|
270
270
|
* Lets consumers extend the closed `AUDIT_EVENT_TYPES` enum with their own
|
|
271
271
|
* event strings (and metadata Zod schemas) without forking. Pass to
|
|
272
272
|
* `create_audit_emitter` (or `query_audit_log` for in-tx call sites) as the
|
|
273
|
-
* optional `config` argument; both default to `
|
|
273
|
+
* optional `config` argument; both default to `builtin_audit_log_config`.
|
|
274
274
|
*
|
|
275
275
|
* The DB column is `TEXT NOT NULL` and never enforced an enum, so consumer
|
|
276
276
|
* event types round-trip through `query_audit_log_list` and SSE identically
|
|
@@ -291,7 +291,7 @@ export interface AuditLogConfig {
|
|
|
291
291
|
readonly metadata_schemas: Readonly<Record<string, z.ZodType>>;
|
|
292
292
|
}
|
|
293
293
|
/** Builtin fuz_app audit-log config — every existing event type and its metadata schema. */
|
|
294
|
-
export declare const
|
|
294
|
+
export declare const builtin_audit_log_config: AuditLogConfig;
|
|
295
295
|
/** Options for `create_audit_log_config`. */
|
|
296
296
|
export interface CreateAuditLogConfigOptions {
|
|
297
297
|
/**
|
|
@@ -313,7 +313,7 @@ export interface CreateAuditLogConfigOptions {
|
|
|
313
313
|
*
|
|
314
314
|
* Call once at startup; pass the result to `create_app_backend` (which
|
|
315
315
|
* threads it into `AppDeps.audit`). Builtin handlers omit the
|
|
316
|
-
* `audit_log_config` slot and pick up `
|
|
316
|
+
* `audit_log_config` slot and pick up `builtin_audit_log_config`.
|
|
317
317
|
*
|
|
318
318
|
* @throws Error when an `extra_events` key collides with a builtin event type or fails `AuditEventTypeName` format validation
|
|
319
319
|
*/
|
|
@@ -64,7 +64,7 @@ export const AuditOutcome = z.enum(['success', 'failure']);
|
|
|
64
64
|
* stub schema); the Zod schemas themselves are reachable and mutable —
|
|
65
65
|
* freeze isn't a security boundary.
|
|
66
66
|
*/
|
|
67
|
-
export const
|
|
67
|
+
export const audit_metadata_schemas = Object.freeze({
|
|
68
68
|
login: z
|
|
69
69
|
.looseObject({
|
|
70
70
|
username: z.string().meta({ description: 'Username submitted with the login attempt.' }),
|
|
@@ -265,9 +265,9 @@ export const get_audit_metadata = (event) => {
|
|
|
265
265
|
return event.metadata;
|
|
266
266
|
};
|
|
267
267
|
/** Builtin fuz_app audit-log config — every existing event type and its metadata schema. */
|
|
268
|
-
export const
|
|
268
|
+
export const builtin_audit_log_config = Object.freeze({
|
|
269
269
|
event_types: AUDIT_EVENT_TYPES,
|
|
270
|
-
metadata_schemas:
|
|
270
|
+
metadata_schemas: audit_metadata_schemas,
|
|
271
271
|
});
|
|
272
272
|
/**
|
|
273
273
|
* Build an `AuditLogConfig` by merging fuz_app builtins with consumer extras.
|
|
@@ -277,20 +277,20 @@ export const BUILTIN_AUDIT_LOG_CONFIG = Object.freeze({
|
|
|
277
277
|
*
|
|
278
278
|
* Call once at startup; pass the result to `create_app_backend` (which
|
|
279
279
|
* threads it into `AppDeps.audit`). Builtin handlers omit the
|
|
280
|
-
* `audit_log_config` slot and pick up `
|
|
280
|
+
* `audit_log_config` slot and pick up `builtin_audit_log_config`.
|
|
281
281
|
*
|
|
282
282
|
* @throws Error when an `extra_events` key collides with a builtin event type or fails `AuditEventTypeName` format validation
|
|
283
283
|
*/
|
|
284
284
|
export const create_audit_log_config = (options) => {
|
|
285
285
|
const extras = options?.extra_events;
|
|
286
286
|
if (!extras)
|
|
287
|
-
return
|
|
287
|
+
return builtin_audit_log_config;
|
|
288
288
|
const extra_entries = Object.entries(extras);
|
|
289
289
|
if (extra_entries.length === 0)
|
|
290
|
-
return
|
|
290
|
+
return builtin_audit_log_config;
|
|
291
291
|
const builtin_set = new Set(AUDIT_EVENT_TYPES);
|
|
292
292
|
const extra_keys = [];
|
|
293
|
-
const metadata_schemas = { ...
|
|
293
|
+
const metadata_schemas = { ...audit_metadata_schemas };
|
|
294
294
|
for (const [t, schema] of extra_entries) {
|
|
295
295
|
if (builtin_set.has(t)) {
|
|
296
296
|
throw new Error(`extra_events key "${t}" collides with a builtin event type — pick a distinct string (e.g. "app_${t}")`);
|
package/dist/auth/auth_ddl.d.ts
CHANGED
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
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)";
|
|
13
13
|
export declare const ACTOR_SCHEMA = "\nCREATE TABLE IF NOT EXISTS actor (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ,\n updated_by UUID REFERENCES actor(id) ON DELETE SET NULL\n)";
|
|
14
14
|
export declare const ACTOR_INDEX = "\nCREATE INDEX IF NOT EXISTS idx_actor_account ON actor(account_id)";
|
|
15
|
+
/**
|
|
16
|
+
* Functional index on `LOWER(actor.name)` supporting case-insensitive
|
|
17
|
+
* prefix search by `actor_search` (`LOWER(name) LIKE LOWER(query) || '%'`).
|
|
18
|
+
* `text_pattern_ops` keeps the LIKE-prefix pattern index-eligible — without
|
|
19
|
+
* it the planner falls back to a sequential scan once the table grows.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ACTOR_NAME_LOWER_INDEX = "\nCREATE INDEX IF NOT EXISTS idx_actor_name_lower ON actor (LOWER(name) text_pattern_ops)";
|
|
15
22
|
export declare const ROLE_GRANT_SCHEMA = "\nCREATE TABLE IF NOT EXISTS role_grant (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n actor_id UUID NOT NULL REFERENCES actor(id) ON DELETE CASCADE,\n role TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n expires_at TIMESTAMPTZ,\n revoked_at TIMESTAMPTZ,\n revoked_by UUID REFERENCES actor(id) ON DELETE SET NULL,\n granted_by UUID REFERENCES actor(id) ON DELETE SET NULL\n)";
|
|
16
23
|
export declare const ROLE_GRANT_INDEXES: string[];
|
|
17
24
|
export declare const AUTH_SESSION_SCHEMA = "\nCREATE TABLE IF NOT EXISTS auth_session (\n id TEXT PRIMARY KEY,\n account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n expires_at TIMESTAMPTZ NOT NULL,\n last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n)";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/auth_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,cAAc,8WAWzB,CAAC;AAEH,eAAO,MAAM,YAAY,mUAQvB,CAAC;AAEH,eAAO,MAAM,WAAW,wEAC0C,CAAC;AAEnE,eAAO,MAAM,iBAAiB,2ZAU5B,CAAC;AAEH,eAAO,MAAM,kBAAkB,UAI9B,CAAC;AAEF,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"}
|
|
1
|
+
{"version":3,"file":"auth_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/auth_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,cAAc,8WAWzB,CAAC;AAEH,eAAO,MAAM,YAAY,mUAQvB,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;AAEF,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
|
@@ -32,6 +32,14 @@ CREATE TABLE IF NOT EXISTS actor (
|
|
|
32
32
|
)`;
|
|
33
33
|
export const ACTOR_INDEX = `
|
|
34
34
|
CREATE INDEX IF NOT EXISTS idx_actor_account ON actor(account_id)`;
|
|
35
|
+
/**
|
|
36
|
+
* Functional index on `LOWER(actor.name)` supporting case-insensitive
|
|
37
|
+
* prefix search by `actor_search` (`LOWER(name) LIKE LOWER(query) || '%'`).
|
|
38
|
+
* `text_pattern_ops` keeps the LIKE-prefix pattern index-eligible — without
|
|
39
|
+
* it the planner falls back to a sequential scan once the table grows.
|
|
40
|
+
*/
|
|
41
|
+
export const ACTOR_NAME_LOWER_INDEX = `
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_actor_name_lower ON actor (LOWER(name) text_pattern_ops)`;
|
|
35
43
|
export const ROLE_GRANT_SCHEMA = `
|
|
36
44
|
CREATE TABLE IF NOT EXISTS role_grant (
|
|
37
45
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
@@ -67,7 +67,7 @@ export interface CredentialTypeMeta {
|
|
|
67
67
|
* here. Read once at startup by `create_credential_type_schema`;
|
|
68
68
|
* runtime mutation has no effect on already-built schemas.
|
|
69
69
|
*/
|
|
70
|
-
export declare const
|
|
70
|
+
export declare const builtin_credential_type_meta: ReadonlyMap<string, CredentialTypeMeta>;
|
|
71
71
|
/** The result of `create_credential_type_schema` — a Zod schema and metadata map. */
|
|
72
72
|
export interface CredentialTypeSchemaResult {
|
|
73
73
|
/**
|
|
@@ -60,7 +60,7 @@ export const BuiltinCredentialType = z.enum(BUILTIN_CREDENTIAL_TYPES);
|
|
|
60
60
|
* here. Read once at startup by `create_credential_type_schema`;
|
|
61
61
|
* runtime mutation has no effect on already-built schemas.
|
|
62
62
|
*/
|
|
63
|
-
export const
|
|
63
|
+
export const builtin_credential_type_meta = new Map([
|
|
64
64
|
[
|
|
65
65
|
CREDENTIAL_TYPE_SESSION,
|
|
66
66
|
{ description: 'Cookie-based session credential, signed and validated server-side.' },
|
|
@@ -109,7 +109,7 @@ export const create_credential_type_schema = (consumer_types = {}) => {
|
|
|
109
109
|
if (!parsed.success) {
|
|
110
110
|
throw new Error(`Invalid credential-type name "${name}": ${parsed.error.issues[0].message}`);
|
|
111
111
|
}
|
|
112
|
-
if (
|
|
112
|
+
if (builtin_credential_type_meta.has(name)) {
|
|
113
113
|
throw new Error(`Consumer credential-type "${name}" collides with builtin credential-type`);
|
|
114
114
|
}
|
|
115
115
|
if (seen.has(name)) {
|
|
@@ -119,7 +119,7 @@ export const create_credential_type_schema = (consumer_types = {}) => {
|
|
|
119
119
|
}
|
|
120
120
|
const all_names = [...BUILTIN_CREDENTIAL_TYPES, ...consumer_names];
|
|
121
121
|
const CredentialType = z.enum(all_names);
|
|
122
|
-
const credential_types = new Map(
|
|
122
|
+
const credential_types = new Map(builtin_credential_type_meta);
|
|
123
123
|
for (const name of consumer_names) {
|
|
124
124
|
credential_types.set(name, consumer_types[name]);
|
|
125
125
|
}
|
|
@@ -70,7 +70,7 @@ export interface GrantPathMeta {
|
|
|
70
70
|
* here. Read once at startup by `create_grant_path_schema`; runtime
|
|
71
71
|
* mutation has no effect on already-built schemas.
|
|
72
72
|
*/
|
|
73
|
-
export declare const
|
|
73
|
+
export declare const builtin_grant_path_meta: ReadonlyMap<string, GrantPathMeta>;
|
|
74
74
|
/** The result of `create_grant_path_schema` — a Zod schema and metadata map. */
|
|
75
75
|
export interface GrantPathSchemaResult {
|
|
76
76
|
/**
|
|
@@ -63,7 +63,7 @@ export const BuiltinGrantPath = z.enum(BUILTIN_GRANT_PATHS);
|
|
|
63
63
|
* here. Read once at startup by `create_grant_path_schema`; runtime
|
|
64
64
|
* mutation has no effect on already-built schemas.
|
|
65
65
|
*/
|
|
66
|
-
export const
|
|
66
|
+
export const builtin_grant_path_meta = new Map([
|
|
67
67
|
[
|
|
68
68
|
GRANT_PATH_ADMIN,
|
|
69
69
|
{
|
|
@@ -119,7 +119,7 @@ export const create_grant_path_schema = (consumer_paths = {}) => {
|
|
|
119
119
|
if (!parsed.success) {
|
|
120
120
|
throw new Error(`Invalid grant-path name "${name}": ${parsed.error.issues[0].message}`);
|
|
121
121
|
}
|
|
122
|
-
if (
|
|
122
|
+
if (builtin_grant_path_meta.has(name)) {
|
|
123
123
|
throw new Error(`Consumer grant-path "${name}" collides with builtin grant-path`);
|
|
124
124
|
}
|
|
125
125
|
if (seen.has(name)) {
|
|
@@ -129,7 +129,7 @@ export const create_grant_path_schema = (consumer_paths = {}) => {
|
|
|
129
129
|
}
|
|
130
130
|
const all_names = [...BUILTIN_GRANT_PATHS, ...consumer_names];
|
|
131
131
|
const GrantPath = z.enum(all_names);
|
|
132
|
-
const grant_paths = new Map(
|
|
132
|
+
const grant_paths = new Map(builtin_grant_path_meta);
|
|
133
133
|
for (const name of consumer_names) {
|
|
134
134
|
grant_paths.set(name, consumer_paths[name]);
|
|
135
135
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* To add a migration in the pre-stable phase, prefer extending an existing
|
|
19
19
|
* entry's body (consumers will re-bootstrap on upgrade). If you do append
|
|
20
|
-
* a new entry to `
|
|
20
|
+
* a new entry to `auth_migrations`, the runner will apply it on existing
|
|
21
21
|
* tracker rows — the same shape that will become mandatory once the
|
|
22
22
|
* schema stabilizes:
|
|
23
23
|
*
|
|
@@ -46,7 +46,7 @@ export declare const AUTH_MIGRATION_NAMESPACE = "fuz_auth";
|
|
|
46
46
|
* as `ReadonlyArray<string>` (not a literal tuple) so `.includes()` accepts
|
|
47
47
|
* any consumer-supplied namespace string without a cast.
|
|
48
48
|
*/
|
|
49
|
-
export declare const
|
|
49
|
+
export declare const reserved_migration_namespaces: ReadonlyArray<string>;
|
|
50
50
|
/**
|
|
51
51
|
* Auth schema migrations in order.
|
|
52
52
|
*
|
|
@@ -67,7 +67,7 @@ export declare const RESERVED_MIGRATION_NAMESPACES: ReadonlyArray<string>;
|
|
|
67
67
|
* (registry-membership validation against `create_scope_kind_schema`);
|
|
68
68
|
* v2 may add INSERT-time `(role, scope_kind)` enforcement.
|
|
69
69
|
*/
|
|
70
|
-
export declare const
|
|
70
|
+
export declare const auth_migrations: Array<Migration>;
|
|
71
71
|
/** Pre-composed migration namespace for auth tables. */
|
|
72
|
-
export declare const
|
|
72
|
+
export declare const auth_migration_ns: MigrationNamespace;
|
|
73
73
|
//# sourceMappingURL=migrations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AA+BH,OAAO,KAAK,EAAC,SAAS,EAAE,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AAEpE,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,CAsF5C,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,kBAG/B,CAAC"}
|