@micha.bigler/ui-core-micha 1.4.14 → 1.4.17

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,14 +1,9 @@
1
- import axios from 'axios';
1
+ import apiClient from './apiClient';
2
2
  import { HEADLESS_BASE, USERS_BASE, ACCESS_CODES_BASE } from './authConfig';
3
3
 
4
4
  // -----------------------------
5
5
  // WebAuthn Serialization Helpers
6
6
  // -----------------------------
7
-
8
- /**
9
- * Converts an ArrayBuffer to a Base64URL encoded string.
10
- * Required for manual credential serialization when .toJSON() is missing.
11
- */
12
7
  function bufferToBase64URL(buffer) {
13
8
  const bytes = new Uint8Array(buffer);
14
9
  let str = '';
@@ -19,16 +14,11 @@ function bufferToBase64URL(buffer) {
19
14
  return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
20
15
  }
21
16
 
22
- /**
23
- * Serializes a PublicKeyCredential into a JSON-compatible object.
24
- * Acts as a polyfill for environments where credential.toJSON() is missing (e.g., Proton Pass).
25
- */
26
17
  function serializeCredential(credential) {
27
18
  if (typeof credential.toJSON === 'function') {
28
19
  return credential.toJSON();
29
20
  }
30
21
 
31
- // Manual serialization for extensions that return incomplete objects
32
22
  const p = {
33
23
  id: credential.id,
34
24
  rawId: bufferToBase64URL(credential.rawId),
@@ -39,26 +29,21 @@ function serializeCredential(credential) {
39
29
  };
40
30
 
41
31
  if (credential.response.attestationObject) {
42
- // Registration specific
43
32
  p.response.attestationObject = bufferToBase64URL(credential.response.attestationObject);
44
33
  }
45
34
 
46
35
  if (credential.response.authenticatorData) {
47
- // Login specific
48
36
  p.response.authenticatorData = bufferToBase64URL(credential.response.authenticatorData);
49
37
  }
50
38
 
51
39
  if (credential.response.signature) {
52
- // Login specific
53
40
  p.response.signature = bufferToBase64URL(credential.response.signature);
54
41
  }
55
42
 
56
43
  if (credential.response.userHandle) {
57
- // Login specific
58
44
  p.response.userHandle = bufferToBase64URL(credential.response.userHandle);
59
45
  }
60
46
 
61
- // Include clientExtensionResults if present
62
47
  if (typeof credential.getClientExtensionResults === 'function') {
63
48
  p.clientExtensionResults = credential.getClientExtensionResults();
64
49
  }
@@ -68,7 +53,6 @@ function serializeCredential(credential) {
68
53
 
69
54
  function normaliseApiError(error, defaultCode = 'Auth.GENERIC_ERROR') {
70
55
  const info = extractErrorInfo(error);
71
-
72
56
  const code = info.code || defaultCode;
73
57
  const message = info.message || code || defaultCode;
74
58
 
@@ -80,13 +64,8 @@ function normaliseApiError(error, defaultCode = 'Auth.GENERIC_ERROR') {
80
64
  return err;
81
65
  }
82
66
 
83
-
84
67
  function mapAllauthDetailToCode(detail) {
85
68
  if (!detail || typeof detail !== 'string') return null;
86
- // Optional: bekannte allauth-Texte auf eigene Codes mappen
87
- // if (detail.includes('Unable to log in with provided credentials')) {
88
- // return 'Auth.INVALID_CREDENTIALS';
89
- // }
90
69
  return null;
91
70
  }
92
71
 
@@ -95,56 +74,27 @@ function extractErrorInfo(error) {
95
74
  const data = error.response?.data ?? null;
96
75
 
97
76
  if (!data) {
98
- return {
99
- status,
100
- code: null,
101
- message: error.message || null,
102
- raw: null,
103
- };
77
+ return { status, code: null, message: error.message || null, raw: null };
104
78
  }
105
79
 
106
80
  if (typeof data.code === 'string') {
107
- return {
108
- status,
109
- code: data.code,
110
- message: null,
111
- raw: data,
112
- };
81
+ return { status, code: data.code, message: null, raw: data };
113
82
  }
114
83
 
115
84
  if (typeof data.detail === 'string') {
116
85
  const mapped = mapAllauthDetailToCode(data.detail);
117
- return {
118
- status,
119
- code: mapped,
120
- message: data.detail,
121
- raw: data,
122
- };
86
+ return { status, code: mapped, message: data.detail, raw: data };
123
87
  }
124
88
 
125
- if (
126
- Array.isArray(data.non_field_errors) &&
127
- data.non_field_errors.length > 0
128
- ) {
89
+ if (Array.isArray(data.non_field_errors) && data.non_field_errors.length > 0) {
129
90
  const msg = data.non_field_errors[0];
130
91
  const mapped = mapAllauthDetailToCode(msg);
131
- return {
132
- status,
133
- code: mapped,
134
- message: msg,
135
- raw: data,
136
- };
92
+ return { status, code: mapped, message: msg, raw: data };
137
93
  }
138
94
 
139
- return {
140
- status,
141
- code: null,
142
- message: null,
143
- raw: data,
144
- };
95
+ return { status, code: null, message: null, raw: data };
145
96
  }
146
97
 
147
-
148
98
  // -----------------------------
149
99
  // CSRF helper
150
100
  // -----------------------------
@@ -152,7 +102,8 @@ function getCsrfToken() {
152
102
  if (typeof document === 'undefined' || !document.cookie) {
153
103
  return null;
154
104
  }
155
- const match = document.cookie.match(/csrftoken=([^;]+)/);
105
+ // Robust regex for CSRF token
106
+ const match = document.cookie.match(/(?:^|; )csrftoken=([^;]+)/);
156
107
  return match ? match[1] : null;
157
108
  }
158
109
 
@@ -172,10 +123,7 @@ function ensureWebAuthnSupport() {
172
123
  !window.PublicKeyCredential ||
173
124
  !navigator.credentials
174
125
  ) {
175
- throw normaliseApiError(
176
- new Error('Auth.PASSKEY_NOT_SUPPORTED'),
177
- 'Auth.PASSKEY_NOT_SUPPORTED'
178
- );
126
+ throw normaliseApiError(new Error('Auth.PASSKEY_NOT_SUPPORTED'), 'Auth.PASSKEY_NOT_SUPPORTED');
179
127
  }
180
128
  }
181
129
 
@@ -183,17 +131,14 @@ function ensureWebAuthnSupport() {
183
131
  // User-related helpers
184
132
  // -----------------------------
185
133
  export async function fetchCurrentUser() {
186
- const res = await axios.get(`${USERS_BASE}/current/`, {
187
- withCredentials: true,
188
- });
134
+ const res = await apiClient.get(`${USERS_BASE}/current/`);
189
135
  return res.data;
190
136
  }
191
137
 
192
138
  export async function updateUserProfile(data) {
193
139
  try {
194
- const res = await axios.patch(`${USERS_BASE}/current/`, data, {
195
- withCredentials: true,
196
- });
140
+ // CHANGED: axios -> apiClient
141
+ const res = await apiClient.patch(`${USERS_BASE}/current/`, data);
197
142
  return res.data;
198
143
  } catch (error) {
199
144
  throw normaliseApiError(error, 'Auth.PROFILE_UPDATE_FAILED');
@@ -203,45 +148,22 @@ export async function updateUserProfile(data) {
203
148
  // -----------------------------
204
149
  // Authentication: password
205
150
  // -----------------------------
206
- /*
207
- export async function loginWithPassword(email, password) {
208
- try {
209
- await axios.post(
210
- `${HEADLESS_BASE}/auth/login`,
211
- { email, password },
212
- { withCredentials: true },
213
- );
214
- } catch (error) {
215
- if (error.response && error.response.status === 409) {
216
- // Already logged in: continue and fetch current user
217
- } else {
218
- throw new Error(extractErrorMessage(error));
219
- }
220
- }
221
-
222
- const user = await fetchCurrentUser();
223
- return { user };
224
- }
225
- */
226
151
  export async function requestPasswordReset(email) {
227
152
  try {
228
- await axios.post(
153
+ await apiClient.post(
229
154
  `${USERS_BASE}/reset-request/`,
230
- { email },
231
- { withCredentials: true },
155
+ { email }
232
156
  );
233
157
  } catch (error) {
234
158
  throw normaliseApiError(error, 'Auth.RESET_REQUEST_FAILED');
235
159
  }
236
160
  }
237
161
 
238
- // (Falls du es noch brauchst: klassischer allauth-Key-Flow)
239
162
  export async function resetPasswordWithKey(key, newPassword) {
240
163
  try {
241
- await axios.post(
164
+ await apiClient.post(
242
165
  `${HEADLESS_BASE}/auth/password/reset/key`,
243
- { key, password: newPassword },
244
- { withCredentials: true },
166
+ { key, password: newPassword }
245
167
  );
246
168
  } catch (error) {
247
169
  throw normaliseApiError(error, 'Auth.RESET_WITH_KEY_FAILED');
@@ -250,13 +172,12 @@ export async function resetPasswordWithKey(key, newPassword) {
250
172
 
251
173
  export async function changePassword(currentPassword, newPassword) {
252
174
  try {
253
- await axios.post(
175
+ await apiClient.post(
254
176
  `${HEADLESS_BASE}/account/password/change`,
255
177
  {
256
178
  current_password: currentPassword,
257
179
  new_password: newPassword,
258
- },
259
- { withCredentials: true },
180
+ }
260
181
  );
261
182
  } catch (error) {
262
183
  throw normaliseApiError(error, 'Auth.PASSWORD_CHANGE_FAILED');
@@ -274,16 +195,13 @@ export async function logoutSession() {
274
195
  headers['X-CSRFToken'] = csrfToken;
275
196
  }
276
197
 
277
- await axios.delete(
198
+ // CHANGED: axios -> apiClient
199
+ await apiClient.delete(
278
200
  `${HEADLESS_BASE}/auth/session`,
279
- {
280
- withCredentials: true,
281
- headers,
282
- },
201
+ { headers }
283
202
  );
284
203
  } catch (error) {
285
204
  if (error.response && [401, 404, 410].includes(error.response.status)) {
286
- // Session already gone, nothing to do
287
205
  return;
288
206
  }
289
207
  // eslint-disable-next-line no-console
@@ -292,9 +210,7 @@ export async function logoutSession() {
292
210
  }
293
211
 
294
212
  export async function fetchHeadlessSession() {
295
- const res = await axios.get(`${HEADLESS_BASE}/auth/session`, {
296
- withCredentials: true,
297
- });
213
+ const res = await apiClient.get(`${HEADLESS_BASE}/auth/session`);
298
214
  return res.data;
299
215
  }
300
216
 
@@ -308,8 +224,6 @@ export function startSocialLogin(provider) {
308
224
  'Auth.SOCIAL_LOGIN_NOT_IN_BROWSER'
309
225
  );
310
226
  }
311
-
312
- // Classic allauth HTML flow
313
227
  window.location.href = `/accounts/${provider}/login/?process=login`;
314
228
  }
315
229
 
@@ -318,17 +232,16 @@ export function startSocialLogin(provider) {
318
232
  // -----------------------------
319
233
  export async function validateAccessCode(code) {
320
234
  try {
321
- const res = await axios.post(
235
+ const res = await apiClient.post(
322
236
  `${ACCESS_CODES_BASE}/validate/`,
323
- { code },
324
- { withCredentials: true },
237
+ { code }
325
238
  );
326
239
  return res.data;
327
240
  } catch (error) {
328
- // Default für "irgendwas ist mit dem Code schief"
329
241
  throw normaliseApiError(error, 'Auth.ACCESS_CODE_INVALID_OR_INACTIVE');
330
242
  }
331
243
  }
244
+
332
245
  export async function requestInviteWithCode(email, accessCode) {
333
246
  const payload = { email };
334
247
  if (accessCode) {
@@ -336,14 +249,12 @@ export async function requestInviteWithCode(email, accessCode) {
336
249
  }
337
250
 
338
251
  try {
339
- const res = await axios.post(
252
+ const res = await apiClient.post(
340
253
  `${USERS_BASE}/invite/`,
341
- payload,
342
- { withCredentials: true },
254
+ payload
343
255
  );
344
256
  return res.data;
345
257
  } catch (error) {
346
- // z.B. bei Netzwerkfehlern oder wenn Backend ausnahmsweise keinen code liefert
347
258
  throw normaliseApiError(error, 'Auth.INVITE_FAILED');
348
259
  }
349
260
  }
@@ -353,23 +264,20 @@ export async function requestInviteWithCode(email, accessCode) {
353
264
  // -----------------------------
354
265
  export async function verifyResetToken(uid, token) {
355
266
  try {
356
- const res = await axios.get(
357
- `${USERS_BASE}/password-reset/${uid}/${token}/`,
358
- { withCredentials: true },
267
+ const res = await apiClient.get(
268
+ `${USERS_BASE}/password-reset/${uid}/${token}/`
359
269
  );
360
270
  return res.data;
361
271
  } catch (error) {
362
- // Wenn der Link nicht passt oder Netzwerkfehler: generisch als "Link invalid"
363
272
  throw normaliseApiError(error, 'Auth.RESET_LINK_INVALID');
364
273
  }
365
274
  }
366
275
 
367
276
  export async function setNewPassword(uid, token, newPassword) {
368
277
  try {
369
- const res = await axios.post(
278
+ const res = await apiClient.post(
370
279
  `${USERS_BASE}/password-reset/${uid}/${token}/`,
371
- { new_password: newPassword },
372
- { withCredentials: true },
280
+ { new_password: newPassword }
373
281
  );
374
282
  return res.data;
375
283
  } catch (error) {
@@ -378,180 +286,121 @@ export async function setNewPassword(uid, token, newPassword) {
378
286
  }
379
287
 
380
288
  // -----------------------------
381
- // WebAuthn / Passkeys: register
289
+ // WebAuthn / Passkeys
382
290
  // -----------------------------
383
291
  async function registerPasskeyStart({ passwordless = true } = {}) {
384
292
  ensureWebAuthnSupport();
385
-
386
- const res = await axios.get(
293
+ const res = await apiClient.get(
387
294
  `${HEADLESS_BASE}/account/authenticators/webauthn`,
388
295
  {
389
296
  params: passwordless ? { passwordless: true } : {},
390
- withCredentials: true,
391
- },
297
+ }
392
298
  );
393
-
394
299
  const responseBody = res.data || {};
395
-
396
- // Handle nested 'data' wrapper if present, otherwise use body directly
397
300
  const payload = responseBody.data || responseBody;
398
-
399
- // Extract the inner publicKey structure required by the browser API
400
301
  const publicKeyJson =
401
302
  (payload.creation_options && payload.creation_options.publicKey) ||
402
303
  payload.publicKey ||
403
304
  payload;
404
-
405
305
  return publicKeyJson;
406
306
  }
407
307
 
408
-
409
308
  async function registerPasskeyComplete(credentialJson, name = 'Passkey') {
410
- const res = await axios.post(
309
+ const res = await apiClient.post(
411
310
  `${HEADLESS_BASE}/account/authenticators/webauthn`,
412
- {
413
- credential: credentialJson,
414
- name,
415
- },
416
- { withCredentials: true },
311
+ { credential: credentialJson, name }
417
312
  );
418
313
  return res.data;
419
314
  }
420
315
 
421
316
  export async function registerPasskey(name = 'Passkey') {
422
317
  ensureWebAuthnSupport();
423
-
424
318
  if (!hasJsonWebAuthn) {
425
319
  throw normaliseApiError(
426
320
  new Error('Auth.PASSKEY_JSON_HELPERS_UNAVAILABLE'),
427
- 'Auth.PASSKEY_JSON_HELPERS_UNAVAILABLE',
321
+ 'Auth.PASSKEY_JSON_HELPERS_UNAVAILABLE'
428
322
  );
429
323
  }
430
-
431
- // ... (previous logic for registerPasskeyStart) ...
432
324
  const publicKeyJson = await registerPasskeyStart({ passwordless: true });
433
-
434
325
  let credential;
435
326
  try {
436
- const publicKeyOptions =
437
- window.PublicKeyCredential.parseCreationOptionsFromJSON(publicKeyJson);
438
-
439
- credential = await navigator.credentials.create({
440
- publicKey: publicKeyOptions,
441
- });
327
+ const publicKeyOptions = window.PublicKeyCredential.parseCreationOptionsFromJSON(publicKeyJson);
328
+ credential = await navigator.credentials.create({ publicKey: publicKeyOptions });
442
329
  } catch (err) {
443
- // ... (error handling) ...
444
330
  if (err && err.name === 'NotAllowedError') {
445
331
  throw normaliseApiError(err, 'Auth.PASSKEY_CREATION_CANCELLED');
446
332
  }
447
333
  throw err;
448
334
  }
449
-
450
335
  if (!credential) {
451
- throw normaliseApiError(
452
- new Error('Auth.PASSKEY_CREATION_CANCELLED'),
453
- 'Auth.PASSKEY_CREATION_CANCELLED',
454
- );
336
+ throw normaliseApiError(new Error('Auth.PASSKEY_CREATION_CANCELLED'), 'Auth.PASSKEY_CREATION_CANCELLED');
455
337
  }
456
-
457
- // CHANGED: Use helper instead of direct .toJSON()
458
338
  const credentialJson = serializeCredential(credential);
459
-
460
339
  return registerPasskeyComplete(credentialJson, name);
461
340
  }
462
341
 
463
- // -----------------------------
464
- // WebAuthn / Passkeys: login
465
- // -----------------------------
466
342
  async function loginWithPasskeyStart() {
467
343
  ensureWebAuthnSupport();
468
-
469
- const res = await axios.get(
470
- `${HEADLESS_BASE}/auth/webauthn/login`,
471
- { withCredentials: true },
472
- );
473
-
344
+ const res = await apiClient.get(`${HEADLESS_BASE}/auth/webauthn/login`);
474
345
  const responseBody = res.data || {};
475
-
476
- // Handle nested 'data' wrapper if present
477
346
  const payload = responseBody.data || responseBody;
478
-
479
- // Extract request options for authentication
480
347
  const requestOptionsJson =
481
348
  (payload.request_options && payload.request_options.publicKey) ||
482
349
  payload.request_options ||
483
350
  payload;
484
-
485
351
  return requestOptionsJson;
486
352
  }
487
353
 
488
354
  async function loginWithPasskeyComplete(credentialJson) {
489
- const res = await axios.post(
355
+ const res = await apiClient.post(
490
356
  `${HEADLESS_BASE}/auth/webauthn/login`,
491
- { credential: credentialJson },
492
- { withCredentials: true },
357
+ { credential: credentialJson }
493
358
  );
494
359
  return res.data;
495
360
  }
496
361
 
497
362
  export async function loginWithPasskey() {
498
363
  ensureWebAuthnSupport();
499
-
500
364
  if (!hasJsonWebAuthn) {
501
365
  throw normaliseApiError(
502
366
  new Error('Auth.PASSKEY_JSON_HELPERS_UNAVAILABLE'),
503
- 'Auth.PASSKEY_JSON_HELPERS_UNAVAILABLE',
367
+ 'Auth.PASSKEY_JSON_HELPERS_UNAVAILABLE'
504
368
  );
505
369
  }
506
-
507
370
  const requestOptionsJson = await loginWithPasskeyStart();
508
-
509
371
  let assertion;
510
372
  try {
511
- const publicKeyOptions =
512
- window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptionsJson);
513
-
373
+ const publicKeyOptions = window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptionsJson);
514
374
  assertion = await navigator.credentials.get({ publicKey: publicKeyOptions });
515
375
  } catch (err) {
516
- // Browser-interner Abbruch (z.B. Dialog weggeklickt)
517
376
  const e = new Error('Auth.PASSKEY_AUTH_CANCELLED');
518
377
  e.code = 'Auth.PASSKEY_AUTH_CANCELLED';
519
378
  throw e;
520
379
  }
521
-
522
380
  if (!assertion) {
523
381
  const e = new Error('Auth.PASSKEY_AUTH_CANCELLED');
524
382
  e.code = 'Auth.PASSKEY_AUTH_CANCELLED';
525
383
  throw e;
526
384
  }
527
-
528
385
  const credentialJson = serializeCredential(assertion);
529
-
530
386
  let postError = null;
531
387
  try {
532
388
  await loginWithPasskeyComplete(credentialJson);
533
389
  } catch (err) {
534
390
  postError = err;
535
391
  }
536
-
537
392
  try {
538
393
  const user = await fetchCurrentUser();
539
394
  return { user };
540
395
  } catch (err) {
541
- if (postError) {
542
- throw normaliseApiError(postError, 'Auth.PASSKEY_FAILED');
543
- }
396
+ if (postError) throw normaliseApiError(postError, 'Auth.PASSKEY_FAILED');
544
397
  throw normaliseApiError(err, 'Auth.PASSKEY_FAILED');
545
398
  }
546
399
  }
547
400
 
548
-
549
401
  export async function fetchPasskeys() {
550
402
  try {
551
- const res = await axios.get(
552
- `${USERS_BASE}/passkeys/`,
553
- { withCredentials: true },
554
- );
403
+ const res = await apiClient.get(`${USERS_BASE}/passkeys/`);
555
404
  return Array.isArray(res.data) ? res.data : [];
556
405
  } catch (error) {
557
406
  throw normaliseApiError(error, 'Auth.PASSKEY_LIST_FAILED');
@@ -560,10 +409,8 @@ export async function fetchPasskeys() {
560
409
 
561
410
  export async function deletePasskey(id) {
562
411
  try {
563
- await axios.delete(
564
- `${USERS_BASE}/passkeys/${id}/`,
565
- { withCredentials: true },
566
- );
412
+ // CHANGED: axios -> apiClient
413
+ await apiClient.delete(`${USERS_BASE}/passkeys/${id}/`);
567
414
  } catch (error) {
568
415
  throw normaliseApiError(error, 'Auth.PASSKEY_DELETE_FAILED');
569
416
  }
@@ -578,10 +425,9 @@ export async function authenticateWithMFA({ code, credential }) {
578
425
  if (credential) payload.credential = credential;
579
426
 
580
427
  try {
581
- const res = await axios.post(
428
+ const res = await apiClient.post(
582
429
  `${HEADLESS_BASE}/auth/2fa/authenticate`,
583
- payload,
584
- { withCredentials: true },
430
+ payload
585
431
  );
586
432
  return res.data;
587
433
  } catch (error) {
@@ -590,20 +436,17 @@ export async function authenticateWithMFA({ code, credential }) {
590
436
  }
591
437
 
592
438
  // -----------------------------
593
- // Authentication: password (MODIFIZIERT)
439
+ // Authentication: password
594
440
  // -----------------------------
595
441
  export async function loginWithPassword(email, password) {
596
442
  try {
597
- await axios.post(
443
+ await apiClient.post(
598
444
  `${HEADLESS_BASE}/auth/login`,
599
- { email, password },
600
- { withCredentials: true },
445
+ { email, password }
601
446
  );
602
447
  } catch (error) {
603
448
  const status = error.response?.status;
604
449
  const body = error.response?.data;
605
-
606
- // Flows können in body.data.flows oder body.flows liegen
607
450
  const flows = body?.data?.flows || body?.flows || [];
608
451
  const mfaFlow = Array.isArray(flows)
609
452
  ? flows.find((f) => f.id === 'mfa_authenticate')
@@ -612,55 +455,41 @@ export async function loginWithPassword(email, password) {
612
455
  if (status === 401 && mfaFlow && mfaFlow.is_pending) {
613
456
  return {
614
457
  needsMfa: true,
615
- availableTypes: mfaFlow.types || [], // ["recovery_codes", "totp", "webauthn"]
458
+ availableTypes: mfaFlow.types || [],
616
459
  };
617
460
  }
618
-
619
461
  if (status === 409) {
620
- // Already logged in – einfach weitermachen
462
+ // Already logged in
621
463
  } else {
622
464
  throw normaliseApiError(error, 'Auth.LOGIN_FAILED');
623
465
  }
624
466
  }
625
-
626
- // Erfolg ohne MFA
627
467
  const user = await fetchCurrentUser();
628
468
  return { user, needsMfa: false };
629
469
  }
630
470
 
631
- // 1. Status prüfen (Liste der aktiven Authenticators)
471
+ // -----------------------------
472
+ // Authenticators & MFA
473
+ // -----------------------------
632
474
  export async function fetchAuthenticators() {
633
- const res = await axios.get(
634
- `${HEADLESS_BASE}/account/authenticators`,
635
- { withCredentials: true },
636
- );
637
-
475
+ const res = await apiClient.get(`${HEADLESS_BASE}/account/authenticators`);
638
476
  const body = res.data || {};
639
- // Headless gibt i.d.R. { status, data: [...] } zurück
640
477
  const items = Array.isArray(body.data) ? body.data : (Array.isArray(body) ? body : []);
641
478
  return items;
642
479
  }
643
480
 
644
- // 2. TOTP Einrichtung starten (liefert Secret & QR-URL)
645
481
  export async function requestTotpKey() {
646
482
  try {
647
- const res = await axios.get(
648
- `${HEADLESS_BASE}/account/authenticators/totp`,
649
- { withCredentials: true },
650
- );
651
-
652
- // Fall: TOTP existiert bereits -> 200
483
+ const res = await apiClient.get(`${HEADLESS_BASE}/account/authenticators/totp`);
653
484
  const body = res.data || {};
654
485
  const data = body.data || body;
655
-
656
486
  return {
657
487
  exists: true,
658
- authenticator: data, // z.B. { id, type, created_at, ... }
488
+ authenticator: data,
659
489
  };
660
490
  } catch (error) {
661
491
  const { response } = error;
662
492
  if (response?.status === 404) {
663
- // Spezialfall von allauth: noch kein TOTP eingerichtet
664
493
  const meta = response.data?.meta || {};
665
494
  return {
666
495
  exists: false,
@@ -674,10 +503,9 @@ export async function requestTotpKey() {
674
503
 
675
504
  export async function activateTotp(code) {
676
505
  try {
677
- const res = await axios.post(
506
+ const res = await apiClient.post(
678
507
  `${HEADLESS_BASE}/account/authenticators/totp`,
679
- { code },
680
- { withCredentials: true },
508
+ { code }
681
509
  );
682
510
  return res.data;
683
511
  } catch (error) {
@@ -687,37 +515,26 @@ export async function activateTotp(code) {
687
515
 
688
516
  export async function deactivateTotp() {
689
517
  try {
690
- const res = await axios.delete(
691
- `${HEADLESS_BASE}/account/authenticators/totp`,
692
- { withCredentials: true },
693
- );
518
+ // CHANGED: axios -> apiClient
519
+ const res = await apiClient.delete(`${HEADLESS_BASE}/account/authenticators/totp`);
694
520
  return res.data;
695
521
  } catch (error) {
696
522
  throw normaliseApiError(error, 'Auth.TOTP_DEACTIVATE_FAILED');
697
523
  }
698
524
  }
699
- // -----------------------------
700
- // MFA: Recovery Codes
701
- // -----------------------------
702
525
 
703
526
  export async function fetchRecoveryCodes() {
704
527
  try {
705
- const res = await axios.get(
706
- `${HEADLESS_BASE}/account/authenticators/recovery-codes`,
707
- { withCredentials: true },
708
- );
528
+ const res = await apiClient.get(`${HEADLESS_BASE}/account/authenticators/recovery-codes`);
709
529
  const body = res.data || {};
710
- // Inneres "data" herausziehen, sonst direkt body verwenden
711
530
  const data = body.data || body;
712
- return data; // { type, unused_codes, ... }
531
+ return data;
713
532
  } catch (error) {
714
533
  const status = error.response?.status;
715
534
  if (status === 404) {
716
- // Noch keine Codes -> automatisch generieren
717
- const resPost = await axios.post(
535
+ const resPost = await apiClient.post(
718
536
  `${HEADLESS_BASE}/account/authenticators/recovery-codes`,
719
- {},
720
- { withCredentials: true },
537
+ {}
721
538
  );
722
539
  const body = resPost.data || {};
723
540
  const data = body.data || body;
@@ -729,10 +546,9 @@ export async function fetchRecoveryCodes() {
729
546
 
730
547
  export async function generateRecoveryCodes() {
731
548
  try {
732
- const res = await axios.post(
549
+ const res = await apiClient.post(
733
550
  `${HEADLESS_BASE}/account/authenticators/recovery-codes`,
734
- {},
735
- { withCredentials: true },
551
+ {}
736
552
  );
737
553
  const body = res.data || {};
738
554
  const data = body.data || body;
@@ -742,23 +558,16 @@ export async function generateRecoveryCodes() {
742
558
  }
743
559
  }
744
560
 
745
-
746
-
747
561
  export async function fetchOrGenerateRecoveryCodes() {
748
562
  try {
749
- const res = await axios.get(
750
- `${HEADLESS_BASE}/mfa/recovery_codes`,
751
- { withCredentials: true },
752
- );
563
+ const res = await apiClient.get(`${HEADLESS_BASE}/mfa/recovery_codes`);
753
564
  return res.data;
754
565
  } catch (error) {
755
566
  const { response } = error;
756
567
  if (response?.status === 404) {
757
- // Noch keine Codes -> jetzt generieren
758
- const resPost = await axios.post(
568
+ const resPost = await apiClient.post(
759
569
  `${HEADLESS_BASE}/mfa/recovery_codes`,
760
- {},
761
- { withCredentials: true },
570
+ {}
762
571
  );
763
572
  return resPost.data;
764
573
  }
@@ -766,76 +575,59 @@ export async function fetchOrGenerateRecoveryCodes() {
766
575
  }
767
576
  }
768
577
 
769
-
770
578
  export function isStrongSession(session) {
771
579
  const methods = session?.methods || [];
772
580
  const used = methods.map((m) => m.method);
773
-
774
- // alles, was klar 2FA / starker Faktor ist:
775
581
  const strongMethods = ['totp', 'recovery_codes', 'webauthn'];
776
-
777
582
  return used.some((m) => strongMethods.includes(m));
778
583
  }
779
584
 
780
585
  export async function requestMfaSupportHelp(emailOrIdentifier, message = '') {
781
- // Wir nutzen hier die Users-API, nicht HEADLESS_BASE
782
586
  const payload = { email: emailOrIdentifier, message };
783
-
784
- const res = await axios.post(
587
+ const res = await apiClient.post(
785
588
  `${USERS_BASE}/mfa/support-help/`,
786
- payload,
787
- { withCredentials: true }
589
+ payload
788
590
  );
789
591
  return res.data;
790
592
  }
791
593
 
792
594
  export async function fetchRecoveryRequests(status = 'pending') {
793
- const res = await axios.get(
595
+ const res = await apiClient.get(
794
596
  '/api/support/recovery-requests/',
795
- {
796
- params: { status },
797
- withCredentials: true,
798
- },
597
+ { params: { status } }
799
598
  );
800
599
  return res.data;
801
600
  }
802
601
 
803
602
  export async function approveRecoveryRequest(id, supportNote) {
804
- const res = await axios.post(
603
+ const res = await apiClient.post(
805
604
  `/api/support/recovery-requests/${id}/approve/`,
806
- { support_note: supportNote || '' },
807
- { withCredentials: true },
605
+ { support_note: supportNote || '' }
808
606
  );
809
607
  return res.data;
810
608
  }
811
609
 
812
610
  export async function rejectRecoveryRequest(id, supportNote) {
813
- const res = await axios.post(
611
+ const res = await apiClient.post(
814
612
  `/api/support/recovery-requests/${id}/reject/`,
815
- { support_note: supportNote || '' },
816
- { withCredentials: true },
613
+ { support_note: supportNote || '' }
817
614
  );
818
615
  return res.data;
819
616
  }
820
617
 
821
-
822
618
  export async function loginWithRecoveryPassword(email, password, token) {
823
619
  try {
824
- await axios.post(
620
+ await apiClient.post(
825
621
  `/api/support/recovery-requests/recovery-login/${token}/`,
826
- { email, password },
827
- { withCredentials: true },
622
+ { email, password }
828
623
  );
829
624
  } catch (error) {
830
625
  throw normaliseApiError(error, 'Auth.RECOVERY_LOGIN_FAILED');
831
626
  }
832
-
833
627
  const user = await fetchCurrentUser();
834
628
  return { user, needsMfa: false };
835
629
  }
836
630
 
837
-
838
-
839
631
  // -----------------------------
840
632
  // Aggregated API object
841
633
  // -----------------------------
@@ -872,4 +664,4 @@ export const authApi = {
872
664
  loginWithRecoveryPassword,
873
665
  };
874
666
 
875
- export default authApi;
667
+ export default authApi;