@universis/janitor
Version:
Universis api plugin for handling user authorization and rate limiting
385 lines (342 loc) • 14 kB
TypeScript
import * as _themost_common from '@themost/common';
import { ApplicationService, ApplicationBase, ConfigurationStrategy, ConfigurationBase, HttpForbiddenError, HttpError } from '@themost/common';
import { Router, Application, Handler, Request as Request$1 } from 'express';
import { Store, Options } from 'express-rate-limit';
import { BehaviorSubject } from 'rxjs';
import { Store as Store$1, Options as Options$1 } from 'express-slow-down';
import RedisStore from 'rate-limit-redis';
import { DataContext } from '@themost/data';
import BearerStrategy from 'passport-http-bearer';
import { Authenticator } from 'passport';
declare type RateLimitStoreConstructor = new (...arg: unknown[]) => Store;
declare interface RateLimitServiceConfiguration {
extends?: string;
storeType?: string;
profiles?: [string, Options][];
paths?: [string, ({ profile: string } | Options)][];
}
declare class RateLimitService extends ApplicationService {
constructor(app: _themost_common.ApplicationBase);
readonly loaded: BehaviorSubject<Router>;
getServiceContainer(): BehaviorSubject<Router | Application>;
getServiceName(): string;
getServiceConfiguration(): RateLimitServiceConfiguration;
set(path: string, options: { profile?: string } | Options): this;
unset(path: string): this;
}
declare type SpeedLimitStoreConstructor = new (...arg: unknown[]) => Store$1;
declare interface SpeedLimitServiceConfiguration {
extends?: string;
storeType?: string;
profiles?: Map<string, Options$1>;
paths?: Map<string, ({ profile: string } | Options$1)>;
}
declare class SpeedLimitService extends ApplicationService {
constructor(app: ApplicationBase);
getServiceContainer(): BehaviorSubject<Router | Application>;
getServiceName(): string;
getServiceConfiguration(): SpeedLimitServiceConfiguration;
set(path: string, options: { profile?: string } | Options$1): this;
unset(path: string): this;
}
declare class RedisClientStore extends RedisStore {
constructor(app: ApplicationService);
}
/**
* @license
* Universis Project Version 1.0
* Copyright (c) 2018, Universis Project All rights reserved
*
* Use of this source code is governed by an LGPL 3.0 license that can be
* found in the LICENSE file at https://universis.io/license
*/
declare class ScopeString {
constructor(str: string);
split(): string[];
}
declare interface UniversisConfigurationSection {
universis: {
[k: string]: any;
}
}
declare interface ScopeAccessConfigurationSection {
janitor: {
scopeAccess: {
imports: string[]
}
}
}
/**
* Declares a configuration element for managing scope-based permissions on server resources
*/
declare interface ScopeAccessConfigurationElement {
/**
* Gets or sets an array of strings that holds an array of scopes e.g. students or students:read or students,teachers etc
*/
scope: string[],
/**
* Gets or sets a string which represents the regular expression that is going to be used for validating endpoint
*/
resource: string;
/**
* Gets or sets an array of strings which represents the access levels for the given scopes e.g. READ or READ,WRITE etc
*/
access: string[];
/**
* Gets or sets a string which represents a short description for this item
*/
description?: string;
}
declare class ScopeAccessConfiguration extends ConfigurationStrategy {
constructor(configuration: ConfigurationBase);
/**
* Gets an array of scope access configuration elements
*/
public elements: ScopeAccessConfigurationElement[];
/**
* Verifies the given request and returns a promise that resolves with a scope access configuration element
*/
verify(req: Request): Promise<ScopeAccessConfigurationElement>;
}
declare class DefaultScopeAccessConfiguration extends ScopeAccessConfiguration {
constructor(configuration: ConfigurationBase);
/**
* Gets an array of scope access configuration elements
*/
public elements: ScopeAccessConfigurationElement[];
/**
* Verifies the given request and returns a promise that resolves with a scope access configuration element
*/
verify(req: Request): Promise<ScopeAccessConfigurationElement>;
}
declare class EnableScopeAccessConfiguration extends ApplicationService {
constructor(app: ApplicationBase);
}
declare class ExtendScopeAccessConfiguration extends ApplicationService {
constructor(app: ApplicationBase);
}
declare function validateScope(): Handler;
declare interface OAuth2MethodOptions {
access_token: string;
}
declare interface OAuth2AuthorizeUser {
client_id?: string;
client_secret?: string;
username: string;
password: string;
grant_type: string;
scope?: string;
}
declare interface OAuth2ServiceSettings {
unattendedExecutionAccount?: string;
client_id: string;
client_secret?: string;
server_uri: string;
userinfo_uri?: string;
introspect_uri?: string;
admin_uri?: string;
well_known_configuration_uri?: string;
adminAccount: {
username: string;
password: string;
client_id: string;
client_secret?: string;
scope?: string;
}
}
declare interface OAuth2UserProfile {
sub: string;
name: string;
preferred_username: string;
given_name: string;
family_name: string;
email: string;
}
declare interface GenericUser {
id?: any;
additionalType?: string;
alternateName?: string;
description?: string;
givenName?: string;
familyName?: string;
image?: string;
name?: string;
url?: string;
dateCreated?: Date;
dateModified?: Date;
createdBy?: any;
modifiedBy?: any;
lockoutTime?: Date;
logonCount?: number;
enabled?: boolean;
lastLogon?: Date;
userCredentials?: {
userPassword?: string;
userActivated?: boolean;
temporary?: boolean;
}
}
declare interface OAuth2User {
id?: any;
username?: string;
email?: string;
enabled?: boolean;
emailVerified?: boolean;
firstName?: string;
lastName?: string;
credentials?: {
algorithm?: string,
temporary?: boolean,
type?: string,
value?: string
}
}
declare class OAuth2ClientService extends ApplicationService {
get settings(): OAuth2ServiceSettings;
constructor(app: ApplicationBase)
getUserInfo(context: DataContext, token: string): Promise<OAuth2UserProfile>;
getTokenInfo(context: DataContext, token: string): Promise<any>;
getContextTokenInfo(context: DataContext): Promise<any>;
authorize(authorizeUser: OAuth2AuthorizeUser): Promise<{ access_token?: string, refresh_token?: string}>;
getUser(username: string, options: OAuth2MethodOptions): Promise<any>;
getUserById(user_id: any, options: OAuth2MethodOptions): Promise<any>;
getUserByEmail(email: string, options: OAuth2MethodOptions): Promise<any>;
updateUser(user: GenericUser | any, options: OAuth2MethodOptions): Promise<any>;
createUser(user: GenericUser | any, options: OAuth2MethodOptions): Promise<any>;
deleteUser(user: { id: any }, options: OAuth2MethodOptions): Promise<any>;
}
declare class RemoteAddressValidator extends ApplicationService {
constructor(app: ApplicationService);
validateRemoteAddress(request: Request$1): Promise<boolean>;
getRemoteAddress(request: Request$1): string;
}
declare class AppRateLimitService extends RateLimitService {
constructor(app: _themost_common.ApplicationBase);
}
declare class AppSpeedLimitService extends SpeedLimitService {
constructor(app: _themost_common.ApplicationBase);
}
declare class HttpBearerTokenRequired extends HttpError {
constructor() {
super(499, 'A token is required to fulfill the request.');
this.code = 'E_TOKEN_REQUIRED';
this.title = 'Token Required';
}
}
declare class HttpBearerTokenNotFound extends HttpError {
constructor() {
super(498, 'Token was not found.');
this.code = 'E_TOKEN_NOT_FOUND';
this.title = 'Invalid token';
}
}
declare class HttpBearerTokenExpired extends HttpError {
constructor() {
super(498, 'Token was expired or is in invalid state.');
this.code = 'E_TOKEN_EXPIRED';
this.title = 'Invalid token';
}
}
declare class HttpAccountDisabled extends HttpForbiddenError {
constructor() {
super('Access is denied. User account is disabled.');
this.code = 'E_ACCOUNT_DISABLED';
this.statusCode = 403.2;
this.title = 'Disabled account';
}
}
declare class HttpBearerStrategy extends BearerStrategy {
constructor() {
super({
passReqToCallback: true
},
/**
* @param {Request} req
* @param {string} token
* @param {Function} done
*/
function(req, token, done) {
/**
* Gets OAuth2 client services
* @type {import('./OAuth2ClientService').OAuth2ClientService}
*/
let client = req.context.getApplication().getStrategy(function OAuth2ClientService() {});
// if client cannot be found
if (client == null) {
// throw configuration error
return done(new Error('Invalid application configuration. OAuth2 client service cannot be found.'))
}
if (token == null) {
// throw 499 Token Required error
return done(new HttpBearerTokenRequired());
}
// get token info
client.getTokenInfo(req.context, token).then(info => {
if (info == null) {
// the specified token cannot be found - 498 invalid token with specific code
return done(new HttpBearerTokenNotFound());
}
// if the given token is not active throw token expired - 498 invalid token with specific code
if (!info.active) {
return done(new HttpBearerTokenExpired());
}
// find user from token info
return (function () {
/**
* @type {import('./services/user-provisioning-mapper-service').UserProvisioningMapperService}
*/
const mapper = req.context.getApplication().getService(function UserProvisioningMapperService() {});
if (mapper == null) {
return req.context.model('User').where('name').equal(info.username).silent().getItem();
}
return mapper.getUser(req.context, info);
})().then((user) => {
// check if userProvisioning service is installed and try to find related user only if user not found
if (user == null) {
/**
* @type {import('./services/user-provisioning-service').UserProvisioningService}
*/
const service = req.context.getApplication().getService(function UserProvisioningService() {});
if (service == null) {
return user;
}
return service.validateUser(req.context, info);
}
return user;
}).then( user => {
// user cannot be found and of course cannot be authenticated (throw forbidden error)
if (user == null) {
// write access log for forbidden
return done(new HttpForbiddenError());
}
// check if user has enabled attribute
if (Object.prototype.hasOwnProperty.call(user, 'enabled') && !user.enabled) {
//if user.enabled is off throw forbidden error
return done(new HttpAccountDisabled('Access is denied. User account is disabled.'));
}
// otherwise return user data
return done(null, {
'name': user.name,
'authenticationProviderKey': user.id,
'authenticationType':'Bearer',
'authenticationToken': token,
'authenticationScope': info.scope
});
});
}).catch(err => {
// end log token info request with error
if (err && err.statusCode === 404) {
// revert 404 not found returned by auth server to 498 invalid token
return done(new HttpBearerTokenNotFound());
}
// otherwise continue with error
return done(err);
});
});
}
}
declare class PassportService extends ApplicationService {
getInstance(): Authenticator
}
export { AppRateLimitService, AppSpeedLimitService, DefaultScopeAccessConfiguration, EnableScopeAccessConfiguration, ExtendScopeAccessConfiguration, HttpAccountDisabled, HttpBearerStrategy, HttpBearerTokenExpired, HttpBearerTokenNotFound, HttpBearerTokenRequired, OAuth2ClientService, PassportService, RateLimitService, RedisClientStore, RemoteAddressValidator, ScopeAccessConfiguration, ScopeString, SpeedLimitService, validateScope };
export type { GenericUser, OAuth2AuthorizeUser, OAuth2MethodOptions, OAuth2ServiceSettings, OAuth2User, OAuth2UserProfile, RateLimitServiceConfiguration, RateLimitStoreConstructor, ScopeAccessConfigurationElement, ScopeAccessConfigurationSection, SpeedLimitServiceConfiguration, SpeedLimitStoreConstructor, UniversisConfigurationSection };