backend/source/services/invitation.ts

358 lines
7.9 KiB
TypeScript
Raw Normal View History

2025-03-31 20:24:43 +00:00
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
2025-07-07 12:10:52 +02:00
namespace _espe.service.invitation
2025-03-31 20:24:43 +00:00
{
2025-04-12 10:20:58 +00:00
/**
*/
export function list(
) : Promise<
Array<
{
2025-07-07 12:10:52 +02:00
id : _espe.type.invitation_id;
key : _espe.type.invitation_key;
2025-04-12 10:20:58 +00:00
expiry : (null | int);
2025-07-07 12:10:52 +02:00
name_value : (null | string);
2025-04-12 10:20:58 +00:00
}
>
>
{
return (
2025-07-07 12:10:52 +02:00
_espe.repository.invitation.dump()
2025-04-12 10:20:58 +00:00
.then(
entries => Promise.resolve(
entries.map(
entry => ({
"id": entry.id,
"key": entry.object.key,
"expiry": entry.object.expiry,
"name_value": entry.object.name_value
})
)
)
)
);
}
2025-03-31 20:24:43 +00:00
/**
*/
2025-04-01 04:20:59 +00:00
export async function create(
{
2025-04-12 10:20:58 +00:00
"name_changeable": name_changeable,
2025-04-01 04:20:59 +00:00
"name_value": name_value,
2025-04-12 10:20:58 +00:00
"email_address_changeable": email_address_changeable,
2025-04-01 04:20:59 +00:00
"email_address_value": email_address_value,
2025-04-12 10:20:58 +00:00
"groups_changeable": groups_changeable,
2025-04-01 04:20:59 +00:00
"groups_value": groups_value,
} : {
2025-04-12 10:20:58 +00:00
name_changeable : boolean;
2025-04-01 04:20:59 +00:00
name_value : string;
2025-04-12 10:20:58 +00:00
email_address_changeable : boolean;
2025-04-01 04:20:59 +00:00
email_address_value : (null | string);
2025-04-12 10:20:58 +00:00
groups_changeable : boolean;
2025-07-07 12:10:52 +02:00
groups_value : Array<_espe.type.group_id>;
2025-04-01 04:20:59 +00:00
},
{
"expiry": expiry = -1,
"notification_target_url_template": notification_target_url_template = null,
"send_immediatly": send_immediatly = true,
2025-04-01 04:20:59 +00:00
} : {
expiry ?: (null | int);
notification_target_url_template ?: (null | string);
send_immediatly ?: boolean;
2025-04-01 04:20:59 +00:00
} = {
}
2025-07-07 12:10:52 +02:00
) : Promise<{id : _espe.type.invitation_id; key : _espe.type.invitation_key}>
2025-03-31 20:24:43 +00:00
{
2025-04-01 04:20:59 +00:00
/**
* @todo outsource to conf
*/
const default_lifetime : int = (60 * 60 * 24 * 7 * 2);
/**
* @todo proper salt
*/
2025-07-07 12:10:52 +02:00
const invitation_key : _espe.type.invitation_key = lib_plankton.sha256.get(
2025-04-01 04:20:59 +00:00
(
name_value
+
"/"
+
2025-04-02 21:01:11 +00:00
lib_plankton.base.get_current_timestamp(true).toFixed(0)
2025-04-01 04:20:59 +00:00
),
"secret"
);
2025-07-07 12:10:52 +02:00
const invitation_object : _espe.type.invitation_object = {
"key": invitation_key,
2025-04-01 04:20:59 +00:00
"expiry": (
((expiry !== null) && (expiry < 0))
?
2025-04-02 21:01:11 +00:00
(lib_plankton.base.get_current_timestamp(true) + default_lifetime)
2025-04-01 04:20:59 +00:00
:
expiry
),
2025-04-12 10:20:58 +00:00
"name_changeable": name_changeable,
2025-04-01 04:20:59 +00:00
"name_value": name_value,
2025-04-12 10:20:58 +00:00
"email_address_changeable": email_address_changeable,
2025-04-01 04:20:59 +00:00
"email_address_value": email_address_value,
2025-04-12 10:20:58 +00:00
"groups_changeable": groups_changeable,
2025-04-01 04:20:59 +00:00
"groups_value": groups_value,
};
2025-07-07 12:10:52 +02:00
const invitation_id : _espe.type.invitation_id = await _espe.repository.invitation.create(invitation_object);
// send link
{
if (! send_immediatly)
{
// do nothing
}
else
{
if (
! (
(
(email_address_value !== null)
&&
(email_address_value !== "")
)
&&
(notification_target_url_template !== null)
)
)
{
lib_plankton.log._warning(
2025-07-07 12:10:52 +02:00
"espe.service.invitation.create.email.condition_unmet",
{
"details": {
"provided_address": email_address_value,
"notification_target_url_template": notification_target_url_template,
},
}
);
}
else
{
const url : (null | string) = _espe.helpers.frontend_url_get(
notification_target_url_template,
{
2025-07-07 12:10:52 +02:00
"key": invitation_key,
}
);
try {
await _espe.helpers.email_send(
[email_address_value],
lib_plankton.translate.get(
"email.invitation.subject",
{
}
),
lib_plankton.translate.get(
"email.invitation.body",
{
"url": (url ?? "?"),
}
),
);
}
catch (error)
{
lib_plankton.log._error(
2025-07-07 12:10:52 +02:00
"espe.service.invitation.create.email.could_not_be_sent",
{
"details": {
"provided_address": email_address_value,
"error": String(error),
},
}
);
}
}
}
}
2025-04-01 04:20:59 +00:00
return {
2025-07-07 12:10:52 +02:00
"id": invitation_id,
"key": invitation_key,
2025-04-01 04:20:59 +00:00
};
2025-03-31 20:24:43 +00:00
}
/**
2025-04-02 21:01:11 +00:00
*/
export function get_by_id(
2025-07-07 12:10:52 +02:00
id : _espe.type.invitation_id
) : Promise<_espe.type.invitation_object>
{
2025-07-07 12:10:52 +02:00
return _espe.repository.invitation.read(id)
}
/**
*/
function get_by_key(
2025-07-07 12:10:52 +02:00
key : _espe.type.invitation_key
) : Promise<_espe.type.invitation_object>
2025-04-02 21:01:11 +00:00
{
return (
2025-07-07 12:10:52 +02:00
_espe.repository.invitation.identify(key)
2025-04-02 21:01:11 +00:00
.then(
2025-07-07 12:10:52 +02:00
(id) => _espe.repository.invitation.read(id)
2025-04-02 21:01:11 +00:00
)
);
}
/**
2025-03-31 20:24:43 +00:00
*/
2025-04-01 04:20:59 +00:00
export async function examine(
2025-07-07 12:10:52 +02:00
key : _espe.type.invitation_key
) : Promise<_espe.type.invitation_object>
2025-03-31 20:24:43 +00:00
{
2025-07-07 12:10:52 +02:00
let invitation_object : (null | _espe.type.invitation_object);
2025-04-02 21:01:11 +00:00
try {
2025-07-07 12:10:52 +02:00
invitation_object = await get_by_key(key);
2025-04-02 21:01:11 +00:00
}
catch (error) {
2025-07-07 12:10:52 +02:00
invitation_object = null;
2025-04-02 21:01:11 +00:00
}
2025-07-07 12:10:52 +02:00
if (invitation_object === null) {
2025-04-02 21:01:11 +00:00
return Promise.reject(new Error("not found"))
}
else {
const now : int = lib_plankton.base.get_current_timestamp(true);
2025-07-07 12:10:52 +02:00
if ((invitation_object.expiry !== null) && (invitation_object.expiry < now)) {
2025-04-02 21:01:11 +00:00
return Promise.reject(new Error("expired"));
}
else {
2025-07-07 12:10:52 +02:00
return Promise.resolve(invitation_object);
2025-04-02 21:01:11 +00:00
}
}
2025-03-31 20:24:43 +00:00
}
/**
*/
2025-04-01 04:20:59 +00:00
export async function accept(
2025-07-07 12:10:52 +02:00
key : _espe.type.invitation_key,
2025-07-03 08:53:05 +00:00
data : {
name : (null | string);
2025-07-07 12:10:52 +02:00
groups : (null | Array<_espe.type.group_id>);
2025-07-03 08:53:05 +00:00
email_address : (null | string);
password : (null | string);
}
)
: Promise<
Array<
{
incident : string;
details : Record<string, any>;
}
>
>
2025-03-31 20:24:43 +00:00
{
2025-07-07 12:10:52 +02:00
const invitation_id : _espe.type.invitation_id = await _espe.repository.invitation.identify(key);
2025-07-03 08:53:05 +00:00
/**
* might throw, but that's fine, since caught and handled in the API action
*/
2025-07-07 12:10:52 +02:00
const invitation_object : _espe.type.invitation_object = await _espe.repository.invitation.read(invitation_id);
2025-04-03 12:18:01 +00:00
const now : int = lib_plankton.base.get_current_timestamp(true);
2025-07-07 12:10:52 +02:00
if ((invitation_object.expiry !== null) && (invitation_object.expiry < now))
2025-07-03 08:53:05 +00:00
{
2025-04-03 12:18:01 +00:00
return Promise.reject(new Error("expired"));
}
2025-07-03 08:53:05 +00:00
else
{
const password : string = (
(
(data.password !== null)
&&
(data.password !== "")
)
?
data.password
:
_espe.service.member.generate_password()
);
const flaws_password : Array<
2025-04-03 12:18:01 +00:00
{
2025-07-03 08:53:05 +00:00
incident : string;
details : Record<string, any>;
2025-04-03 12:18:01 +00:00
}
2025-07-03 08:53:05 +00:00
> = _espe.service.member.validate_password(password);
if (flaws_password.length > 0)
{
return (
flaws_password
.map(flaw => ({"incident": ("password_" + flaw.incident), "details": flaw.details}))
);
}
else
{
2025-07-07 12:10:52 +02:00
if (
(invitation_object.name_value === null)
&&
(data.name === null)
)
{
throw (new Error("no name provided"));
}
else
{
const member_id : _espe.type.member_id = await _espe.service.member.add(
{
"name": (
(
invitation_object.name_changeable
?
data.name
:
invitation_object.name_value
) as string
),
"email_address": (
(
invitation_object.email_address_changeable
&&
(data.email_address !== null)
)
?
data.email_address
:
invitation_object.email_address_value
),
"groups": (
(
invitation_object.groups_changeable
?
data.groups
:
invitation_object.groups_value
)
??
[]
),
"password": password,
}
);
await _espe.repository.invitation.delete_(invitation_id);
return [];
}
2025-07-03 08:53:05 +00:00
}
2025-04-03 12:18:01 +00:00
}
2025-03-31 20:24:43 +00:00
}
}