namespace _espe.service.member { /** */ var _hooks_change : Array<() => void> = []; /** */ export function listen_change( procedure : () => void ) : void { _hooks_change.push(procedure); } /** */ function notify_change( ) : void { _hooks_change.forEach( procedure => { procedure(); } ); } /** * @todo test */ function validate_password( password : string ) : Array<{incident : string; details : Record}> { let flaws : Array<{incident : string; details : Record}> = []; const conf = { "minimum_length": 8, "maximum_length": 240, // "pattern": "must_contain_letter": true, "must_contain_number": true, "must_contain_special_character": true, }; if (password.length < conf.minimum_length) { flaws.push( { "incident": "too_short", "details": { "minimum_length": conf.minimum_length, "actual_length": password.length, } } ); } if (password.length > conf.maximum_length) { flaws.push( { "incident": "too_long", "details": { "maximum_length": conf.maximum_length, "actual_length": password.length, } } ); } if (conf.must_contain_letter && (! (new RegExp("[a-zA-Z]")).test(password))) { flaws.push( { "incident": "lacks_letter", "details": { } } ); } if (conf.must_contain_number && (! (new RegExp("[0-9]")).test(password))) { flaws.push( { "incident": "lacks_number", "details": { } } ); } if (conf.must_contain_special_character && (! (new RegExp("[!?-_.,;/\~%&$'()\\[\\]{}^'#|+*<>=\"`:@]")).test(password))) { flaws.push( { "incident": "lacks_special_character", "details": { } } ); } return flaws; } /** */ export function name_login( object : _espe.type.member_object ) : string { return lib_plankton.string.coin( "{{object}}{{extension}}", { "object": lib_plankton.call.convey( object.name_real_value, [ x => x.toLowerCase(), x => x.replace(new RegExp(" ", "g"), "."), x => x.replace(new RegExp("ä", "g"), "ae"), x => x.replace(new RegExp("ö", "g"), "oe"), x => x.replace(new RegExp("ü", "g"), "ue"), x => x.replace(new RegExp("ß", "g"), "ss"), ] ), "extension": ( (object.name_real_index <= 1) ? "" : ("." + object.name_real_index.toFixed(0)) ), } ); } /** */ export function name_display( object : _espe.type.member_object ) : string { return object.name_real_value; } /** */ export function email_address_veiled( object : _espe.type.member_object ) : string { return lib_plankton.string.coin( "{{prefix}}{{membership_number}}@{{domain}}", { "prefix": _espe.conf.get().settings.prefix_for_numberbased_email_addresses, "membership_number": object.membership_number, "domain": _espe.conf.get().settings.target_domain, } ); } /** */ export function email_address_nominal( object : _espe.type.member_object ) : string { return lib_plankton.string.coin( "{{user}}@{{domain}}", { "user": name_login(object), "domain": _espe.conf.get().settings.target_domain, } ); } /** */ export function email_address( object : _espe.type.member_object ) : (null | string) { return ( object.email_use_nominal_address ? email_address_nominal(object) : ( object.email_use_veiled_address ? email_address_veiled(object) : object.email_address_private ) ); } /** */ async function dump( ) : Promise< Array< { id : _espe.type.member_id; object : _espe.type.member_object; } > > { return _espe.repository.member.dump(); } /** */ export async function list( search_term : (null | string) ) : Promise< Array< { id : _espe.type.member_id; preview : { membership_number : string; name_real_value : string; name_real_index : int; }; } > > { return _espe.repository.member.list(search_term); } /** */ export function get( id : _espe.type.member_id ) : Promise<_espe.type.member_object> { return _espe.repository.member.read(id); } /** */ export async function project( data : { membership_number : string; name_real_value : string; email_address_private : (null | string); } ) : Promise<_espe.type.member_id> { const name_real_index : int = await _espe.service.name_index.next(data.name_real_value); const object : _espe.type.member_object = { "membership_number": data.membership_number, "name_real_value": data.name_real_value, "name_real_index": name_real_index, "email_address_private": data.email_address_private, "registered": false, "enabled": false, "email_use_veiled_address": false, "email_use_nominal_address": false, "email_redirect_to_private_address": false, "email_allow_sending": false, "password_image": null, }; const id : _espe.type.member_id = await _espe.repository.member.create(object); notify_change(); return id; } /** */ export async function summon( member_id : _espe.type.member_id, url_template : string ) : Promise { const member_object : _espe.type.member_object = await get(member_id); const verification : string = await _espe.helpers.verification_get(member_id); await _espe.helpers.email_send( [ member_object.email_address_private, ], _espe.conf.get().settings.registration_email.subject, lib_plankton.string.coin( _espe.conf.get().settings.registration_email.body, { "name": member_object.name_real_value, "url": lib_plankton.string.coin( url_template, { "verification": verification, } ), } ) ); } /** */ export async function info( member_id : _espe.type.member_id ) : Promise< ( null | { name_real_value : string; name_real_index : int; name_login : string; email_address_veiled : string; email_address_nominal : string; } ) > { const member_object : _espe.type.member_object = await _espe.repository.member.read(member_id); if (! member_object.registered) { return { "name_real_value": member_object.name_real_value, "name_real_index": member_object.name_real_index, "name_login": name_login(member_object), "email_address_veiled": email_address_veiled(member_object), "email_address_nominal": email_address_nominal(member_object), }; } else { return null; } } /** * @todo customize notifiation email (conf values) */ export async function register( member_id : _espe.type.member_id, data : { email_use_veiled_address : boolean; email_use_nominal_address : boolean; email_redirect_to_private_address : boolean; password : string; }, options : { notify_admins ?: boolean; } = {} ) : Promise;}>> { options = Object.assign( { "notify_admins": false, }, options ); const member_object : _espe.type.member_object = await get(member_id); let flaws : Array<{incident : string; details : Record;}> = []; const password_set : boolean = ( (data.password !== null) && (data.password !== "") ); if (member_object.registered) { flaws.push({"incident": "already_registered", "details": {}}); } else { if (password_set) { flaws = flaws.concat( validate_password(data.password) .map(flaw => ({"incident": ("password_" + flaw.incident), "details": flaw.details})) ); } else { // do nothing } } if (flaws.length > 0) { // do nothing } else { member_object.email_use_veiled_address = data.email_use_veiled_address member_object.email_use_nominal_address = data.email_use_nominal_address member_object.email_redirect_to_private_address = data.email_redirect_to_private_address; member_object.password_image = ( password_set ? await _espe.helpers.bcrypt_compute(data.password) : null ); member_object.registered = true; await _espe.repository.member.update(member_id, member_object); notify_change(); if (! options.notify_admins) { // do nothing } else { await _espe.helpers.email_send( _espe.conf.get().admins.map(admin => admin.email_address).filter(x => (x !== null)), "Eingegangene Registrierung", ("Member-ID: " + member_id.toFixed(0)) ); } } return Promise.resolve(flaws); } /** */ export async function modify( member_id : _espe.type.member_id, data : { email_address_private : (null | string); registered : boolean; enabled : boolean; } ) : Promise { const member_object_old : _espe.type.member_object = await get(member_id); const member_object_new : _espe.type.member_object = { "membership_number": member_object_old.membership_number, "name_real_value": member_object_old.name_real_value, "name_real_index": member_object_old.name_real_index, "email_address_private": data.email_address_private, "registered": data.registered, "enabled": data.enabled, "email_use_veiled_address": member_object_old.email_use_veiled_address, "email_use_nominal_address": member_object_old.email_use_nominal_address, "email_redirect_to_private_address": member_object_old.email_redirect_to_private_address, "email_allow_sending": member_object_old.email_allow_sending, "password_image": member_object_old.password_image, }; await _espe.repository.member.update(member_id, member_object_new); notify_change(); } /* export async function remove( id : _espe.type.member_id ) : Promise { await _espe.repository.member.delete(id); notify_change(); } */ /** * @todo check validity (e.g. username characters) */ export async function export_authelia_user_file( ) : Promise { const nm_yaml = require("yaml"); return lib_plankton.call.convey( await dump(), [ x => x.map( entry => Object.assign( entry, {"email_address": email_address(entry.object)} ) ), x => x.filter( entry => ( entry.object.registered && ( (entry.object.password_image !== null) && (entry.object.password_image !== "") ) && (entry.email_address !== null) ) ), x => x.map( entry => ([ name_login(entry.object), { "disabled": (! entry.object.enabled), "displayname": name_display(entry.object), "email": entry.email_address, "groups": [], "password": entry.object.password_image, } ]) ), Object.fromEntries, x => ({"users": x}), x => nm_yaml.stringify(x), ] ); } }