@valentinkolb/cloud 0.4.0 → 0.5.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 +18 -6
- package/scripts/preload.ts +78 -23
- package/src/_internal/define-app.ts +53 -46
- package/src/api/accounts-entities.ts +4 -0
- package/src/api/admin-core-settings.ts +98 -0
- package/src/api/announcements.ts +131 -0
- package/src/api/auth/schemas.ts +24 -0
- package/src/api/auth.ts +113 -10
- package/src/api/index.ts +7 -2
- package/src/api/me.ts +203 -14
- package/src/api/search/schemas.ts +1 -0
- package/src/api/search.ts +62 -8
- package/src/config/ssr.ts +2 -9
- package/src/contracts/announcements.test.ts +37 -0
- package/src/contracts/announcements.ts +121 -0
- package/src/contracts/app.ts +2 -0
- package/src/contracts/index.ts +3 -2
- package/src/contracts/registry.ts +2 -0
- package/src/contracts/shared.ts +108 -1
- package/src/desktop/index.ts +704 -0
- package/src/desktop/solid.tsx +938 -0
- package/src/server/api/index.ts +1 -1
- package/src/server/api/respond.ts +50 -10
- package/src/server/index.ts +44 -38
- package/src/server/middleware/auth.ts +98 -9
- package/src/server/middleware/index.ts +2 -1
- package/src/server/middleware/settings.ts +26 -0
- package/src/server/services/access.test.ts +197 -0
- package/src/server/services/access.ts +254 -6
- package/src/server/services/index.ts +14 -11
- package/src/server/services/pagination.ts +22 -0
- package/src/server/time.ts +45 -0
- package/src/services/account-lifecycle/index.ts +142 -18
- package/src/services/accounts/app.ts +658 -170
- package/src/services/accounts/authz.test.ts +77 -0
- package/src/services/accounts/authz.ts +22 -0
- package/src/services/accounts/entities.ts +84 -5
- package/src/services/accounts/groups.ts +30 -24
- package/src/services/accounts/model.test.ts +30 -0
- package/src/services/accounts/switching.test.ts +14 -0
- package/src/services/accounts/switching.ts +15 -6
- package/src/services/accounts/users.ts +75 -52
- package/src/services/announcements/index.test.ts +32 -0
- package/src/services/announcements/index.ts +224 -0
- package/src/services/audit/index.test.ts +84 -0
- package/src/services/audit/index.ts +431 -0
- package/src/services/auth-flows/index.ts +9 -2
- package/src/services/auth-flows/ipa.ts +0 -2
- package/src/services/auth-flows/magic-link.ts +3 -2
- package/src/services/auth-flows/password-reset.ts +284 -0
- package/src/services/auth-flows/proxy-return.test.ts +24 -0
- package/src/services/auth-flows/proxy-return.ts +49 -0
- package/src/services/gateway.ts +162 -0
- package/src/services/index.ts +44 -2
- package/src/services/ipa/effective-groups.test.ts +33 -0
- package/src/services/ipa/effective-groups.ts +70 -0
- package/src/services/ipa/profile.ts +45 -3
- package/src/services/ipa/search.ts +3 -5
- package/src/services/ipa/service-account.ts +15 -0
- package/src/services/ipa/sync-planning.test.ts +32 -0
- package/src/services/ipa/sync-planning.ts +22 -0
- package/src/services/ipa/sync.ts +110 -38
- package/src/services/oauth-tokens.ts +104 -0
- package/src/services/postgres.ts +21 -6
- package/src/services/providers/local/auth.test.ts +22 -0
- package/src/services/providers/local/auth.ts +46 -3
- package/src/services/secrets.ts +10 -0
- package/src/services/service-account-credentials.test.ts +210 -0
- package/src/services/service-account-credentials.ts +715 -0
- package/src/services/service-accounts.ts +188 -0
- package/src/services/session/index.ts +7 -8
- package/src/services/settings/app.ts +4 -20
- package/src/services/settings/defaults.ts +64 -22
- package/src/services/settings/store.ts +47 -0
- package/src/services/weather/forecast.ts +40 -7
- package/src/services/webauthn.test.ts +36 -0
- package/src/services/webauthn.ts +384 -0
- package/src/shared/icons.ts +391 -100
- package/src/shared/index.ts +7 -0
- package/src/shared/markdown/extensions/code.ts +38 -1
- package/src/shared/markdown/extensions/images.ts +39 -3
- package/src/shared/markdown/extensions/info-blocks.ts +5 -5
- package/src/shared/markdown/extensions/mark.ts +48 -0
- package/src/shared/markdown/extensions/sub-sup.ts +60 -0
- package/src/shared/markdown/extensions/tables.ts +79 -58
- package/src/shared/markdown/formula.test.ts +1089 -0
- package/src/shared/markdown/formula.ts +1187 -0
- package/src/shared/markdown/index.ts +76 -2
- package/src/shared/mock-cover.ts +130 -0
- package/src/shared/redirect.test.ts +49 -0
- package/src/shared/redirect.ts +52 -0
- package/src/shared/theme.test.ts +24 -0
- package/src/shared/theme.ts +68 -0
- package/src/shared/time.ts +13 -0
- package/src/ssr/AdminLayout.tsx +7 -3
- package/src/ssr/AdminSidebar.tsx +115 -49
- package/src/ssr/AppLaunchpad.island.tsx +176 -0
- package/src/ssr/Footer.island.tsx +3 -8
- package/src/ssr/GlobalAnnouncements.island.tsx +141 -0
- package/src/ssr/GlobalSearchDialog.tsx +545 -117
- package/src/ssr/HotkeysHelpRail.island.tsx +3 -70
- package/src/ssr/Layout.tsx +74 -66
- package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
- package/src/ssr/LayoutHelp.tsx +266 -0
- package/src/ssr/NavMenu.island.tsx +0 -39
- package/src/ssr/ThemeToggleRail.island.tsx +3 -3
- package/src/ssr/TimezoneCookie.island.tsx +23 -0
- package/src/ssr/islands/index.ts +13 -0
- package/src/styles/base-popover.css +5 -2
- package/src/styles/effects.css +87 -6
- package/src/styles/global.css +146 -9
- package/src/styles/input.css +3 -1
- package/src/styles/utilities-buttons.css +133 -27
- package/src/styles/utilities-code-display.css +67 -0
- package/src/styles/utilities-completion.css +223 -0
- package/src/styles/utilities-detail.css +73 -0
- package/src/styles/utilities-feedback.css +16 -15
- package/src/styles/utilities-layout.css +42 -2
- package/src/styles/utilities-markdown-editor.css +472 -0
- package/src/styles/utilities-navigation.css +63 -8
- package/src/styles/utilities-script.css +84 -0
- package/src/styles/utilities-table-tile.css +229 -0
- package/src/types/ambient.d.ts +9 -0
- package/src/ui/completion/behaviors.test.ts +95 -0
- package/src/ui/completion/behaviors.ts +205 -0
- package/src/ui/completion/engine.ts +368 -0
- package/src/ui/completion/index.ts +40 -0
- package/src/ui/completion/overlay.ts +92 -0
- package/src/ui/dialog-core.ts +173 -45
- package/src/ui/filter/FilterChip.tsx +42 -40
- package/src/ui/index.ts +11 -12
- package/src/ui/input/AutocompleteEditor.tsx +656 -0
- package/src/ui/input/CheckboxCard.tsx +91 -0
- package/src/ui/input/Combobox.tsx +375 -0
- package/src/ui/input/DatePicker.tsx +846 -0
- package/src/ui/input/DateTimeInput.tsx +29 -4
- package/src/ui/input/FileDropzone.tsx +116 -0
- package/src/ui/input/IconInput.tsx +116 -0
- package/src/ui/input/ImageInput.tsx +19 -2
- package/src/ui/input/MultiSelectInput.tsx +448 -0
- package/src/ui/input/NumberInput.tsx +417 -61
- package/src/ui/input/SegmentedControl.tsx +2 -2
- package/src/ui/input/Select.tsx +172 -10
- package/src/ui/input/Slider.tsx +3 -4
- package/src/ui/input/Switch.tsx +3 -2
- package/src/ui/input/TemplateEditor.tsx +212 -0
- package/src/ui/input/TextInput.tsx +144 -13
- package/src/ui/input/index.ts +53 -8
- package/src/ui/input/markdown/MarkdownEditor.tsx +774 -0
- package/src/ui/input/markdown/Toolbar.tsx +90 -0
- package/src/ui/input/markdown/actions.ts +233 -0
- package/src/ui/input/markdown/active-formats.ts +94 -0
- package/src/ui/input/markdown/behaviors.ts +193 -0
- package/src/ui/input/markdown/code-zone.ts +23 -0
- package/src/ui/input/markdown/highlight.ts +316 -0
- package/src/ui/layout.ts +22 -0
- package/src/ui/misc/AppOverview.tsx +105 -0
- package/src/ui/misc/AppWorkspace.tsx +607 -0
- package/src/ui/misc/Calendar.tsx +1291 -0
- package/src/ui/misc/Chart.tsx +162 -0
- package/src/ui/misc/CodeDisplay.tsx +54 -0
- package/src/ui/misc/ContextMenu.tsx +2 -2
- package/src/ui/misc/DataTable.tsx +269 -0
- package/src/ui/misc/DockWorkspace.tsx +425 -0
- package/src/ui/misc/Docs.tsx +153 -0
- package/src/ui/misc/Dropdown.tsx +2 -2
- package/src/ui/misc/EntitySearch.tsx +260 -129
- package/src/ui/misc/LinkCard.tsx +14 -2
- package/src/ui/misc/LogEntriesTable.tsx +34 -31
- package/src/ui/misc/Pagination.tsx +31 -12
- package/src/ui/misc/PanelDialog.tsx +109 -0
- package/src/ui/misc/Panes.tsx +873 -0
- package/src/ui/misc/PermissionEditor.tsx +358 -262
- package/src/ui/misc/Placeholder.tsx +40 -0
- package/src/ui/misc/ProgressBar.tsx +1 -1
- package/src/ui/misc/ResourceApiKeys.tsx +260 -0
- package/src/ui/misc/SettingsModal.tsx +150 -0
- package/src/ui/misc/StatCell.tsx +182 -40
- package/src/ui/misc/StatGrid.tsx +149 -0
- package/src/ui/misc/StructuredDataPreview.tsx +107 -0
- package/src/ui/misc/code-highlight.ts +213 -0
- package/src/ui/misc/index.ts +93 -12
- package/src/ui/prompts.tsx +362 -312
- package/src/ui/toast.ts +384 -0
- package/src/ui/widgets/Widget.tsx +12 -4
- package/src/ssr/MoreAppsDropdown.island.tsx +0 -61
- package/src/ui/ipa/GroupView.tsx +0 -36
- package/src/ui/ipa/LoginBtn.tsx +0 -16
- package/src/ui/ipa/UserView.tsx +0 -58
- package/src/ui/ipa/index.ts +0 -4
- package/src/ui/navigation.ts +0 -32
- package/src/ui/sidebar.tsx +0 -468
- /package/src/ui/{ipa → misc}/Avatar.tsx +0 -0
package/src/config/ssr.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { createSSRHandler } from "@valentinkolb/ssr/hono";
|
|
|
7
7
|
import { env } from "./env";
|
|
8
8
|
import { dirname, resolve } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { themeBootstrapScript } from "../shared/theme";
|
|
10
11
|
|
|
11
12
|
/** Cache-busting version stamp — changes on every server start / rebuild. */
|
|
12
13
|
const v = Date.now();
|
|
@@ -37,15 +38,7 @@ export const { config, plugin, html } = createConfig<PageOptions>({
|
|
|
37
38
|
<link rel="icon" href="/branding/favicon">
|
|
38
39
|
<link rel="stylesheet" href="/public/build.css?v=${v}">
|
|
39
40
|
<link rel="stylesheet" href="/public/katex.css?v=${v}">
|
|
40
|
-
<script>
|
|
41
|
-
(function() {
|
|
42
|
-
var el = document.documentElement;
|
|
43
|
-
if (!el.hasAttribute('data-theme-fixed')) {
|
|
44
|
-
var theme = document.cookie.match(/theme=([^;]+)/)?.[1] || 'light';
|
|
45
|
-
el.classList.add(theme);
|
|
46
|
-
}
|
|
47
|
-
})();
|
|
48
|
-
</script>
|
|
41
|
+
<script>${themeBootstrapScript}</script>
|
|
49
42
|
</head>
|
|
50
43
|
<body>
|
|
51
44
|
${body}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
ANNOUNCEMENTS_COOKIE,
|
|
4
|
+
mergeAnnouncementCookieState,
|
|
5
|
+
parseAnnouncementCookieHeader,
|
|
6
|
+
parseAnnouncementCookieValue,
|
|
7
|
+
serializeAnnouncementCookieState,
|
|
8
|
+
} from "./announcements";
|
|
9
|
+
|
|
10
|
+
describe("announcement cookie state", () => {
|
|
11
|
+
test("returns defaults for empty or malformed cookies", () => {
|
|
12
|
+
expect(parseAnnouncementCookieValue(null)).toEqual({ seenAnnouncementVersion: 0, dismissedBannerVersions: [] });
|
|
13
|
+
expect(parseAnnouncementCookieValue("%7Bbad")).toEqual({ seenAnnouncementVersion: 0, dismissedBannerVersions: [] });
|
|
14
|
+
expect(parseAnnouncementCookieHeader("theme=dark")).toEqual({ seenAnnouncementVersion: 0, dismissedBannerVersions: [] });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("parses state from a cookie header", () => {
|
|
18
|
+
const value = serializeAnnouncementCookieState({
|
|
19
|
+
seenAnnouncementVersion: 12,
|
|
20
|
+
dismissedBannerVersions: [3, 2, 3],
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(parseAnnouncementCookieHeader(`theme=dark; ${ANNOUNCEMENTS_COOKIE}=${value}; other=1`)).toEqual({
|
|
24
|
+
seenAnnouncementVersion: 12,
|
|
25
|
+
dismissedBannerVersions: [3, 2],
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("merges state monotonically", () => {
|
|
30
|
+
expect(
|
|
31
|
+
mergeAnnouncementCookieState(
|
|
32
|
+
{ seenAnnouncementVersion: 10, dismissedBannerVersions: [4] },
|
|
33
|
+
{ seenAnnouncementVersion: 8, dismissedBannerVersions: [5, 4] },
|
|
34
|
+
),
|
|
35
|
+
).toEqual({ seenAnnouncementVersion: 10, dismissedBannerVersions: [5, 4] });
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const ANNOUNCEMENTS_COOKIE = "cloud_announcements";
|
|
4
|
+
export const ANNOUNCEMENTS_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
|
|
5
|
+
export const MAX_DISMISSED_BANNER_VERSIONS = 50;
|
|
6
|
+
|
|
7
|
+
export const AnnouncementKindSchema = z.enum(["announcement", "banner"]);
|
|
8
|
+
export type AnnouncementKind = z.infer<typeof AnnouncementKindSchema>;
|
|
9
|
+
|
|
10
|
+
export const AnnouncementToneSchema = z.enum(["info", "success", "warning", "danger"]);
|
|
11
|
+
export type AnnouncementTone = z.infer<typeof AnnouncementToneSchema>;
|
|
12
|
+
|
|
13
|
+
export const AnnouncementCookieStateSchema = z.object({
|
|
14
|
+
seenAnnouncementVersion: z.number().int().nonnegative().default(0),
|
|
15
|
+
dismissedBannerVersions: z.array(z.number().int().positive()).default([]),
|
|
16
|
+
});
|
|
17
|
+
export type AnnouncementCookieState = z.infer<typeof AnnouncementCookieStateSchema>;
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_ANNOUNCEMENT_COOKIE_STATE: AnnouncementCookieState = {
|
|
20
|
+
seenAnnouncementVersion: 0,
|
|
21
|
+
dismissedBannerVersions: [],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const AnnouncementEntrySchema = z.object({
|
|
25
|
+
id: z.uuid(),
|
|
26
|
+
version: z.number().int().positive(),
|
|
27
|
+
kind: AnnouncementKindSchema,
|
|
28
|
+
title: z.string(),
|
|
29
|
+
body: z.string(),
|
|
30
|
+
tone: AnnouncementToneSchema,
|
|
31
|
+
publishedAt: z.string().datetime(),
|
|
32
|
+
expiresAt: z.string().datetime().nullable(),
|
|
33
|
+
createdAt: z.string().datetime(),
|
|
34
|
+
updatedAt: z.string().datetime(),
|
|
35
|
+
createdBy: z.uuid().nullable(),
|
|
36
|
+
updatedBy: z.uuid().nullable(),
|
|
37
|
+
});
|
|
38
|
+
export type AnnouncementEntry = z.infer<typeof AnnouncementEntrySchema>;
|
|
39
|
+
|
|
40
|
+
export const AnnouncementDisplayEntrySchema = AnnouncementEntrySchema.omit({ body: true }).extend({
|
|
41
|
+
bodyHtml: z.string(),
|
|
42
|
+
});
|
|
43
|
+
export type AnnouncementDisplayEntry = z.infer<typeof AnnouncementDisplayEntrySchema>;
|
|
44
|
+
|
|
45
|
+
const DatetimeInputSchema = z.string().datetime();
|
|
46
|
+
const NullableDatetimeInputSchema = z.string().datetime().nullable();
|
|
47
|
+
|
|
48
|
+
export const CreateAnnouncementSchema = z.object({
|
|
49
|
+
kind: AnnouncementKindSchema,
|
|
50
|
+
title: z.string().trim().min(1).max(180),
|
|
51
|
+
body: z.string().trim().min(1).max(20_000),
|
|
52
|
+
tone: AnnouncementToneSchema.default("info"),
|
|
53
|
+
publishedAt: DatetimeInputSchema.optional(),
|
|
54
|
+
expiresAt: NullableDatetimeInputSchema.optional(),
|
|
55
|
+
});
|
|
56
|
+
export type CreateAnnouncement = z.infer<typeof CreateAnnouncementSchema>;
|
|
57
|
+
|
|
58
|
+
export const UpdateAnnouncementSchema = z
|
|
59
|
+
.object({
|
|
60
|
+
kind: AnnouncementKindSchema.optional(),
|
|
61
|
+
title: z.string().trim().min(1).max(180).optional(),
|
|
62
|
+
body: z.string().trim().min(1).max(20_000).optional(),
|
|
63
|
+
tone: AnnouncementToneSchema.optional(),
|
|
64
|
+
publishedAt: DatetimeInputSchema.optional(),
|
|
65
|
+
expiresAt: NullableDatetimeInputSchema.optional(),
|
|
66
|
+
})
|
|
67
|
+
.refine((value) => Object.keys(value).length > 0, "Provide at least one field to update.");
|
|
68
|
+
export type UpdateAnnouncement = z.infer<typeof UpdateAnnouncementSchema>;
|
|
69
|
+
|
|
70
|
+
export const AnnouncementListResponseSchema = z.object({
|
|
71
|
+
items: z.array(AnnouncementEntrySchema),
|
|
72
|
+
});
|
|
73
|
+
export type AnnouncementListResponse = z.infer<typeof AnnouncementListResponseSchema>;
|
|
74
|
+
|
|
75
|
+
export const ActiveAnnouncementsResponseSchema = z.object({
|
|
76
|
+
banners: z.array(AnnouncementDisplayEntrySchema),
|
|
77
|
+
announcements: z.array(AnnouncementDisplayEntrySchema),
|
|
78
|
+
latestAnnouncementVersion: z.number().int().nonnegative(),
|
|
79
|
+
});
|
|
80
|
+
export type ActiveAnnouncementsResponse = z.infer<typeof ActiveAnnouncementsResponseSchema>;
|
|
81
|
+
|
|
82
|
+
const normalizeCookieState = (value: unknown): AnnouncementCookieState => {
|
|
83
|
+
const parsed = AnnouncementCookieStateSchema.safeParse(value);
|
|
84
|
+
if (!parsed.success) return DEFAULT_ANNOUNCEMENT_COOKIE_STATE;
|
|
85
|
+
const dismissed = [...new Set(parsed.data.dismissedBannerVersions)]
|
|
86
|
+
.filter((version) => Number.isInteger(version) && version > 0)
|
|
87
|
+
.sort((a, b) => b - a)
|
|
88
|
+
.slice(0, MAX_DISMISSED_BANNER_VERSIONS);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
seenAnnouncementVersion: Math.max(0, parsed.data.seenAnnouncementVersion),
|
|
92
|
+
dismissedBannerVersions: dismissed,
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const parseAnnouncementCookieValue = (value: string | null | undefined): AnnouncementCookieState => {
|
|
97
|
+
if (!value) return DEFAULT_ANNOUNCEMENT_COOKIE_STATE;
|
|
98
|
+
try {
|
|
99
|
+
return normalizeCookieState(JSON.parse(decodeURIComponent(value)));
|
|
100
|
+
} catch {
|
|
101
|
+
return DEFAULT_ANNOUNCEMENT_COOKIE_STATE;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const parseAnnouncementCookieHeader = (cookieHeader: string | null | undefined): AnnouncementCookieState => {
|
|
106
|
+
if (!cookieHeader) return DEFAULT_ANNOUNCEMENT_COOKIE_STATE;
|
|
107
|
+
const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${ANNOUNCEMENTS_COOKIE}=([^;]+)`));
|
|
108
|
+
return parseAnnouncementCookieValue(match?.[1]);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const serializeAnnouncementCookieState = (state: AnnouncementCookieState): string =>
|
|
112
|
+
encodeURIComponent(JSON.stringify(normalizeCookieState(state)));
|
|
113
|
+
|
|
114
|
+
export const mergeAnnouncementCookieState = (
|
|
115
|
+
current: AnnouncementCookieState,
|
|
116
|
+
patch: Partial<AnnouncementCookieState>,
|
|
117
|
+
): AnnouncementCookieState =>
|
|
118
|
+
normalizeCookieState({
|
|
119
|
+
seenAnnouncementVersion: Math.max(current.seenAnnouncementVersion, patch.seenAnnouncementVersion ?? 0),
|
|
120
|
+
dismissedBannerVersions: [...current.dismissedBannerVersions, ...(patch.dismissedBannerVersions ?? [])],
|
|
121
|
+
});
|
package/src/contracts/app.ts
CHANGED
|
@@ -43,6 +43,8 @@ export type AppMeta = {
|
|
|
43
43
|
* silently skip rendering for the current user.
|
|
44
44
|
*/
|
|
45
45
|
widgets?: WidgetEndpoint[];
|
|
46
|
+
/** Setting keys declared by this app. Used by admin tooling to protect active app-owned settings. */
|
|
47
|
+
settingKeys?: readonly string[];
|
|
46
48
|
/** Gateway-relative URL where this app's OpenAPI JSON is served, or undefined. */
|
|
47
49
|
openapi?: string;
|
|
48
50
|
};
|
package/src/contracts/index.ts
CHANGED
|
@@ -47,6 +47,8 @@ export type AppRegistryEntry = {
|
|
|
47
47
|
search?: AppRegistrySearch;
|
|
48
48
|
legalLinks?: AppRegistryLegalLink[];
|
|
49
49
|
widgets?: AppRegistryWidget[];
|
|
50
|
+
/** Setting keys declared by this app. Used by admin tooling to avoid treating live app-owned settings as legacy. */
|
|
51
|
+
settingKeys?: readonly string[];
|
|
50
52
|
/** Gateway-relative URL where this app serves its OpenAPI JSON spec. */
|
|
51
53
|
openapi?: string;
|
|
52
54
|
};
|
package/src/contracts/shared.ts
CHANGED
|
@@ -93,7 +93,7 @@ export const BaseGroupSchema = z.object({
|
|
|
93
93
|
});
|
|
94
94
|
export type BaseGroup = z.infer<typeof BaseGroupSchema>;
|
|
95
95
|
|
|
96
|
-
export const EntityKindSchema = z.enum(["user", "group"]);
|
|
96
|
+
export const EntityKindSchema = z.enum(["user", "group", "service_account"]);
|
|
97
97
|
export type EntityKind = z.infer<typeof EntityKindSchema>;
|
|
98
98
|
|
|
99
99
|
export const EntityRelationSchema = z.object({
|
|
@@ -112,6 +112,22 @@ export const EntityListItemSchema = z.discriminatedUnion("kind", [
|
|
|
112
112
|
group: BaseGroupSchema,
|
|
113
113
|
relation: EntityRelationSchema.optional(),
|
|
114
114
|
}),
|
|
115
|
+
z.object({
|
|
116
|
+
kind: z.literal("service_account"),
|
|
117
|
+
serviceAccount: z.object({
|
|
118
|
+
id: z.uuid(),
|
|
119
|
+
name: z.string(),
|
|
120
|
+
kind: z.enum(["user_delegated", "resource_bound"]),
|
|
121
|
+
status: z.enum(["active", "disabled"]),
|
|
122
|
+
delegatedUserId: z.uuid().nullable(),
|
|
123
|
+
appId: z.string().nullable(),
|
|
124
|
+
resourceType: z.string().nullable(),
|
|
125
|
+
resourceId: z.string().nullable(),
|
|
126
|
+
createdBy: z.uuid().nullable(),
|
|
127
|
+
createdAt: z.string(),
|
|
128
|
+
}),
|
|
129
|
+
relation: EntityRelationSchema.optional(),
|
|
130
|
+
}),
|
|
115
131
|
]);
|
|
116
132
|
export type EntityListItem = z.infer<typeof EntityListItemSchema>;
|
|
117
133
|
|
|
@@ -181,9 +197,100 @@ export type MutationResult<T = void> = { ok: true; data: T } | { ok: false; erro
|
|
|
181
197
|
export const PermissionLevelSchema = z.enum(["none", "read", "write", "admin"]);
|
|
182
198
|
export type PermissionLevel = z.infer<typeof PermissionLevelSchema>;
|
|
183
199
|
|
|
200
|
+
export const ServiceAccountKindSchema = z.enum(["user_delegated", "resource_bound"]);
|
|
201
|
+
export type ServiceAccountKind = z.infer<typeof ServiceAccountKindSchema>;
|
|
202
|
+
|
|
203
|
+
export const ServiceAccountStatusSchema = z.enum(["active", "disabled"]);
|
|
204
|
+
export type ServiceAccountStatus = z.infer<typeof ServiceAccountStatusSchema>;
|
|
205
|
+
|
|
206
|
+
export const ServiceAccountSchema = z.object({
|
|
207
|
+
id: z.uuid(),
|
|
208
|
+
name: z.string(),
|
|
209
|
+
kind: ServiceAccountKindSchema,
|
|
210
|
+
status: ServiceAccountStatusSchema,
|
|
211
|
+
delegatedUserId: z.uuid().nullable(),
|
|
212
|
+
appId: z.string().nullable(),
|
|
213
|
+
resourceType: z.string().nullable(),
|
|
214
|
+
resourceId: z.string().nullable(),
|
|
215
|
+
createdBy: z.uuid().nullable(),
|
|
216
|
+
createdAt: z.string(),
|
|
217
|
+
});
|
|
218
|
+
export type ServiceAccount = z.infer<typeof ServiceAccountSchema>;
|
|
219
|
+
|
|
220
|
+
export const ServiceAccountCredentialStatusSchema = z.enum(["active", "revoked"]);
|
|
221
|
+
export type ServiceAccountCredentialStatus = z.infer<typeof ServiceAccountCredentialStatusSchema>;
|
|
222
|
+
|
|
223
|
+
export const ServiceAccountCredentialSchema = z.object({
|
|
224
|
+
id: z.uuid(),
|
|
225
|
+
serviceAccountId: z.uuid(),
|
|
226
|
+
name: z.string(),
|
|
227
|
+
kind: z.literal("api_token"),
|
|
228
|
+
status: ServiceAccountCredentialStatusSchema,
|
|
229
|
+
tokenPrefix: z.string(),
|
|
230
|
+
scopes: z.array(z.string()),
|
|
231
|
+
expiresAt: z.string().nullable(),
|
|
232
|
+
lastUsedAt: z.string().nullable(),
|
|
233
|
+
createdBy: z.uuid().nullable(),
|
|
234
|
+
createdAt: z.string(),
|
|
235
|
+
revokedAt: z.string().nullable(),
|
|
236
|
+
revokedBy: z.uuid().nullable(),
|
|
237
|
+
});
|
|
238
|
+
export type ServiceAccountCredential = z.infer<typeof ServiceAccountCredentialSchema>;
|
|
239
|
+
|
|
240
|
+
export const CreateUserApiKeySchema = z.object({
|
|
241
|
+
name: z.string().trim().min(1).max(120),
|
|
242
|
+
expiresAt: z.string().datetime().nullable().optional(),
|
|
243
|
+
});
|
|
244
|
+
export type CreateUserApiKey = z.infer<typeof CreateUserApiKeySchema>;
|
|
245
|
+
|
|
246
|
+
export const CreateUserApiKeyResponseSchema = z.object({
|
|
247
|
+
credential: ServiceAccountCredentialSchema,
|
|
248
|
+
token: z.string(),
|
|
249
|
+
});
|
|
250
|
+
export type CreateUserApiKeyResponse = z.infer<typeof CreateUserApiKeyResponseSchema>;
|
|
251
|
+
|
|
252
|
+
export const WebAuthnPasskeySchema = z.object({
|
|
253
|
+
id: z.uuid(),
|
|
254
|
+
userId: z.uuid(),
|
|
255
|
+
name: z.string(),
|
|
256
|
+
transports: z.array(z.string()),
|
|
257
|
+
deviceType: z.string().nullable(),
|
|
258
|
+
backedUp: z.boolean(),
|
|
259
|
+
createdAt: z.string(),
|
|
260
|
+
lastUsedAt: z.string().nullable(),
|
|
261
|
+
});
|
|
262
|
+
export type WebAuthnPasskey = z.infer<typeof WebAuthnPasskeySchema>;
|
|
263
|
+
|
|
264
|
+
export const CreateWebAuthnPasskeySchema = z.object({
|
|
265
|
+
name: z.string().trim().min(1).max(120),
|
|
266
|
+
response: z.unknown(),
|
|
267
|
+
});
|
|
268
|
+
export type CreateWebAuthnPasskey = z.infer<typeof CreateWebAuthnPasskeySchema>;
|
|
269
|
+
|
|
270
|
+
export const ListWebAuthnPasskeysResponseSchema = z.object({
|
|
271
|
+
items: z.array(WebAuthnPasskeySchema),
|
|
272
|
+
});
|
|
273
|
+
export type ListWebAuthnPasskeysResponse = z.infer<typeof ListWebAuthnPasskeysResponseSchema>;
|
|
274
|
+
|
|
275
|
+
export const AccountActivitySchema = z.object({
|
|
276
|
+
id: z.number(),
|
|
277
|
+
createdAt: z.string(),
|
|
278
|
+
action: z.string(),
|
|
279
|
+
label: z.string(),
|
|
280
|
+
outcome: z.enum(["allowed", "denied", "failed"]),
|
|
281
|
+
context: z.string().nullable(),
|
|
282
|
+
});
|
|
283
|
+
export type AccountActivity = z.infer<typeof AccountActivitySchema>;
|
|
284
|
+
|
|
285
|
+
export const AccountActivityListResponseSchema = z.object({
|
|
286
|
+
items: z.array(AccountActivitySchema),
|
|
287
|
+
});
|
|
288
|
+
export type AccountActivityListResponse = z.infer<typeof AccountActivityListResponseSchema>;
|
|
289
|
+
|
|
184
290
|
export const PrincipalSchema = z.discriminatedUnion("type", [
|
|
185
291
|
z.object({ type: z.literal("user"), userId: z.uuid() }),
|
|
186
292
|
z.object({ type: z.literal("group"), groupId: z.uuid() }),
|
|
293
|
+
z.object({ type: z.literal("service_account"), serviceAccountId: z.uuid() }),
|
|
187
294
|
z.object({ type: z.literal("authenticated") }),
|
|
188
295
|
z.object({ type: z.literal("public") }),
|
|
189
296
|
]);
|