[mod] notifications
This commit is contained in:
parent
1483bf95a8
commit
8a33c029c7
4 changed files with 252 additions and 119 deletions
|
@ -29,6 +29,7 @@ namespace _espe.api
|
|||
email_use_nominal_address : boolean;
|
||||
email_redirect_to_private_address : boolean;
|
||||
password : (null | string);
|
||||
notification_target_url_template ?: (null | string);
|
||||
},
|
||||
Array<
|
||||
{
|
||||
|
@ -67,6 +68,11 @@ namespace _espe.api
|
|||
"nullable": true,
|
||||
"description": "Passwort für alle Netz-Dienste",
|
||||
},
|
||||
"notification_target_url_template": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Platz-Halter: id"
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"email_use_veiled_address",
|
||||
|
@ -128,6 +134,9 @@ namespace _espe.api
|
|||
"email_use_nominal_address": input.email_use_nominal_address,
|
||||
"email_redirect_to_private_address": input.email_redirect_to_private_address,
|
||||
"password": input.password,
|
||||
},
|
||||
{
|
||||
"notification_target_url_template": (input.notification_target_url_template ?? null),
|
||||
}
|
||||
)
|
||||
.then(
|
||||
|
|
|
@ -96,12 +96,21 @@ namespace _espe.conf
|
|||
settings : {
|
||||
target_domain : string;
|
||||
frontend_url_base : (null | string);
|
||||
login_url : (null | string);
|
||||
prefix_for_nominal_email_addresses : string;
|
||||
facultative_membership_number : boolean;
|
||||
summon_email : {
|
||||
subject : string;
|
||||
body : string;
|
||||
};
|
||||
registration_email : {
|
||||
subject : string;
|
||||
body : string;
|
||||
};
|
||||
activation_email : {
|
||||
subject : string;
|
||||
body : string;
|
||||
};
|
||||
password_policy : {
|
||||
minimum_length : (null | int);
|
||||
maximum_length : (null | int);
|
||||
|
@ -248,12 +257,21 @@ namespace _espe.conf
|
|||
((node_settings) => ({
|
||||
"target_domain": (node_settings["target_domain"] ?? "example.org"),
|
||||
"frontend_url_base": (node_settings["frontend_url_base"] ?? null), // TODO: mandatory?
|
||||
"login_url": (node_settings["login_url"] ?? null),
|
||||
"prefix_for_nominal_email_addresses": (node_settings["prefix_for_nominal_email_addresses"] ?? "member-"),
|
||||
"facultative_membership_number": (node_settings["facultative_membership_number"] ?? false),
|
||||
"summon_email": {
|
||||
"subject": ((node_settings["summon_email"] ?? {})["subject"] ?? "Please register"),
|
||||
"body": ((node_settings["summon_email"] ?? {})["body"] ?? "URL: {{url}}"),
|
||||
},
|
||||
"registration_email": {
|
||||
"subject": ((node_settings["registration_email"] ?? {})["subject"] ?? "Registration"),
|
||||
"subject": ((node_settings["registration_email"] ?? {})["subject"] ?? "Mmeber registered"),
|
||||
"body": ((node_settings["registration_email"] ?? {})["body"] ?? "URL: {{url}}"),
|
||||
},
|
||||
"activation_email": {
|
||||
"subject": ((node_settings["activation_email"] ?? {})["subject"] ?? "Account activated"),
|
||||
"body": ((node_settings["activation_email"] ?? {})["body"] ?? "URL: {{url}}\n\nLogin name: {{name_login}}\n\n"),
|
||||
},
|
||||
"password_policy": (
|
||||
((node_settings_password_policy) => ({
|
||||
"minimum_length": (
|
||||
|
|
|
@ -268,4 +268,64 @@ namespace _espe.helpers
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
export function frontend_url_check(
|
||||
) : void
|
||||
{
|
||||
const frontend_url_base : (null | string) = _espe.conf.get().settings.frontend_url_base;
|
||||
if (frontend_url_base === null) {
|
||||
throw (new Error("no frontend url base set; add in conf as 'settings.frontend_url_base'!"));
|
||||
}
|
||||
else {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
export function frontend_url_get(
|
||||
template : string,
|
||||
arguments_ : Record<string, string>
|
||||
) : (null | string)
|
||||
{
|
||||
const frontend_url_base : (null | string) = _espe.conf.get().settings.frontend_url_base;
|
||||
if (frontend_url_base === null) {
|
||||
// throw (new Error("no frontend url base set; add in conf as 'settings.frontend_url_base'!"));
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return lib_plankton.string.coin(
|
||||
"{{base}}/{{rest}}",
|
||||
{
|
||||
"base": frontend_url_base,
|
||||
"rest": lib_plankton.string.coin(template, arguments_),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
export async function notify_admins(
|
||||
subject : string,
|
||||
body : string
|
||||
) : Promise<void>
|
||||
{
|
||||
await _espe.helpers.email_send(
|
||||
(
|
||||
(
|
||||
_espe.conf.get().admins
|
||||
.map(admin => admin.email_address)
|
||||
.filter(x => (x !== null))
|
||||
) as Array<string>
|
||||
),
|
||||
subject,
|
||||
body
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace _espe.service.member
|
|||
|
||||
/**
|
||||
*/
|
||||
function notify_change(
|
||||
function signal_change(
|
||||
) : void
|
||||
{
|
||||
_hooks_change.forEach(
|
||||
|
@ -251,6 +251,40 @@ namespace _espe.service.member
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
async function send_activation_email(
|
||||
member_object : _espe.type.member_object
|
||||
) : Promise<void>
|
||||
{
|
||||
if (! member_object.enabled) {
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
if (member_object.email_address_private === null) {
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
await _espe.helpers.email_send(
|
||||
[
|
||||
member_object.email_address_private,
|
||||
],
|
||||
_espe.conf.get().settings.activation_email.subject,
|
||||
lib_plankton.string.coin(
|
||||
_espe.conf.get().settings.activation_email.body,
|
||||
{
|
||||
"name_display": name_display(member_object),
|
||||
"name_login": name_login(member_object),
|
||||
"url": (_espe.conf.get().settings.login_url ?? "--"),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* gibt die vollständigen Daten aller Mitglieder aus
|
||||
*/
|
||||
|
@ -330,47 +364,49 @@ namespace _espe.service.member
|
|||
"password_change_token": null,
|
||||
};
|
||||
const id : _espe.type.member_id = await _espe.repository.member.create(object);
|
||||
notify_change();
|
||||
signal_change();
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* sendet an ein Mitglied eine E-Mail mit Aufforderung zur Registrierung
|
||||
*
|
||||
* @todo conf:settings.frontend_url_base verwenden
|
||||
*/
|
||||
export async function summon(
|
||||
member_id : _espe.type.member_id,
|
||||
url_template : string
|
||||
) : Promise<(null | string)>
|
||||
{
|
||||
_espe.helpers.frontend_url_check();
|
||||
const member_object : _espe.type.member_object = await get(member_id);
|
||||
if (member_object.email_address_private === null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
const verification : string = await _espe.helpers.verification_get(member_id);
|
||||
|
||||
const url : string = lib_plankton.string.coin(
|
||||
const url : (null | string) = _espe.helpers.frontend_url_get(
|
||||
url_template,
|
||||
{
|
||||
"verification": verification,
|
||||
"verification": 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": url,
|
||||
}
|
||||
)
|
||||
);
|
||||
if (url === null) {
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
await _espe.helpers.email_send(
|
||||
[
|
||||
member_object.email_address_private,
|
||||
],
|
||||
_espe.conf.get().settings.summon_email.subject,
|
||||
lib_plankton.string.coin(
|
||||
_espe.conf.get().settings.summon_email.body,
|
||||
{
|
||||
"name": name_display(member_object),
|
||||
"url": url,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
@ -425,13 +461,13 @@ namespace _espe.service.member
|
|||
password : (null | string);
|
||||
},
|
||||
options : {
|
||||
notify_admins ?: boolean;
|
||||
notification_target_url_template ?: (null | string);
|
||||
} = {}
|
||||
) : Promise<Array<{incident : string; details : Record<string, any>;}>>
|
||||
{
|
||||
options = Object.assign(
|
||||
{
|
||||
"notify_admins": false,
|
||||
"notification_target_url_template": null,
|
||||
},
|
||||
options
|
||||
);
|
||||
|
@ -472,25 +508,39 @@ namespace _espe.service.member
|
|||
member_object.password_image = await password_image(data.password);
|
||||
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(
|
||||
signal_change();
|
||||
{
|
||||
const url : (null | string) = (
|
||||
(
|
||||
(
|
||||
_espe.conf.get().admins
|
||||
.map(admin => admin.email_address)
|
||||
.filter(x => (x !== null))
|
||||
) as Array<string>
|
||||
),
|
||||
"Eingegangene Registrierung", // TODO
|
||||
("Member-ID: " + member_id.toFixed(0))
|
||||
(options.notification_target_url_template === undefined)
|
||||
||
|
||||
(options.notification_target_url_template === null)
|
||||
)
|
||||
? null
|
||||
: _espe.helpers.frontend_url_get(
|
||||
options.notification_target_url_template,
|
||||
{
|
||||
"id": member_id.toFixed(0),
|
||||
}
|
||||
)
|
||||
);
|
||||
if (url === null) {
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
/*await*/ _espe.helpers.notify_admins(
|
||||
_espe.conf.get().settings.registration_email.subject,
|
||||
lib_plankton.string.coin(
|
||||
_espe.conf.get().settings.registration_email.body,
|
||||
{
|
||||
"name_display": name_display(member_object),
|
||||
"url": url,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
/*await*/ send_activation_email(member_object);
|
||||
}
|
||||
|
||||
return Promise.resolve(flaws);
|
||||
|
@ -526,7 +576,8 @@ namespace _espe.service.member
|
|||
"password_change_token": member_object_old.password_change_token,
|
||||
};
|
||||
await _espe.repository.member.update(member_id, member_object_new);
|
||||
notify_change();
|
||||
signal_change();
|
||||
/*await*/ send_activation_email(member_object_new);
|
||||
}
|
||||
|
||||
|
||||
|
@ -540,84 +591,91 @@ namespace _espe.service.member
|
|||
url_template : string
|
||||
) : Promise<void>
|
||||
{
|
||||
const frontend_url_base : (null | string) = _espe.conf.get().settings.frontend_url_base;
|
||||
if (frontend_url_base === null) {
|
||||
return Promise.reject<void>(new Error("no frontend url base set; add in conf as 'settings.frontend_url_base'!"));
|
||||
}
|
||||
else {
|
||||
const now : int = lib_plankton.base.get_current_timestamp(true);
|
||||
const cooldown_time : int = _espe.conf.get().settings.password_change.cooldown_time;
|
||||
const member_ids : Array<_espe.type.member_id> = await (
|
||||
(await _espe.repository.member.dump())
|
||||
.filter(
|
||||
member_entry => (
|
||||
member_entry.object.registered
|
||||
&&
|
||||
member_entry.object.enabled
|
||||
&&
|
||||
_espe.helpers.frontend_url_check();
|
||||
const now : int = lib_plankton.base.get_current_timestamp(true);
|
||||
const cooldown_time : int = _espe.conf.get().settings.password_change.cooldown_time;
|
||||
const member_ids : Array<_espe.type.member_id> = await (
|
||||
(await _espe.repository.member.dump())
|
||||
.filter(
|
||||
member_entry => (
|
||||
member_entry.object.registered
|
||||
&&
|
||||
member_entry.object.enabled
|
||||
&&
|
||||
(
|
||||
(
|
||||
(
|
||||
(! (member_entry.object.email_address_private === null))
|
||||
&&
|
||||
(member_entry.object.email_address_private === identifier)
|
||||
)
|
||||
||
|
||||
(name_login(member_entry.object) === identifier)
|
||||
(! (member_entry.object.email_address_private === null))
|
||||
&&
|
||||
(member_entry.object.email_address_private === identifier)
|
||||
)
|
||||
||
|
||||
(name_login(member_entry.object) === identifier)
|
||||
)
|
||||
)
|
||||
.map(
|
||||
member_entry => member_entry.id
|
||||
)
|
||||
);
|
||||
for await (const member_id of member_ids) {
|
||||
const member_object_old : _espe.type.member_object = await _espe.repository.member.read(member_id);
|
||||
if (
|
||||
(! (member_object_old.password_change_last_attempt === null))
|
||||
&&
|
||||
((now - member_object_old.password_change_last_attempt) <= cooldown_time)
|
||||
) {
|
||||
)
|
||||
.map(
|
||||
member_entry => member_entry.id
|
||||
)
|
||||
);
|
||||
for await (const member_id of member_ids) {
|
||||
const member_object_old : _espe.type.member_object = await _espe.repository.member.read(member_id);
|
||||
if (
|
||||
(! (member_object_old.password_change_last_attempt === null))
|
||||
&&
|
||||
((now - member_object_old.password_change_last_attempt) <= cooldown_time)
|
||||
) {
|
||||
lib_plankton.log.notice(
|
||||
"member_password_change_cooldown_not_over",
|
||||
{
|
||||
"member_id": member_id,
|
||||
"last_attempt": member_object_old.password_change_last_attempt,
|
||||
"now": now,
|
||||
}
|
||||
);
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
if (member_object_old.email_address_private === null) {
|
||||
lib_plankton.log.notice(
|
||||
"member_password_change_cooldown_not_over",
|
||||
"member_password_change_impossible_due_to_missing_private_email_address",
|
||||
{
|
||||
"member_id": member_id,
|
||||
"last_attempt": member_object_old.password_change_last_attempt,
|
||||
"now": now,
|
||||
}
|
||||
);
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
if (member_object_old.email_address_private === null) {
|
||||
lib_plankton.log.notice(
|
||||
"member_password_change_impossible_due_to_missing_private_email_address",
|
||||
{
|
||||
"member_id": member_id,
|
||||
}
|
||||
);
|
||||
// keine echte Verifizierung, der Algorithmus ist aber der passende
|
||||
const token : string = await _espe.helpers.verification_get(Math.floor(Math.random() * (1 << 24)));
|
||||
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": member_object_old.email_address_private,
|
||||
"registered": member_object_old.registered,
|
||||
"enabled": member_object_old.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,
|
||||
"password_change_last_attempt": now,
|
||||
"password_change_token": token,
|
||||
};
|
||||
await _espe.repository.member.update(member_id, member_object_new);
|
||||
// signal_change();
|
||||
// do NOT wait in order to reduce information for potential attackers
|
||||
const url : (null | string) = _espe.helpers.frontend_url_get(
|
||||
url_template,
|
||||
{
|
||||
"id": member_id.toFixed(0),
|
||||
"token": token,
|
||||
}
|
||||
);
|
||||
if (url === null) {
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
// keine echte Verifizierung, der Algorithmus ist aber der passende
|
||||
const token : string = await _espe.helpers.verification_get(Math.floor(Math.random() * (1 << 24)));
|
||||
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": member_object_old.email_address_private,
|
||||
"registered": member_object_old.registered,
|
||||
"enabled": member_object_old.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,
|
||||
"password_change_last_attempt": now,
|
||||
"password_change_token": token,
|
||||
};
|
||||
await _espe.repository.member.update(member_id, member_object_new);
|
||||
// notify_change();
|
||||
// do NOT wait in order to reduce information for potential attackers
|
||||
/*await*/ _espe.helpers.email_send(
|
||||
[
|
||||
member_object_old.email_address_private,
|
||||
|
@ -627,19 +685,7 @@ namespace _espe.service.member
|
|||
_espe.conf.get().settings.password_change.initialization_email.body,
|
||||
{
|
||||
"name": name_display(member_object_old),
|
||||
"url": lib_plankton.string.coin(
|
||||
"{{base}}{{rest}}",
|
||||
{
|
||||
"base": frontend_url_base,
|
||||
"rest": lib_plankton.string.coin(
|
||||
url_template,
|
||||
{
|
||||
"id": member_id.toFixed(0),
|
||||
"token": token,
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
"url": url,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -706,7 +752,7 @@ namespace _espe.service.member
|
|||
"password_change_token": null,
|
||||
};
|
||||
await _espe.repository.member.update(member_id, member_object_new);
|
||||
notify_change();
|
||||
signal_change();
|
||||
await _espe.helpers.email_send(
|
||||
[
|
||||
member_object_old.email_address_private,
|
||||
|
@ -732,7 +778,7 @@ namespace _espe.service.member
|
|||
) : Promise<void>
|
||||
{
|
||||
await _espe.repository.member.delete(id);
|
||||
notify_change();
|
||||
signal_change();
|
||||
}
|
||||
*/
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue