• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FIWARE / VCVerifier / 22715655698

05 Mar 2026 11:24AM UTC coverage: 50.151% (+6.1%) from 44.081%
22715655698

Pull #81

github

web-flow
Merge branch 'main' into trustbloc
Pull Request #81: Remove trustbloc libraries

860 of 1397 new or added lines in 23 files covered. (61.56%)

11 existing lines in 2 files now uncovered.

2488 of 4961 relevant lines covered (50.15%)

0.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

49.74
/openapi/api_api.go
1
/*
2
 * vcverifier
3
 *
4
 * Backend component to verify credentials
5
 *
6
 * API version: 0.0.1
7
 * Generated by: OpenAPI Generator (https://openapi-generator.tech)
8
 */
9

10
package openapi
11

12
import (
13
        "encoding/base64"
14
        "encoding/json"
15
        "fmt"
16
        "net/http"
17
        "slices"
18
        "strings"
19

20
        "github.com/fiware/VCVerifier/common"
21
        "github.com/fiware/VCVerifier/did"
22
        "github.com/fiware/VCVerifier/logging"
23
        "github.com/fiware/VCVerifier/verifier"
24
        "github.com/google/uuid"
25
        "github.com/lestrrat-go/jwx/v3/jwa"
26
        "github.com/lestrrat-go/jwx/v3/jwt"
27

28
        "github.com/gin-gonic/gin"
29
)
30

31
const DEEPLINK = "DEEPLINK"
32
const FRONTEND_V1 = "FRONTEND_V1"
33
const FRONTEND_V2 = "FRONTEND_V2"
34

35
var apiVerifier verifier.Verifier
36
var presentationParser verifier.PresentationParser
37
var sdJwtParser verifier.SdJwtParser
38
var keyResolver verifier.KeyResolver
39

40
var ErrorMessagNoGrantType = ErrorMessage{"no_grant_type_provided", "Token requests require a grant_type."}
41
var ErrorMessageUnsupportedGrantType = ErrorMessage{"unsupported_grant_type", "Provided grant_type is not supported by the implementation."}
42
var ErrorMessageNoCode = ErrorMessage{"no_code_provided", "Token requests require a code."}
43
var ErrorMessageNoRedircetUri = ErrorMessage{"no_redirect_uri_provided", "Token requests require a redirect_uri."}
44
var ErrorMessageNoResource = ErrorMessage{"no_resource_provided", "When using token-exchange, resource is required to provide the client_id."}
45
var ErrorMessageNoState = ErrorMessage{"no_state_provided", "Authentication requires a state provided as query parameter."}
46
var ErrorMessageNoScope = ErrorMessage{"no_scope_provided", "Authentication requires a scope provided as a parameter."}
47
var ErrorMessageInvalidResponseType = ErrorMessage{"invalid_response_type", "Authentication requires the response_type to be `code`."}
48
var ErrorMessageFailedSameDevice = ErrorMessage{"failed_same_device", "Was not able to start a same-device flow."}
49
var ErrorMessageNoClientId = ErrorMessage{"no_client_id_provided", "Authentication requires a client-id provided as a parameter."}
50
var ErrorMessageNoNonce = ErrorMessage{"no_nonce_provided", "Authentication requires a nonce provided as a query parameter."}
51
var ErrorMessageNoToken = ErrorMessage{"no_token_provided", "Authentication requires a token provided as a form parameter."}
52
var ErrorMessageNoPresentationSubmission = ErrorMessage{"no_presentation_submission_provided", "Authentication requires a presentation submission provided as a form parameter."}
53
var ErrorMessageNoCallback = ErrorMessage{"NoCallbackProvided", "A callback address has to be provided as query-parameter."}
54
var ErrorMessageUnableToDecodeToken = ErrorMessage{"invalid_token", "Token could not be decoded."}
55
var ErrorMessageUnableToDecodeCredential = ErrorMessage{"invalid_token", "Could not read the credential(s) inside the token."}
56
var ErrorMessageUnableToDecodeHolder = ErrorMessage{"invalid_token", "Could not read the holder inside the token."}
57
var ErrorMessageNoSuchSession = ErrorMessage{"no_session", "Session with the requested id is not available."}
58
var ErrorMessageInvalidSdJwt = ErrorMessage{"invalid_sdjwt", "SdJwt does not contain all required fields."}
59
var ErrorMessageNoWebsocketConnection = ErrorMessage{"invalid_connection", "No Websocket connection available for the authenticated session."}
60
var ErrorMessageUnresolvableRequestObject = ErrorMessage{"unresolvable_request_object", "Was not able to get the request object from the client."}
61
var ErrorMessageInvalidAudience = ErrorMessage{"invalid_audience", "Audience of the request object was invalid."}
62
var ErrorMessageUnsupportedAssertionType = ErrorMessage{"unsupported_assertion_type", "Assertion type is not supported."}
63
var ErrorMessageInvalidClientAssertion = ErrorMessage{"invalid_client_assertion", "Provided client assertion is invalid."}
64
var ErrorMessageInvalidTokenRequest = ErrorMessage{"invalid_token_request", "Token request has no redirect_uri and no valid client assertion."}
65
var ErrorMessageInvalidSubjectTokenType = ErrorMessage{"invalid_subject_token_type", "Token exchange is only supported for token type urn:eu:oidf:vp_token."}
66
var ErrorMessageInvalidRequestedTokenType = ErrorMessage{"invalid_requested_token_type", "Token exchange is only supported for requesting tokens of type urn:ietf:params:oauth:token-type:access_token."}
67

