@sphereon/ssi-sdk.oid4vci-issuer-rest-api
Version:
188 lines (165 loc) • 6.93 kB
text/typescript
import { CredentialDataSupplier, VcIssuer } from '@sphereon/oid4vci-issuer'
import { getBasePath, OID4VCIServer } from '@sphereon/oid4vci-issuer-server'
import { IOID4VCIServerOpts } from '@sphereon/oid4vci-issuer-server'
import { ExpressSupport } from '@sphereon/ssi-express-support'
import {
createAuthRequestUriCallback,
getAccessTokenSignerCallback,
IIssuerInstanceArgs,
IssuerInstance,
createVerifyAuthResponseCallback,
} from '@sphereon/ssi-sdk.oid4vci-issuer'
import express, { Express, Request, Response, Router } from 'express'
import { IRequiredContext } from './types'
import swaggerUi from 'swagger-ui-express'
export interface IOID4VCIRestAPIOpts extends IOID4VCIServerOpts {}
export class OID4VCIRestAPI {
private readonly _expressSupport: ExpressSupport
private readonly _context: IRequiredContext
private readonly _opts?: IOID4VCIRestAPIOpts
private readonly _restApi: OID4VCIServer
private readonly _instance: IssuerInstance
private readonly _issuer: VcIssuer
private readonly _router: Router
private _baseUrl: URL
private _basePath: string
static async init(args: {
context: IRequiredContext
issuerInstanceArgs: IIssuerInstanceArgs
credentialDataSupplier?: CredentialDataSupplier
expressSupport: ExpressSupport
opts?: IOID4VCIRestAPIOpts
}): Promise<OID4VCIRestAPI> {
const { issuerInstanceArgs, context } = args
const opts = args.opts ?? {}
const expressSupport = args.expressSupport
const instance = await context.agent.oid4vciGetInstance(args.issuerInstanceArgs)
const issuer = await instance.get({ context, credentialDataSupplier: args.credentialDataSupplier })
if (!opts.endpointOpts) {
opts.endpointOpts = {}
}
if (!opts.endpointOpts.tokenEndpointOpts) {
opts.endpointOpts.tokenEndpointOpts = {
accessTokenIssuer: instance.metadataOptions.credentialIssuer ?? issuer.issuerMetadata.credential_issuer,
}
}
if (
opts?.endpointOpts.tokenEndpointOpts?.tokenEndpointDisabled !== true &&
typeof opts?.endpointOpts.tokenEndpointOpts?.accessTokenSignerCallback !== 'function'
) {
const idOpts = instance.issuerOptions.idOpts
const tokenOpts = {
iss: opts.endpointOpts.tokenEndpointOpts.accessTokenIssuer ?? instance.metadataOptions.credentialIssuer,
didOpts: instance.issuerOptions.didOpts,
idOpts,
}
opts.endpointOpts.tokenEndpointOpts.accessTokenSignerCallback = await getAccessTokenSignerCallback(
{
...tokenOpts,
},
args.context,
)
}
if (opts?.endpointOpts.authorizationChallengeOpts?.enabled === true) {
if (!instance.issuerOptions.presentationDefinitionId) {
throw Error(`Unable to set createAuthRequestUriCallback. No presentationDefinitionId present in issuer options`)
}
if (typeof opts?.endpointOpts.authorizationChallengeOpts.createAuthRequestUriCallback !== 'function') {
if (!opts.endpointOpts.authorizationChallengeOpts?.createAuthRequestUriEndpointPath) {
throw Error(`Unable to set createAuthRequestUriCallback. No createAuthRequestUriEndpointPath present in options`)
}
opts.endpointOpts.authorizationChallengeOpts.createAuthRequestUriCallback = await createAuthRequestUriCallback({
path: opts.endpointOpts.authorizationChallengeOpts.createAuthRequestUriEndpointPath,
presentationDefinitionId: instance.issuerOptions.presentationDefinitionId,
})
}
if (typeof opts?.endpointOpts.authorizationChallengeOpts?.verifyAuthResponseCallback !== 'function') {
if (!opts.endpointOpts.authorizationChallengeOpts?.verifyAuthResponseEndpointPath) {
throw Error(`Unable to set verifyAuthResponseCallback. No createAuthRequestUriEndpointPath present in options`)
}
opts.endpointOpts.authorizationChallengeOpts.verifyAuthResponseCallback = await createVerifyAuthResponseCallback({
path: opts.endpointOpts.authorizationChallengeOpts.verifyAuthResponseEndpointPath,
presentationDefinitionId: instance.issuerOptions.presentationDefinitionId,
})
}
}
return new OID4VCIRestAPI({ context, issuerInstanceArgs, expressSupport, opts, instance, issuer })
}
private readonly OID4VCI_SWAGGER_URL = 'https://api.swaggerhub.com/apis/SphereonInt/OID4VCI/0.1.1'
private constructor(args: {
issuer: VcIssuer
instance: IssuerInstance
context: IRequiredContext
issuerInstanceArgs: IIssuerInstanceArgs
expressSupport: ExpressSupport
opts: IOID4VCIRestAPIOpts
}) {
const { context, opts, issuerInstanceArgs } = args
this._baseUrl = new URL(
opts?.baseUrl ??
process.env.BASE_URL ??
opts?.issuer?.issuerMetadata?.credential_issuer ??
issuerInstanceArgs.credentialIssuer ??
'http://localhost',
)
this._basePath = getBasePath(this._baseUrl)
this._context = context
this._opts = opts ?? {}
this._expressSupport = args.expressSupport
this._issuer = args.issuer
this._instance = args.instance
this._restApi = new OID4VCIServer(args.expressSupport, { ...opts, issuer: this._issuer })
// The above setups the generic OID4VCI management and wallet APIs from the OID4VCI lib.
// Below sets up the management of configurations
this._router = express.Router()
this.express.use(this._basePath, this._router)
this.setupSwaggerUi()
}
private setupSwaggerUi() {
fetch(this.OID4VCI_SWAGGER_URL)
.then((res) => res.json())
.then((swagger: any) => {
const apiDocs = `/api-docs`
console.log(`[OID4VCI] API docs available at ${this._baseUrl.toString()}${this._basePath}${apiDocs}`)
swagger.servers = [{ url: this._baseUrl.toString(), description: 'This server' }]
this.express.set('trust proxy', this.opts?.endpointOpts?.trustProxy ?? true)
this._router.use(
apiDocs,
(req: Request, res: Response, next: any) => {
// @ts-ignore
req.swaggerDoc = swagger
next()
},
swaggerUi.serveFiles(swagger, options),
swaggerUi.setup(),
)
})
.catch((err) => {
console.log(`[OID4VCI] Unable to fetch swagger document: ${err}. Will not host api-docs on this instance`)
})
const options = {
// customCss: '.swagger-ui .topbar { display: none }',
}
}
get express(): Express {
return this._expressSupport.express
}
get context(): IRequiredContext {
return this._context
}
get opts(): IOID4VCIRestAPIOpts | undefined {
return this._opts
}
get restApi(): OID4VCIServer {
return this._restApi
}
get instance(): IssuerInstance {
return this._instance
}
get issuer(): VcIssuer {
return this._issuer
}
async stop(): Promise<boolean> {
return this._expressSupport.stop()
}
}