/**
* GET / POST / DELETE /ds-devices/{serial_number}/agent-token — 制御盤トークンの発行日時・発行・失効
* (指示書AY 段階7-1。D16・P1・P2・D-179)
*
* 仕様の正は `d-watch-api.yaml` の同パス。★既存の cookie 認証のパス★(制御盤のトークンでは作り直せない。表4 A11)。
* ★生のトークンは POST の応答で1回だけ★(`Cache-Control: no-store`)。保存は SHA-256 だけ(DFA 独自ストア。メインDBには書かない)。
* ★ログは `serial_number` だけ★(表3 KA8)。
*/
import { NextResponse } from "next/server";
import { parsePathSerialNumber } from "@/lib/api/path-params";
import { db } from "@/lib/db";
import { errorBody, jsonError, noContent } from "@/lib/ds-agent/agent-request";
import { ensureDsAgentLogging } from "@/lib/ds-agent/relay-log";
import { agentTokenIssuedAt, issueAgentToken, revokeAgentToken } from "@/lib/ds-agent/tokens";
import { logEvent, withRequestId } from "@/lib/logger";
import { logApiError } from "@/lib/logger/api";
import type { DsAgentTokenIssued, DsAgentTokenStatus } from "@/lib/types";
const ROUTE = "/api/ds-devices/[serial_number]/agent-token";
type Method = "GET" | "POST" | "DELETE";
async function withDevice(
method: Method,
request: Request,
params: Promise<{ serial_number: string }>,
action: (serial: string) => Promise<Response>
): Promise<Response> {
return withRequestId(request, async () => {
ensureDsAgentLogging();
const { serial_number: serialRaw } = await params;
const parsedSerial = parsePathSerialNumber(serialRaw);
if (!parsedSerial.ok) return NextResponse.json(parsedSerial.error, { status: parsedSerial.status });
const serial = parsedSerial.query;
try {
if (!(await db.dsDeviceExists(serial))) return jsonError(404, "not_found", "デバイスが見つかりません");
return await action(serial);
} catch (e) {
const body = errorBody("internal_error", "制御盤トークンの処理に失敗しました");
logApiError({ route: ROUTE, method, body, status: 500, err: e });
return NextResponse.json(body, { status: 500 });
}
});
}
export async function GET(request: Request, { params }: { params: Promise<{ serial_number: string }> }) {
return withDevice("GET", request, params, async (serial) => {
const body: DsAgentTokenStatus = { issued_at: await agentTokenIssuedAt(serial) };
return NextResponse.json(body);
});
}
export async function POST(request: Request, { params }: { params: Promise<{ serial_number: string }> }) {
return withDevice("POST", request, params, async (serial) => {
const issued = await issueAgentToken(serial);
logEvent("info", { event: "ds_agent.token_issued", serial_number: serial });
const body: DsAgentTokenIssued = { token: issued.token, issued_at: issued.issued_at };
return NextResponse.json(body, { status: 201, headers: { "Cache-Control": "no-store" } });
});
}
export async function DELETE(request: Request, { params }: { params: Promise<{ serial_number: string }> }) {
return withDevice("DELETE", request, params, async (serial) => {
// ★未発行でも 204(冪等。P2)★。ログは実際に失効させたときだけ
if (await revokeAgentToken(serial)) {
logEvent("info", { event: "ds_agent.token_revoked", serial_number: serial });
}
return noContent();
});
}