68
func getApiVerifier() verifier.Verifier {
1✔
69
        if apiVerifier == nil {
1✔
70
                apiVerifier = verifier.GetVerifier()
×
71
        }
×
72
        return apiVerifier
1✔
73
}
74

75
func getPresentationParser() verifier.PresentationParser {
1✔
76
        if presentationParser == nil {
1✔
77
                presentationParser = verifier.GetPresentationParser()
×
78
        }
×
79
        return presentationParser
1✔
80
}
81

82
func getSdJwtParser() verifier.SdJwtParser {
1✔
83
        if sdJwtParser == nil {
1✔
84
                sdJwtParser = verifier.GetSdJwtParser()
×
85
        }
×
86
        return sdJwtParser
1✔
87
}
88

89
func getKeyResolver() verifier.KeyResolver {
×
90
        if keyResolver == nil {
×
NEW
91
                keyResolver = &verifier.VdrKeyResolver{Vdr: []did.VDR{did.NewKeyVDR(), did.NewJWKVDR(), did.NewWebVDR()}}
×
92
        }
×
93
        return keyResolver
×
94
}
95

96
// GetToken - Token endpoint to exchange the authorization code with the actual JWT.
97
func GetToken(c *gin.Context) {
1✔
98

1✔
99
        logging.Log().Debugf("%v", c.Request)
1✔
100
        grantType, grantTypeExists := c.GetPostForm("grant_type")
1✔
101
        if !grantTypeExists {
2✔
102
                logging.Log().Debug("No grant_type present in the request.")
1✔
103
                c.AbortWithStatusJSON(400, ErrorMessagNoGrantType)
1✔
104
                return
1✔
105
        }
1✔
106

107
        switch grantType {
1✔
108
        case common.TYPE_CODE:
1✔
109
                handleTokenTypeCode(c)
1✔
110
        case common.TYPE_VP_TOKEN:
1✔
111
                handleTokenTypeVPToken(c, c.GetHeader("client_id"))
1✔
112
        case common.TYPE_TOKEN_EXCHANGE:
1✔
113
                resource, resourceExists := c.GetPostForm("resource")
1✔
114
                if !resourceExists {
2✔
115
                        c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoResource)
1✔
116
                        return
1✔
117
                }
1✔
118
                handleTokenTypeTokenExchange(c, resource)
1✔
119
        default:
1✔
120
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageUnsupportedGrantType)
1✔
121
        }
122
}
123

124
func AuthorizationEndpoint(c *gin.Context) {
×
125
        logging.Log().Debug("Receive authorization request.")
×
126
        clientId, clientIdExists := c.GetQuery("client_id")
×
127
        responseType, responseTypeExists := c.GetQuery("response_type")
×
128
        scope, scopeExists := c.GetQuery("scope")
×
129
        redirectUri, redirectUriExists := c.GetQuery("redirect_uri")
×
130
        nonce, nonceExists := c.GetQuery("nonce")
×
131
        state, stateExists := c.GetQuery("state")
×
132

×
133
        requestUri, requestUriExists := c.GetQuery("request_uri")
×
134
        if requestUriExists {
×
135
                logging.Log().Debug("Requesting the client for its request object.")
×
136
                cro, err := getRequestObjectClient().GetClientRequestObject(requestUri)
×
137
                if err != nil {
×
138
                        logging.Log().Warnf("Was not able to get request object. Err: %v", err)
×
139
                        c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessageUnresolvableRequestObject)
×
140
                        return
×
141
                }
×
142
                if !slices.Contains(cro.Aud, getFrontendVerifier().GetHost()) {
×
143
                        c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessageInvalidAudience)
×
144
                        return
×
145
                }
×
146

147
                if cro.ClientId != "" {
×
148
                        clientId = cro.ClientId
×
149
                        clientIdExists = true
×
150
                }
×
151

152
                if cro.Scope != "" {
×
153
                        scope = cro.Scope
×
154
                        scopeExists = true
×
155
                }
×
156

157
                if cro.RedirectUri != "" {
×
158
                        redirectUri = cro.RedirectUri
×
159
                        redirectUriExists = true
×
160
                }
×
161

