@fuzdev/fuz_app 0.55.0 → 0.56.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 +211 -155
- package/dist/actions/action_bridge.d.ts +8 -5
- package/dist/actions/action_bridge.d.ts.map +1 -1
- package/dist/actions/action_bridge.js +1 -11
- package/dist/actions/action_codegen.d.ts +19 -0
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +20 -14
- package/dist/actions/action_registry.d.ts.map +1 -1
- package/dist/actions/action_registry.js +5 -2
- package/dist/actions/action_rpc.d.ts +110 -44
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +92 -287
- package/dist/actions/action_spec.d.ts +55 -16
- package/dist/actions/action_spec.d.ts.map +1 -1
- package/dist/actions/action_spec.js +16 -11
- package/dist/actions/action_types.d.ts +28 -60
- package/dist/actions/action_types.d.ts.map +1 -1
- package/dist/actions/action_types.js +13 -5
- package/dist/actions/broadcast_api.d.ts +2 -2
- package/dist/actions/broadcast_api.js +2 -2
- package/dist/actions/compile_action_registry.d.ts +50 -0
- package/dist/actions/compile_action_registry.d.ts.map +1 -0
- package/dist/actions/compile_action_registry.js +69 -0
- package/dist/actions/heartbeat.d.ts +8 -4
- package/dist/actions/heartbeat.d.ts.map +1 -1
- package/dist/actions/heartbeat.js +5 -4
- package/dist/actions/perform_action.d.ts +145 -0
- package/dist/actions/perform_action.d.ts.map +1 -0
- package/dist/actions/perform_action.js +258 -0
- package/dist/actions/register_action_ws.d.ts +44 -38
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +101 -159
- package/dist/actions/register_ws_endpoint.d.ts +2 -10
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +32 -10
- package/dist/actions/transports_ws_auth_guard.d.ts +1 -1
- package/dist/actions/transports_ws_auth_guard.js +1 -1
- package/dist/actions/transports_ws_backend.d.ts +1 -1
- package/dist/actions/transports_ws_backend.js +1 -1
- package/dist/auth/CLAUDE.md +673 -442
- package/dist/auth/account_action_specs.d.ts +28 -7
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +7 -7
- package/dist/auth/account_actions.d.ts +8 -14
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +26 -32
- package/dist/auth/account_queries.d.ts +46 -13
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +73 -33
- package/dist/auth/account_routes.d.ts +4 -3
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +58 -33
- package/dist/auth/account_schema.d.ts +46 -54
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +21 -48
- package/dist/auth/admin_action_specs.d.ts +55 -21
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +42 -26
- package/dist/auth/admin_actions.d.ts +14 -21
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +47 -44
- package/dist/auth/audit_emitter.d.ts +160 -0
- package/dist/auth/audit_emitter.d.ts.map +1 -0
- package/dist/auth/audit_emitter.js +83 -0
- package/dist/auth/audit_log_queries.d.ts +17 -87
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +17 -96
- package/dist/auth/audit_log_routes.d.ts +1 -1
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +7 -3
- package/dist/auth/audit_log_schema.d.ts +48 -42
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +56 -43
- package/dist/auth/auth_guard_resolver.d.ts +44 -0
- package/dist/auth/auth_guard_resolver.d.ts.map +1 -0
- package/dist/auth/auth_guard_resolver.js +56 -0
- package/dist/auth/bootstrap_account.d.ts +7 -7
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +7 -7
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +11 -10
- package/dist/auth/cleanup.d.ts +20 -26
- package/dist/auth/cleanup.d.ts.map +1 -1
- package/dist/auth/cleanup.js +33 -47
- package/dist/auth/credential_type_schema.d.ts +115 -0
- package/dist/auth/credential_type_schema.d.ts.map +1 -0
- package/dist/auth/credential_type_schema.js +127 -0
- package/dist/auth/daemon_token_middleware.d.ts +1 -1
- package/dist/auth/daemon_token_middleware.js +3 -3
- package/dist/auth/ddl.d.ts +2 -2
- package/dist/auth/ddl.d.ts.map +1 -1
- package/dist/auth/ddl.js +6 -6
- package/dist/auth/deps.d.ts +7 -32
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/grant_path_schema.d.ts +117 -0
- package/dist/auth/grant_path_schema.d.ts.map +1 -0
- package/dist/auth/grant_path_schema.js +137 -0
- package/dist/auth/invite_queries.d.ts +12 -1
- package/dist/auth/invite_queries.d.ts.map +1 -1
- package/dist/auth/invite_queries.js +12 -1
- package/dist/auth/invite_schema.d.ts +1 -1
- package/dist/auth/invite_schema.d.ts.map +1 -1
- package/dist/auth/invite_schema.js +1 -1
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +5 -2
- package/dist/auth/migrations.d.ts +22 -7
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +64 -25
- package/dist/auth/request_context.d.ts +157 -170
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +224 -268
- package/dist/auth/{permit_offer_action_specs.d.ts → role_grant_offer_action_specs.d.ts} +130 -100
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -0
- package/dist/auth/role_grant_offer_action_specs.js +262 -0
- package/dist/auth/role_grant_offer_actions.d.ts +104 -0
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -0
- package/dist/auth/{permit_offer_actions.js → role_grant_offer_actions.js} +153 -140
- package/dist/auth/{permit_offer_notifications.d.ts → role_grant_offer_notifications.d.ts} +80 -70
- package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -0
- package/dist/auth/role_grant_offer_notifications.js +182 -0
- package/dist/auth/{permit_offer_queries.d.ts → role_grant_offer_queries.d.ts} +64 -64
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -0
- package/dist/auth/{permit_offer_queries.js → role_grant_offer_queries.js} +136 -123
- package/dist/auth/role_grant_offer_schema.d.ts +150 -0
- package/dist/auth/role_grant_offer_schema.d.ts.map +1 -0
- package/dist/auth/{permit_offer_schema.js → role_grant_offer_schema.js} +55 -36
- package/dist/auth/role_grant_queries.d.ts +231 -0
- package/dist/auth/role_grant_queries.d.ts.map +1 -0
- package/dist/auth/role_grant_queries.js +320 -0
- package/dist/auth/role_schema.d.ts +150 -40
- package/dist/auth/role_schema.d.ts.map +1 -1
- package/dist/auth/role_schema.js +144 -45
- package/dist/auth/scope_kind_schema.d.ts +96 -0
- package/dist/auth/scope_kind_schema.d.ts.map +1 -0
- package/dist/auth/scope_kind_schema.js +94 -0
- package/dist/auth/self_service_role_action_specs.d.ts +4 -1
- package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
- package/dist/auth/self_service_role_action_specs.js +2 -2
- package/dist/auth/self_service_role_actions.d.ts +35 -29
- package/dist/auth/self_service_role_actions.d.ts.map +1 -1
- package/dist/auth/self_service_role_actions.js +58 -48
- package/dist/auth/session_cookie.d.ts +43 -6
- package/dist/auth/session_cookie.d.ts.map +1 -1
- package/dist/auth/session_cookie.js +31 -5
- package/dist/auth/session_middleware.d.ts +37 -3
- package/dist/auth/session_middleware.d.ts.map +1 -1
- package/dist/auth/session_middleware.js +33 -7
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +48 -19
- package/dist/auth/standard_action_specs.d.ts +2 -2
- package/dist/auth/standard_action_specs.js +4 -4
- package/dist/auth/standard_rpc_actions.d.ts +23 -19
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +12 -12
- package/dist/db/migrate.d.ts +1 -1
- package/dist/db/migrate.js +1 -1
- package/dist/dev/setup.d.ts +2 -2
- package/dist/dev/setup.d.ts.map +1 -1
- package/dist/dev/setup.js +4 -4
- package/dist/env/load.d.ts +1 -1
- package/dist/env/load.js +1 -1
- package/dist/hono_context.d.ts +27 -45
- package/dist/hono_context.d.ts.map +1 -1
- package/dist/hono_context.js +14 -28
- package/dist/http/CLAUDE.md +235 -121
- package/dist/http/auth_shape.d.ts +191 -0
- package/dist/http/auth_shape.d.ts.map +1 -0
- package/dist/http/auth_shape.js +237 -0
- package/dist/http/common_routes.js +3 -3
- package/dist/http/db_routes.d.ts +4 -0
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +44 -7
- package/dist/http/error_schemas.d.ts +56 -34
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +63 -28
- package/dist/http/pending_effects.d.ts +71 -18
- package/dist/http/pending_effects.d.ts.map +1 -1
- package/dist/http/pending_effects.js +87 -18
- package/dist/http/proxy.d.ts +52 -5
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +92 -14
- package/dist/http/route_spec.d.ts +89 -75
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +54 -72
- package/dist/http/schema_helpers.d.ts +3 -14
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +2 -14
- package/dist/http/surface.d.ts +2 -10
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +3 -4
- package/dist/http/surface_query.d.ts +39 -35
- package/dist/http/surface_query.d.ts.map +1 -1
- package/dist/http/surface_query.js +79 -36
- package/dist/primitive_schemas.d.ts +39 -0
- package/dist/primitive_schemas.d.ts.map +1 -0
- package/dist/primitive_schemas.js +40 -0
- package/dist/realtime/sse_auth_guard.d.ts +5 -5
- package/dist/realtime/sse_auth_guard.js +9 -9
- package/dist/runtime/mock.d.ts +1 -1
- package/dist/runtime/mock.js +1 -1
- package/dist/server/app_backend.d.ts +14 -11
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +12 -8
- package/dist/server/app_server.d.ts +7 -7
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +35 -40
- package/dist/server/validate_nginx.d.ts +1 -1
- package/dist/server/validate_nginx.js +1 -1
- package/dist/testing/CLAUDE.md +50 -38
- package/dist/testing/admin_integration.d.ts +5 -6
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +87 -85
- package/dist/testing/app_server.d.ts +11 -14
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +16 -15
- package/dist/testing/assertions.d.ts.map +1 -1
- package/dist/testing/assertions.js +2 -1
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +15 -9
- package/dist/testing/audit_completeness.d.ts +2 -2
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +36 -36
- package/dist/testing/auth_apps.d.ts +5 -4
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +22 -19
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +5 -5
- package/dist/testing/db.d.ts +1 -1
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +4 -4
- package/dist/testing/db_entities.d.ts +22 -0
- package/dist/testing/db_entities.d.ts.map +1 -0
- package/dist/testing/db_entities.js +28 -0
- package/dist/testing/entities.d.ts +8 -7
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +21 -18
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +13 -14
- package/dist/testing/integration_helpers.d.ts +4 -4
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +20 -18
- package/dist/testing/middleware.d.ts +4 -4
- package/dist/testing/middleware.d.ts.map +1 -1
- package/dist/testing/middleware.js +12 -11
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +40 -24
- package/dist/testing/rpc_round_trip.d.ts +1 -1
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +14 -13
- package/dist/testing/sse_round_trip.d.ts +3 -4
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +7 -11
- package/dist/testing/standard.d.ts +1 -1
- package/dist/testing/stubs.d.ts +25 -0
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +43 -2
- package/dist/testing/surface_invariants.d.ts +2 -2
- package/dist/testing/ws_round_trip.d.ts +12 -13
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +19 -11
- package/dist/ui/AdminAccounts.svelte +23 -20
- package/dist/ui/AdminOverview.svelte +15 -13
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/{AdminPermitHistory.svelte → AdminRoleGrantHistory.svelte} +12 -12
- package/dist/ui/AdminRoleGrantHistory.svelte.d.ts +4 -0
- package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -0
- package/dist/ui/BootstrapForm.svelte +1 -1
- package/dist/ui/CLAUDE.md +60 -60
- package/dist/ui/{PermitOfferForm.svelte → RoleGrantOfferForm.svelte} +27 -26
- package/dist/ui/{PermitOfferForm.svelte.d.ts → RoleGrantOfferForm.svelte.d.ts} +7 -7
- package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -0
- package/dist/ui/{PermitOfferHistory.svelte → RoleGrantOfferHistory.svelte} +12 -12
- package/dist/ui/{PermitOfferHistory.svelte.d.ts → RoleGrantOfferHistory.svelte.d.ts} +4 -4
- package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -0
- package/dist/ui/{PermitOfferInbox.svelte → RoleGrantOfferInbox.svelte} +14 -14
- package/dist/ui/{PermitOfferInbox.svelte.d.ts → RoleGrantOfferInbox.svelte.d.ts} +4 -4
- package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -0
- package/dist/ui/SignupForm.svelte +1 -1
- package/dist/ui/SurfaceExplorer.svelte +35 -15
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.d.ts +2 -3
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +2 -3
- package/dist/ui/admin_accounts_state.svelte.d.ts +18 -18
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +16 -16
- package/dist/ui/admin_rpc_adapters.d.ts +20 -20
- package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
- package/dist/ui/admin_rpc_adapters.js +17 -17
- package/dist/ui/admin_sessions_state.svelte.d.ts +2 -2
- package/dist/ui/admin_sessions_state.svelte.js +2 -2
- package/dist/ui/audit_log_state.svelte.d.ts +7 -7
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +6 -6
- package/dist/ui/auth_state.svelte.d.ts +3 -3
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +6 -6
- package/dist/ui/format_scope.d.ts +2 -2
- package/dist/ui/format_scope.js +2 -2
- package/dist/ui/{permit_offers_state.svelte.d.ts → role_grant_offers_state.svelte.d.ts} +30 -30
- package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -0
- package/dist/ui/{permit_offers_state.svelte.js → role_grant_offers_state.svelte.js} +18 -18
- package/dist/ui/ui_format.js +2 -2
- package/package.json +3 -3
- package/dist/auth/permit_offer_action_specs.d.ts.map +0 -1
- package/dist/auth/permit_offer_action_specs.js +0 -258
- package/dist/auth/permit_offer_actions.d.ts +0 -110
- package/dist/auth/permit_offer_actions.d.ts.map +0 -1
- package/dist/auth/permit_offer_notifications.d.ts.map +0 -1
- package/dist/auth/permit_offer_notifications.js +0 -182
- package/dist/auth/permit_offer_queries.d.ts.map +0 -1
- package/dist/auth/permit_offer_schema.d.ts +0 -125
- package/dist/auth/permit_offer_schema.d.ts.map +0 -1
- package/dist/auth/permit_queries.d.ts +0 -222
- package/dist/auth/permit_queries.d.ts.map +0 -1
- package/dist/auth/permit_queries.js +0 -305
- package/dist/auth/require_keeper.d.ts +0 -20
- package/dist/auth/require_keeper.d.ts.map +0 -1
- package/dist/auth/require_keeper.js +0 -35
- package/dist/auth/route_guards.d.ts +0 -27
- package/dist/auth/route_guards.d.ts.map +0 -1
- package/dist/auth/route_guards.js +0 -38
- package/dist/auth/session_lifecycle.d.ts +0 -37
- package/dist/auth/session_lifecycle.d.ts.map +0 -1
- package/dist/auth/session_lifecycle.js +0 -29
- package/dist/ui/AdminPermitHistory.svelte.d.ts +0 -4
- package/dist/ui/AdminPermitHistory.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferForm.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferHistory.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferInbox.svelte.d.ts.map +0 -1
- package/dist/ui/permit_offers_state.svelte.d.ts.map +0 -1
|
@@ -22,13 +22,12 @@
|
|
|
22
22
|
* @module
|
|
23
23
|
*/
|
|
24
24
|
import { z } from 'zod';
|
|
25
|
-
import { clear_session_cookie } from './session_middleware.js';
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
25
|
+
import { clear_session_cookie, create_session_and_set_cookie } from './session_middleware.js';
|
|
26
|
+
import { ActorSummaryJson, RoleGrantSummaryJson, SessionAccountJson, to_session_account, } from './account_schema.js';
|
|
27
|
+
import { UsernameProvided } from '../primitive_schemas.js';
|
|
28
28
|
import { hash_session_token, query_session_revoke_all_for_account, query_session_revoke_by_hash_unscoped, } from './session_queries.js';
|
|
29
29
|
import { query_account_by_username_or_email, query_update_account_password, } from './account_queries.js';
|
|
30
30
|
import { query_revoke_all_api_tokens_for_account } from './api_token_queries.js';
|
|
31
|
-
import { audit_log_fire_and_forget } from './audit_log_queries.js';
|
|
32
31
|
import { build_account_context, build_request_context, get_request_context, require_request_context, resolve_acting_actor, } from './request_context.js';
|
|
33
32
|
import { ACCOUNT_ID_KEY } from '../hono_context.js';
|
|
34
33
|
import { get_route_input } from '../http/route_spec.js';
|
|
@@ -42,15 +41,15 @@ export const AccountStatusInput = z.null();
|
|
|
42
41
|
* Output for `GET /api/account/status` on the authenticated path.
|
|
43
42
|
*
|
|
44
43
|
* `account` is always populated for authenticated callers. `actor` and
|
|
45
|
-
* `
|
|
44
|
+
* `role_grants` are populated when the caller's account has a unique actor or
|
|
46
45
|
* the request supplies `?acting=<actor_id>`; on multi-actor accounts
|
|
47
|
-
* without an `acting` query, `actor` is `null` and `
|
|
46
|
+
* without an `acting` query, `actor` is `null` and `role_grants` is empty so
|
|
48
47
|
* the frontend can show a persona picker without a separate roundtrip.
|
|
49
48
|
*/
|
|
50
49
|
export const AccountStatusOutput = z.strictObject({
|
|
51
50
|
account: SessionAccountJson,
|
|
52
51
|
actor: ActorSummaryJson.nullable(),
|
|
53
|
-
|
|
52
|
+
role_grants: z.array(RoleGrantSummaryJson),
|
|
54
53
|
});
|
|
55
54
|
/** Error body for `GET /api/account/status` on the unauthenticated path. */
|
|
56
55
|
export const AccountStatusUnauthenticatedError = z.looseObject({
|
|
@@ -73,7 +72,7 @@ export const AccountStatusUnauthenticatedError = z.looseObject({
|
|
|
73
72
|
export const create_account_status_route_spec = (options) => ({
|
|
74
73
|
method: 'GET',
|
|
75
74
|
path: options?.path ?? '/api/account/status',
|
|
76
|
-
auth: {
|
|
75
|
+
auth: { account: 'none', actor: 'none' },
|
|
77
76
|
description: 'Current account info (unauthenticated: 401 with bootstrap status)',
|
|
78
77
|
input: AccountStatusInput,
|
|
79
78
|
output: AccountStatusOutput,
|
|
@@ -94,9 +93,10 @@ export const create_account_status_route_spec = (options) => ({
|
|
|
94
93
|
// it directly to avoid redundant lookups.
|
|
95
94
|
const existing = get_request_context(c);
|
|
96
95
|
if (existing && existing.account.id === account_id) {
|
|
97
|
-
const
|
|
96
|
+
const role_grants = existing.role_grants.map((p) => ({
|
|
98
97
|
id: p.id,
|
|
99
98
|
role: p.role,
|
|
99
|
+
scope_kind: p.scope_kind,
|
|
100
100
|
scope_id: p.scope_id,
|
|
101
101
|
created_at: p.created_at,
|
|
102
102
|
expires_at: p.expires_at,
|
|
@@ -105,10 +105,10 @@ export const create_account_status_route_spec = (options) => ({
|
|
|
105
105
|
return c.json({
|
|
106
106
|
account: to_session_account(existing.account),
|
|
107
107
|
actor: existing.actor ? { id: existing.actor.id, name: existing.actor.name } : null,
|
|
108
|
-
|
|
108
|
+
role_grants,
|
|
109
109
|
});
|
|
110
110
|
}
|
|
111
|
-
// Resolve actor +
|
|
111
|
+
// Resolve actor + role_grants when the caller is unambiguous (single-actor
|
|
112
112
|
// account, or supplied `?acting=<uuid>`). On multi-actor accounts
|
|
113
113
|
// without `acting`, fall back to account-only so the frontend can
|
|
114
114
|
// surface a persona picker.
|
|
@@ -117,9 +117,10 @@ export const create_account_status_route_spec = (options) => ({
|
|
|
117
117
|
if (acting_result.ok) {
|
|
118
118
|
const ctx = await build_request_context(route, account_id, acting_result.actor_id);
|
|
119
119
|
if (ctx) {
|
|
120
|
-
const
|
|
120
|
+
const role_grants = ctx.role_grants.map((p) => ({
|
|
121
121
|
id: p.id,
|
|
122
122
|
role: p.role,
|
|
123
|
+
scope_kind: p.scope_kind,
|
|
123
124
|
scope_id: p.scope_id,
|
|
124
125
|
created_at: p.created_at,
|
|
125
126
|
expires_at: p.expires_at,
|
|
@@ -128,7 +129,7 @@ export const create_account_status_route_spec = (options) => ({
|
|
|
128
129
|
return c.json({
|
|
129
130
|
account: to_session_account(ctx.account),
|
|
130
131
|
actor: { id: ctx.actor.id, name: ctx.actor.name },
|
|
131
|
-
|
|
132
|
+
role_grants,
|
|
132
133
|
});
|
|
133
134
|
}
|
|
134
135
|
}
|
|
@@ -142,7 +143,7 @@ export const create_account_status_route_spec = (options) => ({
|
|
|
142
143
|
return c.json({
|
|
143
144
|
account: to_session_account(account_ctx.account),
|
|
144
145
|
actor: null,
|
|
145
|
-
|
|
146
|
+
role_grants: [],
|
|
146
147
|
});
|
|
147
148
|
},
|
|
148
149
|
});
|
|
@@ -221,7 +222,7 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
221
222
|
{
|
|
222
223
|
method: 'GET',
|
|
223
224
|
path: '/verify',
|
|
224
|
-
auth: {
|
|
225
|
+
auth: { account: 'required', actor: 'none' },
|
|
225
226
|
description: 'Session-validity probe for nginx auth_request (empty body, 200 or 401)',
|
|
226
227
|
input: z.null(),
|
|
227
228
|
output: z.null(),
|
|
@@ -233,7 +234,7 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
233
234
|
{
|
|
234
235
|
method: 'POST',
|
|
235
236
|
path: '/login',
|
|
236
|
-
auth: {
|
|
237
|
+
auth: { account: 'none', actor: 'none' },
|
|
237
238
|
description: 'Exchange credentials for session',
|
|
238
239
|
input: LoginInput,
|
|
239
240
|
output: LoginOutput,
|
|
@@ -279,12 +280,12 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
279
280
|
ip_rate_limiter.record(ip);
|
|
280
281
|
if (login_account_rate_limiter)
|
|
281
282
|
login_account_rate_limiter.record(account_rate_key);
|
|
282
|
-
|
|
283
|
+
deps.audit.emit(route, {
|
|
283
284
|
event_type: 'login',
|
|
284
285
|
outcome: 'failure',
|
|
285
286
|
ip: get_client_ip(c),
|
|
286
287
|
metadata: { username },
|
|
287
|
-
}
|
|
288
|
+
});
|
|
288
289
|
await delay;
|
|
289
290
|
return c.json({ error: ERROR_INVALID_CREDENTIALS }, 401);
|
|
290
291
|
}
|
|
@@ -294,13 +295,13 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
294
295
|
ip_rate_limiter.record(ip);
|
|
295
296
|
if (login_account_rate_limiter)
|
|
296
297
|
login_account_rate_limiter.record(account_rate_key);
|
|
297
|
-
|
|
298
|
+
deps.audit.emit(route, {
|
|
298
299
|
event_type: 'login',
|
|
299
300
|
outcome: 'failure',
|
|
300
301
|
account_id: account.id,
|
|
301
302
|
ip: get_client_ip(c),
|
|
302
303
|
metadata: { username },
|
|
303
|
-
}
|
|
304
|
+
});
|
|
304
305
|
await delay;
|
|
305
306
|
return c.json({ error: ERROR_INVALID_CREDENTIALS }, 401);
|
|
306
307
|
}
|
|
@@ -317,18 +318,18 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
317
318
|
session_options,
|
|
318
319
|
max_sessions,
|
|
319
320
|
});
|
|
320
|
-
|
|
321
|
+
deps.audit.emit(route, {
|
|
321
322
|
event_type: 'login',
|
|
322
323
|
account_id: account.id,
|
|
323
324
|
ip: get_client_ip(c),
|
|
324
|
-
}
|
|
325
|
+
});
|
|
325
326
|
return c.json({ ok: true });
|
|
326
327
|
},
|
|
327
328
|
},
|
|
328
329
|
{
|
|
329
330
|
method: 'POST',
|
|
330
331
|
path: '/logout',
|
|
331
|
-
auth: {
|
|
332
|
+
auth: { account: 'required', actor: 'none' },
|
|
332
333
|
description: 'Revoke current session and clear cookie',
|
|
333
334
|
input: LogoutInput,
|
|
334
335
|
output: LogoutOutput,
|
|
@@ -343,18 +344,18 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
343
344
|
// Account-grain operation — no `actor_id` (which actor was
|
|
344
345
|
// resolved per-request is incidental to "this account ended
|
|
345
346
|
// its session"). Mirrors `login`.
|
|
346
|
-
|
|
347
|
+
deps.audit.emit(route, {
|
|
347
348
|
event_type: 'logout',
|
|
348
349
|
account_id: ctx.account.id,
|
|
349
350
|
ip: get_client_ip(c),
|
|
350
|
-
}
|
|
351
|
+
});
|
|
351
352
|
return c.json({ ok: true, username: ctx.account.username });
|
|
352
353
|
},
|
|
353
354
|
},
|
|
354
355
|
{
|
|
355
356
|
method: 'POST',
|
|
356
357
|
path: '/password',
|
|
357
|
-
auth: {
|
|
358
|
+
auth: { account: 'required', actor: 'none' },
|
|
358
359
|
description: 'Change password (revokes all sessions and API tokens)',
|
|
359
360
|
input: PasswordChangeInput,
|
|
360
361
|
output: PasswordChangeOutput,
|
|
@@ -388,12 +389,12 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
388
389
|
ip_rate_limiter.record(ip);
|
|
389
390
|
if (login_account_rate_limiter)
|
|
390
391
|
login_account_rate_limiter.record(ctx.account.id);
|
|
391
|
-
|
|
392
|
+
deps.audit.emit(route, {
|
|
392
393
|
event_type: 'password_change',
|
|
393
394
|
outcome: 'failure',
|
|
394
395
|
account_id: ctx.account.id,
|
|
395
396
|
ip: get_client_ip(c),
|
|
396
|
-
}
|
|
397
|
+
});
|
|
397
398
|
return c.json({ error: ERROR_INVALID_CREDENTIALS }, 401);
|
|
398
399
|
}
|
|
399
400
|
// successful verification — reset rate limiters
|
|
@@ -402,9 +403,33 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
402
403
|
if (login_account_rate_limiter)
|
|
403
404
|
login_account_rate_limiter.reset(ctx.account.id);
|
|
404
405
|
const new_hash = await password.hash_password(new_password);
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
|
|
406
|
+
// Conditional UPDATE keyed on the verified hash: closes the
|
|
407
|
+
// verify-write race with a concurrent password change that
|
|
408
|
+
// already committed against the same starting hash. Account-grain
|
|
409
|
+
// operation — `updated_by` stays null (the per-request actor is
|
|
410
|
+
// incidental; password is account-level state).
|
|
411
|
+
const updated = await query_update_account_password(route, ctx.account.id, new_hash, null, ctx.account.password_hash);
|
|
412
|
+
if (!updated) {
|
|
413
|
+
// A concurrent password change committed first — our
|
|
414
|
+
// `current_password` was correct at read-time but the row's
|
|
415
|
+
// `password_hash` no longer matches. Mirrors the wrong-password
|
|
416
|
+
// 401 shape; tag the failure metadata so admins reading the
|
|
417
|
+
// audit log can distinguish "user typoed" from "two clients
|
|
418
|
+
// raced." Sessions/tokens were already revoked by the winner;
|
|
419
|
+
// no cookie clear here either.
|
|
420
|
+
if (ip_rate_limiter && ip)
|
|
421
|
+
ip_rate_limiter.record(ip);
|
|
422
|
+
if (login_account_rate_limiter)
|
|
423
|
+
login_account_rate_limiter.record(ctx.account.id);
|
|
424
|
+
deps.audit.emit(route, {
|
|
425
|
+
event_type: 'password_change',
|
|
426
|
+
outcome: 'failure',
|
|
427
|
+
account_id: ctx.account.id,
|
|
428
|
+
ip: get_client_ip(c),
|
|
429
|
+
metadata: { reason: 'concurrent_change' },
|
|
430
|
+
});
|
|
431
|
+
return c.json({ error: ERROR_INVALID_CREDENTIALS }, 401);
|
|
432
|
+
}
|
|
408
433
|
// revoke all sessions and API tokens (force re-auth everywhere)
|
|
409
434
|
const sessions_revoked = await query_session_revoke_all_for_account(route, ctx.account.id);
|
|
410
435
|
const tokens_revoked = await query_revoke_all_api_tokens_for_account(route, ctx.account.id);
|
|
@@ -413,12 +438,12 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
413
438
|
// account-level state; which per-request actor was resolved
|
|
414
439
|
// has no semantic bearing on "this account changed its
|
|
415
440
|
// password". Mirrors `login`/`logout`.
|
|
416
|
-
|
|
441
|
+
deps.audit.emit(route, {
|
|
417
442
|
event_type: 'password_change',
|
|
418
443
|
account_id: ctx.account.id,
|
|
419
444
|
ip: get_client_ip(c),
|
|
420
445
|
metadata: { sessions_revoked, tokens_revoked },
|
|
421
|
-
}
|
|
446
|
+
});
|
|
422
447
|
return c.json({ ok: true, sessions_revoked, tokens_revoked });
|
|
423
448
|
},
|
|
424
449
|
},
|
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
* Auth entity types and client-safe schemas.
|
|
3
3
|
*
|
|
4
4
|
* Defines the runtime types for the fuz identity system:
|
|
5
|
-
* `Account`, `Actor`, `
|
|
5
|
+
* `Account`, `Actor`, `RoleGrant`, `AuthSession`, and `ApiToken`.
|
|
6
|
+
*
|
|
7
|
+
* Identifier primitives (`Username`, `UsernameProvided`, `Email`) live
|
|
8
|
+
* in `../primitive_schemas.ts` — they're general validator shapes that
|
|
9
|
+
* don't depend on the auth domain. The auth-shape request-contract
|
|
10
|
+
* primitive `ActingActor` lives in `../http/auth_shape.ts` next to
|
|
11
|
+
* `RouteAuth` (the two pair: `auth.actor !== 'none'` ⟺ input declares
|
|
12
|
+
* `acting?: ActingActor`).
|
|
6
13
|
*
|
|
7
14
|
* DDL lives in `auth/ddl.ts`; role system in `auth/role_schema.ts`.
|
|
8
15
|
* See docs/identity.md for design rationale.
|
|
@@ -11,40 +18,7 @@
|
|
|
11
18
|
*/
|
|
12
19
|
import { z } from 'zod';
|
|
13
20
|
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
14
|
-
|
|
15
|
-
export declare const USERNAME_LENGTH_MIN = 3;
|
|
16
|
-
/** Maximum username length (matches GitHub's limit). */
|
|
17
|
-
export declare const USERNAME_LENGTH_MAX = 39;
|
|
18
|
-
/** Maximum length for username input on login/lookup — more permissive than `USERNAME_LENGTH_MAX` for forward-compatibility if the creation limit is raised. */
|
|
19
|
-
export declare const USERNAME_PROVIDED_LENGTH_MAX = 255;
|
|
20
|
-
/** Username for account creation — starts with letter, alphanumeric/dash/underscore middle, ends with alphanumeric. No @ or . allowed. */
|
|
21
|
-
export declare const Username: z.ZodString;
|
|
22
|
-
export type Username = z.infer<typeof Username>;
|
|
23
|
-
/** Username submitted for login or lookup — minimal validation for forward-compatibility if format rules change. */
|
|
24
|
-
export declare const UsernameProvided: z.ZodString;
|
|
25
|
-
export type UsernameProvided = z.infer<typeof UsernameProvided>;
|
|
26
|
-
/** Email validation. */
|
|
27
|
-
export declare const Email: z.ZodEmail;
|
|
28
|
-
export type Email = z.infer<typeof Email>;
|
|
29
|
-
/**
|
|
30
|
-
* `acting` field shared by every action input that needs the caller's
|
|
31
|
-
* acting actor. Declaring `acting: ActingActor` on an action's input
|
|
32
|
-
* is the signal to the RPC dispatcher / route-spec wrapper to resolve
|
|
33
|
-
* an actor against the authenticated account: the authorization phase
|
|
34
|
-
* runs `resolve_acting_actor`, builds the actor-bound `RequestContext`,
|
|
35
|
-
* and loads permits before auth guards fire.
|
|
36
|
-
*
|
|
37
|
-
* Resolution rules: omitted + 1 actor → use it; omitted + multiple
|
|
38
|
-
* actors → `actor_required` with the available list; supplied + on
|
|
39
|
-
* the account → use it; supplied + foreign actor → `actor_not_on_account`.
|
|
40
|
-
*
|
|
41
|
-
* Account-grain routes — input doesn't declare `acting` and auth
|
|
42
|
-
* doesn't require permits (`role` / `keeper`) — skip resolution
|
|
43
|
-
* entirely; their `RequestContext.actor` is `null` and the audit
|
|
44
|
-
* envelope's `actor_id` stays null.
|
|
45
|
-
*/
|
|
46
|
-
export declare const ActingActor: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
47
|
-
export type ActingActor = z.infer<typeof ActingActor>;
|
|
21
|
+
import { Username, Email } from '../primitive_schemas.js';
|
|
48
22
|
/** Account — authentication identity. You log in as an account. */
|
|
49
23
|
export interface Account {
|
|
50
24
|
id: Uuid;
|
|
@@ -65,7 +39,7 @@ export interface SessionAccount {
|
|
|
65
39
|
email_verified: boolean;
|
|
66
40
|
created_at: string;
|
|
67
41
|
}
|
|
68
|
-
/** Actor — the entity that acts. Owns cells, holds
|
|
42
|
+
/** Actor — the entity that acts. Owns cells, holds role_grants, appears in audit trails. */
|
|
69
43
|
export interface Actor {
|
|
70
44
|
id: Uuid;
|
|
71
45
|
account_id: Uuid;
|
|
@@ -76,17 +50,25 @@ export interface Actor {
|
|
|
76
50
|
}
|
|
77
51
|
/**
|
|
78
52
|
* Maximum length of the optional free-form `revoked_reason` attached to a
|
|
79
|
-
* revoked
|
|
53
|
+
* revoked role_grant. Bounds the value at the schema layer so both the admin
|
|
80
54
|
* input (when the route surfaces a reason field) and the revokee-facing
|
|
81
|
-
* `
|
|
55
|
+
* `role_grant_revoke` WS notification validate against the same ceiling.
|
|
82
56
|
*/
|
|
83
|
-
export declare const
|
|
84
|
-
/**
|
|
85
|
-
export interface
|
|
57
|
+
export declare const ROLE_GRANT_REVOKED_REASON_LENGTH_MAX = 500;
|
|
58
|
+
/** Role grant — time-bounded, revocable grant of a role to an actor. */
|
|
59
|
+
export interface RoleGrant {
|
|
86
60
|
id: Uuid;
|
|
87
61
|
actor_id: Uuid;
|
|
88
62
|
role: string;
|
|
89
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Machine-readable kind tag for the polymorphic `scope_id`. Paired-null
|
|
65
|
+
* with `scope_id` per the `role_grant_scope_kind_paired` CHECK: both null
|
|
66
|
+
* (global) or both non-null (scoped). Consumer-declared via
|
|
67
|
+
* `create_scope_kind_schema(...)`; v1 keeps validation registry-membership
|
|
68
|
+
* only, with no INSERT-time `(role, scope_kind)` enforcement.
|
|
69
|
+
*/
|
|
70
|
+
scope_kind: string | null;
|
|
71
|
+
/** Resource scope this grant applies to (e.g. a classroom id). `null` for global role_grants. */
|
|
90
72
|
scope_id: Uuid | null;
|
|
91
73
|
created_at: string;
|
|
92
74
|
expires_at: string | null;
|
|
@@ -95,10 +77,10 @@ export interface Permit {
|
|
|
95
77
|
/** Optional free-form reason attached on revoke (surfaced in the revokee WS notification once it lands). */
|
|
96
78
|
revoked_reason: string | null;
|
|
97
79
|
granted_by: Uuid | null;
|
|
98
|
-
/** Offer that produced this
|
|
80
|
+
/** Offer that produced this role_grant (set by `query_accept_offer`). `null` for direct grants. */
|
|
99
81
|
source_offer_id: Uuid | null;
|
|
100
82
|
}
|
|
101
|
-
export declare const
|
|
83
|
+
export declare const is_role_grant_active: (p: {
|
|
102
84
|
revoked_at?: string | null;
|
|
103
85
|
expires_at: string | null;
|
|
104
86
|
}, now?: Date) => boolean;
|
|
@@ -150,16 +132,17 @@ export declare const ClientApiTokenJson: z.ZodObject<{
|
|
|
150
132
|
created_at: z.ZodString;
|
|
151
133
|
}, z.core.$strict>;
|
|
152
134
|
export type ClientApiTokenJson = z.infer<typeof ClientApiTokenJson>;
|
|
153
|
-
/** Zod schema for the
|
|
154
|
-
export declare const
|
|
135
|
+
/** Zod schema for the role_grant summary returned in admin account listings. */
|
|
136
|
+
export declare const RoleGrantSummaryJson: z.ZodObject<{
|
|
155
137
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
156
138
|
role: z.ZodString;
|
|
139
|
+
scope_kind: z.ZodNullable<z.ZodString>;
|
|
157
140
|
scope_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
158
141
|
created_at: z.ZodString;
|
|
159
142
|
expires_at: z.ZodNullable<z.ZodString>;
|
|
160
143
|
granted_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
161
144
|
}, z.core.$strict>;
|
|
162
|
-
export type
|
|
145
|
+
export type RoleGrantSummaryJson = z.infer<typeof RoleGrantSummaryJson>;
|
|
163
146
|
/** Zod schema for the actor summary returned in admin account listings. */
|
|
164
147
|
export declare const ActorSummaryJson: z.ZodObject<{
|
|
165
148
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
@@ -178,9 +161,9 @@ export declare const AdminAccountJson: z.ZodObject<{
|
|
|
178
161
|
}, z.core.$strict>;
|
|
179
162
|
export type AdminAccountJson = z.infer<typeof AdminAccountJson>;
|
|
180
163
|
/**
|
|
181
|
-
* Zod schema for a pending
|
|
164
|
+
* Zod schema for a pending role_grant offer surfaced in admin account listings.
|
|
182
165
|
*
|
|
183
|
-
* Deliberately narrower than `
|
|
166
|
+
* Deliberately narrower than `RoleGrantOfferJson`: omits `message` and
|
|
184
167
|
* `decline_reason` so cross-admin visibility of the listing does not expose
|
|
185
168
|
* grantor-authored text that the audit log also withholds. Full offer
|
|
186
169
|
* payloads remain available through the offer-specific RPC surface and the
|
|
@@ -193,6 +176,7 @@ export type AdminAccountJson = z.infer<typeof AdminAccountJson>;
|
|
|
193
176
|
export declare const PendingOfferSummaryJson: z.ZodObject<{
|
|
194
177
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
195
178
|
role: z.ZodString;
|
|
179
|
+
scope_kind: z.ZodNullable<z.ZodString>;
|
|
196
180
|
scope_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
197
181
|
from_actor_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
198
182
|
from_username: z.ZodString;
|
|
@@ -200,7 +184,7 @@ export declare const PendingOfferSummaryJson: z.ZodObject<{
|
|
|
200
184
|
expires_at: z.ZodString;
|
|
201
185
|
}, z.core.$strict>;
|
|
202
186
|
export type PendingOfferSummaryJson = z.infer<typeof PendingOfferSummaryJson>;
|
|
203
|
-
/** Zod schema for an admin account listing entry (account + actor +
|
|
187
|
+
/** Zod schema for an admin account listing entry (account + actor + role_grants + pending offers). */
|
|
204
188
|
export declare const AdminAccountEntryJson: z.ZodObject<{
|
|
205
189
|
account: z.ZodObject<{
|
|
206
190
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
@@ -215,9 +199,10 @@ export declare const AdminAccountEntryJson: z.ZodObject<{
|
|
|
215
199
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
216
200
|
name: z.ZodString;
|
|
217
201
|
}, z.core.$strict>>;
|
|
218
|
-
|
|
202
|
+
role_grants: z.ZodArray<z.ZodObject<{
|
|
219
203
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
220
204
|
role: z.ZodString;
|
|
205
|
+
scope_kind: z.ZodNullable<z.ZodString>;
|
|
221
206
|
scope_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
222
207
|
created_at: z.ZodString;
|
|
223
208
|
expires_at: z.ZodNullable<z.ZodString>;
|
|
@@ -226,6 +211,7 @@ export declare const AdminAccountEntryJson: z.ZodObject<{
|
|
|
226
211
|
pending_offers: z.ZodArray<z.ZodObject<{
|
|
227
212
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
228
213
|
role: z.ZodString;
|
|
214
|
+
scope_kind: z.ZodNullable<z.ZodString>;
|
|
229
215
|
scope_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
230
216
|
from_actor_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
231
217
|
from_username: z.ZodString;
|
|
@@ -239,14 +225,20 @@ export interface CreateAccountInput {
|
|
|
239
225
|
password_hash: string;
|
|
240
226
|
email?: Email | null;
|
|
241
227
|
}
|
|
242
|
-
export interface
|
|
228
|
+
export interface CreateRoleGrantInput {
|
|
243
229
|
actor_id: Uuid;
|
|
244
230
|
role: string;
|
|
245
|
-
/**
|
|
231
|
+
/**
|
|
232
|
+
* Machine-readable kind for the `scope_id`. Required iff `scope_id` is
|
|
233
|
+
* set; must be null/omitted when `scope_id` is null. The DB-level
|
|
234
|
+
* `role_grant_scope_kind_paired` CHECK rejects mismatched pairs.
|
|
235
|
+
*/
|
|
236
|
+
scope_kind?: string | null;
|
|
237
|
+
/** Scope the grant applies to. `null` / omitted grants a global role_grant. */
|
|
246
238
|
scope_id?: Uuid | null;
|
|
247
239
|
expires_at?: Date | null;
|
|
248
240
|
granted_by: Uuid | null;
|
|
249
|
-
/** Offer id that produced this
|
|
241
|
+
/** Offer id that produced this role_grant. Set by `query_accept_offer`; leave unset for direct grants. */
|
|
250
242
|
source_offer_id?: Uuid | null;
|
|
251
243
|
}
|
|
252
244
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_schema.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"account_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
* Auth entity types and client-safe schemas.
|
|
3
3
|
*
|
|
4
4
|
* Defines the runtime types for the fuz identity system:
|
|
5
|
-
* `Account`, `Actor`, `
|
|
5
|
+
* `Account`, `Actor`, `RoleGrant`, `AuthSession`, and `ApiToken`.
|
|
6
|
+
*
|
|
7
|
+
* Identifier primitives (`Username`, `UsernameProvided`, `Email`) live
|
|
8
|
+
* in `../primitive_schemas.ts` — they're general validator shapes that
|
|
9
|
+
* don't depend on the auth domain. The auth-shape request-contract
|
|
10
|
+
* primitive `ActingActor` lives in `../http/auth_shape.ts` next to
|
|
11
|
+
* `RouteAuth` (the two pair: `auth.actor !== 'none'` ⟺ input declares
|
|
12
|
+
* `acting?: ActingActor`).
|
|
6
13
|
*
|
|
7
14
|
* DDL lives in `auth/ddl.ts`; role system in `auth/role_schema.ts`.
|
|
8
15
|
* See docs/identity.md for design rationale.
|
|
@@ -11,51 +18,15 @@
|
|
|
11
18
|
*/
|
|
12
19
|
import { z } from 'zod';
|
|
13
20
|
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
14
|
-
|
|
15
|
-
/** Minimum username length (must have start + middle + end characters). */
|
|
16
|
-
export const USERNAME_LENGTH_MIN = 3;
|
|
17
|
-
/** Maximum username length (matches GitHub's limit). */
|
|
18
|
-
export const USERNAME_LENGTH_MAX = 39;
|
|
19
|
-
/** Maximum length for username input on login/lookup — more permissive than `USERNAME_LENGTH_MAX` for forward-compatibility if the creation limit is raised. */
|
|
20
|
-
export const USERNAME_PROVIDED_LENGTH_MAX = 255;
|
|
21
|
-
/** Username for account creation — starts with letter, alphanumeric/dash/underscore middle, ends with alphanumeric. No @ or . allowed. */
|
|
22
|
-
export const Username = z
|
|
23
|
-
.string()
|
|
24
|
-
.min(USERNAME_LENGTH_MIN)
|
|
25
|
-
.max(USERNAME_LENGTH_MAX)
|
|
26
|
-
.regex(/^[a-zA-Z][0-9a-zA-Z_-]*[0-9a-zA-Z]$/);
|
|
27
|
-
/** Username submitted for login or lookup — minimal validation for forward-compatibility if format rules change. */
|
|
28
|
-
export const UsernameProvided = z.string().min(1).max(USERNAME_PROVIDED_LENGTH_MAX);
|
|
29
|
-
/** Email validation. */
|
|
30
|
-
export const Email = z.email();
|
|
31
|
-
/**
|
|
32
|
-
* `acting` field shared by every action input that needs the caller's
|
|
33
|
-
* acting actor. Declaring `acting: ActingActor` on an action's input
|
|
34
|
-
* is the signal to the RPC dispatcher / route-spec wrapper to resolve
|
|
35
|
-
* an actor against the authenticated account: the authorization phase
|
|
36
|
-
* runs `resolve_acting_actor`, builds the actor-bound `RequestContext`,
|
|
37
|
-
* and loads permits before auth guards fire.
|
|
38
|
-
*
|
|
39
|
-
* Resolution rules: omitted + 1 actor → use it; omitted + multiple
|
|
40
|
-
* actors → `actor_required` with the available list; supplied + on
|
|
41
|
-
* the account → use it; supplied + foreign actor → `actor_not_on_account`.
|
|
42
|
-
*
|
|
43
|
-
* Account-grain routes — input doesn't declare `acting` and auth
|
|
44
|
-
* doesn't require permits (`role` / `keeper`) — skip resolution
|
|
45
|
-
* entirely; their `RequestContext.actor` is `null` and the audit
|
|
46
|
-
* envelope's `actor_id` stays null.
|
|
47
|
-
*/
|
|
48
|
-
export const ActingActor = Uuid.optional().meta({
|
|
49
|
-
description: 'Actor on the authenticated account that this request acts as. Omit on single-actor accounts; required on multi-actor.',
|
|
50
|
-
});
|
|
21
|
+
import { Username, Email } from '../primitive_schemas.js';
|
|
51
22
|
/**
|
|
52
23
|
* Maximum length of the optional free-form `revoked_reason` attached to a
|
|
53
|
-
* revoked
|
|
24
|
+
* revoked role_grant. Bounds the value at the schema layer so both the admin
|
|
54
25
|
* input (when the route surfaces a reason field) and the revokee-facing
|
|
55
|
-
* `
|
|
26
|
+
* `role_grant_revoke` WS notification validate against the same ceiling.
|
|
56
27
|
*/
|
|
57
|
-
export const
|
|
58
|
-
export const
|
|
28
|
+
export const ROLE_GRANT_REVOKED_REASON_LENGTH_MAX = 500;
|
|
29
|
+
export const is_role_grant_active = (p, now = new Date()) => !p.revoked_at && (!p.expires_at || new Date(p.expires_at) > now);
|
|
59
30
|
// Client-safe Zod schemas — for route output validation and ActionSpec outputs.
|
|
60
31
|
/** Zod schema for `SessionAccount` — account without sensitive fields. */
|
|
61
32
|
export const SessionAccountJson = z.strictObject({
|
|
@@ -83,10 +54,11 @@ export const ClientApiTokenJson = z.strictObject({
|
|
|
83
54
|
last_used_ip: z.string().nullable(),
|
|
84
55
|
created_at: z.string(),
|
|
85
56
|
});
|
|
86
|
-
/** Zod schema for the
|
|
87
|
-
export const
|
|
57
|
+
/** Zod schema for the role_grant summary returned in admin account listings. */
|
|
58
|
+
export const RoleGrantSummaryJson = z.strictObject({
|
|
88
59
|
id: Uuid,
|
|
89
60
|
role: z.string(),
|
|
61
|
+
scope_kind: z.string().nullable(),
|
|
90
62
|
scope_id: Uuid.nullable(),
|
|
91
63
|
created_at: z.string(),
|
|
92
64
|
expires_at: z.string().nullable(),
|
|
@@ -103,9 +75,9 @@ export const AdminAccountJson = SessionAccountJson.extend({
|
|
|
103
75
|
updated_by: Uuid.nullable(),
|
|
104
76
|
});
|
|
105
77
|
/**
|
|
106
|
-
* Zod schema for a pending
|
|
78
|
+
* Zod schema for a pending role_grant offer surfaced in admin account listings.
|
|
107
79
|
*
|
|
108
|
-
* Deliberately narrower than `
|
|
80
|
+
* Deliberately narrower than `RoleGrantOfferJson`: omits `message` and
|
|
109
81
|
* `decline_reason` so cross-admin visibility of the listing does not expose
|
|
110
82
|
* grantor-authored text that the audit log also withholds. Full offer
|
|
111
83
|
* payloads remain available through the offer-specific RPC surface and the
|
|
@@ -118,17 +90,18 @@ export const AdminAccountJson = SessionAccountJson.extend({
|
|
|
118
90
|
export const PendingOfferSummaryJson = z.strictObject({
|
|
119
91
|
id: Uuid,
|
|
120
92
|
role: z.string(),
|
|
93
|
+
scope_kind: z.string().nullable(),
|
|
121
94
|
scope_id: Uuid.nullable(),
|
|
122
95
|
from_actor_id: Uuid,
|
|
123
96
|
from_username: z.string(),
|
|
124
97
|
created_at: z.string(),
|
|
125
98
|
expires_at: z.string(),
|
|
126
99
|
});
|
|
127
|
-
/** Zod schema for an admin account listing entry (account + actor +
|
|
100
|
+
/** Zod schema for an admin account listing entry (account + actor + role_grants + pending offers). */
|
|
128
101
|
export const AdminAccountEntryJson = z.strictObject({
|
|
129
102
|
account: AdminAccountJson,
|
|
130
103
|
actor: ActorSummaryJson.nullable(),
|
|
131
|
-
|
|
104
|
+
role_grants: z.array(RoleGrantSummaryJson),
|
|
132
105
|
pending_offers: z.array(PendingOfferSummaryJson),
|
|
133
106
|
});
|
|
134
107
|
/**
|