UNPKG

@sphereon/ssi-sdk.siopv2-oid4vp-rp-rest-api

Version:

195 lines (172 loc) • 8.12 kB
import { AuthorizationRequestStateStatus, CreateAuthorizationRequest, createAuthorizationRequestFromPayload, CreateAuthorizationRequestPayloadSchema, CreateAuthorizationResponsePayload, } from '@sphereon/did-auth-siop' import { checkAuth, ISingleEndpointOpts, sendErrorResponse } from '@sphereon/ssi-express-support' import { uriWithBase } from '@sphereon/ssi-sdk.siopv2-oid4vp-common' import { Request, Response, Router } from 'express' import uuid from 'short-uuid' import { validateData } from './middleware/validationMiddleware' import { buildQueryIdFilter } from './siop-api-functions' import { AuthStatusResponse, CreateAuthorizationRequestPayloadRequest, CreateAuthorizationResponsePayloadResponse, DeleteAuthorizationRequest, GetAuthorizationRequestStatus, ICreateAuthRequestWebappEndpointOpts, IRequiredContext, QRCodeOpts, } from './types' export function createAuthRequestUniversalOID4VPEndpoint(router: Router, context: IRequiredContext, opts?: ICreateAuthRequestWebappEndpointOpts) { if (opts?.enabled === false) { console.log(`createAuthRequest universal OID4VP endpoint is disabled`) return } const path = opts?.path ?? '/backend/auth/requests' router.post( path, checkAuth(opts?.endpoint), validateData(CreateAuthorizationRequestPayloadSchema), async (request: CreateAuthorizationRequestPayloadRequest, response: CreateAuthorizationResponsePayloadResponse) => { try { const authRequest: CreateAuthorizationRequest = createAuthorizationRequestFromPayload(request.body) const correlationId = authRequest.correlationId ?? uuid.uuid() const qrCodeOpts = authRequest.qrCode ? ({ ...authRequest.qrCode } satisfies QRCodeOpts) : opts?.qrCodeOpts const queryId = authRequest.queryId const definitionItems = await context.agent.pdmGetDefinitions({ filter: buildQueryIdFilter(queryId), }) if (definitionItems.length === 0) { console.log(`No query could be found for the given id. Query id: ${queryId}`) return sendErrorResponse(response, 404, { status: 404, message: 'No query could be found' }) } const requestByReferenceURI = uriWithBase(`/siop/queries/${queryId}/auth-requests/${correlationId}`, { baseURI: authRequest.requestUriBase ?? opts?.siopBaseURI, }) const responseURI = uriWithBase(`/siop/queries/${queryId}/auth-responses/${correlationId}`, { baseURI: opts?.siopBaseURI }) const authRequestURI = await context.agent.siopCreateAuthRequestURI({ queryId, correlationId, nonce: uuid.uuid(), requestByReferenceURI, responseURIType: 'response_uri', responseURI, ...(authRequest.directPostResponseRedirectUri && { responseRedirectURI: authRequest.directPostResponseRedirectUri }), ...(authRequest.callback && { callback: authRequest.callback }), }) let qrCodeDataUri: string | undefined if (qrCodeOpts) { const { AwesomeQR } = await import('awesome-qr') const qrCode = new AwesomeQR({ text: authRequestURI, size: qrCodeOpts.size ?? 250, colorDark: qrCodeOpts.colorDark ?? '#000000', colorLight: qrCodeOpts.colorLight ?? '#FFFFFF', }) qrCodeDataUri = `data:image/png;base64,${(await qrCode.draw())!.toString('base64')}` } else { qrCodeDataUri = authRequestURI } const authRequestBody = { query_id: queryId, correlation_id: correlationId, request_uri: authRequestURI, status_uri: `${uriWithBase(opts?.webappAuthStatusPath ?? `/backend/auth/status/${correlationId}`, { baseURI: opts?.webappBaseURI })}`, ...(qrCodeDataUri && { qr_uri: qrCodeDataUri }), } satisfies CreateAuthorizationResponsePayload console.log(`Auth Request URI data to send back: ${JSON.stringify(authRequestBody)}`) return response.status(201).json(authRequestBody) } catch (error) { return sendErrorResponse(response, 500, { status: 500, message: 'Could not create an authorization request URI' }, error) } }, ) } export function removeAuthRequestStateUniversalOID4VPEndpoint(router: Router, context: IRequiredContext, opts?: ISingleEndpointOpts) { if (opts?.enabled === false) { console.log(`removeAuthStatus universal OID4VP endpoint is disabled`) return } const path = opts?.path ?? '/backend/auth/requests/:correlationId' router.delete(path, checkAuth(opts?.endpoint), async (request: DeleteAuthorizationRequest, response: Response) => { try { const correlationId: string = request.params.correlationId const authRequestState = await context.agent.siopGetAuthRequestState({ correlationId, errorOnNotFound: false, }) if (!authRequestState) { console.log(`No authorization request could be found for the given correlationId. correlationId: ${correlationId}`) return sendErrorResponse(response, 404, { status: 404, message: 'No authorization request could be found' }) } await context.agent.siopDeleteAuthState({ correlationId }) return response.status(204).json() } catch (error) { return sendErrorResponse(response, 500, { status: 500, message: error.message }, error) } }) } export function authStatusUniversalOID4VPEndpoint(router: Router, context: IRequiredContext, opts?: ISingleEndpointOpts) { if (opts?.enabled === false) { console.log(`authStatus universal OID4VP endpoint is disabled`) return } const path = opts?.path ?? '/backend/auth/status/:correlationId' router.get(path, checkAuth(opts?.endpoint), async (request: GetAuthorizationRequestStatus, response: Response) => { try { console.log('Received auth-status request...') const correlationId: string = request.params.correlationId const requestState = await context.agent.siopGetAuthRequestState({ correlationId, errorOnNotFound: false, }) if (!requestState) { console.log(`No authorization request could be found for the given correlationId. correlationId: ${correlationId}`) return sendErrorResponse(response, 404, { status: 404, message: 'No authorization request could be found' }) } let responseState if (requestState.status === AuthorizationRequestStateStatus.RETRIEVED) { responseState = await context.agent.siopGetAuthResponseState({ correlationId, errorOnNotFound: false, }) } const overallState = responseState ?? requestState const statusBody = { status: overallState.status, correlation_id: overallState.correlationId, query_id: overallState.queryId, last_updated: overallState.lastUpdated, ...('verifiedData' in overallState && { verified_data: overallState.verifiedData }), ...(overallState.error && { message: overallState.error.message }), } satisfies AuthStatusResponse console.debug(`Will send auth status: ${JSON.stringify(statusBody)}`) if (overallState.status === 'error') { return response.status(500).json(statusBody) } return response.status(200).json(statusBody) } catch (error) { return sendErrorResponse(response, 500, { status: 500, message: error.message }, error) } }) } export function getDefinitionsEndpoint(router: Router, context: IRequiredContext, opts?: ISingleEndpointOpts) { if (opts?.enabled === false) { console.log(`getDefinitions universal OID4VP endpoint is disabled`) return } const path = opts?.path ?? '/backend/definitions' router.get(path, checkAuth(opts?.endpoint), async (request: Request, response: Response) => { try { const definitions = await context.agent.pdmGetDefinitions() response.statusCode = 200 return response.json(definitions) } catch (error) { return sendErrorResponse(response, 500, { status: 500, message: error.message }, error) } }) }