@burdenoff/microfe-auth 2026.624.2 → 2026.625.1
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/dist/auth/hooks/index.js +1 -0
- package/dist/auth/hooks/useActiveContextBootstrapMirror.js +27 -0
- package/dist/auth/hooks/useActiveContextBootstrapMirror.js.map +1 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/pages/AuditLogPage.js +73 -72
- package/dist/auth/pages/AuditLogPage.js.map +1 -1
- package/dist/auth/pages/EmailVerifyPage.js +58 -53
- package/dist/auth/pages/EmailVerifyPage.js.map +1 -1
- package/dist/auth/pages/LoginPage.js +222 -214
- package/dist/auth/pages/LoginPage.js.map +1 -1
- package/dist/auth/pages/MagicLinkPage.js +75 -68
- package/dist/auth/pages/MagicLinkPage.js.map +1 -1
- package/dist/auth/pages/MfaSetupPage.js +176 -138
- package/dist/auth/pages/MfaSetupPage.js.map +1 -1
- package/dist/auth/pages/MfaVerifyPage.js +60 -52
- package/dist/auth/pages/MfaVerifyPage.js.map +1 -1
- package/dist/auth/pages/PasswordResetPage.js +277 -262
- package/dist/auth/pages/PasswordResetPage.js.map +1 -1
- package/dist/auth/pages/ReactivatePage.js +54 -46
- package/dist/auth/pages/ReactivatePage.js.map +1 -1
- package/dist/auth/pages/RegisterPage.js +223 -213
- package/dist/auth/pages/RegisterPage.js.map +1 -1
- package/dist/auth/pages/SecurityPage.js +298 -267
- package/dist/auth/pages/SecurityPage.js.map +1 -1
- package/dist/auth/pages/SessionsPage.js +169 -160
- package/dist/auth/pages/SessionsPage.js.map +1 -1
- package/dist/auth/pages/SsoCallbackPage.js +53 -50
- package/dist/auth/pages/SsoCallbackPage.js.map +1 -1
- package/dist/auth/pages/SsoConfigPage.js +466 -444
- package/dist/auth/pages/SsoConfigPage.js.map +1 -1
- package/dist/auth/pages/SsoLoginPage.js +67 -59
- package/dist/auth/pages/SsoLoginPage.js.map +1 -1
- package/dist/auth/pages/UserDetailsPage.js +154 -144
- package/dist/auth/pages/UserDetailsPage.js.map +1 -1
- package/dist/auth/pages/UserProfilePage.js +211 -170
- package/dist/auth/pages/UserProfilePage.js.map +1 -1
- package/dist/auth/pages/UsersPage.js +572 -576
- package/dist/auth/pages/UsersPage.js.map +1 -1
- package/dist/index.js +7 -6
- package/dist/microfe-auth.css +1 -1
- package/package.json +2 -2
package/dist/auth/hooks/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import "./useApiKeyOperations.js";
|
|
|
9
9
|
import "./useAppOperations.js";
|
|
10
10
|
import "./useWorkspaceOperations.js";
|
|
11
11
|
import "./useContextSwitcherData.js";
|
|
12
|
+
import "./useActiveContextBootstrapMirror.js";
|
|
12
13
|
import "./useDelegationOperations.js";
|
|
13
14
|
import "./useUserOperations.js";
|
|
14
15
|
import "./useSsoOperations.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useEffect as e } from "react";
|
|
2
|
+
//#region src/auth/hooks/useActiveContextBootstrapMirror.ts
|
|
3
|
+
function t({ storeOrganizationId: t, storeWorkspaceId: n, storeProjectId: r, activeOrganizationId: i, activeWorkspaceId: a, activeProjectId: o, storeWorkspaces: s, storeProjects: c, setOrganization: l, setWorkspace: u, setProject: d }) {
|
|
4
|
+
e(() => {
|
|
5
|
+
t && !i && l(t);
|
|
6
|
+
let e = i || t, f = !!n && !!e && !!s?.some((t) => t.id === n && t.organizationId === e);
|
|
7
|
+
n && !a && f && u(n);
|
|
8
|
+
let p = a || (f ? n : null), m = !!r && !!p && !!c?.some((e) => e.id === r && e.workspaceId === p);
|
|
9
|
+
r && !o && m && d(r);
|
|
10
|
+
}, [
|
|
11
|
+
t,
|
|
12
|
+
n,
|
|
13
|
+
r,
|
|
14
|
+
i,
|
|
15
|
+
a,
|
|
16
|
+
o,
|
|
17
|
+
s,
|
|
18
|
+
c,
|
|
19
|
+
l,
|
|
20
|
+
u,
|
|
21
|
+
d
|
|
22
|
+
]);
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { t as useActiveContextBootstrapMirror };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=useActiveContextBootstrapMirror.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useActiveContextBootstrapMirror.js","names":[],"sources":["../../../src/auth/hooks/useActiveContextBootstrapMirror.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\ntype ContextId = string | null | undefined;\n\ninterface MirrorWorkspace {\n id: string;\n organizationId?: string | null;\n}\n\ninterface MirrorProject {\n id: string;\n workspaceId?: string | null;\n}\n\nexport interface ActiveContextBootstrapMirrorArgs {\n /** Auth-store `currentOrganizationId` */\n storeOrganizationId: ContextId;\n /** Auth-store `currentWorkspaceId` */\n storeWorkspaceId: ContextId;\n /** Auth-store `currentProjectId` */\n storeProjectId: ContextId;\n /** ActiveContext `organizationId` */\n activeOrganizationId: ContextId;\n /** ActiveContext `workspaceId` */\n activeWorkspaceId: ContextId;\n /** ActiveContext `projectId` */\n activeProjectId: ContextId;\n /**\n * The auth store's loaded `workspaces`. Used to verify that\n * `storeWorkspaceId` actually belongs to the organization being mirrored\n * before it is filled. If omitted, the workspace axis is never mirrored\n * (safe default — better no workspace than one from the wrong org).\n */\n storeWorkspaces?: ReadonlyArray<MirrorWorkspace>;\n /**\n * The auth store's loaded `projects`. Used to verify that `storeProjectId`\n * actually belongs to the workspace being mirrored before it is filled. If\n * omitted, the project axis is never mirrored.\n */\n storeProjects?: ReadonlyArray<MirrorProject>;\n /**\n * ActiveContext setters. Must be **stable references** (they are, coming\n * from `ActiveContextProvider`'s memoized value) — passing inline arrows\n * makes this effect re-run every render. Likewise prefer stable references\n * for `storeWorkspaces` / `storeProjects` (e.g. a shallow-equal Zustand\n * selector or `useMemo`); a new array reference each render re-runs the\n * effect. Re-runs are harmless (the fills are idempotent), just wasted work.\n */\n setOrganization: (organizationId: string | null) => void;\n setWorkspace: (workspaceId: string | null) => void;\n setProject: (projectId: string | null) => void;\n}\n\n/**\n * One-way, **fill-only** mirror of the auth store's org / workspace / project\n * ids into ActiveContext, for app-shell headers / context switchers.\n *\n * Some context ids are derived programmatically in the Zustand auth store\n * without a paired ActiveContext write:\n * - the bootstrap in `useContextSwitcherData` auto-selects an organization\n * (`setCurrentOrganizationId`) and, on the persisted-workspace path, a\n * workspace (`setCurrentWorkspaceId`) without calling `setActiveContext`;\n * - `useProjectSyncEffect` / `selectWorkspace` auto-select a project.\n *\n * ActiveContext is the source of truth for the URL + storage + the props passed\n * to workspace MFEs and the workspace-token context, so those store-only ids\n * must reach it — otherwise e.g. a fresh `defaultWorkspaceId` login leaves\n * `organizationId` undefined and the token / MFE context break until the user\n * reselects.\n *\n * The rule is **fill, never override**: copy a store id into ActiveContext only\n * when ActiveContext has no value for that axis yet. We must NOT overwrite a\n * non-null ActiveContext value, because:\n * 1. it would clobber an explicit selection — a switch handler's write, or a\n * deep-link / browser-back `?workspace=`/`?project=` that the router already\n * applied; and\n * 2. overriding is exactly what turns this into a two-store ping-pong — the\n * store→AC and AC→store effects each read the other's pre-commit value and\n * swap the ids every commit until React throws \"Maximum update depth\n * exceeded\" (error #185). Filling only nulls is self-terminating: once the\n * axis is set the guard is false forever, so it can never alternate.\n *\n * Cross-context safety: a value is filled only when it provably belongs to the\n * parent context being mirrored, validated against the loaded store metadata —\n * the **workspace** must belong to the target organization (`storeWorkspaces`),\n * and the **project** must belong to the target workspace (`storeProjects`).\n * The store can transiently hold a stale child id after its parent advanced\n * (e.g. `useContextSwitcherData` copies a new org into the store before the new\n * org's — unpersisted — workspace list loads, leaving `currentWorkspaceId`\n * pointing at the previous org; or the workspace advances to B while the\n * project still points at A). Gating on loaded metadata means such a stale id\n * isn't in the list yet, so it is not mirrored until it is genuinely valid.\n *\n * See BOFF-2805.\n */\nexport function useActiveContextBootstrapMirror({\n storeOrganizationId,\n storeWorkspaceId,\n storeProjectId,\n activeOrganizationId,\n activeWorkspaceId,\n activeProjectId,\n storeWorkspaces,\n storeProjects,\n setOrganization,\n setWorkspace,\n setProject,\n}: ActiveContextBootstrapMirrorArgs): void {\n useEffect(() => {\n if (storeOrganizationId && !activeOrganizationId) {\n setOrganization(storeOrganizationId);\n }\n\n // Fill the workspace only when it provably belongs to the organization\n // ActiveContext is on (or is being filled this pass). Guards against\n // publishing a (org, workspace) pair from two different orgs while the\n // store's org has advanced but its (unpersisted) workspace list / current\n // workspace id has not caught up.\n const targetOrganizationId = activeOrganizationId || storeOrganizationId;\n const workspaceBelongsToTargetOrganization =\n !!storeWorkspaceId &&\n !!targetOrganizationId &&\n !!storeWorkspaces?.some(\n (workspace) =>\n workspace.id === storeWorkspaceId &&\n workspace.organizationId === targetOrganizationId,\n );\n\n if (\n storeWorkspaceId &&\n !activeWorkspaceId &&\n workspaceBelongsToTargetOrganization\n ) {\n setWorkspace(storeWorkspaceId);\n }\n\n // Fill the project only when it provably belongs to the workspace\n // ActiveContext is on (or is being filled this pass). Prevents a stale\n // project from a previously-selected workspace leaking across a switch.\n // Fall back to the store workspace only when it is being filled this pass;\n // otherwise (e.g. workspace fill skipped because its metadata hasn't\n // loaded) we'd let a project fill without its parent workspace present,\n // an inconsistent (org, null, project) state.\n const targetWorkspaceId =\n activeWorkspaceId ||\n (workspaceBelongsToTargetOrganization ? storeWorkspaceId : null);\n const projectBelongsToTargetWorkspace =\n !!storeProjectId &&\n !!targetWorkspaceId &&\n !!storeProjects?.some(\n (project) =>\n project.id === storeProjectId &&\n project.workspaceId === targetWorkspaceId,\n );\n\n if (storeProjectId && !activeProjectId && projectBelongsToTargetWorkspace) {\n setProject(storeProjectId);\n }\n }, [\n storeOrganizationId,\n storeWorkspaceId,\n storeProjectId,\n activeOrganizationId,\n activeWorkspaceId,\n activeProjectId,\n storeWorkspaces,\n storeProjects,\n setOrganization,\n setWorkspace,\n setProject,\n ]);\n}\n"],"mappings":";;AA+FA,SAAgB,EAAgC,EAC9C,wBACA,qBACA,mBACA,yBACA,sBACA,oBACA,oBACA,kBACA,oBACA,iBACA,iBACyC;AACzC,SAAgB;AACd,EAAI,KAAuB,CAAC,KAC1B,EAAgB,EAAoB;EAQtC,IAAM,IAAuB,KAAwB,GAC/C,IACJ,CAAC,CAAC,KACF,CAAC,CAAC,KACF,CAAC,CAAC,GAAiB,MAChB,MACC,EAAU,OAAO,KACjB,EAAU,mBAAmB,EAChC;AAEH,EACE,KACA,CAAC,KACD,KAEA,EAAa,EAAiB;EAUhC,IAAM,IACJ,MACC,IAAuC,IAAmB,OACvD,IACJ,CAAC,CAAC,KACF,CAAC,CAAC,KACF,CAAC,CAAC,GAAe,MACd,MACC,EAAQ,OAAO,KACf,EAAQ,gBAAgB,EAC3B;AAEH,EAAI,KAAkB,CAAC,KAAmB,KACxC,EAAW,EAAe;IAE3B;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC"}
|
package/dist/auth/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import "./hooks/usePasswordOperations.js";
|
|
|
10
10
|
import "./hooks/useSessionOperations.js";
|
|
11
11
|
import "./hooks/useWorkspaceOperations.js";
|
|
12
12
|
import "./hooks/useContextSwitcherData.js";
|
|
13
|
+
import "./hooks/useActiveContextBootstrapMirror.js";
|
|
13
14
|
import "./hooks/useUserOperations.js";
|
|
14
15
|
import "./hooks/index.js";
|
|
15
16
|
import "./hooks/useWorkspaceToken.js";
|
|
@@ -6,24 +6,25 @@ import { useState as a } from "react";
|
|
|
6
6
|
import { useI18n as o } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
7
7
|
import { Fragment as s, jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
8
8
|
import { useFeatureFlagDetails as u } from "@burdenoff/fe-libs/shared/feature-flags";
|
|
9
|
-
import {
|
|
9
|
+
import { EmphasisPanel as d, IllustratedEmptyState as f, PagePurpose as p } from "@burdenoff/fe-libs/ui";
|
|
10
|
+
import { useGatewayClient as m } from "@burdenoff/fe-libs/shared/graphql";
|
|
10
11
|
//#region src/auth/pages/AuditLogPage.tsx
|
|
11
|
-
var
|
|
12
|
-
function
|
|
13
|
-
let { t:
|
|
14
|
-
let n =
|
|
12
|
+
var h = 20;
|
|
13
|
+
function g() {
|
|
14
|
+
let { t: g } = o(), _ = (e, t) => {
|
|
15
|
+
let n = g(e);
|
|
15
16
|
return n === e ? t : n;
|
|
16
|
-
}, [
|
|
17
|
-
skip: !
|
|
17
|
+
}, [v, y] = a(1), b = (v - 1) * h, x = u("auth.audit-log")?.value === !0, { data: S, loading: C, error: w } = e({
|
|
18
|
+
skip: !x,
|
|
18
19
|
fetchPolicy: "cache-and-network",
|
|
19
20
|
variables: { pagination: {
|
|
20
|
-
limit:
|
|
21
|
-
offset:
|
|
21
|
+
limit: h,
|
|
22
|
+
offset: b
|
|
22
23
|
} }
|
|
23
|
-
}),
|
|
24
|
-
|
|
24
|
+
}), T = S?.authAuditLogs ?? [], E = T.length === h, [D, { loading: O }] = i({ client: m("workspace") }), [k, A] = a(null), j = async () => {
|
|
25
|
+
A(null);
|
|
25
26
|
try {
|
|
26
|
-
await
|
|
27
|
+
await D({ variables: { input: {
|
|
27
28
|
name: "Audit Log Export",
|
|
28
29
|
entityTypes: ["permission_audit_logs"],
|
|
29
30
|
format: "CSV",
|
|
@@ -31,9 +32,9 @@ function p() {
|
|
|
31
32
|
includeMetadata: !0
|
|
32
33
|
} } });
|
|
33
34
|
} catch (e) {
|
|
34
|
-
|
|
35
|
+
A(e instanceof Error ? e.message : "Failed to start export");
|
|
35
36
|
}
|
|
36
|
-
},
|
|
37
|
+
}, M = (e) => {
|
|
37
38
|
switch (e) {
|
|
38
39
|
case "LOGIN_SUCCESS": return "OK";
|
|
39
40
|
case "LOGIN_FAILURE": return "ER";
|
|
@@ -45,24 +46,24 @@ function p() {
|
|
|
45
46
|
default: return "AU";
|
|
46
47
|
}
|
|
47
48
|
};
|
|
48
|
-
return
|
|
49
|
+
return x ? /* @__PURE__ */ l("div", {
|
|
49
50
|
className: "max-w-4xl mx-auto p-6",
|
|
50
51
|
children: [
|
|
51
52
|
/* @__PURE__ */ l("div", {
|
|
52
53
|
className: "flex items-center justify-between mb-8",
|
|
53
54
|
children: [/* @__PURE__ */ c("h1", {
|
|
54
55
|
className: "text-3xl font-semibold text-text-primary",
|
|
55
|
-
children:
|
|
56
|
+
children: _("auth.auditLog.title", "Security Audit Log")
|
|
56
57
|
}), /* @__PURE__ */ c("button", {
|
|
57
58
|
type: "button",
|
|
58
|
-
onClick: () => void
|
|
59
|
-
disabled:
|
|
60
|
-
"aria-label":
|
|
59
|
+
onClick: () => void j(),
|
|
60
|
+
disabled: O,
|
|
61
|
+
"aria-label": _("auth.auditLog.exportAriaLabel", "Export audit log"),
|
|
61
62
|
className: "flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium",
|
|
62
|
-
children:
|
|
63
|
+
children: O ? /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c(n, {
|
|
63
64
|
className: "size-4 animate-spin",
|
|
64
65
|
"aria-hidden": "true"
|
|
65
|
-
}),
|
|
66
|
+
}), _("auth.auditLog.exporting", "Exporting...")] }) : /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c("svg", {
|
|
66
67
|
className: "size-4",
|
|
67
68
|
fill: "none",
|
|
68
69
|
viewBox: "0 0 24 24",
|
|
@@ -74,10 +75,24 @@ function p() {
|
|
|
74
75
|
strokeWidth: 2,
|
|
75
76
|
d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
|
76
77
|
})
|
|
77
|
-
}),
|
|
78
|
+
}), _("auth.auditLog.export", "Export")] })
|
|
78
79
|
})]
|
|
79
80
|
}),
|
|
80
|
-
|
|
81
|
+
/* @__PURE__ */ c(p, {
|
|
82
|
+
className: "mb-6",
|
|
83
|
+
children: _("auth.auditLog.purpose", "A running record of security-relevant events on your account — sign-ins, password and MFA changes, and anomalies. Scan it to spot anything you didn't do, and export it for your own records or compliance.")
|
|
84
|
+
}),
|
|
85
|
+
/* @__PURE__ */ l(d, {
|
|
86
|
+
className: "mb-6",
|
|
87
|
+
children: [/* @__PURE__ */ c("p", {
|
|
88
|
+
className: "text-sm font-medium text-text-primary",
|
|
89
|
+
children: _("auth.auditLog.emphasisTitle", "What this log captures")
|
|
90
|
+
}), /* @__PURE__ */ c("p", {
|
|
91
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
92
|
+
children: _("auth.auditLog.emphasisBody", "Successful and failed sign-ins, logouts, password and two-factor changes, and detected anomalies — newest first.")
|
|
93
|
+
})]
|
|
94
|
+
}),
|
|
95
|
+
k && /* @__PURE__ */ l("div", {
|
|
81
96
|
role: "alert",
|
|
82
97
|
className: "mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border",
|
|
83
98
|
children: [/* @__PURE__ */ c(t, {
|
|
@@ -85,10 +100,10 @@ function p() {
|
|
|
85
100
|
"aria-hidden": "true"
|
|
86
101
|
}), /* @__PURE__ */ c("p", {
|
|
87
102
|
className: "text-sm text-status-error-text",
|
|
88
|
-
children:
|
|
103
|
+
children: k
|
|
89
104
|
})]
|
|
90
105
|
}),
|
|
91
|
-
|
|
106
|
+
C ? /* @__PURE__ */ l("div", {
|
|
92
107
|
"aria-live": "polite",
|
|
93
108
|
"aria-busy": "true",
|
|
94
109
|
className: "text-center py-12",
|
|
@@ -97,26 +112,27 @@ function p() {
|
|
|
97
112
|
"aria-hidden": "true"
|
|
98
113
|
}), /* @__PURE__ */ c("p", {
|
|
99
114
|
className: "mt-3 text-text-secondary",
|
|
100
|
-
children:
|
|
115
|
+
children: _("auth.auditLog.loading", "Loading audit events...")
|
|
101
116
|
})]
|
|
102
|
-
}) :
|
|
117
|
+
}) : w ? /* @__PURE__ */ c("div", {
|
|
103
118
|
role: "alert",
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
119
|
+
children: /* @__PURE__ */ c(f, {
|
|
120
|
+
illustration: "empty-generic",
|
|
121
|
+
title: _("auth.auditLog.error", "Unable to load audit events"),
|
|
122
|
+
description: _("auth.auditLog.errorDesc", "Something went wrong fetching your security events. Please try again in a moment.")
|
|
108
123
|
})
|
|
109
|
-
}) :
|
|
110
|
-
className: "block
|
|
111
|
-
children: /* @__PURE__ */ c(
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
}) : T.length === 0 ? /* @__PURE__ */ c("output", {
|
|
125
|
+
className: "block",
|
|
126
|
+
children: /* @__PURE__ */ c(f, {
|
|
127
|
+
illustration: "empty-notifications",
|
|
128
|
+
title: _("auth.auditLog.noEvents", "No security events recorded"),
|
|
129
|
+
description: _("auth.auditLog.noEventsDesc", "As you sign in and change security settings, those events will appear here.")
|
|
114
130
|
})
|
|
115
131
|
}) : /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c("div", {
|
|
116
132
|
className: "space-y-4",
|
|
117
133
|
"aria-live": "polite",
|
|
118
|
-
"aria-busy":
|
|
119
|
-
children:
|
|
134
|
+
"aria-busy": C,
|
|
135
|
+
children: T.map((e) => /* @__PURE__ */ c("div", {
|
|
120
136
|
className: "p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm",
|
|
121
137
|
children: /* @__PURE__ */ l("div", {
|
|
122
138
|
className: "flex items-start gap-4",
|
|
@@ -124,7 +140,7 @@ function p() {
|
|
|
124
140
|
/* @__PURE__ */ c("div", {
|
|
125
141
|
className: "flex size-10 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold text-text-secondary",
|
|
126
142
|
"aria-hidden": "true",
|
|
127
|
-
children:
|
|
143
|
+
children: M(e.action)
|
|
128
144
|
}),
|
|
129
145
|
/* @__PURE__ */ l("div", {
|
|
130
146
|
className: "flex-1",
|
|
@@ -142,72 +158,57 @@ function p() {
|
|
|
142
158
|
}),
|
|
143
159
|
/* @__PURE__ */ l("p", {
|
|
144
160
|
className: "text-sm text-text-secondary",
|
|
145
|
-
children: [e.description ??
|
|
161
|
+
children: [e.description ?? _("auth.auditLog.defaultDescription", "Authentication event"), e.ipAddress && ` • ${e.ipAddress}`]
|
|
146
162
|
})
|
|
147
163
|
]
|
|
148
164
|
}),
|
|
149
165
|
e.action.includes("FAILURE") && /* @__PURE__ */ c("output", {
|
|
150
166
|
className: "px-3 py-1 rounded-full bg-status-error-bg-subtle text-status-error-text text-sm font-medium",
|
|
151
|
-
children:
|
|
167
|
+
children: _("auth.auditLog.failed", "Failed")
|
|
152
168
|
})
|
|
153
169
|
]
|
|
154
170
|
})
|
|
155
171
|
}, e.id))
|
|
156
172
|
}), /* @__PURE__ */ l("nav", {
|
|
157
|
-
"aria-label":
|
|
173
|
+
"aria-label": _("auth.auditLog.paginationAriaLabel", "Audit log pagination"),
|
|
158
174
|
className: "mt-6 flex items-center justify-between",
|
|
159
175
|
children: [/* @__PURE__ */ l("p", {
|
|
160
176
|
className: "text-sm text-text-secondary",
|
|
161
177
|
children: [
|
|
162
|
-
|
|
178
|
+
_("auth.auditLog.page", "Page"),
|
|
163
179
|
" ",
|
|
164
|
-
|
|
180
|
+
v
|
|
165
181
|
]
|
|
166
182
|
}), /* @__PURE__ */ l("div", {
|
|
167
183
|
className: "flex gap-2",
|
|
168
184
|
children: [/* @__PURE__ */ c("button", {
|
|
169
185
|
type: "button",
|
|
170
|
-
onClick: () =>
|
|
171
|
-
disabled:
|
|
172
|
-
"aria-label":
|
|
186
|
+
onClick: () => y((e) => Math.max(1, e - 1)),
|
|
187
|
+
disabled: v === 1,
|
|
188
|
+
"aria-label": _("auth.auditLog.previousPageAriaLabel", "Go to previous page"),
|
|
173
189
|
className: "px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors",
|
|
174
|
-
children:
|
|
190
|
+
children: _("auth.auditLog.previous", "Previous")
|
|
175
191
|
}), /* @__PURE__ */ c("button", {
|
|
176
192
|
type: "button",
|
|
177
|
-
onClick: () =>
|
|
178
|
-
disabled: !
|
|
179
|
-
"aria-label":
|
|
193
|
+
onClick: () => y((e) => e + 1),
|
|
194
|
+
disabled: !E,
|
|
195
|
+
"aria-label": _("auth.auditLog.nextPageAriaLabel", "Go to next page"),
|
|
180
196
|
className: "px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors",
|
|
181
|
-
children:
|
|
197
|
+
children: _("auth.auditLog.next", "Next")
|
|
182
198
|
})]
|
|
183
199
|
})]
|
|
184
200
|
})] })
|
|
185
201
|
]
|
|
186
202
|
}) : /* @__PURE__ */ c("div", {
|
|
187
203
|
className: "max-w-4xl mx-auto p-6",
|
|
188
|
-
children: /* @__PURE__ */
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
className: "size-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center",
|
|
193
|
-
children: /* @__PURE__ */ c(t, {
|
|
194
|
-
className: "size-8 text-status-warning-text",
|
|
195
|
-
"aria-hidden": "true"
|
|
196
|
-
})
|
|
197
|
-
}),
|
|
198
|
-
/* @__PURE__ */ c("h2", {
|
|
199
|
-
className: "text-xl font-semibold text-text-primary mb-2",
|
|
200
|
-
children: m("auth.auditLog.notAvailable", "Page Not Available")
|
|
201
|
-
}),
|
|
202
|
-
/* @__PURE__ */ c("p", {
|
|
203
|
-
className: "text-text-secondary",
|
|
204
|
-
children: m("auth.auditLog.notEnabled", "The Security Audit Log is not enabled for your account.")
|
|
205
|
-
})
|
|
206
|
-
]
|
|
204
|
+
children: /* @__PURE__ */ c(f, {
|
|
205
|
+
illustration: "empty-generic",
|
|
206
|
+
title: _("auth.auditLog.notAvailable", "Page Not Available"),
|
|
207
|
+
description: _("auth.auditLog.notEnabled", "The Security Audit Log is not enabled for your account.")
|
|
207
208
|
})
|
|
208
209
|
});
|
|
209
210
|
}
|
|
210
211
|
//#endregion
|
|
211
|
-
export {
|
|
212
|
+
export { g as AuditLogPage };
|
|
212
213
|
|
|
213
214
|
//# sourceMappingURL=AuditLogPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuditLogPage.js","names":[],"sources":["../../../src/auth/pages/AuditLogPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\nimport { useFeatureFlagDetails } from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { formatDateTime } from \"../utils/formatters\";\nimport { AlertIcon, SpinnerIcon } from \"../components/AuthIcons\";\nimport {\n useAuthAuditLogsQuery,\n type AuditLogEntry_AuthAuditLogFragment,\n} from \"../../generated/global-operations\";\nimport { useCreateAuthExportJobMutation } from \"../../generated/wspace-operations\";\n\nconst PAGE_SIZE = 20;\n\n/**\n * Security audit log page with pagination and export\n *\n * Fragment: AuditLogEntry_AuthAuditLog (graphql/operations/global/fragments/AuditLogEntry.fragment.graphql)\n * Query: AuthAuditLogs (graphql/operations/global/queries/auditLog.graphql)\n *\n * TODO: When authAuditLogs backend migrates to AuthAuditLogConnection type,\n * update this query to use: authAuditLogs { auditLogs: [...], totalCount, hasMore }\n * and remove the client-side hasMore approximation below.\n */\nexport function AuditLogPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const [page, setPage] = useState(1);\n const offset = (page - 1) * PAGE_SIZE;\n\n // Default-deny: only show when the flag is explicitly enabled in the system.\n // useFeatureFlag uses default-allow (returns true when not found), so we use\n // useFeatureFlagDetails + explicit value check instead.\n const auditLogFlag = useFeatureFlagDetails(\"auth.audit-log\");\n const showAuditLog = auditLogFlag?.value === true;\n\n const { data, loading, error } = useAuthAuditLogsQuery({\n skip: !showAuditLog,\n fetchPolicy: \"cache-and-network\",\n variables: { pagination: { limit: PAGE_SIZE, offset } },\n });\n\n const auditLogs = data?.authAuditLogs ?? [];\n // Approximate hasMore: if we received a full page there may be more\n const hasMore = auditLogs.length === PAGE_SIZE;\n\n const wspaceClient = useGatewayClient(\"workspace\");\n const [createExportJob, { loading: isExporting }] =\n useCreateAuthExportJobMutation({ client: wspaceClient });\n const [exportError, setExportError] = useState<string | null>(null);\n\n const handleExport = async () => {\n setExportError(null);\n try {\n await createExportJob({\n variables: {\n input: {\n name: \"Audit Log Export\",\n // permission_audit_logs covers auth audit logs in the export system\n entityTypes: [\"permission_audit_logs\"],\n format: \"CSV\",\n exportScope: \"INDIVIDUAL\",\n includeMetadata: true,\n },\n },\n });\n } catch (err) {\n setExportError(\n err instanceof Error ? err.message : \"Failed to start export\",\n );\n }\n };\n\n const getEventMarker = (eventType: string) => {\n switch (eventType) {\n case \"LOGIN_SUCCESS\":\n return \"OK\";\n case \"LOGIN_FAILURE\":\n return \"ER\";\n case \"LOGOUT\":\n return \"LO\";\n case \"PASSWORD_CHANGE\":\n return \"PW\";\n case \"MFA_ENABLED\":\n case \"MFA_VERIFIED\":\n return \"MF\";\n case \"ANOMALY_DETECTED\":\n return \"AL\";\n default:\n return \"AU\";\n }\n };\n\n if (!showAuditLog) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <div className=\"text-center py-16 border-2 border-dashed border-border-subtle rounded-xl\">\n <div className=\"size-16 mx-auto mb-4 rounded-full bg-status-warning-bg-subtle flex items-center justify-center\">\n <AlertIcon\n className=\"size-8 text-status-warning-text\"\n aria-hidden=\"true\"\n />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr(\"auth.auditLog.notAvailable\", \"Page Not Available\")}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n \"auth.auditLog.notEnabled\",\n \"The Security Audit Log is not enabled for your account.\",\n )}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between mb-8\">\n <h1 className=\"text-3xl font-semibold text-text-primary\">\n {tr(\"auth.auditLog.title\", \"Security Audit Log\")}\n </h1>\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\"auth.auditLog.exportAriaLabel\", \"Export audit log\")}\n className=\"flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {isExporting ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n {tr(\"auth.auditLog.exporting\", \"Exporting...\")}\n </>\n ) : (\n <>\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n {tr(\"auth.auditLog.export\", \"Export\")}\n </>\n )}\n </button>\n </div>\n\n {/* Export error */}\n {exportError && (\n <div\n role=\"alert\"\n className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\n <p className=\"text-sm text-status-error-text\">{exportError}</p>\n </div>\n )}\n\n {loading ? (\n <div aria-live=\"polite\" aria-busy=\"true\" className=\"text-center py-12\">\n <SpinnerIcon\n className=\"size-8 mx-auto text-text-link animate-spin\"\n aria-hidden=\"true\"\n />\n <p className=\"mt-3 text-text-secondary\">\n {tr(\"auth.auditLog.loading\", \"Loading audit events...\")}\n </p>\n </div>\n ) : error ? (\n <div\n role=\"alert\"\n className=\"text-center py-12 border border-status-error-border rounded-xl bg-status-error-bg-subtle\"\n >\n <p className=\"text-status-error-text\">\n {tr(\"auth.auditLog.error\", \"Unable to load audit events\")}\n </p>\n </div>\n ) : auditLogs.length === 0 ? (\n <output className=\"block text-center py-12\">\n <p className=\"text-text-secondary\">\n {tr(\"auth.auditLog.noEvents\", \"No security events recorded\")}\n </p>\n </output>\n ) : (\n <>\n <div className=\"space-y-4\" aria-live=\"polite\" aria-busy={loading}>\n {auditLogs.map((event: AuditLogEntry_AuthAuditLogFragment) => (\n <div\n key={event.id}\n className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\"\n >\n <div className=\"flex items-start gap-4\">\n <div\n className=\"flex size-10 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold text-text-secondary\"\n aria-hidden=\"true\"\n >\n {getEventMarker(event.action)}\n </div>\n <div className=\"flex-1\">\n <h3 className=\"text-lg font-medium text-text-primary mb-1\">\n {event.action.replace(/_/g, \" \")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-2\">\n <time dateTime={event.createdAt}>\n {formatDateTime(event.createdAt)}\n </time>\n </p>\n <p className=\"text-sm text-text-secondary\">\n {event.description ??\n tr(\n \"auth.auditLog.defaultDescription\",\n \"Authentication event\",\n )}\n {event.ipAddress && ` • ${event.ipAddress}`}\n </p>\n </div>\n {event.action.includes(\"FAILURE\") && (\n <output className=\"px-3 py-1 rounded-full bg-status-error-bg-subtle text-status-error-text text-sm font-medium\">\n {tr(\"auth.auditLog.failed\", \"Failed\")}\n </output>\n )}\n </div>\n </div>\n ))}\n </div>\n\n {/* Pagination */}\n <nav\n aria-label={tr(\n \"auth.auditLog.paginationAriaLabel\",\n \"Audit log pagination\",\n )}\n className=\"mt-6 flex items-center justify-between\"\n >\n <p className=\"text-sm text-text-secondary\">\n {tr(\"auth.auditLog.page\", \"Page\")} {page}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((prev) => Math.max(1, prev - 1))}\n disabled={page === 1}\n aria-label={tr(\n \"auth.auditLog.previousPageAriaLabel\",\n \"Go to previous page\",\n )}\n className=\"px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.auditLog.previous\", \"Previous\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setPage((prev) => prev + 1)}\n disabled={!hasMore}\n aria-label={tr(\n \"auth.auditLog.nextPageAriaLabel\",\n \"Go to next page\",\n )}\n className=\"px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.auditLog.next\", \"Next\")}\n </button>\n </div>\n </nav>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AuditLogPage.js","names":[],"sources":["../../../src/auth/pages/AuditLogPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport {\n EmphasisPanel,\n IllustratedEmptyState,\n PagePurpose,\n} from \"@burdenoff/fe-libs/ui\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\nimport { useFeatureFlagDetails } from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { formatDateTime } from \"../utils/formatters\";\nimport { AlertIcon, SpinnerIcon } from \"../components/AuthIcons\";\nimport {\n useAuthAuditLogsQuery,\n type AuditLogEntry_AuthAuditLogFragment,\n} from \"../../generated/global-operations\";\nimport { useCreateAuthExportJobMutation } from \"../../generated/wspace-operations\";\n\nconst PAGE_SIZE = 20;\n\n/**\n * Security audit log page with pagination and export\n *\n * Fragment: AuditLogEntry_AuthAuditLog (graphql/operations/global/fragments/AuditLogEntry.fragment.graphql)\n * Query: AuthAuditLogs (graphql/operations/global/queries/auditLog.graphql)\n *\n * TODO: When authAuditLogs backend migrates to AuthAuditLogConnection type,\n * update this query to use: authAuditLogs { auditLogs: [...], totalCount, hasMore }\n * and remove the client-side hasMore approximation below.\n */\nexport function AuditLogPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const [page, setPage] = useState(1);\n const offset = (page - 1) * PAGE_SIZE;\n\n // Default-deny: only show when the flag is explicitly enabled in the system.\n // useFeatureFlag uses default-allow (returns true when not found), so we use\n // useFeatureFlagDetails + explicit value check instead.\n const auditLogFlag = useFeatureFlagDetails(\"auth.audit-log\");\n const showAuditLog = auditLogFlag?.value === true;\n\n const { data, loading, error } = useAuthAuditLogsQuery({\n skip: !showAuditLog,\n fetchPolicy: \"cache-and-network\",\n variables: { pagination: { limit: PAGE_SIZE, offset } },\n });\n\n const auditLogs = data?.authAuditLogs ?? [];\n // Approximate hasMore: if we received a full page there may be more\n const hasMore = auditLogs.length === PAGE_SIZE;\n\n const wspaceClient = useGatewayClient(\"workspace\");\n const [createExportJob, { loading: isExporting }] =\n useCreateAuthExportJobMutation({ client: wspaceClient });\n const [exportError, setExportError] = useState<string | null>(null);\n\n const handleExport = async () => {\n setExportError(null);\n try {\n await createExportJob({\n variables: {\n input: {\n name: \"Audit Log Export\",\n // permission_audit_logs covers auth audit logs in the export system\n entityTypes: [\"permission_audit_logs\"],\n format: \"CSV\",\n exportScope: \"INDIVIDUAL\",\n includeMetadata: true,\n },\n },\n });\n } catch (err) {\n setExportError(\n err instanceof Error ? err.message : \"Failed to start export\",\n );\n }\n };\n\n const getEventMarker = (eventType: string) => {\n switch (eventType) {\n case \"LOGIN_SUCCESS\":\n return \"OK\";\n case \"LOGIN_FAILURE\":\n return \"ER\";\n case \"LOGOUT\":\n return \"LO\";\n case \"PASSWORD_CHANGE\":\n return \"PW\";\n case \"MFA_ENABLED\":\n case \"MFA_VERIFIED\":\n return \"MF\";\n case \"ANOMALY_DETECTED\":\n return \"AL\";\n default:\n return \"AU\";\n }\n };\n\n if (!showAuditLog) {\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.auditLog.notAvailable\", \"Page Not Available\")}\n description={tr(\n \"auth.auditLog.notEnabled\",\n \"The Security Audit Log is not enabled for your account.\",\n )}\n />\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl mx-auto p-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between mb-8\">\n <h1 className=\"text-3xl font-semibold text-text-primary\">\n {tr(\"auth.auditLog.title\", \"Security Audit Log\")}\n </h1>\n <button\n type=\"button\"\n onClick={() => void handleExport()}\n disabled={isExporting}\n aria-label={tr(\"auth.auditLog.exportAriaLabel\", \"Export audit log\")}\n className=\"flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-default text-text-primary bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium\"\n >\n {isExporting ? (\n <>\n <SpinnerIcon className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n {tr(\"auth.auditLog.exporting\", \"Exporting...\")}\n </>\n ) : (\n <>\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n {tr(\"auth.auditLog.export\", \"Export\")}\n </>\n )}\n </button>\n </div>\n\n <PagePurpose className=\"mb-6\">\n {tr(\n \"auth.auditLog.purpose\",\n \"A running record of security-relevant events on your account — sign-ins, password and MFA changes, and anomalies. Scan it to spot anything you didn't do, and export it for your own records or compliance.\",\n )}\n </PagePurpose>\n\n {/* Emphasis: what's tracked — the one highlighted zone */}\n <EmphasisPanel className=\"mb-6\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr(\"auth.auditLog.emphasisTitle\", \"What this log captures\")}\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n \"auth.auditLog.emphasisBody\",\n \"Successful and failed sign-ins, logouts, password and two-factor changes, and detected anomalies — newest first.\",\n )}\n </p>\n </EmphasisPanel>\n\n {/* Export error */}\n {exportError && (\n <div\n role=\"alert\"\n className=\"mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\"\n >\n <AlertIcon\n className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\"\n aria-hidden=\"true\"\n />\n <p className=\"text-sm text-status-error-text\">{exportError}</p>\n </div>\n )}\n\n {loading ? (\n <div aria-live=\"polite\" aria-busy=\"true\" className=\"text-center py-12\">\n <SpinnerIcon\n className=\"size-8 mx-auto text-text-link animate-spin\"\n aria-hidden=\"true\"\n />\n <p className=\"mt-3 text-text-secondary\">\n {tr(\"auth.auditLog.loading\", \"Loading audit events...\")}\n </p>\n </div>\n ) : error ? (\n <div role=\"alert\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr(\"auth.auditLog.error\", \"Unable to load audit events\")}\n description={tr(\n \"auth.auditLog.errorDesc\",\n \"Something went wrong fetching your security events. Please try again in a moment.\",\n )}\n />\n </div>\n ) : auditLogs.length === 0 ? (\n <output className=\"block\">\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={tr(\"auth.auditLog.noEvents\", \"No security events recorded\")}\n description={tr(\n \"auth.auditLog.noEventsDesc\",\n \"As you sign in and change security settings, those events will appear here.\",\n )}\n />\n </output>\n ) : (\n <>\n <div className=\"space-y-4\" aria-live=\"polite\" aria-busy={loading}>\n {auditLogs.map((event: AuditLogEntry_AuthAuditLogFragment) => (\n <div\n key={event.id}\n className=\"p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm\"\n >\n <div className=\"flex items-start gap-4\">\n <div\n className=\"flex size-10 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold text-text-secondary\"\n aria-hidden=\"true\"\n >\n {getEventMarker(event.action)}\n </div>\n <div className=\"flex-1\">\n <h3 className=\"text-lg font-medium text-text-primary mb-1\">\n {event.action.replace(/_/g, \" \")}\n </h3>\n <p className=\"text-sm text-text-secondary mb-2\">\n <time dateTime={event.createdAt}>\n {formatDateTime(event.createdAt)}\n </time>\n </p>\n <p className=\"text-sm text-text-secondary\">\n {event.description ??\n tr(\n \"auth.auditLog.defaultDescription\",\n \"Authentication event\",\n )}\n {event.ipAddress && ` • ${event.ipAddress}`}\n </p>\n </div>\n {event.action.includes(\"FAILURE\") && (\n <output className=\"px-3 py-1 rounded-full bg-status-error-bg-subtle text-status-error-text text-sm font-medium\">\n {tr(\"auth.auditLog.failed\", \"Failed\")}\n </output>\n )}\n </div>\n </div>\n ))}\n </div>\n\n {/* Pagination */}\n <nav\n aria-label={tr(\n \"auth.auditLog.paginationAriaLabel\",\n \"Audit log pagination\",\n )}\n className=\"mt-6 flex items-center justify-between\"\n >\n <p className=\"text-sm text-text-secondary\">\n {tr(\"auth.auditLog.page\", \"Page\")} {page}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((prev) => Math.max(1, prev - 1))}\n disabled={page === 1}\n aria-label={tr(\n \"auth.auditLog.previousPageAriaLabel\",\n \"Go to previous page\",\n )}\n className=\"px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.auditLog.previous\", \"Previous\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setPage((prev) => prev + 1)}\n disabled={!hasMore}\n aria-label={tr(\n \"auth.auditLog.nextPageAriaLabel\",\n \"Go to next page\",\n )}\n className=\"px-4 py-2 rounded-lg border border-border-default text-text-primary bg-bg-surface disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n {tr(\"auth.auditLog.next\", \"Next\")}\n </button>\n </div>\n </nav>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -6,58 +6,59 @@ import { useCallback as i, useEffect as a, useRef as o, useState as s } from "re
|
|
|
6
6
|
import { useSearchParams as c } from "react-router-dom";
|
|
7
7
|
import { useI18n as l } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
8
8
|
import { Fragment as u, jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
9
|
+
import { Button as p } from "@burdenoff/fe-libs/button";
|
|
9
10
|
//#region src/auth/pages/EmailVerifyPage.tsx
|
|
10
|
-
var
|
|
11
|
-
function
|
|
11
|
+
var m = 5;
|
|
12
|
+
function h(e) {
|
|
12
13
|
let t = "/auth/login", n = e ? `${t}?email=${encodeURIComponent(e)}` : t;
|
|
13
14
|
window.location.href = n;
|
|
14
15
|
}
|
|
15
|
-
function
|
|
16
|
-
let { t:
|
|
17
|
-
let n =
|
|
16
|
+
function g() {
|
|
17
|
+
let { t: g } = l(), _ = (e, t) => {
|
|
18
|
+
let n = g(e);
|
|
18
19
|
return n === e ? t : n;
|
|
19
|
-
}, [
|
|
20
|
+
}, [v] = c(), y = v.get("token"), b = v.get("email"), { verifyEmail: x, resendVerification: S } = t(), C = e((e) => e.user), [w, T] = s("verifying"), E = o(b || void 0), [D, O] = s(!1), [k, A] = s(!1), [j, M] = s(m), N = o(!1);
|
|
20
21
|
a(() => {
|
|
21
|
-
if (!
|
|
22
|
-
let e = `auth:verifyEmail:inflight:${
|
|
22
|
+
if (!y || N.current) return;
|
|
23
|
+
let e = `auth:verifyEmail:inflight:${y}`;
|
|
23
24
|
if (!(typeof window < "u" && window.sessionStorage.getItem(e))) {
|
|
24
|
-
|
|
25
|
+
N.current = !0;
|
|
25
26
|
try {
|
|
26
27
|
window.sessionStorage.setItem(e, "1");
|
|
27
28
|
} catch {}
|
|
28
|
-
|
|
29
|
-
e.success ? (
|
|
30
|
-
}).catch(() =>
|
|
29
|
+
x(y).then((e) => {
|
|
30
|
+
e.success ? (T("success"), e.email && (E.current = e.email)) : T("error");
|
|
31
|
+
}).catch(() => T("error"));
|
|
31
32
|
}
|
|
32
|
-
}, [
|
|
33
|
-
if (
|
|
33
|
+
}, [y, x]), a(() => {
|
|
34
|
+
if (w !== "success") return;
|
|
34
35
|
let e = setInterval(() => {
|
|
35
|
-
|
|
36
|
+
M((t) => t <= 1 ? (clearInterval(e), h(E.current), 0) : t - 1);
|
|
36
37
|
}, 1e3);
|
|
37
38
|
return () => clearInterval(e);
|
|
38
|
-
}, [
|
|
39
|
-
let
|
|
40
|
-
let e =
|
|
39
|
+
}, [w]);
|
|
40
|
+
let P = async () => {
|
|
41
|
+
let e = b || C?.email;
|
|
41
42
|
if (!e) {
|
|
42
43
|
console.error("No email available for resend");
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
+
O(!0);
|
|
46
47
|
try {
|
|
47
|
-
await
|
|
48
|
+
await S(e), A(!0);
|
|
48
49
|
} catch (e) {
|
|
49
50
|
console.error("Failed to resend verification:", e);
|
|
50
51
|
} finally {
|
|
51
|
-
|
|
52
|
+
O(!1);
|
|
52
53
|
}
|
|
53
|
-
},
|
|
54
|
-
|
|
55
|
-
}, []),
|
|
56
|
-
|
|
54
|
+
}, F = i(() => {
|
|
55
|
+
h(E.current);
|
|
56
|
+
}, []), I = i(() => {
|
|
57
|
+
h();
|
|
57
58
|
}, []);
|
|
58
|
-
return
|
|
59
|
-
title:
|
|
60
|
-
subtitle:
|
|
59
|
+
return w === "verifying" ? /* @__PURE__ */ d(n, {
|
|
60
|
+
title: _("auth.emailVerify.verifyingTitle", "Verifying your email"),
|
|
61
|
+
subtitle: _("auth.emailVerify.verifyingSubtitle", "Please wait while we confirm your email address"),
|
|
61
62
|
children: /* @__PURE__ */ f("div", {
|
|
62
63
|
className: "flex flex-col items-center justify-center py-12",
|
|
63
64
|
children: [/* @__PURE__ */ d("div", {
|
|
@@ -65,12 +66,12 @@ function h() {
|
|
|
65
66
|
children: /* @__PURE__ */ d(r, { className: "size-8 text-[var(--color-text-link)] animate-spin" })
|
|
66
67
|
}), /* @__PURE__ */ d("p", {
|
|
67
68
|
className: "text-[var(--color-text-secondary)]",
|
|
68
|
-
children:
|
|
69
|
+
children: _("auth.emailVerify.moment", "This will only take a moment...")
|
|
69
70
|
})]
|
|
70
71
|
})
|
|
71
|
-
}) :
|
|
72
|
-
title:
|
|
73
|
-
subtitle:
|
|
72
|
+
}) : w === "success" ? /* @__PURE__ */ d(n, {
|
|
73
|
+
title: _("auth.emailVerify.verifiedTitle", "Email verified!"),
|
|
74
|
+
subtitle: _("auth.emailVerify.verifiedSubtitle", "Your email address has been confirmed"),
|
|
74
75
|
children: /* @__PURE__ */ f("div", {
|
|
75
76
|
className: "flex flex-col items-center justify-center py-6",
|
|
76
77
|
children: [
|
|
@@ -91,32 +92,34 @@ function h() {
|
|
|
91
92
|
}),
|
|
92
93
|
/* @__PURE__ */ d("p", {
|
|
93
94
|
className: "text-center text-[var(--color-text-secondary)] mb-6",
|
|
94
|
-
children:
|
|
95
|
+
children: _("auth.emailVerify.successMessage", "Your email has been successfully verified. You can now sign in to access all features.")
|
|
95
96
|
}),
|
|
96
97
|
/* @__PURE__ */ f("p", {
|
|
97
98
|
className: "text-center text-sm text-[var(--color-text-tertiary)] mb-6",
|
|
98
99
|
children: [
|
|
99
|
-
|
|
100
|
+
_("auth.emailVerify.redirecting", "Redirecting to sign in in"),
|
|
100
101
|
" ",
|
|
101
102
|
/* @__PURE__ */ d("span", {
|
|
102
103
|
className: "font-semibold text-[var(--color-text-primary)]",
|
|
103
|
-
children:
|
|
104
|
+
children: j
|
|
104
105
|
}),
|
|
105
106
|
" ",
|
|
106
|
-
|
|
107
|
+
_("auth.emailVerify.seconds", "seconds...")
|
|
107
108
|
]
|
|
108
109
|
}),
|
|
109
|
-
/* @__PURE__ */ d(
|
|
110
|
+
/* @__PURE__ */ d(p, {
|
|
110
111
|
type: "button",
|
|
111
|
-
onClick:
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
onClick: F,
|
|
113
|
+
variant: "default",
|
|
114
|
+
size: "lg",
|
|
115
|
+
className: "w-full font-semibold",
|
|
116
|
+
children: _("auth.emailVerify.continueSignIn", "Continue to sign in now")
|
|
114
117
|
})
|
|
115
118
|
]
|
|
116
119
|
})
|
|
117
120
|
}) : /* @__PURE__ */ d(n, {
|
|
118
|
-
title:
|
|
119
|
-
subtitle:
|
|
121
|
+
title: _("auth.emailVerify.failedTitle", "Verification failed"),
|
|
122
|
+
subtitle: _("auth.emailVerify.failedSubtitle", "We couldn't verify your email address"),
|
|
120
123
|
children: /* @__PURE__ */ f("div", {
|
|
121
124
|
className: "flex flex-col items-center justify-center py-6",
|
|
122
125
|
children: [
|
|
@@ -137,32 +140,34 @@ function h() {
|
|
|
137
140
|
}),
|
|
138
141
|
/* @__PURE__ */ d("p", {
|
|
139
142
|
className: "text-center text-[var(--color-text-secondary)] mb-8",
|
|
140
|
-
children:
|
|
143
|
+
children: _("auth.emailVerify.invalidLink", "The verification link is invalid or has expired. Please request a new verification email.")
|
|
141
144
|
}),
|
|
142
|
-
|
|
145
|
+
k ? /* @__PURE__ */ d("div", {
|
|
143
146
|
className: "w-full p-4 rounded-xl bg-[var(--color-status-success-bg-subtle)] border border-[var(--color-status-success-border)] text-center mb-4",
|
|
144
147
|
children: /* @__PURE__ */ d("p", {
|
|
145
148
|
className: "text-sm text-[var(--color-status-success-text)]",
|
|
146
|
-
children:
|
|
149
|
+
children: _("auth.emailVerify.emailSent", "Verification email sent! Please check your inbox.")
|
|
147
150
|
})
|
|
148
|
-
}) : /* @__PURE__ */ d(
|
|
151
|
+
}) : /* @__PURE__ */ d(p, {
|
|
149
152
|
type: "button",
|
|
150
|
-
onClick:
|
|
151
|
-
disabled:
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
onClick: P,
|
|
154
|
+
disabled: D,
|
|
155
|
+
variant: "default",
|
|
156
|
+
size: "lg",
|
|
157
|
+
className: "mb-4 w-full font-semibold",
|
|
158
|
+
children: D ? /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ d(r, { className: "size-5 animate-spin" }), /* @__PURE__ */ d("span", { children: "Sending…" })] }) : _("auth.emailVerify.resend", "Resend verification email")
|
|
154
159
|
}),
|
|
155
160
|
/* @__PURE__ */ d("button", {
|
|
156
161
|
type: "button",
|
|
157
|
-
onClick:
|
|
162
|
+
onClick: I,
|
|
158
163
|
className: "w-full py-3 px-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] font-medium hover:bg-[var(--color-bg-sunken)] transition-all",
|
|
159
|
-
children:
|
|
164
|
+
children: _("auth.emailVerify.backToSignIn", "Back to sign in")
|
|
160
165
|
})
|
|
161
166
|
]
|
|
162
167
|
})
|
|
163
168
|
});
|
|
164
169
|
}
|
|
165
170
|
//#endregion
|
|
166
|
-
export {
|
|
171
|
+
export { g as EmailVerifyPage };
|
|
167
172
|
|
|
168
173
|
//# sourceMappingURL=EmailVerifyPage.js.map
|