@sphereon/ssi-sdk.siopv2-oid4vp-rp-rest-api
Version:
195 lines (172 loc) • 8.12 kB
text/typescript
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)
}
})
}