Merge branch 'dev-auto_register'

This commit is contained in:
roydfalk 2024-06-23 13:53:48 +02:00
commit 2b1e99d1d6
15 changed files with 874 additions and 322 deletions

View file

@ -923,6 +923,16 @@ declare namespace lib_plankton.file {
*/ */
function delete_(path: string): Promise<void>; function delete_(path: string): Promise<void>;
} }
declare namespace lib_plankton.email {
/**
*/
function send(smtp_credentials: {
host: string;
port: int;
username: string;
password: string;
}, sender: string, receivers: Array<string>, subject: string, content: string): Promise<void>;
}
declare namespace lib_plankton.log { declare namespace lib_plankton.log {
/** /**
*/ */
@ -996,10 +1006,40 @@ declare namespace lib_plankton.log {
* the path of the log file * the path of the log file
*/ */
private path; private path;
/**
*/
private human_readable;
/** /**
* [constructor] * [constructor]
*/ */
constructor(path: string); constructor(path: string, human_readable: boolean);
/**
*/
add(entry: type_entry): void;
}
}
declare namespace lib_plankton.log {
/**
*/
class class_channel_email extends class_channel {
/**
*/
private smtp_credentials;
/**
*/
private sender;
/**
*/
private receivers;
/**
* [constructor]
*/
constructor(smtp_credentials: {
host: string;
port: int;
username: string;
password: string;
}, sender: string, receivers: Array<string>);
/** /**
*/ */
add(entry: type_entry): void; add(entry: type_entry): void;

View file

@ -2173,6 +2173,100 @@ var lib_plankton;
file.delete_ = delete_; file.delete_ = delete_;
})(file = lib_plankton.file || (lib_plankton.file = {})); })(file = lib_plankton.file || (lib_plankton.file = {}));
})(lib_plankton || (lib_plankton = {})); })(lib_plankton || (lib_plankton = {}));
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
/*
This file is part of »bacterio-plankton:email«.
Copyright 2016-2023 'Christian Fraß, Christian Neubauer, Martin Springwald GbR'
<info@greenscale.de>
»bacterio-plankton:email« is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
»bacterio-plankton:lang« 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with »bacterio-plankton:email«. If not, see <http://www.gnu.org/licenses/>.
*/
var lib_plankton;
(function (lib_plankton) {
var email;
(function (email) {
/**
*/
function send(smtp_credentials, sender, receivers, subject, content) {
return __awaiter(this, void 0, void 0, function () {
var nm_nodemailer, transporter, info;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
nm_nodemailer = require("nodemailer");
transporter = nm_nodemailer.createTransport({
"host": smtp_credentials.host,
"port": smtp_credentials.port,
"secure": false,
"auth": {
"user": smtp_credentials.username,
"pass": smtp_credentials.password
},
"debug": true
});
return [4 /*yield*/, transporter.sendMail({
"from": sender,
"to": receivers.join(", "),
"subject": subject,
"text": content
})];
case 1:
info = _a.sent();
return [2 /*return*/];
}
});
});
}
email.send = send;
})(email = lib_plankton.email || (lib_plankton.email = {}));
})(lib_plankton || (lib_plankton = {}));
var __extends = (this && this.__extends) || (function () { var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) { var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf || extendStatics = Object.setPrototypeOf ||
@ -2445,9 +2539,10 @@ var lib_plankton;
/** /**
* [constructor] * [constructor]
*/ */
function class_channel_file(path) { function class_channel_file(path, human_readable) {
var _this = _super.call(this) || this; var _this = _super.call(this) || this;
_this.path = path; _this.path = path;
_this.human_readable = human_readable;
return _this; return _this;
} }
/** /**
@ -2455,24 +2550,40 @@ var lib_plankton;
class_channel_file.prototype.add = function (entry) { class_channel_file.prototype.add = function (entry) {
var _this = this; var _this = this;
var nm_fs = require("fs"); var nm_fs = require("fs");
nm_fs.writeFile(this.path, { var line = (this.human_readable
? (("<" + (new Date(Date.now())).toISOString().slice(0, 19) + ">")
+
" "
+
("[" + log.level_show(entry.level) + "]")
+
" "
+
("" + entry.incident + "")
+
": "
+
JSON.stringify(entry.details, undefined, " ")
+
"\n")
: (JSON.stringify({
"timestamp": lib_plankton.base.get_current_timestamp(),
"level_number": entry.level,
"level_name": log.level_show(entry.level),
"incident": entry.incident,
"details": entry.details
})
+
"\n"));
nm_fs.writeFile(this.path, line, {
"flag": "a+" "flag": "a+"
}, (("<" + (new Date(Date.now())).toISOString().slice(0, 19) + ">") }, function (error) {
+ if (error !== null) {
" " process.stderr.write('-- [plankton] could not add log entry to file ' + _this.path + "\n");
+ }
("[" + log.level_show(entry.level) + "]") else {
+ // do nothing
" " }
+
("" + entry.incident + "")
+
": "
+
JSON.stringify(entry.details, undefined, " ")
+
"\n"), function (error) {
process.stderr.write('-- [plankton] could not add log entry to file ' + _this.path + "\n");
}); });
}; };
return class_channel_file; return class_channel_file;
@ -2496,6 +2607,58 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with »bacterio-plankton:log«. If not, see <http://www.gnu.org/licenses/>.
*/
var lib_plankton;
(function (lib_plankton) {
var log;
(function (log) {
/**
*/
var class_channel_email = /** @class */ (function (_super) {
__extends(class_channel_email, _super);
/**
* [constructor]
*/
function class_channel_email(smtp_credentials, sender, receivers) {
var _this = _super.call(this) || this;
_this.smtp_credentials = smtp_credentials;
_this.sender = sender;
_this.receivers = receivers;
return _this;
}
/**
*/
class_channel_email.prototype.add = function (entry) {
var nm_fs = require("fs");
lib_plankton.email.send(this.smtp_credentials, this.sender, this.receivers, (("[" + log.level_show(entry.level) + "]")
+
" "
+
("" + entry.incident + "")), JSON.stringify(entry.details, undefined, " "));
};
return class_channel_email;
}(log.class_channel));
log.class_channel_email = class_channel_email;
})(log = lib_plankton.log || (lib_plankton.log = {}));
})(lib_plankton || (lib_plankton = {}));
/*
This file is part of »bacterio-plankton:log«.
Copyright 2016-2023 'Christian Fraß, Christian Neubauer, Martin Springwald GbR'
<info@greenscale.de>
»bacterio-plankton:log« is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
»bacterio-plankton:lang« 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License You should have received a copy of the GNU Lesser General Public License
along with »bacterio-plankton:log«. If not, see <http://www.gnu.org/licenses/>. along with »bacterio-plankton:log«. If not, see <http://www.gnu.org/licenses/>.
*/ */
@ -2635,7 +2798,7 @@ var lib_plankton;
/** /**
*/ */
function channel_make(description) { function channel_make(description) {
var _a, _b, _c, _d; var _a, _b, _c, _d, _e;
switch (description.kind) { switch (description.kind) {
default: { default: {
throw (new Error("unhandled log channel kind: " + description.kind)); throw (new Error("unhandled log channel kind: " + description.kind));
@ -2646,11 +2809,15 @@ var lib_plankton;
break; break;
} }
case "file": { case "file": {
return (new log.class_channel_minlevel(new log.class_channel_file((_b = description.data["path"]) !== null && _b !== void 0 ? _b : "/tmp/plankton.log"), translate_level((_c = description.data["threshold"]) !== null && _c !== void 0 ? _c : "debug"))); return (new log.class_channel_minlevel(new log.class_channel_file(((_b = description.data["path"]) !== null && _b !== void 0 ? _b : "/tmp/plankton.log"), false), translate_level((_c = description.data["threshold"]) !== null && _c !== void 0 ? _c : "debug")));
break;
}
case "email": {
return (new log.class_channel_minlevel(new log.class_channel_email(description.data["smtp_credentials"], description.data["sender"], description.data["receivers"]), translate_level((_d = description.data["threshold"]) !== null && _d !== void 0 ? _d : "debug")));
break; break;
} }
case "notify": { case "notify": {
return (new log.class_channel_minlevel(new log.class_channel_notify(), translate_level((_d = description.data["threshold"]) !== null && _d !== void 0 ? _d : "debug"))); return (new log.class_channel_minlevel(new log.class_channel_notify(), translate_level((_e = description.data["threshold"]) !== null && _e !== void 0 ? _e : "debug")));
break; break;
} }
} }
@ -10453,7 +10620,7 @@ var lib_plankton;
// "input_shape": options.input_type, // "input_shape": options.input_type,
// "output_shape": options.output_type, // "output_shape": options.output_type,
}); });
lib_plankton.log.info("rest_route_added", { lib_plankton.log.debug("rest_route_added", {
"http_method": http_method, "http_method": http_method,
"path": path, "path": path,
// "routetree": rest.routetree, // "routetree": rest.routetree,

69
misc/conf.example.json Normal file
View file

@ -0,0 +1,69 @@
{
"general": {
"language": null,
"verbosity": "info",
"verification_secret": null
},
"server": {
"port": 4916,
"path_base": ""
},
"database": {
"kind": "sqlite",
"data": {
"path": "data.sqlite"
}
},
"email_sending": {
"kind": "console",
"data": {
}
},
"session_management": {
"in_memory": false,
"drop_all_at_start": false,
"lifetime": 7200
},
"settings": {
"organisation": {
"name": "Example Orginsation",
"domain": "example.org"
},
"misc": {
"prefix_for_veiled_email_addresses": "member-",
"facultative_membership_number": true,
"auto_register": true
},
"summon_email": {
"remark": null
},
"password_policy": {
"minimum_length": 4,
"maximum_length": 12,
"must_contain_letter": true,
"must_contain_number": false,
"must_contain_special_character": false
},
"password_change": {
"cooldown_time": 300
},
"name_index": {
"veil": false,
"salt": null
},
"connections": {
"frontend_url_base": null,
"login_url": null
}
},
"admins": [
{
"name": "admin",
"password_image": "$2b$12$xOa6iWPOMjiwJ3oIOZWDGu/w2Ca/eKLHWE7aDItkNsP/79nJk065i",
"email_address": "espe-admin@example.org"
}
],
"output": {
"authelia": "/tmp/authelia-users.yml"
}
}

View file

@ -22,15 +22,15 @@ namespace _espe.api
rest_subject : lib_plankton.rest.type_rest rest_subject : lib_plankton.rest.type_rest
) : void ) : void
{ {
register<_espe.service.member.type_value, null>( register<null, null>(
rest_subject, rest_subject,
lib_plankton.http.enum_method.delete, lib_plankton.http.enum_method.delete,
"/member/:id", "/member/delete/:id",
{ {
"description": "löscht ein vorhandenes Mitglied", "description": "löscht ein vorhandenes Mitglied",
"restriction": restriction_logged_in, "restriction": restriction_logged_in,
"execution": async ({"path_parameters": path_parameters}) => { "execution": async ({"path_parameters": path_parameters}) => {
const member_id : _espe.service.member.type_id = parseInt(path_parameters["id"]); const member_id : _espe.type.member_id = parseInt(path_parameters["id"]);
await _espe.service.member.remove(member_id); await _espe.service.member.remove(member_id);
return Promise.resolve({ return Promise.resolve({
"status_code": 200, "status_code": 200,

View file

@ -27,6 +27,7 @@ namespace _espe.api
membership_number : (null | string); membership_number : (null | string);
name_real_value : string; name_real_value : string;
email_address_private : (null | string); email_address_private : (null | string);
notification_target_url_template ?: (null | string);
}, },
( (
string string
@ -59,6 +60,11 @@ namespace _espe.api
"nullable": true, "nullable": true,
"description": "private E-Mail-Adresse" "description": "private E-Mail-Adresse"
}, },
"notification_target_url_template": {
"type": "string",
"nullable": true,
"description": "Platz-Halter: id"
},
}, },
"required": [ "required": [
"membership_number", "membership_number",
@ -105,6 +111,24 @@ namespace _espe.api
), ),
} }
); );
if (! _espe.conf.get().settings.misc.auto_register) {
// do nothing
}
else {
// TODO: Werte in Konfiguration auslagern
await _espe.service.member.register(
member_id,
{
"email_use_veiled_address": false,
"email_use_nominal_address": false,
"email_redirect_to_private_address": false,
"password": null,
},
{
"notification_target_url_template": input.notification_target_url_template,
}
);
}
return Promise.resolve({ return Promise.resolve({
"status_code": 201, "status_code": 201,
"data": member_id "data": member_id

View file

@ -52,7 +52,7 @@ namespace _espe.api
_espe.api.register_member_list(rest_subject); _espe.api.register_member_list(rest_subject);
_espe.api.register_member_read(rest_subject); _espe.api.register_member_read(rest_subject);
_espe.api.register_member_modify(rest_subject); _espe.api.register_member_modify(rest_subject);
// _espe.api.register_member_delete(rest_subject); _espe.api.register_member_delete(rest_subject);
// password_change // password_change
{ {
_espe.api.register_member_password_change_initialize(rest_subject); _espe.api.register_member_password_change_initialize(rest_subject);

View file

@ -16,26 +16,68 @@ You should have received a copy of the GNU General Public License along with thi
namespace _espe.conf namespace _espe.conf
{ {
/**
*/
type type_log_threshold = (
"debug"
|
"info"
|
"notice"
|
"warning"
|
"error"
);
/**
*/
type type_log_format = (
"jsonl"
|
"human_readable"
);
/** /**
*/ */
export type type_conf = { export type type_conf = {
general : { general : {
language : (null | string); language : (null | string);
verbosity : (
"none"
|
"debug"
|
"notice"
|
"info"
|
"warning"
|
"error"
);
verification_secret : (null | string); verification_secret : (null | string);
}; };
log : Array<
{
kind : "stdout";
data : {
threshold : type_log_threshold;
};
}
|
{
kind : "file";
data : {
threshold : type_log_threshold;
path : string;
};
}
|
{
kind : "email";
data : {
threshold : type_log_threshold;
smtp_credentials : {
host : string;
port : int;
username : string;
password : string;
};
sender : string;
receivers : Array<string>;
};
}
>;
server : { server : {
port : int; port : int;
path_base : string; path_base : string;
@ -102,6 +144,7 @@ namespace _espe.conf
misc : { misc : {
prefix_for_veiled_email_addresses : string; prefix_for_veiled_email_addresses : string;
facultative_membership_number : boolean; facultative_membership_number : boolean;
auto_register : boolean;
}; };
summon_email : { summon_email : {
remark : string; remark : string;
@ -150,164 +193,366 @@ namespace _espe.conf
conf_raw : any conf_raw : any
) : void ) : void
{ {
_data = { switch (conf_raw["version"]) {
"general": ( case 1: {
((node_general) => ({ _data = {
"language": (node_general["language"] ?? null), "general": (
"verbosity": (node_general["verbosity"] ?? "notice"), ((node_general) => ({
"verification_secret": (node_general["verification_secret"] ?? null), "language": (node_general["language"] ?? null),
})) (conf_raw["general"] ?? {}) "verification_secret": (node_general["verification_secret"] ?? null),
), })) (conf_raw["general"] ?? {})
"server": (
((node_server) => ({
"port": (node_server["port"] ?? 3593),
"path_base": (node_server["path_base"] ?? ""),
})) (conf_raw["server"] ?? {})
),
"database": (
((node_database) => {
const kind : string = (node_database["kind"] ?? "sqlite");
const node_database_data_raw = (node_database["data"] ?? {});
switch (kind) {
case "sqlite": {
return {
"kind": kind,
"data": {
"path": (node_database_data_raw["path"] ?? "data.sqlite"),
}
};
break;
}
case "postgresql": {
return {
"kind": kind,
"data": node_database_data_raw,
};
break;
}
default: {
throw (new Error("unhandled"));
break;
}
}
}) (conf_raw["database"] ?? {})
),
"email_sending": (
((node_email_sending) => {
const kind : string = (node_email_sending["kind"] ?? "regular");
const data_raw = (node_email_sending["data"] ?? {});
switch (kind) {
case "regular": {
return {
"kind": kind,
"data": {
"smtp_credentials": (data_raw["smtp_credentials"] ?? null),
"sender": data_raw["sender"],
}
};
break;
}
case "redirect": {
return {
"kind": kind,
"data": {
"smtp_credentials": (data_raw["smtp_credentials"] ?? null),
"sender": data_raw["sender"],
"target": data_raw["target"],
}
};
break;
}
case "console": {
return {
"kind": kind,
"data": {
}
};
break;
}
case "drop": {
return {
"kind": kind,
"data": {
}
};
break;
}
default: {
throw (new Error("unhandled"));
break;
}
}
}) (conf_raw["email_sending"] ?? {})
),
"session_management": (
((node_session_management) => ({
"in_memory": (node_session_management["in_memory"] ?? true),
"drop_all_at_start": (node_session_management["drop_all_at_start"] ?? true),
"lifetime": (node_session_management["lifetime"] ?? 900),
})) (conf_raw["session_management"] ?? {})
),
"settings": (
((node_settings) => ({
"organisation": {
"name": ((node_settings["organisation"] ?? {})["name"] ?? "Example Orginsation"), // TODO: mandatory?
"domain": ((node_settings["organisation"] ?? {})["domain"] ?? "example.org"), // TODO: mandatory?
},
"misc": (
((node_settings_misc) => ({
"prefix_for_veiled_email_addresses": (node_settings_misc["prefix_for_veiled_email_addresses"] ?? "member-"),
"facultative_membership_number": (node_settings_misc["facultative_membership_number"] ?? false),
})) (node_settings["misc"] ?? {})
), ),
"summon_email": ( "log": [
((node_settings_summon_email) => ({ {
"remark": (node_settings_summon_email["remark"] ?? null), "kind": "stdout",
})) (node_settings["summon_email"] ?? {}) "data": {
"threshold": ((conf_raw["general"] ?? {})["verbosity"] ?? "notice"),
}
},
],
"server": (
((node_server) => ({
"port": (node_server["port"] ?? 4916),
"path_base": (node_server["path_base"] ?? ""),
})) (conf_raw["server"] ?? {})
), ),
"password_policy": ( "database": (
((node_settings_password_policy) => ({ ((node_database) => {
"minimum_length": ( const kind : string = (node_database["kind"] ?? "sqlite");
("minimum_length" in node_settings_password_policy) const node_database_data_raw = (node_database["data"] ?? {});
? node_settings_password_policy["minimum_length"] switch (kind) {
: 8 case "sqlite": {
return {
"kind": kind,
"data": {
"path": (node_database_data_raw["path"] ?? "data.sqlite"),
}
};
break;
}
case "postgresql": {
return {
"kind": kind,
"data": node_database_data_raw,
};
break;
}
default: {
throw (new Error("unhandled"));
break;
}
}
}) (conf_raw["database"] ?? {})
),
"email_sending": (
((node_email_sending) => {
const kind : string = (node_email_sending["kind"] ?? "console");
const data_raw = (node_email_sending["data"] ?? {});
switch (kind) {
case "regular": {
return {
"kind": kind,
"data": {
"smtp_credentials": (data_raw["smtp_credentials"] ?? null),
"sender": data_raw["sender"],
}
};
break;
}
case "redirect": {
return {
"kind": kind,
"data": {
"smtp_credentials": (data_raw["smtp_credentials"] ?? null),
"sender": data_raw["sender"],
"target": data_raw["target"],
}
};
break;
}
case "console": {
return {
"kind": kind,
"data": {
}
};
break;
}
case "drop": {
return {
"kind": kind,
"data": {
}
};
break;
}
default: {
throw (new Error("unhandled"));
break;
}
}
}) (conf_raw["email_sending"] ?? {})
),
"session_management": (
((node_session_management) => ({
"in_memory": (node_session_management["in_memory"] ?? true),
"drop_all_at_start": (node_session_management["drop_all_at_start"] ?? true),
"lifetime": (node_session_management["lifetime"] ?? 900),
})) (conf_raw["session_management"] ?? {})
),
"settings": (
((node_settings) => ({
"organisation": {
"name": ((node_settings["organisation"] ?? {})["name"] ?? "Example Orginsation"), // TODO: mandatory?
"domain": ((node_settings["organisation"] ?? {})["domain"] ?? "example.org"), // TODO: mandatory?
},
"misc": (
((node_settings_misc) => ({
"prefix_for_veiled_email_addresses": (node_settings_misc["prefix_for_veiled_email_addresses"] ?? "member-"),
"facultative_membership_number": (node_settings_misc["facultative_membership_number"] ?? false),
"auto_register": (node_settings_misc["auto_register"] ?? false),
})) (node_settings["misc"] ?? {})
), ),
"maximum_length": ( "summon_email": (
("maximum_length" in node_settings_password_policy) ((node_settings_summon_email) => ({
? node_settings_password_policy["maximum_length"] "remark": (node_settings_summon_email["remark"] ?? null),
: 240 })) (node_settings["summon_email"] ?? {})
), ),
"must_contain_letter": (node_settings_password_policy["must_contain_letter"] ?? true), "password_policy": (
"must_contain_number": (node_settings_password_policy["must_contain_number"] ?? true), ((node_settings_password_policy) => ({
"must_contain_special_character": (node_settings_password_policy["must_contain_special_character"] ?? true), "minimum_length": (
})) (node_settings["password_policy"] ?? {}) ("minimum_length" in node_settings_password_policy)
? node_settings_password_policy["minimum_length"]
: 8
),
"maximum_length": (
("maximum_length" in node_settings_password_policy)
? node_settings_password_policy["maximum_length"]
: 240
),
"must_contain_letter": (node_settings_password_policy["must_contain_letter"] ?? true),
"must_contain_number": (node_settings_password_policy["must_contain_number"] ?? true),
"must_contain_special_character": (node_settings_password_policy["must_contain_special_character"] ?? true),
})) (node_settings["password_policy"] ?? {})
),
"password_change": (
((node_settings_password_change) => ({
"cooldown_time": (node_settings_password_change["cooldown_time"] ?? 86400),
})) (node_settings["password_change"] ?? {})
),
"name_index": (
((node_settings_password_policy) => ({
"veil": (node_settings_password_policy["veil"] ?? true),
"salt": (node_settings_password_policy["salt"] ?? ""),
})) (node_settings["name_index"] ?? {})
),
"connections": (
((node_settings_connections) => ({
"frontend_url_base": (node_settings_connections["frontend_url_base"] ?? null),
"login_url": (node_settings_connections["login_url"] ?? null),
})) (node_settings["connections"] ?? {})
),
})) (conf_raw["settings"] ?? {})
), ),
"password_change": ( "admins": (conf_raw["admins"] ?? []),
((node_settings_password_change) => ({ "output": (
"cooldown_time": (node_settings_password_change["cooldown_time"] ?? 86400), ((node_session_output) => ({
})) (node_settings["password_change"] ?? {}) "authelia": (node_session_output["authelia"] ?? null),
})) (conf_raw["output"] ?? {})
), ),
"name_index": ( };
((node_settings_password_policy) => ({ break;
"veil": (node_settings_password_policy["veil"] ?? false), }
"salt": (node_settings_password_policy["salt"] ?? null), case 2: {
})) (node_settings["name_index"] ?? {}) _data = {
"general": (
((node_general) => ({
"language": (node_general["language"] ?? null),
"verification_secret": (node_general["verification_secret"] ?? null),
})) (conf_raw["general"] ?? {})
), ),
"connections": ( "log": (
((node_settings_connections) => ({ ((node_log) => node_log.map(
"frontend_url_base": (node_settings_connections["frontend_url_base"] ?? null), (node_log_entry : any) => ({
"login_url": (node_settings_connections["login_url"] ?? null), "kind": node_log_entry["kind"],
})) (node_settings["connections"] ?? {}) "data": Object.assign(
{
"format": "human_readable",
"threshold": "notice",
},
(node_log_entry["data"] ?? {})
)
})
)) (
conf_raw["log"]
??
[
{
"kind": "console",
"data": {
}
},
]
)
), ),
})) (conf_raw["settings"] ?? {}) "server": (
), ((node_server) => ({
"admins": (conf_raw["admins"] ?? []), "port": (node_server["port"] ?? 4916),
"output": ( "path_base": (node_server["path_base"] ?? ""),
((node_session_output) => ({ })) (conf_raw["server"] ?? {})
"authelia": (node_session_output["authelia"] ?? null), ),
})) (conf_raw["output"] ?? {}) "database": (
), ((node_database) => {
}; const kind : string = (node_database["kind"] ?? "sqlite");
const node_database_data_raw = (node_database["data"] ?? {});
switch (kind) {
case "sqlite": {
return {
"kind": kind,
"data": {
"path": (node_database_data_raw["path"] ?? "data.sqlite"),
}
};
break;
}
case "postgresql": {
return {
"kind": kind,
"data": node_database_data_raw,
};
break;
}
default: {
throw (new Error("unhandled"));
break;
}
}
}) (conf_raw["database"] ?? {})
),
"email_sending": (
((node_email_sending) => {
const kind : string = (node_email_sending["kind"] ?? "console");
const data_raw = (node_email_sending["data"] ?? {});
switch (kind) {
case "regular": {
return {
"kind": kind,
"data": {
"smtp_credentials": (data_raw["smtp_credentials"] ?? null),
"sender": data_raw["sender"],
}
};
break;
}
case "redirect": {
return {
"kind": kind,
"data": {
"smtp_credentials": (data_raw["smtp_credentials"] ?? null),
"sender": data_raw["sender"],
"target": data_raw["target"],
}
};
break;
}
case "console": {
return {
"kind": kind,
"data": {
}
};
break;
}
case "drop": {
return {
"kind": kind,
"data": {
}
};
break;
}
default: {
throw (new Error("unhandled"));
break;
}
}
}) (conf_raw["email_sending"] ?? {})
),
"session_management": (
((node_session_management) => ({
"in_memory": (node_session_management["in_memory"] ?? true),
"drop_all_at_start": (node_session_management["drop_all_at_start"] ?? true),
"lifetime": (node_session_management["lifetime"] ?? 900),
})) (conf_raw["session_management"] ?? {})
),
"settings": (
((node_settings) => ({
"organisation": {
"name": ((node_settings["organisation"] ?? {})["name"] ?? "Example Orginsation"), // TODO: mandatory?
"domain": ((node_settings["organisation"] ?? {})["domain"] ?? "example.org"), // TODO: mandatory?
},
"misc": (
((node_settings_misc) => ({
"prefix_for_veiled_email_addresses": (node_settings_misc["prefix_for_veiled_email_addresses"] ?? "member-"),
"facultative_membership_number": (node_settings_misc["facultative_membership_number"] ?? false),
"auto_register": (node_settings_misc["auto_register"] ?? false),
})) (node_settings["misc"] ?? {})
),
"summon_email": (
((node_settings_summon_email) => ({
"remark": (node_settings_summon_email["remark"] ?? null),
})) (node_settings["summon_email"] ?? {})
),
"password_policy": (
((node_settings_password_policy) => ({
"minimum_length": (
("minimum_length" in node_settings_password_policy)
? node_settings_password_policy["minimum_length"]
: 8
),
"maximum_length": (
("maximum_length" in node_settings_password_policy)
? node_settings_password_policy["maximum_length"]
: 240
),
"must_contain_letter": (node_settings_password_policy["must_contain_letter"] ?? true),
"must_contain_number": (node_settings_password_policy["must_contain_number"] ?? true),
"must_contain_special_character": (node_settings_password_policy["must_contain_special_character"] ?? true),
})) (node_settings["password_policy"] ?? {})
),
"password_change": (
((node_settings_password_change) => ({
"cooldown_time": (node_settings_password_change["cooldown_time"] ?? 86400),
})) (node_settings["password_change"] ?? {})
),
"name_index": (
((node_settings_password_policy) => ({
"veil": (node_settings_password_policy["veil"] ?? true),
"salt": (node_settings_password_policy["salt"] ?? ""),
})) (node_settings["name_index"] ?? {})
),
"connections": (
((node_settings_connections) => ({
"frontend_url_base": (node_settings_connections["frontend_url_base"] ?? null),
"login_url": (node_settings_connections["login_url"] ?? null),
})) (node_settings["connections"] ?? {})
),
})) (conf_raw["settings"] ?? {})
),
"admins": (conf_raw["admins"] ?? []),
"output": (
((node_session_output) => ({
"authelia": (node_session_output["authelia"] ?? null),
})) (conf_raw["output"] ?? {})
),
};
break;
}
default: {
throw (new Error("invalid conf version: " + conf_raw["version"]));
break;
}
}
} }

View file

@ -8,7 +8,8 @@
"email.registration.subject": "Registrierung erfolgt", "email.registration.subject": "Registrierung erfolgt",
"email.registration.body": "Das Mitglied '{{name_display}}' hat sich soeben registriert:\n\n{{url}}", "email.registration.body": "Das Mitglied '{{name_display}}' hat sich soeben registriert:\n\n{{url}}",
"email.activation.subject": "Freischaltung erfolgt", "email.activation.subject": "Freischaltung erfolgt",
"email.activation.body": "Hi, {{name_display}}\n\nDein Mitglieder-Konto wurde gerade freigeschalten. Du kannst dich nun anmelden:\n\nURL: {{url}}\nAnmelde-Name: {{name_login}}", "email.activation.body": "Hi, {{name_display}}\n\nDein Mitglieder-Konto wurde gerade freigeschalten. Du kannst dich nun anmelden:\n\nURL: {{url}}\nAnmelde-Name: {{name_login}}\n{{password_info}}",
"email.activation.password_info": "Passwort: {{password}}\n\nBitte ändere dein Passwort zeitnah!",
"email.password_change.initialization.subject": "Passwort-Änderung eingeleitet", "email.password_change.initialization.subject": "Passwort-Änderung eingeleitet",
"email.password_change.initialization.body": "Hi, {{name}}\n\nDie Funktion zum Ändern deines Passwortes wurde aufgerufen. Wenn du dein Passwort ändern willst, rufe folgenden Link auf:\n\n{{url}}\n", "email.password_change.initialization.body": "Hi, {{name}}\n\nDie Funktion zum Ändern deines Passwortes wurde aufgerufen. Wenn du dein Passwort ändern willst, rufe folgenden Link auf:\n\n{{url}}\n",
"email.password_change.execution.subject": "Passwort-Änderung abgeschlossen", "email.password_change.execution.subject": "Passwort-Änderung abgeschlossen",

View file

@ -8,7 +8,8 @@
"email.registration.subject": "Registration received", "email.registration.subject": "Registration received",
"email.registration.body": "The member '{{name_display}}' just registered:\n\n{{url}}", "email.registration.body": "The member '{{name_display}}' just registered:\n\n{{url}}",
"email.activation.subject": "Activated", "email.activation.subject": "Activated",
"email.activation.body": "Hi, {{name_display}}\n\nYour account has just been activated. You may login now:\n\nURL: {{url}}\nLogin name: {{name_login}}", "email.activation.body": "Hi, {{name_display}}\n\nYour account has just been activated. You may login now:\n\nURL: {{url}}\nLogin name: {{name_login}}\n{{password_info}}",
"email.activation.password_info": "Password: {{password}}\n\nPlease change your password soon!",
"email.password_change.initialization.subject": "Password change initialized", "email.password_change.initialization.subject": "Password change initialized",
"email.password_change.initialization.body": "Hi, {{name}}\n\nThe function for changing your password has been triggered. If you want to change your password, open the folloling link:\n\n{{url}}", "email.password_change.initialization.body": "Hi, {{name}}\n\nThe function for changing your password has been triggered. If you want to change your password, open the folloling link:\n\n{{url}}",
"email.password_change.execution.subject": "Password change concluded", "email.password_change.execution.subject": "Password change concluded",

View file

@ -124,61 +124,6 @@ namespace _espe.helpers
} }
/**
* @todo outsource?
*/
async function email_send_real(
smtp_credentials : {
host : string;
port : int;
username : string;
password : string;
},
receivers : Array<string>,
subject : string,
content : string,
options : {
sender ?: (null | string);
} = {}
) : Promise<void>
{
options = Object.assign(
{
"sender": /*null*/"admin@example.org",
},
options
);
lib_plankton.log.info(
"email_send_real",
{
"receivers": receivers,
"subject": subject,
}
);
const nm_nodemailer = require("nodemailer");
const transporter = nm_nodemailer.createTransport(
{
"host": smtp_credentials.host,
"port": smtp_credentials.port,
"secure": false,
"auth": {
"user": smtp_credentials.username,
"pass": smtp_credentials.password,
},
"debug": true,
}
);
const info = await transporter.sendMail(
{
"from": (options.sender ?? ""),
"to": receivers.join(", "),
"subject": subject,
"text": content,
}
)
}
/** /**
*/ */
export async function email_send( export async function email_send(
@ -207,14 +152,12 @@ namespace _espe.helpers
return Promise.reject<void>("no smtp credentials specified; add in conf as 'email_sending.data.smtp_credentials'!"); return Promise.reject<void>("no smtp credentials specified; add in conf as 'email_sending.data.smtp_credentials'!");
} }
else { else {
return email_send_real( return lib_plankton.email.send(
parameters.smtp_credentials, parameters.smtp_credentials,
parameters.sender,
receivers, receivers,
subject, subject,
content, content
{
"sender": parameters.sender,
}
); );
} }
break; break;
@ -230,8 +173,9 @@ namespace _espe.helpers
return Promise.reject<void>("no smtp credentials specified; add in conf as 'email_sending.data.smtp_credentials'!"); return Promise.reject<void>("no smtp credentials specified; add in conf as 'email_sending.data.smtp_credentials'!");
} }
else { else {
return email_send_real( return lib_plankton.email.send(
parameters.smtp_credentials, parameters.smtp_credentials,
parameters.sender,
[parameters.target], [parameters.target],
lib_plankton.string.coin( lib_plankton.string.coin(
"[REDIRECTION] {{receivers}} | {{original_subject}}", "[REDIRECTION] {{receivers}} | {{original_subject}}",
@ -240,10 +184,7 @@ namespace _espe.helpers
"original_subject": subject "original_subject": subject
} }
), ),
content, content
{
"sender": parameters.sender,
}
); );
} }
break; break;

View file

@ -21,16 +21,61 @@ async function main(
) : Promise<void> ) : Promise<void>
{ {
// init // init
await lib_plankton.translate.initialize( lib_plankton.log.conf_push(
{ [
"verbosity": 1, lib_plankton.log.channel_make(
"packages": [ {
JSON.parse(await lib_plankton.file.read("data/localization/deu.loc.json")), "kind": "stdout",
JSON.parse(await lib_plankton.file.read("data/localization/eng.loc.json")), "data": {
], "threshold": "notice",
"order": ["deu", "eng"], // "format": "human_readable",
"autopromote": false, }
} }
),
]
);
const language_codes : Array<string> = [
"deu",
"eng",
];
await (
Promise.all(
language_codes
.map(
language_code => (
lib_plankton.file.read(
lib_plankton.string.coin(
"data/localization/{{language_code}}.loc.json",
{
"language_code": language_code,
}
)
)
.then<any>(
content => (new Promise<any>(
(resolve, reject) => {
try {
resolve(JSON.parse(content));
}
catch (error) {
reject(error);
}
}
))
)
)
)
)
.then(
packages => lib_plankton.translate.initialize(
{
"verbosity": 1,
"packages": packages,
"order": language_codes,
"autopromote": false,
}
)
)
); );
// args // args
@ -138,19 +183,14 @@ async function main(
lib_plankton.translate.promote(_espe.conf.get().general.language); lib_plankton.translate.promote(_espe.conf.get().general.language);
} }
lib_plankton.log.conf_push( lib_plankton.log.conf_push(
[ _espe.conf.get().log.map(
new lib_plankton.log.class_channel_minlevel( log_output => lib_plankton.log.channel_make(
new lib_plankton.log.class_channel_stdout(),
{ {
"none": lib_plankton.log.enum_level.error, "kind": log_output.kind,
"error": lib_plankton.log.enum_level.error, "data": log_output.data
"warning": lib_plankton.log.enum_level.warning, }
"notice": lib_plankton.log.enum_level.notice, )
"info": lib_plankton.log.enum_level.info, )
"debug":lib_plankton.log.enum_level.debug,
}[_espe.conf.get().general.verbosity]
),
]
); );
// exec // exec

View file

@ -191,9 +191,18 @@ namespace _espe.service.member
/** /**
*/ */
async function send_activation_email( async function send_activation_email(
member_object : _espe.type.member_object member_object : _espe.type.member_object,
options : {
password ?: (null | string);
} = {}
) : Promise<void> ) : Promise<void>
{ {
options = Object.assign(
{
"password": null,
},
options
);
if (! member_object.enabled) { if (! member_object.enabled) {
// do nothing // do nothing
} }
@ -219,6 +228,20 @@ namespace _espe.service.member
"name_display": name_display(member_object), "name_display": name_display(member_object),
"name_login": name_login(member_object), "name_login": name_login(member_object),
"url": (_espe.conf.get().settings.connections.login_url ?? "--"), "url": (_espe.conf.get().settings.connections.login_url ?? "--"),
"password_info": (
(
(options.password === undefined)
||
(options.password === null)
)
? ""
: lib_plankton.string.coin(
lib_plankton.translate.get("email.activation.password_info"),
{
"password": options.password,
}
)
),
} }
) )
); );
@ -424,28 +447,31 @@ namespace _espe.service.member
); );
const member_object : _espe.type.member_object = await get(member_id); const member_object : _espe.type.member_object = await get(member_id);
let flaws : Array<{incident : string; details : Record<string, any>;}> = []; let flaws : Array<{incident : string; details : Record<string, any>;}> = [];
const password_set : boolean = ( let password_value : string;
(data.password !== null) let password_generated : boolean;
&&
(data.password !== "")
);
if (member_object.registered) { if (member_object.registered) {
flaws.push({"incident": "already_registered", "details": {}}); flaws.push({"incident": "already_registered", "details": {}});
password_value = "";
password_generated = false;
} }
else { else {
if ( if (
password_set
&&
(data.password !== null) (data.password !== null)
&&
(data.password !== "")
) { ) {
flaws = flaws.concat( flaws = flaws.concat(
validate_password(data.password) validate_password(data.password)
.map(flaw => ({"incident": ("password_" + flaw.incident), "details": flaw.details})) .map(flaw => ({"incident": ("password_" + flaw.incident), "details": flaw.details}))
); );
password_value = data.password;
password_generated = false;
} }
else { else {
// do nothing password_value = generate_password();
password_generated = true;
} }
} }
@ -456,7 +482,7 @@ namespace _espe.service.member
member_object.email_use_veiled_address = data.email_use_veiled_address; 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_use_nominal_address = data.email_use_nominal_address;
member_object.email_redirect_to_private_address = data.email_redirect_to_private_address; member_object.email_redirect_to_private_address = data.email_redirect_to_private_address;
member_object.password_image = await password_image(data.password); member_object.password_image = await password_image(password_value);
member_object.registered = true; member_object.registered = true;
await _espe.repository.member.update(member_id, member_object); await _espe.repository.member.update(member_id, member_object);
signal_change(); signal_change();
@ -475,29 +501,24 @@ namespace _espe.service.member
} }
) )
); );
if (url === null) { /*await*/ _espe.helpers.notify_admins(
// do nothing lib_plankton.string.coin(
} "{{head}} | {{core}}",
else { {
/*await*/ _espe.helpers.notify_admins( "head": _espe.conf.get().settings.organisation.name,
lib_plankton.string.coin( "core": lib_plankton.translate.get("email.registration.subject"),
"{{head}} | {{core}}", }
{ ),
"head": _espe.conf.get().settings.organisation.name, lib_plankton.string.coin(
"core": lib_plankton.translate.get("email.registration.subject"), lib_plankton.translate.get("email.registration.body"),
} {
), "name_display": name_display(member_object),
lib_plankton.string.coin( "url": (url ?? "?"),
lib_plankton.translate.get("email.registration.body"), }
{ )
"name_display": name_display(member_object), );
"url": url,
}
)
);
}
} }
/*await*/ send_activation_email(member_object); /*await*/ send_activation_email(member_object, {"password": password_generated ? password_value : null});
} }
return Promise.resolve(flaws); return Promise.resolve(flaws);
@ -538,6 +559,17 @@ namespace _espe.service.member
} }
/**
*/
export async function remove(
id : _espe.type.member_id
) : Promise<void>
{
await _espe.repository.member.delete_(id);
signal_change();
}
/** /**
* bereitet eine Passwort-Rücksetzung für Mitglieder vor * bereitet eine Passwort-Rücksetzung für Mitglieder vor
* *
@ -741,17 +773,6 @@ namespace _espe.service.member
} }
/*
export async function remove(
id : _espe.type.member_id
) : Promise<void>
{
await _espe.repository.member.delete(id);
signal_change();
}
*/
/** /**
* @todo check validity (e.g. username characters) * @todo check validity (e.g. username characters)
*/ */

