@cosmicdrift/kumiko-bundled-features 0.346.0 → 0.347.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/auth-email-password/handlers/signup-confirm.write.d.ts +15 -3
- package/dist/auth-email-password/handlers/signup-confirm.write.js +12 -1
- package/dist/auth-email-password/web/auth-client.d.ts +1 -0
- package/dist/auth-email-password/web/auth-client.js +7 -1
- package/dist/auth-email-password/web/signup-complete-screen.js +4 -1
- package/dist/personal-access-tokens/constants.d.ts +1 -0
- package/dist/personal-access-tokens/constants.js +1 -0
- package/dist/personal-access-tokens/feature.d.ts +4 -0
- package/dist/personal-access-tokens/feature.js +5 -2
- package/dist/personal-access-tokens/handlers/availability.query.d.ts +4 -0
- package/dist/personal-access-tokens/handlers/availability.query.js +17 -0
- package/dist/personal-access-tokens/screens.js +9 -0
- package/dist/step-dispatcher/webhook-runner.js +2 -0
- package/dist/tier-engine/feature.js +1 -0
- package/dist/tier-engine/i18n.js +1 -0
- package/package.json +9 -9
- package/src/auth-email-password/changes.json +6 -0
- package/src/personal-access-tokens/changes.json +6 -0
- package/src/step-dispatcher/changes.json +6 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type SessionUser } from "@cosmicdrift/kumiko-framework/engine";
|
|
1
|
+
import { type SessionUser, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
import { type LoginHandlerOptions, type LoginResult } from "./login.write.js";
|
|
4
4
|
export type SignupConfirmData = {
|
|
@@ -9,11 +9,23 @@ export type SignupConfirmData = {
|
|
|
9
9
|
readonly entityType: string;
|
|
10
10
|
readonly id: string;
|
|
11
11
|
};
|
|
12
|
-
} | Exclude<LoginResult, {
|
|
12
|
+
} | (Exclude<LoginResult, {
|
|
13
13
|
readonly kind: "auth-session";
|
|
14
|
-
}
|
|
14
|
+
}> & {
|
|
15
|
+
readonly signup: {
|
|
16
|
+
readonly roles: readonly string[];
|
|
17
|
+
readonly tenantId: TenantId;
|
|
18
|
+
readonly tenantKey: string;
|
|
19
|
+
readonly handover?: ClaimedHandover;
|
|
20
|
+
};
|
|
21
|
+
});
|
|
15
22
|
export type SignupConfirmOptions = Pick<LoginHandlerOptions, "mfaStatusChecker">;
|
|
23
|
+
type ClaimedHandover = {
|
|
24
|
+
readonly entityType: string;
|
|
25
|
+
readonly id: string;
|
|
26
|
+
};
|
|
16
27
|
export declare function createSignupConfirmHandler(opts?: SignupConfirmOptions): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"signup-confirm", z.ZodObject<{
|
|
17
28
|
token: z.ZodString;
|
|
18
29
|
password: z.ZodString;
|
|
19
30
|
}, z.core.$strip>, SignupConfirmData, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
31
|
+
export {};
|
|
@@ -168,7 +168,18 @@ export function createSignupConfirmHandler(opts = {}) {
|
|
|
168
168
|
await deleteSignupHandover(ctx.redis, event.payload.token);
|
|
169
169
|
if (mfaGate !== undefined) {
|
|
170
170
|
committed = true;
|
|
171
|
-
return {
|
|
171
|
+
return {
|
|
172
|
+
isSuccess: true,
|
|
173
|
+
data: {
|
|
174
|
+
...mfaGate,
|
|
175
|
+
signup: {
|
|
176
|
+
roles: session.roles,
|
|
177
|
+
tenantId: provisioned.tenantId,
|
|
178
|
+
tenantKey,
|
|
179
|
+
...(handover !== undefined && { handover }),
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
};
|
|
172
183
|
}
|
|
173
184
|
committed = true;
|
|
174
185
|
return {
|
|
@@ -116,6 +116,7 @@ export type SignupConfirmResult = ({
|
|
|
116
116
|
readonly kind: "signed-in";
|
|
117
117
|
} & SignupConfirmSuccess) | {
|
|
118
118
|
readonly kind: "mfa-pending";
|
|
119
|
+
readonly landingPath?: string;
|
|
119
120
|
};
|
|
120
121
|
export declare function confirmSignup(token: string, password: string): Promise<{
|
|
121
122
|
ok: true;
|
|
@@ -232,7 +232,13 @@ export async function confirmSignup(token, password) {
|
|
|
232
232
|
// @cast-boundary engine-payload
|
|
233
233
|
const body = (await res.json());
|
|
234
234
|
if (body.mfaRequired === true || body.mfaSetupRequired === true) {
|
|
235
|
-
return {
|
|
235
|
+
return {
|
|
236
|
+
ok: true,
|
|
237
|
+
data: {
|
|
238
|
+
kind: "mfa-pending",
|
|
239
|
+
...(typeof body.landingPath === "string" && { landingPath: body.landingPath }),
|
|
240
|
+
},
|
|
241
|
+
};
|
|
236
242
|
}
|
|
237
243
|
return { ok: true, data: { kind: "signed-in", ...body } };
|
|
238
244
|
}
|
|
@@ -22,6 +22,7 @@ import { useState } from "react";
|
|
|
22
22
|
import { confirmSignup } from "./auth-client.js";
|
|
23
23
|
import { passwordPairIssue, resolvePostAuthHref } from "./auth-form-logic.js";
|
|
24
24
|
import { AuthCard, useUrlToken } from "./auth-form-primitives.js";
|
|
25
|
+
import { buildLoginRedirectUrl } from "./auth-redirect.js";
|
|
25
26
|
export function SignupCompleteScreen({ title, token: tokenProp, loggedInHref = "/", loginHref = "/login", }) {
|
|
26
27
|
const t = useTranslation();
|
|
27
28
|
const { Form, Field, Input, Button, Banner, Link } = usePrimitives();
|
|
@@ -49,7 +50,9 @@ export function SignupCompleteScreen({ title, token: tokenProp, loggedInHref = "
|
|
|
49
50
|
if (res.ok) {
|
|
50
51
|
if (res.data.kind === "mfa-pending") {
|
|
51
52
|
setMfaPending(true);
|
|
52
|
-
setContinueHref(
|
|
53
|
+
setContinueHref(res.data.landingPath === undefined
|
|
54
|
+
? loginHref
|
|
55
|
+
: buildLoginRedirectUrl(loginHref, res.data.landingPath, window.location.origin));
|
|
53
56
|
return;
|
|
54
57
|
}
|
|
55
58
|
// Cookies are already set (auto-login). Show a confirmation with an
|
|
@@ -14,6 +14,7 @@ export declare const PatHandlers: {
|
|
|
14
14
|
export declare const PatQueries: {
|
|
15
15
|
readonly mine: "personal-access-tokens:query:mine";
|
|
16
16
|
readonly availableScopes: "personal-access-tokens:query:available-scopes";
|
|
17
|
+
readonly availability: "personal-access-tokens:query:availability";
|
|
17
18
|
};
|
|
18
19
|
export declare const PAT_PREFIX_DISPLAY_LENGTH = 12;
|
|
19
20
|
export type PatRateLimit = {
|
|
@@ -26,6 +26,7 @@ export const PatHandlers = {
|
|
|
26
26
|
export const PatQueries = {
|
|
27
27
|
mine: "personal-access-tokens:query:mine",
|
|
28
28
|
availableScopes: "personal-access-tokens:query:available-scopes",
|
|
29
|
+
availability: "personal-access-tokens:query:availability",
|
|
29
30
|
};
|
|
30
31
|
// Only the first chars of a minted token are stored (alongside the hash) so the
|
|
31
32
|
// UI can show "kpat_ab12…" for recognition without ever holding the secret.
|
|
@@ -13,6 +13,10 @@ export type PersonalAccessTokensOptions = {
|
|
|
13
13
|
readonly toggleable?: {
|
|
14
14
|
readonly default: boolean;
|
|
15
15
|
};
|
|
16
|
+
/** Screen (same-feature id or `<feature>:screen:<id>`, must not be gated itself)
|
|
17
|
+
* shown in place of the token screens when the tenant's tier excludes the
|
|
18
|
+
* feature, e.g. an upgrade notice. Default: the standard "unavailable" notice. */
|
|
19
|
+
readonly lockedFallbackScreen?: string;
|
|
16
20
|
readonly mfaVerifier?: CreatePatOptions["mfaVerifier"];
|
|
17
21
|
readonly autoRevokeOnPasswordChange?: (userId: string) => Promise<number>;
|
|
18
22
|
};
|
|
@@ -3,6 +3,7 @@ import { PAT_TOKEN_PREFIX } from "@cosmicdrift/kumiko-framework/api";
|
|
|
3
3
|
import { deriveEntityTableMeta } from "@cosmicdrift/kumiko-framework/db";
|
|
4
4
|
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
5
5
|
import { API_TOKEN_ENTITY_NAME, PAT_DEFAULT_RATE_LIMIT, PAT_FEATURE, } from "./constants.js";
|
|
6
|
+
import { availabilityQuery } from "./handlers/availability.query.js";
|
|
6
7
|
import { buildAvailableScopesQuery } from "./handlers/available-scopes.query.js";
|
|
7
8
|
import { createPatCreateHandler } from "./handlers/create.write.js";
|
|
8
9
|
import { listPatQuery } from "./handlers/list.query.js";
|
|
@@ -105,12 +106,14 @@ export function createPersonalAccessTokensFeature(options) {
|
|
|
105
106
|
};
|
|
106
107
|
const queries = {
|
|
107
108
|
mine: r.queryHandler(listPatQuery),
|
|
109
|
+
availability: r.queryHandler(availabilityQuery),
|
|
108
110
|
availableScopes: r.queryHandler(buildAvailableScopesQuery(scopes)),
|
|
109
111
|
};
|
|
110
112
|
// Declarative screens — list-with-revoke + mint-with-reveal. The app
|
|
111
113
|
// places `patListScreen` via r.nav in its logged-in settings area.
|
|
112
|
-
|
|
113
|
-
r.screen(
|
|
114
|
+
const lockedFallback = options.lockedFallbackScreen === undefined ? {} : { fallback: options.lockedFallbackScreen };
|
|
115
|
+
r.screen({ ...patListScreen, ...lockedFallback });
|
|
116
|
+
r.screen({ ...createPatMintScreen(scopes), ...lockedFallback });
|
|
114
117
|
r.translations({ keys: { ...PAT_FEATURE_I18N, ...patScopeOptionTranslations(scopes) } });
|
|
115
118
|
// rateLimit flows into feature.exports so run-prod-app builds the
|
|
116
119
|
// limiter from the same declaration — single source of truth. `scopes`
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export declare const availabilityQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"availability", z.ZodObject<{}, z.core.$strip>, {
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
// Screen gate probe: the dispatcher's feature gate rejects every handler of a
|
|
4
|
+
// toggleable feature the tenant's tier does not include, so this query only
|
|
5
|
+
// answers for tenants that may use tokens — a rejection makes the screens'
|
|
6
|
+
// visibleWhen fall back instead of rendering an unusable token list.
|
|
7
|
+
export const availabilityQuery = defineQueryHandler({
|
|
8
|
+
name: "availability",
|
|
9
|
+
schema: z.object({}),
|
|
10
|
+
access: {
|
|
11
|
+
openToAll: {
|
|
12
|
+
reason: "constant answer that only exists to be gated by the feature toggle; it carries no user or tenant data",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
description: "Reports that personal access tokens are available to the caller's tenant; fails with feature_disabled when the tenant's tier excludes them. The token screens use it to decide whether to render.",
|
|
16
|
+
handler: async () => ({ enabled: true }),
|
|
17
|
+
});
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { i18nKey, MAX_LIST_LIMIT, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { PAT_MINT_SCREEN_ID, PAT_SCREEN_ID, PatHandlers, PatQueries } from "./constants.js";
|
|
3
3
|
const PAT_STATUS_OPTION_KEY_PREFIX = "pat.list.status.";
|
|
4
|
+
// Both screens render only for tenants whose tier includes the feature; the
|
|
5
|
+
// availability query is rejected by the feature gate otherwise.
|
|
6
|
+
const PAT_SCREEN_VISIBLE_WHEN = {
|
|
7
|
+
query: PatQueries.availability,
|
|
8
|
+
field: "enabled",
|
|
9
|
+
eq: true,
|
|
10
|
+
};
|
|
4
11
|
export const patListScreen = {
|
|
5
12
|
id: PAT_SCREEN_ID,
|
|
6
13
|
type: "projectionList",
|
|
14
|
+
visibleWhen: PAT_SCREEN_VISIBLE_WHEN,
|
|
7
15
|
query: PatQueries.mine,
|
|
8
16
|
// The `mine` handler honours `limit` only (no offset/total), so a pager would
|
|
9
17
|
// show page 1 forever: send one max-size request and render no pager.
|
|
@@ -76,6 +84,7 @@ export function createPatMintScreen(scopes) {
|
|
|
76
84
|
return {
|
|
77
85
|
id: PAT_MINT_SCREEN_ID,
|
|
78
86
|
type: "secretMint",
|
|
87
|
+
visibleWhen: PAT_SCREEN_VISIBLE_WHEN,
|
|
79
88
|
handler: PatHandlers.create,
|
|
80
89
|
fields: {
|
|
81
90
|
name: { type: "text", required: true, maxLength: 120 },
|
|
@@ -184,6 +184,8 @@ export async function performWebhookDispatch(spec, deps) {
|
|
|
184
184
|
signal: AbortSignal.timeout(deps.requestTimeoutMs ?? WEBHOOK_REQUEST_TIMEOUT_MS),
|
|
185
185
|
body: spec.body !== undefined ? JSON.stringify(spec.body) : undefined,
|
|
186
186
|
});
|
|
187
|
+
// Only the status is used; an unread body would pin the socket until the timeout fires.
|
|
188
|
+
await res.body?.cancel().catch(() => { });
|
|
187
189
|
if (!res.ok) {
|
|
188
190
|
return { ok: false, error: `HTTP ${res.status}: ${res.statusText}` };
|
|
189
191
|
}
|
|
@@ -200,6 +200,7 @@ export function createTierEngineFeature(opts = {}) {
|
|
|
200
200
|
],
|
|
201
201
|
},
|
|
202
202
|
submitLabel: "tier-admin.submit",
|
|
203
|
+
successMessage: "tier-admin.success",
|
|
203
204
|
cancelTarget: false,
|
|
204
205
|
description: "tier-admin.screen.subtitle",
|
|
205
206
|
access: { roles: ["SystemAdmin"] },
|
package/dist/tier-engine/i18n.js
CHANGED
|
@@ -18,5 +18,6 @@ export const TIER_ENGINE_I18N = {
|
|
|
18
18
|
"tier-engine:entity:__action-form__:field:tenantId": { en: "Tenant" },
|
|
19
19
|
"tier-engine:entity:__action-form__:field:tier": { en: "New tier" },
|
|
20
20
|
"tier-admin.submit": { en: "Assign tier" },
|
|
21
|
+
"tier-admin.success": { en: "Tier assigned: {tenantId} → {tier}" },
|
|
21
22
|
};
|
|
22
23
|
export const defaultTranslations = translationsByLocaleFromKeys(TIER_ENGINE_I18N);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-bundled-features",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.347.0",
|
|
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>",
|
|
@@ -503,12 +503,12 @@
|
|
|
503
503
|
}
|
|
504
504
|
},
|
|
505
505
|
"dependencies": {
|
|
506
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
507
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
508
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
509
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
510
|
-
"@cosmicdrift/kumiko-renderer-web": "0.
|
|
511
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
506
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.347.0",
|
|
507
|
+
"@cosmicdrift/kumiko-framework": "0.347.0",
|
|
508
|
+
"@cosmicdrift/kumiko-headless": "0.347.0",
|
|
509
|
+
"@cosmicdrift/kumiko-renderer": "0.347.0",
|
|
510
|
+
"@cosmicdrift/kumiko-renderer-web": "0.347.0",
|
|
511
|
+
"@cosmicdrift/kumiko-types": "0.347.0",
|
|
512
512
|
"@mollie/api-client": "^4.5.0",
|
|
513
513
|
"@node-rs/argon2": "^2.0.2",
|
|
514
514
|
"@types/mailparser": "^3.4.6",
|
|
@@ -1025,8 +1025,8 @@
|
|
|
1025
1025
|
],
|
|
1026
1026
|
"devDependencies": {
|
|
1027
1027
|
"@testing-library/user-event": "^14.6.1",
|
|
1028
|
-
"@cosmicdrift/kumiko-locale-de": "0.
|
|
1029
|
-
"@cosmicdrift/kumiko-locale-es": "0.
|
|
1028
|
+
"@cosmicdrift/kumiko-locale-de": "0.347.0",
|
|
1029
|
+
"@cosmicdrift/kumiko-locale-es": "0.347.0",
|
|
1030
1030
|
"jsqr": "^1.4.0"
|
|
1031
1031
|
}
|
|
1032
1032
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.347.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Signup-confirm under an MFA policy returns the signup landing path and the activation screen forwards it to login as next",
|
|
6
|
+
"detail": "Signup with an MFA requirement keeps its landing path\nWhen the MFA policy asks for a factor at self-registration, `/auth/signup-confirm` now also returns the `landingPath` that `auth.postAuthLanding` resolves for the signup flow (including a claimed handover). `SignupCompleteScreen` passes it to the login link as `?next=`, so the login that follows can land where a signup without MFA would have."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.345.0",
|
|
4
10
|
"type": "fix",
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.347.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "API-token screens hide behind the tier gate and can fall back to an upgrade screen via lockedFallbackScreen",
|
|
6
|
+
"detail": "Token screens follow the tier gate of personal-access-tokens\nThe list and mint screens carry a `visibleWhen` on the new `personal-access-tokens:query:availability` probe. For a tenant whose tier excludes the feature (`toggleable`), the dispatcher already rejected every token handler with `feature_disabled`; now the screens also show the unavailable notice instead of an empty list with a broken Create button. A new option `lockedFallbackScreen` names a screen (for example an upgrade notice) to show in its place. Without `toggleable` nothing changes: the feature stays always on."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.336.0",
|
|
4
10
|
"type": "fix",
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.347.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Webhook dispatch cancels the unread response body so a stalling receiver cannot hold the connection",
|
|
6
|
+
"detail": "Webhook dispatch releases the connection right after the status\nThe step-dispatcher only needs the response status, so it now cancels the unread response body instead of leaving the socket open until the 10 s request timeout fires."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.346.0",
|
|
4
10
|
"type": "fix",
|