backend/source/services/member.ts

538 lines
11 KiB
TypeScript

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 do not export
*/
export function validate_password(
password : string
) : Array<{incident : string; details : Record<string, any>}>
{
let flaws : Array<{incident : string; details : Record<string, any>}> = [];
if (
(_espe.conf.get().settings.password_policy.minimum_length !== null)
&&
(password.length < _espe.conf.get().settings.password_policy.minimum_length)
) {
flaws.push(
{
"incident": "too_short",
"details": {
"minimum_length": _espe.conf.get().settings.password_policy.minimum_length,
"actual_length": password.length,
}
}
);
}
if (
(_espe.conf.get().settings.password_policy.maximum_length !== null)
&&
(password.length > _espe.conf.get().settings.password_policy.maximum_length)
) {
flaws.push(
{
"incident": "too_long",
"details": {
"maximum_length": _espe.conf.get().settings.password_policy.maximum_length,
"actual_length": password.length,
}
}
);
}
if (
_espe.conf.get().settings.password_policy.must_contain_letter
&&
(! (new RegExp("[a-zA-Z]")).test(password))
) {
flaws.push(
{
"incident": "lacks_letter",
"details": {
}
}
);
}
if (
_espe.conf.get().settings.password_policy.must_contain_number
&&
(! (new RegExp("[0-9]")).test(password))
) {
flaws.push(
{
"incident": "lacks_number",
"details": {
}
}
);
}
if (
_espe.conf.get().settings.password_policy.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"),
x => x.replace(new RegExp("[^a-z-\.]", "g"), "_"),
]
),
"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": true,
"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<string>
{
const member_object : _espe.type.member_object = await get(member_id);
const verification : string = await _espe.helpers.verification_get(member_id);
const url : string = lib_plankton.string.coin(
url_template,
{
"verification": verification,
}
);
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": url,
}
)
);
return url;
}
/**
*/
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<Array<{incident : string; details : Record<string, any>;}>>
{
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<string, any>;}> = [];
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<void>
{
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<void>
{
await _espe.repository.member.delete(id);
notify_change();
}
*/
/**
* @todo check validity (e.g. username characters)
*/
export async function export_authelia_user_file(
options : {
custom_data ?: (null | Array<_espe.type.member_object>);
} = {}
) : Promise<string>
{
options = Object.assign(
{
"custom_data": null,
},
options
);
const nm_yaml = require("yaml");
return lib_plankton.call.convey(
(
(options.custom_data !== null)
? (options.custom_data.map((member_object, index) => ({"id": index, "object": member_object})))
: 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),
]
);
}
}