View file

@ -46,6 +46,7 @@ def main():
"--update", "--update",
"--verbose", "--verbose",
"--exclude='conf.json'", "--exclude='conf.json'",
"--exclude='data.sqlite'",
("%s/" % args.build_directory), ("%s/" % args.build_directory),
( (
("%s" % args.target_directory) ("%s" % args.target_directory)

View file

@ -65,6 +65,7 @@ ${dir_temp}/espe-core.js ${dir_temp}/espe-core.d.ts: \
${dir_source}/api/actions/member_list.ts \ ${dir_source}/api/actions/member_list.ts \
${dir_source}/api/actions/member_read.ts \ ${dir_source}/api/actions/member_read.ts \
${dir_source}/api/actions/member_modify.ts \ ${dir_source}/api/actions/member_modify.ts \
${dir_source}/api/actions/member_delete.ts \
${dir_source}/api/actions/member_password_change_initialize.ts \ ${dir_source}/api/actions/member_password_change_initialize.ts \
${dir_source}/api/actions/member_password_change_execute.ts \ ${dir_source}/api/actions/member_password_change_execute.ts \
${dir_source}/api/functions.ts \ ${dir_source}/api/functions.ts \

View file

@ -19,6 +19,7 @@ modules="${modules} api"
modules="${modules} rest" modules="${modules} rest"
modules="${modules} http" modules="${modules} http"
modules="${modules} server" modules="${modules} server"
modules="${modules} email"
modules="${modules} args" modules="${modules} args"
modules="${modules} translate" modules="${modules} translate"
modules="${modules} log" modules="${modules} log"