@cosmicdrift/kumiko-bundled-features 0.263.0 → 0.264.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-bundled-features",
3
- "version": "0.263.0",
3
+ "version": "0.264.1",
4
4
  "description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -129,12 +129,12 @@
129
129
  "./workflow-runner": "./src/workflow-runner/index.ts"
130
130
  },
131
131
  "dependencies": {
132
- "@cosmicdrift/kumiko-dispatcher-live": "0.263.0",
133
- "@cosmicdrift/kumiko-framework": "0.263.0",
134
- "@cosmicdrift/kumiko-headless": "0.263.0",
135
- "@cosmicdrift/kumiko-renderer": "0.263.0",
136
- "@cosmicdrift/kumiko-renderer-web": "0.263.0",
137
- "@cosmicdrift/kumiko-types": "0.263.0",
132
+ "@cosmicdrift/kumiko-dispatcher-live": "0.264.1",
133
+ "@cosmicdrift/kumiko-framework": "0.264.1",
134
+ "@cosmicdrift/kumiko-headless": "0.264.1",
135
+ "@cosmicdrift/kumiko-renderer": "0.264.1",
136
+ "@cosmicdrift/kumiko-renderer-web": "0.264.1",
137
+ "@cosmicdrift/kumiko-types": "0.264.1",
138
138
  "@mollie/api-client": "^4.5.0",
139
139
  "@node-rs/argon2": "^2.0.2",
140
140
  "@types/mailparser": "^3.4.6",
@@ -163,7 +163,7 @@
163
163
  ],
164
164
  "devDependencies": {
165
165
  "@testing-library/user-event": "^14.6.1",
166
- "@cosmicdrift/kumiko-locale-de": "0.263.0",
167
- "@cosmicdrift/kumiko-locale-es": "0.263.0"
166
+ "@cosmicdrift/kumiko-locale-de": "0.264.1",
167
+ "@cosmicdrift/kumiko-locale-es": "0.264.1"
168
168
  }
169
169
  }
@@ -21,6 +21,12 @@ export const changePasswordWrite = defineWriteHandler({
21
21
  newPassword: passwordSchema,
22
22
  }),
23
23
  access: { roles: access.authenticated },
24
+ escapeHatch: {
25
+ reason:
26
+ "Reads the caller's own passwordHash (privileged-only field) via ctx.queryAs(SYSTEM, ...) " +
27
+ "and writes the new hash via ctx.writeAs(SYSTEM, user:update) — the caller's own " +
28
+ "identity can't reach either, field-access on passwordHash is privileged-only.",
29
+ },
24
30
  description:
25
31
  "Replaces the signed-in caller's own password after re-checking the current one; use it for a self-service password change rather than the emailed reset flow.",
