@universis/janitor
Version:
Universis api plugin for handling user authorization and rate limiting
256 lines (239 loc) • 8.85 kB
JavaScript
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
}