@burdenoff/microfe-auth 2026.710.2 → 2026.717.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/useContextSwitcherData.js +176 -151
- package/dist/auth/hooks/useContextSwitcherData.js.map +1 -1
- package/dist/auth/pages/AuditLogPage.js +106 -90
- package/dist/auth/pages/AuditLogPage.js.map +1 -1
- package/dist/auth/pages/MfaSetupPage.js +4 -4
- package/dist/auth/pages/UserDetailsPage.js +135 -120
- package/dist/auth/pages/UserDetailsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
import { useAuthAuditLogsQuery as e } from "../../generated/global-operations.js";
|
|
2
2
|
import { AlertIcon as t, SpinnerIcon as n } from "../components/AuthIcons.js";
|
|
3
3
|
import { formatDateTime as r } from "../utils/formatters.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
4
|
+
import { ConfirmActionDialog as i } from "../components/ConfirmActionDialog.js";
|
|
5
|
+
import { useCreateAuthExportJobMutation as a } from "../../generated/wspace-operations.js";
|
|
6
|
+
import { useState as o } from "react";
|
|
7
|
+
import { useI18n as s } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
8
|
+
import { Fragment as c, jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
9
|
+
import { useFeatureFlagDetails as d } from "@burdenoff/fe-libs/shared/feature-flags";
|
|
10
|
+
import { isQuotaExhaustedError as f } from "@burdenoff/fe-libs/shared/utils";
|
|
11
|
+
import { EmphasisPanel as p, IllustratedEmptyState as m, PagePurpose as h } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
import { useGatewayClient as g } from "@burdenoff/fe-libs/shared/graphql";
|
|
11
13
|
//#region src/auth/pages/AuditLogPage.tsx
|
|
12
|
-
var
|
|
13
|
-
function
|
|
14
|
-
let { t:
|
|
15
|
-
let n =
|
|
14
|
+
var _ = 20;
|
|
15
|
+
function v() {
|
|
16
|
+
let { t: v } = s(), y = (e, t) => {
|
|
17
|
+
let n = v(e);
|
|
16
18
|
return n === e ? t : n;
|
|
17
|
-
}, [
|
|
18
|
-
skip: !
|
|
19
|
+
}, [b, x] = o(1), S = (b - 1) * _, C = d("auth.audit-log")?.value === !0, { data: w, loading: T, error: E } = e({
|
|
20
|
+
skip: !C,
|
|
19
21
|
fetchPolicy: "cache-and-network",
|
|
20
22
|
variables: { pagination: {
|
|
21
|
-
limit:
|
|
22
|
-
offset:
|
|
23
|
+
limit: _,
|
|
24
|
+
offset: S
|
|
23
25
|
} }
|
|
24
|
-
}),
|
|
25
|
-
|
|
26
|
+
}), D = w?.authAuditLogs ?? [], O = D.length === _, [k, { loading: A }] = a({ client: g("workspace") }), [j, M] = o(null), [N, P] = o(!1), F = async () => {
|
|
27
|
+
M(null);
|
|
26
28
|
try {
|
|
27
|
-
await
|
|
29
|
+
await k({ variables: { input: {
|
|
28
30
|
name: "Audit Log Export",
|
|
29
31
|
entityTypes: ["permission_audit_logs"],
|
|
30
32
|
format: "CSV",
|
|
@@ -32,9 +34,13 @@ function g() {
|
|
|
32
34
|
includeMetadata: !0
|
|
33
35
|
} } });
|
|
34
36
|
} catch (e) {
|
|
35
|
-
|
|
37
|
+
if (f(e)) {
|
|
38
|
+
P(!0), M(y("auth.auditLog.exportQuotaReached", "You have reached your export limit for this month."));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
M(e instanceof Error ? e.message : "Failed to start export");
|
|
36
42
|
}
|
|
37
|
-
},
|
|
43
|
+
}, I = (e) => {
|
|
38
44
|
switch (e) {
|
|
39
45
|
case "LOGIN_SUCCESS": return "OK";
|
|
40
46
|
case "LOGIN_FAILURE": return "ER";
|
|
@@ -46,169 +52,179 @@ function g() {
|
|
|
46
52
|
default: return "AU";
|
|
47
53
|
}
|
|
48
54
|
};
|
|
49
|
-
return
|
|
55
|
+
return C ? /* @__PURE__ */ u("div", {
|
|
50
56
|
className: "max-w-4xl mx-auto p-6",
|
|
51
57
|
children: [
|
|
52
|
-
/* @__PURE__ */
|
|
58
|
+
/* @__PURE__ */ u("div", {
|
|
53
59
|
className: "flex items-center justify-between mb-8",
|
|
54
|
-
children: [/* @__PURE__ */
|
|
60
|
+
children: [/* @__PURE__ */ l("h1", {
|
|
55
61
|
className: "text-3xl font-semibold text-text-primary",
|
|
56
|
-
children:
|
|
57
|
-
}), /* @__PURE__ */
|
|
62
|
+
children: y("auth.auditLog.title", "Security Audit Log")
|
|
63
|
+
}), /* @__PURE__ */ l("button", {
|
|
58
64
|
type: "button",
|
|
59
|
-
onClick: () => void
|
|
60
|
-
disabled:
|
|
61
|
-
"aria-label":
|
|
65
|
+
onClick: () => void F(),
|
|
66
|
+
disabled: A,
|
|
67
|
+
"aria-label": y("auth.auditLog.exportAriaLabel", "Export audit log"),
|
|
62
68
|
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",
|
|
63
|
-
children:
|
|
69
|
+
children: A ? /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l(n, {
|
|
64
70
|
className: "size-4 animate-spin",
|
|
65
71
|
"aria-hidden": "true"
|
|
66
|
-
}),
|
|
72
|
+
}), y("auth.auditLog.exporting", "Exporting...")] }) : /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l("svg", {
|
|
67
73
|
className: "size-4",
|
|
68
74
|
fill: "none",
|
|
69
75
|
viewBox: "0 0 24 24",
|
|
70
76
|
stroke: "currentColor",
|
|
71
77
|
"aria-hidden": "true",
|
|
72
|
-
children: /* @__PURE__ */
|
|
78
|
+
children: /* @__PURE__ */ l("path", {
|
|
73
79
|
strokeLinecap: "round",
|
|
74
80
|
strokeLinejoin: "round",
|
|
75
81
|
strokeWidth: 2,
|
|
76
82
|
d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
|
77
83
|
})
|
|
78
|
-
}),
|
|
84
|
+
}), y("auth.auditLog.export", "Export")] })
|
|
79
85
|
})]
|
|
80
86
|
}),
|
|
81
|
-
/* @__PURE__ */
|
|
87
|
+
/* @__PURE__ */ l(h, {
|
|
82
88
|
className: "mb-6",
|
|
83
|
-
children:
|
|
89
|
+
children: y("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
90
|
}),
|
|
85
|
-
/* @__PURE__ */
|
|
91
|
+
/* @__PURE__ */ u(p, {
|
|
86
92
|
className: "mb-6",
|
|
87
|
-
children: [/* @__PURE__ */
|
|
93
|
+
children: [/* @__PURE__ */ l("p", {
|
|
88
94
|
className: "text-sm font-medium text-text-primary",
|
|
89
|
-
children:
|
|
90
|
-
}), /* @__PURE__ */
|
|
95
|
+
children: y("auth.auditLog.emphasisTitle", "What this log captures")
|
|
96
|
+
}), /* @__PURE__ */ l("p", {
|
|
91
97
|
className: "mt-1 text-sm text-text-secondary",
|
|
92
|
-
children:
|
|
98
|
+
children: y("auth.auditLog.emphasisBody", "Successful and failed sign-ins, logouts, password and two-factor changes, and detected anomalies — newest first.")
|
|
93
99
|
})]
|
|
94
100
|
}),
|
|
95
|
-
|
|
101
|
+
j && /* @__PURE__ */ u("div", {
|
|
96
102
|
role: "alert",
|
|
97
103
|
className: "mb-6 flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border",
|
|
98
|
-
children: [/* @__PURE__ */
|
|
104
|
+
children: [/* @__PURE__ */ l(t, {
|
|
99
105
|
className: "size-5 text-status-error-text flex-shrink-0 mt-0.5",
|
|
100
106
|
"aria-hidden": "true"
|
|
101
|
-
}), /* @__PURE__ */
|
|
107
|
+
}), /* @__PURE__ */ l("p", {
|
|
102
108
|
className: "text-sm text-status-error-text",
|
|
103
|
-
children:
|
|
109
|
+
children: j
|
|
104
110
|
})]
|
|
105
111
|
}),
|
|
106
|
-
|
|
112
|
+
/* @__PURE__ */ l(i, {
|
|
113
|
+
open: N,
|
|
114
|
+
onOpenChange: P,
|
|
115
|
+
title: y("auth.auditLog.exportQuotaTitle", "Export Limit Reached"),
|
|
116
|
+
description: y("auth.auditLog.exportQuotaDescription", "You have reached your export limit for this month."),
|
|
117
|
+
confirmText: y("auth.auditLog.exportQuotaViewBilling", "View Billing"),
|
|
118
|
+
cancelText: y("auth.auditLog.exportQuotaDismiss", "Dismiss"),
|
|
119
|
+
variant: "warning",
|
|
120
|
+
onConfirm: () => window.location.assign("/billing")
|
|
121
|
+
}),
|
|
122
|
+
T ? /* @__PURE__ */ u("div", {
|
|
107
123
|
"aria-live": "polite",
|
|
108
124
|
"aria-busy": "true",
|
|
109
125
|
className: "text-center py-12",
|
|
110
|
-
children: [/* @__PURE__ */
|
|
126
|
+
children: [/* @__PURE__ */ l(n, {
|
|
111
127
|
className: "size-8 mx-auto text-text-link animate-spin",
|
|
112
128
|
"aria-hidden": "true"
|
|
113
|
-
}), /* @__PURE__ */
|
|
129
|
+
}), /* @__PURE__ */ l("p", {
|
|
114
130
|
className: "mt-3 text-text-secondary",
|
|
115
|
-
children:
|
|
131
|
+
children: y("auth.auditLog.loading", "Loading audit events...")
|
|
116
132
|
})]
|
|
117
|
-
}) :
|
|
133
|
+
}) : E ? /* @__PURE__ */ l("div", {
|
|
118
134
|
role: "alert",
|
|
119
|
-
children: /* @__PURE__ */
|
|
135
|
+
children: /* @__PURE__ */ l(m, {
|
|
120
136
|
illustration: "empty-generic",
|
|
121
|
-
title:
|
|
122
|
-
description:
|
|
137
|
+
title: y("auth.auditLog.error", "Unable to load audit events"),
|
|
138
|
+
description: y("auth.auditLog.errorDesc", "Something went wrong fetching your security events. Please try again in a moment.")
|
|
123
139
|
})
|
|
124
|
-
}) :
|
|
140
|
+
}) : D.length === 0 ? /* @__PURE__ */ l("output", {
|
|
125
141
|
className: "block",
|
|
126
|
-
children: /* @__PURE__ */
|
|
142
|
+
children: /* @__PURE__ */ l(m, {
|
|
127
143
|
illustration: "empty-notifications",
|
|
128
|
-
title:
|
|
129
|
-
description:
|
|
144
|
+
title: y("auth.auditLog.noEvents", "No security events recorded"),
|
|
145
|
+
description: y("auth.auditLog.noEventsDesc", "As you sign in and change security settings, those events will appear here.")
|
|
130
146
|
})
|
|
131
|
-
}) : /* @__PURE__ */
|
|
147
|
+
}) : /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l("div", {
|
|
132
148
|
className: "space-y-4",
|
|
133
149
|
"aria-live": "polite",
|
|
134
|
-
"aria-busy":
|
|
135
|
-
children:
|
|
150
|
+
"aria-busy": T,
|
|
151
|
+
children: D.map((e) => /* @__PURE__ */ l("div", {
|
|
136
152
|
className: "p-6 rounded-xl border border-border-subtle bg-bg-surface shadow-sm",
|
|
137
|
-
children: /* @__PURE__ */
|
|
153
|
+
children: /* @__PURE__ */ u("div", {
|
|
138
154
|
className: "flex items-start gap-4",
|
|
139
155
|
children: [
|
|
140
|
-
/* @__PURE__ */
|
|
156
|
+
/* @__PURE__ */ l("div", {
|
|
141
157
|
className: "flex size-10 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold text-text-secondary",
|
|
142
158
|
"aria-hidden": "true",
|
|
143
|
-
children:
|
|
159
|
+
children: I(e.action)
|
|
144
160
|
}),
|
|
145
|
-
/* @__PURE__ */
|
|
161
|
+
/* @__PURE__ */ u("div", {
|
|
146
162
|
className: "flex-1",
|
|
147
163
|
children: [
|
|
148
|
-
/* @__PURE__ */
|
|
164
|
+
/* @__PURE__ */ l("h3", {
|
|
149
165
|
className: "text-lg font-medium text-text-primary mb-1",
|
|
150
166
|
children: e.action.replace(/_/g, " ")
|
|
151
167
|
}),
|
|
152
|
-
/* @__PURE__ */
|
|
168
|
+
/* @__PURE__ */ l("p", {
|
|
153
169
|
className: "text-sm text-text-secondary mb-2",
|
|
154
|
-
children: /* @__PURE__ */
|
|
170
|
+
children: /* @__PURE__ */ l("time", {
|
|
155
171
|
dateTime: e.createdAt,
|
|
156
172
|
children: r(e.createdAt)
|
|
157
173
|
})
|
|
158
174
|
}),
|
|
159
|
-
/* @__PURE__ */
|
|
175
|
+
/* @__PURE__ */ u("p", {
|
|
160
176
|
className: "text-sm text-text-secondary",
|
|
161
|
-
children: [e.description ??
|
|
177
|
+
children: [e.description ?? y("auth.auditLog.defaultDescription", "Authentication event"), e.ipAddress && ` • ${e.ipAddress}`]
|
|
162
178
|
})
|
|
163
179
|
]
|
|
164
180
|
}),
|
|
165
|
-
e.action.includes("FAILURE") && /* @__PURE__ */
|
|
181
|
+
e.action.includes("FAILURE") && /* @__PURE__ */ l("output", {
|
|
166
182
|
className: "px-3 py-1 rounded-full bg-status-error-bg-subtle text-status-error-text text-sm font-medium",
|
|
167
|
-
children:
|
|
183
|
+
children: y("auth.auditLog.failed", "Failed")
|
|
168
184
|
})
|
|
169
185
|
]
|
|
170
186
|
})
|
|
171
187
|
}, e.id))
|
|
172
|
-
}), /* @__PURE__ */
|
|
173
|
-
"aria-label":
|
|
188
|
+
}), /* @__PURE__ */ u("nav", {
|
|
189
|
+
"aria-label": y("auth.auditLog.paginationAriaLabel", "Audit log pagination"),
|
|
174
190
|
className: "mt-6 flex items-center justify-between",
|
|
175
|
-
children: [/* @__PURE__ */
|
|
191
|
+
children: [/* @__PURE__ */ u("p", {
|
|
176
192
|
className: "text-sm text-text-secondary",
|
|
177
193
|
children: [
|
|
178
|
-
|
|
194
|
+
y("auth.auditLog.page", "Page"),
|
|
179
195
|
" ",
|
|
180
|
-
|
|
196
|
+
b
|
|
181
197
|
]
|
|
182
|
-
}), /* @__PURE__ */
|
|
198
|
+
}), /* @__PURE__ */ u("div", {
|
|
183
199
|
className: "flex gap-2",
|
|
184
|
-
children: [/* @__PURE__ */
|
|
200
|
+
children: [/* @__PURE__ */ l("button", {
|
|
185
201
|
type: "button",
|
|
186
|
-
onClick: () =>
|
|
187
|
-
disabled:
|
|
188
|
-
"aria-label":
|
|
202
|
+
onClick: () => x((e) => Math.max(1, e - 1)),
|
|
203
|
+
disabled: b === 1,
|
|
204
|
+
"aria-label": y("auth.auditLog.previousPageAriaLabel", "Go to previous page"),
|
|
189
205
|
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",
|
|
190
|
-
children:
|
|
191
|
-
}), /* @__PURE__ */
|
|
206
|
+
children: y("auth.auditLog.previous", "Previous")
|
|
207
|
+
}), /* @__PURE__ */ l("button", {
|
|
192
208
|
type: "button",
|
|
193
|
-
onClick: () =>
|
|
194
|
-
disabled: !
|
|
195
|
-
"aria-label":
|
|
209
|
+
onClick: () => x((e) => e + 1),
|
|
210
|
+
disabled: !O,
|
|
211
|
+
"aria-label": y("auth.auditLog.nextPageAriaLabel", "Go to next page"),
|
|
196
212
|
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",
|
|
197
|
-
children:
|
|
213
|
+
children: y("auth.auditLog.next", "Next")
|
|
198
214
|
})]
|
|
199
215
|
})]
|
|
200
216
|
})] })
|
|
201
217
|
]
|
|
202
|
-
}) : /* @__PURE__ */
|
|
218
|
+
}) : /* @__PURE__ */ l("div", {
|
|
203
219
|
className: "max-w-4xl mx-auto p-6",
|
|
204
|
-
children: /* @__PURE__ */
|
|
220
|
+
children: /* @__PURE__ */ l(m, {
|
|
205
221
|
illustration: "empty-generic",
|
|
206
|
-
title:
|
|
207
|
-
description:
|
|
222
|
+
title: y("auth.auditLog.notAvailable", "Page Not Available"),
|
|
223
|
+
description: y("auth.auditLog.notEnabled", "The Security Audit Log is not enabled for your account.")
|
|
208
224
|
})
|
|
209
225
|
});
|
|
210
226
|
}
|
|
211
227
|
//#endregion
|
|
212
|
-
export {
|
|
228
|
+
export { v as AuditLogPage };
|
|
213
229
|
|
|
214
230
|
//# 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 {\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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"}
|
|
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 { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\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 { ConfirmActionDialog } from \"../components/ConfirmActionDialog\";\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 const [showExportQuotaDialog, setShowExportQuotaDialog] = useState(false);\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 if (isQuotaExhaustedError(err)) {\n setShowExportQuotaDialog(true);\n setExportError(\n tr(\n \"auth.auditLog.exportQuotaReached\",\n \"You have reached your export limit for this month.\",\n ),\n );\n return;\n }\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 <ConfirmActionDialog\n open={showExportQuotaDialog}\n onOpenChange={setShowExportQuotaDialog}\n title={tr(\"auth.auditLog.exportQuotaTitle\", \"Export Limit Reached\")}\n description={tr(\n \"auth.auditLog.exportQuotaDescription\",\n \"You have reached your export limit for this month.\",\n )}\n confirmText={tr(\"auth.auditLog.exportQuotaViewBilling\", \"View Billing\")}\n cancelText={tr(\"auth.auditLog.exportQuotaDismiss\", \"Dismiss\")}\n variant=\"warning\"\n onConfirm={() => window.location.assign(\"/billing\")}\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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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useAuthStore as e } from "../store/authStore.js";
|
|
2
2
|
import { AlertIcon as t, SpinnerIcon as n } from "../components/AuthIcons.js";
|
|
3
3
|
import { useMfaOperations as r } from "../hooks/useMfaOperations.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { ConfirmActionDialog as i } from "../components/ConfirmActionDialog.js";
|
|
5
|
+
import { QRCodeSVG as a } from "../../node_modules/qrcode.react/lib/esm/index.js";
|
|
6
6
|
import { nativeCopyText as o, nativeImpact as s, nativeNotify as c } from "../../utils/nativeBridge.js";
|
|
7
7
|
import { useState as l } from "react";
|
|
8
8
|
import { useNavigate as u } from "react-router-dom";
|
|
@@ -25,7 +25,7 @@ function v() {
|
|
|
25
25
|
return E ? /* @__PURE__ */ m("div", {
|
|
26
26
|
className: "max-w-2xl mx-auto p-6",
|
|
27
27
|
children: [
|
|
28
|
-
/* @__PURE__ */ p(
|
|
28
|
+
/* @__PURE__ */ p(i, {
|
|
29
29
|
open: L,
|
|
30
30
|
onOpenChange: R,
|
|
31
31
|
title: y("auth.mfaSetup.disableTitle", "Disable two-factor authentication?"),
|
|
@@ -126,7 +126,7 @@ function v() {
|
|
|
126
126
|
className: "flex justify-center",
|
|
127
127
|
children: /* @__PURE__ */ p("div", {
|
|
128
128
|
className: "p-4 bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)]",
|
|
129
|
-
children: /* @__PURE__ */ p(
|
|
129
|
+
children: /* @__PURE__ */ p(a, {
|
|
130
130
|
value: D.qrCode,
|
|
131
131
|
size: 192,
|
|
132
132
|
level: "M",
|