@micha.bigler/ui-core-micha 1.4.13 → 1.4.16

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.
@@ -76,6 +76,6 @@ const MfaLoginComponent = ({ availableTypes, identifier, onSuccess, onCancel })
76
76
  setSubmitting(false);
77
77
  }
78
78
  };
79
- return (_jsxs(Box, { children: [_jsx(Typography, { variant: "body2", sx: { mb: 2 }, children: t('Auth.MFA_SUBTITLE', 'Please confirm your login using one of the available methods.') }), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), infoKey && (_jsx(Alert, { severity: "info", sx: { mb: 2 }, children: t(infoKey) })), _jsxs(Stack, { spacing: 2, children: [supportsWebauthn && (_jsx(Button, { variant: "contained", fullWidth: true, onClick: handlePasskey, disabled: submitting || helpRequested, children: t('Auth.LOGIN_USE_PASSKEY_BUTTON', 'Use passkey / security key') })), _jsx(Divider, { sx: { my: 2 }, children: t('Auth.LOGIN_OR') }), supportsTotpOrRecovery && (_jsxs(Box, { component: "form", onSubmit: handleSubmitCode, children: [_jsx(TextField, { label: t('Auth.MFA_CODE_LABEL', 'Authenticator code (or recovery code)'), value: code, onChange: (e) => setCode(e.target.value), fullWidth: true, disabled: submitting || helpRequested, autoComplete: "one-time-code", sx: { mb: 2 } }), _jsx(Button, { type: "submit", variant: "contained", fullWidth: true, disabled: submitting || !code.trim() || helpRequested, children: t('Auth.MFA_VERIFY', 'Verify') })] })), _jsx(Divider, { sx: { my: 2 }, children: t('Auth.LOGIN_OR') }), _jsxs(Stack, { direction: "row", spacing: 1, sx: { mt: 1 }, children: [_jsx(Button, { fullWidth: true, size: "small", variant: "outlined", onClick: onCancel, disabled: submitting, children: t('Auth.MFA_BACK_TO_LOGIN', 'Back to login') }), _jsx(Button, { fullWidth: true, size: "small", variant: "outlined", color: "secondary", onClick: openHelpDialog, disabled: submitting || helpRequested, children: t('Auth.MFA_NEED_HELP', "I can't use any of these methods") })] })] }), _jsxs(Dialog, { open: helpDialogOpen, onClose: () => setHelpDialogOpen(false), fullWidth: true, maxWidth: "sm", children: [_jsx(DialogTitle, { children: t('Auth.MFA_HELP_DIALOG_TITLE', 'Need help with sign-in') }), _jsxs(DialogContent, { dividers: true, children: [_jsx(Typography, { variant: "body2", sx: { mb: 2 }, children: t('Auth.MFA_HELP_DIALOG_DESCRIPTION', 'Describe briefly why you cannot use the available methods. A support person will review your request.') }), _jsx(TextField, { label: t('Auth.MFA_HELP_MESSAGE_LABEL', 'Your message to support'), helperText: t('Auth.MFA_HELP_MESSAGE_HELP', 'Optional, but helps support verify your identity.'), multiline: true, minRows: 3, fullWidth: true, value: helpMessage, onChange: (e) => setHelpMessage(e.target.value), disabled: submitting })] }), _jsxs(DialogActions, { children: [_jsx(Button, { onClick: () => setHelpDialogOpen(false), disabled: submitting, children: t('Common.CANCEL', 'Cancel') }), _jsx(Button, { onClick: handleNeedHelp, disabled: submitting, variant: "contained", children: t('Auth.MFA_HELP_SUBMIT', 'Send request') })] })] })] }));
79
+ return (_jsxs(Box, { children: [_jsx(Typography, { variant: "body2", sx: { mb: 2 }, children: t('Auth.MFA_SUBTITLE', 'Please confirm your login using one of the available methods.') }), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), infoKey && (_jsx(Alert, { severity: "info", sx: { mb: 2 }, children: t(infoKey) })), _jsxs(Stack, { spacing: 2, children: [supportsWebauthn && (_jsx(Button, { variant: "contained", fullWidth: true, onClick: handlePasskey, disabled: submitting || helpRequested, children: t('Auth.LOGIN_USE_PASSKEY_BUTTON', 'Use passkey / security key') })), _jsx(Divider, { sx: { my: 2 }, children: t('Auth.LOGIN_OR') }), supportsTotpOrRecovery && (_jsxs(Box, { component: "form", onSubmit: handleSubmitCode, children: [_jsx(TextField, { label: t('Auth.MFA_CODE_LABEL', 'Authenticator code (or recovery code)'), value: code, onChange: (e) => setCode(e.target.value), fullWidth: true, disabled: submitting || helpRequested, autoComplete: "one-time-code", sx: { mb: 2 } }), _jsx(Button, { type: "submit", variant: "contained", fullWidth: true, disabled: submitting || !code.trim() || helpRequested, children: t('Auth.MFA_VERIFY', 'Verify') })] })), _jsx(Divider, { sx: { my: 2 }, children: t('Auth.LOGIN_OR') }), _jsxs(Stack, { direction: "row", spacing: 1, sx: { mt: 1 }, children: [_jsx(Button, { fullWidth: true, size: "small", variant: "outlined", onClick: onCancel, disabled: submitting, children: t('Auth.MFA_BACK_TO_LOGIN', 'Back to login') }), _jsx(Button, { fullWidth: true, size: "small", variant: "outlined", color: "secondary", onClick: openHelpDialog, disabled: submitting || helpRequested, children: t('Auth.MFA_NEED_HELP', "I can't use any of these methods") })] })] }), _jsxs(Dialog, { open: helpDialogOpen, onClose: () => setHelpDialogOpen(false), fullWidth: true, maxWidth: "sm", children: [_jsx(DialogTitle, { children: t('Auth.MFA_HELP_DIALOG_TITLE', 'Need help with sign-in') }), _jsxs(DialogContent, { dividers: true, children: [_jsx(Typography, { variant: "body2", sx: { mb: 2 }, children: t('Auth.MFA_HELP_DIALOG_DESCRIPTION', 'Describe briefly why you cannot use the available methods. A support person will review your request.') }), _jsx(TextField, { label: t('Auth.MFA_HELP_MESSAGE_LABEL', 'Your message to support'), multiline: true, minRows: 3, fullWidth: true, value: helpMessage, onChange: (e) => setHelpMessage(e.target.value), disabled: submitting })] }), _jsxs(DialogActions, { children: [_jsx(Button, { onClick: () => setHelpDialogOpen(false), disabled: submitting, children: t('Common.CANCEL', 'Cancel') }), _jsx(Button, { onClick: handleNeedHelp, disabled: submitting, variant: "contained", children: t('Auth.MFA_HELP_SUBMIT', 'Send request') })] })] })] }));
80
80
  };