162
                if cro.Nonce != "" {
×
163
                        nonce = cro.Nonce
×
164
                        nonceExists = true
×
165
                }
×
166

167
                if cro.State != "" {
×
168
                        state = cro.State
×
169
                        stateExists = true
×
170
                }
×
171

172
                if cro.ResponseType != "" {
×
173
                        responseType = cro.ResponseType
×
174
                        responseTypeExists = true
×
175
                }
×
176
        }
177

178
        if !clientIdExists {
×
179
                logging.Log().Info("Received an authorization request without a client_id.")
×
180
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoClientId)
×
181
                return
×
182
        }
×
183
        if !scopeExists {
×
184
                logging.Log().Info("Received an authorization request without a scope.")
×
185
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoScope)
×
186
                return
×
187
        }
×
188
        if !redirectUriExists {
×
189
                logging.Log().Info("Received an authorization request without a redirect_uri.")
×
190
                //c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoRedircetUri)
×
191
                //return
×
192
        }
×
193
        if !nonceExists {
×
194
                logging.Log().Info("Received an authorization request without a nonce.")
×
195
                nonce = uuid.NewString()
×
196
        }
×
197
        if !stateExists {
×
198
                logging.Log().Info("Received an authorization request without a state.")
×
199
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoState)
×
200
                return
×
201
        }
×
202
        if !responseTypeExists && responseType != "code" {
×
203
                logging.Log().Infof("Received an authorization request with an invalid response type. Was %s.", responseType)
×
204
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidResponseType)
×
205
                return
×
206
        }
×
207

208
        logging.Log().Debugf("Received request: clientID - %s, scope - %s, redirect_uri - %s, nonce - %s, state - %s.", clientId, scope, redirectUri, nonce, state)
×
209

×
210
        protocol := "https"
×
211
        if c.Request.TLS == nil {
×
212
                protocol = "http"
×
213
        }
×
214

215
        authorizationType := getApiVerifier().GetAuthorizationType(clientId)
×
216
        var redirect string
×
217
        var err error
×
218
        switch authorizationType {
×
219
        case DEEPLINK:
×
220
                redirect, err = getApiVerifier().StartSameDeviceFlow(c.Request.Host, protocol, state, "", clientId, nonce, verifier.REQUEST_MODE_BY_REFERENCE, scope, verifier.OPENID4VP_PROTOCOL)
×
221
                if err != nil {
×
222
                        logging.Log().Warnf("Was not able start a same device flow. Err: %v", err)
×
223
                        c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessageFailedSameDevice)
×
224
                        return
×
225
                }
×
226
        case FRONTEND_V2:
×
227
                redirect = buildFrontendV2Address(protocol, c.Request.Host, state, clientId, redirectUri, scope, nonce)
×
228
        }
229
        c.Redirect(http.StatusFound, redirect)
×
230
}
231

232
func buildFrontendV2Address(protocol, host, state, clientId, redirectUri, scope, nonce string) string {
×
233
        logging.Log().Debugf("%s://%s/api/v2/loginQR?state=%s&client_id=%s&redirect_uri=%s&scope=%s&nonce=%s&request_mode=byReference", protocol, host, state, clientId, redirectUri, scope, nonce)
×
234
        return fmt.Sprintf("%s://%s/api/v2/loginQR?state=%s&client_id=%s&redirect_uri=%s&scope=%s&nonce=%s&request_mode=byReference", protocol, host, state, clientId, redirectUri, scope, nonce)
×
235
}
×
236

237
// GetToken - Token endpoint to exchange the authorization code with the actual JWT.
238
func GetTokenForService(c *gin.Context) {
×
239

×
240
        logging.Log().Debugf("%v", c.Request)
×
241
        grantType, grantTypeExists := c.GetPostForm("grant_type")
×
242
        if !grantTypeExists {
×
243
                logging.Log().Debug("No grant_type present in the request.")
×
244
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessagNoGrantType)
×
245
                return
×
246
        }
×
247

248
        switch grantType {
×
249
        case common.TYPE_CODE:
×
250
                handleTokenTypeCode(c)
×
251
        case common.TYPE_VP_TOKEN:
×
252
                handleTokenTypeVPToken(c, c.Param("service_id"))
×
253
        case common.TYPE_TOKEN_EXCHANGE:
×
254
                handleTokenTypeTokenExchange(c, c.Param("service_id"))
×
255
        default:
×
256
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageUnsupportedGrantType)
×
257
        }
258
}
259

260
func handleTokenTypeTokenExchange(c *gin.Context, clientId string) {
1✔
261
        subjectTokenType, subjectTokenTypeExists := c.GetPostForm("subject_token_type")
1✔
262
        if !subjectTokenTypeExists || subjectTokenType != common.TYPE_VP_TOKEN_SUBJECT {
2✔
263
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidSubjectTokenType)
1✔
264
                return
1✔
265
        }
