UNPKG

@hapic/oauth2

Version:

A oauth2 api client based on axios.

522 lines (503 loc) 18 kB
import { createClient as createClient$1, isClient as isClient$1, stringifyAuthorizationHeader, isObject, HeaderName, Client, hasOwnProperty as hasOwnProperty$1, verifyInstanceBySymbol } from 'hapic'; export { isClientError, isClientErrorDueNetworkIssue, isClientErrorWithStatusCode } from 'hapic'; /* * Copyright (c) 2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ var AuthorizeResponseMode = /*#__PURE__*/ function(AuthorizeResponseMode) { AuthorizeResponseMode["QUERY"] = "query"; AuthorizeResponseMode["FRAGMENT"] = "fragment"; AuthorizeResponseMode["FORM_POST"] = "form_post"; AuthorizeResponseMode["WEB_MESSAGE"] = "web_message"; return AuthorizeResponseMode; }({}); var AuthorizeCodeChallengeMethod = /*#__PURE__*/ function(AuthorizeCodeChallengeMethod) { AuthorizeCodeChallengeMethod["SHA_256"] = "S256"; AuthorizeCodeChallengeMethod["PLAIN"] = "plain"; return AuthorizeCodeChallengeMethod; }({}); var AuthorizeResponseType = /*#__PURE__*/ function(AuthorizeResponseType) { AuthorizeResponseType["NONE"] = "none"; AuthorizeResponseType["CODE"] = "code"; AuthorizeResponseType["TOKEN"] = "token"; AuthorizeResponseType["ID_TOKEN"] = "id_token"; return AuthorizeResponseType; }({}); /* * Copyright (c) 2021. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ // eslint-disable-next-line @typescript-eslint/ban-types function hasOwnProperty(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } /* * Copyright (c) 2023. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ function scopeToArray(scope) { return Array.isArray(scope) ? scope : scope.split(' '); } function scopeToString(scope) { if (typeof scope === 'string') { return scope; } return [ ...new Set(scope) ].join(' '); } function buildAuthorizeURL(url, parameters) { const output = new URL(url); if (parameters.response_type) { output.searchParams.set('response_type', Array.isArray(parameters.response_type) ? scopeToString(parameters.response_type) : parameters.response_type); } else { output.searchParams.set('response_type', 'code'); } if (parameters.client_id) { output.searchParams.set('client_id', parameters.client_id); } if (parameters.redirect_uri) { output.searchParams.set('redirect_uri', parameters.redirect_uri); } if (parameters.response_mode) { output.searchParams.set('response_mode', parameters.response_mode); } if (parameters.scope) { const scope = Array.isArray(parameters.scope) ? scopeToString(parameters.scope) : parameters.scope; output.searchParams.set('scope', scope); } if (parameters.state) { output.searchParams.set('state', parameters.state); } if (parameters.code_challenge) { output.searchParams.set('code_challenge', parameters.code_challenge); } if (parameters.code_challenge_method) { output.searchParams.set('code_challenge_method', parameters.code_challenge_method); } return output.href; } class BaseAPI { // ----------------------------------------------------------------------------------- setClient(input) { this.client = isClient$1(input) ? input : createClient$1(input); } setOptions(options) { this.options = options || {}; } setOption(key, value) { this.options[key] = value; } // ----------------------------------------------------------------------------------- constructor(context = {}){ this.setClient(context.client); this.setOptions(context.options); } } class AuthorizeAPI extends BaseAPI { /** * Build authorize url based on * input parameters. * * @param parameters */ buildURL(parameters = {}) { let baseURL; if (this.options.authorizationEndpoint) { baseURL = this.options.authorizationEndpoint; } else { const clientURL = this.client.defaults.baseURL; baseURL = new URL('authorize', clientURL).href; } return buildAuthorizeURL(baseURL, { ...parameters, redirect_uri: parameters.redirect_uri || this.options.redirectUri, client_id: parameters.client_id || this.options.clientId, scope: parameters.scope || this.options.scope, response_type: parameters.response_type || 'code' }); } } /** * Set Content-Type and Authorization Header for request. * * @param headers * @param options */ function transformHeadersForTokenAPIRequest(headers, options) { options = options || {}; // set content type headers.set('Content-Type', 'application/x-www-form-urlencoded'); if (options.authorizationHeaderInherit && headers.has('Authorization')) { return; } // request should be in general unauthorized headers.delete('Authorization'); if (options.authorizationHeader) { const header = typeof options.authorizationHeader === 'string' ? options.authorizationHeader : stringifyAuthorizationHeader(options.authorizationHeader); headers.set('Authorization', header); return; } if (options.clientCredentialsAsHeader && options.clientId && options.clientSecret) { headers.set('Authorization', stringifyAuthorizationHeader({ type: 'Basic', username: options.clientId, password: options.clientSecret })); } } function createRequestTransformerForTokenAPIRequest(parameters, options) { return (data, headers)=>{ options = options || {}; if (!options.clientId) { options.clientId = parameters.client_id; options.clientSecret = parameters.client_secret; } transformHeadersForTokenAPIRequest(headers, options); if (options.clientCredentialsAsHeader) { if (data instanceof URLSearchParams) { data.delete('client_id'); data.delete('client_secret'); return data; } if (isObject(data)) { if (typeof data.client_id !== 'undefined') { delete data.client_id; } if (typeof data.client_secret !== 'undefined') { delete data.client_secret; } } } return data; }; } class TokenAPI extends BaseAPI { // ------------------------------------------------------------------ async createWithRefreshToken(parameters, options) { return this.create({ grant_type: 'refresh_token', ...parameters }, options); } async createWithClientCredentials(parameters, options) { return this.create({ grant_type: 'client_credentials', ...parameters || {} }, options); } async createWithPassword(parameters, options) { return this.create({ grant_type: 'password', ...parameters }, options); } async createWithAuthorizationCode(parameters, options) { return this.create({ grant_type: 'authorization_code', ...parameters }, options); } async createWithRobotCredentials(parameters, options) { return this.create({ grant_type: 'robot_credentials', ...parameters }, options); } // ------------------------------------------------------------------ /** * @throws Error * @param parameters * @param options */ async create(parameters, options = {}) { this.extendCreateParameters(parameters); const urlSearchParams = this.buildURLSearchParams(parameters); const { data } = await this.client.post(this.options.tokenEndpoint || '/token', urlSearchParams, { transform: this.buildRequestTransformers(parameters, options), headers: { [HeaderName.ACCEPT]: 'application/json' } }); const tokenResponse = { access_token: data.access_token, expires_in: data.expires_in, token_type: data.token_type || 'Bearer' }; if (data.refresh_token) { tokenResponse.refresh_token = data.refresh_token; } if (typeof data.id_token === 'string') { tokenResponse.id_token = data.id_token; } if (typeof data.mac_key === 'string') { tokenResponse.mac_key = data.mac_key; } if (typeof data.mac_algorithm === 'string') { tokenResponse.mac_algorithm = data.mac_algorithm; } return tokenResponse; } async revoke(parameters = {}, options = {}) { const urlSearchParams = this.buildURLSearchParams(parameters); return this.client.post(this.options.revocationEndpoint || '/token/revoke', urlSearchParams, { transform: this.buildRequestTransformers(parameters, options), headers: { [HeaderName.ACCEPT]: 'application/json' } }); } async introspect(parameters = {}, options = {}) { const urlSearchParams = this.buildURLSearchParams(parameters); const { data } = await this.client.post(this.options.introspectionEndpoint || '/token/introspect', urlSearchParams, { transform: this.buildRequestTransformers(parameters, options), headers: { [HeaderName.ACCEPT]: 'application/json' } }); return data; } buildRequestTransformers(parameters, options = {}) { const transformers = []; if (!options.clientId) { if (this.options.clientId) { options.clientId = this.options.clientId; } if (this.options.clientSecret) { options.clientSecret = this.options.clientSecret; } } transformers.push(createRequestTransformerForTokenAPIRequest(parameters, options)); if (this.client.defaults.transform) { if (Array.isArray(this.client.defaults.transform)) { transformers.push(...this.client.defaults.transform); } else { transformers.push(this.client.defaults.transform); } } return transformers; } // ------------------------------------------------------------------ extendCreateParameters(parameters) { if (parameters.grant_type !== 'authorization_code' && parameters.grant_type !== 'robot_credentials') { if (!parameters.scope && this.options.scope) { parameters.scope = this.options.scope; } } if (parameters.grant_type === 'authorization_code') { if (!parameters.redirect_uri && this.options.redirectUri) { parameters.redirect_uri = this.options.redirectUri; } } if (!parameters.client_id) { if (parameters.client_secret) { delete parameters.client_secret; } if (this.options.clientId) { parameters.client_id = this.options.clientId; } if (this.options.clientSecret) { parameters.client_secret = this.options.clientSecret; } } return parameters; } // ------------------------------------------------------------------ buildURLSearchParams(input) { const urlSearchParams = new URLSearchParams(); const keys = Object.keys(input); for(let i = 0; i < keys.length; i++){ const value = input[keys[i]]; if (typeof value === 'string' && !!value) { urlSearchParams.append(keys[i], value); } else if (Array.isArray(value)) { const str = value.filter((el)=>el).join(' '); if (str) { urlSearchParams.append(keys[i], str); } } } return urlSearchParams; } } class UserInfoAPI extends BaseAPI { // ----------------------------------------------------------------------------------- /** * @throws Error * @param header */ async get(header) { const headers = { [HeaderName.ACCEPT]: 'application/json' }; if (header) { if (typeof header === 'string') { if (header.indexOf(' ') === -1) { headers.Authorization = `Bearer ${header}`; } else { headers.Authorization = header; } } else { headers.Authorization = stringifyAuthorizationHeader(header); } } const { data } = await this.client.get(this.options.userinfoEndpoint || '/userinfo', { headers }); return data; } // eslint-disable-next-line no-useless-constructor,@typescript-eslint/no-useless-constructor constructor(context){ super(context); } } class OAuth2Client extends Client { // ----------------------------------------------------------------------------------- constructor(input){ input = input || {}; super(input.request), this['@instanceof'] = Symbol.for('OAuth2Client'); const options = input.options || {}; this.options = options; this.token = new TokenAPI({ client: this, options }); this.authorize = new AuthorizeAPI({ client: this, options }); this.userInfo = new UserInfoAPI({ client: this, options }); } } const instanceMap = {}; /** * Verify if an oauth2 client singleton instance exists. * * @param key */ function hasClient(key) { return hasOwnProperty$1(instanceMap, key || 'default'); } /** * Set the oauth2 client singleton instance. * * @param client * @param key */ function setClient(client, key) { key = key || 'default'; instanceMap[key] = client; return client; } /** * Receive an oauth2 singleton instance. * * @param key */ function useClient(key) { key = key || 'default'; if (Object.prototype.hasOwnProperty.call(instanceMap, key)) { return instanceMap[key]; } const instance = createClient(); instanceMap[key] = instance; return instance; } /** * Unset an oauth2 client singleton instance. * * @param key */ function unsetClient(key) { key = key || 'default'; if (hasOwnProperty$1(instanceMap, key)) { delete instanceMap[key]; } } /** * Create an oauth2 client. * * @param input */ function createClient(input) { return new OAuth2Client(input); } /** * Check if the argument is of instance Client. * * @param input */ function isClient(input) { if (input instanceof OAuth2Client) { return true; } return verifyInstanceBySymbol(input, 'OAuth2Client'); } /* * Copyright (c) 2023. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ var ResponseType = /*#__PURE__*/ function(ResponseType) { ResponseType["NONE"] = "none"; ResponseType["CODE"] = "code"; ResponseType["ID_TOKEN"] = "id_token"; ResponseType["TOKEN"] = "token"; return ResponseType; }({}); /* * Copyright (c) 2023. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ function parseOpenIDProviderMetadata(data) { const options = {}; if (data.authorization_endpoint) { options.authorizationEndpoint = data.authorization_endpoint; } if (data.introspection_endpoint) { options.introspectionEndpoint = data.introspection_endpoint; } if (data.token_endpoint) { options.tokenEndpoint = data.token_endpoint; } if (data.userinfo_endpoint) { options.userinfoEndpoint = data.userinfo_endpoint; } if (data.revocation_endpoint) { options.revocationEndpoint = data.revocation_endpoint; } return options; } /** * Create a client instance by requesting details of * the open-id discovery endpoint. * * @param url (.e.g. .well-known/openid-configuration) * @param options */ async function createClientWithOpenIDDiscoveryURL(url, options) { let client; if (isClient$1(options)) { client = options; } else { client = createClient$1(options); } const { data } = await client.get(url); return new OAuth2Client({ request: client.defaults, options: parseOpenIDProviderMetadata(data) }); } /* * Copyright (c) 2023. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ function buildOpenIDDiscoveryURL(baseURL) { let url = '.well-known/openid-configuration'; if (baseURL) { url = new URL(url, baseURL).href; } else { url = `/${url}`; } return url; } const client = createClient(); export { AuthorizeAPI, AuthorizeCodeChallengeMethod, AuthorizeResponseMode, AuthorizeResponseType, OAuth2Client, ResponseType, TokenAPI, UserInfoAPI, buildAuthorizeURL, buildOpenIDDiscoveryURL, createClient, createClientWithOpenIDDiscoveryURL, client as default, hasClient, hasOwnProperty, isClient, parseOpenIDProviderMetadata, scopeToArray, scopeToString, setClient, unsetClient, useClient }; //# sourceMappingURL=index.mjs.map