fix(admin): remove manual reasons and repair code creation
This commit is contained in:
@@ -25,7 +25,7 @@ const compatibilityRoles = readFileSync(new URL("../../deploy/postgres/002-ensur
|
||||
const administratorsRoute = readFileSync(new URL("../src/app/api/admin/administrators/route.ts", import.meta.url), "utf8");
|
||||
const mfaRoute = readFileSync(new URL("../src/app/api/admin/mfa/route.ts", import.meta.url), "utf8");
|
||||
const mfaSecurity = readFileSync(new URL("../src/components/admin/mfa-security.tsx", import.meta.url), "utf8");
|
||||
const reasonActionModal = readFileSync(new URL("../src/components/admin/reason-action-modal.tsx", import.meta.url), "utf8");
|
||||
const confirmActionModal = readFileSync(new URL("../src/components/admin/confirm-action-modal.tsx", import.meta.url), "utf8");
|
||||
const customersRoute = readFileSync(new URL("../src/app/api/admin/customers/route.ts", import.meta.url), "utf8");
|
||||
const adminUser = readFileSync(new URL("../src/lib/supabase/admin.ts", import.meta.url), "utf8");
|
||||
const codesRoute = readFileSync(new URL("../src/app/api/admin/codes/route.ts", import.meta.url), "utf8");
|
||||
@@ -237,9 +237,9 @@ test("administrator writes keep permission and origin checks without operation-l
|
||||
assert.doesNotMatch(adminHttp, /requireHighRiskAdminMutation|verifyHighRiskAdminProof/);
|
||||
assert.doesNotMatch(administratorsRoute, /requireHighRiskAdminMutation/);
|
||||
assert.equal(existsSync(new URL("../src/app/api/admin/reauth/route.ts", import.meta.url)), false);
|
||||
assert.match(reasonActionModal, /label="操作原因"/);
|
||||
assert.match(reasonActionModal, /await onSubmit\(values\.reason\.trim\(\)\)/);
|
||||
assert.doesNotMatch(reasonActionModal, /邮箱验证码|\/api\/admin\/reauth|reauthPermission/);
|
||||
assert.doesNotMatch(confirmActionModal, /操作原因|name="reason"|TextArea/);
|
||||
assert.match(confirmActionModal, /await onConfirm\(\)/);
|
||||
assert.doesNotMatch(confirmActionModal, /邮箱验证码|\/api\/admin\/reauth|reauthPermission/);
|
||||
|
||||
assert.match(authFactory, /twoFactor\(/);
|
||||
assert.match(authFactory, /schema: identityModelMapping\.twoFactor/);
|
||||
|
||||
@@ -75,12 +75,12 @@ test("MFA API keeps native Better Auth enrollment, recovery, rotation, and proof
|
||||
assert.match(migration, /grant select, insert, update, delete on table identity\.two_factors[\s\S]*identity_runtime/);
|
||||
});
|
||||
|
||||
test("business confirmation UI requires only an audit reason while account MFA remains separate", () => {
|
||||
const modal = readFileSync(new URL("../src/components/admin/reason-action-modal.tsx", import.meta.url), "utf8");
|
||||
test("business confirmation UI has no per-operation input while account MFA remains separate", () => {
|
||||
const modal = readFileSync(new URL("../src/components/admin/confirm-action-modal.tsx", import.meta.url), "utf8");
|
||||
const http = readFileSync(new URL("../src/lib/admin/http.ts", import.meta.url), "utf8");
|
||||
|
||||
assert.match(modal, /label="操作原因"/);
|
||||
assert.match(modal, /await onSubmit\(values\.reason\.trim\(\)\)/);
|
||||
assert.doesNotMatch(modal, /操作原因|name="reason"|TextArea/);
|
||||
assert.match(modal, /await onConfirm\(\)/);
|
||||
assert.doesNotMatch(modal, /邮箱验证码|\/api\/admin\/reauth|reauthPermission/);
|
||||
assert.match(http, /requireAdminMutation/);
|
||||
assert.doesNotMatch(http, /requireHighRiskAdminMutation|verifyHighRiskAdminProof/);
|
||||
|
||||
@@ -38,22 +38,25 @@ test("operation-level admin email reauthentication is removed from routes and UI
|
||||
}
|
||||
});
|
||||
|
||||
test("admin mutations retain session, permission, trusted-origin, reason, request-id, and audit boundaries", () => {
|
||||
test("admin mutations retain session, permission, trusted-origin, server audit, request-id, and audit boundaries", () => {
|
||||
const http = source("src/lib/admin/http.ts");
|
||||
const codesRoute = source("src/app/api/admin/codes/route.ts");
|
||||
const codesHelper = source("src/lib/admin/codes.ts");
|
||||
const codesUi = source("src/components/admin/codes-resource.tsx");
|
||||
const modal = source("src/components/admin/reason-action-modal.tsx");
|
||||
const modal = source("src/components/admin/confirm-action-modal.tsx");
|
||||
|
||||
assert.match(http, /requirePermission\(permission, request\.headers\)/);
|
||||
assert.match(http, /isTrustedAdminMutationRequest\(request, process\.env\.ADMIN_USER_ORIGIN\)/);
|
||||
assert.match(codesRoute, /requireAdminMutation\([\s\S]*"billing\.adjustments\.write"/);
|
||||
assert.match(codesRoute, /reason:\s*z\.string\(\)\.trim\(\)\.min\(1\)\.max\(500\)/);
|
||||
assert.doesNotMatch(codesRoute, /reason:\s*z\.string/);
|
||||
assert.match(codesRoute, /p_reason:\s*"admin_console_create_redemption_codes"/);
|
||||
assert.match(codesHelper, /JSON\.stringify\(input\.p_codes\)/);
|
||||
assert.match(codesRoute, /requestId\(request\)/);
|
||||
assert.match(codesRoute, /admin_create_redemption_codes/);
|
||||
assert.match(codesUi, /permissions\.includes\("billing\.adjustments\.write"\)/);
|
||||
assert.match(codesUi, /okText="确认生成"/);
|
||||
assert.match(modal, /label="操作原因"/);
|
||||
assert.match(modal, /await onSubmit\(values\.reason\.trim\(\)\)/);
|
||||
assert.doesNotMatch(modal, /操作原因|TextArea|name="reason"/);
|
||||
assert.match(modal, /await onConfirm\(\)/);
|
||||
});
|
||||
|
||||
test("account-level TOTP MFA and customer login email OTP remain available", () => {
|
||||
|
||||
@@ -66,7 +66,7 @@ test("admin mutation UI permissions match ordinary API guards without operation
|
||||
await t.test(mapping.name, () => {
|
||||
const ui = source(mapping.ui);
|
||||
const api = source(mapping.api);
|
||||
assert.match(ui, /ReasonActionModal/);
|
||||
assert.match(ui, /ConfirmActionModal/);
|
||||
assert.doesNotMatch(ui, /reauthPermission|邮箱验证码|\/api\/admin\/reauth/);
|
||||
assert.match(api, new RegExp(String.raw`requireAdminMutation\(\s*request,\s*(?:permission|["']${mapping.permission.replaceAll(".", "\\.")}["']),?\s*\)`));
|
||||
assert.doesNotMatch(api, /requireHighRiskAdminMutation/);
|
||||
@@ -115,17 +115,17 @@ test("model management uses ordinary admin mutation guards without reauth", () =
|
||||
assert.doesNotMatch(ui, /验证并(?:保存|获取)/);
|
||||
});
|
||||
|
||||
test("admin request failures are real Error instances and reason modal keeps failures visible", () => {
|
||||
test("admin request failures are real Error instances and confirmation modal keeps failures visible", () => {
|
||||
const providers = source("src/lib/admin/providers.ts");
|
||||
const modal = source("src/components/admin/reason-action-modal.tsx");
|
||||
const modal = source("src/components/admin/confirm-action-modal.tsx");
|
||||
assert.match(
|
||||
providers,
|
||||
/Object\.assign\(new Error\(message\),\s*\{\s*statusCode: response\.status,/,
|
||||
);
|
||||
assert.doesNotMatch(providers, /throw \{ message, statusCode/);
|
||||
assert.match(modal, /catch \(error\)[\s\S]*setActionError\(error instanceof Error \? error\.message/);
|
||||
assert.match(modal, /name="reason"[\s\S]*required: true, whitespace: true/);
|
||||
assert.match(modal, /await onSubmit\(values\.reason\.trim\(\)\)/);
|
||||
assert.doesNotMatch(modal, /操作原因|name="reason"|TextArea/);
|
||||
assert.match(modal, /await onConfirm\(\)/);
|
||||
assert.doesNotMatch(modal, /reauth|邮箱验证码|发送验证码/);
|
||||
});
|
||||
|
||||
|
||||
@@ -40,13 +40,13 @@ test("existing payment orders remain queryable and settleable when subscriptions
|
||||
|
||||
test("billing and operations writes use the shared ordinary mutation guard", () => {
|
||||
assert.match(productsRoute, /requireAdminMutation\(request, permission\)/);
|
||||
assert.match(productsRoute, /body\.data\.reason[\s\S]*requestId/);
|
||||
assert.match(productsRoute, /admin_console_(?:save|publish)_product/);
|
||||
assert.match(epaySettingsRoute, /requireAdminMutation\(request, "billing\.adjustments\.write"\)/);
|
||||
assert.match(epaySettingsRoute, /admin_save_epay_settings/);
|
||||
assert.match(subscriptionsRoute, /requireAdminMutation\(request,"billing\.adjustments\.write"\)/);
|
||||
assert.match(subscriptionsRoute, /body\.data\.reason,rid/);
|
||||
assert.match(subscriptionsRoute, /admin_console_adjust_subscription/);
|
||||
assert.match(featureFlagsRoute, /requireAdminMutation\(request,"ops\.flags\.write"\)/);
|
||||
assert.match(featureFlagsRoute, /admin_publish_feature_flag[\s\S]*b\.data\.reason,rid/);
|
||||
assert.match(featureFlagsRoute, /admin_console_publish_feature_flag/);
|
||||
for (const route of [productsRoute, epaySettingsRoute, subscriptionsRoute, featureFlagsRoute]) {
|
||||
assert.doesNotMatch(route, /requireHighRiskAdminMutation/);
|
||||
}
|
||||
@@ -74,13 +74,14 @@ test("self-hosted payment catalog uses simple queries and immutable product snap
|
||||
assert.match(createRoute, /product_snapshot:\s*productSnapshot/);
|
||||
});
|
||||
|
||||
test("order adjustments require permission, reason, version, idempotency request id, and the domain RPC", () => {
|
||||
test("order adjustments require permission, version, server audit reason, request id, and the domain RPC", () => {
|
||||
assert.match(
|
||||
ordersRoute,
|
||||
/requireAdminMutation\(\s*request,\s*"billing\.adjustments\.write",?\s*\)/,
|
||||
);
|
||||
assert.match(ordersRoute, /expectedVersion:\s*z\.number\(\)\.int\(\)\.min\(0\)/);
|
||||
assert.match(ordersRoute, /reason:\s*z\.string\(\)\.trim\(\)\.min\(1\)\.max\(500\)/);
|
||||
assert.doesNotMatch(ordersRoute, /reason:\s*z\.string/);
|
||||
assert.match(ordersRoute, /admin_console_adjust_order/);
|
||||
assert.match(ordersRoute, /queryAdminRows<AdjustmentRow>/);
|
||||
assert.match(ordersRoute, /public\.admin_adjust_order\(/);
|
||||
assert.match(ordersRoute, /requestId\(request\)/);
|
||||
@@ -92,16 +93,16 @@ test("order adjustments require permission, reason, version, idempotency request
|
||||
assert.match(billingOperationsUi, /不调用支付网关|仅记录账务/);
|
||||
});
|
||||
|
||||
test("every redemption-code write requires a reason and forwards it to the audited RPC", () => {
|
||||
test("redemption-code writes generate server audit reasons and encode JSONB for self-hosted pg", () => {
|
||||
assert.match(codesRoute, /requireAdminMutation\(\s*request,\s*"billing\.adjustments\.write",?\s*\)/);
|
||||
assert.equal((codeRoute.match(/requireAdminMutation\(/g) ?? []).length, 2);
|
||||
assert.doesNotMatch(codeRoute, /requireHighRiskAdminMutation/);
|
||||
assert.match(codesRoute, /reason:\s*z\.string\(\)\.trim\(\)\.min\(1\)\.max\(500\)/);
|
||||
assert.match(codesRoute, /p_reason:\s*parsed\.data\.reason/);
|
||||
assert.match(codeRoute, /const revokeCodeSchema[\s\S]*reason:\s*z\.string\(\)\.trim\(\)\.min\(1\)\.max\(500\)/);
|
||||
assert.match(codeRoute, /const updateCodeSchema[\s\S]*reason:\s*z\.string\(\)\.trim\(\)\.min\(1\)\.max\(500\)/);
|
||||
assert.match(codeRoute, /p_reason:\s*body\.reason/);
|
||||
assert.match(codeRoute, /p_reason:\s*parsedBody\.data\.reason/);
|
||||
assert.doesNotMatch(codesRoute, /reason:\s*z\.string/);
|
||||
assert.doesNotMatch(codeRoute, /reason:\s*z\.string/);
|
||||
assert.match(codesRoute, /p_reason:\s*"admin_console_create_redemption_codes"/);
|
||||
assert.match(codeRoute, /p_reason:\s*"admin_console_update_redemption_code"/);
|
||||
assert.match(codeRoute, /p_reason:\s*"admin_console_revoke_redemption_code"/);
|
||||
assert.match(codesHelper, /JSON\.stringify\(input\.p_codes\)/);
|
||||
assert.match(codesHelper, /queryAdminRows<RpcCodeRow>/);
|
||||
assert.match(codesHelper, /public\.admin_create_redemption_codes\(/);
|
||||
assert.match(codesHelper, /public\.admin_update_redemption_code\(/);
|
||||
@@ -109,7 +110,7 @@ test("every redemption-code write requires a reason and forwards it to the audit
|
||||
assert.doesNotMatch(codesHelper, /createAdminSupabaseClient|\.rpc\(|\$\{functionName\}/);
|
||||
assert.match(codesUi, /permissions\.includes\("billing\.adjustments\.write"\)/);
|
||||
assert.doesNotMatch(codesUi, /reauthPermission|邮箱验证码|\/api\/admin\/reauth/);
|
||||
assert.match(codesUi, /open=\{Boolean\(pendingCreate\)\}[\s\S]*onSubmit=\{submitCreate\}/);
|
||||
assert.match(codesUi, /open=\{Boolean\(pendingEdit\)\}[\s\S]*onSubmit=\{submitEdit\}/);
|
||||
assert.match(codesUi, /<ReasonActionModal[\s\S]*onSubmit=\{\(reason\) => revoke\(revokeRecord!, reason\)\}/);
|
||||
assert.match(codesUi, /open=\{Boolean\(pendingCreate\)\}[\s\S]*onConfirm=\{submitCreate\}/);
|
||||
assert.match(codesUi, /open=\{Boolean\(pendingEdit\)\}[\s\S]*onConfirm=\{submitEdit\}/);
|
||||
assert.match(codesUi, /<ConfirmActionModal[\s\S]*onConfirm=\{\(\) => revoke\(revokeRecord!\)\}/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user