@micha.bigler/ui-core-micha 1.2.2 → 1.2.4

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,6 +1,8 @@
1
1
  import axios from 'axios';
2
2
  import { HEADLESS_BASE, USERS_BASE, ACCESS_CODES_BASE } from './authConfig';
3
- // Helper to normalise error messages from API responses
3
+ // -----------------------------
4
+ // Error helper
5
+ // -----------------------------
4
6
  function extractErrorMessage(error) {
5
7
  var _a;
6
8
  const data = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data;
@@ -15,13 +17,19 @@ function extractErrorMessage(error) {
15
17
  }
16
18
  return 'An error occurred. Please try again.';
17
19
  }
18
- // Helper to get CSRF token from cookies manually
20
+ // -----------------------------
21
+ // CSRF helper
22
+ // -----------------------------
19
23
  function getCsrfToken() {
20
- if (!document.cookie)
24
+ if (typeof document === 'undefined' || !document.cookie) {
21
25
  return null;
26
+ }
22
27
  const match = document.cookie.match(/csrftoken=([^;]+)/);
23
28
  return match ? match[1] : null;
24
29
  }
30
+ // -----------------------------
31
+ // WebAuthn capability helpers
32
+ // -----------------------------
25
33
  const hasJsonWebAuthn = typeof window !== 'undefined' &&
26
34
  typeof window.PublicKeyCredential !== 'undefined' &&
27
35
  typeof window.PublicKeyCredential.parseCreationOptionsFromJSON === 'function' &&
@@ -34,49 +42,39 @@ function ensureWebAuthnSupport() {
34
42
  throw new Error('Passkeys are not supported in this browser.');
35
43
  }
36
44
  }
37
- /**
38
- * Fetches the current authenticated user from your own User API.
39
- */
45
+ // -----------------------------
46
+ // User-related helpers
47
+ // -----------------------------
40
48
  export async function fetchCurrentUser() {
41
49
  const res = await axios.get(`${USERS_BASE}/current/`, {
42
50
  withCredentials: true,
43
51
  });
44
52
  return res.data;
45
53
  }
46
- /**
47
- * Updates the user profile fields.
48
- */
49
54
  export async function updateUserProfile(data) {
50
55
  const res = await axios.patch(`${USERS_BASE}/current/`, data, {
51
56
  withCredentials: true,
52
57
  });
53
58
  return res.data;
54
59
  }
55
- /**
56
- * Logs a user in using email/password via allauth headless.
57
- */
60
+ // -----------------------------
61
+ // Authentication: password
62
+ // -----------------------------
58
63
  export async function loginWithPassword(email, password) {
59
64
  try {
60
- await axios.post(`${HEADLESS_BASE}/auth/login`, {
61
- email,
62
- password,
63
- }, { withCredentials: true });
65
+ await axios.post(`${HEADLESS_BASE}/auth/login`, { email, password }, { withCredentials: true });
64
66
  }
65
67
  catch (error) {
66
68
  if (error.response && error.response.status === 409) {
67
- // User is already logged in, continue and fetch current user
69
+ // Already logged in: continue and fetch current user
68
70
  }
69
71
  else {
70
72
  throw new Error(extractErrorMessage(error));
71
73
  }
72
74
  }
73
75
  const user = await fetchCurrentUser();
74
- // Normalise return shape so callers always get { user }
75
76
  return { user };
76
77
  }
