@micha.bigler/ui-core-micha 1.4.34 → 1.4.36

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.
@@ -255,6 +255,32 @@ export async function generateRecoveryCodes() {
255
255
  // -----------------------------
256
256
  // Invitations & Access Codes
257
257
  // -----------------------------
258
+ export async function fetchAccessCodes() {
259
+ try {
260
+ const res = await apiClient.get(`${ACCESS_CODES_BASE}/`);
261
+ return res.data || [];
262
+ }
263
+ catch (error) {
264
+ throw normaliseApiError(error, 'Auth.ACCESS_CODE_LIST_FAILED');
265
+ }
266
+ }
267
+ export async function createAccessCode(code) {
268
+ try {
269
+ const res = await apiClient.post(`${ACCESS_CODES_BASE}/`, { code });
270
+ return res.data;
271
+ }
272
+ catch (error) {
273
+ throw normaliseApiError(error, 'Auth.ACCESS_CODE_SAVE_FAILED');
274
+ }
275
+ }
276
+ export async function deleteAccessCode(id) {
277
+ try {
278
+ await apiClient.delete(`${ACCESS_CODES_BASE}/${id}/`);
279
+ }
280
+ catch (error) {
281
+ throw normaliseApiError(error, 'Auth.ACCESS_CODE_DELETE_FAILED');
282
+ }
283
+ }
258
284
  export async function validateAccessCode(code) {
259
285
  try {
260
286
  const res = await apiClient.post(`${ACCESS_CODES_BASE}/validate/`, { code });
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // src/auth/components/AccessCodeManager.jsx
3
3
  import React, { useEffect, useState } from 'react';
4
- import axios from 'axios';
5
- import { Box, Stack, Typography, Slider, Button, TextField, IconButton, Chip, Alert, CircularProgress, } from '@mui/material';
4
+ import { Box, Stack, Typography, Slider, Button, TextField, Chip, Alert, CircularProgress, } from '@mui/material';
6
5
  import CloseIcon from '@mui/icons-material/Close';
7
6
  import { useTranslation } from 'react-i18next';
8
- import { ACCESS_CODES_BASE } from '../auth/authConfig';
7
+ // Pfad ggf. anpassen je nach Struktur: von src/auth/components zu src/auth/authApi
8
+ import { fetchAccessCodes, createAccessCode, deleteAccessCode, } from '../authApi';
9
9
  export function AccessCodeManager() {
10
10
  const { t } = useTranslation();
11
11
  const [codes, setCodes] = useState([]);
@@ -15,25 +15,22 @@ export function AccessCodeManager() {
15
15
  const [manualCode, setManualCode] = useState('');
16
16
  const [errorKey, setErrorKey] = useState(null);
17
17
  const [successKey, setSuccessKey] = useState(null);
18
- // Kleine Helper: versucht, einen Backend-Code zu nehmen, sonst Fallback
19
- const setErrorFromAxios = (err, fallbackCode) => {
20
- var _a, _b;
21
- const backendCode = (_b = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.code;
18
+ // Helper that prefers backend error code if available
19
+ const setErrorFromErrorObject = (err, fallbackCode) => {
20
+ const backendCode = err === null || err === void 0 ? void 0 : err.code;
22
21
  setErrorKey(backendCode || fallbackCode);
23
22
  };
24
- // Loads all access codes
23
+ // Load all access codes from backend
25
24
  const loadCodes = async () => {
26
25
  setLoading(true);
27
26
  setErrorKey(null);
28
27
  setSuccessKey(null);
29
28
  try {
30
- const res = await apiClient.get(`${ACCESS_CODES_BASE}/`, {
31
- withCredentials: true,
32
- });
33
- setCodes(res.data || []);
29
+ const data = await fetchAccessCodes();
30
+ setCodes(Array.isArray(data) ? data : []);
34
31
  }
35
32
  catch (err) {
36
- setErrorFromAxios(err, 'Auth.ACCESS_CODE_LIST_FAILED');
33
+ setErrorFromErrorObject(err, 'Auth.ACCESS_CODE_LIST_FAILED');
37
34
  }
38
35
  finally {
39
36
  setLoading(false);
@@ -41,6 +38,7 @@ export function AccessCodeManager() {
41
38
  };
42
39
  useEffect(() => {
43
40
  loadCodes();
41
+ // eslint-disable-next-line react-hooks/exhaustive-deps
44
42
  }, []);
45
43
  // Generates a random code with the selected length
46
44
  const generateRandomCode = (len) => {
@@ -54,12 +52,12 @@ export function AccessCodeManager() {
54
52
  setErrorKey(null);
55
53
  setSuccessKey(null);
56
54
  try {
57
- const res = await apiClient.post(`${ACCESS_CODES_BASE}/`, { code }, { withCredentials: true });
58
- setCodes((prev) => [...prev, res.data]);
55
+ const created = await createAccessCode(code);
56
+ setCodes((prev) => [...prev, created]);
59
57
  setSuccessKey('Auth.ACCESS_CODE_SAVE_SUCCESS');
60
58
  }
61
59
  catch (err) {
62
- setErrorFromAxios(err, 'Auth.ACCESS_CODE_SAVE_FAILED');
60
+ setErrorFromErrorObject(err, 'Auth.ACCESS_CODE_SAVE_FAILED');
63
61
  }
64
62
  finally {
65
63
  setSubmitting(false);
@@ -81,14 +79,12 @@ export function AccessCodeManager() {
81
79
  setErrorKey(null);
82
80
  setSuccessKey(null);
83
81
  try {
84
- await axios.delete(`${ACCESS_CODES_BASE}/${id}/`, {
85
- withCredentials: true,
86
- });
82
+ await deleteAccessCode(id);
87
83
  setCodes((prev) => prev.filter((c) => c.id !== id));
88
84
  setSuccessKey('Auth.ACCESS_CODE_DELETE_SUCCESS');
89
85
  }
90
86
  catch (err) {
91
- setErrorFromAxios(err, 'Auth.ACCESS_CODE_DELETE_FAILED');
87
+ setErrorFromErrorObject(err, 'Auth.ACCESS_CODE_DELETE_FAILED');
92
88
  }
93
89
  };
94
90
  if (loading) {
@@ -80,7 +80,7 @@ export function LoginPage() {
80
80
  setSubmitting(true);
81
81
  try {
82
82
  // Service handles browser interaction + API calls
83
- const { user } = await loginWithPasskey();
83
+ const user = await loginWithPasskey();
84
84
  handleLoginSuccess(user);
85
85
  }
86
86
  catch (err) {
@@ -2,8 +2,14 @@ import { getPasskeyRegistrationOptions, completePasskeyRegistration, getPasskeyL
2
2
  import { ensureWebAuthnSupport, serializeCredential } from '../utils/webauthn';
3
3
  import { normaliseApiError } from '../utils/auth-errors';
4
4
  import apiClient from '../auth/apiClient';
5
- // Falls du die Konstanten importieren möchtest, um Hardcoding zu vermeiden:
6
5
  import { HEADLESS_BASE } from '../auth/authConfig';
6
+ // HILFSFUNKTION: Entpackt die Optionen, falls sie in 'publicKey' stecken
7
+ function resolveWebAuthnOptions(options) {
8
+ if (options && options.publicKey) {
9
+ return options.publicKey;
10
+ }
11
+ return options;
12
+ }
7
13
  export async function registerPasskey(name = 'Passkey') {
8
14
  ensureWebAuthnSupport();
9
15
  // 1. Get Options from Server
@@ -11,8 +17,10 @@ export async function registerPasskey(name = 'Passkey') {
11
17
  // 2. Call Browser API
12
18
  let credential;
13
19
  try {
20
+ // FIX: Erst entpacken (JSON -> JSON ohne publicKey Wrapper)
21
+ const optionsJson = resolveWebAuthnOptions(creationOptions);
22
+ // Dann parsen (JSON -> ArrayBuffer)
14
23
  const publicKeyOptions = window.PublicKeyCredential.parseCreationOptionsFromJSON(creationOptions);
15
- // KORREKTUR: Wir MÜSSEN { publicKey: ... } wrappen!
16
24
  credential = await navigator.credentials.create({ publicKey: publicKeyOptions });
17
25
  }
18
26
  catch (err) {
@@ -32,8 +40,10 @@ export async function loginWithPasskey() {
32
40
  // 2. Browser Sign
33
41
  let assertion;
34
42
  try {
35
- const publicKeyOptions = window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptions);
36
- // KORREKTUR: Wir MÜSSEN { publicKey: ... } wrappen!
43
+ // FIX: Erst entpacken
44
+ const optionsJson = resolveWebAuthnOptions(requestOptions);
45
+ // Dann parsen
46
+ const publicKeyOptions = window.PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
37
47
  assertion = await navigator.credentials.get({ publicKey: publicKeyOptions });
38
48
  }
39
49
  catch (err) {
@@ -56,11 +66,11 @@ export async function authenticateMfaWithPasskey() {
56
66
  ensureWebAuthnSupport();
57
67
  let requestOptions;
58
68
  try {
59
- // Nutze HEADLESS_BASE falls verfügbar, sonst den Pfad
60
69
  const url = typeof HEADLESS_BASE !== 'undefined'
61
70
  ? `${HEADLESS_BASE}/auth/2fa/authenticate`
62
71
  : '/api/auth/browser/v1/auth/2fa/authenticate';
63
72
  const res = await apiClient.get(url);
73
+ // Allauth liefert oft: { data: { request_options: { publicKey: ... } } }
64
74
  const data = ((_a = res.data) === null || _a === void 0 ? void 0 : _a.data) || res.data;
65
75
  requestOptions = data.request_options || data;
66
76
  }
@@ -73,8 +83,11 @@ export async function authenticateMfaWithPasskey() {
73
83
  // 2. Browser Sign
74
84
  let assertion;
75
85
  try {
76
- const publicKeyOptions = window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptions);
77
- // KORREKTUR: Wir MÜSSEN { publicKey: ... } wrappen!
86
+ // FIX: Erst entpacken
87
+ const optionsJson = resolveWebAuthnOptions(requestOptions);
88
+ // Dann parsen
89
+ const publicKeyOptions = window.PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
90
+ // Dann wrappen
78
91
  assertion = await navigator.credentials.get({ publicKey: publicKeyOptions });
79
92
  }
80
93
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micha.bigler/ui-core-micha",
3
- "version": "1.4.34",
3
+ "version": "1.4.36",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "private": false,
@@ -265,6 +265,32 @@ export async function generateRecoveryCodes() {
265
265
  // Invitations & Access Codes
266
266
  // -----------------------------
267
267
 
268
+ export async function fetchAccessCodes() {
269
+ try {
270
+ const res = await apiClient.get(`${ACCESS_CODES_BASE}/`);
271
+ return res.data || [];
272
+ } catch (error) {
273
+ throw normaliseApiError(error, 'Auth.ACCESS_CODE_LIST_FAILED');
274
+ }
275
+ }
276
+
277
+ export async function createAccessCode(code) {
278
+ try {
279
+ const res = await apiClient.post(`${ACCESS_CODES_BASE}/`, { code });
280
+ return res.data;
281
+ } catch (error) {
282
+ throw normaliseApiError(error, 'Auth.ACCESS_CODE_SAVE_FAILED');
283
+ }
284
+ }
285
+
286
+ export async function deleteAccessCode(id) {
287
+ try {
288
+ await apiClient.delete(`${ACCESS_CODES_BASE}/${id}/`);
289
+ } catch (error) {
290
+ throw normaliseApiError(error, 'Auth.ACCESS_CODE_DELETE_FAILED');
291
+ }
292
+ }
293
+
268
294
  export async function validateAccessCode(code) {
269
295
  try {
270
296
  const res = await apiClient.post(`${ACCESS_CODES_BASE}/validate/`, { code });
@@ -1,6 +1,5 @@
1
1
  // src/auth/components/AccessCodeManager.jsx
2
2
  import React, { useEffect, useState } from 'react';
3
- import axios from 'axios';
4
3
  import {
5
4
  Box,
6
5
  Stack,
@@ -8,14 +7,19 @@ import {
8
7
  Slider,
9
8
  Button,
10
9
  TextField,
11
- IconButton,
12
10
  Chip,
13
11
  Alert,
14
12
  CircularProgress,
15
13
  } from '@mui/material';
16
14
  import CloseIcon from '@mui/icons-material/Close';
17
15
  import { useTranslation } from 'react-i18next';
18
- import { ACCESS_CODES_BASE } from '../auth/authConfig';
16
+
17
+ // Pfad ggf. anpassen je nach Struktur: von src/auth/components zu src/auth/authApi
18
+ import {
19
+ fetchAccessCodes,
20
+ createAccessCode,
21
+ deleteAccessCode,
22
+ } from '../authApi';
19
23
 
20
24
  export function AccessCodeManager() {
21
25
  const { t } = useTranslation();
@@ -30,24 +34,22 @@ export function AccessCodeManager() {
30
34
  const [errorKey, setErrorKey] = useState(null);
31
35
  const [successKey, setSuccessKey] = useState(null);
32
36
 
33
- // Kleine Helper: versucht, einen Backend-Code zu nehmen, sonst Fallback
34
- const setErrorFromAxios = (err, fallbackCode) => {
35
- const backendCode = err?.response?.data?.code;
37
+ // Helper that prefers backend error code if available
38
+ const setErrorFromErrorObject = (err, fallbackCode) => {
39
+ const backendCode = err?.code;
36
40
  setErrorKey(backendCode || fallbackCode);
37
41
  };
38
42
 
39
- // Loads all access codes
43
+ // Load all access codes from backend
40
44
  const loadCodes = async () => {
41
45
  setLoading(true);
42
46
  setErrorKey(null);
43
47
  setSuccessKey(null);
44
48
  try {
45
- const res = await apiClient.get(`${ACCESS_CODES_BASE}/`, {
46
- withCredentials: true,
47
- });
48
- setCodes(res.data || []);
49
+ const data = await fetchAccessCodes();
50
+ setCodes(Array.isArray(data) ? data : []);
49
51
  } catch (err) {
50
- setErrorFromAxios(err, 'Auth.ACCESS_CODE_LIST_FAILED');
52
+ setErrorFromErrorObject(err, 'Auth.ACCESS_CODE_LIST_FAILED');
51
53
  } finally {
52
54
  setLoading(false);
53
55
  }
@@ -55,6 +57,7 @@ export function AccessCodeManager() {
55
57
 
56
58
  useEffect(() => {
57
59
  loadCodes();
60
+ // eslint-disable-next-line react-hooks/exhaustive-deps
58
61
  }, []);
59
62
 
60
63
  // Generates a random code with the selected length
@@ -70,15 +73,11 @@ export function AccessCodeManager() {
70
73
  setErrorKey(null);
71
74
  setSuccessKey(null);
72
75
  try {
73
- const res = await apiClient.post(
74
- `${ACCESS_CODES_BASE}/`,
75
- { code },
76
- { withCredentials: true },
77
- );
78
- setCodes((prev) => [...prev, res.data]);
76
+ const created = await createAccessCode(code);
77
+ setCodes((prev) => [...prev, created]);
79
78
  setSuccessKey('Auth.ACCESS_CODE_SAVE_SUCCESS');
80
79
  } catch (err) {
81
- setErrorFromAxios(err, 'Auth.ACCESS_CODE_SAVE_FAILED');
80
+ setErrorFromErrorObject(err, 'Auth.ACCESS_CODE_SAVE_FAILED');
82
81
  } finally {
83
82
  setSubmitting(false);
84
83
  }
@@ -102,13 +101,11 @@ export function AccessCodeManager() {
102
101
  setErrorKey(null);
103
102
  setSuccessKey(null);
104
103
  try {
105
- await axios.delete(`${ACCESS_CODES_BASE}/${id}/`, {
106
- withCredentials: true,
107
- });
104
+ await deleteAccessCode(id);
108
105
  setCodes((prev) => prev.filter((c) => c.id !== id));
109
106
  setSuccessKey('Auth.ACCESS_CODE_DELETE_SUCCESS');
110
107
  } catch (err) {
111
- setErrorFromAxios(err, 'Auth.ACCESS_CODE_DELETE_FAILED');
108
+ setErrorFromErrorObject(err, 'Auth.ACCESS_CODE_DELETE_FAILED');
112
109
  }
113
110
  };
114
111
 
@@ -133,7 +130,7 @@ export function AccessCodeManager() {
133
130
  </Alert>
134
131
  )}
135
132
 
136
- {/* Liste aller Codes */}
133
+ {/* Active codes list */}
137
134
  <Box sx={{ mb: 3 }}>
138
135
  <Typography variant="subtitle1" gutterBottom>
139
136
  {t('Auth.ACCESS_CODE_SECTION_ACTIVE')}
@@ -143,11 +140,7 @@ export function AccessCodeManager() {
143
140
  {t('Auth.ACCESS_CODE_NONE')}
144
141
  </Typography>
145
142
  ) : (
146
- <Stack
147
- direction="row"
148
- flexWrap="wrap"
149
- gap={1}
150
- >
143
+ <Stack direction="row" flexWrap="wrap" gap={1}>
151
144
  {codes.map((code) => (
152
145
  <Chip
153
146
  key={code.id}
@@ -193,7 +186,7 @@ export function AccessCodeManager() {
193
186
  </Button>
194
187
  </Box>
195
188
 
196
- {/* Manuelles Hinzufügen */}
189
+ {/* Manual add */}
197
190
  <Box sx={{ mb: 2, maxWidth: 360 }}>
198
191
  <Typography variant="subtitle1" gutterBottom>
199
192
  {t('Auth.ACCESS_CODE_SECTION_MANUAL')}
@@ -218,4 +211,3 @@ export function AccessCodeManager() {
218
211
  </Box>
219
212
  );
220
213
  }
221
-
@@ -92,7 +92,7 @@ export function LoginPage() {
92
92
  setSubmitting(true);
93
93
  try {
94
94
  // Service handles browser interaction + API calls
95
- const { user } = await loginWithPasskey();
95
+ const user = await loginWithPasskey();
96
96
  handleLoginSuccess(user);
97
97
  } catch (err) {
98
98
  // 'Auth.PASSKEY_CANCELLED' is generic, maybe ignore visually or show specific hint
@@ -13,9 +13,16 @@ import {
13
13
  } from '../utils/webauthn';
14
14
  import { normaliseApiError } from '../utils/auth-errors';
15
15
  import apiClient from '../auth/apiClient';
16
- // Falls du die Konstanten importieren möchtest, um Hardcoding zu vermeiden:
17
16
  import { HEADLESS_BASE } from '../auth/authConfig';
18
17
 
18
+ // HILFSFUNKTION: Entpackt die Optionen, falls sie in 'publicKey' stecken
19
+ function resolveWebAuthnOptions(options) {
20
+ if (options && options.publicKey) {
21
+ return options.publicKey;
22
+ }
23
+ return options;
24
+ }
25
+
19
26
  export async function registerPasskey(name = 'Passkey') {
20
27
  ensureWebAuthnSupport();
21
28
 
@@ -25,8 +32,12 @@ export async function registerPasskey(name = 'Passkey') {
25
32
  // 2. Call Browser API
26
33
  let credential;
27
34
  try {
28
- const publicKeyOptions = window.PublicKeyCredential.parseCreationOptionsFromJSON(creationOptions);
29
- // KORREKTUR: Wir MÜSSEN { publicKey: ... } wrappen!
35
+ // FIX: Erst entpacken (JSON -> JSON ohne publicKey Wrapper)
36
+ const optionsJson = resolveWebAuthnOptions(creationOptions);
37
+
38
+ // Dann parsen (JSON -> ArrayBuffer)
39
+ const publicKeyOptions =
40
+ window.PublicKeyCredential.parseCreationOptionsFromJSON(creationOptions);
30
41
  credential = await navigator.credentials.create({ publicKey: publicKeyOptions });
31
42
  } catch (err) {
32
43
  if (err.name === 'NotAllowedError') {
@@ -49,8 +60,12 @@ export async function loginWithPasskey() {
49
60
  // 2. Browser Sign
50
61
  let assertion;
51
62
  try {
52
- const publicKeyOptions = window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptions);
53
- // KORREKTUR: Wir MÜSSEN { publicKey: ... } wrappen!
63
+ // FIX: Erst entpacken
64
+ const optionsJson = resolveWebAuthnOptions(requestOptions);
65
+
66
+ // Dann parsen
67
+ const publicKeyOptions =
68
+ window.PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
54
69
  assertion = await navigator.credentials.get({ publicKey: publicKeyOptions });
55
70
  } catch (err) {
56
71
  if (err.name === 'NotAllowedError') {
@@ -75,12 +90,12 @@ export async function authenticateMfaWithPasskey() {
75
90
 
76
91
  let requestOptions;
77
92
  try {
78
- // Nutze HEADLESS_BASE falls verfügbar, sonst den Pfad
79
93
  const url = typeof HEADLESS_BASE !== 'undefined'
80
94
  ? `${HEADLESS_BASE}/auth/2fa/authenticate`
81
95
  : '/api/auth/browser/v1/auth/2fa/authenticate';
82
96
 
83
97
  const res = await apiClient.get(url);
98
+ // Allauth liefert oft: { data: { request_options: { publicKey: ... } } }
84
99
  const data = res.data?.data || res.data;
85
100
  requestOptions = data.request_options || data;
86
101
  } catch (err) {
@@ -94,8 +109,13 @@ export async function authenticateMfaWithPasskey() {
94
109
  // 2. Browser Sign
95
110
  let assertion;
96
111
  try {
97
- const publicKeyOptions = window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptions);
98
- // KORREKTUR: Wir MÜSSEN { publicKey: ... } wrappen!
112
+ // FIX: Erst entpacken
113
+ const optionsJson = resolveWebAuthnOptions(requestOptions);
114
+
115
+ // Dann parsen
116
+ const publicKeyOptions = window.PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
117
+
118
+ // Dann wrappen
99
119
  assertion = await navigator.credentials.get({ publicKey: publicKeyOptions });
100
120
  } catch (err) {
101
121
  if (err.name === 'NotAllowedError') {