@@ -9,14 +9,14 @@ import {
9
9
CredentialRequest ,
10
10
getCredentialConfigurationsMatchingRequestFormat ,
11
11
} from '@openid4vc/openid4vci'
12
-
13
12
import createHttpError from 'http-errors'
14
13
import { getCredentialConfigurationsSupportedForScopes } from '../../shared'
15
14
import { CredoRouter , getRequestContext } from '../../shared/router'
16
15
import { addSecondsToDate } from '../../shared/utils'
17
16
import { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState'
18
17
import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService'
19
18
import { OpenId4VcIssuanceSessionRecord , OpenId4VcIssuanceSessionRepository } from '../repository'
19
+ import { oauth2Error , oauth2UnauthorizedError } from '../util/errors'
20
20
21
21
export function configureCredentialEndpoint ( router : CredoRouter , config : BaseOpenId4VcIssuerModuleConfig ) {
22
22
router . post ( config . credentialEndpointPath , async ( request : OpenId4VcIssuancePostRequest < CredentialRequest > ) => {
@@ -41,21 +41,12 @@ export function configureCredentialEndpoint(router: CredoRouter, config: BaseOpe
41
41
} ,
42
42
} )
43
43
. catch ( ( error : unknown ) => {
44
- throw createHttpError (
45
- 403 ,
46
- error instanceof Oauth2ResourceUnauthorizedError ? error . message : 'Unknown error occured' ,
47
- {
48
- headers : {
49
- 'WWW-Authenticate' :
50
- error instanceof Oauth2ResourceUnauthorizedError
51
- ? error . toHeaderValue ( )
52
- : new Oauth2ResourceUnauthorizedError (
53
- 'No credential configurations match credential request and access token scope' ,
54
- [ { scheme : SupportedAuthenticationScheme . DPoP } , { scheme : SupportedAuthenticationScheme . Bearer } ]
55
- ) . toHeaderValue ( ) ,
56
- } ,
57
- }
58
- )
44
+ throw error instanceof Oauth2ResourceUnauthorizedError
45
+ ? oauth2UnauthorizedError ( error . message , error . wwwAuthenticateHeaders )
46
+ : oauth2UnauthorizedError ( 'Unknown error occured' , [
47
+ { scheme : SupportedAuthenticationScheme . DPoP } ,
48
+ { scheme : SupportedAuthenticationScheme . Bearer } ,
49
+ ] )
59
50
} )
60
51
if ( ! resourceRequestResult ) return
61
52
const { tokenPayload, accessToken, scheme, authorizationServer } = resourceRequestResult
@@ -75,34 +66,25 @@ export function configureCredentialEndpoint(router: CredoRouter, config: BaseOpe
75
66
76
67
const subject = tokenPayload . sub
77
68
if ( ! subject ) {
78
- throw createHttpError (
79
- 400 ,
80
- `Received token without 'sub' claim. Subject is required for binding issuance session` ,
81
- {
82
- type : 'oauth2_error' ,
83
- errorResponse : { error : Oauth2ErrorCodes . ServerError } ,
84
- }
69
+ throw oauth2Error (
70
+ Oauth2ErrorCodes . ServerError ,
71
+ `Received token without 'sub' claim. Subject is required for binding issuance session`
85
72
)
86
73
}
87
74
88
75
// Already handle request without format. Simplifies next code sections
89
76
if ( ! parsedCredentialRequest . format ) {
90
- throw createHttpError (
91
- 400 ,
92
- parsedCredentialRequest . credentialIdentifier
93
- ? `Credential request containing 'credential_identifier' not supported`
94
- : parsedCredentialRequest . credentialConfigurationId
95
- ? `Credential configuration '${ parsedCredentialRequest . credentialConfigurationId } ' not supported`
96
- : `Credential format '${ parsedCredentialRequest . credentialRequest . format } ' not supported` ,
97
- {
98
- type : 'oauth2_error' ,
99
- errorResponse : {
100
- error : parsedCredentialRequest . credentialIdentifier
101
- ? Oauth2ErrorCodes . InvalidCredentialRequest
102
- : Oauth2ErrorCodes . UnsupportedCredentialFormat ,
103
- } ,
104
- }
105
- )
77
+ throw parsedCredentialRequest . credentialIdentifier
78
+ ? oauth2Error (
79
+ Oauth2ErrorCodes . InvalidCredentialRequest ,
80
+ `Credential request containing 'credential_identifier' not supported`
81
+ )
82
+ : oauth2Error (
83
+ Oauth2ErrorCodes . UnsupportedCredentialFormat ,
84
+ parsedCredentialRequest . credentialConfigurationId
85
+ ? `Credential configuration '${ parsedCredentialRequest . credentialConfigurationId } ' not supported`
86
+ : `Credential format '${ parsedCredentialRequest . credentialRequest . format } ' not supported`
87
+ )
106
88
}
107
89
108
90
if ( preAuthorizedCode || issuerState ) {
@@ -124,34 +106,28 @@ export function configureCredentialEndpoint(router: CredoRouter, config: BaseOpe
124
106
}
125
107
)
126
108
127
- throw createHttpError (
128
- 400 ,
129
- `No issuance session found for incoming credential request for issuer ${ issuer . issuerId } and access token data` ,
130
- {
131
- type : 'oauth2_error' ,
132
- errorResponse : { error : Oauth2ErrorCodes . CredentialRequestDenied } ,
133
- }
109
+ throw oauth2Error (
110
+ Oauth2ErrorCodes . CredentialRequestDenied ,
111
+ `No issuance session found for incoming credential request for issuer ${ issuer . issuerId } and access token data`
134
112
)
135
113
}
136
114
137
115
// Use issuance session dpop config
138
116
if ( issuanceSession . dpop ?. required && ! resourceRequestResult . dpop ) {
139
- return createHttpError ( 401 , 'Missing required DPoP proof' , agentContext . config . logger , {
140
- tyoe : 'oauth2_error' ,
141
- errorResponse : { error : Oauth2ErrorCodes . InvalidDpopProof } ,
142
- } )
117
+ return oauth2UnauthorizedError ( 'Missing required DPoP proof' , [
118
+ {
119
+ scheme,
120
+ error : Oauth2ErrorCodes . InvalidDpopProof ,
121
+ } ,
122
+ ] )
143
123
}
144
124
145
125
// Verify the issuance session subject
146
126
if ( issuanceSession . authorization ?. subject ) {
147
127
if ( issuanceSession . authorization . subject !== tokenPayload . sub ) {
148
- throw createHttpError (
149
- 400 ,
150
- `Issuance session authorization subject does not match with the token payload subject for issuance session '${ issuanceSession . id } '. Returning error response` ,
151
- {
152
- type : 'oauth2_error' ,
153
- errorResponse : { error : Oauth2ErrorCodes . CredentialRequestDenied } ,
154
- }
128
+ throw oauth2Error (
129
+ Oauth2ErrorCodes . CredentialRequestDenied ,
130
+ `Issuance session authorization subject does not match with the token payload subject for issuance session '${ issuanceSession . id } '. Returning error response`
155
131
)
156
132
}
157
133
}
@@ -163,10 +139,7 @@ export function configureCredentialEndpoint(router: CredoRouter, config: BaseOpe
163
139
) {
164
140
issuanceSession . errorMessage = 'Credential offer has expired'
165
141
await openId4VcIssuerService . updateState ( agentContext , issuanceSession , OpenId4VcIssuanceSessionState . Error )
166
- throw createHttpError ( 400 , 'Session expired' , {
167
- type : 'oauth2_error' ,
168
- errorResponse : { error : Oauth2ErrorCodes . CredentialRequestDenied } ,
169
- } )
142
+ throw oauth2Error ( Oauth2ErrorCodes . CredentialRequestDenied , 'Session expired' )
170
143
} else {
171
144
issuanceSession . authorization = {
172
145
...issuanceSession . authorization ,
@@ -186,10 +159,12 @@ export function configureCredentialEndpoint(router: CredoRouter, config: BaseOpe
186
159
187
160
// Use global config when creating a dynamic session
188
161
if ( config . dpopRequired && ! resourceRequestResult . dpop ) {
189
- return createHttpError ( 401 , 'Missing required DPoP proof' , agentContext . config . logger , {
190
- tyoe : 'oauth2_error' ,
191
- errorResponse : { error : Oauth2ErrorCodes . InvalidDpopProof } ,
192
- } )
162
+ throw oauth2UnauthorizedError ( 'Missing required DPoP proof' , [
163
+ {
164
+ scheme : scheme ,
165
+ error : Oauth2ErrorCodes . InvalidDpopProof ,
166
+ } ,
167
+ ] )
193
168
}
194
169
195
170
const configurationsForScope = getCredentialConfigurationsSupportedForScopes (
@@ -215,17 +190,12 @@ export function configureCredentialEndpoint(router: CredoRouter, config: BaseOpe
215
190
}
216
191
217
192
if ( Object . keys ( configurationsForToken ) . length === 0 ) {
218
- throw createHttpError ( 403 , 'No credential configurations match credential request and access token scope' , {
219
- headers : {
220
- 'WWW-Authenticate' : new Oauth2ResourceUnauthorizedError (
221
- 'No credential configurationss match credential request and access token scope' ,
222
- {
223
- scheme,
224
- error : Oauth2ErrorCodes . InsufficientScope ,
225
- }
226
- ) . toHeaderValue ( ) ,
193
+ throw oauth2UnauthorizedError ( 'No credential configurations match credential request and access token scope' , [
194
+ {
195
+ scheme,
196
+ error : Oauth2ErrorCodes . InsufficientScope ,
227
197
} ,
228
- } )
198
+ ] )
229
199
}
230
200
231
201
issuanceSession = new OpenId4VcIssuanceSessionRecord ( {
@@ -251,13 +221,9 @@ export function configureCredentialEndpoint(router: CredoRouter, config: BaseOpe
251
221
await issuanceSessionRepository . save ( agentContext , issuanceSession )
252
222
openId4VcIssuerService . emitStateChangedEvent ( agentContext , issuanceSession , null )
253
223
} else if ( ! issuanceSession ) {
254
- throw createHttpError (
255
- 400 ,
256
- `Access token without 'issuer_state' or 'pre-authorized_code' issued by external authorization server provided, but 'allowDynamicIssuanceSessions' is disabled. Either bind the access token to a stateful credential offer, or enable 'allowDynamicIssuanceSessions'.` ,
257
- {
258
- type : 'oauth2_error' ,
259
- errorResponse : { error : Oauth2ErrorCodes . CredentialRequestDenied } ,
260
- }
224
+ throw oauth2Error (
225
+ Oauth2ErrorCodes . CredentialRequestDenied ,
226
+ `Access token without 'issuer_state' or 'pre-authorized_code' issued by external authorization server provided, but 'allowDynamicIssuanceSessions' is disabled. Either bind the access token to a stateful credential offer, or enable 'allowDynamicIssuanceSessions'.`
261
227
)
262
228
}
263
229
0 commit comments