1✔
266
        requestedTokenType, requestedTokenTypeExists := c.GetPostForm("requested_token_type")
1✔
267
        if requestedTokenTypeExists && requestedTokenType != common.TYPE_ACCESS_TOKEN {
2✔
268
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidRequestedTokenType)
1✔
269
                return
1✔
270
        }
1✔
271

272
        scopes := getScopesFromRequest(c, clientId)
1✔
273

1✔
274
        audience, audienceExists := c.GetPostForm("audience")
1✔
275
        if !audienceExists {
2✔
276
                audience = clientId
1✔
277
        }
1✔
278

279
        subjectToken, subjectTokenExists := c.GetPostForm("subject_token")
1✔
280
        if !subjectTokenExists {
2✔
281
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoToken)
1✔
282
                return
1✔
283
        }
1✔
284

285
        logging.Log().Debugf("Got token %s", subjectToken)
1✔
286

1✔
287
        verifiyVPToken(c, subjectToken, clientId, scopes, audience)
1✔
288
}
289

290
func handleTokenTypeVPToken(c *gin.Context, clientId string) {
1✔
291

1✔
292
        vpToken, vpTokenExists := c.GetPostForm("vp_token")
1✔
293
        if !vpTokenExists {
2✔
294
                logging.Log().Debug("No vp token present in the request.")
1✔
295
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoToken)
1✔
296
                return
1✔
297
        }
1✔
298

299
        logging.Log().Warnf("Got token %s", vpToken)
1✔
300

1✔
301
        scopes := getScopesFromRequest(c, clientId)
1✔
302
        if len(scopes) == 0 {
1✔
303
                return
×
304
        }
×
305

306
        verifiyVPToken(c, vpToken, clientId, scopes, clientId)
1✔
307
}
308

309
func verifiyVPToken(c *gin.Context, vpToken string, clientId string, scopes []string, audience string) {
1✔
310

1✔
311
        presentation, err := extractVpFromToken(c, vpToken)
1✔
312
        if err != nil {
1✔
313
                logging.Log().Warnf("Was not able to extract the credentials from the vp_token. E: %v", err)
×
314
                return
×
315
        }
×
316

317
        logging.Log().Debug("Was able to extract presentation")
1✔
318
        // Subject is empty since multiple VCs with different subjects can be provided
1✔
319
        expiration, signedToken, err := getApiVerifier().GenerateToken(clientId, "", clientId, scopes, presentation)
1✔
320
        if err != nil {
1✔
321
                logging.Log().Error("Failure during generating M2M token: ", err)
×
322
                c.AbortWithStatusJSON(http.StatusBadRequest, err)
×
323
                return
×
324
        }
×
325
        response := TokenResponse{TokenType: "Bearer", IssuedTokenType: common.TYPE_ACCESS_TOKEN, ExpiresIn: float32(expiration), AccessToken: signedToken, Scope: strings.Join(scopes, ",")}
1✔
326
        logging.Log().Infof("Generated and signed token: %v", response)
1✔
327
        c.JSON(http.StatusOK, response)
1✔
328
}
329

330
func handleTokenTypeCode(c *gin.Context) {
1✔
331

1✔
332
        code, codeExists := c.GetPostForm("code")
1✔
333
        if !codeExists {
2✔
334
                logging.Log().Debug("No code present in the request.")
1✔
335
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoCode)
1✔
336
                return
1✔
337
        }
1✔
338

339
        assertionType, assertionTypeExists := c.GetPostForm("client_assertion_type")
1✔
340
        redirectUri, redirectUriExists := c.GetPostForm("redirect_uri")
1✔
341
        if redirectUriExists {
2✔
342
                jwt, expiration, err := getApiVerifier().GetToken(code, redirectUri, false)
1✔
343
                if err != nil {
2✔
344
                        c.AbortWithStatusJSON(http.StatusForbidden, ErrorMessage{Summary: err.Error()})
1✔
345
                        return
1✔
346
                }
1✔
347
                c.JSON(http.StatusOK, TokenResponse{TokenType: "Bearer", ExpiresIn: float32(expiration), AccessToken: jwt})
1✔
348
                return
1✔
349
        }
350
        if assertionTypeExists {
1✔
351
                handleWithClientAssertion(c, assertionType, code)
×
352
                return
×
353
        }
×
354
        c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidTokenRequest)
1✔
355
}
356

357
func getScopesFromRequest(c *gin.Context, clientId string) (scopes []string) {
1✔
358

1✔
359
        scope, scopeExists := c.GetPostForm("scope")
1✔
360
        if !scopeExists {
2✔
361
                defaultScope, err := getApiVerifier().GetDefaultScope(clientId)
1✔
362
                if err != nil {
1✔
363
                        c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoScope)
×
364
                        return scopes
×
365
                }
×
366
                logging.Log().Debugf("No scope present in the request, use the default scope %s", defaultScope)
1✔
367
                return []string{defaultScope}
1✔
368
        }
