diff --git a/conf/example.json b/conf/example.json index 7cff1d0..5961c72 100644 --- a/conf/example.json +++ b/conf/example.json @@ -1,9 +1,26 @@ { "version": 1, "log": [ - {"kind": "stdout", "data": {"threshold": "info"}} + { + "kind": "stdout", + "data": { + "threshold": "info" + } + } ], "session_management": { "in_memory": false + }, + "authentication": { + "kind": "oidc", + "data": { + "url_authorization": "https://authelia.linke.sx/api/oidc/authorization", + "url_token": "https://authelia.linke.sx/api/oidc/token", + "url_userinfo": "https://authelia.linke.sx/api/oidc/userinfo", + "client_id": "zeitbild", + "client_secret": "cee00b08a818db87e17e703273818e5194f83280e1ef3eae9214ff14675d9e6d", + "backend_url_base": "https://zeitbild.linke.sx", + "label": "linke.sx" + } } } diff --git a/data/example..json b/data/example.json similarity index 100% rename from data/example..json rename to data/example.json diff --git a/lib/plankton/plankton.d.ts b/lib/plankton/plankton.d.ts index 0172138..40d1a07 100644 --- a/lib/plankton/plankton.d.ts +++ b/lib/plankton/plankton.d.ts @@ -3759,17 +3759,17 @@ declare namespace lib_plankton.rest { */ type type_operation = { action_name: string; - query_parameters: Array<{ + query_parameters: ((version: string) => Array<{ name: string; description: (null | string); required: boolean; - }>; + }>); + input_schema: ((version: (null | string)) => type_oas_schema); + output_schema: ((version: (null | string)) => type_oas_schema); request_body_mimetype: string; request_body_decode: ((http_request_body: Buffer, http_request_header_content_type: (null | string)) => any); response_body_mimetype: string; response_body_encode: ((output: any) => Buffer); - input_schema: ((version: (null | string)) => type_oas_schema); - output_schema: ((version: (null | string)) => type_oas_schema); }; /** */ @@ -3838,11 +3838,11 @@ declare namespace lib_plankton.rest { execution?: type_execution; title?: (null | string); description?: (null | string); - query_parameters?: Array<{ + query_parameters?: ((version: string) => Array<{ name: string; description: (null | string); required: boolean; - }>; + }>); input_schema?: ((version: string) => type_oas_schema); output_schema?: ((version: string) => type_oas_schema); request_body_mimetype?: string; @@ -3860,11 +3860,11 @@ declare namespace lib_plankton.rest { execution?: type_execution; title?: (null | string); description?: (null | string); - query_parameters?: Array<{ + query_parameters?: ((version: string) => Array<{ name: string; description: (null | string); required: boolean; - }>; + }>); input_schema?: ((version: (null | string)) => type_oas_schema); output_schema?: ((version: (null | string)) => type_oas_schema); request_body_mimetype?: string; @@ -4187,3 +4187,159 @@ declare namespace lib_plankton.bcrypt { */ function compare(password_shall_image: string, password_is: string): Promise; } +declare namespace lib_plankton.base64 { + /** + * @author fenris + */ + type type_source = string; + /** + * @author fenris + */ + type type_target = string; + /** + * @author fenris + */ + export function encode(source: type_source): type_target; + /** + * @author fenris + */ + export function decode(target: type_target): type_source; + /** + * @author fenris + */ + export function implementation_code(): lib_plankton.code.type_code; + export {}; +} +declare namespace lib_plankton.base64 { + /** + * @author fenris + */ + class class_base64 implements lib_plankton.code.interface_code { + /** + * @author fenris + */ + constructor(); + /** + * @implementation + * @author fenris + */ + encode(x: string): string; + /** + * @implementation + * @author fenris + */ + decode(x: string): string; + } +} +declare namespace lib_plankton.auth { + /** + */ + type type_auth = { + login_prepare: (() => Promise); + login_execute: ((input: type_execute_input) => Promise); + login_control: ((input: type_control_input) => Promise); + }; +} +declare namespace lib_plankton.auth { +} +declare namespace lib_plankton.auth.internal { + /** + */ + type type_parameters = { + password_image_chest: lib_plankton.storage.type_chest; + check_password: ((image: string, input: string) => Promise); + }; + /** + */ + type type_preparation = null; + /** + */ + type type_execute_input = { + name: string; + password: string; + }; + /** + */ + type type_control_input = void; + /** + */ + export function implementation_auth(parameters: type_parameters): type_auth; + export {}; +} +declare namespace lib_plankton.auth.oidc { + /** + */ + enum enum_state_label { + idle = "idle", + wait = "wait", + ready = "ready" + } + /** + */ + export type type_parameters_raw = { + url_authorization: string; + url_token: string; + url_userinfo: string; + client_id: string; + client_secret: string; + url_redirect: string; + scopes?: (null | Array); + label?: string; + login_url_mode?: (null | ("log" | "open")); + }; + /** + */ + export type type_parameters = { + url_authorization: string; + url_token: string; + url_userinfo: string; + client_id: string; + client_secret: string; + url_redirect: string; + scopes: Array; + label: string; + login_url_mode: ("log" | "open"); + }; + /** + */ + type type_preparation = { + url: string; + label: string; + }; + /** + */ + type type_execute_input = void; + /** + */ + type type_control_input = ({ + kind: "authorization_callback"; + data: { + stuff: Record; + cookie: string; + }; + } | { + kind: "token_callback"; + data: { + http_request: lib_plankton.http.type_request; + }; + }); + /** + */ + export type type_subject = { + parameters: type_parameters; + state: { + id: string; + label: enum_state_label; + handlers: Array<{ + resolve: ((result: string) => void); + reject: ((reason: Error) => void); + }>; + token: (null | string); + name: (null | string); + }; + }; + /** + */ + export function implementation_auth(parameters_raw: type_parameters_raw): type_auth; + export {}; +} diff --git a/lib/plankton/plankton.js b/lib/plankton/plankton.js index c3044b5..4fa30eb 100644 --- a/lib/plankton/plankton.js +++ b/lib/plankton/plankton.js @@ -9069,7 +9069,7 @@ var lib_plankton; // query { if (url.query !== null) { - result += ("?" + encodeURI(url.query)); + result += ("?" + url.query); } } // hash @@ -12973,11 +12973,11 @@ var lib_plankton; "active": ((version) => true), "execution": ((stuff) => Promise.resolve({ "status_code": 501, "data": null })), "restriction": ((stuff) => Promise.resolve(true)), - "input_schema": ((version) => ({})), - "output_schema": ((version) => ({})), "title": null, "description": null, - "query_parameters": [], + "query_parameters": ((version) => ([])), + "input_schema": ((version) => ({})), + "output_schema": ((version) => ({})), "request_body_mimetype": "application/json", "request_body_decode": ((http_request_body, http_request_header_content_type) => (((http_request_header_content_type !== null) && @@ -13382,7 +13382,7 @@ var lib_plankton; })), ]), // query parameters - operation.query_parameters.map((query_parameter) => ({ + operation.query_parameters(options.version).map((query_parameter) => ({ "name": query_parameter.name, "in": "query", "required": query_parameter.required, @@ -14559,3 +14559,496 @@ var lib_plankton; bcrypt.compare = compare; })(bcrypt = lib_plankton.bcrypt || (lib_plankton.bcrypt = {})); })(lib_plankton || (lib_plankton = {})); +/* +This file is part of »bacterio-plankton:base64«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:base64« 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:base64« 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:base64«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var base64; + (function (base64) { + /** + * @author fenris + */ + function encode(source) { + return Buffer.from(source, "utf-8").toString("base64"); + } + base64.encode = encode; + /** + * @author fenris + */ + function decode(target) { + return Buffer.from(target, "base64").toString("utf-8"); + } + base64.decode = decode; + /** + * @author fenris + */ + function implementation_code() { + return { + "encode": encode, + "decode": decode, + }; + } + base64.implementation_code = implementation_code; + })(base64 = lib_plankton.base64 || (lib_plankton.base64 = {})); +})(lib_plankton || (lib_plankton = {})); +/* +This file is part of »bacterio-plankton:base64«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:base64« 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:base64« 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:base64«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var base64; + (function (base64) { + /** + * @author fenris + */ + class class_base64 { + /** + * @author fenris + */ + constructor() { + } + /** + * @implementation + * @author fenris + */ + encode(x) { + return base64.encode(x); + } + /** + * @implementation + * @author fenris + */ + decode(x) { + return base64.decode(x); + } + } + base64.class_base64 = class_base64; + })(base64 = lib_plankton.base64 || (lib_plankton.base64 = {})); +})(lib_plankton || (lib_plankton = {})); +/* +This file is part of »bacterio-plankton:auth«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:auth« 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:auth« 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:auth«. If not, see . + */ +/* +This file is part of »bacterio-plankton:auth«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:auth« 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:auth« 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:auth«. If not, see . + */ +/* +This file is part of »bacterio-plankton:auth«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:auth« 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:auth« 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:auth«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var auth; + (function (auth) { + var internal; + (function (internal) { + /** + */ + function make(parameters) { + return { + "parameters": parameters, + }; + } + /** + */ + function login_prepare(subject) { + return Promise.resolve(null); + } + /** + */ + async function login_execute(subject, input) { + let password_image; + try { + password_image = await subject.parameters.password_image_chest.read(input.name); + } + catch (error) { + password_image = null; + } + if (password_image === null) { + return Promise.reject(new Error("user not found")); + } + else { + let valid; + try { + valid = await subject.parameters.check_password(password_image, input.password); + } + catch (error) { + valid = null; + } + if (valid === null) { + return Promise.reject(new Error("password check failed")); + } + else { + if (!valid) { + return Promise.reject(new Error("wrong password")); + } + else { + return Promise.resolve(input.name); + } + } + } + } + /** + */ + function login_control(subject, input) { + return Promise.reject(new Error("not available")); + } + /** + */ + function implementation_auth(parameters) { + const subject = make(parameters); + return { + "login_prepare": () => login_prepare(subject), + "login_execute": (input) => login_execute(subject, input), + "login_control": (input) => login_control(subject, input), + }; + } + internal.implementation_auth = implementation_auth; + })(internal = auth.internal || (auth.internal = {})); + })(auth = lib_plankton.auth || (lib_plankton.auth = {})); +})(lib_plankton || (lib_plankton = {})); +/* +This file is part of »bacterio-plankton:auth«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:auth« 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:auth« 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:auth«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var auth; + (function (auth) { + var oidc; + (function (oidc) { + /** + */ + let enum_state_label; + (function (enum_state_label) { + enum_state_label["idle"] = "idle"; + enum_state_label["wait"] = "wait"; + enum_state_label["ready"] = "ready"; + })(enum_state_label || (enum_state_label = {})); + ; + /** + */ + function make(parameters_raw) { + return { + "parameters": { + "url_authorization": parameters_raw.url_authorization, + "url_token": parameters_raw.url_token, + "url_userinfo": parameters_raw.url_userinfo, + "client_id": parameters_raw.client_id, + "client_secret": parameters_raw.client_secret, + "url_redirect": parameters_raw.url_redirect, + "scopes": (parameters_raw.scopes + ?? + ["openid", "profile"]), + "label": (parameters_raw.label + ?? + "OIDC"), + "login_url_mode": (parameters_raw.login_url_mode + ?? + "log"), + }, + "state": { + "id": lib_plankton.random.generate_guid(), + "label": enum_state_label.idle, + "handlers": [], + "token": null, + "name": null, + } + }; + } + /** + * @see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + * @todo save state for different requests/users? + */ + function login_prepare(subject) { + const url = lib_plankton.url.encode(Object.assign(lib_plankton.url.decode(subject.parameters.url_authorization), { + "query": lib_plankton.www_form.encode({ + "response_type": "code", + "client_id": subject.parameters.client_id, + // "client_secret": subject.parameters.client_secret, + "scope": subject.parameters.scopes.join(" "), + "state": subject.state.id, + "redirect_uri": subject.parameters.url_redirect, + }) + })); + /* + switch (subject.parameters.login_url_mode) { + case "log": { + lib_plankton.log.info( + "auth_login_url", + { + "url": url, + }, + ); + break; + } + case "open": { + const nm_opn = require("opn"); + nm_opn(url); + break; + } + default: { + throw (new Error("invalid login_url_mode: " + subject.parameters.login_url_mode)); + break; + } + } + */ + return Promise.resolve({ + "url": url, + "label": subject.parameters.label, + }); + } + /** + * @see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + */ + function login_execute(subject, input) { + switch (subject.state.label) { + case enum_state_label.idle: { + return (new Promise((resolve, reject) => { + subject.state = { + "id": subject.state.id, + "label": enum_state_label.wait, + "handlers": subject.state.handlers.concat([{ "resolve": resolve, "reject": reject }]), + "token": null, + "name": null, + }; + })); + break; + } + case enum_state_label.wait: { + return (new Promise((resolve, reject) => { + subject.state = { + "id": subject.state.id, + "label": enum_state_label.wait, + "handlers": subject.state.handlers.concat([{ "resolve": resolve, "reject": reject }]), + "token": null, + "name": null, + }; + })); + break; + } + case enum_state_label.ready: { + return Promise.resolve(subject.state.name); + break; + } + } + } + /** + * @see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + * @see https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest + * @see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest + * @todo authentication + */ + async function login_control(subject, input) { + switch (input.kind) { + default: { + throw (new Error("unhandled login control kind: " + input.kind)); + break; + } + case "authorization_callback": { + lib_plankton.log.info("auth_authorization_callback_input", { + "input": input, + }); + if ("error" in input.data.stuff) { + // query_parts["error"] + // query_parts["error_description"] + const handlers = subject.state.handlers; + subject.state = { + "id": subject.state.id, + "label": enum_state_label.idle, + "handlers": [], + "token": null, + "name": null, + }; + handlers.forEach((handler) => { + handler.reject(new Error("failed")); + }); + return Promise.resolve(undefined); + } + else { + const code = input.data.stuff["code"]; + const cookie = input.data.cookie; + const token_url = lib_plankton.url.decode(subject.parameters.url_token); + const token_http_request = { + "version": "HTTP/2", + "scheme": ((token_url.scheme === "http") ? "http" : "https"), + "host": token_url.host, + "path": token_url.path, + "query": null, + "method": lib_plankton.http.enum_method.post, + "headers": { + "Content-Type": "application/x-www-form-urlencoded", + "Cookie": cookie, + "Authorization": lib_plankton.string.coin("Basic {{value}}", { + "value": lib_plankton.base64.encode(lib_plankton.string.coin("{{id}}:{{secret}}", { + "id": subject.parameters.client_id, + "secret": subject.parameters.client_secret, + })), + }), + }, + "body": Buffer.from(lib_plankton.www_form.encode({ + "grant_type": "authorization_code", + "code": code, + "redirect_uri": subject.parameters.url_redirect, + })), + }; + lib_plankton.log.info("auth_token_request", { + "request": Object.assign(token_http_request, { + "body": token_http_request.body.toString(), + }) + }); + const token_http_response = await lib_plankton.http.call(token_http_request); + lib_plankton.log.info("auth_token_response", { + "response": token_http_response, + }); + const token_data = lib_plankton.json.decode(token_http_response.body.toString()); + const token = token_data["access_token"]; + const userinfo_url = lib_plankton.url.decode(subject.parameters.url_userinfo); + const userinfo_http_request = { + "version": "HTTP/2", + "scheme": ((userinfo_url.scheme === "http") ? "http" : "https"), + "host": userinfo_url.host, + "path": userinfo_url.path, + "query": null, + "method": lib_plankton.http.enum_method.get, + "headers": { + "Cookie": cookie, + "Authorization": ("Bearer " + token), + }, + "body": null + }; + lib_plankton.log.info("auth_userinfo_request", { + "request": userinfo_http_request, + }); + const userinfo_http_response = await lib_plankton.http.call(userinfo_http_request); + lib_plankton.log.info("auth_userinfo_response", { + "response": userinfo_http_response, + }); + const userinfo_data = lib_plankton.json.decode(userinfo_http_response.body.toString()); + const name = userinfo_data["name"]; // TODO: requires "profile" in "scope" + lib_plankton.log.info("auth_oidc_name", { "value": name }); + const handlers = subject.state.handlers; + subject.state = { + "id": subject.state.id, + "label": enum_state_label.ready, + "handlers": [], + "token": token, + "name": name, + }; + handlers.forEach((handler) => { + handler.resolve(subject.state.name); + }); + return Promise.resolve(undefined); + } + break; + } + } + } + /** + */ + function implementation_auth(parameters_raw) { + const subject = make(parameters_raw); + return { + "login_prepare": () => login_prepare(subject), + "login_execute": (input) => login_execute(subject, input), + "login_control": (input) => login_control(subject, input), + }; + } + oidc.implementation_auth = implementation_auth; + })(oidc = auth.oidc || (auth.oidc = {})); + })(auth = lib_plankton.auth || (lib_plankton.auth = {})); +})(lib_plankton || (lib_plankton = {})); diff --git a/source/api/actions/calendar_list.ts b/source/api/actions/calendar_list.ts index dd92729..9dc4d21 100644 --- a/source/api/actions/calendar_list.ts +++ b/source/api/actions/calendar_list.ts @@ -24,8 +24,8 @@ namespace _zeitbild.api "/calendars", { "description": "listet alle verfügbaren Kalender auf", - "query_parameters": [ - ], + "query_parameters": () => ([ + ]), "output_schema": () => ({ "type": "array", "items": { diff --git a/source/api/actions/events.ts b/source/api/actions/events.ts index f4e4d5b..37725c7 100644 --- a/source/api/actions/events.ts +++ b/source/api/actions/events.ts @@ -31,7 +31,7 @@ namespace _zeitbild.api "/events", { "description": "stellt Veranstaltungen aus verschiedenen Kalendern zusammen", - "query_parameters": [ + "query_parameters": () => ([ { "name": "from", "required": true, @@ -47,7 +47,7 @@ namespace _zeitbild.api "required": false, "description": "comma separated", }, - ], + ]), "output_schema": () => ({ "type": "array", "items": { diff --git a/source/api/actions/session_oidc.ts b/source/api/actions/session_oidc.ts index 308749f..389f51c 100644 --- a/source/api/actions/session_oidc.ts +++ b/source/api/actions/session_oidc.ts @@ -8,33 +8,82 @@ namespace _zeitbild.api rest_subject : lib_plankton.rest.type_rest ) : void { - register( + register< + null, + string + >( rest_subject, - lib_plankton.http.enum_method.delete, + lib_plankton.http.enum_method.get, "/session/oidc", { "description": "verarbeitet einen OIDC login callback", + "query_parameters": () => ([ + { + "name": "code", + "required": true, + "description": null, + }, + { + "name": "iss", + "required": true, + "description": null, + }, + { + "name": "scope", + "required": true, + "description": null, + }, + { + "name": "state", + "required": true, + "description": null, + }, + ]), "input_schema": () => ({ "type": "null", }), "output_schema": () => ({ - "type": "null", + "nullable": false, + "type": "string", }), - "restriction": restriction_logged_in, + "response_body_mimetype": "text/html", + "response_body_encode": (output => Buffer.from(output)), + "restriction": restriction_none, "execution": async (stuff) => { - // do NOT wait _zeitbild.auth.control( { "kind": "authorization_callback", "data": { - "http_request": http_request + "stuff": stuff.query_parameters, + "cookie": (stuff.headers["Cookie"] ?? stuff.headers["cookie"]), } } ); - return Promise.resolve({ - "status_code": 200, - "data": null, - }); + return ( + _zeitbild.auth.execute( + undefined + ) + .then( + (name) => lib_plankton.session.begin(name) + ) + .then( + (session_key) => Promise.resolve({ + "status_code": 200, + "data": lib_plankton.string.coin( + "", + { + // TODO: get url from frontend + "url": lib_plankton.string.coin( + "http://localhost:8888/#oidc_finish,session_key={{session_key}}", + { + "session_key": session_key, + } + ), + } + ), + }) + ) + ); }, } ); diff --git a/source/api/base.ts b/source/api/base.ts index 93ed49b..56f7a07 100644 --- a/source/api/base.ts +++ b/source/api/base.ts @@ -55,13 +55,13 @@ namespace _zeitbild.api execution ?: lib_plankton.rest.type_execution; title ?: (null | string); description ?: (null | string); - query_parameters ?: Array< + query_parameters ?: ((version : (null | string)) => Array< { name : string; description : (null | string); required : boolean; } - >; + >); input_schema ?: ((version: (null | string)) => lib_plankton.rest.type_oas_schema); output_schema ?: ((version: (null | string)) => lib_plankton.rest.type_oas_schema); request_body_mimetype ?: string; diff --git a/source/api/functions.ts b/source/api/functions.ts index 935cb43..860ad60 100644 --- a/source/api/functions.ts +++ b/source/api/functions.ts @@ -26,10 +26,10 @@ namespace _zeitbild.api } // session { - // _zeitbild.api.register_session_prepare(rest_subject); + _zeitbild.api.register_session_prepare(rest_subject); _zeitbild.api.register_session_begin(rest_subject); _zeitbild.api.register_session_end(rest_subject); - // _zeitbild.api.register_session_oidc(rest_subject); + _zeitbild.api.register_session_oidc(rest_subject); } // calendar { diff --git a/source/auth.ts b/source/auth.ts index 1be7e2f..ec8da0c 100644 --- a/source/auth.ts +++ b/source/auth.ts @@ -3,12 +3,12 @@ namespace _zeitbild.auth { /** + */ let _subject : ( null | - lib_plankton.auth.type_auth + lib_plankton.auth.type_auth ) = null; - */ /** @@ -16,78 +16,99 @@ namespace _zeitbild.auth export function init( ) : Promise { - /* - switch (_zeitbild.conf.get().auth.kind) { + switch (_zeitbild.conf.get().authentication.kind) { case "internal": { _subject = lib_plankton.auth.internal.implementation_auth( { - // "password_image_chest": password_image_chest, - // "check_password": (image, input) => Promise.resolve(image === get_password_image(input)), + "password_image_chest": { + "setup": (input) => Promise.resolve(undefined), + "clear": () => Promise.reject("not implemented"), + "write": (key, item) => _zeitbild.repository.auth_internal.write(key, item), + "delete": (key) => _zeitbild.repository.auth_internal.delete_(key), + "read": (key) => _zeitbild.repository.auth_internal.read(key), + "search": (term) => Promise.reject("not implemented"), + }, + "check_password": (image, input) => _zeitbild.service.auth_internal.check_raw(image, input), } ); + break; } case "oidc": { _subject = lib_plankton.auth.oidc.implementation_auth( { - "url_authorization": _zeitbild.conf.get().auth.data.url_authorization, - "url_token": _zeitbild.conf.get().auth.data.url_token, - "url_userinfo": _zeitbild.conf.get().auth.data.url_userinfo, - "client_id": _zeitbild.conf.get().auth.data.client_id, - "client_secret": _zeitbild.conf.get().auth.data.client_secret, - "url_redirect": "/session/begin", + "url_authorization": _zeitbild.conf.get().authentication.data.url_authorization, + "url_token": _zeitbild.conf.get().authentication.data.url_token, + "url_userinfo": _zeitbild.conf.get().authentication.data.url_userinfo, + "client_id": _zeitbild.conf.get().authentication.data.client_id, + "client_secret": _zeitbild.conf.get().authentication.data.client_secret, + "url_redirect": (_zeitbild.conf.get().authentication.data.backend_url_base + "/session/oidc"), "scopes": [ "openid", "profile", "email", ], - "login_url_mode": "open", + "label": _zeitbild.conf.get().authentication.data.label, + "login_url_mode": "log", } ); + break; } default: { // do nothing break; } } - */ return Promise.resolve(undefined); } /** + */ export function prepare( ) : Promise<{kind : string; data : any;}> { - return ( - _subject.prepare() - .then( - (data) => ({ - "kind": _zeitbild.conf.get().auth.kind, - "data": data, - }) - ) - ); + if (_subject === null) { + return Promise.reject(new Error("not initialized yet")); + } + else { + return ( + _subject.login_prepare() + .then( + (data : any) => ({ + "kind": _zeitbild.conf.get().authentication.kind, + "data": data, + }) + ) + ); + } } - */ /** + */ export function execute( input : any ) : Promise { - return _subject.execute(input); + if (_subject === null) { + return Promise.reject(new Error("not initialized yet")); + } + else { + return _subject.login_execute(input); + } } - */ - /** export function control( input : any ) : Promise { - return _subject.control(input); + if (_subject === null) { + return Promise.reject(new Error("not initialized yet")); + } + else { + return _subject.login_control(input); + } } - */ - + } diff --git a/source/conf.ts b/source/conf.ts index 084f127..b58dce1 100644 --- a/source/conf.ts +++ b/source/conf.ts @@ -185,6 +185,93 @@ namespace _zeitbild.conf ], "additionalProperties": false, "default": {} + }, + "authentication": { + "anyOf": [ + { + "type": "object", + "properties": { + "kind": { + "nullable": false, + "type": "string", + "enum": ["internal"] + }, + "data": { + "nullable": false, + "type": "object", + "properties": { + }, + "required": [ + ], + "default": {} + } + }, + "required": [ + "kind" + ] + }, + { + "type": "object", + "properties": { + "kind": { + "nullable": false, + "type": "string", + "enum": ["oidc"] + }, + "data": { + "nullable": false, + "type": "object", + "properties": { + "url_authorization": { + "nullable": false, + "type": "string" + }, + "url_token": { + "nullable": false, + "type": "string" + }, + "url_userinfo": { + "nullable": false, + "type": "string" + }, + "client_id": { + "nullable": false, + "type": "string" + }, + "client_secret": { + "nullable": false, + "type": "string" + }, + "backend_url_base": { + "nullable": false, + "type": "string" + }, + "label": { + "nullable": false, + "type": "string", + "default": "OIDC" + } + }, + "required": [ + "url_authorization", + "url_token", + "url_userinfo", + "client_id", + "client_secret" + ] + } + }, + "required": [ + "kind", + "data" + ] + } + ], + "default": { + "kind": "internal", + "data": { + } + } } }, "required": [ diff --git a/source/repositories/auth_internal.ts b/source/repositories/auth_internal.ts index 31ba27b..62f9c57 100644 --- a/source/repositories/auth_internal.ts +++ b/source/repositories/auth_internal.ts @@ -64,14 +64,19 @@ namespace _zeitbild.repository.auth_internal export function write( name : string, password_image : string + ) : Promise + { + return get_chest().write([name], {"password_image": password_image}); + } + + + /** + */ + export function delete_( + name : string ) : Promise { - return ( - get_chest().write([name], {"password_image": password_image}) - .then( - () => Promise.resolve(undefined) - ) - ); + return get_chest().delete([name]); } } diff --git a/source/services/auth_internal.ts b/source/services/auth_internal.ts index e5535ad..9a5bccf 100644 --- a/source/services/auth_internal.ts +++ b/source/services/auth_internal.ts @@ -11,9 +11,26 @@ namespace _zeitbild.service.auth_internal { return ( lib_plankton.bcrypt.compute(password) - .then( + .then( (password_image) => _zeitbild.repository.auth_internal.write(name, password_image) ) + .then( + () => Promise.resolve(undefined) + ) + ); + } + + + /** + */ + export async function check_raw( + password_image : string, + password : string + ) : Promise + { + return lib_plankton.bcrypt.compare( + password_image, + password ); } @@ -27,14 +44,11 @@ namespace _zeitbild.service.auth_internal { try { const password_image : string = await _zeitbild.repository.auth_internal.read(name); - return lib_plankton.bcrypt.compare( - password_image, - password - ); + return check_raw(password_image, password); } catch (error) { return Promise.resolve(false); } } - + } diff --git a/tools/deploy b/tools/deploy new file mode 100755 index 0000000..33ade02 --- /dev/null +++ b/tools/deploy @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +import sys as _sys +import os as _os +import argparse as _argparse + + +def main(): + ## args + argument_parser = _argparse.ArgumentParser() + argument_parser.add_argument( + type = str, + dest = "target_system", + metavar = "", + help = "either 'localhost' or SSH handle of the target system", + ) + argument_parser.add_argument( + "-t", + "--target-directory", + type = str, + dest = "target_directory", + default = "/opt/zeitbild", + metavar = "", + help = "directory on the target system, where the files shall be put; default: /opt/zeitbild", + ) + argument_parser.add_argument( + "-b", + "--build-directory", + type = str, + dest = "build_directory", + default = "build", + metavar = "", + help = "directory to where the build was put", + ) + args = argument_parser.parse_args() + + ## exec + if (not _os.path.exists(args.build_directory)): + _sys.stderr.write("-- build directory not found; probably you need to run /tools/build\n") + _sys.exit(1) + else: + _os.system( + " ".join([ + "rsync", + "--recursive", + "--update", + "--verbose", + "--exclude='conf.json'", + "--exclude='data.sqlite'", + ("%s/" % args.build_directory), + ( + ("%s" % args.target_directory) + if (args.target_system == "localhost") else + ("%s:%s" % (args.target_system, args.target_directory, )) + ), + ]) + ) + + +main() diff --git a/tools/makefile b/tools/makefile index 1df0b18..24533e2 100644 --- a/tools/makefile +++ b/tools/makefile @@ -48,7 +48,9 @@ ${dir_temp}/zeitbild-unlinked.js: \ ${dir_source}/api/base.ts \ ${dir_source}/api/actions/meta_ping.ts \ ${dir_source}/api/actions/meta_spec.ts \ + ${dir_source}/api/actions/session_prepare.ts \ ${dir_source}/api/actions/session_begin.ts \ + ${dir_source}/api/actions/session_oidc.ts \ ${dir_source}/api/actions/session_end.ts \ ${dir_source}/api/actions/calendar_list.ts \ ${dir_source}/api/actions/events.ts \ diff --git a/tools/update-plankton b/tools/update-plankton index ea7ca0e..8d932fd 100755 --- a/tools/update-plankton +++ b/tools/update-plankton @@ -23,8 +23,8 @@ modules="${modules} rest" modules="${modules} rest" modules="${modules} server" modules="${modules} args" -# modules="${modules} auth" modules="${modules} bcrypt" +modules="${modules} auth" ## exec