backend/source/services/member.ts

518 lines
11 KiB
TypeScript
Raw Normal View History

2024-04-22 10:22:18 +02:00
namespace _espe.service.member
2024-04-22 10:02:34 +02:00
{
/**
*/
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();
}
);
}
2024-04-30 14:05:01 +02:00
/**
* @todo test
*/
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)
) {
2024-04-30 14:05:01 +02:00
flaws.push(
{
"incident": "too_short",
"details": {
"minimum_length": _espe.conf.get().settings.password_policy.minimum_length,
2024-04-30 14:05:01 +02:00
"actual_length": password.length,
}
}
);
}
if (
(_espe.conf.get().settings.password_policy.maximum_length !== null)
&&
(password.length > _espe.conf.get().settings.password_policy.maximum_length)
) {
2024-04-30 14:05:01 +02:00
flaws.push(
{
"incident": "too_long",
"details": {
"maximum_length": _espe.conf.get().settings.password_policy.maximum_length,
2024-04-30 14:05:01 +02:00
"actual_length": password.length,
}
}
);
}
if (
_espe.conf.get().settings.password_policy.must_contain_letter
&&
(! (new RegExp("[a-zA-Z]")).test(password))
) {
2024-04-30 14:05:01 +02:00
flaws.push(
{
"incident": "lacks_letter",
"details": {
}
}
);
}
if (
_espe.conf.get().settings.password_policy.must_contain_number
&&
(! (new RegExp("[0-9]")).test(password))
) {
2024-04-30 14:05:01 +02:00
flaws.push(
{
"incident": "lacks_number",
"details": {
}
}
);
}
if (
_espe.conf.get().settings.password_policy.must_contain_special_character
&&
(! (new RegExp("[!?-_.,;/\~%&$'()\\[\\]{}^'#|+*<>=\"`:@]")).test(password))
) {
2024-04-30 14:05:01 +02:00
flaws.push(
{
"incident": "lacks_special_character",
"details": {
}
}
);
}
return flaws;
}
2024-04-22 10:02:34 +02:00
/**
*/
2024-04-30 08:46:52 +02:00
export function name_login(
2024-04-29 22:40:52 +02:00
object : _espe.type.member_object
) : string
{
return lib_plankton.string.coin(
"{{object}}{{extension}}",
{
2024-04-30 14:05:01 +02:00
"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"),
]
),
2024-04-29 22:40:52 +02:00
"extension": (
2024-04-30 01:32:24 +02:00
(object.name_real_index <= 1)
2024-04-29 22:40:52 +02:00
? ""
: ("." + object.name_real_index.toFixed(0))
),
}
);
}
/**
*/
2024-04-30 08:46:52 +02:00
export function name_display(
2024-04-29 22:40:52 +02:00
object : _espe.type.member_object
) : string
{
return object.name_real_value;
}
/**
*/
2024-04-30 08:46:52 +02:00
export function email_address_veiled(
2024-04-29 22:40:52 +02:00
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,
}
);
}
/**
*/
2024-04-30 08:46:52 +02:00
export function email_address_nominal(
2024-04-29 22:40:52 +02:00
object : _espe.type.member_object
) : string
{
return lib_plankton.string.coin(
"{{user}}@{{domain}}",
{
"user": name_login(object),
"domain": _espe.conf.get().settings.target_domain,
}
);
}
/**
*/
2024-04-30 08:46:52 +02:00
export function email_address(
2024-04-29 22:40:52 +02:00
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();
}
2024-04-30 01:32:24 +02:00
/**
*/
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);
}
2024-04-29 22:40:52 +02:00
/**
*/
2024-04-30 01:32:24 +02:00
export function get(
2024-04-29 22:40:52 +02:00
id : _espe.type.member_id
) : Promise<_espe.type.member_object>
{
return _espe.repository.member.read(id);
}
/**
*/
export async function project(
2024-04-29 20:48:20 +02:00
data : {
membership_number : string;
name_real_value : string;
email_address_private : (null | string);
}
2024-04-29 22:40:52 +02:00
) : Promise<_espe.type.member_id>
2024-04-22 10:02:34 +02:00
{
2024-04-29 22:40:52 +02:00
const name_real_index : int = await _espe.service.name_index.next(data.name_real_value);
const object : _espe.type.member_object = {
2024-04-29 20:48:20 +02:00
"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,
2024-04-30 15:35:17 +02:00
"enabled": true,
2024-04-29 20:48:20 +02:00
"email_use_veiled_address": false,
"email_use_nominal_address": false,
"email_redirect_to_private_address": false,
"email_allow_sending": false,
"password_image": null,
2024-04-22 10:02:34 +02:00
};
2024-04-29 22:40:52 +02:00
const id : _espe.type.member_id = await _espe.repository.member.create(object);
2024-04-29 20:48:20 +02:00
notify_change();
return id;
2024-04-22 10:02:34 +02:00
}
2024-04-29 22:40:52 +02:00
/**
*/
export async function summon(
member_id : _espe.type.member_id,
url_template : string
2024-04-22 10:02:34 +02:00
) : Promise<void>
{
2024-04-29 22:40:52 +02:00
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,
}
),
}
)
);
2024-04-22 10:02:34 +02:00
}
2024-04-29 22:40:52 +02:00
/**
*/
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;
}
)
>
2024-04-22 10:02:34 +02:00
{
2024-04-29 22:40:52 +02:00
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;
}
2024-04-22 10:02:34 +02:00
}
2024-04-29 22:40:52 +02:00
/**
* @todo customize notifiation email (conf values)
*/
2024-04-22 10:02:34 +02:00
export async function register(
2024-04-29 22:40:52 +02:00
member_id : _espe.type.member_id,
2024-04-22 10:02:34 +02:00
data : {
2024-04-29 22:40:52 +02:00
email_use_veiled_address : boolean;
email_use_nominal_address : boolean;
email_redirect_to_private_address : boolean;
2024-04-22 10:02:34 +02:00
password : string;
2024-04-29 22:40:52 +02:00
},
options : {
notify_admins ?: boolean;
} = {}
2024-04-30 14:05:01 +02:00
) : Promise<Array<{incident : string; details : Record<string, any>;}>>
2024-04-22 10:02:34 +02:00
{
2024-04-29 22:40:52 +02:00
options = Object.assign(
{
"notify_admins": false,
},
options
);
2024-04-30 14:05:01 +02:00
2024-04-29 22:40:52 +02:00
const member_object : _espe.type.member_object = await get(member_id);
2024-04-30 14:05:01 +02:00
let flaws : Array<{incident : string; details : Record<string, any>;}> = [];
const password_set : boolean = (
(data.password !== null)
&&
(data.password !== "")
);
2024-04-29 22:40:52 +02:00
if (member_object.registered) {
2024-04-30 14:05:01 +02:00
flaws.push({"incident": "already_registered", "details": {}});
2024-04-22 10:02:34 +02:00
}
else {
2024-04-30 14:05:01 +02:00
if (password_set) {
flaws = flaws.concat(
validate_password(data.password)
.map(flaw => ({"incident": ("password_" + flaw.incident), "details": flaw.details}))
);
}
else {
// do nothing
}
2024-04-29 22:40:52 +02:00
}
2024-04-30 14:05:01 +02:00
if (flaws.length > 0) {
2024-04-29 22:40:52 +02:00
// do nothing
}
else {
2024-04-30 14:05:01 +02:00
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
2024-04-29 22:40:52 +02:00
);
2024-04-30 14:05:01 +02:00
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))
);
}
2024-04-22 10:02:34 +02:00
}
2024-04-30 14:05:01 +02:00
return Promise.resolve(flaws);
2024-04-22 10:02:34 +02:00
}
2024-04-30 01:32:24 +02:00
/**
*/
2024-04-29 22:40:52 +02:00
export async function modify(
2024-04-30 01:32:24 +02:00
member_id : _espe.type.member_id,
data : {
email_address_private : (null | string);
registered : boolean;
enabled : boolean;
}
2024-04-29 22:40:52 +02:00
) : Promise<void>
2024-04-22 10:02:34 +02:00
{
2024-04-30 01:32:24 +02:00
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);
2024-04-29 22:40:52 +02:00
notify_change();
2024-04-22 10:02:34 +02:00
}
2024-04-30 01:32:24 +02:00
/*
2024-04-29 22:40:52 +02:00
export async function remove(
id : _espe.type.member_id
) : Promise<void>
2024-04-22 10:02:34 +02:00
{
2024-04-29 22:40:52 +02:00
await _espe.repository.member.delete(id);
notify_change();
2024-04-22 10:02:34 +02:00
}
2024-04-29 22:40:52 +02:00
*/
2024-04-22 10:02:34 +02:00
2024-04-29 22:40:52 +02:00
/**
* @todo check validity (e.g. username characters)
2024-04-29 22:40:52 +02:00
*/
export async function export_authelia_user_file(
2024-04-22 10:02:34 +02:00
) : Promise<string>
{
const nm_yaml = require("yaml");
return lib_plankton.call.convey(
await dump(),
[
x => x.map(
entry => Object.assign(
entry,
2024-04-29 22:40:52 +02:00
{"email_address": email_address(entry.object)}
2024-04-22 10:02:34 +02:00
)
),
x => x.filter(
entry => (
2024-04-29 22:40:52 +02:00
entry.object.registered
2024-04-22 10:02:34 +02:00
&&
(
2024-04-29 22:40:52 +02:00
(entry.object.password_image !== null)
2024-04-22 10:02:34 +02:00
&&
2024-04-29 22:40:52 +02:00
(entry.object.password_image !== "")
2024-04-22 10:02:34 +02:00
)
&&
(entry.email_address !== null)
)
),
x => x.map(
entry => ([
2024-04-29 22:40:52 +02:00
name_login(entry.object),
2024-04-22 10:02:34 +02:00
{
2024-04-29 22:40:52 +02:00
"disabled": (! entry.object.enabled),
"displayname": name_display(entry.object),
2024-04-22 10:02:34 +02:00
"email": entry.email_address,
"groups": [],
2024-04-29 22:40:52 +02:00
"password": entry.object.password_image,
2024-04-22 10:02:34 +02:00
}
])
),
Object.fromEntries,
x => ({"users": x}),
x => nm_yaml.stringify(x),
]
);
}
}