369

370
        return strings.Split(scope, ",")
1✔
371

372
}
373

374
func handleWithClientAssertion(c *gin.Context, assertionType string, code string) {
×
375
        if assertionType != "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" {
×
376
                logging.Log().Warnf("Assertion type %s is not supported.", assertionType)
×
377
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageUnsupportedAssertionType)
×
378
                return
×
379
        }
×
380

381
        clientAssertion, clientAssertionExists := c.GetPostForm("client_assertion")
×
382
        clientId, clientIdExists := c.GetPostForm("client_id")
×
383
        if !clientAssertionExists || !clientIdExists {
×
384
                logging.Log().Warnf("Client Id (%s) or assertion (%s) not provided.", clientId, clientAssertion)
×
385
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageUnsupportedAssertionType)
×
386
                return
×
387
        }
×
388

389
        kid, err := getKeyResolver().ExtractKIDFromJWT(clientAssertion)
×
390
        if err != nil {
×
391
                logging.Log().Warnf("Was not able to retrive kid from token %s. Err: %v.", clientAssertion, err)
×
392
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidClientAssertion)
×
393
                return
×
394
        }
×
395
        pubKey, err := getKeyResolver().ResolvePublicKeyFromDID(kid)
×
396
        if err != nil {
×
397
                logging.Log().Warnf("Was not able to retrive key from kid %s. Err: %v.", kid, err)
×
398
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidClientAssertion)
×
399
                return
×
400
        }
×
401

402
        alg, algExists := pubKey.Algorithm()
×
403
        if !algExists {
×
404
                // fallback to default
×
405
                alg = jwa.ES256()
×
406
        }
×
407

408
        parsed, err := jwt.Parse([]byte(clientAssertion), jwt.WithKey(alg, pubKey))
×
409
        if err != nil {
×
410
                logging.Log().Warnf("Was not able to parse and verify the token %s. Err: %v", clientAssertion, err)
×
411
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidClientAssertion)
×
412
                return
×
413
        }
×
414

415
        // Serialize token to JSON
416
        jsonBytes, err := json.Marshal(parsed)
×
417
        if err != nil {
×
418
                logging.Log().Warnf("Was not able to marshal the token %s. Err: %v", clientAssertion, err)
×
419
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidClientAssertion)
×
420
                return
×
421
        }
×
422

423
        // Unmarshal to your struct
424
        var clientAssertionObject ClientAssertion
×
425
        if err := json.Unmarshal(jsonBytes, &clientAssertionObject); err != nil {
×
426
                logging.Log().Warnf("Was not able to unmarshal the token: %s, Err: %v", string(jsonBytes), err)
×
427
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidClientAssertion)
×
428
                return
×
429
        }
×
430

431
        if clientAssertionObject.Sub != clientId || clientAssertionObject.Iss != clientId || !slices.Contains(clientAssertionObject.Aud, getFrontendVerifier().GetHost()) {
×
432
                logging.Log().Warnf("Invalid assertion: %s. Client Id: %s, Host: %s", logging.PrettyPrintObject(clientAssertionObject), clientId, getApiVerifier().GetHost())
×
433
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidClientAssertion)
×
434
                return
×
435
        }
×
436

437
        jwt, expiration, err := getApiVerifier().GetToken(code, "", true)
×
438
        if err != nil {
×
439
                c.AbortWithStatusJSON(http.StatusForbidden, ErrorMessage{Summary: err.Error()})
×
440
                return
×
441
        }
×
442
        c.JSON(http.StatusOK, TokenResponse{TokenType: "Bearer", ExpiresIn: float32(expiration), AccessToken: jwt})
×
443
}
444

445
// StartSIOPSameDevice - Starts the siop flow for credentials hold by the same device
446
func StartSIOPSameDevice(c *gin.Context) {
1✔
447
        state, stateExists := c.GetQuery("state")
1✔
448
        if !stateExists {
2✔
449
                logging.Log().Debugf("No state was provided.")
1✔
450
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessage{"no_state_provided", "Authentication requires a state provided as query parameter."})
1✔
451
                return
1✔
452
        }
1✔
453
        redirectPath, redirectPathExists := c.GetQuery("redirect_path")
1✔
454
        requestProtocol := verifier.REDIRECT_PROTOCOL
1✔
455
        if !redirectPathExists {
2✔
456
                requestProtocol = verifier.OPENID4VP_PROTOCOL
1✔
457
        }
1✔
458

459
        protocol := "https"
1✔
460
        if c.Request.TLS == nil {
2✔
461
                protocol = "http"
1✔
462
        }
1✔
463

464
        clientId, clientIdExists := c.GetQuery("client_id")
1✔
465
        logging.Log().Debugf("The client id %s", clientId)
1✔
466
        if !clientIdExists {
2✔
467
                logging.Log().Infof("Start a login flow for a not specified client.")
1✔
468
        }
