From 8611bb94452c2fd6acbebe58e7800f9722afe4b0 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Thu, 26 Sep 2024 18:59:12 +0200 Subject: [PATCH] [upd] plankton --- lib/plankton/plankton.d.ts | 60 +++----- lib/plankton/plankton.js | 282 ++++++++++++------------------------- 2 files changed, 110 insertions(+), 232 deletions(-) diff --git a/lib/plankton/plankton.d.ts b/lib/plankton/plankton.d.ts index 3248bb2..2d664db 100644 --- a/lib/plankton/plankton.d.ts +++ b/lib/plankton/plankton.d.ts @@ -4140,11 +4140,13 @@ declare namespace lib_plankton.auth.internal { declare namespace lib_plankton.auth.oidc { /** */ - enum enum_state_label { - idle = "idle", - wait = "wait", - ready = "ready" - } + type type_token = string; + /** + */ + type type_userinfo = { + name: (null | string); + email: (null | string); + }; /** */ export type type_parameters_raw = { @@ -4156,7 +4158,6 @@ declare namespace lib_plankton.auth.oidc { url_redirect: string; scopes?: (null | Array); label?: string; - login_url_mode?: (null | ("log" | "open")); }; /** */ @@ -4169,48 +4170,25 @@ declare namespace lib_plankton.auth.oidc { 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); - }; + state: {}; }; /** */ - export function implementation_auth(parameters_raw: type_parameters_raw): type_auth; + export function make(parameters_raw: type_parameters_raw): type_subject; + /** + * @see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + */ + export function authorization_url(subject: type_subject): string; + /** + */ + export function handle_authorization_callback(subject: type_subject, cookie: (null | string), stuff: Record): Promise<{ + token: type_token; + userinfo: type_userinfo; + }>; export {}; } diff --git a/lib/plankton/plankton.js b/lib/plankton/plankton.js index b588354..bd4afd2 100644 --- a/lib/plankton/plankton.js +++ b/lib/plankton/plankton.js @@ -14795,218 +14795,118 @@ var lib_plankton; "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, - } + "state": {} }; } + oidc.make = make; /** * @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), { + function authorization_url(subject) { + return 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, + "state": lib_plankton.random.generate_guid(), "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; - } - } - */ + } + oidc.authorization_url = authorization_url; + /** + * https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest + */ + async function token_call(subject, cookie, code) { + const url = lib_plankton.url.decode(subject.parameters.url_token); + const http_request = { + "version": "HTTP/2", + "scheme": ((url.scheme === "http") ? "http" : "https"), + "host": url.host, + "path": 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(http_request, { + "body": http_request.body.toString(), + }) + }); + const http_response = await lib_plankton.http.call(http_request); + lib_plankton.log.info("auth_token_response", { + "response": http_response, + }); + const data = lib_plankton.json.decode(http_response.body.toString()); + return Promise.resolve(data["access_token"]); + } + /** + * @see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest + */ + async function userinfo_call(subject, cookie, token) { + const url = lib_plankton.url.decode(subject.parameters.url_userinfo); + const http_request = { + "version": "HTTP/2", + "scheme": ((url.scheme === "http") ? "http" : "https"), + "host": url.host, + "path": 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": http_request, + }); + const http_response = await lib_plankton.http.call(http_request); + lib_plankton.log.info("auth_userinfo_response", { + "response": http_response, + }); + const data = lib_plankton.json.decode(http_response.body.toString()); return Promise.resolve({ - "url": url, - "label": subject.parameters.label, + "name": (data["name"] ?? null), + "email": (data["email"] ?? null), }); } /** - * @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; - } + async function handle_authorization_callback(subject, cookie, stuff) { + if (("error" in stuff) + || + (!("code" in stuff)) + || + (cookie === null)) { + return Promise.reject(new Error("failed")); + } + else { + const token = await token_call(subject, cookie, stuff["code"]); + const userinfo = await userinfo_call(subject, cookie, token); + return Promise.resolve({ + "token": token, + "userinfo": userinfo, + }); } } - /** - * @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.handle_authorization_callback = handle_authorization_callback; })(oidc = auth.oidc || (auth.oidc = {})); })(auth = lib_plankton.auth || (lib_plankton.auth = {})); })(lib_plankton || (lib_plankton = {}));