@micha.bigler/ui-core-micha 1.4.31 → 1.4.33

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.
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // src/auth/components/MfaLoginComponent.jsx
3
2
  import React, { useState } from 'react';
4
3
  import { Box, Typography, TextField, Button, Stack, Alert, Divider, Dialog, DialogTitle, DialogContent, DialogActions, } from '@mui/material';
5
4
  import { useTranslation } from 'react-i18next';
@@ -44,11 +43,22 @@ export function MfaLoginComponent({ availableTypes, identifier, onSuccess, onCan
44
43
  setInfoKey(null);
45
44
  setSubmitting(true);
46
45
  try {
47
- const { user } = await loginWithPasskey();
46
+ // Use standard passkey login (starts a fresh session flow).
47
+ // This allows users to bypass the password MFA step if they have a valid passkey.
48
+ const user = await loginWithPasskey();
48
49
  onSuccess({ user, method: 'webauthn' });
49
50
  }
50
51
  catch (err) {
51
- setErrorKey(err.code || 'Auth.PASSKEY_FAILED');
52
+ // Detailed error handling
53
+ if (err.code === 'Auth.PASSKEY_CANCELLED') {
54
+ // User cancelled - show specific key or just ignore
55
+ setErrorKey('Auth.PASSKEY_CANCELLED');
56
+ }
57
+ else {
58
+ // eslint-disable-next-line no-console
59
+ console.error(err);
60
+ setErrorKey('Auth.PASSKEY_FAILED');
61
+ }
52
62
  }
53
63
  finally {
54
64
  setSubmitting(false);
@@ -1,7 +1,7 @@
1
- // src/utils/authService.js
2
- import { getPasskeyRegistrationOptions, completePasskeyRegistration, getPasskeyLoginOptions, completePasskeyLogin, fetchCurrentUser } from '../auth/authApi';
1
+ import { getPasskeyRegistrationOptions, completePasskeyRegistration, getPasskeyLoginOptions, completePasskeyLogin, fetchCurrentUser, authenticateWithMFA } from '../auth/authApi';
3
2
  import { ensureWebAuthnSupport, serializeCredential } from '../utils/webauthn';
4
3
  import { normaliseApiError } from '../utils/auth-errors';
4
+ import apiClient from '../auth/apiClient';
5
5
  export async function registerPasskey(name = 'Passkey') {
6
6
  ensureWebAuthnSupport();
7
7
  // 1. Get Options from Server
@@ -9,11 +9,10 @@ export async function registerPasskey(name = 'Passkey') {
9
9
  // 2. Call Browser API
10
10
  let credential;
11
11
  try {
12
- // Note: Parse JSON to Options needs a helper if creationOptions is raw JSON strings
13
- // modern browsers/allauth usually provide correct types, but check your library version
14
- // For standard Allauth headless, you might need window.PublicKeyCredential.parseCreationOptionsFromJSON
15
- const pubKey = window.PublicKeyCredential.parseCreationOptionsFromJSON(creationOptions);
16
- credential = await navigator.credentials.create({ publicKey: pubKey });
12
+ // FIX: options enthält bereits die korrekte Struktur für create()
13
+ const options = window.PublicKeyCredential.parseCreationOptionsFromJSON(creationOptions);
14
+ // FIX: Kein manuelles { publicKey: ... } Wrapping mehr!
15
+ credential = await navigator.credentials.create(options);
17
16
  }
18
17
  catch (err) {
19
18
  if (err.name === 'NotAllowedError') {
@@ -32,11 +31,16 @@ export async function loginWithPasskey() {
32
31
  // 2. Browser Sign
33
32
  let assertion;
34
33
  try {
35
- const pubKey = window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptions);
36
- assertion = await navigator.credentials.get({ publicKey: pubKey });
34
+ // FIX: options enthält bereits die korrekte Struktur für get()
35
+ const options = window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptions);
36
+ // FIX: Kein manuelles { publicKey: ... } Wrapping mehr!
37
+ assertion = await navigator.credentials.get(options);
37
38
  }
38
39
  catch (err) {
39
- throw normaliseApiError(new Error('Auth.PASSKEY_CANCELLED'), 'Auth.PASSKEY_CANCELLED');
40
+ if (err.name === 'NotAllowedError') {
41
+ throw normaliseApiError(new Error('Auth.PASSKEY_CANCELLED'), 'Auth.PASSKEY_CANCELLED');
42
+ }
43
+ throw err;
40
44
  }
41
45
  // 3. Complete
42
46
  const credentialJson = serializeCredential(assertion);
@@ -44,10 +48,44 @@ export async function loginWithPasskey() {
44
48
  // 4. Reload User
45
49
  return fetchCurrentUser();
46
50
  }
51
+ /**
52
+ * WebAuthn als 2. Faktor nutzen (wenn Passwort schon eingegeben wurde).
53
+ */
54
+ export async function authenticateMfaWithPasskey() {
55
+ var _a;
56
+ ensureWebAuthnSupport();
57
+ let requestOptions;
58
+ try {
59
+ const res = await apiClient.get('/api/auth/browser/v1/auth/2fa/authenticate');
60
+ const data = ((_a = res.data) === null || _a === void 0 ? void 0 : _a.data) || res.data;
61
+ requestOptions = data.request_options || data;
62
+ }
63
+ catch (err) {
64
+ throw normaliseApiError(err, 'Auth.MFA_CHALLENGE_FAILED');
65
+ }
66
+ if (!requestOptions) {
67
+ throw new Error('No WebAuthn challenge received for MFA.');
68
+ }
69
+ // 2. Browser Sign
70
+ let assertion;
71
+ try {
72
+ const options = window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptions);
73
+ // FIX: Kein manuelles Wrapping
74
+ assertion = await navigator.credentials.get(options);
75
+ }
76
+ catch (err) {
77
+ if (err.name === 'NotAllowedError') {
78
+ throw normaliseApiError(new Error('Auth.PASSKEY_CANCELLED'), 'Auth.PASSKEY_CANCELLED');
79
+ }
80
+ throw err;
81
+ }
82
+ // 3. Authenticate via existing API function
83
+ const credentialJson = serializeCredential(assertion);
84
+ return authenticateWithMFA({ credential: credentialJson });
85
+ }
47
86
  export function startSocialLogin(provider) {
48
87
  if (typeof window === 'undefined') {
49
88
  throw normaliseApiError(new Error('Auth.SOCIAL_LOGIN_NOT_IN_BROWSER'), 'Auth.SOCIAL_LOGIN_NOT_IN_BROWSER');
50
89
  }
51
- // Browser-Redirect ist ein Side-Effect -> Service Layer
52
90
  window.location.href = `/accounts/${provider}/login/?process=login`;
53
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micha.bigler/ui-core-micha",
3
- "version": "1.4.31",
3
+ "version": "1.4.33",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "private": false,
@@ -1,4 +1,3 @@
1
- // src/auth/components/MfaLoginComponent.jsx
2
1
  import React, { useState } from 'react';
3
2
  import {
4
3
  Box,
@@ -14,10 +13,10 @@ import {
14
13
  DialogActions,
15
14
  } from '@mui/material';
16
15
  import { useTranslation } from 'react-i18next';
17
- import { authenticateWithMFA, fetchCurrentUser, requestMfaSupportHelp } from '../auth/authApi';
18
- import { loginWithPasskey } from '../utils/authService';
16
+ import { authenticateWithMFA, fetchCurrentUser, requestMfaSupportHelp } from '../auth/authApi';
17
+ import { loginWithPasskey } from '../utils/authService';
19
18
 
20
- export function MfaLoginComponent({ availableTypes, identifier, onSuccess, onCancel }) {
19
+ export function MfaLoginComponent({ availableTypes, identifier, onSuccess, onCancel }) {
21
20
  const { t } = useTranslation();
22
21
  const [code, setCode] = useState('');
23
22
  const [submitting, setSubmitting] = useState(false);
@@ -62,10 +61,20 @@ import { loginWithPasskey } from '../utils/authService';
62
61
  setInfoKey(null);
63
62
  setSubmitting(true);
64
63
  try {
65
- const { user } = await loginWithPasskey();
64
+ // Use standard passkey login (starts a fresh session flow).
65
+ // This allows users to bypass the password MFA step if they have a valid passkey.
66
+ const user = await loginWithPasskey();
66
67
  onSuccess({ user, method: 'webauthn' });
67
68
  } catch (err) {
68
- setErrorKey(err.code || 'Auth.PASSKEY_FAILED');
69
+ // Detailed error handling
70
+ if (err.code === 'Auth.PASSKEY_CANCELLED') {
71
+ // User cancelled - show specific key or just ignore
72
+ setErrorKey('Auth.PASSKEY_CANCELLED');
73
+ } else {
74
+ // eslint-disable-next-line no-console
75
+ console.error(err);
76
+ setErrorKey('Auth.PASSKEY_FAILED');
77
+ }
69
78
  } finally {
70
79
  setSubmitting(false);
71
80
  }
@@ -236,4 +245,3 @@ import { loginWithPasskey } from '../utils/authService';
236
245
 
237
246
  };
238
247
 
239
-
@@ -1,11 +1,18 @@
1
- // src/utils/authService.js
2
- import { getPasskeyRegistrationOptions, completePasskeyRegistration, getPasskeyLoginOptions, completePasskeyLogin, fetchCurrentUser } from '../auth/authApi';
1
+ import {
2
+ getPasskeyRegistrationOptions,
3
+ completePasskeyRegistration,
4
+ getPasskeyLoginOptions,
5
+ completePasskeyLogin,
6
+ fetchCurrentUser,
7
+ authenticateWithMFA
8
+ } from '../auth/authApi';
3
9
 
4
10
  import {
5
11
  ensureWebAuthnSupport,
6
12
  serializeCredential
7
13
  } from '../utils/webauthn';
8
14
  import { normaliseApiError } from '../utils/auth-errors';
15
+ import apiClient from '../auth/apiClient';
9
16
 
10
17
  export async function registerPasskey(name = 'Passkey') {
11
18
  ensureWebAuthnSupport();
@@ -16,11 +23,10 @@ export async function registerPasskey(name = 'Passkey') {
16
23
  // 2. Call Browser API
17
24
  let credential;
18
25
  try {
19
- // Note: Parse JSON to Options needs a helper if creationOptions is raw JSON strings
20
- // modern browsers/allauth usually provide correct types, but check your library version
21
- // For standard Allauth headless, you might need window.PublicKeyCredential.parseCreationOptionsFromJSON
22
- const pubKey = window.PublicKeyCredential.parseCreationOptionsFromJSON(creationOptions);
23
- credential = await navigator.credentials.create({ publicKey: pubKey });
26
+ // FIX: options enthält bereits die korrekte Struktur für create()
27
+ const options = window.PublicKeyCredential.parseCreationOptionsFromJSON(creationOptions);
28
+ // FIX: Kein manuelles { publicKey: ... } Wrapping mehr!
29
+ credential = await navigator.credentials.create(options);
24
30
  } catch (err) {
25
31
  if (err.name === 'NotAllowedError') {
26
32
  throw normaliseApiError(new Error('Auth.PASSKEY_CANCELLED'), 'Auth.PASSKEY_CANCELLED');
@@ -42,10 +48,15 @@ export async function loginWithPasskey() {
42
48
  // 2. Browser Sign
43
49
  let assertion;
44
50
  try {
45
- const pubKey = window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptions);
46
- assertion = await navigator.credentials.get({ publicKey: pubKey });
51
+ // FIX: options enthält bereits die korrekte Struktur für get()
52
+ const options = window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptions);
53
+ // FIX: Kein manuelles { publicKey: ... } Wrapping mehr!
54
+ assertion = await navigator.credentials.get(options);
47
55
  } catch (err) {
48
- throw normaliseApiError(new Error('Auth.PASSKEY_CANCELLED'), 'Auth.PASSKEY_CANCELLED');
56
+ if (err.name === 'NotAllowedError') {
57
+ throw normaliseApiError(new Error('Auth.PASSKEY_CANCELLED'), 'Auth.PASSKEY_CANCELLED');
58
+ }
59
+ throw err;
49
60
  }
50
61
 
51
62
  // 3. Complete
@@ -56,6 +67,43 @@ export async function loginWithPasskey() {
56
67
  return fetchCurrentUser();
57
68
  }
58
69
 
70
+ /**
71
+ * WebAuthn als 2. Faktor nutzen (wenn Passwort schon eingegeben wurde).
72
+ */
73
+ export async function authenticateMfaWithPasskey() {
74
+ ensureWebAuthnSupport();
75
+
76
+ let requestOptions;
77
+ try {
78
+ const res = await apiClient.get('/api/auth/browser/v1/auth/2fa/authenticate');
79
+ const data = res.data?.data || res.data;
80
+ requestOptions = data.request_options || data;
81
+ } catch (err) {
82
+ throw normaliseApiError(err, 'Auth.MFA_CHALLENGE_FAILED');
83
+ }
84
+
85
+ if (!requestOptions) {
86
+ throw new Error('No WebAuthn challenge received for MFA.');
87
+ }
88
+
89
+ // 2. Browser Sign
90
+ let assertion;
91
+ try {
92
+ const options = window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptions);
93
+ // FIX: Kein manuelles Wrapping
94
+ assertion = await navigator.credentials.get(options);
95
+ } catch (err) {
96
+ if (err.name === 'NotAllowedError') {
97
+ throw normaliseApiError(new Error('Auth.PASSKEY_CANCELLED'), 'Auth.PASSKEY_CANCELLED');
98
+ }
99
+ throw err;
100
+ }
101
+
102
+ // 3. Authenticate via existing API function
103
+ const credentialJson = serializeCredential(assertion);
104
+ return authenticateWithMFA({ credential: credentialJson });
105
+ }
106
+
59
107
  export function startSocialLogin(provider) {
60
108
  if (typeof window === 'undefined') {
61
109
  throw normaliseApiError(
@@ -63,6 +111,5 @@ export function startSocialLogin(provider) {
63
111
  'Auth.SOCIAL_LOGIN_NOT_IN_BROWSER'
64
112
  );
65
113
  }
66
- // Browser-Redirect ist ein Side-Effect -> Service Layer
67
114
  window.location.href = `/accounts/${provider}/login/?process=login`;
68
- }
115
+ }