77
- /**
78
- * Requests a password reset email via allauth headless.
79
- */
80
78
  export async function requestPasswordReset(email) {
81
79
  try {
82
80
  await axios.post(`${USERS_BASE}/reset-request/`, { email }, { withCredentials: true });
@@ -85,23 +83,15 @@ export async function requestPasswordReset(email) {
85
83
  throw new Error(extractErrorMessage(error));
86
84
  }
87
85
  }
88
- /**
89
- * Sets a new password using a reset key (from email link).
90
- */
86
+ // (Falls du es noch brauchst: klassischer allauth-Key-Flow)
91
87
  export async function resetPasswordWithKey(key, newPassword) {
92
88
  try {
93
- await axios.post(`${HEADLESS_BASE}/auth/password/reset/key`, {
94
- key,
95
- password: newPassword,
96
- }, { withCredentials: true });
89
+ await axios.post(`${HEADLESS_BASE}/auth/password/reset/key`, { key, password: newPassword }, { withCredentials: true });
97
90
  }
98
91
  catch (error) {
99
92
  throw new Error(extractErrorMessage(error));
100
93
  }
101
94
  }
102
- /**
103
- * Changes the password for an authenticated user.
104
- */
105
95
  export async function changePassword(currentPassword, newPassword) {
106
96
  try {
107
97
  await axios.post(`${HEADLESS_BASE}/account/password/change`, {
@@ -113,9 +103,9 @@ export async function changePassword(currentPassword, newPassword) {
113
103
  throw new Error(extractErrorMessage(error));
114
104
  }
115
105
  }
116
- /**
117
- * Logs the user out via allauth headless.
118
- */
106
+ // -----------------------------
107
+ // Logout / Session
108
+ // -----------------------------
119
109
  export async function logoutSession() {
120
110
  try {
121
111
  const headers = {};
@@ -130,39 +120,36 @@ export async function logoutSession() {
130
120
  }
131
121
  catch (error) {
132
122
  if (error.response && [401, 404, 410].includes(error.response.status)) {
123
+ // Session already gone, nothing to do
133
124
  return;
134
125
  }
135
126
  // eslint-disable-next-line no-console
136
127
  console.error('Logout error:', error);
137
128
  }
138
129
  }
139
- /**
140
- * Starts an OAuth social login flow for the given provider.
141
- * Provider examples: "google", "microsoft".
142
- * * FIX:
143
- * 1. Uses POST instead of GET (standard for headless init flows).
144
- * 2. Uses the correct path '/providers/{provider}/login'.
145
- */
130
+ export async function fetchHeadlessSession() {
131
+ const res = await axios.get(`${HEADLESS_BASE}/auth/session`, {
132
+ withCredentials: true,
133
+ });
134
+ return res.data;
135
+ }
136
+ // -----------------------------
137
+ // Social login
138
+ // -----------------------------
146
139
  export function startSocialLogin(provider) {
147
- // Basic safety check to avoid runtime errors in non-browser environments
148
140
  if (typeof window === 'undefined') {
149
141
  throw new Error('Social login is only available in a browser environment.');
150
142
  }
143
+ // Classic allauth HTML flow
151
144
  window.location.href = `/accounts/${provider}/login/?process=login`;
152
145
  }
153
- /**
154
- * Prüft, ob ein Access-Code gültig ist.
155
- * Erwartet: POST /api/access-codes/validate/ { code }
156
- * Antwort: { valid: true/false } oder 400 mit detail-Fehler.
157
- */
146
+ // -----------------------------
147
+ // Invitation / access code
148
+ // -----------------------------
158
149
  export async function validateAccessCode(code) {
159
150
  const res = await axios.post(`${ACCESS_CODES_BASE}/validate/`, { code }, { withCredentials: true });
160
- return res.data; // { valid: bool } oder Error
151
+ return res.data;
161
152
  }
162
- /**
163
- * Fordert eine Einladung mit optionalem Access-Code an.
164
- * Backend prüft den Code noch einmal serverseitig.
165
- */
166
153
  export async function requestInviteWithCode(email, accessCode) {
167
154
  const payload = { email };
168
155
  if (accessCode) {
@@ -171,15 +158,9 @@ export async function requestInviteWithCode(email, accessCode) {
171
158
  const res = await axios.post(`${USERS_BASE}/invite/`, payload, { withCredentials: true });
172
159
  return res.data;
173
160
  }
174
- /**
175
- * Loads the current session information directly from allauth headless.
176
- */
177
- export async function fetchHeadlessSession() {
178
- const res = await axios.get(`${HEADLESS_BASE}/auth/session`, {
179
- withCredentials: true,
180
- });
181
- return res.data;
182
- }
161
+ // -----------------------------
162
+ // Custom password-reset via uid/token
163
+ // -----------------------------
183
164
  export async function verifyResetToken(uid, token) {
184
165
  const res = await axios.get(`${USERS_BASE}/password-reset/${uid}/${token}/`, { withCredentials: true });
185
166
  return res.data;
@@ -188,16 +169,23 @@ export async function setNewPassword(uid, token, newPassword) {
188
169
  const res = await axios.post(`${USERS_BASE}/password-reset/${uid}/${token}/`, { new_password: newPassword }, { withCredentials: true });
189
170
  return res.data;
190
171
  }
191
- // Start: Optionen für Credential-Erzeugung holen
172
+ // -----------------------------
173
+ // WebAuthn / Passkeys: register
174
+ // -----------------------------
192
175
  async function registerPasskeyStart({ passwordless = true } = {}) {
193
176
  ensureWebAuthnSupport();
194
177
  const res = await axios.get(`${HEADLESS_BASE}/account/authenticators/webauthn`, {
195
178
  params: passwordless ? { passwordless: true } : {},
196
179
  withCredentials: true,
197
180
  });
198
- return res.data; // JSON-Options vom Server
181
+ const data = res.data || {};
182
+ // allauth.headless: { creation_options: { publicKey: { ... } } }
183
+ // Wir wollen am Ende den INNEREN publicKey-Block haben:
184
+ const publicKeyJson = (data.creation_options && data.creation_options.publicKey) ||
185
+ data.publicKey ||
186
+ data;
187
+ return publicKeyJson;
199
188
  }
200
- // Complete: Credential an Server schicken
201
189
  async function registerPasskeyComplete(credentialJson, name = 'Passkey') {
202
190
  const res = await axios.post(`${HEADLESS_BASE}/account/authenticators/webauthn`, {
203
191
  credential: credentialJson,
@@ -205,22 +193,22 @@ async function registerPasskeyComplete(credentialJson, name = 'Passkey') {
205
193
  }, { withCredentials: true });
206
194
  return res.data;
207
195
  }
208
- // High-level Helper, den das UI aufruft
209
196
  export async function registerPasskey(name = 'Passkey') {
210
197
  ensureWebAuthnSupport();
211
198
  if (!hasJsonWebAuthn) {
212
199
  throw new Error('Passkey JSON helpers are not available in this browser.');
213
200
  }
214
- const optionsJson = await registerPasskeyStart({ passwordless: true });
201
+ // Hier bekommst du bereits den inneren publicKey-Block mit challenge etc.
202
+ const publicKeyJson = await registerPasskeyStart({ passwordless: true });
215
203
  let credential;
216
204
  try {
217
- const publicKeyOptions = window.PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
205
+ // publicKeyJson hat challenge auf Top-Level
206
+ const publicKeyOptions = window.PublicKeyCredential.parseCreationOptionsFromJSON(publicKeyJson);
218
207
  credential = await navigator.credentials.create({
219
208
  publicKey: publicKeyOptions,
220
209
  });
221
210
  }
222
211
  catch (err) {
223
- // Typischer Fall: User klickt "Abbrechen"
224
212
  if (err && err.name === 'NotAllowedError') {
225
213
  throw new Error('Passkey creation was cancelled by the user.');
226
214
  }
@@ -232,32 +220,38 @@ export async function registerPasskey(name = 'Passkey') {
232
220
  const credentialJson = credential.toJSON();
233
221
  return registerPasskeyComplete(credentialJson, name);
234
222
  }
223
+ // -----------------------------
224
+ // WebAuthn / Passkeys: login
225
+ // -----------------------------
235
226
  async function loginWithPasskeyStart() {
236
227
  ensureWebAuthnSupport();
237
228
  const res = await axios.get(`${HEADLESS_BASE}/auth/webauthn/login`, { withCredentials: true });
238
- return res.data;
229
+ const data = res.data || {};
230
+ // allauth.headless: { request_options: { publicKey: { ... } } }
231
+ const requestOptionsJson = (data.request_options && data.request_options.publicKey) ||
232
+ data.request_options ||
233
+ data;
234
+ return requestOptionsJson;
239
235
  }
240
236
  async function loginWithPasskeyComplete(credentialJson) {
241
237
  const res = await axios.post(`${HEADLESS_BASE}/auth/webauthn/login`, { credential: credentialJson }, { withCredentials: true });
242
238
  return res.data;
243
239
  }
244
- // Public API, die du im UI verwendest
245
240
  export async function loginWithPasskey() {
246
241
  ensureWebAuthnSupport();
247
242
  if (!hasJsonWebAuthn) {
248
243
  throw new Error('Passkey JSON helpers are not available in this browser.');
249
244
  }
250
- const optionsJson = await loginWithPasskeyStart();
245
+ const requestOptionsJson = await loginWithPasskeyStart();
251
246
  let assertion;
252
247
  try {
253
- const publicKeyOptions = window.PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
248
+ const publicKeyOptions = window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptionsJson);
254
249
  assertion = await navigator.credentials.get({
255
250
  publicKey: publicKeyOptions,
256
251
  });
257
252
  }
258
253
  catch (err) {
259
254
  if (err && err.name === 'NotAllowedError') {
260
- // User hat abgebrochen oder Timeout
261
255
  throw new Error('Passkey authentication was cancelled by the user.');
262
256
  }
263
257
  throw err;
@@ -271,13 +265,12 @@ export async function loginWithPasskey() {
271
265
  await loginWithPasskeyComplete(credentialJson);
272
266
  }
273
267
  catch (err) {
274
- // z.B. 401, obwohl das Credential eigentlich ok war
268
+ // For example 401 although credential looked fine
275
269
  postError = err;
276
270
  }
277
- // Versuchen, die aktuelle Session zu laden
278
271
  try {
279
272
  const user = await fetchCurrentUser();
280
- return { user }; // <--- wie loginWithPassword
273
+ return { user };
281
274
  }
282
275
  catch (err) {
283
276
  if (postError) {
@@ -286,6 +279,9 @@ export async function loginWithPasskey() {
286
279
  throw new Error(extractErrorMessage(err));
287
280
  }
288
281
  }
282
+ // -----------------------------
283
+ // Aggregated API object
284
+ // -----------------------------
289
285
  export const authApi = {
290
286
  fetchCurrentUser,
291
287
  updateUserProfile,
@@ -302,3 +298,4 @@ export const authApi = {
302
298
  validateAccessCode,
303
299
  requestInviteWithCode,
304
300
  };
301
+ export default authApi;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micha.bigler/ui-core-micha",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "private": false,
@@ -1,7 +1,9 @@
1
1
  import axios from 'axios';
2
- import { HEADLESS_BASE, USERS_BASE, ACCESS_CODES_BASE } from './authConfig';
2
+ import { HEADLESS_BASE, USERS_BASE, ACCESS_CODES_BASE } from './authConfig';
3
3
 
4
- // Helper to normalise error messages from API responses
4
+ // -----------------------------
5
+ // Error helper
6
+ // -----------------------------
5
7
  function extractErrorMessage(error) {
6
8
  const data = error.response?.data;
7
9
  if (!data) {
@@ -16,13 +18,20 @@ function extractErrorMessage(error) {
16
18
  return 'An error occurred. Please try again.';
17
19
  }
18
20
 
19
- // Helper to get CSRF token from cookies manually
21
+ // -----------------------------
22
+ // CSRF helper
23
+ // -----------------------------
20
24
  function getCsrfToken() {
21
- if (!document.cookie) return null;
25
+ if (typeof document === 'undefined' || !document.cookie) {
26
+ return null;
27
+ }
22
28
  const match = document.cookie.match(/csrftoken=([^;]+)/);
23
29
  return match ? match[1] : null;
24
30
  }
25
31
 
32
+ // -----------------------------
33
+ // WebAuthn capability helpers
34
+ // -----------------------------
26
35
  const hasJsonWebAuthn =
27
36
  typeof window !== 'undefined' &&
28
37
  typeof window.PublicKeyCredential !== 'undefined' &&
@@ -40,9 +49,9 @@ function ensureWebAuthnSupport() {
40
49
  }
41
50
  }
42
51
 
43
- /**
44
- * Fetches the current authenticated user from your own User API.
45
- */
52
+ // -----------------------------
53
+ // User-related helpers
54
+ // -----------------------------
46
55
  export async function fetchCurrentUser() {
47
56
  const res = await axios.get(`${USERS_BASE}/current/`, {
48
57
  withCredentials: true,
@@ -50,9 +59,6 @@ export async function fetchCurrentUser() {
50
59
  return res.data;
51
60
  }
52
61
 
53
- /**
54
- * Updates the user profile fields.
55
- */
56
62
  export async function updateUserProfile(data) {
57
63
  const res = await axios.patch(`${USERS_BASE}/current/`, data, {
58
64
  withCredentials: true,
@@ -60,35 +66,28 @@ export async function updateUserProfile(data) {
60
66
  return res.data;
61
67
  }
62
68
 
63
- /**
64
- * Logs a user in using email/password via allauth headless.
65
- */
69
+ // -----------------------------
70
+ // Authentication: password
71
+ // -----------------------------
66
72
  export async function loginWithPassword(email, password) {
67
73
  try {
68
74
  await axios.post(
69
75
  `${HEADLESS_BASE}/auth/login`,
70
- {
71
- email,
72
- password,
73
- },
76
+ { email, password },
74
77
  { withCredentials: true },
75
78
  );
76
79
  } catch (error) {
77
80
  if (error.response && error.response.status === 409) {
78
- // User is already logged in, continue and fetch current user
81
+ // Already logged in: continue and fetch current user
79
82
  } else {
80
83
  throw new Error(extractErrorMessage(error));
81
84
  }
82
85
  }
83
86
 
84
87
  const user = await fetchCurrentUser();
85
- // Normalise return shape so callers always get { user }
86
88
  return { user };
87
89
  }
88
90
 
89
- /**
90
- * Requests a password reset email via allauth headless.
91
- */
92
91
  export async function requestPasswordReset(email) {
93
92
  try {
94
93
  await axios.post(
@@ -101,17 +100,12 @@ export async function requestPasswordReset(email) {
101
100
  }
102
101
  }
103
102
 
104
- /**
105
- * Sets a new password using a reset key (from email link).
106
- */
103
+ // (Falls du es noch brauchst: klassischer allauth-Key-Flow)
107
104
  export async function resetPasswordWithKey(key, newPassword) {
108
105
  try {
109
106
  await axios.post(
110
107
  `${HEADLESS_BASE}/auth/password/reset/key`,
111
- {
112
- key,
113
- password: newPassword,
114
- },
108
+ { key, password: newPassword },
115
109
  { withCredentials: true },
116
110
  );
117
111
  } catch (error) {
@@ -119,9 +113,6 @@ export async function resetPasswordWithKey(key, newPassword) {
119
113
  }
120
114
  }
121
115
 
122
- /**
123
- * Changes the password for an authenticated user.
124
- */
125
116
  export async function changePassword(currentPassword, newPassword) {
126
117
  try {
127
118
  await axios.post(
@@ -137,9 +128,9 @@ export async function changePassword(currentPassword, newPassword) {
137
128
  }
138
129
  }
139
130
 
140
- /**
141
- * Logs the user out via allauth headless.
142
- */
131
+ // -----------------------------
132
+ // Logout / Session
133
+ // -----------------------------
143
134
  export async function logoutSession() {
144
135
  try {
145
136
  const headers = {};
@@ -150,13 +141,14 @@ export async function logoutSession() {
150
141
 
151
142
  await axios.delete(
152
143
  `${HEADLESS_BASE}/auth/session`,
153
- {
144
+ {
154
145
  withCredentials: true,
155
- headers,
146
+ headers,
156
147
  },
157
148
  );
158
149
  } catch (error) {
159
150
  if (error.response && [401, 404, 410].includes(error.response.status)) {
151
+ // Session already gone, nothing to do
160
152
  return;
161
153
  }
162
154
  // eslint-disable-next-line no-console
@@ -164,40 +156,37 @@ export async function logoutSession() {
164
156
  }
165
157
  }
166
158
 
167
- /**
168
- * Starts an OAuth social login flow for the given provider.
169
- * Provider examples: "google", "microsoft".
170
- * * FIX:
171
- * 1. Uses POST instead of GET (standard for headless init flows).
172
- * 2. Uses the correct path '/providers/{provider}/login'.
173
- */
159
+ export async function fetchHeadlessSession() {
160
+ const res = await axios.get(`${HEADLESS_BASE}/auth/session`, {
161
+ withCredentials: true,
162
+ });
163
+ return res.data;
164
+ }
165
+
166
+ // -----------------------------
167
+ // Social login
168
+ // -----------------------------
174
169
  export function startSocialLogin(provider) {
175
- // Basic safety check to avoid runtime errors in non-browser environments
176
170
  if (typeof window === 'undefined') {
177
171
  throw new Error('Social login is only available in a browser environment.');
178
172
  }
179
173
 
174
+ // Classic allauth HTML flow
180
175
  window.location.href = `/accounts/${provider}/login/?process=login`;
181
176
  }
182
177
 
183
- /**
184
- * Prüft, ob ein Access-Code gültig ist.
185
- * Erwartet: POST /api/access-codes/validate/ { code }
186
- * Antwort: { valid: true/false } oder 400 mit detail-Fehler.
187
- */
178
+ // -----------------------------
179
+ // Invitation / access code
180
+ // -----------------------------
188
181
  export async function validateAccessCode(code) {
189
182
  const res = await axios.post(
190
183
  `${ACCESS_CODES_BASE}/validate/`,
191
184
  { code },
192
185
  { withCredentials: true },
193
186
  );
194
- return res.data; // { valid: bool } oder Error
187
+ return res.data;
195
188
  }
196
189
 
197
- /**
198
- * Fordert eine Einladung mit optionalem Access-Code an.
199
- * Backend prüft den Code noch einmal serverseitig.
200
- */
201
190
  export async function requestInviteWithCode(email, accessCode) {
202
191
  const payload = { email };
203
192
  if (accessCode) {
@@ -212,19 +201,9 @@ export async function requestInviteWithCode(email, accessCode) {
212
201
  return res.data;
213
202
  }
214
203
 
215
-
216
- /**
217
- * Loads the current session information directly from allauth headless.
218
- */
219
- export async function fetchHeadlessSession() {
220
- const res = await axios.get(`${HEADLESS_BASE}/auth/session`, {
221
- withCredentials: true,
222
- });
223
- return res.data;
224
- }
225
-
226
-
227
-
204
+ // -----------------------------
205
+ // Custom password-reset via uid/token
206
+ // -----------------------------
228
207
  export async function verifyResetToken(uid, token) {
229
208
  const res = await axios.get(
230
209
  `${USERS_BASE}/password-reset/${uid}/${token}/`,
@@ -242,7 +221,9 @@ export async function setNewPassword(uid, token, newPassword) {
242
221
  return res.data;
243
222
  }
244
223
 
245
- // Start: Optionen für Credential-Erzeugung holen
224
+ // -----------------------------
225
+ // WebAuthn / Passkeys: register
226
+ // -----------------------------
246
227
  async function registerPasskeyStart({ passwordless = true } = {}) {
247
228
  ensureWebAuthnSupport();
248
229
 
@@ -253,10 +234,19 @@ async function registerPasskeyStart({ passwordless = true } = {}) {
253
234
  withCredentials: true,
254
235
  },
255
236
  );
256
- return res.data; // JSON-Options vom Server
237
+
238
+ const data = res.data || {};
239
+
240
+ // allauth.headless: { creation_options: { publicKey: { ... } } }
241
+ // Wir wollen am Ende den INNEREN publicKey-Block haben:
242
+ const publicKeyJson =
243
+ (data.creation_options && data.creation_options.publicKey) ||
244
+ data.publicKey ||
245
+ data;
246
+
247
+ return publicKeyJson;
257
248
  }
258
249
 
259
- // Complete: Credential an Server schicken
260
250
  async function registerPasskeyComplete(credentialJson, name = 'Passkey') {
261
251
  const res = await axios.post(
262
252
  `${HEADLESS_BASE}/account/authenticators/webauthn`,
@@ -269,7 +259,6 @@ async function registerPasskeyComplete(credentialJson, name = 'Passkey') {
269
259
  return res.data;
270
260
  }
271
261
 
272
- // High-level Helper, den das UI aufruft
273
262
  export async function registerPasskey(name = 'Passkey') {
274
263
  ensureWebAuthnSupport();
275
264
 
@@ -277,18 +266,19 @@ export async function registerPasskey(name = 'Passkey') {
277
266
  throw new Error('Passkey JSON helpers are not available in this browser.');
278
267
  }
279
268
 
280
- const optionsJson = await registerPasskeyStart({ passwordless: true });
269
+ // Hier bekommst du bereits den inneren publicKey-Block mit challenge etc.
270
+ const publicKeyJson = await registerPasskeyStart({ passwordless: true });
281
271
 
282
272
  let credential;
283
273
  try {
274
+ // publicKeyJson hat challenge auf Top-Level
284
275
  const publicKeyOptions =
285
- window.PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
276
+ window.PublicKeyCredential.parseCreationOptionsFromJSON(publicKeyJson);
286
277
 
287
278
  credential = await navigator.credentials.create({
288
279
  publicKey: publicKeyOptions,
289
280
  });
290
281
  } catch (err) {
291
- // Typischer Fall: User klickt "Abbrechen"
292
282
  if (err && err.name === 'NotAllowedError') {
293
283
  throw new Error('Passkey creation was cancelled by the user.');
294
284
  }
@@ -303,6 +293,9 @@ export async function registerPasskey(name = 'Passkey') {
303
293
  return registerPasskeyComplete(credentialJson, name);
304
294
  }
305
295
 
296
+ // -----------------------------
297
+ // WebAuthn / Passkeys: login
298
+ // -----------------------------
306
299
  async function loginWithPasskeyStart() {
307
300
  ensureWebAuthnSupport();
308
301
 
@@ -310,7 +303,16 @@ async function loginWithPasskeyStart() {
310
303
  `${HEADLESS_BASE}/auth/webauthn/login`,
311
304
  { withCredentials: true },
312
305
  );
313
- return res.data;
306
+
307
+ const data = res.data || {};
308
+
309
+ // allauth.headless: { request_options: { publicKey: { ... } } }
310
+ const requestOptionsJson =
311
+ (data.request_options && data.request_options.publicKey) ||
312
+ data.request_options ||
313
+ data;
314
+
315
+ return requestOptionsJson;
314
316
  }
315
317
 
316
318
  async function loginWithPasskeyComplete(credentialJson) {
@@ -322,7 +324,6 @@ async function loginWithPasskeyComplete(credentialJson) {
322
324
  return res.data;
323
325
  }
324
326
 
325
- // Public API, die du im UI verwendest
326
327
  export async function loginWithPasskey() {
327
328
  ensureWebAuthnSupport();
328
329
 
@@ -330,19 +331,18 @@ export async function loginWithPasskey() {
330
331
  throw new Error('Passkey JSON helpers are not available in this browser.');
331
332
  }
332
333
 
333
- const optionsJson = await loginWithPasskeyStart();
334
+ const requestOptionsJson = await loginWithPasskeyStart();
334
335
 
335
336
  let assertion;
336
337
  try {
337
338
  const publicKeyOptions =
338
- window.PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
339
+ window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptionsJson);
339
340
 
340
341
  assertion = await navigator.credentials.get({
341
342
  publicKey: publicKeyOptions,
342
343
  });
343
344
  } catch (err) {
344
345
  if (err && err.name === 'NotAllowedError') {
345
- // User hat abgebrochen oder Timeout
346
346
  throw new Error('Passkey authentication was cancelled by the user.');
347
347
  }
348
348
  throw err;
@@ -358,14 +358,13 @@ export async function loginWithPasskey() {
358
358
  try {
359
359
  await loginWithPasskeyComplete(credentialJson);
360
360
  } catch (err) {
361
- // z.B. 401, obwohl das Credential eigentlich ok war
361
+ // For example 401 although credential looked fine
362
362
  postError = err;
363
363
  }
364
364
 
365
- // Versuchen, die aktuelle Session zu laden
366
365
  try {
367
366
  const user = await fetchCurrentUser();
368
- return { user }; // <--- wie loginWithPassword
367
+ return { user };
369
368
  } catch (err) {
370
369
  if (postError) {
371
370
  throw new Error(extractErrorMessage(postError));
@@ -374,7 +373,9 @@ export async function loginWithPasskey() {
374
373
  }
375
374
  }
376
375
 
377
-
376
+ // -----------------------------
377
+ // Aggregated API object
378
+ // -----------------------------
378
379
  export const authApi = {
379
380
  fetchCurrentUser,
380
381
  updateUserProfile,
@@ -390,4 +391,6 @@ export const authApi = {
390
391
  registerPasskey,
391
392
  validateAccessCode,
392
393
  requestInviteWithCode,
393
- };
394
+ };
395
+
396
+ export default authApi;