@futoin/security
Version:
FutoIn Security Concept reference implementation
299 lines (253 loc) • 9.24 kB
JavaScript
;
/**
* @file
*
* Copyright 2018 FutoIn Project (https://futoin.org)
* Copyright 2018 Andrey Galkin <andrey@futoin.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const moment = require( 'moment' );
const UUIDTool = require( 'futoin-uuid' );
const Errors = require( 'futoin-asyncsteps/Errors' );
const BaseService = require( './lib/BaseService' );
const ManageFace = require( './ManageFace' );
const {
EVTGEN_FACE,
DB_USERS_TABLE,
} = require( './lib/main' );
const SYM_SELECT_USER = Symbol( 'selectUser' );
const SYM_SELECT_USER_INFO = Symbol( 'selectUserInfo' );
/**
* FTN8: main Manage Service
*/
class ManageService extends BaseService {
static get IFACE_IMPL() {
return ManageFace;
}
setup( as, reqinfo ) {
const ccm = reqinfo.ccm();
const scope = this._scope;
const { config } = scope;
const params = reqinfo.params();
for ( let k of Object.keys( params ) ) {
const v = params[k];
if ( v !== null ) {
config[k] = v;
}
}
const domain2service = {};
if ( config.auth_service ) {
as.forEach( config.domains, ( as, _, global_id ) => {
this._ensureNoChecks( as, {
ccm,
global_id,
is_local: true,
is_service: true,
} );
as.add( ( as, local_id ) => {
domain2service[global_id] = local_id;
} );
} );
as.add( ( as ) => {
scope.system_local_id = domain2service[config.domains[0]];
} );
}
as.add( ( as ) => {
// atomic change
Object.freeze( domain2service );
scope.domain2service = domain2service;
reqinfo.result( true );
} );
}
genConfig( as, _reqinfo ) {
as.success( this._scope.config );
}
_ensureNoChecks( as, { ccm, global_id, is_local, is_service } ) {
const db = ccm.db( 'ftnsec' );
const evt = ccm.iface( EVTGEN_FACE );
let local_id;
as.repeat( 2, ( as ) => {
db
.getPrepared( SYM_SELECT_USER, ( db ) => {
const qb = db.select( DB_USERS_TABLE );
qb.get( 'local_id' );
qb.where( 'global_id', qb.param( 'global_id' ) );
return qb.prepare();
} )
.execute( as, { global_id } );
as.add( ( as, { rows } ) => {
if ( rows.length === 1 ) {
local_id = rows[0][0];
as.break();
}
local_id = UUIDTool.genB64();
const user_info = {
local_id,
global_id,
is_local : is_local ? 'Y' : 'N',
is_enabled : 'Y',
is_service : is_service ? 'Y' : 'N',
};
const q_now = db.helpers().now();
const xfer = db.newXfer();
xfer.insert( DB_USERS_TABLE )
.set( user_info )
.set( 'created', q_now )
.set( 'updated', q_now );
evt.addXferEvent( xfer, 'USR_NEW', user_info );
as.add(
( as ) => {
xfer.execute( as );
as.add( ( as ) => as.break() );
},
( as, err ) => {
if ( err === 'Duplicate' ) {
as.continue();
}
}
);
} );
} );
as.add( ( as ) => as.success( local_id ) );
}
_ensureCommon( as, global_id, reqinfo, is_service ) {
const { config } = this._scope;
if ( !config.auth_service ) {
as.error( Errors.InternalError, 'AuthService is not enabled' );
}
//---
const { domain } = reqinfo.params();
const is_local = config.domains.indexOf( domain ) >= 0;
//---
const ccm = reqinfo.ccm();
this._ensureNoChecks( as, { ccm, global_id, is_local, is_service } );
as.add( ( as, local_id ) => reqinfo.result( local_id ) );
}
ensureUser( as, reqinfo ) {
const p = reqinfo.params();
const global_id = `${p.user}@${p.domain}`;
this._ensureCommon( as, global_id, reqinfo, false );
}
ensureService( as, reqinfo ) {
const p = reqinfo.params();
const global_id = `${p.hostname}.${p.domain}`;
this._ensureCommon( as, global_id, reqinfo, true );
}
_userInfoCommon( as, reqinfo ) {
const db = reqinfo.ccm().db( 'ftnsec' );
const local_id = reqinfo.params().local_id;
db
.getPrepared( SYM_SELECT_USER_INFO, ( db ) => {
const qb = db.select( DB_USERS_TABLE );
qb.get( [
'local_id',
'global_id',
'is_enabled',
'is_local',
'is_service',
'ms_max',
'ds_max',
'created',
'updated',
] );
qb.where( 'local_id', qb.param( 'local_id' ) );
return qb.prepare();
} )
.executeAssoc( as, { local_id } );
as.add( ( as, rows ) => {
if ( rows.length !== 1 ) {
as.error( 'UnknownUser', `UserID: ${local_id}` );
}
as.success( rows[0] );
} );
}
_onUserInfoUpdate( _evt_data ) {
// pass
}
getUserInfo( as, reqinfo ) {
// TODO: caching
this._userInfoCommon( as, reqinfo );
as.add( ( as, r ) => {
const { config } = this._scope;
r.is_enabled = ( r.is_enabled === 'Y' );
r.is_local = ( r.is_local === 'Y' );
r.is_service = ( r.is_service === 'Y' );
r.ms_max = r.ms_max ||
( r.is_service
? config.def_service_ms_max
: config.def_user_ms_max );
r.ds_max = r.ds_max || r.ms_max;
r.created = moment.utc( r.created ).format();
r.updated = moment.utc( r.updated ).format();
reqinfo.result( r );
} );
}
setUserInfo( as, reqinfo ) {
this._userInfoCommon( as, reqinfo );
as.add( ( as, r ) => {
const params = reqinfo.params();
const { local_id, is_enabled, ms_max, ds_max } = params;
const { config } = this._scope;
const ccm = reqinfo.ccm();
const db = ccm.db( 'ftnsec' );
const evt = ccm.iface( EVTGEN_FACE );
//---
const xfer = db.newXfer();
const uq = xfer.update( DB_USERS_TABLE )
.set( 'updated', db.helpers().now() )
.where( 'local_id', local_id );
const changes = {};
if ( is_enabled !== null ) {
changes.is_enabled = is_enabled ? 'Y' : 'N';
}
if ( ms_max !== null ) {
if ( ( ( r.is_service === 'Y' ) &&
( ms_max === config.def_service_ms_max ) ) ||
( ( r.is_service === 'N' ) &&
( ms_max === config.def_user_ms_max ) )
) {
changes.ms_max = null;
} else {
changes.ms_max = ms_max;
}
}
if ( ds_max !== null ) {
if ( ds_max === ms_max ) {
changes.ds_max = null;
} else {
changes.ds_max = ds_max;
}
}
uq.set( changes );
const evt_data = Object.assign( { local_id }, changes );
evt.addXferEvent( xfer, 'USR_MOD', evt_data );
xfer.execute( as );
//---
as.add( ( as ) => {
this._onUserInfoUpdate( evt_data );
reqinfo.result( true );
} );
} );
}
/**
* Register futoin.auth.manage interface with Executor
* @alias ManageService.register
* @param {AsyncSteps} as - steps interface
* @param {Executor} executor - executor instance
* @param {object} options - implementation defined options
* @param {Executor} options.scope=main.globalScope
* @returns {ManageService} instance
*/
}
module.exports = ManageService;