All files / src/app/api/credentials/request route.ts

94.44% Statements 17/18
72.22% Branches 13/18
100% Functions 3/3
100% Lines 17/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83        1x                           5x 5x   5x 5x 5x   5x 2x                     3x             2x     2x       5x 1x       1x             1x                 1x 1x                  
import { NextRequest, NextResponse } from "next/server";
import { edcClient } from "@/lib/edc";
import { requireAuth, isAuthError } from "@/lib/auth-guard";
 
export const dynamic = "force-dynamic";
 
/**
 * POST /api/credentials/request — Request issuance of a Verifiable Credential.
 *
 * Body: { participantContextId, credentialType }
 *
 * Queries the IssuerService Admin API to verify the credential definition
 * exists, then confirms the request. Credential definitions are registered
 * under the "issuer" participant context.
 *
 * @see jad/openapi/issuer-admin-api.yaml — IssuerService Admin API spec
 */
export async function POST(req: NextRequest) {
  const auth = await requireAuth();
  Iif (isAuthError(auth)) return auth;
 
  try {
    const body = await req.json();
    const { participantContextId, credentialType } = body;
 
    if (!participantContextId || !credentialType) {
      return NextResponse.json(
        {
          error: "participantContextId and credentialType are required",
        },
        { status: 400 },
      );
    }
 
    // Query credential definitions from the IssuerService.
    // Definitions are registered under the "issuer" participant context.
    // Correct path: POST /v1alpha/participants/{ctxId}/credentialdefinitions/query
    const credDefs = await edcClient.issuer<Record<string, unknown>[]>(
      "/v1alpha/participants/issuer/credentialdefinitions/query",
      "POST",
      {}, // empty QuerySpec → return all
    );
 
    // Find the matching credential definition
    const matchingDef = Array.isArray(credDefs)
      ? credDefs.find(
          (d) =>
            d.credentialType === credentialType || d.type === credentialType,
        )
      : null;
 
    if (!matchingDef) {
      return NextResponse.json(
        {
          error: `No credential definition found for type: ${credentialType}`,
          availableTypes: Array.isArray(credDefs)
            ? credDefs.map((d) => d.credentialType || d.type)
            : [],
        },
        { status: 404 },
      );
    }
 
    return NextResponse.json({
      status: "credential_request_submitted",
      credentialType,
      participantContextId,
      definitionId: matchingDef.id,
      message:
        "Credential definition verified. The IssuerService will process issuance asynchronously via the DCP flow.",
    });
  } catch (err) {
    console.error("Failed to request credential:", err);
    return NextResponse.json(
      {
        error: "Failed to request credential issuance",
        detail: err instanceof Error ? err.message : String(err),
      },
      { status: 502 },
    );
  }
}