UNPKG

@sap/xssec

Version:

XS Advanced Container Security API for node.js

154 lines (132 loc) 5.94 kB
'use strict'; const SecurityContext = require('../context/SecurityContext'); const XsaSecurityContext = require('../context/XsaSecurityContext'); const XsaToken = require('../token/XsaToken'); const Jwk = require('../jwks/Jwk'); const MissingKidError = require('../error/validation/MissingKidError'); const MissingVerificationKeyError = require('../error/validation/MissingVerificationKeyError'); const { getLogger } = require('../util/logging'); const XsuaaService = require('./XsuaaService'); const Token = require('../token/Token'); const LOG = getLogger("XsaService.js"); /** * @typedef {import('../util/Types').ServiceCredentials} ServiceCredentials * @typedef {import('../util/Types').XsaServiceCredentials} XsaServiceCredentials * @typedef {import('../util/Types').ServiceConfig} ServiceConfig * @typedef {import('../util/Types').SecurityContextConfig} SecurityContextConfig * @typedef {import('../util/Types').TokenFetchOptions} TokenFetchOptions * @typedef {import('../util/Types').TokenFetchResponse} TokenFetchResponse * @typedef {import('../util/Types').RefreshableTokenFetchResponse} RefreshableTokenFetchResponse * @typedef {import('../util/Types').GrantType} GrantType */ /** * New SAP BTP applications should start with SAP Identity Services instead of XSA! See README for details.\ * This {@link Service} class is constructed from XSA credentials to provide an API with selected functionality against that XSA service instance, e.g. token validation and token fetches. */ class XsaService extends XsuaaService { /** * @param {ServiceCredentials & XsaServiceCredentials} credentials * @param {ServiceConfig} [serviceConfig={}] */ constructor(credentials, serviceConfig) { super(credentials, serviceConfig); } /** * @override * @param {String|XsaToken} token token as JWT or XsaToken object * @param {SecurityContextConfig} contextConfig * @returns {Promise<XsaSecurityContext>} */ async createSecurityContext(token, contextConfig = {}) { if (typeof token === "string") { token = new XsaToken(token); } else if (token instanceof Token && !(token instanceof XsaToken)) { token = new XsaToken(token.jwt, { header: token.header, payload: token.payload }); } SecurityContext.buildContextConfig(contextConfig); if (contextConfig.skipValidation !== true && this.config.validation.enabled !== false) { await this.validateToken(token, contextConfig); } let ctx = new XsaSecurityContext(this, token, contextConfig); for (let extension of this.contextExtensions) { ctx = await extension.extendSecurityContext(ctx) ?? ctx; } return ctx; } async validateTokenSignature(token, contextConfig) { const pemKeyFromConfig = this.credentials.verificationkey; if (!token.header.jku || !token.header.kid || token.header.kid == 'legacy-token-key') { LOG.info("Token header contained no JKU or KID or the KID was 'legacy-token-key'"); return this.#validateTokenSignatureWithFallback(token, pemKeyFromConfig); } try { await super.validateTokenSignature(token, contextConfig); } catch (error) { if (error instanceof MissingKidError) { LOG.info("JWKS did not contain kid."); return this.#validateTokenSignatureWithFallback(token, pemKeyFromConfig); } else { throw error; } } } #validateTokenSignatureWithFallback(token, pemKeyFromConfig) { if (!pemKeyFromConfig) { throw new MissingVerificationKeyError(); } else { LOG.info("Validating token signature with verificationkey from service configuration."); return Jwk.fromPEM(pemKeyFromConfig).validateSignature(token); } } /** * Fetches a token from this service with this service's client credentials. * @param {TokenFetchOptions} options * @returns {Promise<TokenFetchResponse>} response */ async fetchClientCredentialsToken(options = {}) { return super.fetchClientCredentialsToken(options); } /** * Fetches a user token from this service with the given username and password. * @param {String} username * @param {String} password * @param {TokenFetchOptions} options * @returns {Promise<TokenFetchResponse & RefreshableTokenFetchResponse>} response */ async fetchPasswordToken(username, password, options = {}) { return super.fetchPasswordToken(username, password, options); } /** * Fetches a JWT bearer token from this service with the given user token as assertion. * @param {String} assertion JWT bearer token used as assertion * @param {TokenFetchOptions} options * @returns {Promise<TokenFetchResponse & RefreshableTokenFetchResponse>} response */ async fetchJwtBearerToken(assertion, options = {}) { return super.fetchJwtBearerToken(assertion, options); } /** * Determines the URL that can be used for fetching tokens from this service. * @param {GrantType} grant_type */ async getTokenUrl(grant_type) { let baseUrl; if (this.credentials.certificate) { this.validateCredentials("fetch token via certificate authentication", "certurl"); baseUrl = this.credentials.certurl; } else { this.validateCredentials("fetch token via client secret authentication", "url"); baseUrl = this.credentials.url; } return new URL(this.endpoints.token, baseUrl); } /** * @override * @inheritdoc */ get jwksBaseUrl() { this.validateCredentials("fetch JWKS", "url"); return this.credentials.url; } } module.exports = XsaService;