/* This file is part of »zeitbild«. Copyright 2025 'kcf' »zeitbild« 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. »zeitbild« 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 »zeitbild«. If not, see . */ namespace _zeitbild.auth { /** */ let _subject : ( null | lib_plankton.auth.type_auth ) = null; /** */ let _subject_oidc : (null | lib_plankton.auth.oidc.type_subject) = null; /** */ let _oidc_redict_uri_template_map : ( null | lib_plankton.map.type_map ) = null; /** */ export function oidc_get_redirect_uri_template( key : string ) : string { if (_oidc_redict_uri_template_map === null) { throw (new Error("apparently not initialized yet")); } else { return _oidc_redict_uri_template_map.get(key); } } /** */ export function init( ) : Promise { switch (_zeitbild.conf.get().authentication.kind) { case "internal": { _subject = lib_plankton.auth.internal.implementation_auth( { "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_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", "groups", ], "label": _zeitbild.conf.get().authentication.data.label, } ); _oidc_redict_uri_template_map = lib_plankton.map.simplemap.implementation_map( lib_plankton.map.simplemap.make( ) ); // TODO return Promise.resolve(undefined); break; } default: { // do nothing break; } } return Promise.resolve(undefined); } /** */ export function prepare( input : any ) : Promise<{kind : string; data : any;}> { switch (_zeitbild.conf.get().authentication.kind) { case "oidc": { if ((_subject_oidc === null) || (_oidc_redict_uri_template_map === null)) { throw (new Error("not initialized yet")); } else { const stuff : {state : string; authorization_url : string;} = lib_plankton.auth.oidc.prepare_login(_subject_oidc); _oidc_redict_uri_template_map.set( stuff.state, input["oidc_redirect_uri_template"] ); return Promise.resolve( { "kind": "oidc", "data": { "url": stuff.authorization_url, "label": _zeitbild.conf.get().authentication.data.label, } } ); } break; } default: { 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, }) ) ); } break; } } } /** */ export async function oidc_handle_authorization_callback( cookie : (null | string), data : Record ) : Promise< { token : string; userinfo : lib_plankton.auth.oidc.type_userinfo; redirect_uri_template : string; } > { if ((_subject_oidc === null) || (_oidc_redict_uri_template_map === null)) { throw (new Error("not initialized yet")); } else { const state : string = data["state"]; const result : { token : string; userinfo : lib_plankton.auth.oidc.type_userinfo; } = await lib_plankton.auth.oidc.handle_authorization_callback( _subject_oidc, cookie, data ); return Promise.resolve< { token : string; userinfo : lib_plankton.auth.oidc.type_userinfo; redirect_uri_template : string; } >( { "token": result.token, "userinfo": result.userinfo, "redirect_uri_template": _oidc_redict_uri_template_map.get(state), } ); } } /** * @todo switch for enabling/disabling auto provisioning */ export async function oidc_adapt_user( userinfo : lib_plankton.auth.oidc.type_userinfo ) : Promise< { id : _zeitbild.type_user_id; object : _zeitbild.type_user_object; } > { if (userinfo.name === null) { return Promise.reject(new Error("IDP did not return user name")); } else { // groups const group_ids : Array<_zeitbild.type_group_id> = await (async () => { const derive_name : ((group_name_raw : string) => string) = ( (group_name_raw) => lib_plankton.string.coin( "auto-{{name_raw}}", { "name_raw": group_name_raw, } ) ); const derive_label : ((group_name_raw : string) => string) = ( (group_name_raw) => lib_plankton.string.coin( "{{name_raw}}", { "name_raw": group_name_raw, } ) ); return Promise.all<_zeitbild.type_group_id>( (userinfo.groups ?? []) .map( async (group_name_raw) => { const group_name : string = derive_name(group_name_raw); const group_id_raw : (null | _zeitbild.type_group_id) = await ( _zeitbild.repository.group.identify(group_name) .catch(() => Promise.resolve(null)) ); if (group_id_raw === null) { // create const group_id : _zeitbild.type_group_id = await _zeitbild.service.group.add( { "name": group_name, "label": derive_label(group_name_raw), } ); lib_plankton.log.info( "zeitbild.oidc_adapt_user.auto_provisioned_group", { "id": group_id, "name": group_name, } ); return group_id; } else { // update const group_id : _zeitbild.type_group_id = group_id_raw; await _zeitbild.service.group.change( group_id, { "name": group_name, "label": derive_label(group_name_raw), } ); return group_id; } } ) ); }) (); // user const user : { id : _zeitbild.type_user_id; object : _zeitbild.type_user_object; } = await (async () => { const user_id_raw : (null | _zeitbild.type_user_id) = await ( _zeitbild.service.user.identify(userinfo.name as string) .catch(() => Promise.resolve(null)) ); if (user_id_raw === null) { // provision const user_object : _zeitbild.type_user_object = { "name": (userinfo.name as string), "groups": group_ids, "email_address": userinfo.email, "dav_token": null, }; const user_id : _zeitbild.type_user_id = await _zeitbild.service.user.add( user_object ); lib_plankton.log.info( "user_provisioned", { "id": user_id, "name": user_object.name, } ); return {"id": user_id, "object": user_object}; } else { // update const user_id : _zeitbild.type_user_id = user_id_raw; const user_object : _zeitbild.type_user_object = await _zeitbild.service.user.get(user_id); user_object.name = (userinfo.name as string); user_object.groups = group_ids; user_object.email_address = userinfo.email; await _zeitbild.service.user.change( user_id, user_object ); lib_plankton.log.info( "user_updated", { "id": user_id, "name": user_object.name, } ); return {"id": user_id, "object": user_object}; } }) (); return user; } } }