-
Notifications
You must be signed in to change notification settings - Fork 16
/
OID4VCIServer.ts
177 lines (158 loc) · 7.13 KB
/
OID4VCIServer.ts
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import * as console from 'console'
import process from 'process'
import { AuthorizationRequest, CredentialSupported, IssuerCredentialSubjectDisplay, OID4VCICredentialFormat } from '@sphereon/oid4vci-common'
import { CredentialSupportedBuilderV1_11, ITokenEndpointOpts, VcIssuer, VcIssuerBuilder } from '@sphereon/oid4vci-issuer'
import { ExpressSupport, HasEndpointOpts, ISingleEndpointOpts } from '@sphereon/ssi-express-support'
import express, { Express } from 'express'
import {
accessTokenEndpoint,
createCredentialOfferEndpoint,
getBasePath,
getCredentialEndpoint,
getCredentialOfferEndpoint,
getIssueStatusEndpoint,
getMetadataEndpoint,
pushedAuthorizationEndpoint,
} from './oid4vci-api-functions'
function buildVCIFromEnvironment<DIDDoc extends object>() {
const credentialsSupported: CredentialSupported = new CredentialSupportedBuilderV1_11()
.withCryptographicSuitesSupported(process.env.cryptographic_suites_supported as string)
.withCryptographicBindingMethod(process.env.cryptographic_binding_methods_supported as string)
.withFormat(process.env.credential_supported_format as unknown as OID4VCICredentialFormat)
.withId(process.env.credential_supported_id as string)
.withTypes([process.env.credential_supported_types_1 as string, process.env.credential_supported_types_2 as string])
.withCredentialSupportedDisplay({
name: process.env.credential_display_name as string,
locale: process.env.credential_display_locale as string,
logo: {
url: process.env.credential_display_logo_url as string,
alt_text: process.env.credential_display_logo_alt_text as string,
},
background_color: process.env.credential_display_background_color as string,
text_color: process.env.credential_display_text_color as string,
})
.addCredentialSubjectPropertyDisplay(
process.env.credential_subject_display_key1 as string,
{
name: process.env.credential_subject_display_key1_name as string,
locale: process.env.credential_subject_display_key1_locale as string,
} as IssuerCredentialSubjectDisplay, // fixme: This is wrong (remove the cast and see it has no matches)
)
.build()
return new VcIssuerBuilder<DIDDoc>()
.withUserPinRequired(process.env.user_pin_required as unknown as boolean)
.withAuthorizationServer(process.env.authorization_server as string)
.withCredentialEndpoint(process.env.credential_endpoint as string)
.withCredentialIssuer(process.env.credential_issuer as string)
.withIssuerDisplay({
name: process.env.issuer_name as string,
locale: process.env.issuer_locale as string,
})
.withCredentialsSupported(credentialsSupported)
.withInMemoryCredentialOfferState()
.withInMemoryCNonceState()
.build()
}
export type ICreateCredentialOfferURIResponse = {
uri: string
userPin?: string
userPinLength?: number
userPinRequired: boolean
}
export interface IGetCredentialOfferEndpointOpts extends ISingleEndpointOpts {
baseUrl: string
}
export interface ICreateCredentialOfferEndpointOpts extends ISingleEndpointOpts {
getOfferPath?: string
}
export interface IGetIssueStatusEndpointOpts extends ISingleEndpointOpts {
baseUrl: string | URL
}
export interface IOID4VCIServerOpts extends HasEndpointOpts {
endpointOpts?: {
tokenEndpointOpts?: ITokenEndpointOpts
createCredentialOfferOpts?: ICreateCredentialOfferEndpointOpts
getCredentialOfferOpts?: IGetCredentialOfferEndpointOpts
getStatusOpts?: IGetIssueStatusEndpointOpts
parOpts?: ISingleEndpointOpts
}
baseUrl?: string
}
export class OID4VCIServer<DIDDoc extends object> {
private readonly _issuer: VcIssuer<DIDDoc>
private authRequestsData: Map<string, AuthorizationRequest> = new Map()
private readonly _app: Express
private readonly _baseUrl: URL
private readonly _expressSupport: ExpressSupport
// private readonly _server?: http.Server
private readonly _router: express.Router
constructor(
expressSupport: ExpressSupport,
opts: IOID4VCIServerOpts & { issuer?: VcIssuer<DIDDoc> } /*If not supplied as argument, it will be fully configured from environment variables*/,
) {
this._baseUrl = new URL(opts?.baseUrl ?? process.env.BASE_URL ?? opts?.issuer?.issuerMetadata?.credential_issuer ?? 'http://localhost')
this._expressSupport = expressSupport
this._app = expressSupport.express
this._router = express.Router()
this._issuer = opts?.issuer ? opts.issuer : buildVCIFromEnvironment()
pushedAuthorizationEndpoint(this.router, this.issuer, this.authRequestsData)
getMetadataEndpoint(this.router, this.issuer)
if (opts?.endpointOpts?.createCredentialOfferOpts?.enabled !== false || process.env.CREDENTIAL_OFFER_ENDPOINT_EBALBED === 'true') {
createCredentialOfferEndpoint(this.router, this.issuer, opts?.endpointOpts?.createCredentialOfferOpts)
}
getCredentialOfferEndpoint(this.router, this.issuer, opts?.getCredentialOfferOpts)
getCredentialEndpoint(this.router, this.issuer, { ...opts?.endpointOpts?.tokenEndpointOpts, baseUrl: this.baseUrl })
this.assertAccessTokenHandling()
if (!this.isTokenEndpointDisabled(opts?.endpointOpts?.tokenEndpointOpts)) {
accessTokenEndpoint(this.router, this.issuer, { ...opts?.endpointOpts?.tokenEndpointOpts, baseUrl: this.baseUrl })
}
if (this.isStatusEndpointEnabled(opts?.endpointOpts?.getStatusOpts)) {
getIssueStatusEndpoint(this.router, this.issuer, { ...opts?.endpointOpts?.getStatusOpts, baseUrl: this.baseUrl })
}
this._app.use(getBasePath(this.baseUrl), this._router)
}
public get app(): Express {
return this._app
}
/*public get server(): http.Server | undefined {
return this._server
}*/
public get router(): express.Router {
return this._router
}
get issuer(): VcIssuer<DIDDoc> {
return this._issuer
}
public async stop() {
if (!this._expressSupport) {
throw Error('Cannot stop server is the REST API is only a router of an existing express app')
}
await this._expressSupport.stop()
}
private isTokenEndpointDisabled(tokenEndpointOpts?: ITokenEndpointOpts) {
return tokenEndpointOpts?.tokenEndpointDisabled === true || process.env.TOKEN_ENDPOINT_DISABLED === 'true'
}
private isStatusEndpointEnabled(statusEndpointOpts?: IGetIssueStatusEndpointOpts) {
return statusEndpointOpts?.enabled !== false || process.env.STATUS_ENDPOINT_ENABLED === 'false'
}
private assertAccessTokenHandling(tokenEndpointOpts?: ITokenEndpointOpts) {
const authServer = this.issuer.issuerMetadata.authorization_server
if (this.isTokenEndpointDisabled(tokenEndpointOpts)) {
if (!authServer) {
throw Error(
`No Authorization Server (AS) is defined in the issuer metadata and the token endpoint is disabled. An AS or token endpoints needs to be present`,
)
}
console.log('Token endpoint disabled by configuration')
} else {
if (authServer) {
throw Error(
`A Authorization Server (AS) was already enabled in the issuer metadata (${authServer}). Cannot both have an AS and enable the token endpoint at the same time `,
)
}
}
}
get baseUrl(): URL {
return this._baseUrl
}
}