@sap-cloud-sdk/connectivity
Version:
SAP Cloud SDK for JavaScript connectivity
226 lines • 8.09 kB
JavaScript
;
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