@valentinkolb/cloud 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +69 -0
- package/public/logo.svg +1 -0
- package/scripts/build.ts +113 -0
- package/scripts/preload.ts +73 -0
- package/src/_internal/define-app.ts +399 -0
- package/src/_internal/heartbeat.ts +33 -0
- package/src/_internal/registry.ts +100 -0
- package/src/_internal/runtime-context.ts +38 -0
- package/src/api/accounts-entities.ts +134 -0
- package/src/api/admin-lifecycle.ts +210 -0
- package/src/api/auth/schemas.ts +28 -0
- package/src/api/auth.ts +230 -0
- package/src/api/index.ts +66 -0
- package/src/api/me.ts +206 -0
- package/src/api/search/schemas.ts +43 -0
- package/src/api/search.ts +130 -0
- package/src/clients/core.ts +19 -0
- package/src/config/env.ts +23 -0
- package/src/config/index.ts +6 -0
- package/src/config/ssr.ts +58 -0
- package/src/contracts/app.ts +140 -0
- package/src/contracts/index.ts +5 -0
- package/src/contracts/profile.ts +67 -0
- package/src/contracts/registry.ts +50 -0
- package/src/contracts/settings-types.ts +84 -0
- package/src/contracts/shared.ts +258 -0
- package/src/contracts/widgets.ts +121 -0
- package/src/index.ts +6 -0
- package/src/server/api/index.ts +1 -0
- package/src/server/api/respond.ts +55 -0
- package/src/server/api-client.ts +54 -0
- package/src/server/app-context.ts +39 -0
- package/src/server/index.ts +62 -0
- package/src/server/middleware/auth.ts +168 -0
- package/src/server/middleware/index.ts +7 -0
- package/src/server/middleware/middleware.ts +47 -0
- package/src/server/middleware/openapi.ts +126 -0
- package/src/server/middleware/rate-limit.ts +126 -0
- package/src/server/middleware/request-logger.ts +41 -0
- package/src/server/middleware/validator.ts +35 -0
- package/src/server/services/access.ts +294 -0
- package/src/server/services/freeipa/client.ts +100 -0
- package/src/server/services/freeipa/index.ts +9 -0
- package/src/server/services/freeipa/session.ts +78 -0
- package/src/server/services/freeipa/tls.ts +48 -0
- package/src/server/services/freeipa/util.ts +60 -0
- package/src/server/services/geo.ts +154 -0
- package/src/server/services/index.ts +28 -0
- package/src/server/services/services.ts +13 -0
- package/src/services/account-lifecycle/audit.ts +41 -0
- package/src/services/account-lifecycle/index.ts +907 -0
- package/src/services/account-lifecycle/scheduler.ts +347 -0
- package/src/services/account-model.ts +21 -0
- package/src/services/accounts/app.ts +966 -0
- package/src/services/accounts/authz.ts +22 -0
- package/src/services/accounts/base-group.ts +11 -0
- package/src/services/accounts/base-user.ts +45 -0
- package/src/services/accounts/entities.ts +529 -0
- package/src/services/accounts/group-sql.ts +106 -0
- package/src/services/accounts/groups.ts +246 -0
- package/src/services/accounts/index.ts +14 -0
- package/src/services/accounts/ipa-data.ts +64 -0
- package/src/services/accounts/lifecycle.ts +2 -0
- package/src/services/accounts/local-groups.ts +491 -0
- package/src/services/accounts/model.ts +135 -0
- package/src/services/accounts/switching.ts +117 -0
- package/src/services/accounts/users.ts +714 -0
- package/src/services/auth-flows/index.ts +6 -0
- package/src/services/auth-flows/ipa.ts +128 -0
- package/src/services/auth-flows/magic-link.ts +119 -0
- package/src/services/freeipa-config.ts +89 -0
- package/src/services/index.ts +46 -0
- package/src/services/ipa/auth.ts +122 -0
- package/src/services/ipa/groups.ts +684 -0
- package/src/services/ipa/guard.ts +17 -0
- package/src/services/ipa/index.ts +17 -0
- package/src/services/ipa/profile.ts +90 -0
- package/src/services/ipa/search.ts +154 -0
- package/src/services/ipa/sync.ts +740 -0
- package/src/services/ipa/users.ts +794 -0
- package/src/services/logging/index.ts +294 -0
- package/src/services/notifications/email.ts +123 -0
- package/src/services/notifications/index.ts +413 -0
- package/src/services/postgres.ts +51 -0
- package/src/services/providers/index.ts +27 -0
- package/src/services/providers/local/auth.ts +13 -0
- package/src/services/providers/local/index.ts +4 -0
- package/src/services/providers/local/users.ts +255 -0
- package/src/services/session/index.ts +137 -0
- package/src/services/settings/api.ts +61 -0
- package/src/services/settings/app.ts +101 -0
- package/src/services/settings/crypto.ts +69 -0
- package/src/services/settings/defaults.ts +824 -0
- package/src/services/settings/index.ts +203 -0
- package/src/services/settings/namespace.ts +9 -0
- package/src/services/settings/snapshot.ts +49 -0
- package/src/services/settings/store.ts +179 -0
- package/src/services/settings/templates.ts +10 -0
- package/src/services/weather/forecast.ts +287 -0
- package/src/services/weather/geo.ts +110 -0
- package/src/services/weather/index.ts +99 -0
- package/src/services/weather/location.ts +24 -0
- package/src/services/weather/locations.ts +125 -0
- package/src/services/weather/migrate.ts +22 -0
- package/src/services/weather/types.ts +61 -0
- package/src/services/weather/ui.ts +50 -0
- package/src/shared/account-display.ts +17 -0
- package/src/shared/account-session.ts +15 -0
- package/src/shared/icons.ts +109 -0
- package/src/shared/index.ts +10 -0
- package/src/shared/markdown/client.ts +130 -0
- package/src/shared/markdown/extensions/code.ts +58 -0
- package/src/shared/markdown/extensions/images.ts +43 -0
- package/src/shared/markdown/extensions/info-blocks.ts +93 -0
- package/src/shared/markdown/extensions/katex.ts +120 -0
- package/src/shared/markdown/extensions/links.ts +34 -0
- package/src/shared/markdown/extensions/tables.ts +88 -0
- package/src/shared/markdown/extensions/task-list.ts +53 -0
- package/src/shared/markdown/index.ts +97 -0
- package/src/shared/markdown/shared.ts +36 -0
- package/src/ssr/AdminLayout.tsx +42 -0
- package/src/ssr/AdminSidebar.tsx +95 -0
- package/src/ssr/Footer.island.tsx +62 -0
- package/src/ssr/GlobalSearchDialog.tsx +389 -0
- package/src/ssr/GlobalSearchHelpDialog.tsx +106 -0
- package/src/ssr/GlobalSearchTrigger.island.tsx +42 -0
- package/src/ssr/HotkeysHelpRail.island.tsx +99 -0
- package/src/ssr/Layout.tsx +326 -0
- package/src/ssr/MoreAppsDropdown.island.tsx +61 -0
- package/src/ssr/NavMenu.island.tsx +108 -0
- package/src/ssr/ThemeToggleRail.island.tsx +27 -0
- package/src/ssr/index.ts +5 -0
- package/src/ssr/islands/SearchBar.island.tsx +77 -0
- package/src/ssr/islands/index.ts +1 -0
- package/src/ssr/runtime.ts +22 -0
- package/src/styles/base-popover.css +28 -0
- package/src/styles/effects.css +65 -0
- package/src/styles/global.css +133 -0
- package/src/styles/input.css +54 -0
- package/src/styles/tokens.css +35 -0
- package/src/styles/utilities-buttons.css +125 -0
- package/src/styles/utilities-feedback.css +65 -0
- package/src/styles/utilities-layout.css +122 -0
- package/src/styles/utilities-navigation.css +196 -0
- package/src/types/ambient.d.ts +8 -0
- package/src/ui/admin-settings.tsx +148 -0
- package/src/ui/dialog-core.ts +146 -0
- package/src/ui/filter/FilterChip.tsx +196 -0
- package/src/ui/filter/index.ts +2 -0
- package/src/ui/index.ts +19 -0
- package/src/ui/input/Checkbox.tsx +55 -0
- package/src/ui/input/ColorInput.tsx +122 -0
- package/src/ui/input/DateTimeInput.tsx +86 -0
- package/src/ui/input/ImageInput.tsx +170 -0
- package/src/ui/input/NumberInput.tsx +113 -0
- package/src/ui/input/PinInput.tsx +169 -0
- package/src/ui/input/SegmentedControl.tsx +99 -0
- package/src/ui/input/Select.tsx +288 -0
- package/src/ui/input/SelectChip.tsx +61 -0
- package/src/ui/input/Slider.tsx +118 -0
- package/src/ui/input/Switch.tsx +62 -0
- package/src/ui/input/TagsInput.tsx +115 -0
- package/src/ui/input/TextInput.tsx +160 -0
- package/src/ui/input/index.ts +13 -0
- package/src/ui/input/types.ts +42 -0
- package/src/ui/input/util.tsx +105 -0
- package/src/ui/ipa/Avatar.tsx +28 -0
- package/src/ui/ipa/GroupView.tsx +36 -0
- package/src/ui/ipa/LoginBtn.tsx +16 -0
- package/src/ui/ipa/UserView.tsx +58 -0
- package/src/ui/ipa/index.ts +4 -0
- package/src/ui/misc/ContextMenu.tsx +211 -0
- package/src/ui/misc/CopyButton.tsx +28 -0
- package/src/ui/misc/Dropdown.tsx +194 -0
- package/src/ui/misc/EntitySearch.tsx +213 -0
- package/src/ui/misc/Lightbox.tsx +194 -0
- package/src/ui/misc/LinkCard.tsx +34 -0
- package/src/ui/misc/LogEntriesTable.tsx +61 -0
- package/src/ui/misc/MarkdownView.tsx +65 -0
- package/src/ui/misc/Pagination.tsx +51 -0
- package/src/ui/misc/PermissionEditor.tsx +379 -0
- package/src/ui/misc/ProgressBar.tsx +47 -0
- package/src/ui/misc/RemoveBtn.tsx +27 -0
- package/src/ui/misc/StatCell.tsx +90 -0
- package/src/ui/misc/index.ts +18 -0
- package/src/ui/navigation.ts +32 -0
- package/src/ui/prompts.tsx +854 -0
- package/src/ui/sidebar.tsx +468 -0
- package/src/ui/widgets/Widget.tsx +62 -0
- package/src/ui/widgets/WidgetCard.tsx +19 -0
- package/src/ui/widgets/WidgetHero.tsx +39 -0
- package/src/ui/widgets/WidgetList.tsx +84 -0
- package/src/ui/widgets/WidgetPills.tsx +68 -0
- package/src/ui/widgets/WidgetStat.tsx +67 -0
- package/src/ui/widgets/WidgetStatus.tsx +62 -0
- package/src/ui/widgets/index.ts +9 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { sql, type SQLQuery } from "bun";
|
|
2
|
+
|
|
3
|
+
type SqlFragment = SQLQuery;
|
|
4
|
+
type SqlValue = SQLQuery | string;
|
|
5
|
+
|
|
6
|
+
export const recursiveUserGroupsSubquery = (params: { userId: SqlValue; select: SqlFragment }) => sql`
|
|
7
|
+
WITH RECURSIVE user_all_groups AS (
|
|
8
|
+
SELECT ug.group_id, g.provider
|
|
9
|
+
FROM auth.user_groups_v2 ug
|
|
10
|
+
JOIN auth.groups g ON g.id = ug.group_id
|
|
11
|
+
WHERE ug.user_id = ${params.userId}::uuid
|
|
12
|
+
UNION
|
|
13
|
+
SELECT gg.parent_group_id, g_parent.provider
|
|
14
|
+
FROM auth.group_groups_v2 gg
|
|
15
|
+
JOIN auth.groups g_parent ON g_parent.id = gg.parent_group_id
|
|
16
|
+
JOIN user_all_groups ag ON gg.child_group_id = ag.group_id
|
|
17
|
+
WHERE g_parent.provider = ag.provider
|
|
18
|
+
)
|
|
19
|
+
${params.select}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
export const managedGroupsNamesSubquery = (userId: SqlValue) =>
|
|
23
|
+
recursiveUserGroupsSubquery({
|
|
24
|
+
userId,
|
|
25
|
+
select: sql`
|
|
26
|
+
SELECT DISTINCT g.name
|
|
27
|
+
FROM auth.groups g
|
|
28
|
+
LEFT JOIN auth.group_manager_users_v2 gmu ON gmu.group_id = g.id AND gmu.user_id = ${userId}::uuid
|
|
29
|
+
LEFT JOIN auth.group_manager_groups_v2 gmg ON gmg.group_id = g.id
|
|
30
|
+
LEFT JOIN user_all_groups ug ON ug.group_id = gmg.manager_group_id AND ug.provider = g.provider
|
|
31
|
+
WHERE gmu.user_id IS NOT NULL OR ug.group_id IS NOT NULL
|
|
32
|
+
ORDER BY g.name
|
|
33
|
+
`,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const managedGroupIdsSubquery = (userId: SqlValue) =>
|
|
37
|
+
recursiveUserGroupsSubquery({
|
|
38
|
+
userId,
|
|
39
|
+
select: sql`
|
|
40
|
+
SELECT managed.id
|
|
41
|
+
FROM (
|
|
42
|
+
SELECT DISTINCT g.id, g.name
|
|
43
|
+
FROM auth.groups g
|
|
44
|
+
LEFT JOIN auth.group_manager_users_v2 gmu ON gmu.group_id = g.id AND gmu.user_id = ${userId}::uuid
|
|
45
|
+
LEFT JOIN auth.group_manager_groups_v2 gmg ON gmg.group_id = g.id
|
|
46
|
+
LEFT JOIN user_all_groups ug ON ug.group_id = gmg.manager_group_id AND ug.provider = g.provider
|
|
47
|
+
WHERE gmu.user_id IS NOT NULL OR ug.group_id IS NOT NULL
|
|
48
|
+
) managed
|
|
49
|
+
ORDER BY managed.name
|
|
50
|
+
`,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const recursiveGroupNamesSubquery = (userId: SqlValue) =>
|
|
54
|
+
recursiveUserGroupsSubquery({
|
|
55
|
+
userId,
|
|
56
|
+
select: sql`
|
|
57
|
+
SELECT DISTINCT g.name
|
|
58
|
+
FROM user_all_groups ag
|
|
59
|
+
JOIN auth.groups g ON g.id = ag.group_id
|
|
60
|
+
ORDER BY g.name
|
|
61
|
+
`,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const recursiveGroupIdsSubquery = (userId: SqlValue) =>
|
|
65
|
+
recursiveUserGroupsSubquery({
|
|
66
|
+
userId,
|
|
67
|
+
select: sql`
|
|
68
|
+
SELECT group_ids.group_id
|
|
69
|
+
FROM (
|
|
70
|
+
SELECT DISTINCT g.id AS group_id, g.name
|
|
71
|
+
FROM user_all_groups ag
|
|
72
|
+
JOIN auth.groups g ON g.id = ag.group_id
|
|
73
|
+
) group_ids
|
|
74
|
+
ORDER BY group_ids.name
|
|
75
|
+
`,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export const buildMemberGroupScopeCondition = (params: { userId: SqlValue; groupProvider: SqlFragment }) => sql`
|
|
79
|
+
g.id IN (
|
|
80
|
+
${recursiveUserGroupsSubquery({
|
|
81
|
+
userId: params.userId,
|
|
82
|
+
select: sql`
|
|
83
|
+
SELECT DISTINCT ug.group_id
|
|
84
|
+
FROM user_all_groups ug
|
|
85
|
+
WHERE ug.provider = ${params.groupProvider}
|
|
86
|
+
`,
|
|
87
|
+
})}
|
|
88
|
+
)
|
|
89
|
+
`;
|
|
90
|
+
|
|
91
|
+
export const buildManagedGroupScopeCondition = (params: { userId: SqlValue; groupProvider: SqlFragment }) => sql`
|
|
92
|
+
g.id IN (
|
|
93
|
+
${recursiveUserGroupsSubquery({
|
|
94
|
+
userId: params.userId,
|
|
95
|
+
select: sql`
|
|
96
|
+
SELECT DISTINCT g_manage.id
|
|
97
|
+
FROM auth.groups g_manage
|
|
98
|
+
LEFT JOIN auth.group_manager_users_v2 gmu ON gmu.group_id = g_manage.id AND gmu.user_id = ${params.userId}::uuid
|
|
99
|
+
LEFT JOIN auth.group_manager_groups_v2 gmg ON gmg.group_id = g_manage.id
|
|
100
|
+
LEFT JOIN user_all_groups ug ON ug.group_id = gmg.manager_group_id AND ug.provider = g_manage.provider
|
|
101
|
+
WHERE g_manage.provider = ${params.groupProvider}
|
|
102
|
+
AND (gmu.user_id IS NOT NULL OR ug.group_id IS NOT NULL)
|
|
103
|
+
`,
|
|
104
|
+
})}
|
|
105
|
+
)
|
|
106
|
+
`;
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import type { BaseGroup, GroupMember, MutationResult, UserProvider } from "../../contracts/shared";
|
|
3
|
+
import * as localGroups from "./local-groups";
|
|
4
|
+
import { providers } from "../providers";
|
|
5
|
+
import { freeipa } from "../../server/services";
|
|
6
|
+
import { toPgUuidArray } from "../postgres";
|
|
7
|
+
import { buildBaseGroup } from "./base-group";
|
|
8
|
+
import {
|
|
9
|
+
buildManagedGroupScopeCondition,
|
|
10
|
+
buildMemberGroupScopeCondition,
|
|
11
|
+
} from "./group-sql";
|
|
12
|
+
|
|
13
|
+
type DbRow = Record<string, unknown>;
|
|
14
|
+
|
|
15
|
+
type GroupListScope = "all" | "member" | "managed";
|
|
16
|
+
|
|
17
|
+
const getGroup = async (id: string): Promise<BaseGroup | null> => {
|
|
18
|
+
const [row] = await sql<DbRow[]>`
|
|
19
|
+
SELECT id, provider, name, description, gid_number
|
|
20
|
+
FROM auth.groups
|
|
21
|
+
WHERE id = ${id}::uuid
|
|
22
|
+
`;
|
|
23
|
+
if (!row) return null;
|
|
24
|
+
return buildBaseGroup(row);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const listCanonical = async (params: {
|
|
28
|
+
ids?: string[];
|
|
29
|
+
userId?: string;
|
|
30
|
+
scope?: GroupListScope;
|
|
31
|
+
search?: string;
|
|
32
|
+
provider?: UserProvider;
|
|
33
|
+
page?: number;
|
|
34
|
+
perPage?: number;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
groups: BaseGroup[];
|
|
37
|
+
total: number;
|
|
38
|
+
pagination: { page: number; perPage: number; totalPages: number; hasNext: boolean };
|
|
39
|
+
}> => {
|
|
40
|
+
const page = params.page ?? 1;
|
|
41
|
+
const perPage = params.perPage ?? 100;
|
|
42
|
+
const offset = (page - 1) * perPage;
|
|
43
|
+
const pattern = params.search ? `%${freeipa.util.escapeLike(params.search.toLowerCase())}%` : null;
|
|
44
|
+
const ids = params.ids ?? [];
|
|
45
|
+
const scope = params.scope ?? (params.userId ? "member" : "all");
|
|
46
|
+
const scopeUserId = params.userId ?? "00000000-0000-0000-0000-000000000000";
|
|
47
|
+
const idsCondition = ids.length === 0 ? sql`TRUE` : sql`g.id = ANY(${toPgUuidArray(ids)}::uuid[])`;
|
|
48
|
+
|
|
49
|
+
if (params.ids && params.ids.length === 0) {
|
|
50
|
+
return {
|
|
51
|
+
groups: [],
|
|
52
|
+
total: 0,
|
|
53
|
+
pagination: {
|
|
54
|
+
page,
|
|
55
|
+
perPage,
|
|
56
|
+
totalPages: 0,
|
|
57
|
+
hasNext: false,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const rows = await sql<DbRow[]>`
|
|
63
|
+
SELECT g.id, g.provider, g.name, g.description, g.gid_number, COUNT(*) OVER() AS total
|
|
64
|
+
FROM auth.groups g
|
|
65
|
+
WHERE (${params.provider ?? null}::text IS NULL OR g.provider = ${params.provider ?? null})
|
|
66
|
+
AND ${idsCondition}
|
|
67
|
+
AND (
|
|
68
|
+
${scope === "all"} = true
|
|
69
|
+
OR ${params.userId ?? null}::uuid IS NULL
|
|
70
|
+
OR (${scope === "member"} = true AND ${buildMemberGroupScopeCondition({ userId: scopeUserId, groupProvider: sql`g.provider` })})
|
|
71
|
+
OR (${scope === "managed"} = true AND ${buildManagedGroupScopeCondition({ userId: scopeUserId, groupProvider: sql`g.provider` })})
|
|
72
|
+
)
|
|
73
|
+
AND (
|
|
74
|
+
${pattern}::text IS NULL
|
|
75
|
+
OR LOWER(g.name) LIKE ${pattern} ESCAPE '\\'
|
|
76
|
+
OR LOWER(COALESCE(g.description, '')) LIKE ${pattern} ESCAPE '\\'
|
|
77
|
+
)
|
|
78
|
+
ORDER BY g.name
|
|
79
|
+
LIMIT ${perPage}
|
|
80
|
+
OFFSET ${offset}
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
const total = rows.length > 0 ? Number((rows[0] as Record<string, unknown>).total) : 0;
|
|
84
|
+
return {
|
|
85
|
+
groups: rows.map(buildBaseGroup),
|
|
86
|
+
total,
|
|
87
|
+
pagination: {
|
|
88
|
+
page,
|
|
89
|
+
perPage,
|
|
90
|
+
totalPages: Math.ceil(total / perPage),
|
|
91
|
+
hasNext: page * perPage < total,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const list = async (params: {
|
|
97
|
+
ids?: string[];
|
|
98
|
+
userId?: string;
|
|
99
|
+
scope?: GroupListScope;
|
|
100
|
+
search?: string;
|
|
101
|
+
provider?: UserProvider;
|
|
102
|
+
page?: number;
|
|
103
|
+
perPage?: number;
|
|
104
|
+
}) => {
|
|
105
|
+
return listCanonical(params);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const get = async (params: { id: string }): Promise<BaseGroup | null> => {
|
|
109
|
+
return getGroup(params.id);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const getMembers = async (params: { id: string; provider?: UserProvider; type?: "user" | "group"; recursive?: boolean }): Promise<GroupMember[]> => {
|
|
113
|
+
const provider = params.provider ?? (await getGroup(params.id))?.provider;
|
|
114
|
+
if (provider === "local") return localGroups.getMembers(params);
|
|
115
|
+
if (!provider) return [];
|
|
116
|
+
return providers.ipa.groups.getMembers(params);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const getManagers = async (params: { id: string; provider?: UserProvider; type?: "user" | "group"; recursive?: boolean }): Promise<GroupMember[]> => {
|
|
120
|
+
const provider = params.provider ?? (await getGroup(params.id))?.provider;
|
|
121
|
+
if (provider === "local") return localGroups.getManagers(params);
|
|
122
|
+
if (!provider) return [];
|
|
123
|
+
return providers.ipa.groups.getManagers(params);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const getParents = async (params: { id: string; provider?: UserProvider; recursive?: boolean }): Promise<string[]> => {
|
|
127
|
+
const provider = params.provider ?? (await getGroup(params.id))?.provider;
|
|
128
|
+
if (provider === "local") return localGroups.getParents(params);
|
|
129
|
+
if (!provider) return [];
|
|
130
|
+
return providers.ipa.groups.getParents(params);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const getManagedGroups = async (params: { id: string; provider?: UserProvider }): Promise<string[]> => {
|
|
134
|
+
const provider = params.provider ?? (await getGroup(params.id))?.provider;
|
|
135
|
+
if (provider === "local") return localGroups.getManagedGroups(params);
|
|
136
|
+
if (!provider) return [];
|
|
137
|
+
return providers.ipa.groups.getManagedGroups(params);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const create = async (params: {
|
|
141
|
+
ipaSession?: string | null;
|
|
142
|
+
provider: UserProvider;
|
|
143
|
+
name: string;
|
|
144
|
+
description?: string;
|
|
145
|
+
posix?: boolean;
|
|
146
|
+
}): Promise<MutationResult<BaseGroup>> => {
|
|
147
|
+
if (params.provider === "local") {
|
|
148
|
+
if (params.posix) return { ok: false, error: "Local groups do not support POSIX mode", status: 400 };
|
|
149
|
+
return localGroups.create({ name: params.name, description: params.description });
|
|
150
|
+
}
|
|
151
|
+
if (!params.ipaSession) return { ok: false, error: "IPA session required to create IPA groups", status: 401 };
|
|
152
|
+
return providers.ipa.groups.add({
|
|
153
|
+
ipaSession: params.ipaSession,
|
|
154
|
+
cn: params.name,
|
|
155
|
+
description: params.description,
|
|
156
|
+
posix: params.posix,
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const update = async (params: {
|
|
161
|
+
ipaSession?: string | null;
|
|
162
|
+
id: string;
|
|
163
|
+
provider?: UserProvider;
|
|
164
|
+
description: string;
|
|
165
|
+
}): Promise<MutationResult<void>> => {
|
|
166
|
+
const provider = params.provider ?? (await getGroup(params.id))?.provider;
|
|
167
|
+
if (provider === "local") return localGroups.update({ id: params.id, description: params.description });
|
|
168
|
+
if (!params.ipaSession) return { ok: false, error: "IPA session required to update IPA groups", status: 401 };
|
|
169
|
+
return providers.ipa.groups.update({
|
|
170
|
+
ipaSession: params.ipaSession,
|
|
171
|
+
id: params.id,
|
|
172
|
+
description: params.description,
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const remove = async (params: { ipaSession?: string | null; id: string; provider?: UserProvider }): Promise<MutationResult<void>> => {
|
|
177
|
+
const provider = params.provider ?? (await getGroup(params.id))?.provider;
|
|
178
|
+
if (provider === "local") return localGroups.remove({ id: params.id });
|
|
179
|
+
if (!params.ipaSession) return { ok: false, error: "IPA session required to delete IPA groups", status: 401 };
|
|
180
|
+
return providers.ipa.groups.remove({
|
|
181
|
+
ipaSession: params.ipaSession,
|
|
182
|
+
id: params.id,
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const makePosix = async (params: {
|
|
187
|
+
ipaSession?: string | null;
|
|
188
|
+
id: string;
|
|
189
|
+
provider?: UserProvider;
|
|
190
|
+
}): Promise<MutationResult<{ gidnumber: number | null }>> => {
|
|
191
|
+
const provider = params.provider ?? (await getGroup(params.id))?.provider;
|
|
192
|
+
if (provider === "local") return { ok: false, error: "Local groups do not support POSIX mode", status: 400 };
|
|
193
|
+
if (!params.ipaSession) return { ok: false, error: "IPA session required to change IPA groups", status: 401 };
|
|
194
|
+
return providers.ipa.groups.makePosix({
|
|
195
|
+
ipaSession: params.ipaSession,
|
|
196
|
+
id: params.id,
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const addMember = async (params: { ipaSession?: string | null; id: string; provider?: UserProvider; user?: string; group?: string }): Promise<MutationResult<void>> => {
|
|
201
|
+
const provider = params.provider ?? (await getGroup(params.id))?.provider;
|
|
202
|
+
if (provider === "local") return localGroups.addMember({ id: params.id, user: params.user, group: params.group });
|
|
203
|
+
if (!params.ipaSession) return { ok: false, error: "IPA session required to update IPA groups", status: 401 };
|
|
204
|
+
return providers.ipa.groups.addMember({
|
|
205
|
+
ipaSession: params.ipaSession,
|
|
206
|
+
id: params.id,
|
|
207
|
+
user: params.user,
|
|
208
|
+
group: params.group,
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const removeMember = async (params: { ipaSession?: string | null; id: string; provider?: UserProvider; user?: string; group?: string }): Promise<MutationResult<void>> => {
|
|
213
|
+
const provider = params.provider ?? (await getGroup(params.id))?.provider;
|
|
214
|
+
if (provider === "local") return localGroups.removeMember({ id: params.id, user: params.user, group: params.group });
|
|
215
|
+
if (!params.ipaSession) return { ok: false, error: "IPA session required to update IPA groups", status: 401 };
|
|
216
|
+
return providers.ipa.groups.removeMember({
|
|
217
|
+
ipaSession: params.ipaSession,
|
|
218
|
+
id: params.id,
|
|
219
|
+
user: params.user,
|
|
220
|
+
group: params.group,
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export const addManager = async (params: { ipaSession?: string | null; id: string; provider?: UserProvider; user?: string; group?: string }): Promise<MutationResult<void>> => {
|
|
225
|
+
const provider = params.provider ?? (await getGroup(params.id))?.provider;
|
|
226
|
+
if (provider === "local") return localGroups.addManager({ id: params.id, user: params.user, group: params.group });
|
|
227
|
+
if (!params.ipaSession) return { ok: false, error: "IPA session required to update IPA groups", status: 401 };
|
|
228
|
+
return providers.ipa.groups.addManager({
|
|
229
|
+
ipaSession: params.ipaSession,
|
|
230
|
+
id: params.id,
|
|
231
|
+
user: params.user,
|
|
232
|
+
group: params.group,
|
|
233
|
+
});
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export const removeManager = async (params: { ipaSession?: string | null; id: string; provider?: UserProvider; user?: string; group?: string }): Promise<MutationResult<void>> => {
|
|
237
|
+
const provider = params.provider ?? (await getGroup(params.id))?.provider;
|
|
238
|
+
if (provider === "local") return localGroups.removeManager({ id: params.id, user: params.user, group: params.group });
|
|
239
|
+
if (!params.ipaSession) return { ok: false, error: "IPA session required to update IPA groups", status: 401 };
|
|
240
|
+
return providers.ipa.groups.removeManager({
|
|
241
|
+
ipaSession: params.ipaSession,
|
|
242
|
+
id: params.id,
|
|
243
|
+
user: params.user,
|
|
244
|
+
group: params.group,
|
|
245
|
+
});
|
|
246
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as model from "./model";
|
|
2
|
+
import * as authz from "./authz";
|
|
3
|
+
import * as users from "./users";
|
|
4
|
+
import * as groups from "./groups";
|
|
5
|
+
import * as entities from "./entities";
|
|
6
|
+
import * as localGroups from "./local-groups";
|
|
7
|
+
import * as switching from "./switching";
|
|
8
|
+
import * as lifecycle from "./lifecycle";
|
|
9
|
+
import { accountsAppService } from "./app";
|
|
10
|
+
|
|
11
|
+
export { model, authz, users, groups, entities, localGroups, switching, lifecycle };
|
|
12
|
+
export { accountsAppService };
|
|
13
|
+
|
|
14
|
+
export const accounts = { model, authz, users, groups, entities, localGroups, switching, lifecycle, app: accountsAppService } as const;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import type { IpaUserData } from "../../contracts/shared";
|
|
3
|
+
|
|
4
|
+
type DbRow = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export const userIpaDataJoin = sql`LEFT JOIN auth.user_ipa_data ui ON ui.user_id = u.id`;
|
|
7
|
+
|
|
8
|
+
export const userIpaDataColumns = sql`
|
|
9
|
+
ui.uid_number AS ipa_uid_number,
|
|
10
|
+
ui.phone AS ipa_phone,
|
|
11
|
+
ui.employee_type AS ipa_employee_type,
|
|
12
|
+
ui.mobile AS ipa_mobile,
|
|
13
|
+
ui.addr_street AS ipa_addr_street,
|
|
14
|
+
ui.addr_postal_code AS ipa_addr_postal_code,
|
|
15
|
+
ui.addr_city AS ipa_addr_city,
|
|
16
|
+
ui.addr_state AS ipa_addr_state,
|
|
17
|
+
ui.ipa_password_expires AS ipa_password_expires,
|
|
18
|
+
ui.last_login_ipa AS ipa_last_login_ipa,
|
|
19
|
+
ui.synced_at AS ipa_synced_at,
|
|
20
|
+
ui.ssh_public_keys AS ipa_ssh_public_keys,
|
|
21
|
+
ui.ssh_fingerprints AS ipa_ssh_fingerprints
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
export const emptyIpaUserData = (): IpaUserData => ({
|
|
25
|
+
uidNumber: null,
|
|
26
|
+
phone: null,
|
|
27
|
+
employeeType: null,
|
|
28
|
+
mobile: null,
|
|
29
|
+
address: {
|
|
30
|
+
street: null,
|
|
31
|
+
postalCode: null,
|
|
32
|
+
city: null,
|
|
33
|
+
state: null,
|
|
34
|
+
},
|
|
35
|
+
passwordExpires: null,
|
|
36
|
+
lastLoginIpa: null,
|
|
37
|
+
syncedAt: null,
|
|
38
|
+
sshPublicKeys: [],
|
|
39
|
+
sshFingerprints: [],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const buildIpaUserData = (row: DbRow): IpaUserData | null => {
|
|
43
|
+
if (!row.ipa_uid_number && !row.ipa_phone && !row.ipa_password_expires && !row.ipa_synced_at && !row.ipa_ssh_public_keys) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
uidNumber: (row.ipa_uid_number as number) ?? null,
|
|
49
|
+
phone: (row.ipa_phone as string) ?? null,
|
|
50
|
+
employeeType: (row.ipa_employee_type as string) ?? null,
|
|
51
|
+
mobile: (row.ipa_mobile as string) ?? null,
|
|
52
|
+
address: {
|
|
53
|
+
street: (row.ipa_addr_street as string) ?? null,
|
|
54
|
+
postalCode: (row.ipa_addr_postal_code as string) ?? null,
|
|
55
|
+
city: (row.ipa_addr_city as string) ?? null,
|
|
56
|
+
state: (row.ipa_addr_state as string) ?? null,
|
|
57
|
+
},
|
|
58
|
+
passwordExpires: row.ipa_password_expires ? (row.ipa_password_expires as Date).toISOString() : null,
|
|
59
|
+
lastLoginIpa: row.ipa_last_login_ipa ? (row.ipa_last_login_ipa as Date).toISOString() : null,
|
|
60
|
+
syncedAt: row.ipa_synced_at ? (row.ipa_synced_at as Date).toISOString() : null,
|
|
61
|
+
sshPublicKeys: (row.ipa_ssh_public_keys as string[]) ?? [],
|
|
62
|
+
sshFingerprints: (row.ipa_ssh_fingerprints as string[]) ?? [],
|
|
63
|
+
};
|
|
64
|
+
};
|