@hed-hog/core 0.0.185 → 0.0.190

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.
Files changed (55) hide show
  1. package/hedhog/frontend/app/account/2fa/page.tsx.ejs +5 -0
  2. package/hedhog/frontend/app/account/accounts/page.tsx.ejs +5 -0
  3. package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +356 -0
  4. package/hedhog/frontend/app/account/components/change-email-form.tsx.ejs +379 -0
  5. package/hedhog/frontend/app/account/components/change-password-form.tsx.ejs +184 -0
  6. package/hedhog/frontend/app/account/components/connected-accounts.tsx.ejs +144 -0
  7. package/hedhog/frontend/app/account/components/email-request-dialog.tsx.ejs +96 -0
  8. package/hedhog/frontend/app/account/components/mfa-add-buttons.tsx.ejs +43 -0
  9. package/hedhog/frontend/app/account/components/mfa-method-card.tsx.ejs +115 -0
  10. package/hedhog/frontend/app/account/components/mfa-setup-dialog.tsx.ejs +236 -0
  11. package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +209 -0
  12. package/hedhog/frontend/app/account/components/recovery-codes-dialog.tsx.ejs +192 -0
  13. package/hedhog/frontend/app/account/components/regenerate-codes-dialog.tsx.ejs +372 -0
  14. package/hedhog/frontend/app/account/components/remove-mfa-dialog.tsx.ejs +337 -0
  15. package/hedhog/frontend/app/account/components/two-factor-auth.tsx.ejs +393 -0
  16. package/hedhog/frontend/app/account/components/verify-before-add-dialog.tsx.ejs +332 -0
  17. package/hedhog/frontend/app/account/email/page.tsx.ejs +5 -0
  18. package/hedhog/frontend/app/account/hooks/use-mfa-methods.ts.ejs +27 -0
  19. package/hedhog/frontend/app/account/hooks/use-mfa-setup.ts.ejs +461 -0
  20. package/hedhog/frontend/app/account/layout.tsx.ejs +105 -0
  21. package/hedhog/frontend/app/account/lib/mfa-utils.tsx.ejs +37 -0
  22. package/hedhog/frontend/app/account/page.tsx.ejs +5 -0
  23. package/hedhog/frontend/app/account/password/page.tsx.ejs +5 -0
  24. package/hedhog/frontend/app/account/profile/page.tsx.ejs +5 -0
  25. package/hedhog/frontend/app/account/sessions/page.tsx.ejs +5 -0
  26. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +490 -0
  27. package/hedhog/frontend/app/configurations/[slug]/page.tsx.ejs +62 -0
  28. package/hedhog/frontend/app/configurations/layout.tsx.ejs +316 -0
  29. package/hedhog/frontend/app/configurations/page.tsx.ejs +35 -0
  30. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +351 -0
  31. package/hedhog/frontend/app/dashboard/[slug]/page.tsx.ejs +11 -0
  32. package/hedhog/frontend/app/dashboard/[slug]/types.ts.ejs +62 -0
  33. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +45 -0
  34. package/hedhog/frontend/app/dashboard/dashboard.css.ejs +196 -0
  35. package/hedhog/frontend/app/dashboard/management/page.tsx.ejs +63 -0
  36. package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +516 -0
  37. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +753 -0
  38. package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +516 -0
  39. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +489 -0
  40. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +621 -0
  41. package/hedhog/frontend/app/dashboard/page.tsx.ejs +14 -0
  42. package/hedhog/frontend/app/mail/log/page.tsx.ejs +312 -0
  43. package/hedhog/frontend/app/mail/template/page.tsx.ejs +1177 -0
  44. package/hedhog/frontend/app/preferences/page.tsx.ejs +448 -0
  45. package/hedhog/frontend/app/roles/menus.tsx.ejs +504 -0
  46. package/hedhog/frontend/app/roles/page.tsx.ejs +814 -0
  47. package/hedhog/frontend/app/roles/routes.tsx.ejs +397 -0
  48. package/hedhog/frontend/app/roles/users.tsx.ejs +306 -0
  49. package/hedhog/frontend/app/users/active-session.tsx.ejs +159 -0
  50. package/hedhog/frontend/app/users/identifiers.tsx.ejs +279 -0
  51. package/hedhog/frontend/app/users/page.tsx.ejs +1257 -0
  52. package/hedhog/frontend/app/users/permissions.tsx.ejs +155 -0
  53. package/hedhog/frontend/messages/en.json +1080 -0
  54. package/hedhog/frontend/messages/pt.json +1135 -0
  55. package/package.json +4 -4
