@hapic/oauth2
Version:
A oauth2 api client based on axios.
522 lines (503 loc) • 18 kB
JavaScript
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