81
81
  export default MfaLoginComponent;
@@ -36,7 +36,7 @@ showName = true, showPrivacy = true, showCookies = true, }) {
36
36
  setErrorKey(null);
37
37
  setSuccessKey(null);
38
38
  try {
39
- const res = await axios.get(`${USERS_BASE}/current/`, {
39
+ const res = await apiClient.get(`${USERS_BASE}/current/`, {
40
40
  withCredentials: true,
41
41
  });
42
42
  if (!mounted)
@@ -29,12 +29,12 @@ const SecurityComponent = ({ fromRecovery = false, fromWeakLogin = false, // opt
29
29
  setErrorKey(null);
30
30
  try {
31
31
  await authApi.changePassword(currentPassword, newPassword);
32
- setMessageKey('Auth.PASSWORD_CHANGE_SUCCESS');
32
+ setMessageKey('Auth.RESET_PASSWORD_SUCCESS');
33
33
  }
34
34
  catch (err) {
35
35
  setErrorKey(err.code || 'Auth.PASSWORD_CHANGE_FAILED');
36
36
  }
37
37
  };
38
- return (_jsxs(Box, { children: [fromRecovery && (_jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: t('Security.RECOVERY_LOGIN_WARNING') })), fromWeakLogin && (_jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: t('Security.WEAK_LOGIN_WARNING') })), messageKey && (_jsx(Alert, { severity: "success", sx: { mb: 2 }, children: t(messageKey) })), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), _jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Security.PASSWORD_SECTION_TITLE') }), _jsx(PasswordChangeForm, { onSubmit: handlePasswordChange }), _jsx(Divider, { sx: { my: 3 } }), _jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Security.SOCIAL_SECTION_TITLE') }), _jsx(Typography, { variant: "body2", sx: { mb: 1 }, children: t('Security.SOCIAL_SECTION_DESCRIPTION') }), _jsx(SocialLoginButtons, { onProviderClick: handleSocialClick }), _jsx(Divider, { sx: { my: 3 } }), _jsx(PasskeysComponent, {}), _jsx(Divider, { sx: { my: 3 } }), _jsx(MFAComponent, {})] }));
38
+ return (_jsxs(Box, { children: [fromRecovery && (_jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: t('Security.RECOVERY_LOGIN_WARNING') })), fromWeakLogin && (_jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: t('Security.WEAK_LOGIN_WARNING') })), messageKey && (_jsx(Alert, { severity: "success", sx: { mb: 2 }, children: t(messageKey) })), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), _jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Security.LOGIN_PASSWORD_LABEL') }), _jsx(PasswordChangeForm, { onSubmit: handlePasswordChange }), _jsx(Divider, { sx: { my: 3 } }), _jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Security.SOCIAL_SECTION_TITLE') }), _jsx(Typography, { variant: "body2", sx: { mb: 1 }, children: t('Security.SOCIAL_SECTION_DESCRIPTION') }), _jsx(SocialLoginButtons, { onProviderClick: handleSocialClick }), _jsx(Divider, { sx: { my: 3 } }), _jsx(PasskeysComponent, {}), _jsx(Divider, { sx: { my: 3 } }), _jsx(MFAComponent, {})] }));
39
39
  };
40
40
  export default SecurityComponent;
