@valentinkolb/cloud 0.3.1 → 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 -8
- package/scripts/preload.ts +78 -23
- package/src/_internal/define-app.ts +119 -47
- package/src/_internal/runtime-context.ts +1 -0
- 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 +15 -25
- 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 +4 -0
- package/src/contracts/index.ts +3 -2
- package/src/contracts/registry.ts +4 -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
|
@@ -3,14 +3,18 @@ import type { User } from "../../contracts/shared";
|
|
|
3
3
|
import { logger } from "../logging";
|
|
4
4
|
import { notifications } from "../notifications";
|
|
5
5
|
import { applyIpaAccountTransitionPolicy } from "../accounts/switching";
|
|
6
|
+
import { audit } from "../audit";
|
|
6
7
|
import { get as getSetting } from "../settings";
|
|
7
8
|
import { renderTemplate } from "../settings/templates";
|
|
8
9
|
import { session } from "../session";
|
|
9
10
|
import { getConfiguredExpiryDays, parseIpaAccountTransitionPolicy } from "../account-model";
|
|
10
11
|
import { getFreeIpaConfig } from "../freeipa-config";
|
|
12
|
+
import { getServiceIpaSession } from "../ipa/service-account";
|
|
13
|
+
import { buildEffectiveIpaGroupsByUid } from "../ipa/effective-groups";
|
|
14
|
+
import { providers } from "../providers";
|
|
11
15
|
import { parsePgJsonRecord } from "../postgres";
|
|
12
16
|
import { dates } from "../../shared";
|
|
13
|
-
import { freeipa } from "../../server/services";
|
|
17
|
+
import { err, fail, freeipa, ok, type Result } from "../../server/services";
|
|
14
18
|
import { writeDeletedAccountAudit } from "./audit";
|
|
15
19
|
import { getIpaUrl } from "../ipa/guard";
|
|
16
20
|
|
|
@@ -55,6 +59,99 @@ const getGuestExpiresDays = async (): Promise<number> => {
|
|
|
55
59
|
const getDeletedAccountsRetentionDays = async (): Promise<number> => settingInt("user.account.deleted_accounts_retention_days", 365);
|
|
56
60
|
const getReminderHistoryRetentionDays = async (): Promise<number> => settingInt("user.account.reminder_history_retention_days", 365);
|
|
57
61
|
|
|
62
|
+
const readIpaList = (config: { response: Awaited<ReturnType<typeof freeipa.client.call>>; entity: string }): Result<Record<string, unknown>[]> => {
|
|
63
|
+
if (config.response.error) {
|
|
64
|
+
return fail(err.internal(`Could not verify FreeIPA ${config.entity} before extension.`));
|
|
65
|
+
}
|
|
66
|
+
const records = config.response.result?.result;
|
|
67
|
+
if (!Array.isArray(records)) {
|
|
68
|
+
return fail(err.internal(`Could not verify FreeIPA ${config.entity} before extension.`));
|
|
69
|
+
}
|
|
70
|
+
return ok(records as Record<string, unknown>[]);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const verifyCurrentIpaSyncScope = async (uid: string): Promise<Result<void>> => {
|
|
74
|
+
const config = await getFreeIpaConfig();
|
|
75
|
+
const serviceSession = await getServiceIpaSession();
|
|
76
|
+
if (!serviceSession.ok) {
|
|
77
|
+
return fail({
|
|
78
|
+
code: serviceSession.status === 400 ? "BAD_INPUT" : "INTERNAL",
|
|
79
|
+
message: serviceSession.error,
|
|
80
|
+
status: serviceSession.status,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const groupsRes = await freeipa.client.call({
|
|
85
|
+
url: config.url,
|
|
86
|
+
ipaSession: serviceSession.data,
|
|
87
|
+
method: "group_find",
|
|
88
|
+
args: [],
|
|
89
|
+
options: {
|
|
90
|
+
sizelimit: 0,
|
|
91
|
+
no_members: false,
|
|
92
|
+
all: true,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
const groups = readIpaList({ response: groupsRes, entity: "groups" });
|
|
96
|
+
if (!groups.ok) return fail(groups.error);
|
|
97
|
+
|
|
98
|
+
const effectiveGroupsByUid = buildEffectiveIpaGroupsByUid(
|
|
99
|
+
groups.data.map((raw) => ({
|
|
100
|
+
cn: freeipa.util.str(raw.cn),
|
|
101
|
+
users: (raw.member_user as string[]) ?? [],
|
|
102
|
+
groups: (raw.member_group as string[]) ?? [],
|
|
103
|
+
})),
|
|
104
|
+
);
|
|
105
|
+
const effectiveGroups = effectiveGroupsByUid.get(uid) ?? [];
|
|
106
|
+
if (!config.groupsBaseSync.some((group) => effectiveGroups.includes(group))) {
|
|
107
|
+
return fail(err.forbidden("Your FreeIPA account is no longer in sync scope and cannot be extended."));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return ok();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const readFreshLocalIpaAccountExpiry = async (uid: string): Promise<Result<Date | null>> => {
|
|
114
|
+
const rows = await sql<DbRow[]>`
|
|
115
|
+
SELECT account_expires
|
|
116
|
+
FROM auth.users
|
|
117
|
+
WHERE uid = ${uid} AND provider = 'ipa'
|
|
118
|
+
`;
|
|
119
|
+
const row = rows[0];
|
|
120
|
+
if (!row) return fail(err.notFound("Your FreeIPA account was not found locally."));
|
|
121
|
+
return ok((row.account_expires as Date | null | undefined) ?? null);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const verifyIpaExtensionFreshness = async (uid: string): Promise<Result<{ accountExpires: Date }>> => {
|
|
125
|
+
const currentScope = await verifyCurrentIpaSyncScope(uid);
|
|
126
|
+
if (!currentScope.ok) return fail(currentScope.error);
|
|
127
|
+
|
|
128
|
+
const freshness = await providers.ipa.sync.user(uid).catch((error) => ({
|
|
129
|
+
status: "fetch_failed" as const,
|
|
130
|
+
error: error instanceof Error ? error.message : String(error),
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
switch (freshness.status) {
|
|
134
|
+
case "synced": {
|
|
135
|
+
const accountExpires = await readFreshLocalIpaAccountExpiry(uid);
|
|
136
|
+
if (!accountExpires.ok) return accountExpires;
|
|
137
|
+
if (accountExpires.data === null) {
|
|
138
|
+
return fail(err.badInput("Accounts without an expiration date cannot be extended."));
|
|
139
|
+
}
|
|
140
|
+
return ok({ accountExpires: accountExpires.data });
|
|
141
|
+
}
|
|
142
|
+
case "expired":
|
|
143
|
+
return fail(err.badInput("Your FreeIPA account is expired and cannot be extended."));
|
|
144
|
+
case "out_of_scope":
|
|
145
|
+
return fail(err.forbidden("Your FreeIPA account is no longer in sync scope and cannot be extended."));
|
|
146
|
+
case "not_found_local":
|
|
147
|
+
return fail(err.notFound("Your FreeIPA account was not found locally."));
|
|
148
|
+
case "fetch_failed":
|
|
149
|
+
return fail(err.internal("Could not verify your FreeIPA account before extension."));
|
|
150
|
+
case "skipped_disabled":
|
|
151
|
+
return fail(err.badInput("FreeIPA is disabled."));
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
58
155
|
const parseReminderDays = async (): Promise<number[]> => {
|
|
59
156
|
const raw = await getSetting<number[]>("user.account.reminder_days");
|
|
60
157
|
const parsed = Array.isArray(raw) ? raw.filter((entry) => Number.isInteger(entry) && entry > 0) : [];
|
|
@@ -675,20 +772,47 @@ export const accountLifecycle = {
|
|
|
675
772
|
|
|
676
773
|
extendCurrentUserAccount: async (config: {
|
|
677
774
|
user: User;
|
|
678
|
-
|
|
679
|
-
|
|
775
|
+
}): Promise<Result<{ message: string; newExpiry?: string }>> => {
|
|
776
|
+
const auditParams = (result: Result<{ message: string; newExpiry?: string }>) => ({
|
|
777
|
+
action: "accounts.user.extend_account",
|
|
778
|
+
actor: {
|
|
779
|
+
userId: config.user.id,
|
|
780
|
+
uid: config.user.uid,
|
|
781
|
+
provider: config.user.provider,
|
|
782
|
+
roles: config.user.roles,
|
|
783
|
+
},
|
|
784
|
+
target: { type: "user", id: config.user.id, label: config.user.uid, provider: config.user.provider },
|
|
785
|
+
result,
|
|
786
|
+
});
|
|
787
|
+
const recordResult = (result: Result<{ message: string; newExpiry?: string }>) =>
|
|
788
|
+
audit.recordResult(auditParams(result));
|
|
789
|
+
const recordCompletedMutation = (result: Result<{ message: string; newExpiry?: string }>) =>
|
|
790
|
+
result.ok ? audit.recordResultAfterSideEffect(auditParams(result)) : audit.recordResult(auditParams(result));
|
|
791
|
+
|
|
792
|
+
if (config.user.accountExpires === null) {
|
|
793
|
+
return recordResult(fail(err.badInput("Accounts without an expiration date cannot be extended.")));
|
|
794
|
+
}
|
|
795
|
+
|
|
680
796
|
if (config.user.provider === "ipa") {
|
|
681
797
|
const freeIpaConfig = (await getFreeIpaConfig());
|
|
682
798
|
if (!freeIpaConfig.enabled) {
|
|
683
|
-
return { message: "FreeIPA is disabled." };
|
|
799
|
+
return recordResult(ok({ message: "FreeIPA is disabled." }));
|
|
684
800
|
}
|
|
685
801
|
const configuredDays = await getIpaExpiresDays();
|
|
686
802
|
if (configuredDays <= 0) {
|
|
687
|
-
return { message: "Automatic account expiry is disabled for IPA accounts." };
|
|
803
|
+
return recordResult(ok({ message: "Automatic account expiry is disabled for IPA accounts." }));
|
|
688
804
|
}
|
|
689
805
|
|
|
690
|
-
|
|
691
|
-
|
|
806
|
+
const freshness = await verifyIpaExtensionFreshness(config.user.uid);
|
|
807
|
+
if (!freshness.ok) return recordResult(fail(freshness.error));
|
|
808
|
+
|
|
809
|
+
const serviceSession = await getServiceIpaSession();
|
|
810
|
+
if (!serviceSession.ok) {
|
|
811
|
+
return recordResult(fail({
|
|
812
|
+
code: serviceSession.status === 400 ? "BAD_INPUT" : "INTERNAL",
|
|
813
|
+
message: serviceSession.error,
|
|
814
|
+
status: serviceSession.status,
|
|
815
|
+
}));
|
|
692
816
|
}
|
|
693
817
|
|
|
694
818
|
const expiresAt = new Date(Date.now() + configuredDays * DAY_MS);
|
|
@@ -697,13 +821,13 @@ export const accountLifecycle = {
|
|
|
697
821
|
|
|
698
822
|
const response = await freeipa.client.call({
|
|
699
823
|
url: freeIpaConfig.url,
|
|
700
|
-
ipaSession:
|
|
824
|
+
ipaSession: serviceSession.data,
|
|
701
825
|
method: "user_mod",
|
|
702
826
|
args: [config.user.uid],
|
|
703
827
|
options: { krbprincipalexpiration: ipaExpiry },
|
|
704
828
|
});
|
|
705
829
|
if (response.error) {
|
|
706
|
-
|
|
830
|
+
return recordResult(fail(err.badInput(response.error.message || "Failed to extend IPA account.")));
|
|
707
831
|
}
|
|
708
832
|
|
|
709
833
|
await sql`
|
|
@@ -717,10 +841,10 @@ export const accountLifecycle = {
|
|
|
717
841
|
ON CONFLICT (user_id) DO UPDATE SET synced_at = EXCLUDED.synced_at
|
|
718
842
|
`;
|
|
719
843
|
|
|
720
|
-
return {
|
|
844
|
+
return recordCompletedMutation(ok({
|
|
721
845
|
message: `Account extended until ${dates.formatDate(expiresAt)}.`,
|
|
722
846
|
newExpiry: expiresAt.toISOString(),
|
|
723
|
-
};
|
|
847
|
+
}));
|
|
724
848
|
}
|
|
725
849
|
|
|
726
850
|
if (config.user.provider === "local" && config.user.profile === "guest") {
|
|
@@ -731,7 +855,7 @@ export const accountLifecycle = {
|
|
|
731
855
|
SET account_expires = NULL
|
|
732
856
|
WHERE id = ${config.user.id}::uuid
|
|
733
857
|
`;
|
|
734
|
-
return { message: "Guest account expiry is disabled." };
|
|
858
|
+
return recordCompletedMutation(ok({ message: "Guest account expiry is disabled." }));
|
|
735
859
|
}
|
|
736
860
|
|
|
737
861
|
const expiresAt = new Date(Date.now() + guestDays * DAY_MS);
|
|
@@ -741,10 +865,10 @@ export const accountLifecycle = {
|
|
|
741
865
|
WHERE id = ${config.user.id}::uuid
|
|
742
866
|
`;
|
|
743
867
|
|
|
744
|
-
return {
|
|
868
|
+
return recordCompletedMutation(ok({
|
|
745
869
|
message: `Guest account extended until ${dates.formatDate(expiresAt)}.`,
|
|
746
870
|
newExpiry: expiresAt.toISOString(),
|
|
747
|
-
};
|
|
871
|
+
}));
|
|
748
872
|
}
|
|
749
873
|
|
|
750
874
|
if (config.user.provider === "local" && config.user.profile === "user") {
|
|
@@ -755,7 +879,7 @@ export const accountLifecycle = {
|
|
|
755
879
|
SET account_expires = NULL
|
|
756
880
|
WHERE id = ${config.user.id}::uuid
|
|
757
881
|
`;
|
|
758
|
-
return { message: "Local user account expiry is disabled." };
|
|
882
|
+
return recordCompletedMutation(ok({ message: "Local user account expiry is disabled." }));
|
|
759
883
|
}
|
|
760
884
|
|
|
761
885
|
const expiresAt = new Date(Date.now() + localUserDays * DAY_MS);
|
|
@@ -765,13 +889,13 @@ export const accountLifecycle = {
|
|
|
765
889
|
WHERE id = ${config.user.id}::uuid
|
|
766
890
|
`;
|
|
767
891
|
|
|
768
|
-
return {
|
|
892
|
+
return recordCompletedMutation(ok({
|
|
769
893
|
message: `Account extended until ${dates.formatDate(expiresAt)}.`,
|
|
770
894
|
newExpiry: expiresAt.toISOString(),
|
|
771
|
-
};
|
|
895
|
+
}));
|
|
772
896
|
}
|
|
773
897
|
|
|
774
|
-
return { message: "Your account does not support extension." };
|
|
898
|
+
return recordResult(ok({ message: "Your account does not support extension." }));
|
|
775
899
|
},
|
|
776
900
|
|
|
777
901
|
listDeletedAccounts: async (config: { page: number; perPage: number; reason?: string; search?: string }) => {
|