1✔
469

470
        requestMode, requestModeExists := c.GetQuery("request_mode")
1✔
471
        if !requestModeExists {
2✔
472
                logging.Log().Infof("Using default request mode %s.", DEFAULT_REQUEST_MODE)
1✔
473
                requestMode = DEFAULT_REQUEST_MODE
1✔
474
        }
1✔
475

476
        scope, scopeExists := c.GetQuery("scope")
1✔
477
        if !scopeExists {
2✔
478
                logging.Log().Infof("Start a login flow with default scope.")
1✔
479
                scope = ""
1✔
480
        }
1✔
481

482
        authenticationRequest, err := getApiVerifier().StartSameDeviceFlow(c.Request.Host, protocol, state, redirectPath, clientId, "", requestMode, scope, requestProtocol)
1✔
483
        if err != nil {
2✔
484
                logging.Log().Warnf("Error starting the same-device flow. Err: %v", err)
1✔
485
                c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessage{err.Error(), "Was not able to start the same device flow."})
1✔
486
                return
1✔
487
        }
1✔
488

489
        c.Redirect(http.StatusFound, authenticationRequest)
1✔
490
}
491

492
// VerifierAPIAuthenticationResponse - Stores the credential for the given session
493
func VerifierAPIAuthenticationResponse(c *gin.Context) {
1✔
494

1✔
495
        var state string
1✔
496
        stateForm, stateFormExists := c.GetPostForm("state")
1✔
497
        stateQuery, stateQueryExists := c.GetQuery("state")
1✔
498
        if !stateFormExists && !stateQueryExists {
2✔
499
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoState)
1✔
500
                return
1✔
501
        }
1✔
502
        if stateFormExists {
2✔
503
                state = stateForm
1✔
504
        } else {
1✔
505
                // allow the state submitted through a query parameter for backwards-compatibility
×
506
                state = stateQuery
×
507
        }
×
508

509
        vptoken, tokenExists := c.GetPostForm("vp_token")
1✔
510
        if !tokenExists {
2✔
511
                logging.Log().Info("No token was provided.")
1✔
512
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoToken)
1✔
513
                return
1✔
514
        }
1✔
515

516
        presentation, err := extractVpFromToken(c, vptoken)
1✔
517
        if err != nil {
1✔
UNCOV
518
                logging.Log().Warnf("Was not able to extract the presentation from the vp_token.")
×
UNCOV
519
                return
×
UNCOV
520
        }
×
521
        handleAuthenticationResponse(c, state, presentation)
1✔
522
}
523

524
// GetVerifierAPIAuthenticationResponse - Stores the credential for the given session
525
func GetVerifierAPIAuthenticationResponse(c *gin.Context) {
×
526
        state, stateExists := c.GetQuery("state")
×
527
        if !stateExists {
×
528
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoState)
×
529
                return
×
530
        }
×
531

532
        vpToken, tokenExists := c.GetQuery("vp_token")
×
533
        if !tokenExists {
×
534
                logging.Log().Info("No token was provided.")
×
535
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoToken)
×
536
                return
×
537
        }
×
538
        presentation, err := extractVpFromToken(c, vpToken)
×
539
        if err != nil {
×
540
                logging.Log().Warnf("Was not able to extract the presentation from the vp_token.")
×
541
                return
×
542
        }
×
543
        handleAuthenticationResponse(c, state, presentation)
×
544
}
545

546
// GetRequestByReference - Get the request object by reference
547
func GetRequestByReference(c *gin.Context) {
×
548
        sessionId := c.Param("id")
×
549

×
550
        jwt, err := verifier.GetVerifier().GetRequestObject(sessionId)
×
551
        if err != nil {
×
552
                logging.Log().Debugf("No request for  %s. Err: %v", sessionId, err)
×
553
                c.AbortWithStatusJSON(http.StatusNotFound, ErrorMessageNoSuchSession)
×
554
                return
×
555
        }
×
556
        c.String(http.StatusOK, jwt)
×
557
}
558

559
func extractVpFromToken(c *gin.Context, vpToken string) (parsedPresentation *common.Presentation, err error) {
1✔
560

1✔
561
        logging.Log().Debugf("The token %s.", vpToken)
1✔
562

1✔
563
        parsedPresentation, err = getPresentationFromQuery(c, vpToken)
1✔
564

1✔
565
        if err != nil {
1✔
566
                logging.Log().Debugf("Received a vpToken with a query, but was not able to extract the presentation. Token: %s", vpToken)
×
567
                return parsedPresentation, err
×
568
        }
×
569
        if parsedPresentation != nil {
1✔
570
                return parsedPresentation, err
×
571
        }
×
572
        return tokenToPresentation(c, vpToken)
1✔
573

574
}
575

