@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.
- package/dist/auth/AuthContext.js +15 -42
- package/dist/auth/apiClient.js +18 -0
- package/dist/auth/authApi.js +58 -165
- package/dist/auth/webauthnClient.js +3 -2
- package/dist/components/AccessCodeManager.js +4 -4
- package/dist/components/LoginForm.js +1 -1
- package/dist/components/MfaLoginComponent.js +1 -1
- package/dist/components/ProfileComponent.js +1 -1
- package/dist/components/SecurityComponent.js +2 -2
- package/dist/components/SupportRecoveryRequestsTab.js +3 -3
- package/dist/i18n/authTranslations.js +6 -137
- package/dist/pages/LoginPage.js +1 -1
- package/dist/pages/PasswordChangePage.js +2 -2
- package/dist/pages/PasswordResetRequestPage.js +1 -1
- package/dist/pages/SignUpPage.js +1 -1
- package/package.json +1 -1
- package/src/auth/AuthContext.jsx +16 -40
- package/src/auth/apiClient.jsx +20 -0
- package/src/auth/authApi.jsx +87 -295
- package/src/auth/webauthnClient.jsx +3 -2
- package/src/components/AccessCodeManager.jsx +4 -4
- package/src/components/LoginForm.jsx +1 -1
- package/src/components/MfaLoginComponent.jsx +0 -4
- package/src/components/ProfileComponent.jsx +1 -1
- package/src/components/SecurityComponent.jsx +2 -2
- package/src/components/SupportRecoveryRequestsTab.jsx +3 -3
- package/src/i18n/authTranslations.js +6 -153
- package/src/pages/LoginPage.jsx +1 -1
- package/src/pages/PasswordChangePage.jsx +3 -3
- package/src/pages/PasswordResetRequestPage.jsx +2 -2
- package/src/pages/SignUpPage.jsx +3 -3
package/dist/auth/authApi.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import apiClient from './apiClient';
|
|
2
2
|
import { HEADLESS_BASE, USERS_BASE, ACCESS_CODES_BASE } from './authConfig';
|
|
3
3
|
// -----------------------------
|
|
4
4
|
// WebAuthn Serialization Helpers
|
|
5
5
|
// -----------------------------
|
|
6
|
-
/**
|
|
7
|
-
* Converts an ArrayBuffer to a Base64URL encoded string.
|
|
8
|
-
* Required for manual credential serialization when .toJSON() is missing.
|
|
9
|
-
*/
|
|
10
6
|
function bufferToBase64URL(buffer) {
|
|
11
7
|
const bytes = new Uint8Array(buffer);
|
|
12
8
|
let str = '';
|
|
@@ -16,15 +12,10 @@ function bufferToBase64URL(buffer) {
|
|
|
16
12
|
const base64 = btoa(str);
|
|
17
13
|
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
18
14
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Serializes a PublicKeyCredential into a JSON-compatible object.
|
|
21
|
-
* Acts as a polyfill for environments where credential.toJSON() is missing (e.g., Proton Pass).
|
|
22
|
-
*/
|
|
23
15
|
function serializeCredential(credential) {
|
|
24
16
|
if (typeof credential.toJSON === 'function') {
|
|
25
17
|
return credential.toJSON();
|
|
26
18
|
}
|
|
27
|
-
// Manual serialization for extensions that return incomplete objects
|
|
28
19
|
const p = {
|
|
29
20
|
id: credential.id,
|
|
30
21
|
rawId: bufferToBase64URL(credential.rawId),
|
|
@@ -34,22 +25,17 @@ function serializeCredential(credential) {
|
|
|
34
25
|
},
|
|
35
26
|
};
|
|
36
27
|
if (credential.response.attestationObject) {
|
|
37
|
-
// Registration specific
|
|
38
28
|
p.response.attestationObject = bufferToBase64URL(credential.response.attestationObject);
|
|
39
29
|
}
|
|
40
30
|
if (credential.response.authenticatorData) {
|
|
41
|
-
// Login specific
|
|
42
31
|
p.response.authenticatorData = bufferToBase64URL(credential.response.authenticatorData);
|
|
43
32
|
}
|
|
44
33
|
if (credential.response.signature) {
|
|
45
|
-
// Login specific
|
|
46
34
|
p.response.signature = bufferToBase64URL(credential.response.signature);
|
|
47
35
|
}
|
|
48
36
|
if (credential.response.userHandle) {
|
|
49
|
-
// Login specific
|
|
50
37
|
p.response.userHandle = bufferToBase64URL(credential.response.userHandle);
|
|
51
38
|
}
|
|
52
|
-
// Include clientExtensionResults if present
|
|
53
39
|
if (typeof credential.getClientExtensionResults === 'function') {
|
|
54
40
|
p.clientExtensionResults = credential.getClientExtensionResults();
|
|
55
41
|
}
|
|
@@ -68,10 +54,6 @@ function normaliseApiError(error, defaultCode = 'Auth.GENERIC_ERROR') {
|
|
|
68
54
|
function mapAllauthDetailToCode(detail) {
|
|
69
55
|
if (!detail || typeof detail !== 'string')
|
|
70
56
|
return null;
|
|
71
|
-
// Optional: bekannte allauth-Texte auf eigene Codes mappen
|
|
72
|
-
// if (detail.includes('Unable to log in with provided credentials')) {
|
|
73
|
-
// return 'Auth.INVALID_CREDENTIALS';
|
|
74
|
-
// }
|
|
75
57
|
return null;
|
|
76
58
|
}
|
|
77
59
|
function extractErrorInfo(error) {
|
|
@@ -79,47 +61,21 @@ function extractErrorInfo(error) {
|
|
|
79
61
|
const status = (_b = (_a = error.response) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : null;
|
|
80
62
|
const data = (_d = (_c = error.response) === null || _c === void 0 ? void 0 : _c.data) !== null && _d !== void 0 ? _d : null;
|
|
81
63
|
if (!data) {
|
|
82
|
-
return {
|
|
83
|
-
status,
|
|
84
|
-
code: null,
|
|
85
|
-
message: error.message || null,
|
|
86
|
-
raw: null,
|
|
87
|
-
};
|
|
64
|
+
return { status, code: null, message: error.message || null, raw: null };
|
|
88
65
|
}
|
|
89
66
|
if (typeof data.code === 'string') {
|
|
90
|
-
return {
|
|
91
|
-
status,
|
|
92
|
-
code: data.code,
|
|
93
|
-
message: null,
|
|
94
|
-
raw: data,
|
|
95
|
-
};
|
|
67
|
+
return { status, code: data.code, message: null, raw: data };
|
|
96
68
|
}
|
|
97
69
|
if (typeof data.detail === 'string') {
|
|
98
70
|
const mapped = mapAllauthDetailToCode(data.detail);
|
|
99
|
-
return {
|
|
100
|
-
status,
|
|
101
|
-
code: mapped,
|
|
102
|
-
message: data.detail,
|
|
103
|
-
raw: data,
|
|
104
|
-
};
|
|
71
|
+
return { status, code: mapped, message: data.detail, raw: data };
|
|
105
72
|
}
|
|
106
|
-
if (Array.isArray(data.non_field_errors) &&
|
|
107
|
-
data.non_field_errors.length > 0) {
|
|
73
|
+
if (Array.isArray(data.non_field_errors) && data.non_field_errors.length > 0) {
|
|
108
74
|
const msg = data.non_field_errors[0];
|
|
109
75
|
const mapped = mapAllauthDetailToCode(msg);
|
|
110
|
-
return {
|
|
111
|
-
status,
|
|
112
|
-
code: mapped,
|
|
113
|
-
message: msg,
|
|
114
|
-
raw: data,
|
|
115
|
-
};
|
|
76
|
+
return { status, code: mapped, message: msg, raw: data };
|
|
116
77
|
}
|
|
117
|
-
return {
|
|
118
|
-
status,
|
|
119
|
-
code: null,
|
|
120
|
-
message: null,
|
|
121
|
-
raw: data,
|
|
122
|
-
};
|
|
78
|
+
return { status, code: null, message: null, raw: data };
|
|
123
79
|
}
|
|
124
80
|
// -----------------------------
|
|
125
81
|
// CSRF helper
|
|
@@ -128,7 +84,8 @@ function getCsrfToken() {
|
|
|
128
84
|
if (typeof document === 'undefined' || !document.cookie) {
|
|
129
85
|
return null;
|
|
130
86
|
}
|
|
131
|
-
|
|
87
|
+
// Robust regex for CSRF token
|
|
88
|
+
const match = document.cookie.match(/(?:^|; )csrftoken=([^;]+)/);
|
|
132
89
|
return match ? match[1] : null;
|
|
133
90
|
}
|
|
134
91
|
// -----------------------------
|
|
@@ -150,16 +107,13 @@ function ensureWebAuthnSupport() {
|
|
|
150
107
|
// User-related helpers
|
|
151
108
|
// -----------------------------
|
|
152
109
|
export async function fetchCurrentUser() {
|
|
153
|
-
const res = await
|
|
154
|
-
withCredentials: true,
|
|
155
|
-
});
|
|
110
|
+
const res = await apiClient.get(`${USERS_BASE}/current/`);
|
|
156
111
|
return res.data;
|
|
157
112
|
}
|
|
158
113
|
export async function updateUserProfile(data) {
|
|
159
114
|
try {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
});
|
|
115
|
+
// CHANGED: axios -> apiClient
|
|
116
|
+
const res = await apiClient.patch(`${USERS_BASE}/current/`, data);
|
|
163
117
|
return res.data;
|
|
164
118
|
}
|
|
165
119
|
catch (error) {
|
|
@@ -169,38 +123,17 @@ export async function updateUserProfile(data) {
|
|
|
169
123
|
// -----------------------------
|
|
170
124
|
// Authentication: password
|
|
171
125
|
// -----------------------------
|
|
172
|
-
/*
|
|
173
|
-
export async function loginWithPassword(email, password) {
|
|
174
|
-
try {
|
|
175
|
-
await axios.post(
|
|
176
|
-
`${HEADLESS_BASE}/auth/login`,
|
|
177
|
-
{ email, password },
|
|
178
|
-
{ withCredentials: true },
|
|
179
|
-
);
|
|
180
|
-
} catch (error) {
|
|
181
|
-
if (error.response && error.response.status === 409) {
|
|
182
|
-
// Already logged in: continue and fetch current user
|
|
183
|
-
} else {
|
|
184
|
-
throw new Error(extractErrorMessage(error));
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const user = await fetchCurrentUser();
|
|
189
|
-
return { user };
|
|
190
|
-
}
|
|
191
|
-
*/
|
|
192
126
|
export async function requestPasswordReset(email) {
|
|
193
127
|
try {
|
|
194
|
-
await
|
|
128
|
+
await apiClient.post(`${USERS_BASE}/reset-request/`, { email });
|
|
195
129
|
}
|
|
196
130
|
catch (error) {
|
|
197
131
|
throw normaliseApiError(error, 'Auth.RESET_REQUEST_FAILED');
|
|
198
132
|
}
|
|
199
133
|
}
|
|
200
|
-
// (Falls du es noch brauchst: klassischer allauth-Key-Flow)
|
|
201
134
|
export async function resetPasswordWithKey(key, newPassword) {
|
|
202
135
|
try {
|
|
203
|
-
await
|
|
136
|
+
await apiClient.post(`${HEADLESS_BASE}/auth/password/reset/key`, { key, password: newPassword });
|
|
204
137
|
}
|
|
205
138
|
catch (error) {
|
|
206
139
|
throw normaliseApiError(error, 'Auth.RESET_WITH_KEY_FAILED');
|
|
@@ -208,10 +141,10 @@ export async function resetPasswordWithKey(key, newPassword) {
|
|
|
208
141
|
}
|
|
209
142
|
export async function changePassword(currentPassword, newPassword) {
|
|
210
143
|
try {
|
|
211
|
-
await
|
|
144
|
+
await apiClient.post(`${HEADLESS_BASE}/account/password/change`, {
|
|
212
145
|
current_password: currentPassword,
|
|
213
146
|
new_password: newPassword,
|
|
214
|
-
}
|
|
147
|
+
});
|
|
215
148
|
}
|
|
216
149
|
catch (error) {
|
|
217
150
|
throw normaliseApiError(error, 'Auth.PASSWORD_CHANGE_FAILED');
|
|
@@ -227,14 +160,11 @@ export async function logoutSession() {
|
|
|
227
160
|
if (csrfToken) {
|
|
228
161
|
headers['X-CSRFToken'] = csrfToken;
|
|
229
162
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
headers,
|
|
233
|
-
});
|
|
163
|
+
// CHANGED: axios -> apiClient
|
|
164
|
+
await apiClient.delete(`${HEADLESS_BASE}/auth/session`, { headers });
|
|
234
165
|
}
|
|
235
166
|
catch (error) {
|
|
236
167
|
if (error.response && [401, 404, 410].includes(error.response.status)) {
|
|
237
|
-
// Session already gone, nothing to do
|
|
238
168
|
return;
|
|
239
169
|
}
|
|
240
170
|
// eslint-disable-next-line no-console
|
|
@@ -242,9 +172,7 @@ export async function logoutSession() {
|
|
|
242
172
|
}
|
|
243
173
|
}
|
|
244
174
|
export async function fetchHeadlessSession() {
|
|
245
|
-
const res = await
|
|
246
|
-
withCredentials: true,
|
|
247
|
-
});
|
|
175
|
+
const res = await apiClient.get(`${HEADLESS_BASE}/auth/session`);
|
|
248
176
|
return res.data;
|
|
249
177
|
}
|
|
250
178
|
// -----------------------------
|
|
@@ -254,7 +182,6 @@ export function startSocialLogin(provider) {
|
|
|
254
182
|
if (typeof window === 'undefined') {
|
|
255
183
|
throw normaliseApiError(new Error('Auth.SOCIAL_LOGIN_NOT_IN_BROWSER'), 'Auth.SOCIAL_LOGIN_NOT_IN_BROWSER');
|
|
256
184
|
}
|
|
257
|
-
// Classic allauth HTML flow
|
|
258
185
|
window.location.href = `/accounts/${provider}/login/?process=login`;
|
|
259
186
|
}
|
|
260
187
|
// -----------------------------
|
|
@@ -262,11 +189,10 @@ export function startSocialLogin(provider) {
|
|
|
262
189
|
// -----------------------------
|
|
263
190
|
export async function validateAccessCode(code) {
|
|
264
191
|
try {
|
|
265
|
-
const res = await
|
|
192
|
+
const res = await apiClient.post(`${ACCESS_CODES_BASE}/validate/`, { code });
|
|
266
193
|
return res.data;
|
|
267
194
|
}
|
|
268
195
|
catch (error) {
|
|
269
|
-
// Default für "irgendwas ist mit dem Code schief"
|
|
270
196
|
throw normaliseApiError(error, 'Auth.ACCESS_CODE_INVALID_OR_INACTIVE');
|
|
271
197
|
}
|
|
272
198
|
}
|
|
@@ -276,11 +202,10 @@ export async function requestInviteWithCode(email, accessCode) {
|
|
|
276
202
|
payload.access_code = accessCode;
|
|
277
203
|
}
|
|
278
204
|
try {
|
|
279
|
-
const res = await
|
|
205
|
+
const res = await apiClient.post(`${USERS_BASE}/invite/`, payload);
|
|
280
206
|
return res.data;
|
|
281
207
|
}
|
|
282
208
|
catch (error) {
|
|
283
|
-
// z.B. bei Netzwerkfehlern oder wenn Backend ausnahmsweise keinen code liefert
|
|
284
209
|
throw normaliseApiError(error, 'Auth.INVITE_FAILED');
|
|
285
210
|
}
|
|
286
211
|
}
|
|
@@ -289,17 +214,16 @@ export async function requestInviteWithCode(email, accessCode) {
|
|
|
289
214
|
// -----------------------------
|
|
290
215
|
export async function verifyResetToken(uid, token) {
|
|
291
216
|
try {
|
|
292
|
-
const res = await
|
|
217
|
+
const res = await apiClient.get(`${USERS_BASE}/password-reset/${uid}/${token}/`);
|
|
293
218
|
return res.data;
|
|
294
219
|
}
|
|
295
220
|
catch (error) {
|
|
296
|
-
// Wenn der Link nicht passt oder Netzwerkfehler: generisch als "Link invalid"
|
|
297
221
|
throw normaliseApiError(error, 'Auth.RESET_LINK_INVALID');
|
|
298
222
|
}
|
|
299
223
|
}
|
|
300
224
|
export async function setNewPassword(uid, token, newPassword) {
|
|
301
225
|
try {
|
|
302
|
-
const res = await
|
|
226
|
+
const res = await apiClient.post(`${USERS_BASE}/password-reset/${uid}/${token}/`, { new_password: newPassword });
|
|
303
227
|
return res.data;
|
|
304
228
|
}
|
|
305
229
|
catch (error) {
|
|
@@ -307,28 +231,22 @@ export async function setNewPassword(uid, token, newPassword) {
|
|
|
307
231
|
}
|
|
308
232
|
}
|
|
309
233
|
// -----------------------------
|
|
310
|
-
// WebAuthn / Passkeys
|
|
234
|
+
// WebAuthn / Passkeys
|
|
311
235
|
// -----------------------------
|
|
312
236
|
async function registerPasskeyStart({ passwordless = true } = {}) {
|
|
313
237
|
ensureWebAuthnSupport();
|
|
314
|
-
const res = await
|
|
238
|
+
const res = await apiClient.get(`${HEADLESS_BASE}/account/authenticators/webauthn`, {
|
|
315
239
|
params: passwordless ? { passwordless: true } : {},
|
|
316
|
-
withCredentials: true,
|
|
317
240
|
});
|
|
318
241
|
const responseBody = res.data || {};
|
|
319
|
-
// Handle nested 'data' wrapper if present, otherwise use body directly
|
|
320
242
|
const payload = responseBody.data || responseBody;
|
|
321
|
-
// Extract the inner publicKey structure required by the browser API
|
|
322
243
|
const publicKeyJson = (payload.creation_options && payload.creation_options.publicKey) ||
|
|
323
244
|
payload.publicKey ||
|
|
324
245
|
payload;
|
|
325
246
|
return publicKeyJson;
|
|
326
247
|
}
|
|
327
248
|
async function registerPasskeyComplete(credentialJson, name = 'Passkey') {
|
|
328
|
-
const res = await
|
|
329
|
-
credential: credentialJson,
|
|
330
|
-
name,
|
|
331
|
-
}, { withCredentials: true });
|
|
249
|
+
const res = await apiClient.post(`${HEADLESS_BASE}/account/authenticators/webauthn`, { credential: credentialJson, name });
|
|
332
250
|
return res.data;
|
|
333
251
|
}
|
|
334
252
|
export async function registerPasskey(name = 'Passkey') {
|
|
@@ -336,17 +254,13 @@ export async function registerPasskey(name = 'Passkey') {
|
|
|
336
254
|
if (!hasJsonWebAuthn) {
|
|
337
255
|
throw normaliseApiError(new Error('Auth.PASSKEY_JSON_HELPERS_UNAVAILABLE'), 'Auth.PASSKEY_JSON_HELPERS_UNAVAILABLE');
|
|
338
256
|
}
|
|
339
|
-
// ... (previous logic for registerPasskeyStart) ...
|
|
340
257
|
const publicKeyJson = await registerPasskeyStart({ passwordless: true });
|
|
341
258
|
let credential;
|
|
342
259
|
try {
|
|
343
260
|
const publicKeyOptions = window.PublicKeyCredential.parseCreationOptionsFromJSON(publicKeyJson);
|
|
344
|
-
credential = await navigator.credentials.create({
|
|
345
|
-
publicKey: publicKeyOptions,
|
|
346
|
-
});
|
|
261
|
+
credential = await navigator.credentials.create({ publicKey: publicKeyOptions });
|
|
347
262
|
}
|
|
348
263
|
catch (err) {
|
|
349
|
-
// ... (error handling) ...
|
|
350
264
|
if (err && err.name === 'NotAllowedError') {
|
|
351
265
|
throw normaliseApiError(err, 'Auth.PASSKEY_CREATION_CANCELLED');
|
|
352
266
|
}
|
|
@@ -355,27 +269,21 @@ export async function registerPasskey(name = 'Passkey') {
|
|
|
355
269
|
if (!credential) {
|
|
356
270
|
throw normaliseApiError(new Error('Auth.PASSKEY_CREATION_CANCELLED'), 'Auth.PASSKEY_CREATION_CANCELLED');
|
|
357
271
|
}
|
|
358
|
-
// CHANGED: Use helper instead of direct .toJSON()
|
|
359
272
|
const credentialJson = serializeCredential(credential);
|
|
360
273
|
return registerPasskeyComplete(credentialJson, name);
|
|
361
274
|
}
|
|
362
|
-
// -----------------------------
|
|
363
|
-
// WebAuthn / Passkeys: login
|
|
364
|
-
// -----------------------------
|
|
365
275
|
async function loginWithPasskeyStart() {
|
|
366
276
|
ensureWebAuthnSupport();
|
|
367
|
-
const res = await
|
|
277
|
+
const res = await apiClient.get(`${HEADLESS_BASE}/auth/webauthn/login`);
|
|
368
278
|
const responseBody = res.data || {};
|
|
369
|
-
// Handle nested 'data' wrapper if present
|
|
370
279
|
const payload = responseBody.data || responseBody;
|
|
371
|
-
// Extract request options for authentication
|
|
372
280
|
const requestOptionsJson = (payload.request_options && payload.request_options.publicKey) ||
|
|
373
281
|
payload.request_options ||
|
|
374
282
|
payload;
|
|
375
283
|
return requestOptionsJson;
|
|
376
284
|
}
|
|
377
285
|
async function loginWithPasskeyComplete(credentialJson) {
|
|
378
|
-
const res = await
|
|
286
|
+
const res = await apiClient.post(`${HEADLESS_BASE}/auth/webauthn/login`, { credential: credentialJson });
|
|
379
287
|
return res.data;
|
|
380
288
|
}
|
|
381
289
|
export async function loginWithPasskey() {
|
|
@@ -390,7 +298,6 @@ export async function loginWithPasskey() {
|
|
|
390
298
|
assertion = await navigator.credentials.get({ publicKey: publicKeyOptions });
|
|
391
299
|
}
|
|
392
300
|
catch (err) {
|
|
393
|
-
// Browser-interner Abbruch (z.B. Dialog weggeklickt)
|
|
394
301
|
const e = new Error('Auth.PASSKEY_AUTH_CANCELLED');
|
|
395
302
|
e.code = 'Auth.PASSKEY_AUTH_CANCELLED';
|
|
396
303
|
throw e;
|
|
@@ -413,15 +320,14 @@ export async function loginWithPasskey() {
|
|
|
413
320
|
return { user };
|
|
414
321
|
}
|
|
415
322
|
catch (err) {
|
|
416
|
-
if (postError)
|
|
323
|
+
if (postError)
|
|
417
324
|
throw normaliseApiError(postError, 'Auth.PASSKEY_FAILED');
|
|
418
|
-
}
|
|
419
325
|
throw normaliseApiError(err, 'Auth.PASSKEY_FAILED');
|
|
420
326
|
}
|
|
421
327
|
}
|
|
422
328
|
export async function fetchPasskeys() {
|
|
423
329
|
try {
|
|
424
|
-
const res = await
|
|
330
|
+
const res = await apiClient.get(`${USERS_BASE}/passkeys/`);
|
|
425
331
|
return Array.isArray(res.data) ? res.data : [];
|
|
426
332
|
}
|
|
427
333
|
catch (error) {
|
|
@@ -430,7 +336,8 @@ export async function fetchPasskeys() {
|
|
|
430
336
|
}
|
|
431
337
|
export async function deletePasskey(id) {
|
|
432
338
|
try {
|
|
433
|
-
|
|
339
|
+
// CHANGED: axios -> apiClient
|
|
340
|
+
await apiClient.delete(`${USERS_BASE}/passkeys/${id}/`);
|
|
434
341
|
}
|
|
435
342
|
catch (error) {
|
|
436
343
|
throw normaliseApiError(error, 'Auth.PASSKEY_DELETE_FAILED');
|
|
@@ -446,7 +353,7 @@ export async function authenticateWithMFA({ code, credential }) {
|
|
|
446
353
|
if (credential)
|
|
447
354
|
payload.credential = credential;
|
|
448
355
|
try {
|
|
449
|
-
const res = await
|
|
356
|
+
const res = await apiClient.post(`${HEADLESS_BASE}/auth/2fa/authenticate`, payload);
|
|
450
357
|
return res.data;
|
|
451
358
|
}
|
|
452
359
|
catch (error) {
|
|
@@ -454,17 +361,16 @@ export async function authenticateWithMFA({ code, credential }) {
|
|
|
454
361
|
}
|
|
455
362
|
}
|
|
456
363
|
// -----------------------------
|
|
457
|
-
// Authentication: password
|
|
364
|
+
// Authentication: password
|
|
458
365
|
// -----------------------------
|
|
459
366
|
export async function loginWithPassword(email, password) {
|
|
460
367
|
var _a, _b, _c;
|
|
461
368
|
try {
|
|
462
|
-
await
|
|
369
|
+
await apiClient.post(`${HEADLESS_BASE}/auth/login`, { email, password });
|
|
463
370
|
}
|
|
464
371
|
catch (error) {
|
|
465
372
|
const status = (_a = error.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
466
373
|
const body = (_b = error.response) === null || _b === void 0 ? void 0 : _b.data;
|
|
467
|
-
// Flows können in body.data.flows oder body.flows liegen
|
|
468
374
|
const flows = ((_c = body === null || body === void 0 ? void 0 : body.data) === null || _c === void 0 ? void 0 : _c.flows) || (body === null || body === void 0 ? void 0 : body.flows) || [];
|
|
469
375
|
const mfaFlow = Array.isArray(flows)
|
|
470
376
|
? flows.find((f) => f.id === 'mfa_authenticate')
|
|
@@ -472,45 +378,42 @@ export async function loginWithPassword(email, password) {
|
|
|
472
378
|
if (status === 401 && mfaFlow && mfaFlow.is_pending) {
|
|
473
379
|
return {
|
|
474
380
|
needsMfa: true,
|
|
475
|
-
availableTypes: mfaFlow.types || [],
|
|
381
|
+
availableTypes: mfaFlow.types || [],
|
|
476
382
|
};
|
|
477
383
|
}
|
|
478
384
|
if (status === 409) {
|
|
479
|
-
// Already logged in
|
|
385
|
+
// Already logged in
|
|
480
386
|
}
|
|
481
387
|
else {
|
|
482
388
|
throw normaliseApiError(error, 'Auth.LOGIN_FAILED');
|
|
483
389
|
}
|
|
484
390
|
}
|
|
485
|
-
// Erfolg ohne MFA
|
|
486
391
|
const user = await fetchCurrentUser();
|
|
487
392
|
return { user, needsMfa: false };
|
|
488
393
|
}
|
|
489
|
-
//
|
|
394
|
+
// -----------------------------
|
|
395
|
+
// Authenticators & MFA
|
|
396
|
+
// -----------------------------
|
|
490
397
|
export async function fetchAuthenticators() {
|
|
491
|
-
const res = await
|
|
398
|
+
const res = await apiClient.get(`${HEADLESS_BASE}/account/authenticators`);
|
|
492
399
|
const body = res.data || {};
|
|
493
|
-
// Headless gibt i.d.R. { status, data: [...] } zurück
|
|
494
400
|
const items = Array.isArray(body.data) ? body.data : (Array.isArray(body) ? body : []);
|
|
495
401
|
return items;
|
|
496
402
|
}
|
|
497
|
-
// 2. TOTP Einrichtung starten (liefert Secret & QR-URL)
|
|
498
403
|
export async function requestTotpKey() {
|
|
499
404
|
var _a;
|
|
500
405
|
try {
|
|
501
|
-
const res = await
|
|
502
|
-
// Fall: TOTP existiert bereits -> 200
|
|
406
|
+
const res = await apiClient.get(`${HEADLESS_BASE}/account/authenticators/totp`);
|
|
503
407
|
const body = res.data || {};
|
|
504
408
|
const data = body.data || body;
|
|
505
409
|
return {
|
|
506
410
|
exists: true,
|
|
507
|
-
authenticator: data,
|
|
411
|
+
authenticator: data,
|
|
508
412
|
};
|
|
509
413
|
}
|
|
510
414
|
catch (error) {
|
|
511
415
|
const { response } = error;
|
|
512
416
|
if ((response === null || response === void 0 ? void 0 : response.status) === 404) {
|
|
513
|
-
// Spezialfall von allauth: noch kein TOTP eingerichtet
|
|
514
417
|
const meta = ((_a = response.data) === null || _a === void 0 ? void 0 : _a.meta) || {};
|
|
515
418
|
return {
|
|
516
419
|
exists: false,
|
|
@@ -523,7 +426,7 @@ export async function requestTotpKey() {
|
|
|
523
426
|
}
|
|
524
427
|
export async function activateTotp(code) {
|
|
525
428
|
try {
|
|
526
|
-
const res = await
|
|
429
|
+
const res = await apiClient.post(`${HEADLESS_BASE}/account/authenticators/totp`, { code });
|
|
527
430
|
return res.data;
|
|
528
431
|
}
|
|
529
432
|
catch (error) {
|
|
@@ -532,30 +435,26 @@ export async function activateTotp(code) {
|
|
|
532
435
|
}
|
|
533
436
|
export async function deactivateTotp() {
|
|
534
437
|
try {
|
|
535
|
-
|
|
438
|
+
// CHANGED: axios -> apiClient
|
|
439
|
+
const res = await apiClient.delete(`${HEADLESS_BASE}/account/authenticators/totp`);
|
|
536
440
|
return res.data;
|
|
537
441
|
}
|
|
538
442
|
catch (error) {
|
|
539
443
|
throw normaliseApiError(error, 'Auth.TOTP_DEACTIVATE_FAILED');
|
|
540
444
|
}
|
|
541
445
|
}
|
|
542
|
-
// -----------------------------
|
|
543
|
-
// MFA: Recovery Codes
|
|
544
|
-
// -----------------------------
|
|
545
446
|
export async function fetchRecoveryCodes() {
|
|
546
447
|
var _a;
|
|
547
448
|
try {
|
|
548
|
-
const res = await
|
|
449
|
+
const res = await apiClient.get(`${HEADLESS_BASE}/account/authenticators/recovery-codes`);
|
|
549
450
|
const body = res.data || {};
|
|
550
|
-
// Inneres "data" herausziehen, sonst direkt body verwenden
|
|
551
451
|
const data = body.data || body;
|
|
552
|
-
return data;
|
|
452
|
+
return data;
|
|
553
453
|
}
|
|
554
454
|
catch (error) {
|
|
555
455
|
const status = (_a = error.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
556
456
|
if (status === 404) {
|
|
557
|
-
|
|
558
|
-
const resPost = await axios.post(`${HEADLESS_BASE}/account/authenticators/recovery-codes`, {}, { withCredentials: true });
|
|
457
|
+
const resPost = await apiClient.post(`${HEADLESS_BASE}/account/authenticators/recovery-codes`, {});
|
|
559
458
|
const body = resPost.data || {};
|
|
560
459
|
const data = body.data || body;
|
|
561
460
|
return data;
|
|
@@ -565,7 +464,7 @@ export async function fetchRecoveryCodes() {
|
|
|
565
464
|
}
|
|
566
465
|
export async function generateRecoveryCodes() {
|
|
567
466
|
try {
|
|
568
|
-
const res = await
|
|
467
|
+
const res = await apiClient.post(`${HEADLESS_BASE}/account/authenticators/recovery-codes`, {});
|
|
569
468
|
const body = res.data || {};
|
|
570
469
|
const data = body.data || body;
|
|
571
470
|
return data;
|
|
@@ -576,14 +475,13 @@ export async function generateRecoveryCodes() {
|
|
|
576
475
|
}
|
|
577
476
|
export async function fetchOrGenerateRecoveryCodes() {
|
|
578
477
|
try {
|
|
579
|
-
const res = await
|
|
478
|
+
const res = await apiClient.get(`${HEADLESS_BASE}/mfa/recovery_codes`);
|
|
580
479
|
return res.data;
|
|
581
480
|
}
|
|
582
481
|
catch (error) {
|
|
583
482
|
const { response } = error;
|
|
584
483
|
if ((response === null || response === void 0 ? void 0 : response.status) === 404) {
|
|
585
|
-
|
|
586
|
-
const resPost = await axios.post(`${HEADLESS_BASE}/mfa/recovery_codes`, {}, { withCredentials: true });
|
|
484
|
+
const resPost = await apiClient.post(`${HEADLESS_BASE}/mfa/recovery_codes`, {});
|
|
587
485
|
return resPost.data;
|
|
588
486
|
}
|
|
589
487
|
throw normaliseApiError(error, 'Auth.RECOVERY_CODES_FETCH_FAILED');
|
|
@@ -592,34 +490,29 @@ export async function fetchOrGenerateRecoveryCodes() {
|
|
|
592
490
|
export function isStrongSession(session) {
|
|
593
491
|
const methods = (session === null || session === void 0 ? void 0 : session.methods) || [];
|
|
594
492
|
const used = methods.map((m) => m.method);
|
|
595
|
-
// alles, was klar 2FA / starker Faktor ist:
|
|
596
493
|
const strongMethods = ['totp', 'recovery_codes', 'webauthn'];
|
|
597
494
|
return used.some((m) => strongMethods.includes(m));
|
|
598
495
|
}
|
|
599
496
|
export async function requestMfaSupportHelp(emailOrIdentifier, message = '') {
|
|
600
|
-
// Wir nutzen hier die Users-API, nicht HEADLESS_BASE
|
|
601
497
|
const payload = { email: emailOrIdentifier, message };
|
|
602
|
-
const res = await
|
|
498
|
+
const res = await apiClient.post(`${USERS_BASE}/mfa/support-help/`, payload);
|
|
603
499
|
return res.data;
|
|
604
500
|
}
|
|
605
501
|
export async function fetchRecoveryRequests(status = 'pending') {
|
|
606
|
-
const res = await
|
|
607
|
-
params: { status },
|
|
608
|
-
withCredentials: true,
|
|
609
|
-
});
|
|
502
|
+
const res = await apiClient.get('/api/support/recovery-requests/', { params: { status } });
|
|
610
503
|
return res.data;
|
|
611
504
|
}
|
|
612
505
|
export async function approveRecoveryRequest(id, supportNote) {
|
|
613
|
-
const res = await
|
|
506
|
+
const res = await apiClient.post(`/api/support/recovery-requests/${id}/approve/`, { support_note: supportNote || '' });
|
|
614
507
|
return res.data;
|
|
615
508
|
}
|
|
616
509
|
export async function rejectRecoveryRequest(id, supportNote) {
|
|
617
|
-
const res = await
|
|
510
|
+
const res = await apiClient.post(`/api/support/recovery-requests/${id}/reject/`, { support_note: supportNote || '' });
|
|
618
511
|
return res.data;
|
|
619
512
|
}
|
|
620
513
|
export async function loginWithRecoveryPassword(email, password, token) {
|
|
621
514
|
try {
|
|
622
|
-
await
|
|
515
|
+
await apiClient.post(`/api/support/recovery-requests/recovery-login/${token}/`, { email, password });
|
|
623
516
|
}
|
|
624
517
|
catch (error) {
|
|
625
518
|
throw normaliseApiError(error, 'Auth.RECOVERY_LOGIN_FAILED');
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
//src/auth/webauthnclient.jsx
|
|
1
2
|
export async function registerPasskeyStart() {
|
|
2
|
-
const res = await
|
|
3
|
+
const res = await apiClient.post(`${HEADLESS_BASE}/mfa/webauthn/register/start`, {}, { withCredentials: true });
|
|
3
4
|
return res.data; // enthält challenge, rpId etc.
|
|
4
5
|
}
|
|
5
6
|
export async function registerPasskeyComplete(publicKeyCredential) {
|
|
6
7
|
const payload = serializePublicKeyCredential(publicKeyCredential);
|
|
7
|
-
const res = await
|
|
8
|
+
const res = await apiClient.post(`${HEADLESS_BASE}/mfa/webauthn/register/complete`, payload, { withCredentials: true });
|
|
8
9
|
return res.data;
|
|
9
10
|
}
|
|
@@ -27,7 +27,7 @@ export function AccessCodeManager() {
|
|
|
27
27
|
setErrorKey(null);
|
|
28
28
|
setSuccessKey(null);
|
|
29
29
|
try {
|
|
30
|
-
const res = await
|
|
30
|
+
const res = await apiClient.get(`${ACCESS_CODES_BASE}/`, {
|
|
31
31
|
withCredentials: true,
|
|
32
32
|
});
|
|
33
33
|
setCodes(res.data || []);
|
|
@@ -54,7 +54,7 @@ export function AccessCodeManager() {
|
|
|
54
54
|
setErrorKey(null);
|
|
55
55
|
setSuccessKey(null);
|
|
56
56
|
try {
|
|
57
|
-
const res = await
|
|
57
|
+
const res = await apiClient.post(`${ACCESS_CODES_BASE}/`, { code }, { withCredentials: true });
|
|
58
58
|
setCodes((prev) => [...prev, res.data]);
|
|
59
59
|
setSuccessKey('Auth.ACCESS_CODE_SAVE_SUCCESS');
|
|
60
60
|
}
|
|
@@ -71,7 +71,7 @@ export function AccessCodeManager() {
|
|
|
71
71
|
};
|
|
72
72
|
const handleAddManual = async () => {
|
|
73
73
|
if (!manualCode.trim()) {
|
|
74
|
-
setErrorKey('Auth.
|
|
74
|
+
setErrorKey('Auth.SIGNUP_ACCESS_CODE_REQUIRED');
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
await handleCreateCode(manualCode.trim());
|
|
@@ -95,7 +95,7 @@ export function AccessCodeManager() {
|
|
|
95
95
|
return (_jsx(Box, { sx: { py: 3, display: 'flex', justifyContent: 'center' }, children: _jsx(CircularProgress, {}) }));
|
|
96
96
|
}
|
|
97
97
|
return (_jsxs(Box, { sx: { mt: 2 }, children: [errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), successKey && (_jsx(Alert, { severity: "success", sx: { mb: 2 }, children: t(successKey) })), _jsxs(Box, { sx: { mb: 3 }, children: [_jsx(Typography, { variant: "subtitle1", gutterBottom: true, children: t('Auth.ACCESS_CODE_SECTION_ACTIVE') }), codes.length === 0 ? (_jsx(Typography, { variant: "body2", children: t('Auth.ACCESS_CODE_NONE') })) : (_jsx(Stack, { direction: "row", flexWrap: "wrap", gap: 1, children: codes.map((code) => (_jsx(Chip, { label: code.code, onDelete: () => handleDelete(code.id), deleteIcon: _jsx(CloseIcon, {}) }, code.id))) }))] }), _jsxs(Box, { sx: { mb: 3 }, children: [_jsx(Typography, { variant: "subtitle1", gutterBottom: true, children: t('Auth.ACCESS_CODE_SECTION_GENERATE') }), _jsxs(Box, { sx: { maxWidth: 360 }, children: [_jsx(Typography, { variant: "body2", gutterBottom: true, children: t('Auth.ACCESS_CODE_LENGTH_LABEL', { length }) }), _jsx(Slider, { min: 6, max: 32, step: 1, value: length, onChange: (_, val) => setLength(val), valueLabelDisplay: "auto", disabled: submitting })] }), _jsx(Button, { variant: "contained", sx: { mt: 1 }, onClick: handleGenerateClick, disabled: submitting, children: submitting
|
|
98
|
-
? t('Auth.
|
|
98
|
+
? t('Auth.SAVE_BUTTON_LOADING')
|
|
99
99
|
: t('Auth.ACCESS_CODE_GENERATE_BUTTON') })] }), _jsxs(Box, { sx: { mb: 2, maxWidth: 360 }, children: [_jsx(Typography, { variant: "subtitle1", gutterBottom: true, children: t('Auth.ACCESS_CODE_SECTION_MANUAL') }), _jsxs(Box, { sx: { display: 'flex', gap: 1 }, children: [_jsx(TextField, { label: t('Auth.ACCESS_CODE_LABEL'), fullWidth: true, value: manualCode, onChange: (e) => setManualCode(e.target.value), disabled: submitting }), _jsx(Button, { variant: "outlined", onClick: handleAddManual, disabled: submitting, children: t('Auth.ACCESS_CODE_ADD_BUTTON') })] })] })] }));
|
|
100
100
|
}
|
|
101
101
|
export default AccessCodeManager;
|
|
@@ -21,6 +21,6 @@ disabled = false, initialIdentifier = '', }) => {
|
|
|
21
21
|
const supportsPasskey = !!onPasskeyLogin &&
|
|
22
22
|
typeof window !== 'undefined' &&
|
|
23
23
|
!!window.PublicKeyCredential;
|
|
24
|
-
return (_jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 3 }, children: [error && (_jsx(Typography, { color: "error", gutterBottom: true, children: error })), supportsPasskey && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "contained", fullWidth: true, type: "button", onClick: onPasskeyLogin, disabled: disabled, children: t('Auth.LOGIN_USE_PASSKEY_BUTTON') }), _jsx(Divider, { sx: { my: 2 }, children: t('Auth.LOGIN_OR') })] })), _jsxs(Box, { component: "form", onSubmit: handleSubmit, sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(TextField, { label: t('Auth.EMAIL_LABEL'), type: "email", required: true, fullWidth: true, value: identifier, onChange: (e) => setIdentifier(e.target.value), disabled: disabled }), _jsx(TextField, { label: t('Auth.LOGIN_PASSWORD_LABEL'), type: "password", required: true, fullWidth: true, value: password, onChange: (e) => setPassword(e.target.value), disabled: disabled }), _jsx(Button, { type: "submit", variant: "contained", fullWidth: true, disabled: disabled, children: t('Auth.
|
|
24
|
+
return (_jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 3 }, children: [error && (_jsx(Typography, { color: "error", gutterBottom: true, children: error })), supportsPasskey && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "contained", fullWidth: true, type: "button", onClick: onPasskeyLogin, disabled: disabled, children: t('Auth.LOGIN_USE_PASSKEY_BUTTON') }), _jsx(Divider, { sx: { my: 2 }, children: t('Auth.LOGIN_OR') })] })), _jsxs(Box, { component: "form", onSubmit: handleSubmit, sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(TextField, { label: t('Auth.EMAIL_LABEL'), type: "email", required: true, fullWidth: true, value: identifier, onChange: (e) => setIdentifier(e.target.value), disabled: disabled }), _jsx(TextField, { label: t('Auth.LOGIN_PASSWORD_LABEL'), type: "password", required: true, fullWidth: true, value: password, onChange: (e) => setPassword(e.target.value), disabled: disabled }), _jsx(Button, { type: "submit", variant: "contained", fullWidth: true, disabled: disabled, children: t('Auth.PAGE_LOGIN_TITLE') })] }), _jsxs(Box, { children: [_jsx(Divider, { sx: { my: 2 }, children: t('Auth.LOGIN_OR') }), _jsx(SocialLoginButtons, { onProviderClick: onSocialLogin })] }), _jsxs(Box, { children: [_jsx(Typography, { variant: "subtitle2", sx: { mb: 1 }, children: t('Auth.LOGIN_ACCOUNT_RECOVERY_TITLE') }), _jsxs(Box, { sx: { display: 'flex', gap: 1, flexWrap: 'wrap' }, children: [onSignUp && (_jsx(Button, { type: "button", variant: "outlined", onClick: onSignUp, disabled: disabled, children: t('Auth.LOGIN_SIGNUP_BUTTON') })), _jsx(Button, { type: "button", variant: "outlined", onClick: onForgotPassword, disabled: disabled, children: t('Auth.LOGIN_FORGOT_PASSWORD_BUTTON') })] })] })] }));
|
|
25
25
|
};
|
|
26
26
|
export default LoginForm;
|