core/source/main.ts

450 lines
10 KiB
TypeScript
Raw Normal View History

2024-08-22 15:22:00 +02:00
/*
ARC | Authelia Remote Control
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/>.
*/
2024-08-18 13:57:55 +02:00
/**
*/
type type_conf = {
server : {
2024-08-21 15:07:27 +02:00
host : string;
2024-08-18 13:57:55 +02:00
port : int;
};
authentication : {
timestamp_tolerance : float;
hash_salt : string;
};
authelia : {
usersfile_path : string;
};
};
/**
*/
type type_user_list_sparse = (
null
|
2024-08-21 15:07:27 +02:00
{
users : Record<
string,
{
disabled ?: boolean;
displayname ?: string;
email ?: string;
groups ?: Array<string>;
password : string;
}
>;
}
2024-08-18 13:57:55 +02:00
);
/**
*/
2024-08-21 15:07:27 +02:00
type type_user_list_complete = {
users : Record<
string,
{
disabled : boolean;
displayname : string;
email : string;
groups : Array<string>;
password : string;
}
>;
};
2024-08-18 13:57:55 +02:00
/**
*/
async function conf_get(
path : string
) : Promise<type_conf>
{
const raw : any = lib_plankton.json.decode(await lib_plankton.file.read(path));;
2024-08-21 15:07:27 +02:00
const version_fallback : int = 1;
if (! ("version" in raw)) {
lib_plankton.log.warning(
"conf_version_unspecified",
{
"assumed_version": version_fallback,
}
);
}
else {
// do nothing
}
const version : int = (raw["version"] ?? version_fallback);
switch (version) {
default: {
throw (new Error("invalid version: " + version.toFixed(0)));
break;
}
case 1: {
return {
"server": {
"host": ((raw["server"] ?? {})["host"] ?? "::"),
"port": ((raw["server"] ?? {})["port"] ?? 7463),
},
"authentication": {
"timestamp_tolerance": (raw["authentication"]["timestamp_tolerance"] ?? 2.0),
"hash_salt": raw["authentication"]["hash_salt"], // required
},
"authelia": {
"usersfile_path": ((raw["authelia"] ?? {})["usersfile_path"] ?? "/var/authelia/users.yaml"),
},
};
break;
}
}
2024-08-18 13:57:55 +02:00
}
/**
*/
function encode_user_list(
2024-08-21 15:07:27 +02:00
user_list : (null | type_user_list_sparse)
2024-08-18 13:57:55 +02:00
) : string
{
let output : string = "";
2024-08-21 15:07:27 +02:00
output += "users:\n";
Object.entries((user_list === null) ? {} : user_list.users).forEach(
2024-08-18 13:57:55 +02:00
([key, value]) => {
2024-08-21 15:07:27 +02:00
output += (" " + key + ":\n");
output += (" " + "disabled: " + ((value.disabled ?? false) ? "true" : "false") + "\n");
output += (" " + "displayname: " + (value.displayname ?? key) + "\n");
2024-08-18 13:57:55 +02:00
if ("email" in value) {
2024-08-21 15:07:27 +02:00
output += (" " + "email: " + value.email + "\n");
2024-08-18 13:57:55 +02:00
}
else {
// do nothing
}
2024-08-21 15:07:27 +02:00
output += (" " + "groups: " + JSON.stringify(value.groups ?? []) + "\n");
output += (" " + "password: " + value.password + "\n");
2024-08-18 13:57:55 +02:00
}
);
return output;
}
/**
*/
async function main(
args_raw : Array<string>
) : Promise<void>
{
lib_plankton.log.conf_push(
[
lib_plankton.log.channel_make(
{
"kind": "stdout",
"data": {
"threshold": "info",
// "format": "human_readable",
}
}
),
]
);
// args
const arg_handler : lib_plankton.args.class_handler = new lib_plankton.args.class_handler({
"conf_path": lib_plankton.args.class_argument.volatile({
"indicators_long": ["conf_path"],
"indicators_short": ["c"],
"type": lib_plankton.args.enum_type.string,
"mode": lib_plankton.args.enum_mode.replace,
"default": "conf.json",
"info": "path to conf file",
"name": "conf-path",
}),
"help": lib_plankton.args.class_argument.volatile({
"indicators_long": ["help"],
"indicators_short": ["h"],
"type": lib_plankton.args.enum_type.boolean,
"mode": lib_plankton.args.enum_mode.replace,
"default": false,
"info": "only show help and exit",
"name": "help",
}),
});
const args : Record<string, any> = arg_handler.read(lib_plankton.args.enum_environment.cli, args_raw.join(" "));
const conf : type_conf = await conf_get(args.conf_path);
// exec
2024-08-21 19:47:31 +02:00
if (args["help"]) {
process.stdout.write(
arg_handler.generate_help(
{
"programname": "arc",
"description": "Authelia Remote Control",
"executable": "arc",
}
)
+
"\n"
2024-08-18 13:57:55 +02:00
);
2024-08-21 19:47:31 +02:00
}
else {
const rest_subject : lib_plankton.rest.type_rest = lib_plankton.rest.make(
2024-08-18 13:57:55 +02:00
{
2024-08-21 19:47:31 +02:00
"title": "authelia-callbacks",
"versioning_method": "header",
"versioning_header_name": "X-Api-Version",
"set_access_control_headers": true,
"authentication": {
"kind": "key_header",
"parameters": {"name": "X-Session-Key"}
2024-08-18 13:57:55 +02:00
},
}
);
2024-08-21 19:47:31 +02:00
{
lib_plankton.rest.register<
null,
string
>
(
rest_subject,
lib_plankton.http.enum_method.get,
"/meta/ping",
{
"description": "sendet ein 'pong' zurück; gedacht um die Erreichbarkeit des Backends zu prüfen",
"input_schema": () => ({
"nullable": true,
}),
"output_schema": () => ({
"nullable": false,
"type": "string",
}),
// "restriction": restriction_none,
"execution": () => {
return Promise.resolve({
"status_code": 200,
"data": "pong",
});
2024-08-18 13:57:55 +02:00
},
2024-08-21 19:47:31 +02:00
}
);
lib_plankton.rest.register<
null,
any
>
(
rest_subject,
lib_plankton.http.enum_method.get,
"/meta/spec",
{
"description": "gibt die API-Spezifikation im OpenAPI-Format aus",
"input_schema": () => ({
"nullable": true,
}),
"output_schema": () => ({
}),
// "restriction": restriction_none,
"execution": () => {
return Promise.resolve({
"status_code": 200,
"data": lib_plankton.rest.to_oas(rest_subject),
});
2024-08-18 13:57:55 +02:00
},
2024-08-21 19:47:31 +02:00
}
);
lib_plankton.rest.register<
type_user_list_sparse,
null
>(
rest_subject,
lib_plankton.http.enum_method.put,
"/users/set",
{
"description": "setzt die Nutzerliste",
"query_parameters": [
{
"name": "timestamp",
"required": true,
"description": "current UNIX timestamp",
},
{
"name": "auth",
"required": true,
"description": "authorization string",
},
],
"input_schema": () => ({
"nullable": true,
"type": "object",
"additionalProperties": false,
"properties": {
"users": {
"nullable": true,
2024-08-21 15:07:27 +02:00
"type": "object",
2024-08-21 19:47:31 +02:00
"additionalProperties": {
"nullable": false,
"type": "object",
"additionalProperties": false,
"properties": {
"disabled": {
2024-08-21 15:07:27 +02:00
"nullable": false,
2024-08-21 19:47:31 +02:00
"type": "boolean",
},
"displayname": {
"nullable": false,
"type": "string",
},
"email": {
"nullable": false,
"type": "string",
},
"groups": {
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"type": "string"
}
},
"password": {
"nullable": false,
"type": "string",
},
2024-08-21 15:07:27 +02:00
},
2024-08-21 19:47:31 +02:00
"required": [
"password",
]
2024-08-21 15:07:27 +02:00
},
2024-08-21 19:47:31 +02:00
"properties": {},
"required": [],
2024-08-21 15:07:27 +02:00
}
2024-08-21 19:47:31 +02:00
},
"required": [
"users"
]
}),
"output_schema": () => ({
"nullable": true,
}),
"restriction": async (stuff) => {
const timestamp_local : float = lib_plankton.base.get_current_timestamp();
const timestamp_remote : float = parseFloat(stuff.query_parameters["timestamp"]);
if (Math.abs(timestamp_local - timestamp_remote) > conf.authentication.timestamp_tolerance) {
2024-08-21 15:07:27 +02:00
lib_plankton.log.notice(
2024-08-21 19:47:31 +02:00
"restriction_access_denied_due_to_invalid_timestamp",
2024-08-21 15:07:27 +02:00
{
2024-08-21 19:47:31 +02:00
"timestamp_local": timestamp_local,
"timestamp_remote": timestamp_remote,
2024-08-21 15:07:27 +02:00
}
);
2024-08-18 13:57:55 +02:00
return false;
}
else {
2024-08-21 19:47:31 +02:00
const authhash_is : string = stuff.query_parameters["auth"];
const authhash_shall : string = lib_plankton.sha256.get(
timestamp_remote.toFixed(0) + conf.authentication.hash_salt
);
if (authhash_is !== authhash_shall) {
lib_plankton.log.notice(
"restriction_access_denied_due_to_mismatching_hashes",
{
"timestamp": timestamp_remote,
"authhash_is": authhash_is,
"authhash_shall": authhash_shall,
}
);
return false;
2024-08-21 15:07:27 +02:00
}
2024-08-21 19:47:31 +02:00
else {
return true;
}
}
},
"execution": async (stuff) => {
await lib_plankton.file.write(
conf.authelia.usersfile_path,
encode_user_list(stuff.input)
2024-08-21 15:07:27 +02:00
);
lib_plankton.log.info(
2024-08-21 19:47:31 +02:00
"userdata_updated",
2024-08-21 15:07:27 +02:00
{
}
);
2024-08-21 19:47:31 +02:00
return Promise.resolve({
"status_code": 200,
"data": null
});
},
}
2024-08-18 13:57:55 +02:00
);
}
2024-08-21 19:47:31 +02:00
const server : lib_plankton.server.type_subject = lib_plankton.server.make(
async (input, metadata) => {
const http_request : lib_plankton.http.type_request = lib_plankton.http.decode_request(input.toString());
return (
lib_plankton.rest.call(
rest_subject,
http_request
)
.catch(
(error) => {
lib_plankton.log.error(
"server_request_processing_failed",
{
"error": String(error),
}
);
return Promise.resolve<lib_plankton.http.type_response>(
{
"version": "HTTP/1.1",
"status_code": 500,
"headers": {},
// @ts-ignore
"body": Buffer.from(""),
}
);
}
)
.then<string>(
(http_response) => {
const output : string = lib_plankton.http.encode_response(http_response);
return Promise.resolve<string>(output);
}
)
);
},
{
"host": conf.server.host,
"port": conf.server.port,
}
);
lib_plankton.server.start(server);
}
2024-08-18 13:57:55 +02:00
return Promise.resolve<void>(undefined);
}
(
main(process.argv.slice(2))
.then(
() => {}
)
.catch(
(error) => {process.stderr.write(String(error));}
)
);