/* 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 . */ namespace _espe.service.invitation { /** */ export function list( ) : Promise< Array< { id : _espe.type.invitation_id; key : _espe.type.invitation_key; expiry : (null | int); name_value : (null | string); } > > { return ( _espe.repository.invitation.dump() .then( entries => Promise.resolve( entries.map( entry => ({ "id": entry.id, "key": entry.object.key, "expiry": entry.object.expiry, "name_value": entry.object.name_value }) ) ) ) ); } /** */ export async function create( { "name_changeable": name_changeable, "name_value": name_value, "email_address_changeable": email_address_changeable, "email_address_value": email_address_value, "groups_changeable": groups_changeable, "groups_value": groups_value, } : { name_changeable : boolean; name_value : string; email_address_changeable : boolean; email_address_value : (null | string); groups_changeable : boolean; groups_value : Array<_espe.type.group_id>; }, { "expiry": expiry = -1, "notification_target_url_template": notification_target_url_template = null, "send_immediatly": send_immediatly = true, } : { expiry ?: (null | int); notification_target_url_template ?: (null | string); send_immediatly ?: boolean; } = { } ) : Promise<{id : _espe.type.invitation_id; key : _espe.type.invitation_key}> { /** * @todo outsource to conf */ const default_lifetime : int = (60 * 60 * 24 * 7 * 2); /** * @todo proper salt */ const invitation_key : _espe.type.invitation_key = lib_plankton.sha256.get( ( name_value + "/" + lib_plankton.base.get_current_timestamp(true).toFixed(0) ), "secret" ); const invitation_object : _espe.type.invitation_object = { "key": invitation_key, "expiry": ( ((expiry !== null) && (expiry < 0)) ? (lib_plankton.base.get_current_timestamp(true) + default_lifetime) : expiry ), "name_changeable": name_changeable, "name_value": name_value, "email_address_changeable": email_address_changeable, "email_address_value": email_address_value, "groups_changeable": groups_changeable, "groups_value": groups_value, }; 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( "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, { "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( "espe.service.invitation.create.email.could_not_be_sent", { "details": { "provided_address": email_address_value, "error": String(error), }, } ); } } } } return { "id": invitation_id, "key": invitation_key, }; } /** */ export function get_by_id( id : _espe.type.invitation_id ) : Promise<_espe.type.invitation_object> { return _espe.repository.invitation.read(id) } /** */ function get_by_key( key : _espe.type.invitation_key ) : Promise<_espe.type.invitation_object> { return ( _espe.repository.invitation.identify(key) .then( (id) => _espe.repository.invitation.read(id) ) ); } /** */ export async function examine( key : _espe.type.invitation_key ) : Promise<_espe.type.invitation_object> { let invitation_object : (null | _espe.type.invitation_object); try { invitation_object = await get_by_key(key); } catch (error) { invitation_object = null; } if (invitation_object === null) { return Promise.reject(new Error("not found")) } else { const now : int = lib_plankton.base.get_current_timestamp(true); if ((invitation_object.expiry !== null) && (invitation_object.expiry < now)) { return Promise.reject(new Error("expired")); } else { return Promise.resolve(invitation_object); } } } /** */ export async function accept( key : _espe.type.invitation_key, data : { name : (null | string); groups : (null | Array<_espe.type.group_id>); email_address : (null | string); password : (null | string); } ) : Promise< Array< { incident : string; details : Record; } > > { const invitation_id : _espe.type.invitation_id = await _espe.repository.invitation.identify(key); /** * might throw, but that's fine, since caught and handled in the API action */ const invitation_object : _espe.type.invitation_object = await _espe.repository.invitation.read(invitation_id); const now : int = lib_plankton.base.get_current_timestamp(true); if ((invitation_object.expiry !== null) && (invitation_object.expiry < now)) { return Promise.reject(new Error("expired")); } else { const password : string = ( ( (data.password !== null) && (data.password !== "") ) ? data.password : _espe.service.member.generate_password() ); const flaws_password : Array< { incident : string; details : Record; } > = _espe.service.member.validate_password(password); if (flaws_password.length > 0) { return ( flaws_password .map(flaw => ({"incident": ("password_" + flaw.incident), "details": flaw.details})) ); } else { 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 []; } } } } }