[mod] auth:oidc

This commit is contained in:
Fenris Wolf 2024-10-20 14:26:15 +02:00
parent 62af4bf485
commit 6521f60601
4 changed files with 122 additions and 95 deletions

View file

@ -1,11 +1,11 @@
/** /**
* @author fenris * @author fenris
*/ */
type int = number; declare type int = number;
/** /**
* @author fenris * @author fenris
*/ */
type float = number; declare type float = number;
declare var process: any; declare var process: any;
declare var require: any; declare var require: any;
declare class Buffer { declare class Buffer {
@ -22,7 +22,7 @@ declare namespace lib_plankton.base {
/** /**
* @author fenris * @author fenris
*/ */
type type_pseudopointer<type_value> = { declare type type_pseudopointer<type_value> = {
value: type_value; value: type_value;
}; };
/** /**
@ -2186,7 +2186,7 @@ declare namespace lib_plankton.storage.memory {
clear(): Promise<void>; clear(): Promise<void>;
write(key: any, value: any): Promise<boolean>; write(key: any, value: any): Promise<boolean>;
delete(key: any): Promise<void>; delete(key: any): Promise<void>;
read(key: any): Promise<Awaited<type_item>>; read(key: any): Promise<type_item>;
search(term: any): Promise<{ search(term: any): Promise<{
key: string; key: string;
preview: string; preview: string;
@ -4175,7 +4175,7 @@ declare namespace lib_plankton.auth.oidc {
*/ */
export type type_subject = { export type type_subject = {
parameters: type_parameters; parameters: type_parameters;
state: {}; cases: Record<string, {}>;
}; };
/** /**
*/ */

View file

@ -1568,7 +1568,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
function verb(n) { return function (v) { return step([n, v]); }; } function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) { function step(op) {
if (f) throw new TypeError("Generator is already executing."); if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try { 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 (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]; if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) { switch (op[0]) {
@ -6564,7 +6564,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
function verb(n) { return function (v) { return step([n, v]); }; } function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) { function step(op) {
if (f) throw new TypeError("Generator is already executing."); if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try { 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 (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]; if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) { switch (op[0]) {
@ -9886,7 +9886,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
function verb(n) { return function (v) { return step([n, v]); }; } function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) { function step(op) {
if (f) throw new TypeError("Generator is already executing."); if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try { 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 (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]; if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) { switch (op[0]) {
@ -13940,7 +13940,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
function verb(n) { return function (v) { return step([n, v]); }; } function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) { function step(op) {
if (f) throw new TypeError("Generator is already executing."); if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try { 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 (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]; if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) { switch (op[0]) {
@ -14771,12 +14771,12 @@ var lib_plankton;
(function (oidc) { (function (oidc) {
/** /**
*/ */
let enum_state_label; let enum_condition;
(function (enum_state_label) { (function (enum_condition) {
enum_state_label["idle"] = "idle"; enum_condition["idle"] = "idle";
enum_state_label["wait"] = "wait"; enum_condition["wait"] = "wait";
enum_state_label["ready"] = "ready"; enum_condition["ready"] = "ready";
})(enum_state_label || (enum_state_label = {})); })(enum_condition || (enum_condition = {}));
; ;
/** /**
*/ */
@ -14796,26 +14796,38 @@ var lib_plankton;
?? ??
"OIDC"), "OIDC"),
}, },
"state": {} "cases": {}
}; };
} }
oidc.make = make; 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
*/ */
function authorization_url(subject) { function authorization_url(subject, state) {
return lib_plankton.url.encode(Object.assign(lib_plankton.url.decode(subject.parameters.url_authorization), { const url = 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": lib_plankton.random.generate_guid(), "state": state,
"redirect_uri": subject.parameters.url_redirect, "redirect_uri": subject.parameters.url_redirect,
}) })
})); }));
return url;
} }
oidc.authorization_url = authorization_url; oidc.authorization_url = authorization_url;
/**
*/
function prepare_login(subject) {
const state = lib_plankton.random.generate_guid();
subject.cases[state] = {};
return {
"state": state,
"authorization_url": authorization_url(state),
};
}
oidc.prepare_login = prepare_login;
/** /**
* https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest * https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest
*/ */
@ -14890,20 +14902,26 @@ var lib_plankton;
/** /**
*/ */
async function handle_authorization_callback(subject, cookie, stuff) { async function handle_authorization_callback(subject, cookie, stuff) {
if (("error" in stuff) const state = stuff["state"];
|| if (!(state in subject.cases)) {
(!("code" in stuff)) return Promise.reject(new Error("no case for this state"));
||
(cookie === null)) {
return Promise.reject(new Error("failed"));
} }
else { else {
const token = await token_call(subject, cookie, stuff["code"]); if (("error" in stuff)
const userinfo = await userinfo_call(subject, cookie, token); ||
return Promise.resolve({ (!("code" in stuff))
"token": token, ||
"userinfo": userinfo, (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,
});
}
} }
} }
oidc.handle_authorization_callback = handle_authorization_callback; oidc.handle_authorization_callback = handle_authorization_callback;

View file

@ -56,8 +56,8 @@ namespace _zeitbild.api
name : (null | string); name : (null | string);
email : (null | string); email : (null | string);
}; };
} = await lib_plankton.auth.oidc.handle_authorization_callback( redirect_uri_template : string;
_zeitbild.auth.oidc_subject(), } = await _zeitbild.auth.oidc_handle_authorization_callback(
(stuff.headers["Cookie"] ?? stuff.headers["cookie"] ?? null), (stuff.headers["Cookie"] ?? stuff.headers["cookie"] ?? null),
stuff.query_parameters stuff.query_parameters
); );
@ -94,7 +94,6 @@ namespace _zeitbild.api
} }
} }
); );
const redirect_uri_template : string = _zeitbild.auth.oidc_get_redirect_uri_template("foo");
return Promise.resolve( return Promise.resolve(
{ {
"status_code": 200, "status_code": 200,
@ -102,7 +101,7 @@ namespace _zeitbild.api
"<html><head><meta http-equiv=\"refresh\" content=\"0; url={{url}}\" /></head><body></body></html>", "<html><head><meta http-equiv=\"refresh\" content=\"0; url={{url}}\" /></head><body></body></html>",
{ {
"url": lib_plankton.string.coin( "url": lib_plankton.string.coin(
redirect_uri_template, data.redirect_uri_template,
{ {
"session_key": session_key, "session_key": session_key,
} }

View file

@ -11,6 +11,13 @@ namespace _zeitbild.auth
) = null; ) = null;
/**
*/
let _subject_oidc : (null | lib_plankton.auth.oidc.type_subject) = null;
/** /**
*/ */
let _oidc_redict_uri_template_map : ( let _oidc_redict_uri_template_map : (
@ -20,30 +27,6 @@ namespace _zeitbild.auth
) = null; ) = null;
/**
*/
export function oidc_subject(
)
{
return lib_plankton.auth.oidc.make(
{
"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",
],
"label": _zeitbild.conf.get().authentication.data.label,
}
);
}
/** /**
*/ */
export function oidc_get_redirect_uri_template( export function oidc_get_redirect_uri_template(
@ -54,12 +37,6 @@ namespace _zeitbild.auth
throw (new Error("apparently not initialized yet")); throw (new Error("apparently not initialized yet"));
} }
else { else {
lib_plankton.log.info(
"oidc_redirect_uri_templates",
{
"val": lib_plankton.map.dump(_oidc_redict_uri_template_map),
}
);
return _oidc_redict_uri_template_map.get(key); return _oidc_redict_uri_template_map.get(key);
} }
} }
@ -88,6 +65,22 @@ lib_plankton.log.info(
break; break;
} }
case "oidc": { case "oidc": {
_subject_oidc = lib_plankton.auth.oidc.make(
{
"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",
],
"label": _zeitbild.conf.get().authentication.data.label,
}
);
_oidc_redict_uri_template_map = lib_plankton.map.simplemap.implementation_map( _oidc_redict_uri_template_map = lib_plankton.map.simplemap.implementation_map(
lib_plankton.map.simplemap.make( lib_plankton.map.simplemap.make(
) )
@ -113,21 +106,21 @@ lib_plankton.log.info(
{ {
switch (_zeitbild.conf.get().authentication.kind) { switch (_zeitbild.conf.get().authentication.kind) {
case "oidc": { case "oidc": {
const subject : lib_plankton.auth.oidc.type_subject = oidc_subject(); if (_subject_oidc === null) {
if (_oidc_redict_uri_template_map === null) { throw (new Error("not initialized yet"));
throw (new Error("apparently not initialized yet"));
} }
else { else {
const stuff : {state : string; authorization_url : string;} = lib_plankton.auth.oidc.prepare_login(_subject_oidc);
_oidc_redict_uri_template_map.set( _oidc_redict_uri_template_map.set(
"foo", // TODO proper key stuff.state,
input["oidc_redirect_uri_template"] input["oidc_redirect_uri_template"]
); );
return Promise.resolve( return Promise.resolve(
{ {
"kind": "oidc", "kind": "oidc",
"data": { "data": {
"url": lib_plankton.auth.oidc.authorization_url(subject), "url": stuff.authorization_url,
"label": subject.parameters.label, "label": _zeitbild.conf.get().authentication.data.label,
} }
} }
); );
@ -157,31 +150,48 @@ lib_plankton.log.info(
/** /**
*/ */
export function execute( export function oidc_handle_authorization_callback(
input : any cookie : (null | string),
) : Promise<string> data : Record<string, string>
) : Promise<
{
token : string;
userinfo : {
name : (null | string);
email : (null | string);
};
redirect_uri_template : string;
}
>
{ {
if (_subject === null) { const state : string = data["state"];
return Promise.reject(new Error("not initialized yet")); const result : {
} token : string;
else { userinfo : {
return _subject.login_execute(input); name : (null | string);
} email : (null | string);
} };
} = await lib_plankton.auth.oidc.handle_authorization_callback(
_oidc_subject,
/** cookie,
*/ data
export function control( );
input : any return Promise.resolve<
) : Promise<void> {
{ token : string;
if (_subject === null) { userinfo : {
return Promise.reject(new Error("not initialized yet")); name : (null | string);
} email : (null | string);
else { };
return _subject.login_control(input); redirect_uri_template : string;
} }
>(
{
"token": result.token,
"userinfo": result.userinfo,
"redirect_uri_template": _oidc_redict_uri_template_map.get(state),
}
)
} }
} }