[upd] plankton

This commit is contained in:
Fenris Wolf 2024-09-26 18:59:12 +02:00
parent 810965d255
commit 8611bb9445
2 changed files with 110 additions and 232 deletions

View file

@ -4140,11 +4140,13 @@ declare namespace lib_plankton.auth.internal {
declare namespace lib_plankton.auth.oidc { declare namespace lib_plankton.auth.oidc {
/** /**
*/ */
enum enum_state_label { type type_token = string;
idle = "idle", /**
wait = "wait", */
ready = "ready" type type_userinfo = {
} name: (null | string);
email: (null | string);
};
/** /**
*/ */
export type type_parameters_raw = { export type type_parameters_raw = {
@ -4156,7 +4158,6 @@ declare namespace lib_plankton.auth.oidc {
url_redirect: string; url_redirect: string;
scopes?: (null | Array<string>); scopes?: (null | Array<string>);
label?: string; label?: string;
login_url_mode?: (null | ("log" | "open"));
}; };
/** /**
*/ */
@ -4169,48 +4170,25 @@ declare namespace lib_plankton.auth.oidc {
url_redirect: string; url_redirect: string;
scopes: Array<string>; scopes: Array<string>;
label: string; 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<string, string>;
cookie: string;
};
} | {
kind: "token_callback";
data: {
http_request: lib_plankton.http.type_request;
};
});
/** /**
*/ */
export type type_subject = { export type type_subject = {
parameters: type_parameters; parameters: type_parameters;
state: { 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<type_preparation, type_execute_input, type_control_input>; 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<string, string>): Promise<{
token: type_token;
userinfo: type_userinfo;
}>;
export {}; export {};
} }

View file

@ -14795,218 +14795,118 @@ var lib_plankton;
"label": (parameters_raw.label "label": (parameters_raw.label
?? ??
"OIDC"), "OIDC"),
"login_url_mode": (parameters_raw.login_url_mode
??
"log"),
}, },
"state": { "state": {}
"id": lib_plankton.random.generate_guid(),
"label": enum_state_label.idle,
"handlers": [],
"token": null,
"name": null,
}
}; };
} }
oidc.make = make;
/** /**
* @see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest * @see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
* @todo save state for different requests/users?
*/ */
function login_prepare(subject) { function authorization_url(subject) {
const url = lib_plankton.url.encode(Object.assign(lib_plankton.url.decode(subject.parameters.url_authorization), { return lib_plankton.url.encode(Object.assign(lib_plankton.url.decode(subject.parameters.url_authorization), {
"query": lib_plankton.www_form.encode({ "query": lib_plankton.www_form.encode({
"response_type": "code", "response_type": "code",
"client_id": subject.parameters.client_id, "client_id": subject.parameters.client_id,
// "client_secret": subject.parameters.client_secret, // "client_secret": subject.parameters.client_secret,
"scope": subject.parameters.scopes.join(" "), "scope": subject.parameters.scopes.join(" "),
"state": subject.state.id, "state": lib_plankton.random.generate_guid(),
"redirect_uri": subject.parameters.url_redirect, "redirect_uri": subject.parameters.url_redirect,
}) })
})); }));
/* }
switch (subject.parameters.login_url_mode) { oidc.authorization_url = authorization_url;
case "log": { /**
lib_plankton.log.info( * https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest
"auth_login_url", */
{ async function token_call(subject, cookie, code) {
"url": url, const url = lib_plankton.url.decode(subject.parameters.url_token);
}, const http_request = {
); "version": "HTTP/2",
break; "scheme": ((url.scheme === "http") ? "http" : "https"),
} "host": url.host,
case "open": { "path": url.path,
const nm_opn = require("opn"); "query": null,
nm_opn(url); "method": lib_plankton.http.enum_method.post,
break; "headers": {
} "Content-Type": "application/x-www-form-urlencoded",
default: { "Cookie": cookie,
throw (new Error("invalid login_url_mode: " + subject.parameters.login_url_mode)); "Authorization": lib_plankton.string.coin("Basic {{value}}", {
break; "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({ return Promise.resolve({
"url": url, "name": (data["name"] ?? null),
"label": subject.parameters.label, "email": (data["email"] ?? null),
}); });
} }
/** /**
* @see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
*/ */
function login_execute(subject, input) { async function handle_authorization_callback(subject, cookie, stuff) {
switch (subject.state.label) { if (("error" in stuff)
case enum_state_label.idle: { ||
return (new Promise((resolve, reject) => { (!("code" in stuff))
subject.state = { ||
"id": subject.state.id, (cookie === null)) {
"label": enum_state_label.wait, return Promise.reject(new Error("failed"));
"handlers": subject.state.handlers.concat([{ "resolve": resolve, "reject": reject }]), }
"token": null, else {
"name": null, const token = await token_call(subject, cookie, stuff["code"]);
}; const userinfo = await userinfo_call(subject, cookie, token);
})); return Promise.resolve({
break; "token": token,
} "userinfo": userinfo,
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;
}
} }
} }
/** oidc.handle_authorization_callback = handle_authorization_callback;
* @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 = {})); })(oidc = auth.oidc || (auth.oidc = {}));
})(auth = lib_plankton.auth || (lib_plankton.auth = {})); })(auth = lib_plankton.auth || (lib_plankton.auth = {}));
})(lib_plankton || (lib_plankton = {})); })(lib_plankton || (lib_plankton = {}));