368 lines
8.6 KiB
TypeScript
368 lines
8.6 KiB
TypeScript
/*
|
|
This file is part of »zeitbild«.
|
|
|
|
Copyright 2025 'kcf' <fenris@folksprak.org>
|
|
|
|
»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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
namespace _zeitbild.auth
|
|
{
|
|
|
|
/**
|
|
*/
|
|
let _subject : (
|
|
null
|
|
|
|
|
lib_plankton.auth.type_auth<any, any, any>
|
|
) = null;
|
|
|
|
|
|
|
|
/**
|
|
*/
|
|
let _subject_oidc : (null | lib_plankton.auth.oidc.type_subject) = null;
|
|
|
|
|
|
|
|
/**
|
|
*/
|
|
let _oidc_redict_uri_template_map : (
|
|
null
|
|
|
|
|
lib_plankton.map.type_map<string, string>
|
|
) = 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<void>
|
|
{
|
|
switch (_zeitbild.conf.get().authentication.kind) {
|
|
case "internal":
|
|
{
|
|
_subject = lib_plankton.auth.internal.implementation_auth(
|
|
{
|
|
"password_image_chest": {
|
|
"setup": (input) => Promise.resolve<void>(undefined),
|
|
"clear": () => Promise.reject<void>("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<any>("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<void>(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<string, string>
|
|
) : 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;
|
|
}
|
|
}
|
|
|
|
}
|