diff --git a/lib/plankton/plankton.d.ts b/lib/plankton/plankton.d.ts
index d07a527..8b1db32 100644
--- a/lib/plankton/plankton.d.ts
+++ b/lib/plankton/plankton.d.ts
@@ -4597,7 +4597,7 @@ declare namespace lib_plankton.auth.oidc {
type type_token = string;
/**
*/
- type type_userinfo = {
+ export type type_userinfo = {
name: (null | string);
label: (null | string);
email: (null | string);
diff --git a/source/api/actions/session_oidc.ts b/source/api/actions/session_oidc.ts
index fee9dda..ebf7515 100644
--- a/source/api/actions/session_oidc.ts
+++ b/source/api/actions/session_oidc.ts
@@ -21,38 +21,6 @@ along with »zeitbild«. If not, see .
namespace _zeitbild.api
{
- /**
- */
- function get_group_name(
- group_name_raw : string
- )
- : string
- {
- return lib_plankton.string.coin(
- "auto-{{name_raw}}",
- {
- "name_raw": group_name_raw,
- }
- );
- }
-
-
- /**
- */
- function get_group_label(
- group_name_raw : string
- )
- : string
- {
- return lib_plankton.string.coin(
- "{{name_raw}}",
- {
- "name_raw": group_name_raw,
- }
- );
- }
-
-
/**
*/
export function register_session_oidc(
@@ -103,132 +71,39 @@ namespace _zeitbild.api
"execution": async (stuff) => {
const data : {
token : string;
- userinfo : {
- name : (null | string);
- email : (null | string);
- groups : (null | Array);
- };
+ userinfo : lib_plankton.auth.oidc.type_userinfo;
redirect_uri_template : string;
} = await _zeitbild.auth.oidc_handle_authorization_callback(
(stuff.headers["Cookie"] ?? stuff.headers["cookie"] ?? null),
stuff.query_parameters
);
- if (data.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 Promise.all<_zeitbild.type_group_id>(
- (data.userinfo.groups ?? [])
- .map(
- async (group_name_raw) => {
- const group_name : string = get_group_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)
- {
- const group_id : _zeitbild.type_group_id = await _zeitbild.service.group.add(
- {
- "name": group_name,
- "label": get_group_label(group_name_raw),
- }
- );
- return group_id;
- }
- else
- {
- const group_id : _zeitbild.type_group_id = group_id_raw;
- await _zeitbild.service.group.change(
- group_id,
- {
- "name": group_name,
- "label": get_group_label(group_name_raw),
- }
- );
- return group_id;
- }
+
+ const user = await _zeitbild.auth.oidc_adapt_user(data.userinfo);
+
+ const session_key : string = await lib_plankton.session.begin(
+ user.object.name,
+ {
+ "data": {
+ "oidc_token": data.token,
+ }
+ }
+ );
+ return Promise.resolve(
+ {
+ "status_code": 200,
+ "data": lib_plankton.string.coin(
+ "",
+ {
+ "url": lib_plankton.string.coin(
+ data.redirect_uri_template,
+ {
+ "session_key": session_key,
+ }
+ ),
}
- )
- );
-
- const user_id : _zeitbild.type_user_id = await (async () => {
- const user_object : _zeitbild.type_user_object = {
- "name": (data.userinfo.name as string),
- "groups": group_ids,
- "email_address": data.userinfo.email,
- "dav_token": null,
- };
- const user_id_raw : (null | _zeitbild.type_user_id) = await (
- _zeitbild.service.user.identify(data.userinfo.name as string)
- .catch(() => Promise.resolve(null))
- );
- if (user_id_raw === null)
- {
- // provision
- 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 user_id;
- }
- else
- {
- const user_id : _zeitbild.type_user_id = user_id_raw;
- // update
- await _zeitbild.service.user.change(
- user_id,
- user_object
- );
- lib_plankton.log.info(
- "user_updated",
- {
- "id": user_id,
- "name": user_object.name,
- }
- );
- return user_id;
- }
- }) ();
-
- const session_key : string = await lib_plankton.session.begin(
- data.userinfo.name,
- {
- "data": {
- "oidc_token": data.token,
- }
- }
- );
- return Promise.resolve(
- {
- "status_code": 200,
- "data": lib_plankton.string.coin(
- "",
- {
- "url": lib_plankton.string.coin(
- data.redirect_uri_template,
- {
- "session_key": session_key,
- }
- ),
- }
- ),
- }
- );
- }
+ ),
+ }
+ );
},
}
);
diff --git a/source/auth.ts b/source/auth.ts
index cfd094b..82b797b 100644
--- a/source/auth.ts
+++ b/source/auth.ts
@@ -188,11 +188,7 @@ namespace _zeitbild.auth
) : Promise<
{
token : string;
- userinfo : {
- name : (null | string);
- email : (null | string);
- groups : (null | Array);
- };
+ userinfo : lib_plankton.auth.oidc.type_userinfo;
redirect_uri_template : string;
}
>
@@ -206,11 +202,7 @@ namespace _zeitbild.auth
const state : string = data["state"];
const result : {
token : string;
- userinfo : {
- name : (null | string);
- email : (null | string);
- groups : (null | Array);
- };
+ userinfo : lib_plankton.auth.oidc.type_userinfo;
} = await lib_plankton.auth.oidc.handle_authorization_callback(
_subject_oidc,
cookie,
@@ -219,11 +211,7 @@ namespace _zeitbild.auth
return Promise.resolve<
{
token : string;
- userinfo : {
- name : (null | string);
- email : (null | string);
- groups : (null | Array);
- };
+ userinfo : lib_plankton.auth.oidc.type_userinfo;
redirect_uri_template : string;
}
>(
@@ -236,4 +224,144 @@ namespace _zeitbild.auth
}
}
+
+ /**
+ * @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;
+ }
+ }
+
}
diff --git a/source/repositories/calendar.ts b/source/repositories/calendar.ts
index 03e54e3..43266df 100644
--- a/source/repositories/calendar.ts
+++ b/source/repositories/calendar.ts
@@ -328,7 +328,6 @@ namespace _zeitbild.repository.calendar
}
-
/**
*/
export async function read(
@@ -601,7 +600,6 @@ namespace _zeitbild.repository.calendar
/**
- * @todo caching
*/
export async function overview(
user_id : (null | _zeitbild.type_user_id)
@@ -612,122 +610,152 @@ namespace _zeitbild.repository.calendar
>
>
{
+ type type_data = {
+ hits_core : Array<
+ {
+ key : int;
+ preview : Record;
+ }
+ >;
+ hits_access_attributed_group : Array<
+ {
+ key : int;
+ preview : Record;
+ }
+ >;
+ hits_access_attributed_user : Array<
+ {
+ key : int;
+ preview : Record;
+ }
+ >;
+ };
return lib_plankton.cache.get_complex>(
_zeitbild.cache_regular,
"calendar_overview",
{
"user_id": user_id,
},
- null,
- () => (
- lib_plankton.file.read("sql/calendar_overview.sql")
- .then(
- (template) => _zeitbild.database.get_implementation().query_free_get(
+ 60,
+ async () => lib_plankton.call.convey(
+ {
+ "hits_core": await get_core_store().search(
{
- "template": template,
- "arguments": {
- "user_id": user_id,
- }
+ "expression": "TRUE",
+ "arguments": {}
}
- )
- )
- .then(
- (rows) => Promise.resolve(
- lib_plankton.call.convey(
- rows,
- [
- (x : Array>) => x.map(
- (row : Record) => ({
- "id": row["id"],
- "name": lib_plankton.call.convey(
- row["name"],
- [
- // JSON.parse,
- (x : Array) => x[0],
- ]
- ),
- "hue": lib_plankton.call.convey(
- row["hue"],
- [
- // JSON.parse,
- (x : Array) => x[0],
- (x : int) => (x / hue_scaling),
- ]
- ),
- /**
- * @todo use _zeitbild.access_level_determine
- */
- "access_level": _zeitbild.access_level_determine_raw(
- lib_plankton.call.convey(
- row["access_public"],
+ ),
+ "hits_access_attributed_group": await get_access_attributed_group_chest().search(
+ (user_id === null)
+ ?
+ {
+ "expression": "TRUE",
+ "arguments": {}
+ }
+ :
+ {
+ "expression": "(group_id IN (SELECT group_id FROM user_groups WHERE (user_id = $user_id)))",
+ "arguments": {"user_id": user_id}
+ }
+ ),
+ "hits_access_attributed_user": await get_access_attributed_user_chest().search(
+ (user_id === null)
+ ?
+ {
+ "expression": "TRUE",
+ "arguments": {}
+ }
+ :
+ {
+ "expression": "(user_id = $user_id)",
+ "arguments": {"user_id": user_id}
+ }
+ ),
+ },
+ [
+ // transform
+ (data : type_data) => data.hits_core.map(
+ (hit_core) => {
+ const calendar_id : _zeitbild.type_calendar_id = hit_core.key;
+ return {
+ "id": calendar_id,
+ "name": hit_core.preview["name"],
+ "hue": (hit_core.preview["hue"] / hue_scaling),
+ "access_level": _zeitbild.access_level_determine_raw(
+ hit_core.preview["access_public"],
+ (
+ (user_id === null)
+ ?
+ null
+ :
+ {
+ "default": decode_access_level(hit_core.preview["access_level_default"]),
+ "group": lib_plankton.call.convey(
+ data.hits_access_attributed_group,
[
- // JSON.parse,
- (x : Array) => x[0],
+ (x : Array<{key : int; preview : Record}>) => x.filter(
+ hit_access_attributed_group => (
+ (hit_access_attributed_group.preview.calendar_id === calendar_id)
+ )
+ ),
+ (x : Array<{key : int; preview : Record}>) => x.map(
+ hit_access_attributed_group => hit_access_attributed_group.preview.level
+ ),
+ (x : Array) => x.map(
+ decode_access_level
+ ),
]
),
- (
- (user_id === null)
- ?
- null
- :
- {
- "default": lib_plankton.call.convey(
- row["access_level_default"],
- [
- // JSON.parse,
- (x : Array) => x[0],
- decode_access_level,
- ]
+ "user": lib_plankton.call.convey(
+ data.hits_access_attributed_user,
+ [
+ (x : Array<{key : int; preview : Record}>) => x.filter(
+ hits_access_attributed_user => (
+ (hits_access_attributed_user.preview.calendar_id === calendar_id)
+ )
),
- "group": lib_plankton.call.convey(
- row["access_level_attributed_group"],
- [
- // JSON.parse,
- (x : Array<(null | int)>) => x.filter(y => (y !== null)),
- (x : Array) => x.map(decode_access_level),
- ]
+ (x : Array<{key : int; preview : Record}>) => x.map(
+ hits_access_attributed_user => hits_access_attributed_user.preview.level
),
- "user": lib_plankton.call.convey(
- row["access_level_attributed_user"],
- [
- // JSON.parse,
- (x : Array<(null | int)>) => x.filter(y => (y !== null)),
- (x : Array) => x.map(decode_access_level),
- (x : Array<_zeitbild.enum_access_level>) => ((x.length > 0) ? x[0] : null),
- ]
+ (x : Array) => x.map(
+ decode_access_level
),
- }
- )
- ),
- })
- ),
- (x : Array) => x.filter(
- (row) => (
- ! _zeitbild.access_level_order(
- row.access_level,
- _zeitbild.enum_access_level.none
- )
+ (x : Array<_zeitbild.enum_access_level>) => (x[0] ?? null),
+ ]
+ ),
+ }
)
),
- (x : Array) => lib_plankton.list.sorted(
- x,
- {
- "compare_element": lib_plankton.order.order_lexicographic_pair_wrapped(
- row => row.access_level,
- row => row.id,
- {
- "order_first": (a, b) => _zeitbild.access_level_order(b, a),
- "order_second": (a, b) => (a <= b)
- }
- ),
- }
- ),
- ]
+ };
+ }
+ ),
+ // only keep visible calendars
+ (x : Array) => x.filter(
+ (row) => (
+ ! _zeitbild.access_level_order(
+ row.access_level,
+ _zeitbild.enum_access_level.none
+ )
)
- )
- )
+ ),
+ // sort by access level and name
+ (x : Array) => lib_plankton.list.sorted(
+ x,
+ {
+ "compare_element": lib_plankton.order.order_lexicographic_pair_wrapped(
+ row => row.access_level,
+ row => row.id,
+ {
+ "order_first": (a, b) => _zeitbild.access_level_order(b, a),
+ "order_second": (a, b) => (a <= b)
+ }
+ ),
+ }
+ ),
+ ]
)
);
}
+
}
diff --git a/source/repositories/sql/calendar_overview.sql b/source/repositories/sql/calendar_overview.sql
deleted file mode 100644
index 70a55b6..0000000
--- a/source/repositories/sql/calendar_overview.sql
+++ /dev/null
@@ -1,15 +0,0 @@
-SELECT
- x.id AS id,
- JSON_AGG(x.name) AS name,
- JSON_AGG(x.hue) AS hue,
- JSON_AGG(x.access_public) AS access_public,
- JSON_AGG(x.access_level_default) AS access_level_default,
- JSON_AGG(y1.level) AS access_level_attributed_group,
- JSON_AGG(y2.level) AS access_level_attributed_user
-FROM
- calendars AS x
- LEFT OUTER JOIN calendar_access_attributed_group AS y1 ON ((x.id = y1.calendar_id) AND (y1.group_id IN (SELECT group_id FROM user_groups WHERE (user_id = $user_id))))
- LEFT OUTER JOIN calendar_access_attributed_user AS y2 ON ((x.id = y2.calendar_id) AND (y2.user_id = $user_id))
-GROUP BY
- x.id
-;
diff --git a/tools/makefile b/tools/makefile
index d894ee2..bdbbd70 100644
--- a/tools/makefile
+++ b/tools/makefile
@@ -18,7 +18,7 @@ cmd_tsc := ${dir_tools}/typescript/node_modules/.bin/tsc
## rules
.PHONY: default
-default: node_modules sql ${dir_build}/zeitbild node_modules
+default: node_modules ${dir_build}/zeitbild node_modules
.PHONY: node_modules
node_modules:
@@ -26,13 +26,6 @@ node_modules:
@ ${cmd_log} "node modules …"
@ ${cmd_cp} -r -u ${dir_lib}/node/node_modules/* ${dir_build}/node_modules/
-.PHONY: sql
-sql: \
- $(wildcard ${dir_source}/repositories/sql/*)
- @ ${cmd_log} "sql …"
- @ ${cmd_mkdir} ${dir_build}/sql
- @ ${cmd_cp} -r -u $^ ${dir_build}/sql/
-
${dir_temp}/conf.ts: \
${dir_source}/conf.ts.tpl \
${dir_source}/conf.schema.json