UNPKG

@sphereon/openid4vci-client

Version:

OpenID for Verifiable Credential Issuance (OpenID4VCI) client

202 lines 18.8 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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AccessTokenClient = void 0; const ssi_types_1 = require("@sphereon/ssi-types"); const debug_1 = __importDefault(require("debug")); const MetadataClient_1 = require("./MetadataClient"); const functions_1 = require("./functions"); const types_1 = require("./types"); const debug = (0, debug_1.default)('sphereon:openid4vci:token'); class AccessTokenClient { acquireAccessTokenUsingIssuanceInitiation({ issuanceInitiation, asOpts, pin, codeVerifier, code, redirectUri, metadata, }) { return __awaiter(this, void 0, void 0, function* () { const { issuanceInitiationRequest } = issuanceInitiation; const isPinRequired = this.isPinRequiredValue(issuanceInitiationRequest); const issuerOpts = { issuer: issuanceInitiationRequest.issuer }; return yield this.acquireAccessTokenUsingRequest({ accessTokenRequest: yield this.createAccessTokenRequest({ issuanceInitiation, asOpts, codeVerifier, code, redirectUri, pin, }), isPinRequired, metadata, asOpts, issuerOpts, }); }); } acquireAccessTokenUsingRequest({ accessTokenRequest, isPinRequired, metadata, asOpts, issuerOpts, }) { return __awaiter(this, void 0, void 0, function* () { this.validate(accessTokenRequest, isPinRequired); const requestTokenURL = AccessTokenClient.determineTokenURL({ asOpts, issuerOpts, metadata: metadata ? metadata : (issuerOpts === null || issuerOpts === void 0 ? void 0 : issuerOpts.fetchMetadata) ? yield MetadataClient_1.MetadataClient.retrieveAllMetadata(issuerOpts.issuer, { errorOnNotFound: false }) : undefined, }); return this.sendAuthCode(requestTokenURL, accessTokenRequest); }); } createAccessTokenRequest({ issuanceInitiation, asOpts, pin, codeVerifier, code, redirectUri, }) { return __awaiter(this, void 0, void 0, function* () { const issuanceInitiationRequest = issuanceInitiation.issuanceInitiationRequest; issuanceInitiationRequest; const request = {}; if (asOpts === null || asOpts === void 0 ? void 0 : asOpts.clientId) { request.client_id = asOpts.clientId; } this.assertNumericPin(this.isPinRequiredValue(issuanceInitiationRequest), pin); request.user_pin = pin; if (issuanceInitiationRequest[types_1.PRE_AUTH_CODE_LITERAL]) { if (codeVerifier) { throw new Error('Cannot pass a code_verifier when flow type is pre-authorized'); } request.grant_type = types_1.GrantTypes.PRE_AUTHORIZED_CODE; request[types_1.PRE_AUTH_CODE_LITERAL] = issuanceInitiationRequest[types_1.PRE_AUTH_CODE_LITERAL]; } if (issuanceInitiationRequest.op_state) { this.throwNotSupportedFlow(); request.grant_type = types_1.GrantTypes.AUTHORIZATION_CODE; } if (codeVerifier) { request.code_verifier = codeVerifier; request.code = code; request.redirect_uri = redirectUri; request.grant_type = types_1.GrantTypes.AUTHORIZATION_CODE; } if (request.grant_type === types_1.GrantTypes.AUTHORIZATION_CODE && issuanceInitiationRequest[types_1.PRE_AUTH_CODE_LITERAL]) { throw Error('A pre_authorized_code flow cannot have an op_state in the initiation request'); } return request; }); } assertPreAuthorizedGrantType(grantType) { if (types_1.GrantTypes.PRE_AUTHORIZED_CODE !== grantType) { throw new Error("grant type must be 'urn:ietf:params:oauth:grant-type:pre-authorized_code'"); } } assertAuthorizationGrantType(grantType) { if (types_1.GrantTypes.AUTHORIZATION_CODE !== grantType) { throw new Error("grant type must be 'authorization_code'"); } } isPinRequiredValue(issuanceInitiationRequest) { let isPinRequired = false; if (issuanceInitiationRequest !== undefined) { if (typeof issuanceInitiationRequest.user_pin_required === 'string') { isPinRequired = issuanceInitiationRequest.user_pin_required.toLowerCase() === 'true'; } else if (typeof issuanceInitiationRequest.user_pin_required === 'boolean') { isPinRequired = issuanceInitiationRequest.user_pin_required; } } debug(`Pin required for issuer ${issuanceInitiationRequest.issuer}: ${isPinRequired}`); return isPinRequired; } assertNumericPin(isPinRequired, pin) { if (isPinRequired) { if (!pin || !/^\d{1,8}$/.test(pin)) { debug(`Pin is not 1 to 8 digits long`); throw new Error('A valid pin consisting of maximal 8 numeric characters must be present.'); } } else if (pin) { debug(`Pin set, whilst not required`); throw new Error('Cannot set a pin, when the pin is not required.'); } } assertNonEmptyPreAuthorizedCode(accessTokenRequest) { if (!accessTokenRequest[types_1.PRE_AUTH_CODE_LITERAL]) { debug(`No pre-authorized code present, whilst it is required`); throw new Error('Pre-authorization must be proven by presenting the pre-authorized code. Code must be present.'); } } assertNonEmptyCodeVerifier(accessTokenRequest) { if (!accessTokenRequest.code_verifier) { debug('No code_verifier present, whilst it is required'); throw new Error('Authorization flow requires the code_verifier to be present'); } } assertNonEmptyCode(accessTokenRequest) { if (!accessTokenRequest.code) { debug('No code present, whilst it is required'); throw new Error('Authorization flow requires the code to be present'); } } assertNonEmptyRedirectUri(accessTokenRequest) { if (!accessTokenRequest.redirect_uri) { debug('No redirect_uri present, whilst it is required'); throw new Error('Authorization flow requires the redirect_uri to be present'); } } validate(accessTokenRequest, isPinRequired) { if (accessTokenRequest.grant_type === types_1.GrantTypes.PRE_AUTHORIZED_CODE) { this.assertPreAuthorizedGrantType(accessTokenRequest.grant_type); this.assertNonEmptyPreAuthorizedCode(accessTokenRequest); this.assertNumericPin(isPinRequired, accessTokenRequest.user_pin); } else if (accessTokenRequest.grant_type === types_1.GrantTypes.AUTHORIZATION_CODE) { this.assertAuthorizationGrantType(accessTokenRequest.grant_type); this.assertNonEmptyCodeVerifier(accessTokenRequest); this.assertNonEmptyCode(accessTokenRequest); this.assertNonEmptyRedirectUri(accessTokenRequest); } else { this.throwNotSupportedFlow; } } sendAuthCode(requestTokenURL, accessTokenRequest) { return __awaiter(this, void 0, void 0, function* () { return yield (0, functions_1.formPost)(requestTokenURL, (0, functions_1.convertJsonToURI)(accessTokenRequest)); }); } static determineTokenURL({ asOpts, issuerOpts, metadata, }) { if (!asOpts && !(metadata === null || metadata === void 0 ? void 0 : metadata.token_endpoint) && !issuerOpts) { throw new Error('Cannot determine token URL if no issuer, metadata and no Authorization Server values are present'); } const url = asOpts && asOpts.as ? this.creatTokenURLFromURL(asOpts.as, asOpts === null || asOpts === void 0 ? void 0 : asOpts.allowInsecureEndpoints, asOpts.tokenEndpoint) : (metadata === null || metadata === void 0 ? void 0 : metadata.token_endpoint) ? metadata.token_endpoint : this.creatTokenURLFromURL(issuerOpts.issuer, asOpts === null || asOpts === void 0 ? void 0 : asOpts.allowInsecureEndpoints, issuerOpts.tokenEndpoint); if (!url || !ssi_types_1.ObjectUtils.isString(url)) { throw new Error('No authorization server token URL present. Cannot acquire access token'); } debug(`Token endpoint determined to be ${url}`); return url; } static creatTokenURLFromURL(url, allowInsecureEndpoints, tokenEndpoint) { if (allowInsecureEndpoints !== true && url.startsWith('http://')) { throw Error(`Unprotected token endpoints are not allowed ${url}`); } const hostname = url.replace(/https?:\/\//, '').replace(/\/$/, ''); const endpoint = tokenEndpoint ? (tokenEndpoint.startsWith('/') ? tokenEndpoint : tokenEndpoint.substring(1)) : '/token'; // We always require https return `https://${hostname}${endpoint}`; } throwNotSupportedFlow() { debug(`Only pre-authorized flow supported.`); throw new Error('Only pre-authorized-code flow is supported'); } } exports.AccessTokenClient = AccessTokenClient; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQWNjZXNzVG9rZW5DbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9saWIvQWNjZXNzVG9rZW5DbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsbURBQWtEO0FBQ2xELGtEQUEwQjtBQUUxQixxREFBa0Q7QUFDbEQsMkNBQXlEO0FBQ3pELG1DQVdpQjtBQUVqQixNQUFNLEtBQUssR0FBRyxJQUFBLGVBQUssRUFBQywyQkFBMkIsQ0FBQyxDQUFDO0FBRWpELE1BQWEsaUJBQWlCO0lBQ2YseUNBQXlDLENBQUMsRUFDckQsa0JBQWtCLEVBQ2xCLE1BQU0sRUFDTixHQUFHLEVBQ0gsWUFBWSxFQUNaLElBQUksRUFDSixXQUFXLEVBQ1gsUUFBUSxHQUNlOztZQUN2QixNQUFNLEVBQUUseUJBQXlCLEVBQUUsR0FBRyxrQkFBa0IsQ0FBQztZQUV6RCxNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsa0JBQWtCLENBQUMseUJBQXlCLENBQUMsQ0FBQztZQUN6RSxNQUFNLFVBQVUsR0FBRyxFQUFFLE1BQU0sRUFBRSx5QkFBeUIsQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUVoRSxPQUFPLE1BQU0sSUFBSSxDQUFDLDhCQUE4QixDQUFDO2dCQUMvQyxrQkFBa0IsRUFBRSxNQUFNLElBQUksQ0FBQyx3QkFBd0IsQ0FBQztvQkFDdEQsa0JBQWtCO29CQUNsQixNQUFNO29CQUNOLFlBQVk7b0JBQ1osSUFBSTtvQkFDSixXQUFXO29CQUNYLEdBQUc7aUJBQ0osQ0FBQztnQkFDRixhQUFhO2dCQUNiLFFBQVE7Z0JBQ1IsTUFBTTtnQkFDTixVQUFVO2FBQ1gsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztLQUFBO0lBRVksOEJBQThCLENBQUMsRUFDMUMsa0JBQWtCLEVBQ2xCLGFBQWEsRUFDYixRQUFRLEVBQ1IsTUFBTSxFQUNOLFVBQVUsR0FPWDs7WUFDQyxJQUFJLENBQUMsUUFBUSxDQUFDLGtCQUFrQixFQUFFLGFB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