UNPKG

@sap-cloud-sdk/connectivity

Version:

SAP Cloud SDK for JavaScript connectivity

226 lines • 8.09 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.verificationKeyCache = exports.defaultTenantId = void 0; exports.userId = userId; exports.getDefaultTenantId = getDefaultTenantId; exports.getTenantId = getTenantId; exports.isIasToken = isIasToken; exports.getSubdomain = getSubdomain; exports.audiences = audiences; exports.decodeJwt = decodeJwt; exports.decodeJwtComplete = decodeJwtComplete; exports.retrieveJwt = retrieveJwt; exports.wrapJwtInHeader = wrapJwtInHeader; exports.isXsuaaToken = isXsuaaToken; exports.getJwtPair = getJwtPair; exports.isUserToken = isUserToken; const util_1 = require("@sap-cloud-sdk/util"); const jsonwebtoken_1 = require("jsonwebtoken"); const cache_1 = require("../cache"); const subdomain_replacer_1 = require("../subdomain-replacer"); const logger = (0, util_1.createLogger)({ package: 'connectivity', messageContext: 'jwt' }); /** * @internal */ exports.defaultTenantId = 'provider-tenant'; function makeArray(val) { return val ? (Array.isArray(val) ? val : [val]) : []; } /** * @internal * Get the user ID from the JWT payload. * For XSUAA tokens, this is `user_id`. * For IAS tokens, this is `user_uuid`. * @param jwtPayload - Token payload to read the user ID from. * @returns The user ID, if available. */ function userId(jwtPayload) { // IAS tokens use user_uuid, XSUAA tokens use user_id const id = jwtPayload.user_id || jwtPayload.user_uuid; logger.debug(`JWT user identifier is: ${id} (from ${jwtPayload.user_id ? 'user_id (XSUAA)' : 'user_uuid (IAS)'}).`); return id; } /** * @internal * Get the default tenant ID. * @returns The default tenant ID. */ function getDefaultTenantId() { logger.debug('Could not determine tenant from JWT nor XSUAA, identity or destination service binding. Client Credentials token is cached without tenant information.'); return exports.defaultTenantId; } /** * Get the tenant ID of a decoded JWT, based on its `zid` or if not available `app_tid` or `zone_uuid` (legacy) property. * @param jwt - Token to read the tenant ID from. * @returns The tenant ID, if available. */ function getTenantId(jwt) { const decodedJwt = jwt ? decodeJwt(jwt) : {}; logger.debug(`JWT zid is: ${decodedJwt.zid}, app_tid is: ${decodedJwt.app_tid}, zone_uuid is: ${decodedJwt.zone_uuid}.`); return (decodedJwt.zid || decodedJwt.app_tid || decodedJwt.zone_uuid || undefined); } /** * Check if the given JWT is an IAS token. * Currently, there are only two domains for IAS tokens: * `accounts.ondemand.com` and `accounts400.ondemand.com`. * @param decodedJwt - The decoded JWT to check. * @returns Whether the given JWT is an IAS token. * @internal */ function isIasToken(decodedJwt) { if (!decodedJwt.iss) { return false; } try { const issUrl = new URL(decodedJwt.iss); const hostname = issUrl.hostname.toLowerCase(); return (hostname.endsWith('.accounts.ondemand.com') || hostname.endsWith('.accounts400.ondemand.com')); } catch { return false; } } /** * @internal * Retrieve the subdomain from the decoded XSUAA JWT or ISS object. * If it is an IAS JWT, or the passed object doesn't contain an ISS propety, * returns `undefined`. * @param jwt - JWT or ISS object to retrieve the subdomain from. * @returns The subdomain, if available. */ function getSubdomain(jwt) { const decodedJwt = jwt ? decodeJwt(jwt) : {}; return (decodedJwt?.ext_attr?.zdn || (isIasToken(decodedJwt) ? undefined : (0, subdomain_replacer_1.getIssuerSubdomain)(decodedJwt))); } /** * @internal * Retrieve the audiences of a decoded JWT based on the audiences and scopes in the token. * @param decodedToken - Token to retrieve the audiences from. * @returns A set of audiences. */ // Comments taken from the Java SDK implementation // Currently, scopes containing dots are allowed. // Since the UAA builds audiences by taking the substring of scopes up to the last dot, // scopes with dots will lead to an incorrect audience which is worked around here. // If a JWT contains no audience, infer audiences based on the scope names in the JWT. // This is currently necessary as the UAA does not correctly fill the audience in the user token flow. function audiences(decodedToken) { const parsedAudiences = audiencesFromAud(decodedToken); return parsedAudiences.length ? parsedAudiences : audiencesFromScope(decodedToken); } function audiencesFromAud({ aud }) { return makeArray(aud).map(audience => audience.split('.')[0]); } function audiencesFromScope({ scope }) { return makeArray(scope).reduce((aud, s) => s.includes('.') ? [...aud, s.split('.')[0]] : aud, []); } /** * Decode JWT. * @param token - JWT to be decoded. * @returns Decoded payload. */ function decodeJwt(token) { return typeof token === 'string' ? decodeJwtComplete(token).payload : token; } /** * Decode JWT and return the complete decoded token. * @param token - JWT to be decoded. * @returns Decoded token containing payload, header and signature. * @internal */ function decodeJwtComplete(token) { const decodedToken = (0, jsonwebtoken_1.decode)(token, { complete: true, json: true }); if (decodedToken !== null && isJwtWithPayloadObject(decodedToken)) { return decodedToken; } throw new Error('JwtError: The given jwt payload does not encode valid JSON.'); } /** * Retrieve JWT from a request that is based on the node `IncomingMessage`. Fails if no authorization header is given or has the wrong format. Expected format is 'Bearer <TOKEN>'. * @param req - Request to retrieve the JWT from. * @returns JWT found in header. */ function retrieveJwt(req) { const authHeader = getAuthHeader(req); if (validateAuthHeader(authHeader)) { return authHeader?.split(' ')[1]; } } function getAuthHeader(req) { const authHeader = (0, util_1.pickValueIgnoreCase)(req.headers, 'authorization'); if (authHeader) { return Array.isArray(authHeader) ? authHeader[0] : authHeader; } } function validateAuthHeader(header) { if (typeof header === 'undefined') { logger.warn('Authorization header not set.'); return false; } const [authType, token] = header.split(' '); if (typeof token === 'undefined') { logger.warn('Token in auth header missing.'); return false; } if (authType.toLowerCase() !== 'bearer') { logger.warn('Authorization type is not Bearer.'); return false; } return true; } /** * 15 minutes is the default value used by the xssec lib. * @internal */ exports.verificationKeyCache = new cache_1.Cache(900000); /** * Wraps the access token in header's authorization. * @param token - Token to attach in request header * @returns The request header that holds the access token * @internal */ function wrapJwtInHeader(token) { return { headers: { Authorization: 'Bearer ' + token } }; } /** * Checks if the given JWT was issued by XSUAA based on the `iss` property and the UAA domain of the XSUAA. * @param decodedJwt - JWT to be checked. * @returns Whether the JWT was issued by XSUAA. * @internal */ function isXsuaaToken(decodedJwt) { return decodedJwt?.ext_attr?.enhancer === 'XSUAA'; } /** * Build JwtPair from an encoded JWT. * @internal */ function getJwtPair(encodedJwt) { return { encoded: encodedJwt, decoded: decodeJwt(encodedJwt) }; } /** * The user JWT can be a full JWT containing user information but also a reduced one setting only the iss value * This method divides the two cases. * @param token - Token to be investigated * @returns Boolean value with true if the input is a UserJwtPair * @internal */ function isUserToken(token) { if (!token) { return false; } // Check if it is an Issuer Payload const keys = Object.keys(token.decoded); return !(keys.length === 1 && keys[0] === 'iss'); } function isJwtWithPayloadObject(decoded) { return typeof decoded.payload !== 'string'; } //# sourceMappingURL=jwt.js.map