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

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,22 @@ 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
+ const creationOptionsJson = (data.creation_options && data.creation_options.publicKey) ||
184
+ data.creation_options ||
185
+ data;
186
+ return creationOptionsJson;
199
187
  }
200
- // Complete: Credential an Server schicken
201
188
  async function registerPasskeyComplete(credentialJson, name = 'Passkey') {
202
189
  const res = await axios.post(`${HEADLESS_BASE}/account/authenticators/webauthn`, {
203
190
  credential: credentialJson,
@@ -205,22 +192,20 @@ async function registerPasskeyComplete(credentialJson, name = 'Passkey') {
205
192
  }, { withCredentials: true });
206
193
  return res.data;
207
194
  }
208
- // High-level Helper, den das UI aufruft
209
195
  export async function registerPasskey(name = 'Passkey') {
210
196
  ensureWebAuthnSupport();
211
197
  if (!hasJsonWebAuthn) {
212
198
  throw new Error('Passkey JSON helpers are not available in this browser.');
213
199
  }
214
- const optionsJson = await registerPasskeyStart({ passwordless: true });
200
+ const creationOptionsJson = await registerPasskeyStart({ passwordless: true });
215
201
  let credential;
216
202
  try {
217
- const publicKeyOptions = window.PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
203
+ const publicKeyOptions = window.PublicKeyCredential.parseCreationOptionsFromJSON(creationOptionsJson);
218
204
  credential = await navigator.credentials.create({
219
205
  publicKey: publicKeyOptions,
220
206
  });
221
207
  }
222
208
  catch (err) {
223
- // Typischer Fall: User klickt "Abbrechen"
224
209
  if (err && err.name === 'NotAllowedError') {
225
210
  throw new Error('Passkey creation was cancelled by the user.');
226
211
  }
@@ -232,32 +217,38 @@ export async function registerPasskey(name = 'Passkey') {
232
217
  const credentialJson = credential.toJSON();
233
218
  return registerPasskeyComplete(credentialJson, name);
234
219
  }
220
+ // -----------------------------
221
+ // WebAuthn / Passkeys: login
222
+ // -----------------------------
235
223
  async function loginWithPasskeyStart() {
236
224
  ensureWebAuthnSupport();
237
225
  const res = await axios.get(`${HEADLESS_BASE}/auth/webauthn/login`, { withCredentials: true });
238
- return res.data;
226
+ const data = res.data || {};
227
+ // allauth.headless: { request_options: { publicKey: { ... } } }
228
+ const requestOptionsJson = (data.request_options && data.request_options.publicKey) ||
229
+ data.request_options ||
230
+ data;
231
+ return requestOptionsJson;
239
232
  }
240
233
  async function loginWithPasskeyComplete(credentialJson) {
241
234
  const res = await axios.post(`${HEADLESS_BASE}/auth/webauthn/login`, { credential: credentialJson }, { withCredentials: true });
242
235
  return res.data;
243
236
  }
244
- // Public API, die du im UI verwendest
245
237
  export async function loginWithPasskey() {
246
238
  ensureWebAuthnSupport();
247
239
  if (!hasJsonWebAuthn) {
248
240
  throw new Error('Passkey JSON helpers are not available in this browser.');
249
241
  }
250
- const optionsJson = await loginWithPasskeyStart();
242
+ const requestOptionsJson = await loginWithPasskeyStart();
251
243
  let assertion;
252
244
  try {
253
- const publicKeyOptions = window.PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
245
+ const publicKeyOptions = window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptionsJson);
254
246
  assertion = await navigator.credentials.get({
255
247
  publicKey: publicKeyOptions,
256
248
  });
257
249
  }
258
250
  catch (err) {
259
251
  if (err && err.name === 'NotAllowedError') {
260
- // User hat abgebrochen oder Timeout
261
252
  throw new Error('Passkey authentication was cancelled by the user.');
262
253
  }
263
254
  throw err;
@@ -271,13 +262,12 @@ export async function loginWithPasskey() {
271
262
  await loginWithPasskeyComplete(credentialJson);
272
263
  }
273
264
  catch (err) {
274
- // z.B. 401, obwohl das Credential eigentlich ok war
265
+ // For example 401 although credential looked fine
275
266
  postError = err;
276
267
  }
277
- // Versuchen, die aktuelle Session zu laden
278
268
  try {
279
269
  const user = await fetchCurrentUser();
280
- return { user }; // <--- wie loginWithPassword
270
+ return { user };
281
271
  }
282
272
  catch (err) {
283
273
  if (postError) {
@@ -286,6 +276,9 @@ export async function loginWithPasskey() {
286
276
  throw new Error(extractErrorMessage(err));
287
277
  }
288
278
  }
279
+ // -----------------------------
280
+ // Aggregated API object
281
+ // -----------------------------
289
282
  export const authApi = {
290
283
  fetchCurrentUser,
291
284
  updateUserProfile,
@@ -302,3 +295,4 @@ export const authApi = {
302
295
  validateAccessCode,
303
296
  requestInviteWithCode,
304
297
  };
298
+ 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.3",
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,18 @@ 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
+ const creationOptionsJson =
242
+ (data.creation_options && data.creation_options.publicKey) ||
243
+ data.creation_options ||
244
+ data;
245
+
246
+ return creationOptionsJson;
257
247
  }
258
248
 
259
- // Complete: Credential an Server schicken
260
249
  async function registerPasskeyComplete(credentialJson, name = 'Passkey') {
261
250
  const res = await axios.post(
262
251
  `${HEADLESS_BASE}/account/authenticators/webauthn`,
@@ -269,7 +258,6 @@ async function registerPasskeyComplete(credentialJson, name = 'Passkey') {
269
258
  return res.data;
270
259
  }
271
260
 
272
- // High-level Helper, den das UI aufruft
273
261
  export async function registerPasskey(name = 'Passkey') {
274
262
  ensureWebAuthnSupport();
275
263
 
@@ -277,18 +265,17 @@ export async function registerPasskey(name = 'Passkey') {
277
265
  throw new Error('Passkey JSON helpers are not available in this browser.');
278
266
  }
279
267
 
280
- const optionsJson = await registerPasskeyStart({ passwordless: true });
268
+ const creationOptionsJson = await registerPasskeyStart({ passwordless: true });
281
269
 
282
270
  let credential;
283
271
  try {
284
272
  const publicKeyOptions =
285
- window.PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
273
+ window.PublicKeyCredential.parseCreationOptionsFromJSON(creationOptionsJson);
286
274
 
287
275
  credential = await navigator.credentials.create({
288
276
  publicKey: publicKeyOptions,
289
277
  });
290
278
  } catch (err) {
291
- // Typischer Fall: User klickt "Abbrechen"
292
279
  if (err && err.name === 'NotAllowedError') {
293
280
  throw new Error('Passkey creation was cancelled by the user.');
294
281
  }
@@ -303,6 +290,9 @@ export async function registerPasskey(name = 'Passkey') {
303
290
  return registerPasskeyComplete(credentialJson, name);
304
291
  }
305
292
 
293
+ // -----------------------------
294
+ // WebAuthn / Passkeys: login
295
+ // -----------------------------
306
296
  async function loginWithPasskeyStart() {
307
297
  ensureWebAuthnSupport();
308
298
 
@@ -310,7 +300,16 @@ async function loginWithPasskeyStart() {
310
300
  `${HEADLESS_BASE}/auth/webauthn/login`,
311
301
  { withCredentials: true },
312
302
  );
313
- return res.data;
303
+
304
+ const data = res.data || {};
305
+
306
+ // allauth.headless: { request_options: { publicKey: { ... } } }
307
+ const requestOptionsJson =
308
+ (data.request_options && data.request_options.publicKey) ||
309
+ data.request_options ||
310
+ data;
311
+
312
+ return requestOptionsJson;
314
313
  }
315
314
 
316
315
  async function loginWithPasskeyComplete(credentialJson) {
@@ -322,7 +321,6 @@ async function loginWithPasskeyComplete(credentialJson) {
322
321
  return res.data;
323
322
  }
324
323
 
325
- // Public API, die du im UI verwendest
326
324
  export async function loginWithPasskey() {
327
325
  ensureWebAuthnSupport();
328
326
 
@@ -330,19 +328,18 @@ export async function loginWithPasskey() {
330
328
  throw new Error('Passkey JSON helpers are not available in this browser.');
331
329
  }
332
330
 
333
- const optionsJson = await loginWithPasskeyStart();
331
+ const requestOptionsJson = await loginWithPasskeyStart();
334
332
 
335
333
  let assertion;
336
334
  try {
337
335
  const publicKeyOptions =
338
- window.PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
336
+ window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptionsJson);
339
337
 
340
338
  assertion = await navigator.credentials.get({
341
339
  publicKey: publicKeyOptions,
342
340
  });
343
341
  } catch (err) {
344
342
  if (err && err.name === 'NotAllowedError') {
345
- // User hat abgebrochen oder Timeout
346
343
  throw new Error('Passkey authentication was cancelled by the user.');
347
344
  }
348
345
  throw err;
@@ -358,14 +355,13 @@ export async function loginWithPasskey() {
358
355
  try {
359
356
  await loginWithPasskeyComplete(credentialJson);
360
357
  } catch (err) {
361
- // z.B. 401, obwohl das Credential eigentlich ok war
358
+ // For example 401 although credential looked fine
362
359
  postError = err;
363
360
  }
364
361
 
365
- // Versuchen, die aktuelle Session zu laden
366
362
  try {
367
363
  const user = await fetchCurrentUser();
368
- return { user }; // <--- wie loginWithPassword
364
+ return { user };
369
365
  } catch (err) {
370
366
  if (postError) {
371
367
  throw new Error(extractErrorMessage(postError));
@@ -374,7 +370,9 @@ export async function loginWithPasskey() {
374
370
  }
375
371
  }
376
372
 
377
-
373
+ // -----------------------------
374
+ // Aggregated API object
375
+ // -----------------------------
378
376
  export const authApi = {
379
377
  fetchCurrentUser,
380
378
  updateUserProfile,
@@ -390,4 +388,6 @@ export const authApi = {
390
388
  registerPasskey,
391
389
  validateAccessCode,
392
390
  requestInviteWithCode,
393
- };
391
+ };
392
+
393
+ export default authApi;