576
func tokenToPresentation(c *gin.Context, vpToken string) (parsedPresentation *common.Presentation, err error) {
1✔
577
        tokenBytes := decodeVpString(vpToken)
1✔
578

1✔
579
        isSdJWT, parsedPresentation, err := isSdJWT(c, vpToken)
1✔
580
        if isSdJWT && err != nil {
1✔
581
                return
×
582
        }
×
583
        if isSdJWT {
2✔
584
                logging.Log().Debugf("Received an sdJwt: %s", logging.PrettyPrintObject(parsedPresentation))
1✔
585
                return
1✔
586
        }
1✔
587

588
        parsedPresentation, err = getSdJwtParser().ParseWithSdJwt(tokenBytes)
1✔
589
        if err == nil {
1✔
590
                logging.Log().Debug("Parsed presentation containing sd-jwt's.")
×
591
                return parsedPresentation, err
×
592
        }
×
593
        if err == verifier.ErrorInvalidProof {
1✔
594
                logging.Log().Infof("Was not able to parse the token %s. Err: %v", vpToken, err)
×
595
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageUnableToDecodeToken)
×
596
                return
×
597
        }
×
598
        logging.Log().Debugf("Parse without SD-Jwt %v", err)
1✔
599

1✔
600
        logging.Log().Debug("Parse presentation.")
1✔
601

1✔
602
        parsedPresentation, err = getPresentationParser().ParsePresentation(tokenBytes)
1✔
603

1✔
604
        if err != nil {
1✔
UNCOV
605
                logging.Log().Infof("Was not able to parse the token %s. Err: %v", vpToken, err)
×
UNCOV
606
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageUnableToDecodeToken)
×
UNCOV
607
                return
×
UNCOV
608
        }
×
609

610
        return
1✔
611
}
612

613
func getPresentationFromQuery(c *gin.Context, vpToken string) (parsedPresentation *common.Presentation, err error) {
1✔
614
        tokenBytes := decodeVpString(vpToken)
1✔
615

1✔
616
        var queryMap map[string]string
1✔
617
        //unmarshal
1✔
618
        err = json.Unmarshal(tokenBytes, &queryMap)
1✔
619
        if err != nil {
2✔
620
                logging.Log().Debug("VP Token does not contain query map. Checking the other options.", err)
1✔
621
                return nil, nil
1✔
622
        }
1✔
623

624
        for _, v := range queryMap {
×
625
                p, err := tokenToPresentation(c, v)
×
626
                if err != nil {
×
627
                        return nil, err
×
628
                }
×
629
                if parsedPresentation == nil {
×
630
                        parsedPresentation = p
×
631
                } else {
×
632
                        parsedPresentation.AddCredentials(p.Credentials()...)
×
633
                }
×
634
        }
635
        return parsedPresentation, err
×
636
}
637

638
// checks if the presented token contains a single sd-jwt credential. Will be repackage to a presentation for further validation
639
func isSdJWT(c *gin.Context, vpToken string) (isSdJwt bool, presentation *common.Presentation, err error) {
1✔
640
        claims, err := getSdJwtParser().Parse(vpToken)
1✔
641
        if err != nil {
2✔
642
                logging.Log().Debugf("Was not a sdjwt. Err: %v", err)
1✔
643
                return false, presentation, err
1✔
644
        }
1✔
645
        issuer, i_ok := claims[common.JWTClaimIss]
1✔
646
        vct, vct_ok := claims[common.JWTClaimVct]
1✔
647
        if !i_ok || !vct_ok {
2✔
648
                // Not an SD-JWT VC (missing iss or vct) — let other parsers handle it
1✔
649
                logging.Log().Debugf("Token does not contain issuer(%v) or vct(%v), not an SD-JWT VC.", i_ok, vct_ok)
1✔
650
                return false, presentation, nil
1✔
651
        }
1✔
652
        customFields := common.CustomFields{}
1✔
653
        for k, v := range claims {
2✔
654
                if k != common.JWTClaimIss && k != common.JWTClaimVct {
2✔
655
                        customFields[k] = v
1✔
656
                }
1✔
657
        }
658
        subject := common.Subject{CustomFields: customFields}
1✔
659
        contents := common.CredentialContents{Issuer: &common.Issuer{ID: issuer.(string)}, Types: []string{vct.(string)}, Subject: []common.Subject{subject}}
1✔
660
        credential, err := common.CreateCredential(contents, common.CustomFields{})
1✔
661
        if err != nil {
1✔
662
                logging.Log().Infof("Was not able to create credential from sdJwt. E: %v", err)
×
663
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidSdJwt)
×
664
                return true, presentation, err
×
665
        }
×
666
        presentation, err = common.NewPresentation()
1✔
667
        if err != nil {
1✔
668
                logging.Log().Infof("Was not able to create credpresentation from sdJwt. E: %v", err)
×
669
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageInvalidSdJwt)
×
670
                return true, presentation, err
×
671
        }