@@ -70,9 +70,9 @@ const SupportRecoveryRequestsTab = () => {
70
70
  if (loading) {
71
71
  return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', mt: 4 }, children: _jsx(CircularProgress, {}) }));
72
72
  }
73
- return (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Support.RECOVERY_REQUESTS_TITLE', 'Account recovery requests') }), _jsx(Typography, { variant: "body2", sx: { mb: 2 }, children: t('Support.RECOVERY_REQUESTS_DESCRIPTION', 'Users who cannot complete MFA can request support. You can review their request and approve or reject it.') }), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), _jsxs(Stack, { direction: "row", spacing: 2, sx: { mb: 2 }, children: [_jsx(Button, { variant: statusFilter === 'pending' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('pending'), children: t('Support.RECOVERY_FILTER_PENDING', 'Open') }), _jsx(Button, { variant: statusFilter === 'approved' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('approved'), children: t('Support.RECOVERY_FILTER_APPROVED', 'Approved') }), _jsx(Button, { variant: statusFilter === 'rejected' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('rejected'), children: t('Support.RECOVERY_FILTER_REJECTED', 'Rejected') })] }), requests.length === 0 ? (_jsx(Typography, { variant: "body2", children: t('Support.RECOVERY_REQUESTS_EMPTY', 'No recovery requests for this filter.') })) : (_jsx(TableContainer, { component: Paper, children: _jsxs(Table, { size: "small", children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: t('Support.RECOVERY_COL_CREATED', 'Created') }), _jsx(TableCell, { children: t('Support.RECOVERY_COL_USER', 'User') }), _jsx(TableCell, { children: t('Support.RECOVERY_COL_STATUS', 'Status') }), _jsx(TableCell, { children: t('Support.RECOVERY_COL_ACTIONS', 'Actions') })] }) }), _jsx(TableBody, { children: requests.map((req) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: req.created_at
73
+ return (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Support.RECOVERY_REQUESTS_TITLE', 'Account recovery requests') }), _jsx(Typography, { variant: "body2", sx: { mb: 2 }, children: t('Support.RECOVERY_REQUESTS_DESCRIPTION', 'Users who cannot complete MFA can request support. You can review their request and approve or reject it.') }), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), _jsxs(Stack, { direction: "row", spacing: 2, sx: { mb: 2 }, children: [_jsx(Button, { variant: statusFilter === 'pending' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('pending'), children: t('Support.RECOVERY_FILTER_PENDING', 'Open') }), _jsx(Button, { variant: statusFilter === 'approved' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('approved'), children: t('Support.RECOVERY_FILTER_APPROVED', 'Approved') }), _jsx(Button, { variant: statusFilter === 'rejected' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('rejected'), children: t('Support.RECOVERY_FILTER_REJECTED', 'Rejected') })] }), requests.length === 0 ? (_jsx(Typography, { variant: "body2", children: t('Support.RECOVERY_REQUESTS_EMPTY', 'No recovery requests for this filter.') })) : (_jsx(TableContainer, { component: Paper, children: _jsxs(Table, { size: "small", children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: t('Support.RECOVERY_COL_CREATED', 'Created') }), _jsx(TableCell, { children: t('Support.RECOVERY_USER', 'User') }), _jsx(TableCell, { children: t('Support.RECOVERY_COL_STATUS', 'Status') }), _jsx(TableCell, { children: t('Support.RECOVERY_COL_ACTIONS', 'Actions') })] }) }), _jsx(TableBody, { children: requests.map((req) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: req.created_at
74
74
  ? new Date(req.created_at).toLocaleString()
75
- : '-' }), _jsx(TableCell, { children: req.user_email || req.user }), _jsx(TableCell, { children: req.status }), _jsx(TableCell, { children: _jsx(Button, { variant: "contained", size: "small", onClick: () => openDialog(req), disabled: req.status !== 'pending', children: t('Support.RECOVERY_ACTION_REVIEW', 'Review') }) })] }, req.id))) })] }) })), _jsxs(Dialog, { open: dialogOpen, onClose: closeDialog, fullWidth: true, maxWidth: "sm", children: [_jsx(DialogTitle, { children: t('Support.RECOVERY_REVIEW_DIALOG_TITLE', 'Review recovery request') }), _jsxs(DialogContent, { dividers: true, children: [selectedRequest && (_jsxs(Box, { sx: { mb: 2 }, children: [_jsxs(Typography, { variant: "body2", sx: { mb: 1 }, children: [_jsxs("strong", { children: [t('Support.RECOVERY_REVIEW_USER', 'User'), ":"] }), ' ', selectedRequest.user_email || selectedRequest.user] }), _jsxs(Typography, { variant: "body2", sx: { mb: 1 }, children: [_jsxs("strong", { children: [t('Support.RECOVERY_REVIEW_CREATED', 'Requested at'), ":"] }), ' ', selectedRequest.created_at
75
+ : '-' }), _jsx(TableCell, { children: req.user_email || req.user }), _jsx(TableCell, { children: req.status }), _jsx(TableCell, { children: _jsx(Button, { variant: "contained", size: "small", onClick: () => openDialog(req), disabled: req.status !== 'pending', children: t('Support.RECOVERY_ACTION_REVIEW', 'Review') }) })] }, req.id))) })] }) })), _jsxs(Dialog, { open: dialogOpen, onClose: closeDialog, fullWidth: true, maxWidth: "sm", children: [_jsx(DialogTitle, { children: t('Support.RECOVERY_REVIEW_DIALOG_TITLE', 'Review recovery request') }), _jsxs(DialogContent, { dividers: true, children: [selectedRequest && (_jsxs(Box, { sx: { mb: 2 }, children: [_jsxs(Typography, { variant: "body2", sx: { mb: 1 }, children: [_jsxs("strong", { children: [t('Support.RECOVERY_USER', 'User'), ":"] }), ' ', selectedRequest.user_email || selectedRequest.user] }), _jsxs(Typography, { variant: "body2", sx: { mb: 1 }, children: [_jsxs("strong", { children: [t('Support.RECOVERY_REVIEW_CREATED', 'Requested at'), ":"] }), ' ', selectedRequest.created_at
76
76
  ? new Date(selectedRequest.created_at).toLocaleString()
77
77
  : '-'] }), _jsx(Typography, { variant: "body2", sx: { mb: 1 }, children: _jsxs("strong", { children: [t('Support.RECOVERY_REVIEW_MESSAGE', 'User’s explanation'), ":"] }) }), _jsx(Box, { sx: {
78
78
  border: 1,
@@ -84,4 +84,4 @@ const SupportRecoveryRequestsTab = () => {
84
84
  fontSize: '0.875rem',
85
85
  }, children: selectedRequest.message || t('Support.RECOVERY_NO_MESSAGE', 'No message provided.') })] })), _jsx(TextField, { label: t('Support.RECOVERY_NOTE_LABEL', 'Reason for your decision'), helperText: t('Support.RECOVERY_NOTE_HELP', 'Explain briefly why you approve or reject this request.'), multiline: true, minRows: 3, fullWidth: true, value: dialogNote, onChange: (e) => setDialogNote(e.target.value) })] }), _jsxs(DialogActions, { children: [_jsx(Button, { onClick: closeDialog, children: t('Common.CANCEL', 'Cancel') }), _jsx(Button, { onClick: handleReject, color: "error", disabled: !dialogNote.trim(), children: t('Support.RECOVERY_REJECT_SUBMIT', 'Reject request') }), _jsx(Button, { onClick: handleApprove, variant: "contained", disabled: !dialogNote.trim(), children: t('Support.RECOVERY_APPROVE_SUBMIT', 'Approve and send link') })] })] })] }));