26
32
  handler: async (event, ctx) => {
@@ -116,6 +116,11 @@ export function createInviteAcceptWithLoginHandler(opts: InviteAcceptWithLoginOp
116
116
  name: "invite-accept-with-login",
117
117
  schema: InviteAcceptWithLoginSchema,
118
118
  access: { roles: ["all"] },
119
+ escapeHatch: {
120
+ reason:
121
+ "Anonymous invite-accept has no session in the invited tenant yet — checks existing " +
122
+ "membership via ctx.queryAs(SYSTEM, tenant:query:memberships) of the invitation's tenant.",
123
+ },
119
124
  agent: { expose: false },
120
125
  // kumiko-lint-ignore complexity-budget reuses login.write.ts's gate chain (lockout/password/email/status/membership/mfa) plus invite-specific branches (email match, already-member check, invitation update, unburn-on-failure) — splitting would scatter gate order across functions without reducing risk
121
126
  handler: async (event, ctx) => {
@@ -263,6 +263,12 @@ export function createLoginHandler(opts: LoginHandlerOptions = {}) {
263
263
  password: z.string().min(1),
264
264
  }),
265
265
  access: { roles: ["all"] },
266
+ escapeHatch: {
267
+ reason:
268
+ "Unauthenticated login has no caller identity yet — it looks up the user row by " +
269
+ "email and resolves tenant memberships via ctx.queryAs(SYSTEM, ...) before a " +
270
+ "session exists.",
271
+ },
266
272
  description:
267
273
  "Signs a user in with email and password, running the lockout, email-verification, account-status, tenant-membership and MFA gates, and answering with a session or with an MFA challenge or setup requirement.",
268
274
  handler: async (event, ctx): Promise<WriteResult<LoginResult>> => {
@@ -26,6 +26,11 @@ export function createResetPasswordHandler(opts: ResetPasswordOptions) {
26
26
  newPassword: passwordSchema,
27
27
  }),
28
28
  access: { roles: ["all"] },
29
+ escapeHatch: {
30
+ reason:
31
+ "Anonymous token holder has no session — runConfirmTokenFlow loads and updates the " +
32
+ "target user via ctx.writeAs(SYSTEM, user:update) to set the new password hash.",
33
+ },
29
34
  agent: { expose: false },
30
35
  handler: async (event, ctx) => {
31
36
  if (!opts.hmacSecret) {
@@ -95,6 +95,11 @@ export function createTokenRequestHandler<TName extends string, TSuccessKind ext
95
95
  name: spec.handlerName,
96
96
  schema: RequestTokenSchema,
97
97
  access: { roles: ["all"] },
98
+ escapeHatch: {
99
+ reason:
100
+ "Anonymous requester has no session — looks up the target user by email via " +
101
+ "ctx.queryAs(SYSTEM, user:findForAuth) to mint the out-of-band token.",
102
+ },
98
103
  description: spec.description,
99
104
  handler: async (event, ctx) => {
100
105
  if (!opts.hmacSecret) {
@@ -27,6 +27,11 @@ export function createVerifyEmailHandler(opts: VerifyEmailOptions) {
27
27
  name: "verify-email",
28
28
  schema: VerifyEmailSchema,
29
29
  access: { roles: ["all"] },
30
+ escapeHatch: {
31
+ reason:
32
+ "Anonymous token holder has no session — runConfirmTokenFlow loads and updates the " +
33
+ "target user via ctx.writeAs(SYSTEM, user:update) to set emailVerified.",
34
+ },
30
35
  agent: { expose: false },
31
36
  handler: async (event, ctx) => {
32
37
  if (!opts.hmacSecret) {
@@ -64,6 +64,11 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
64
64
  code: z.string().length(6),
65
65
  }),
66
66
  access: { roles: ["all"] },
67
+ escapeHatch: {
68
+ reason:
69
+ "Pre-auth MFA enrollment step has no session yet — re-checks status and membership via " +
70
+ "ctx.queryAs(SYSTEM, user:findForAuth / tenant:query:memberships) for the setup token's user.",
71
+ },
67
72
  description:
68
73
  "Completes the enrollment that unblocks a sign-in forced into two-factor setup: verifies the code against the pre-auth setup token, stores the factor and derives the session the blocked login never got.",
69
74
  // Changes the caller's authentication state and mints a session.
@@ -46,6 +46,11 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
46
46
  name: "verify",
47
47
  schema: z.object({ challengeToken: z.string().min(1), code: z.string().min(6).max(9) }),
48
48
  access: { roles: ["all"] },
49
+ escapeHatch: {
50
+ reason:
51
+ "Pre-auth MFA step has no session yet — re-derives it via ctx.queryAs(SYSTEM, " +
52
+ "user:findForAuth / tenant:query:memberships) for the user the challenge token names.",
53
+ },
49
54
  description:
50
55
  "Finishes a two-step sign-in by checking a TOTP or recovery code against the challenge token that login handed back, under a per-account attempt cap, and derives the resulting session.",
51
56
  handler: async (event, ctx) => {
@@ -58,6 +58,11 @@ export function createPatCreateHandler(opts: CreatePatOptions = {}) {
58
58
  mfaCode: z.string().optional(),
59
59
  }),
60
60
  access: { openToAll: true },
61
+ escapeHatch: {
62
+ reason:
63
+ "Reads the caller's own passwordHash (privileged-only field) via ctx.queryAs(SYSTEM, " +
64
+ "user:findForAuth) to re-verify the current password before minting the token.",
65
+ },
61
66
  description:
62
67
  "Mints a personal access token for the calling user after re-verifying their password (and MFA code when enrolled) and returns the plaintext token exactly once; use it when a user needs a long-lived API credential.",
63
68
  // Returns the plaintext token once; an agent turn would archive it in the
@@ -50,6 +50,12 @@ export function createConfirmDeletionByTokenHandler(opts: ConfirmDeletionByToken
50
50
  name: "confirm-deletion-by-token",
51
51
  schema: z.object({ token: z.string().min(1) }),
52
52
  access: { roles: ["anonymous", "Member", "User", "TenantAdmin", "SystemAdmin"] },
53
+ escapeHatch: {
54
+ reason:
55
+ "startDeletionGracePeriod reads the tenant compliance profile via ctx.queryAs(SYSTEM, " +
56
+ "...) to compute the grace period end — the anonymous token holder has no read access " +
57
+ "to that tenant-config projection.",
58
+ },
53
59
  agent: { expose: false },
54
60
  rateLimit: { per: "ip", limit: 10, windowSeconds: 60 },
55
61
  handler: async (event, ctx) => {
@@ -44,6 +44,12 @@ export function createRequestDeletionHandler(opts: RequestDeletionOptions = {})
44
44
  name: "request-deletion",
45
45
  schema: z.object({}),
46
46
  access: { openToAll: true },
47
+ escapeHatch: {
48
+ reason:
49
+ "startDeletionGracePeriod reads the tenant compliance profile via ctx.queryAs(SYSTEM, " +
50
+ "...) to compute the grace period end — the calling user's own identity has no read " +
51
+ "access to that tenant-config projection.",
52
+ },
47
53
  description:
48
54
  "Starts the GDPR Art. 17 deletion of the calling user's own account by arming the grace period from the tenant compliance profile and mailing a confirmation, after which only cancel-deletion can stop the erasure.",
49
55
  agent: { risk: "high" },
@@ -46,6 +46,12 @@ export const restrictAccountWrite = defineWriteHandler({
46
46
  name: "restrict-account",
47
47
  schema: z.object({ userId: z.string().uuid().optional() }),
48
48
  access: { openToAll: true },
49
+ escapeHatch: {
50
+ reason:
51
+ "Revokes all live sessions of the (possibly self-)restricted user via ctx.writeAs(SYSTEM, " +
52
+ "sessions:write:user-session:revoke-all-for-user) — that handler's own access gate needs " +
53
+ "privileged roles the acting caller doesn't necessarily have.",
54
+ },
49
55
  description:
50
56
  "Freezes an account under GDPR Art. 18 by flipping it to restricted and revoking all of its live sessions, locking the user out until an admin calls lift-restriction; targets the caller by default, an admin may name another userId.",
51
57
  agent: { risk: "high" },
@@ -37,6 +37,12 @@ export const changeEmailWrite = defineWriteHandler({
37
37
  newEmail: z.email(),
38
38
  }),
39
39
  access: { roles: access.authenticated },
40
+ escapeHatch: {
41
+ reason:
42
+ "Reads the caller's own passwordHash via ctx.queryAs(SYSTEM, user:findForAuth), checks " +
43
+ "email uniqueness the same way, and writes the new email via ctx.writeAs(SYSTEM, " +
44
+ "user:update) — field-access on passwordHash/email is privileged-only.",
45
+ },
40
46
  description:
41
47
  "Replaces the signed-in caller's own email address after re-checking their current password, refusing an unchanged or already-taken address and clearing the verified flag so the app can re-run email verification.",
42
48
  handler: async (event, ctx) => {