×
672
        presentation.AddCredentials(credential)
1✔
673
        presentation.Holder = issuer.(string)
1✔
674
        return true, presentation, nil
1✔
675
}
676

677
// decodeVpString - In newer versions of OID4VP the token is not encoded as a whole but only its segments separately. This function covers the older and newer versions
678
func decodeVpString(vpToken string) (tokenBytes []byte) {
1✔
679
        tokenBytes, err := base64.RawURLEncoding.DecodeString(vpToken)
1✔
680
        if err != nil {
2✔
681
                return []byte(vpToken)
1✔
682
        }
1✔
683
        return tokenBytes
1✔
684
}
685

686
func handleAuthenticationResponse(c *gin.Context, state string, presentation *common.Presentation) {
1✔
687

1✔
688
        response, err := getApiVerifier().AuthenticationResponse(state, presentation)
1✔
689
        if err != nil {
2✔
690
                logging.Log().Warnf("Was not able to fullfil the authentication response. Err: %v", err)
1✔
691
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessage{Summary: err.Error()})
1✔
692
                return
1✔
693
        }
1✔
694
        if response != (verifier.Response{}) && response.FlowVersion == verifier.SAME_DEVICE {
2✔
695
                if response.Nonce != "" {
1✔
696
                        c.Redirect(302, fmt.Sprintf("%s?state=%s&code=%s&nonce=%s", response.RedirectTarget, response.SessionId, response.Code, response.Nonce))
×
697
                } else {
1✔
698
                        c.Redirect(302, fmt.Sprintf("%s?state=%s&code=%s", response.RedirectTarget, response.SessionId, response.Code))
1✔
699
                }
1✔
700
                return
1✔
701
        } else if response != (verifier.Response{}) && response.FlowVersion == verifier.CROSS_DEVICE_V2 {
×
702
                sendRedirect(c, response.SessionId, response.Code, response.RedirectTarget)
×
703
        }
×
704
        logging.Log().Debugf("Successfully authenticated %s.", state)
×
705
        c.JSON(http.StatusOK, gin.H{})
×
706
}
707

708
// VerifierAPIJWKS - Provides the public keys for the given verifier, to be used for verifing the JWTs
709
func VerifierAPIJWKS(c *gin.Context) {
×
710
        c.JSON(http.StatusOK, getApiVerifier().GetJWKS())
×
711
}
×
712

713
// VerifierAPIOpenID
714
func VerifierAPIOpenIDConfiguration(c *gin.Context) {
×
715

×
716
        metadata, err := getApiVerifier().GetOpenIDConfiguration(c.Param("service_id"))
×
717
        if err != nil {
×
718
                c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessage{err.Error(), "Was not able to generate the OpenID metadata."})
×
719
                return
×
720
        }
×
721
        c.JSON(http.StatusOK, metadata)
×
722
}
723

724
// VerifierAPIStartSIOP - Initiates the siop flow and returns the 'openid://...' connection string
725
func VerifierAPIStartSIOP(c *gin.Context) {
1✔
726
        state, stateExists := c.GetQuery("state")
1✔
727
        if !stateExists {
2✔
728
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoState)
1✔
729
                // early exit
1✔
730
                return
1✔
731
        }
1✔
732

733
        callback, callbackExists := c.GetQuery("client_callback")
1✔
734
        if !callbackExists {
2✔
735
                c.AbortWithStatusJSON(http.StatusBadRequest, ErrorMessageNoCallback)
1✔
736
                // early exit
1✔
737
                return
1✔
738
        }
1✔
739
        protocol := "https"
1✔
740
        if c.Request.TLS == nil {
2✔
741
                protocol = "http"
1✔
742
        }
1✔
743
        clientId, clientIdExists := c.GetQuery("client_id")
1✔
744
        if !clientIdExists {
2✔
745
                logging.Log().Infof("Start a login flow for a not specified client.")
1✔
746
        }
1✔
747

748
        requestMode, requestModeExists := c.GetQuery("request_mode")
1✔
749
        if !requestModeExists {
2✔
750
                logging.Log().Infof("Using default request mode %s.", DEFAULT_REQUEST_MODE)
1✔
751
                requestMode = DEFAULT_REQUEST_MODE
1✔
752
        }
1✔
753

754
        connectionString, err := getApiVerifier().StartSiopFlow(c.Request.Host, protocol, callback, state, clientId, "", requestMode)
1✔
755
        if err != nil {
2✔
756
                c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorMessage{err.Error(), "Was not able to generate the connection string."})
1✔
757
                return
1✔
758
        }
1✔
759
        c.String(http.StatusOK, connectionString)
1✔
760
}
761

762
type ClientAssertion struct {
763
        Iss string   `json:"iss"`
764
        Aud []string `json:"aud"`
765
        Sub string   `json:"sub"`
766
        Exp int      `json:"exp"`
767
        Iat int      `json:"iat"`
768
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc