UNPKG

@sphereon/oid4vci-issuer

Version:

OpenID 4 Verifiable Credential Issuance issuer REST endpoints

176 lines • 12.3 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createAccessTokenResponse = exports.assertValidAccessTokenRequest = exports.isValidGrant = exports.generateAccessToken = void 0; const oid4vc_common_1 = require("@sphereon/oid4vc-common"); const oid4vci_common_1 = require("@sphereon/oid4vci-common"); const functions_1 = require("../functions"); const generateAccessToken = (opts) => __awaiter(void 0, void 0, void 0, function* () { const { dPoPJwk, accessTokenIssuer, alg, accessTokenSignerCallback, tokenExpiresIn, preAuthorizedCode, additionalClaims, accessTokenProvider = 'internal', } = opts; // JWT uses seconds for iat and exp if (accessTokenProvider !== 'internal') { throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_request, `Access token provider ${accessTokenProvider} is an external access token provider. We cannot generate tokens ourselves in this case`); } const iat = new Date().getTime() / 1000; const exp = iat + tokenExpiresIn; const cnf = dPoPJwk ? { cnf: { jkt: yield (0, oid4vc_common_1.calculateJwkThumbprint)(dPoPJwk, 'sha256') } } : undefined; const jwt = { header: { typ: 'JWT', alg: alg !== null && alg !== void 0 ? alg : oid4vci_common_1.Alg.ES256 }, payload: Object.assign(Object.assign(Object.assign(Object.assign({ iat, exp, iss: accessTokenIssuer }, cnf), (preAuthorizedCode && { preAuthorizedCode })), { // Protected resources simultaneously supporting both the DPoP and Bearer schemes need to update how the // evaluation process is performed for bearer tokens to prevent downgraded usage of a DPoP-bound access token. // Specifically, such a protected resource MUST reject a DPoP-bound access token received as a bearer token per [RFC6750]. token_type: dPoPJwk ? 'DPoP' : 'Bearer' }), additionalClaims), }; return yield accessTokenSignerCallback(jwt); }); exports.generateAccessToken = generateAccessToken; const isValidGrant = (assertedState, grantType) => { var _a, _b, _c, _d; if ((_b = (_a = assertedState.credentialOffer) === null || _a === void 0 ? void 0 : _a.credential_offer) === null || _b === void 0 ? void 0 : _b.grants) { // TODO implement authorization_code return (Object.keys((_d = (_c = assertedState.credentialOffer) === null || _c === void 0 ? void 0 : _c.credential_offer) === null || _d === void 0 ? void 0 : _d.grants).includes(oid4vci_common_1.GrantTypes.PRE_AUTHORIZED_CODE) && grantType === oid4vci_common_1.GrantTypes.PRE_AUTHORIZED_CODE); } return false; }; exports.isValidGrant = isValidGrant; const assertValidAccessTokenRequest = (request, opts) => __awaiter(void 0, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x; const { credentialOfferSessions, expirationDuration } = opts; // Only pre-auth supported for now if (request.grant_type !== oid4vci_common_1.GrantTypes.PRE_AUTHORIZED_CODE) { throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_grant, oid4vci_common_1.UNSUPPORTED_GRANT_TYPE_ERROR); } // Pre-auth flow if (!request[oid4vci_common_1.PRE_AUTH_CODE_LITERAL]) { throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_request, oid4vci_common_1.PRE_AUTHORIZED_CODE_REQUIRED_ERROR); } const credentialOfferSession = yield credentialOfferSessions.getAsserted(request[oid4vci_common_1.PRE_AUTH_CODE_LITERAL]); credentialOfferSession.status = oid4vci_common_1.IssueStatus.ACCESS_TOKEN_REQUESTED; credentialOfferSession.lastUpdatedAt = +new Date(); yield credentialOfferSessions.set(request[oid4vci_common_1.PRE_AUTH_CODE_LITERAL], credentialOfferSession); if (!(0, exports.isValidGrant)(credentialOfferSession, request.grant_type)) { throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_grant, oid4vci_common_1.UNSUPPORTED_GRANT_TYPE_ERROR); } /* invalid_request: the Authorization Server does not expect a PIN in the pre-authorized flow but the client provides a PIN */ if (!((_c = (_b = (_a = credentialOfferSession.credentialOffer.credential_offer) === null || _a === void 0 ? void 0 : _a.grants) === null || _b === void 0 ? void 0 : _b[oid4vci_common_1.GrantTypes.PRE_AUTHORIZED_CODE]) === null || _c === void 0 ? void 0 : _c.tx_code) && request.tx_code && !request.user_pin) { // >= v13 throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_request, oid4vci_common_1.USER_PIN_NOT_REQUIRED_ERROR); } else if (!((_f = (_e = (_d = credentialOfferSession.credentialOffer.credential_offer) === null || _d === void 0 ? void 0 : _d.grants) === null || _e === void 0 ? void 0 : _e[oid4vci_common_1.GrantTypes.PRE_AUTHORIZED_CODE]) === null || _f === void 0 ? void 0 : _f.user_pin_required) && request.user_pin && !request.tx_code) { // <= v12 throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_request, oid4vci_common_1.USER_PIN_NOT_REQUIRED_ERROR); } /* invalid_request: the Authorization Server expects a PIN in the pre-authorized flow but the client does not provide a PIN */ if ( // >= v13 !!((_j = (_h = (_g = credentialOfferSession.credentialOffer.credential_offer) === null || _g === void 0 ? void 0 : _g.grants) === null || _h === void 0 ? void 0 : _h[oid4vci_common_1.GrantTypes.PRE_AUTHORIZED_CODE]) === null || _j === void 0 ? void 0 : _j.tx_code) && !request.tx_code) { if (request.user_pin) { throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_request, oid4vci_common_1.USER_PIN_TX_CODE_SPEC_ERROR); } throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_request, oid4vci_common_1.USER_PIN_REQUIRED_ERROR); } else if ( // <= v12 ((_m = (_l = (_k = credentialOfferSession.credentialOffer.credential_offer) === null || _k === void 0 ? void 0 : _k.grants) === null || _l === void 0 ? void 0 : _l[oid4vci_common_1.GrantTypes.PRE_AUTHORIZED_CODE]) === null || _m === void 0 ? void 0 : _m.user_pin_required) && !((_q = (_p = (_o = credentialOfferSession.credentialOffer.credential_offer) === null || _o === void 0 ? void 0 : _o.grants) === null || _p === void 0 ? void 0 : _p[oid4vci_common_1.GrantTypes.PRE_AUTHORIZED_CODE]) === null || _q === void 0 ? void 0 : _q.tx_code) && !request.user_pin) { if (request.tx_code) { throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_request, oid4vci_common_1.USER_PIN_TX_CODE_SPEC_ERROR); } throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_request, oid4vci_common_1.USER_PIN_REQUIRED_ERROR); } if ((0, functions_1.isPreAuthorizedCodeExpired)(credentialOfferSession, expirationDuration)) { throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_grant, oid4vci_common_1.EXPIRED_PRE_AUTHORIZED_CODE); } else if (request[oid4vci_common_1.PRE_AUTH_CODE_LITERAL] !== ((_u = (_t = (_s = (_r = credentialOfferSession.credentialOffer) === null || _r === void 0 ? void 0 : _r.credential_offer) === null || _s === void 0 ? void 0 : _s.grants) === null || _t === void 0 ? void 0 : _t[oid4vci_common_1.GrantTypes.PRE_AUTHORIZED_CODE]) === null || _u === void 0 ? void 0 : _u[oid4vci_common_1.PRE_AUTH_CODE_LITERAL])) { throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_grant, oid4vci_common_1.INVALID_PRE_AUTHORIZED_CODE); } /* invalid_grant: the Authorization Server expects a PIN in the pre-authorized flow but the client provides the wrong PIN the End-User provides the wrong Pre-Authorized Code or the Pre-Authorized Code has expired */ if (request.tx_code) { const txCodeOffer = (_x = (_w = (_v = credentialOfferSession.credentialOffer.credential_offer) === null || _v === void 0 ? void 0 : _v.grants) === null || _w === void 0 ? void 0 : _w[oid4vci_common_1.GrantTypes.PRE_AUTHORIZED_CODE]) === null || _x === void 0 ? void 0 : _x.tx_code; if (!txCodeOffer) { throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_request, oid4vci_common_1.USER_PIN_NOT_REQUIRED_ERROR); } else if (txCodeOffer.input_mode === 'text') { if (!RegExp(`[\\D]{${txCodeOffer.length}`).test(request.tx_code)) { throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_grant, `${oid4vci_common_1.PIN_VALIDATION_ERROR} ${txCodeOffer.length}`); } } else { if (!RegExp(`[\\d]{${txCodeOffer.length}}`).test(request.tx_code)) { throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_grant, `${oid4vci_common_1.PIN_VALIDATION_ERROR} ${txCodeOffer.length}`); } } if (request.tx_code !== credentialOfferSession.txCode) { throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_grant, oid4vci_common_1.PIN_NOT_MATCH_ERROR); } } else if (request.user_pin) { if (!/[\\d]{1,8}/.test(request.user_pin)) { throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_grant, `${oid4vci_common_1.PIN_VALIDATION_ERROR} 1-8`); } else if (request.user_pin !== credentialOfferSession.txCode) { throw new oid4vci_common_1.TokenError(400, oid4vci_common_1.TokenErrorResponse.invalid_grant, oid4vci_common_1.PIN_NOT_MATCH_ERROR); } } return { preAuthSession: credentialOfferSession }; }); exports.assertValidAccessTokenRequest = assertValidAccessTokenRequest; const createAccessTokenResponse = (request, opts) => __awaiter(void 0, void 0, void 0, function* () { var _y; const { dPoPJwk, credentialOfferSessions, cNonces, cNonceExpiresIn, tokenExpiresIn, accessTokenIssuer, accessTokenSignerCallback, interval, accessTokenProvider = 'internal', } = opts; // Pre-auth flow const preAuthorizedCode = request[oid4vci_common_1.PRE_AUTH_CODE_LITERAL]; const cNonce = (_y = opts.cNonce) !== null && _y !== void 0 ? _y : (0, oid4vc_common_1.uuidv4)(); yield cNonces.set(cNonce, { cNonce, createdAt: +new Date(), preAuthorizedCode }); const access_token = yield (0, exports.generateAccessToken)({ tokenExpiresIn, accessTokenSignerCallback, preAuthorizedCode, accessTokenIssuer, dPoPJwk, accessTokenProvider, }); const response = { access_token, token_type: dPoPJwk ? 'DPoP' : 'bearer', expires_in: tokenExpiresIn, c_nonce: cNonce, c_nonce_expires_in: cNonceExpiresIn, interval, }; const credentialOfferSession = yield credentialOfferSessions.getAsserted(preAuthorizedCode); credentialOfferSession.status = oid4vci_common_1.IssueStatus.ACCESS_TOKEN_CREATED; credentialOfferSession.lastUpdatedAt = +new Date(); yield credentialOfferSessions.set(preAuthorizedCode, credentialOfferSession); return response; }); exports.createAccessTokenResponse = createAccessTokenResponse; //# sourceMappingURL=index.js.map