86
86
  };
87
- export default SupportRecove;
87
+ export default SupportRecoveryRequestsTab;
@@ -1,16 +1,6 @@
1
1
  // ui-core-micha/src/i18n/authTranslations.js
2
2
  // Gleiche Struktur wie deine app-eigene translations.json
3
3
  export const authTranslations = {
4
- "Auth.Login.Title": {
5
- de: "Anmelden",
6
- fr: "Connexion",
7
- en: "Login",
8
- },
9
- "Auth.Mfa.Required": {
10
- de: "Zwei-Faktor-Authentifizierung erforderlich.",
11
- fr: "Authentification à deux facteurs requise.",
12
- en: "Two-factor authentication required.",
13
- },
14
4
  "Auth.INVALID_CREDENTIALS": {
15
5
  "de": "E-Mail-Adresse oder Passwort ist falsch.",
16
6
  "fr": "Adresse e-mail ou mot de passe incorrect.",
@@ -201,16 +191,6 @@ export const authTranslations = {
201
191
  "fr": "Les codes de récupération n'ont pas pu être regénérés.",
202
192
  "en": "Recovery codes could not be regenerated."
203
193
  },
204
- "Auth.PASSWORD_CHANGE_SUCCESS": {
205
- "de": "Das Passwort wurde erfolgreich geändert.",
206
- "fr": "Le mot de passe a été modifié avec succès.",
207
- "en": "Password changed successfully."
208
- },
209
- "Auth.PAGE_CHANGE_PASSWORD_TITLE": {
210
- "de": "Passwort ändern",
211
- "fr": "Modifier le mot de passe",
212
- "en": "Change password"
213
- },
214
194
  "Auth.PAGE_CHANGE_PASSWORD_SUBTITLE": {
215
195
  "de": "Geben Sie Ihr aktuelles und ein neues Passwort ein.",
216
196
  "fr": "Saisissez votre mot de passe actuel et un nouveau mot de passe.",
@@ -256,11 +236,6 @@ export const authTranslations = {
256
236
  "fr": "Le mot de passe a été modifié. Vous pouvez maintenant vous connecter.",
257
237
  "en": "Password changed successfully. You can now log in."
258
238
  },
259
- "Auth.PAGE_RESET_REQUEST_TITLE": {
260
- "de": "Passwort zurücksetzen",
261
- "fr": "Réinitialiser le mot de passe",
262
- "en": "Reset password"
263
- },
264
239
  "Auth.PAGE_RESET_REQUEST_SUBTITLE": {
265
240
  "de": "Geben Sie Ihre E-Mail-Adresse ein, um den Link zum Zurücksetzen zu erhalten.",
266
241
  "fr": "Saisissez votre adresse e-mail pour recevoir le lien de réinitialisation.",
@@ -271,11 +246,6 @@ export const authTranslations = {
271
246
  "fr": "Veuillez saisir une adresse e-mail.",
272
247
  "en": "Please enter an email address."
273
248
  },
274
- "Auth.PAGE_SIGNUP_TITLE": {
275
- "de": "Registrieren",
276
- "fr": "Inscription",
277
- "en": "Sign up"
278
- },
279
249
  "Auth.PAGE_SIGNUP_SUBTITLE": {
280
250
  "de": "Geben Sie Ihre E-Mail-Adresse und den Zugangscode ein, um eine Einladung anzufordern.",
281
251
  "fr": "Saisissez votre adresse e-mail et le code d'accès pour demander une invitation.",
@@ -286,11 +256,6 @@ export const authTranslations = {
286
256
  "fr": "Adresse e-mail",
287
257
  "en": "Email address"
288
258
  },
289
- "Auth.SIGNUP_ACCESS_CODE_LABEL": {
290
- "de": "Zugangscode",
291
- "fr": "Code d'accès",
292
- "en": "Access code"
293
- },
294
259
  "Auth.SIGNUP_SUBMIT": {
295
260
  "de": "Einladung anfordern",
296
261
  "fr": "Demander une invitation",
@@ -351,11 +316,6 @@ export const authTranslations = {
351
316
  "fr": "Longueur : {{length}}",
352
317
  "en": "Length: {{length}}"
353
318
  },
354
- "Auth.ACCESS_CODE_SAVING": {
355
- "de": "Wird gespeichert…",
356
- "fr": "Enregistrement…",
357
- "en": "Saving…"
358
- },
359
319
  "Auth.ACCESS_CODE_GENERATE_BUTTON": {
360
320
  "de": "Code generieren",
361
321
  "fr": "Générer un code",
@@ -376,11 +336,6 @@ export const authTranslations = {
376
336
  "fr": "Ajouter",
377
337
  "en": "Add"
378
338
  },
379
- "Auth.ACCESS_CODE_REQUIRED_ADMIN": {
380
- "de": "Bitte geben Sie einen Zugangscode ein.",
381
- "fr": "Veuillez saisir un code d'accès.",
382
- "en": "Please enter an access code."
383
- },
384
339
  "Auth.ACCESS_CODE_LIST_FAILED": {
385
340
  "de": "Zugangscodes konnten nicht geladen werden.",
386
341
  "fr": "Impossible de charger les codes d'accès.",
@@ -411,11 +366,6 @@ export const authTranslations = {
411
366
  "fr": "Mot de passe",
412
367
  "en": "Password"
413
368
  },
414
- "Auth.LOGIN_SUBMIT": {
415
- "de": "Anmelden",
416
- "fr": "Connexion",
417
- "en": "Log in"
418
- },
419
369
  "Auth.LOGIN_SIGNUP_BUTTON": {
420
370
  "de": "Registrieren",
421
371
  "fr": "Créer un compte",
@@ -741,11 +691,6 @@ export const authTranslations = {
741
691
  "fr": "Vous vous êtes connecté avec un code de récupération. Veuillez vérifier vos paramètres de sécurité, configurer une clé d’accès ou une application d’authentification et générer de nouveaux codes de récupération.",
742
692
  "en": "You have signed in using a recovery code. Please review your security settings, set up a passkey or authenticator app, and generate new recovery codes."
743
693
  },
744
- "Security.PASSWORD_SECTION_TITLE": {
745
- "de": "Passwort",
746
- "fr": "Mot de passe",
747
- "en": "Password"
748
- },
749
694
  "Security.SOCIAL_SECTION_TITLE": {
750
695
  "de": "Soziale Logins",
751
696
  "fr": "Connexions sociales",
@@ -771,11 +716,6 @@ export const authTranslations = {
771
716
  "fr": "Veuillez confirmer votre connexion à l’aide d’une des méthodes disponibles.",
772
717
  "en": "Please confirm your login using one of the available methods."
773
718
  },
774
- "Auth.MFA_SUBTITLE_SHORT": {
775
- "de": "Bitte bestätige deine Anmeldung mit einer der verfügbaren Methoden.",
776
- "fr": "Veuillez confirmer votre connexion à l’aide d’une des méthodes disponibles.",
777
- "en": "Please confirm your login using one of the available methods."
778
- },
779
719
  "Auth.MFA_USE_PASSKEY": {
780
720
  "de": "Passkey / Sicherheitsschlüssel verwenden",
781
721
  "fr": "Utiliser une clé d’accès / clé de sécurité",
@@ -821,16 +761,6 @@ export const authTranslations = {
821
761
  "fr": "Récupération de compte",
822
762
  "en": "Account recovery"
823
763
  },
824
- "Auth.MFA_IDENTIFIER_REQUIRED": {
825
- "de": "E-Mail-Adresse oder Kennung ist erforderlich.",
826
- "fr": "L’adresse e-mail ou l’identifiant est requis.",
827
- "en": "Email address or identifier is required."
828
- },
829
- "Auth.MFA_HELP_REQUESTED": {
830
- "de": "Falls dieses Konto existiert, wurde Ihre Anfrage dem Support weitergeleitet. Bitte kontaktiere den Support für weitere Hilfe.",
831
- "fr": "Si ce compte existe, votre demande a été transmise au support. Veuillez contacter le support pour obtenir de l’aide supplémentaire.",
832
- "en": "If this account exists, your request has been forwarded to support. Please contact support for further assistance."
833
- },
834
764
  "Support.RECOVERY_APPROVE_DIALOG_TITLE": {
835
765
  "de": "Kontowiederherstellung bestätigen",
836
766
  "fr": "Confirmer la récupération du compte",
@@ -851,11 +781,6 @@ export const authTranslations = {
851
781
  "fr": "Décrivez brièvement pourquoi vous approuvez cette demande.",
852
782
  "en": "Briefly describe why you are approving this request."
853
783
  },
854
- "Support.RECOVERY_APPROVE_SUBMIT": {
855
- "de": "Link senden",
856
- "fr": "Envoyer le lien",
857
- "en": "Send link"
858
- },
859
784
  "Auth.MFA_HELP_DIALOG_TITLE": {
860
785
  "de": "Hilfe bei der Anmeldung",
861
786
  "fr": "Aide pour la connexion",
@@ -871,11 +796,6 @@ export const authTranslations = {
871
796
  "fr": "Votre message au support",
872
797
  "en": "Your message to support"
873
798
  },
874
- "Auth.MFA_HELP_MESSAGE_HELP": {
875
- "de": "Optional, hilft dem Support bei der Überprüfung Ihrer Identität.",
876
- "fr": "Facultatif, mais aide le support à vérifier votre identité.",
877
- "en": "Optional, but helps support verify your identity."
878
- },
879
799
  "Auth.MFA_HELP_SUBMIT": {
880
800
  "de": "Anfrage senden",
881
801
  "fr": "Envoyer la demande",
@@ -896,11 +816,6 @@ export const authTranslations = {
896
816
  "fr": "Refuser la récupération de compte",
897
817
  "en": "Reject account recovery"
898
818
  },
899
- "Support.RECOVERY_REJECT_CONFIRM_QUESTION": {
900
- "de": "Sind Sie sicher, dass Sie diese Wiederherstellungsanfrage ablehnen möchten?",
901
- "fr": "Êtes-vous sûr de vouloir refuser cette demande de récupération ?",
902
- "en": "Are you sure you want to reject this recovery request?"
903
- },
904
819
  "Support.RECOVERY_REJECT_SUBMIT": {
905
820
  "de": "Anfrage ablehnen",
906
821
  "fr": "Refuser la demande",
@@ -946,7 +861,7 @@ export const authTranslations = {
946
861
  "fr": "Créée le",
947
862
  "en": "Created"
948
863
  },
949
- "Support.RECOVERY_COL_USER": {
864
+ "Support.RECOVERY_USER": {
950
865
  "de": "Benutzer",
951
866
  "fr": "Utilisateur",
952
867
  "en": "User"
@@ -971,11 +886,6 @@ export const authTranslations = {
971
886
  "fr": "Examiner la demande de récupération de compte",
972
887
  "en": "Review recovery request"
973
888
  },
974
- "Support.RECOVERY_REVIEW_USER": {
975
- "de": "Benutzer",
976
- "fr": "Utilisateur",
977
- "en": "User"
978
- },
979
889
  "Support.RECOVERY_REVIEW_CREATED": {
980
890
  "de": "Angefragt am",
981
891
  "fr": "Demandée le",
@@ -991,21 +901,6 @@ export const authTranslations = {
991
901
  "fr": "Aucune explication fournie.",
992
902
  "en": "No message provided."
993
903
  },
994
- "Support.RECOVERY_NOTE_LABEL": {
995
- "de": "Begründung für Ihre Entscheidung",
996
- "fr": "Raison de votre décision",
997
- "en": "Reason for your decision"
998
- },
999
- "Support.RECOVERY_NOTE_HELP": {
1000
- "de": "Beschreiben Sie kurz, warum Sie diese Anfrage akzeptieren oder ablehnen.",
1001
- "fr": "Décrivez brièvement pourquoi vous acceptez ou refusez cette demande.",
1002
- "en": "Briefly explain why you approve or reject this request."
1003
- },
1004
- "Support.RECOVERY_REJECT_SUBMIT": {
1005
- "de": "Anfrage ablehnen",
1006
- "fr": "Refuser la demande",
1007
- "en": "Reject request"
1008
- },
1009
904
  "Support.RECOVERY_APPROVE_SUBMIT": {
1010
905
  "de": "Akzeptieren und Link senden",
1011
906
  "fr": "Accepter et envoyer le lien",
@@ -1021,31 +916,6 @@ export const authTranslations = {
1021
916
  "fr": "La demande n’a pas pu être acceptée.",
1022
917
  "en": "Failed to approve the recovery request."
1023
918
  },
1024
- "Auth.MFA_HELP_DIALOG_TITLE": {
1025
- "de": "Hilfe bei der Anmeldung",
1026
- "fr": "Aide pour la connexion",
1027
- "en": "Help with sign-in"
1028
- },
1029
- "Auth.MFA_HELP_DIALOG_DESCRIPTION": {
1030
- "de": "Beschreiben Sie kurz, warum Sie die verfügbaren Methoden nicht nutzen können. Eine Support-Person wird Ihre Anfrage prüfen.",
1031
- "fr": "Décrivez brièvement pourquoi vous ne pouvez pas utiliser les méthodes disponibles. Une personne du support examinera votre demande.",
1032
- "en": "Briefly describe why you cannot use the available methods. A support person will review your request."
1033
- },
1034
- "Auth.MFA_HELP_MESSAGE_LABEL": {
1035
- "de": "Ihre Nachricht an den Support",
1036
- "fr": "Votre message au support",
1037
- "en": "Your message to support"
1038
- },
1039
- "Auth.MFA_HELP_MESSAGE_HELP": {
1040
- "de": "Optional, hilft dem Support aber bei der Überprüfung Ihrer Identität.",
1041
- "fr": "Optionnel, mais aide le support à vérifier votre identité.",
1042
- "en": "Optional, but helps support verify your identity."
1043
- },
1044
- "Auth.MFA_HELP_SUBMIT": {
1045
- "de": "Anfrage senden",
1046
- "fr": "Envoyer la demande",
1047
- "en": "Send request"
1048
- },
1049
919
  "Auth.MFA_IDENTIFIER_REQUIRED": {
1050
920
  "de": "Bitte geben Sie eine E-Mail-Adresse an.",
1051
921
  "fr": "Veuillez indiquer une adresse e-mail.",
@@ -1056,10 +926,4 @@ export const authTranslations = {
1056
926
  "fr": "Si un compte existe avec cette adresse e-mail, votre demande a été transmise au support.",
1057
927
  "en": "If an account with this email exists, your request has been forwarded to support."
1058
928
  },
1059
- "Auth.MFA_HELP_REQUEST_FAILED": {
1060
- "de": "Die Anfrage an den Support konnte nicht gesendet werden.",
1061
- "fr": "Impossible d’envoyer la demande au support.",
1062
- "en": "Failed to send the request to support."
1063
- }
1064
- // ...
1065
929
  };
@@ -102,6 +102,6 @@ export function LoginPage() {
102
102
  setMfaState(null);
103
103
  setErrorKey(null);
104
104
  };
105
- return (_jsxs(NarrowPage, { title: t('Auth.PAGE_LOGIN_TITLE'), subtitle: t('Auth.PAGE_LOGIN_SUBTITLE'), children: [_jsx(Helmet, { children: _jsxs("title", { children: [t('App.NAME'), " \u2013 ", t('Auth.PAGE_LOGIN_TITLE')] }) }), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), recoveryToken && !errorKey && (_jsx(Alert, { severity: "info", sx: { mb: 2 }, children: t('Auth.RECOVERY_LOGIN_INFO', 'Your recovery link was validated. Please sign in with your password to continue.') })), step === 'credentials' && (_jsx(LoginForm, { onSubmit: handleSubmitCredentials, onForgotPassword: handleForgotPassword, onSocialLogin: handleSocialLogin, onPasskeyLogin: handlePasskeyLoginInitial, onSignUp: handleSignUp, disabled: submitting, initialIdentifier: recoveryEmail })), step === 'mfa' && mfaState && (_jsx(Box, { children: _jsx(MfaLoginComponent, { availableTypes: mfaState.availableTypes, identifier: mfaState.identifier, onSuccess: handleMfaSuccess, onCancel: handleMfaCancel }) }))] }));
105
+ return (_jsxs(NarrowPage, { title: t('Auth.PAGE_LOGIN_TITLE'), subtitle: t('Auth.PAGE_LOGIN_SUBTITLE'), children: [_jsx(Helmet, { children: _jsxs("title", { children: [t('App.NAME'), " \u2013 ", t('Auth.PAGE_LOGIN_TITLE')] }) }), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), recoveryToken && !errorKey && (_jsx(Alert, { severity: "info", sx: { mb: 2 }, children: t('Auth.RECOVERY_LOGIN_WARNING', 'Your recovery link was validated. Please sign in with your password to continue.') })), step === 'credentials' && (_jsx(LoginForm, { onSubmit: handleSubmitCredentials, onForgotPassword: handleForgotPassword, onSocialLogin: handleSocialLogin, onPasskeyLogin: handlePasskeyLoginInitial, onSignUp: handleSignUp, disabled: submitting, initialIdentifier: recoveryEmail })), step === 'mfa' && mfaState && (_jsx(Box, { children: _jsx(MfaLoginComponent, { availableTypes: mfaState.availableTypes, identifier: mfaState.identifier, onSuccess: handleMfaSuccess, onCancel: handleMfaCancel }) }))] }));
106
106
  }
107
107
  export default LoginPage;
@@ -18,7 +18,7 @@ export function PasswordChangePage() {
18
18
  setSubmitting(true);
19
19
  try {
20
20
  await changePassword(oldPassword, newPassword);
21
- setSuccessKey('Auth.PASSWORD_CHANGE_SUCCESS');
21
+ setSuccessKey('Auth.RESET_PASSWORD_SUCCESS');
22
22
  }
23
23
  catch (err) {
24
24
  setErrorKey(err.code || 'Auth.PASSWORD_CHANGE_FAILED');
@@ -27,6 +27,6 @@ export function PasswordChangePage() {
27
27
  setSubmitting(false);
28
28
  }
29
29
  };
30
- return (_jsxs(NarrowPage, { title: t('Auth.PAGE_CHANGE_PASSWORD_TITLE'), subtitle: t('Auth.PAGE_CHANGE_PASSWORD_SUBTITLE'), children: [_jsx(Helmet, { children: _jsxs("title", { children: [t('App.NAME'), " \u2013 ", t('Auth.PAGE_CHANGE_PASSWORD_TITLE')] }) }), successKey && (_jsx(Typography, { color: "primary", gutterBottom: true, children: t(successKey) })), errorKey && (_jsx(Typography, { color: "error", gutterBottom: true, children: t(errorKey) })), _jsx(PasswordChangeForm, { onSubmit: handleSubmit, submitting: submitting })] }));
30
+ return (_jsxs(NarrowPage, { title: t('Auth.CHANGE_PASSWORD_BUTTON'), subtitle: t('Auth.PAGE_CHANGE_PASSWORD_SUBTITLE'), children: [_jsx(Helmet, { children: _jsxs("title", { children: [t('App.NAME'), " \u2013 ", t('Auth.CHANGE_PASSWORD_BUTTON')] }) }), successKey && (_jsx(Typography, { color: "primary", gutterBottom: true, children: t(successKey) })), errorKey && (_jsx(Typography, { color: "error", gutterBottom: true, children: t(errorKey) })), _jsx(PasswordChangeForm, { onSubmit: handleSubmit, submitting: submitting })] }));
31
31
  }
32
32
  export default PasswordChangePage;
@@ -32,6 +32,6 @@ export function PasswordResetRequestPage() {
32
32
  setSubmitting(false);
33
33
  }
34
34
  };
35
- return (_jsxs(NarrowPage, { title: t('Auth.PAGE_RESET_REQUEST_TITLE'), subtitle: t('Auth.PAGE_RESET_REQUEST_SUBTITLE'), children: [_jsx(Helmet, { children: _jsxs("title", { children: [t('App.NAME'), " \u2013 ", t('Auth.PAGE_RESET_REQUEST_TITLE')] }) }), successKey && (_jsx(Typography, { color: "primary", gutterBottom: true, children: t(successKey) })), errorKey && (_jsx(Typography, { color: "error", gutterBottom: true, children: t(errorKey) })), _jsx(PasswordResetRequestForm, { onSubmit: handleSubmit, submitting: submitting })] }));
35
+ return (_jsxs(NarrowPage, { title: t('Auth.PAGE_RESET_PASSWORD_TITLE'), subtitle: t('Auth.PAGE_RESET_REQUEST_SUBTITLE'), children: [_jsx(Helmet, { children: _jsxs("title", { children: [t('App.NAME'), " \u2013 ", t('Auth.PAGE_RESET_PASSWORD_TITLE')] }) }), successKey && (_jsx(Typography, { color: "primary", gutterBottom: true, children: t(successKey) })), errorKey && (_jsx(Typography, { color: "error", gutterBottom: true, children: t(errorKey) })), _jsx(PasswordResetRequestForm, { onSubmit: handleSubmit, submitting: submitting })] }));
36
36
  }
37
37
  export default PasswordResetRequestPage;
@@ -50,7 +50,7 @@ export function SignUpPage() {
50
50
  const handleGoToLogin = () => {
51
51
  navigate('/login');
52
52
  };
53
- return (_jsxs(NarrowPage, { title: t('Auth.PAGE_SIGNUP_TITLE'), subtitle: t('Auth.PAGE_SIGNUP_SUBTITLE'), children: [_jsx(Helmet, { children: _jsxs("title", { children: [t('App.NAME'), " \u2013 ", t('Auth.PAGE_SIGNUP_TITLE')] }) }), successKey && (_jsx(Alert, { severity: "success", sx: { mb: 2 }, children: t(successKey, { email }) })), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), _jsxs(Box, { component: "form", onSubmit: handleSubmit, sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(TextField, { label: t('Auth.EMAIL_LABEL'), type: "email", required: true, fullWidth: true, value: email, onChange: (e) => setEmail(e.target.value), disabled: submitting }), _jsx(TextField, { label: t('Auth.SIGNUP_ACCESS_CODE_LABEL'), type: "text", required: true, fullWidth: true, value: accessCode, onChange: (e) => setAccessCode(e.target.value), disabled: submitting }), _jsx(Button, { type: "submit", variant: "contained", disabled: submitting, children: submitting
53
+ return (_jsxs(NarrowPage, { title: t('Auth.LOGIN_SIGNUP_BUTTON'), subtitle: t('Auth.PAGE_SIGNUP_SUBTITLE'), children: [_jsx(Helmet, { children: _jsxs("title", { children: [t('App.NAME'), " \u2013 ", t('Auth.LOGIN_SIGNUP_BUTTON')] }) }), successKey && (_jsx(Alert, { severity: "success", sx: { mb: 2 }, children: t(successKey, { email }) })), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), _jsxs(Box, { component: "form", onSubmit: handleSubmit, sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(TextField, { label: t('Auth.EMAIL_LABEL'), type: "email", required: true, fullWidth: true, value: email, onChange: (e) => setEmail(e.target.value), disabled: submitting }), _jsx(TextField, { label: t('Auth.ACCESS_CODE_LABEL'), type: "text", required: true, fullWidth: true, value: accessCode, onChange: (e) => setAccessCode(e.target.value), disabled: submitting }), _jsx(Button, { type: "submit", variant: "contained", disabled: submitting, children: submitting
54
54
  ? t('Auth.SIGNUP_SUBMITTING')
55
55
  : t('Auth.SIGNUP_SUBMIT') })] }), _jsx(Box, { sx: { mt: 3 }, children: _jsxs(Typography, { variant: "body2", children: [t('Auth.SIGNUP_ALREADY_HAVE_ACCOUNT'), ' ', _jsx(Button, { onClick: handleGoToLogin, variant: "text", size: "small", children: t('Auth.SIGNUP_GO_TO_LOGIN') })] }) })] }));
56
56
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micha.bigler/ui-core-micha",
3
- "version": "1.4.13",
3
+ "version": "1.4.16",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "private": false,
@@ -4,8 +4,8 @@ import React, {
4
4
  useState,
5
5
  useEffect,
6
6
  } from 'react';
7
- import axios from 'axios';
8
- import { CSRF_URL } from './authConfig';
7
+ // REMOVED: import axios from 'axios'; -> Not needed here anymore
8
+ import { ensureCsrfToken } from './apiClient'; // <--- IMPORT ADDED
9
9
  import {
10
10
  fetchCurrentUser,
11
11
  logoutSession,
@@ -17,31 +17,19 @@ export const AuthProvider = ({ children }) => {
17
17
  const [user, setUser] = useState(null);
18
18
  const [loading, setLoading] = useState(true);
19
19
 
20
- // Einmalige Axios-Basis-Konfiguration
21
- useEffect(() => {
22
- axios.defaults.withCredentials = true;
23
- axios.defaults.xsrfCookieName = 'csrftoken';
24
- axios.defaults.xsrfHeaderName = 'X-CSRFToken';
25
- }, []);
26
-
27
20
  useEffect(() => {
28
21
  let isMounted = true;
29
22
 
30
23
  const initAuth = async () => {
31
24
  try {
32
- // 1) CSRF-Cookie setzen (Django-View /api/csrf/)
33
- try {
34
- await axios.get(CSRF_URL, { withCredentials: true });
35
- // console.log('CSRF cookie set');
36
- } catch (err) {
37
- // eslint-disable-next-line no-console
38
- console.error('Error setting CSRF cookie:', err);
39
- }
25
+ // 1) Ensure CSRF cookie exists using the specific client
26
+ await ensureCsrfToken();
40
27
 
41
- // 2) aktuellen User laden (falls Session vorhanden)
42
- try {
43
- const data = await fetchCurrentUser();
44
- if (!isMounted) return;
28
+ // 2) Load user
29
+ const data = await fetchCurrentUser();
30
+
31
+ if (isMounted) {
32
+ // Map data to ensure consistent structure
45
33
  setUser({
46
34
  id: data.id,
47
35
  username: data.username,
@@ -50,33 +38,22 @@ export const AuthProvider = ({ children }) => {
50
38
  last_name: data.last_name,
51
39
  role: data.role,
52
40
  is_superuser: data.is_superuser,
41
+ security_state: data.security_state, // Ensure this is passed if needed
53
42
  });
54
- } catch (err) {
55
- const status = err.response?.status;
56
- if (status && status !== 401) {
57
- console.error('Error while fetching current user:', err);
58
- } else {
59
- console.log('No logged-in user');
60
- }
61
- if (!isMounted) return;
62
- setUser(null);
63
43
  }
44
+ } catch (err) {
45
+ // Silent failure on 401/403 is expected (user not logged in)
46
+ if (isMounted) setUser(null);
64
47
  } finally {
65
- if (isMounted) {
66
- setLoading(false);
67
- }
48
+ if (isMounted) setLoading(false);
68
49
  }
69
50
  };
70
51
 
71
52
  initAuth();
72
53
 
73
- return () => {
74
- isMounted = false;
75
- };
54
+ return () => { isMounted = false; };
76
55
  }, []);
77
56
 
78
- // Nach erfolgreichem Login das User-Objekt setzen
79
- // (z. B. aus loginWithPassword in authApi)
80
57
  const login = (userData) => {
81
58
  setUser((prev) => ({
82
59
  ...prev,
@@ -84,7 +61,6 @@ export const AuthProvider = ({ children }) => {
84
61
  }));
85
62
  };
86
63
 
87
- // Logout im Backend + lokalen State leeren
88
64
  const logout = async () => {
89
65
  try {
90
66
  await logoutSession();
@@ -108,4 +84,4 @@ export const AuthProvider = ({ children }) => {
108
84
  {children}
109
85
  </AuthContext.Provider>
110
86
  );
111
- };
87
+ };
@@ -0,0 +1,20 @@
1
+ // src/auth/apiClient.js
2
+ import axios from 'axios';
3
+ import { CSRF_URL } from './authConfig';
4
+
5
+ const apiClient = axios.create({
6
+ withCredentials: true,
7
+ xsrfCookieName: 'csrftoken',
8
+ xsrfHeaderName: 'X-CSRFToken',
9
+ });
10
+
11
+ export async function ensureCsrfToken() {
12
+ try {
13
+ await apiClient.get(CSRF_URL);
14
+ } catch (err) {
15
+ // eslint-disable-next-line no-console
16
+ console.warn("CSRF Initialization failed", err);
17
+ }
18
+ }
19
+
20
+ export default apiClient;