UNPKG

@universis/janitor

Version:

Universis api plugin for handling user authorization and rate limiting

256 lines (239 loc) 8.85 kB
import {Request} from 'superagent' import {URL} from 'url'; import {ApplicationService, DataError, HttpError} from '@themost/common'; function responseHander(resolve, reject) { return function (err, response) { if (err) { /** * @type {import('superagent').Response} */ const response = err.response if (response && response.headers['content-type'] === 'application/json') { // get body const clientError = response.body; const error = new HttpError(response.status); return reject(Object.assign(error, { clientError })); } return reject(err); } if (response.status === 204 && response.headers['content-type'] === 'application/json') { return resolve(null); } return resolve(response.body); }; } /** * @class */ class OAuth2ClientService extends ApplicationService { /** * @param {import('@themost/express').ExpressDataApplication} app */ constructor(app) { super(app); /** * @name OAuth2ClientService#settings * @type {{server_uri:string,token_uri?:string}} */ Object.defineProperty(this, 'settings', { writable: false, value: app.getConfiguration().getSourceAt('settings/auth'), enumerable: false, configurable: false }); } /** * Gets keycloak server root * @returns {string} */ getServer() { return this.settings.server_uri; } /** * Gets keycloak server root * @returns {string} */ getAdminRoot() { return this.settings.admin_uri; } // noinspection JSUnusedGlobalSymbols /** * Gets user's profile by calling OAuth2 server profile endpoint * @param {ExpressDataContext} context * @param {string} token */ getUserInfo(token) { return new Promise((resolve, reject) => { const userinfo_uri = this.settings.userinfo_uri ? new URL(this.settings.userinfo_uri, this.getServer()) : new URL('me', this.getServer()); return new Request('GET', userinfo_uri) .set({ 'Authorization': `Bearer ${token}`, 'Accept': 'application/json' }) .query({ 'access_token':token }).end(responseHander(resolve, reject)); }); } // noinspection JSUnusedGlobalSymbols /** * Gets the token info of the current context * @param {ExpressDataContext} context */ getContextTokenInfo(context) { if (context.user == null) { return Promise.reject(new Error('Context user may not be null')); } if (context.user.authenticationType !== 'Bearer') { return Promise.reject(new Error('Invalid context authentication type')); } if (context.user.authenticationToken == null) { return Promise.reject(new Error('Context authentication data may not be null')); } return this.getTokenInfo(context, context.user.authenticationToken); } /** * Gets token info by calling OAuth2 server endpoint * @param {ExpressDataContext} _context * @param {string} token */ getTokenInfo(_context, token) { return new Promise((resolve, reject) => { const introspection_uri = this.settings.introspection_uri ? new URL(this.settings.introspection_uri, this.getServer()) : new URL('tokeninfo', this.getServer()); return new Request('POST', introspection_uri) .auth(this.settings.client_id, this.settings.client_secret) .set('Accept', 'application/json') .type('form') .send({ 'token_type_hint': 'access_token', 'token': token, }).end(responseHander(resolve, reject)); }); } /** * @param {AuthorizeUser} authorizeUser */ authorize(authorizeUser) { const tokenURL = this.settings.token_uri ? new URL(this.settings.token_uri) : new URL('authorize', this.getServer()); return new Promise((resolve, reject)=> { return new Request('POST', tokenURL) .type('form') .send(authorizeUser).end(responseHander(resolve, reject)); }); } /** * Gets a user by name * @param {*} user_id * @param {AdminMethodOptions} options */ getUserById(user_id, options) { return new Promise((resolve, reject) => { return new Request('GET', new URL(`users/${user_id}`, this.getAdminRoot())) .set('Authorization', `Bearer ${options.access_token}`) .end(responseHander(resolve, reject)); }); } /** * Gets a user by name * @param {string} username * @param {AdminMethodOptions} options */ getUser(username, options) { return new Promise((resolve, reject)=> { return new Request('GET', new URL('users', this.getAdminRoot())) .set('Authorization', `Bearer ${options.access_token}`) .query({ '$filter': `name eq '${username}'` }) .end(responseHander(resolve, reject)); }); } /** * Gets a user by email address * @param {string} email * @param {AdminMethodOptions} options */ getUserByEmail(email, options) { return new Promise((resolve, reject)=> { return new Request('GET', new URL('users', this.getAdminRoot())) .set('Authorization', `Bearer ${options.access_token}`) .query({ '$filter': `alternateName eq '${email}'` }) .end(responseHander(resolve, reject)); }); } /** * Updates an existing user * @param {*} user * @param {AdminMethodOptions} options */ updateUser(user, options) { return new Promise((resolve, reject)=> { if (user.id == null) { return reject(new DataError('E_IDENTIFIER', 'User may not be empty at this context.', null, 'User', 'id')); } const request = new Request('PUT', new URL(`users/${user.id}`, this.getAdminRoot())); return request.set('Authorization', `Bearer ${options.access_token}`) .set('Content-Type', 'application/json') .send(user) .end(responseHander(resolve, reject)); }); } /** * Creates a new user * @param {*} user * @param {AdminMethodOptions} options */ createUser(user, options) { return new Promise((resolve, reject)=> { const request = new Request('POST', new URL('users', this.getAdminRoot())); return request.set('Authorization', `Bearer ${options.access_token}`) .set('Content-Type', 'application/json') .send(Object.assign({}, user, { $state: 1 // for create })) .end(responseHander(resolve, reject)); }); } /** * Deletes a user * @param {{id: any}} user * @param {AdminMethodOptions} options */ deleteUser(user, options) { return new Promise((resolve, reject)=> { if (user.id == null) { return reject(new DataError('E_IDENTIFIER', 'User may not be empty at this context.', null, 'User', 'id')); } const request = new Request('DELETE', new URL(`users/${user.id}`, this.getAdminRoot())); return request.set('Authorization', `Bearer ${options.access_token}`) .end(responseHander(resolve, reject)); }); } /** * @param {boolean=} force * @returns {*} */ getWellKnownConfiguration(force) { if (force) { this.well_known_configuration = null; } if (this.well_known_configuration) { return Promise.resolve(this.well_known_configuration); } return new Promise((resolve, reject) => { const well_known_configuration_uri = this.settings.well_known_configuration_uri ? new URL(this.settings.well_known_configuration_uri, this.getServer()) : new URL('.well-known/openid-configuration', this.getServer()); return new Request('GET', well_known_configuration_uri) .end(responseHander(resolve, reject)); }).then((configuration) => { this.well_known_configuration = configuration; return configuration; }); } } export { OAuth2ClientService }