Merge branch 'fix/step-up-and-session-revocation' into release/v1.39.3

# Conflicts:
#	messages/de.json
#	messages/en.json
#	messages/es.json
#	messages/fr.json
#	messages/it.json
#	messages/ko.json
#	messages/pl.json
This commit is contained in:
Marc-André Bombeck committed 2026-09-26 20:40:59 +02:00
commit 03ccb21227
96 files changed
+3238 -495

No files matched your search

@@ -18,7 +18,12 @@ import { Readable } from "node:stream";
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/db";
import { apiHandler, HttpError, requireAdmin } from "@/lib/api-handler";
import {
apiHandler,
HttpError,
requireAdmin,
assertRecentCookieProof,
} from "@/lib/api-handler";
import { apiError, getClientIp } from "@/lib/api-response";
import { auditLog } from "@/lib/auth/audit";
import {
@@ -42,7 +47,10 @@ export const GET = apiHandler(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) => {
const { user: admin } = await requireAdmin();
const { user: admin, session } = await requireAdmin();
// A fresh proof on top of the admin session: this reaches every account's
// data, so a stolen admin session alone must not be enough.
await assertRecentCookieProof(admin, session.id);
const { id } = await params;
annotate({ action: { name: "admin.backups.download" }, meta: { id } });
@@ -20,7 +20,12 @@
*/
import { NextRequest } from "next/server";
import { apiHandler, HttpError, requireAdmin } from "@/lib/api-handler";
import {
apiHandler,
HttpError,
requireAdmin,
assertRecentCookieProof,
} from "@/lib/api-handler";
import { apiError, apiSuccess, getClientIp } from "@/lib/api-response";
import { auditLog } from "@/lib/auth/audit";
import { prisma } from "@/lib/db";
@@ -45,7 +50,10 @@ const handler = apiHandler(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) => {
const { user: admin } = await requireAdmin();
const { user: admin, session } = await requireAdmin();
// A fresh proof on top of the admin session: this reaches every account's
// data, so a stolen admin session alone must not be enough.
await assertRecentCookieProof(admin, session.id);
const { id } = await params;
annotate({ action: { name: "admin.backups.restore" }, meta: { id } });
+10 -2
View File
@@ -35,7 +35,12 @@ import { BodyTooLargeError, readBoundedBody } from "@/lib/labs/ocr-upload";
import { NextRequest } from "next/server";
import { ZodError } from "zod/v4";
import { prisma } from "@/lib/db";
import { apiHandler, HttpError, requireAdmin } from "@/lib/api-handler";
import {
apiHandler,
HttpError,
requireAdmin,
assertRecentCookieProof,
} from "@/lib/api-handler";
import { apiError, apiSuccess, getClientIp } from "@/lib/api-response";
import { auditLog } from "@/lib/auth/audit";
import { BackupJsonError, scanBackupJson } from "@/lib/export/backup-json-scan";
@@ -143,7 +148,10 @@ async function* decodedBytes(
}
export const POST = apiHandler(async (request: NextRequest) => {
const { user: admin } = await requireAdmin();
const { user: admin, session } = await requireAdmin();
// A fresh proof on top of the admin session: this reaches every account's
// data, so a stolen admin session alone must not be enough.
await assertRecentCookieProof(admin, session.id);
annotate({ action: { name: "admin.backups.upload" } });
const ipAddress = getClientIp(request);
+9 -2
View File
@@ -1,5 +1,9 @@
import { prisma } from "@/lib/db";
import { apiHandler, requireAdmin } from "@/lib/api-handler";
import {
apiHandler,
requireAdmin,
assertRecentCookieProof,
} from "@/lib/api-handler";
import { auditLog } from "@/lib/auth/audit";
import { apiSuccess, apiError, getClientIp } from "@/lib/api-response";
import { annotate } from "@/lib/logging/context";
@@ -47,7 +51,10 @@ export const DELETE = apiHandler(async (request: NextRequest) => {
);
}
const { user } = await requireAdmin();
const { user, session } = await requireAdmin();
// A fresh proof on top of the admin session: this reaches every account's
// data, so a stolen admin session alone must not be enough.
await assertRecentCookieProof(user, session.id);
annotate({ action: { name: "admin.data.delete" } });
// v1.18.1 — the documented convention buckets authenticated admin
+6 -2
View File
@@ -3,6 +3,7 @@ import { buildDateKey } from "@/lib/ai/coach/budget";
import { prisma } from "@/lib/db";
import { apiSuccess } from "@/lib/api-response";
import { annotate } from "@/lib/logging/context";
import { OPERATOR_HELD_PROVIDER_TYPES } from "@/lib/ai/provider-egress";
export const dynamic = "force-dynamic";
@@ -43,8 +44,11 @@ interface ProviderHealthSummary {
lastFailureStatus: number | null;
}
/** The two operator-managed tags sort first — they affect every user on the chain. */
const CENTRAL_TYPES = ["admin-openai", "admin-codex"];
/**
* The operator-managed tags sort first — they affect every user on the chain.
* Read from the one definition, in its order (the OpenAI key, then Codex).
*/
const CENTRAL_TYPES = [...OPERATOR_HELD_PROVIDER_TYPES];
export const GET = apiHandler(async () => {
await requireAdmin();
@@ -1,5 +1,9 @@
import { prisma } from "@/lib/db";
import { apiHandler, requireAdmin } from "@/lib/api-handler";
import {
apiHandler,
requireAdmin,
assertRecentCookieProof,
} from "@/lib/api-handler";
import { hashPassword, checkPasswordStrength } from "@/lib/auth/password";
import { auditLog } from "@/lib/auth/audit";
import {
@@ -31,7 +35,10 @@ export const POST = apiHandler(
);
}
const { user } = await requireAdmin();
const { user, session } = await requireAdmin();
// A fresh proof on top of the admin session: this reaches every account's
// data, so a stolen admin session alone must not be enough.
await assertRecentCookieProof(user, session.id);
const { id } = await params;
annotate({