@@ -0,0 +1,393 @@
1
+ 'use client';
2
+
3
+ import { Button } from '@/components/ui/button';
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from '@/components/ui/card';
11
+ import { UserMfaTypeEnum } from '@hed-hog/api-types/UserMfaTypeEnum';
12
+ import { useTranslations } from 'next-intl';
13
+ import { useState } from 'react';
14
+ import { useMfaMethods } from '../hooks/use-mfa-methods';
15
+ import { useMfaSetup } from '../hooks/use-mfa-setup';
16
+ import { EmailRequestDialog } from './email-request-dialog';
17
+ import { MfaAddButtons } from './mfa-add-buttons';
18
+ import { MfaMethodCard } from './mfa-method-card';
19
+ import { MfaSetupDialog } from './mfa-setup-dialog';
20
+ import { RecoveryCodesDialog } from './recovery-codes-dialog';
21
+ import { RegenerateCodesDialog } from './regenerate-codes-dialog';
22
+ import { RemoveMfaDialog } from './remove-mfa-dialog';
23
+ import { VerifyBeforeAddDialog } from './verify-before-add-dialog';
24
+
25
+ interface VerificationState {
26
+ verificationType?: 'totp' | 'email';
27
+ availableMethods?: ('totp' | 'email')[];
28
+ codeHash?: string;
29
+ hasWebAuthn: boolean;
30
+ hasRecoveryCodes: boolean;
31
+ }
32
+
33
+ interface RemoveMethodState extends VerificationState {
34
+ methodId: number | null;
35
+ methodType: UserMfaTypeEnum | null;
36
+ methodName: string;
37
+ }
38
+
39
+ const initialVerificationState: VerificationState = {
40
+ verificationType: undefined,
41
+ availableMethods: undefined,
42
+ codeHash: undefined,
43
+ hasWebAuthn: false,
44
+ hasRecoveryCodes: false,
45
+ };
46
+
47
+ const initialRemoveState: RemoveMethodState = {
48
+ ...initialVerificationState,
49
+ methodId: null,
50
+ methodType: null,
51
+ methodName: '',
52
+ };
53
+
54
+ export function TwoFactorAuth() {
55
+ const t = useTranslations('core.TwoFactorAuth');
56
+
57
+ // Setup states
58
+ const [showAddDialog, setShowAddDialog] = useState(false);
59
+ const [selectedMethod, setSelectedMethod] = useState<UserMfaTypeEnum | null>(
60
+ null
61
+ );
62
+ const [verificationCode, setVerificationCode] = useState('');
63
+ const [name, setName] = useState('');
64
+ const [email, setEmail] = useState('');
65
+ const [showEmailRequestDialog, setShowEmailRequestDialog] = useState(false);
66
+
67
+ // Recovery codes states
68
+ const [recoveryCodes, setRecoveryCodes] = useState<string[]>([]);
69
+ const [showCodesDialog, setShowCodesDialog] = useState(false);
70
+
71
+ // Remove method states
72
+ const [showRemoveDialog, setShowRemoveDialog] = useState(false);
73
+ const [removeState, setRemoveState] =
74
+ useState<RemoveMethodState>(initialRemoveState);
75
+
76
+ // Regenerate codes states
77
+ const [showRegenerateDialog, setShowRegenerateDialog] = useState(false);
78
+ const [regenerateState, setRegenerateState] = useState<VerificationState>(
79
+ initialVerificationState
80
+ );
81
+
82
+ // Verify before add states
83
+ const [showVerifyBeforeAddDialog, setShowVerifyBeforeAddDialog] =
84
+ useState(false);
85
+ const [verifyBeforeAddState, setVerifyBeforeAddState] =
86
+ useState<VerificationState>(initialVerificationState);
87
+ const [pendingMethodToAdd, setPendingMethodToAdd] =
88
+ useState<UserMfaTypeEnum | null>(null);
89
+
90
+ const { mfaMethods, refetchMfaMethods } = useMfaMethods();
91
+ const {
92
+ qrCode,
93
+ verifyEmailMFA,
94
+ verifyTOTPMFA,
95
+ removeMfaUnified,
96
+ updateMFAName,
97
+ initiateSetup,
98
+ regenerateRecoveryCodes,
99
+ checkMfaVerificationType,
100
+ checkIfMfaExists,
101
+ checkMfaBeforeRemove,
102
+ verifyBeforeAddMfa,
103
+ enableWebAuthnMFA,
104
+ resendEmailCode,
105
+ resendLoading,
106
+ resendCooldown,
107
+ } = useMfaSetup();
108
+
109
+ const handleAddMethod = async (type: UserMfaTypeEnum) => {
110
+ try {
111
+ const mfaCheck = await checkIfMfaExists();
112
+ if (mfaCheck.requiresVerification) {
113
+ setPendingMethodToAdd(type);
114
+ setVerifyBeforeAddState({
115
+ verificationType: mfaCheck.verificationType,
116
+ availableMethods: mfaCheck.availableMethods,
117
+ codeHash: mfaCheck.codeHash,
118
+ hasWebAuthn: mfaCheck.hasWebAuthn || false,
119
+ hasRecoveryCodes: mfaCheck.hasRecoveryCodes || false,
120
+ });
121
+ setShowVerifyBeforeAddDialog(true);
122
+ } else {
123
+ await proceedWithAddMethod(type);
124
+ }
125
+ } catch (error) {
126
+ console.error('Failed to check MFA:', error);
127
+ }
128
+ };
129
+
130
+ const proceedWithAddMethod = async (type: UserMfaTypeEnum) => {
131
+ if (type === UserMfaTypeEnum.EMAIL) {
132
+ setSelectedMethod(type);
133
+ setShowEmailRequestDialog(true);
134
+ setName('');
135
+ setEmail('');
136
+ } else {
137
+ if (type !== UserMfaTypeEnum.WEBAUTHN) {
138
+ await initiateSetup(type);
139
+ }
140
+ setSelectedMethod(type);
141
+ setShowAddDialog(true);
142
+ setName('');
143
+ setEmail('');
144
+ }
145
+ };
146
+
147
+ const handleVerifyBeforeAdd = async (
148
+ code: string,
149
+ hash?: string,
150
+ verificationType?: 'totp' | 'email' | 'recovery' | 'webauthn',
151
+ assertionResponse?: any
152
+ ) => {
153
+ try {
154
+ await verifyBeforeAddMfa(code, hash, verificationType, assertionResponse);
155
+ if (pendingMethodToAdd) {
156
+ await proceedWithAddMethod(pendingMethodToAdd);
157
+ setPendingMethodToAdd(null);
158
+ }
159
+ } catch (error) {
160
+ throw error;
161
+ }
162
+ };
163
+
164
+ const handleEmailSubmit = async (emailAddress: string) => {
165
+ setEmail(emailAddress);
166
+ await initiateSetup(UserMfaTypeEnum.EMAIL, emailAddress);
167
+ setShowEmailRequestDialog(false);
168
+ setShowAddDialog(true);
169
+ };
170
+
171
+ const handleVerifySetup = async () => {
172
+ let codes: string[] = [];
173
+
174
+ if (selectedMethod === UserMfaTypeEnum.EMAIL) {
175
+ codes = await verifyEmailMFA(name, verificationCode, email);
176
+ } else if (selectedMethod === UserMfaTypeEnum.TOTP) {
177
+ codes = await verifyTOTPMFA(name, verificationCode);
178
+ } else if (selectedMethod === UserMfaTypeEnum.WEBAUTHN) {
179
+ codes = await enableWebAuthnMFA(name);
180
+ }
181
+
182
+ setRecoveryCodes(codes);
183
+ setShowAddDialog(false);
184
+ setShowCodesDialog(true);
185
+ setVerificationCode('');
186
+ setSelectedMethod(null);
187
+ refetchMfaMethods();
188
+ };
189
+
190
+ const handleRemoveMethod = async (method: any) => {
191
+ setRemoveState(initialRemoveState);
192
+
193
+ try {
194
+ const result = await checkMfaBeforeRemove();
195
+ setRemoveState({
196
+ verificationType: result.verificationType,
197
+ availableMethods: result.availableMethods,
198
+ codeHash: result.codeHash,
199
+ hasWebAuthn: result.hasWebAuthn || false,
200
+ hasRecoveryCodes: false,
201
+ methodId: method.id,
202
+ methodType: method.type,
203
+ methodName: method.name || method.type,
204
+ });
205
+ setShowRemoveDialog(true);
206
+ } catch (error) {
207
+ console.error('Failed to check MFA before remove:', error);
208
+ }
209
+ };
210
+
211
+ const handleVerifyRemoval = async (
212
+ code: string,
213
+ hash?: string,
214
+ verificationType?: 'totp' | 'email' | 'recovery' | 'webauthn',
215
+ assertionResponse?: any
216
+ ) => {
217
+ if (removeState.methodId) {
218
+ try {
219
+ await removeMfaUnified(
220
+ removeState.methodId,
221
+ code,
222
+ hash,
223
+ verificationType,
224
+ assertionResponse
225
+ );
226
+
227
+ setShowRemoveDialog(false);
228
+ setRemoveState(initialRemoveState);
229
+ refetchMfaMethods();
230
+ } catch (error) {
231
+ console.error('Failed to remove MFA:', error);
232
+ throw error;
233
+ }
234
+ }
235
+ };
236
+
237
+ const handleCloseCodesDialog = () => {
238
+ setShowCodesDialog(false);
239
+ setRecoveryCodes([]);
240
+ };
241
+
242
+ const handleCloseRemoveDialog = (open: boolean) => {
243
+ if (!open) {
244
+ setRemoveState(initialRemoveState);
245
+ }
246
+ setShowRemoveDialog(open);
247
+ };
248
+
249
+ const handleOpenRegenerateDialog = async () => {
250
+ const result = await checkMfaVerificationType();
251
+ setRegenerateState({
252
+ verificationType: result.verificationType,
253
+ availableMethods: result.availableMethods,
254
+ codeHash: result.codeHash,
255
+ hasWebAuthn: result.hasWebAuthn || false,
256
+ hasRecoveryCodes: result.hasRecoveryCodes || false,
257
+ });
258
+ setShowRegenerateDialog(true);
259
+ };
260
+
261
+ const handleRegenerateCodes = async (
262
+ verificationCode: string,
263
+ hash?: string,
264
+ useTotp?: boolean,
265
+ verificationType?: 'totp' | 'email' | 'recovery' | 'webauthn',
266
+ assertionResponse?: any
267
+ ) => {
268
+ const finalVerificationType =
269
+ verificationType ||
270
+ (useTotp
271
+ ? 'totp'
272
+ : hash || regenerateState.codeHash
273
+ ? 'email'
274
+ : 'recovery');
275
+
276
+ const codes = await regenerateRecoveryCodes(
277
+ verificationCode || undefined,
278
+ hash || regenerateState.codeHash,
279
+ finalVerificationType,
280
+ assertionResponse
281
+ );
282
+
283
+ setRecoveryCodes(codes);
284
+ setShowRegenerateDialog(false);
285
+ setShowCodesDialog(true);
286
+ };
287
+
288
+ return (
289
+ <>
290
+ <Card>
291
+ <CardHeader className="pb-4">
292
+ <CardTitle>{t('title')}</CardTitle>
293
+ <CardDescription>{t('description')}</CardDescription>
294
+ </CardHeader>
295
+ <CardContent className="space-y-4 px-6 pb-6">
296
+ {mfaMethods.length > 0 ? (
297
+ <div className="space-y-4">
298
+ {mfaMethods.map((method) => (
299
+ <MfaMethodCard
300
+ key={method.id}
301
+ method={method}
302
+ onRemove={handleRemoveMethod}
303
+ onUpdate={updateMFAName}
304
+ refetch={refetchMfaMethods}
305
+ />
306
+ ))}
307
+ </div>
308
+ ) : (
309
+ <div className="text-center text-sm text-muted-foreground py-8">
310
+ {t('noMethodsConfigured')}
311
+ </div>
312
+ )}
313
+
314
+ <MfaAddButtons onAddMethod={handleAddMethod} />
315
+
316
+ {mfaMethods.length > 0 && (
317
+ <div className="pt-4 border-t">
318
+ <Button
319
+ variant="outline"
320
+ className="w-full"
321
+ onClick={handleOpenRegenerateDialog}
322
+ >
323
+ {t('regenerateRecoveryCodes')}
324
+ </Button>
325
+ </div>
326
+ )}
327
+ </CardContent>
328
+ </Card>
329
+
330
+ <MfaSetupDialog
331
+ open={showAddDialog}
332
+ selectedMethod={selectedMethod}
333
+ qrCode={qrCode}
334
+ verificationCode={verificationCode}
335
+ onOpenChange={setShowAddDialog}
336
+ name={name}
337
+ onNameChange={setName}
338
+ onVerificationCodeChange={setVerificationCode}
339
+ onVerify={handleVerifySetup}
340
+ resendLoading={resendLoading}
341
+ resendCooldown={resendCooldown}
342
+ onResendCode={resendEmailCode}
343
+ />
344
+
345
+ <EmailRequestDialog
346
+ open={showEmailRequestDialog}
347
+ onOpenChange={setShowEmailRequestDialog}
348
+ onSubmit={handleEmailSubmit}
349
+ />
350
+
351
+ <RecoveryCodesDialog
352
+ open={showCodesDialog}
353
+ codes={recoveryCodes}
354
+ onOpenChange={setShowCodesDialog}
355
+ onConfirm={handleCloseCodesDialog}
356
+ />
357
+
358
+ <RemoveMfaDialog
359
+ open={showRemoveDialog}
360
+ onOpenChange={handleCloseRemoveDialog}
361
+ onConfirm={handleVerifyRemoval}
362
+ verificationType={removeState.verificationType}
363
+ availableMethods={removeState.availableMethods}
364
+ codeHash={removeState.codeHash}
365
+ hasWebAuthn={removeState.hasWebAuthn}
366
+ methodName={removeState.methodName}
367
+ />
368
+
369
+ <RegenerateCodesDialog
370
+ open={showRegenerateDialog}
371
+ onOpenChange={setShowRegenerateDialog}
372
+ onConfirm={handleRegenerateCodes}
373
+ requiresMfa={mfaMethods.length > 0}
374
+ verificationType={regenerateState.verificationType}
375
+ availableMethods={regenerateState.availableMethods}
376
+ codeHash={regenerateState.codeHash}
377
+ hasWebAuthn={regenerateState.hasWebAuthn}
378
+ hasRecoveryCodes={regenerateState.hasRecoveryCodes}
379
+ />
380
+
381
+ <VerifyBeforeAddDialog
382
+ open={showVerifyBeforeAddDialog}
383
+ onOpenChange={setShowVerifyBeforeAddDialog}
384
+ onVerify={handleVerifyBeforeAdd}
385
+ verificationType={verifyBeforeAddState.verificationType}
386
+ availableMethods={verifyBeforeAddState.availableMethods}
387
+ codeHash={verifyBeforeAddState.codeHash}
388
+ hasWebAuthn={verifyBeforeAddState.hasWebAuthn}
389
+ hasRecoveryCodes={verifyBeforeAddState.hasRecoveryCodes}
390
+ />
391
+ </>
392
+ );
393
+ }