@micha.bigler/ui-core-micha 1.4.19 → 1.4.22
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 +0 -1
- package/dist/auth/authApi.js +137 -378
- package/dist/components/MFAComponent.js +7 -7
- package/dist/components/MfaLoginComponent.js +6 -5
- package/dist/components/PasskeysComponent.js +5 -5
- package/dist/components/SecurityComponent.js +4 -3
- package/dist/components/SupportRecoveryRequestsTab.js +5 -5
- package/dist/components/UserInviteComponent.js +38 -0
- package/dist/components/UserListComponent.js +83 -0
- package/dist/i18n/authTranslations.js +25 -0
- package/dist/pages/AccountPage.js +67 -23
- package/dist/pages/LoginPage.js +6 -5
- package/dist/pages/PasswordInvitePage.js +3 -3
- package/dist/pages/SignUpPage.js +3 -3
- package/dist/utils/authService.js +53 -0
- package/dist/utils/errors.js +33 -0
- package/dist/utils/webauthn.js +44 -0
- package/package.json +1 -1
- package/src/auth/AuthContext.jsx +0 -1
- package/src/auth/authApi.jsx +143 -478
- package/src/components/MFAComponent.jsx +7 -7
- package/src/components/MfaLoginComponent.jsx +6 -5
- package/src/components/PasskeysComponent.jsx +5 -5
- package/src/components/SecurityComponent.jsx +4 -3
- package/src/components/SupportRecoveryRequestsTab.jsx +7 -5
- package/src/components/UserInviteComponent.jsx +69 -0
- package/src/components/UserListComponent.jsx +167 -0
- package/src/i18n/authTranslations.js +25 -0
- package/src/pages/AccountPage.jsx +140 -47
- package/src/pages/LoginPage.jsx +6 -5
- package/src/pages/PasswordInvitePage.jsx +3 -3
- package/src/pages/SignUpPage.jsx +3 -3
- package/src/utils/authService.js +68 -0
- package/src/utils/errors.js +39 -0
- package/src/utils/webauthn.js +51 -0
package/dist/auth/AuthContext.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
// src/auth/AuthContext.jsx
|
|
3
3
|
import React, { createContext, useState, useEffect, } from 'react';
|
|
4
|
-
// REMOVED: import axios from 'axios'; -> Not needed here anymore
|
|
5
4
|
import { ensureCsrfToken } from './apiClient'; // <--- IMPORT ADDED
|
|
6
5
|
import { fetchCurrentUser, logoutSession, } from './authApi';
|
|
7
6
|
export const AuthContext = createContext(null);
|
package/dist/auth/authApi.js
CHANGED
|
@@ -1,110 +1,15 @@
|
|
|
1
1
|
import apiClient from './apiClient';
|
|
2
2
|
import { HEADLESS_BASE, USERS_BASE, ACCESS_CODES_BASE } from './authConfig';
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// -----------------------------
|
|
6
|
-
function bufferToBase64URL(buffer) {
|
|
7
|
-
const bytes = new Uint8Array(buffer);
|
|
8
|
-
let str = '';
|
|
9
|
-
for (const char of bytes) {
|
|
10
|
-
str += String.fromCharCode(char);
|
|
11
|
-
}
|
|
12
|
-
const base64 = btoa(str);
|
|
13
|
-
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
14
|
-
}
|
|
15
|
-
function serializeCredential(credential) {
|
|
16
|
-
// Wir nutzen IMMER die manuelle Serialisierung, um sicherzugehen,
|
|
17
|
-
// dass alles Base64URL-kodiert ist (kompatibel mit Allauth).
|
|
18
|
-
const p = {
|
|
19
|
-
id: credential.id,
|
|
20
|
-
rawId: bufferToBase64URL(credential.rawId),
|
|
21
|
-
type: credential.type,
|
|
22
|
-
response: {
|
|
23
|
-
clientDataJSON: bufferToBase64URL(credential.response.clientDataJSON),
|
|
24
|
-
},
|
|
25
|
-
};
|
|
26
|
-
if (credential.response.attestationObject) {
|
|
27
|
-
p.response.attestationObject = bufferToBase64URL(credential.response.attestationObject);
|
|
28
|
-
}
|
|
29
|
-
if (credential.response.authenticatorData) {
|
|
30
|
-
p.response.authenticatorData = bufferToBase64URL(credential.response.authenticatorData);
|
|
31
|
-
}
|
|
32
|
-
if (credential.response.signature) {
|
|
33
|
-
p.response.signature = bufferToBase64URL(credential.response.signature);
|
|
34
|
-
}
|
|
35
|
-
if (credential.response.userHandle) {
|
|
36
|
-
p.response.userHandle = bufferToBase64URL(credential.response.userHandle);
|
|
37
|
-
}
|
|
38
|
-
// Extension Results direkt übernehmen, falls vorhanden
|
|
39
|
-
if (typeof credential.getClientExtensionResults === 'function') {
|
|
40
|
-
p.clientExtensionResults = credential.getClientExtensionResults();
|
|
41
|
-
}
|
|
42
|
-
return p;
|
|
43
|
-
}
|
|
44
|
-
function normaliseApiError(error, defaultCode = 'Auth.GENERIC_ERROR') {
|
|
45
|
-
const info = extractErrorInfo(error);
|
|
46
|
-
const code = info.code || defaultCode;
|
|
47
|
-
const message = info.message || code || defaultCode;
|
|
48
|
-
const err = new Error(message);
|
|
49
|
-
err.code = code;
|
|
50
|
-
err.status = info.status;
|
|
51
|
-
err.raw = info.raw;
|
|
52
|
-
return err;
|
|
53
|
-
}
|
|
54
|
-
function mapAllauthDetailToCode(detail) {
|
|
55
|
-
if (!detail || typeof detail !== 'string')
|
|
56
|
-
return null;
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
function extractErrorInfo(error) {
|
|
60
|
-
var _a, _b, _c, _d;
|
|
61
|
-
const status = (_b = (_a = error.response) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : null;
|
|
62
|
-
const data = (_d = (_c = error.response) === null || _c === void 0 ? void 0 : _c.data) !== null && _d !== void 0 ? _d : null;
|
|
63
|
-
if (!data) {
|
|
64
|
-
return { status, code: null, message: error.message || null, raw: null };
|
|
65
|
-
}
|
|
66
|
-
if (typeof data.code === 'string') {
|
|
67
|
-
return { status, code: data.code, message: null, raw: data };
|
|
68
|
-
}
|
|
69
|
-
if (typeof data.detail === 'string') {
|
|
70
|
-
const mapped = mapAllauthDetailToCode(data.detail);
|
|
71
|
-
return { status, code: mapped, message: data.detail, raw: data };
|
|
72
|
-
}
|
|
73
|
-
if (Array.isArray(data.non_field_errors) && data.non_field_errors.length > 0) {
|
|
74
|
-
const msg = data.non_field_errors[0];
|
|
75
|
-
const mapped = mapAllauthDetailToCode(msg);
|
|
76
|
-
return { status, code: mapped, message: msg, raw: data };
|
|
77
|
-
}
|
|
78
|
-
return { status, code: null, message: null, raw: data };
|
|
79
|
-
}
|
|
80
|
-
// -----------------------------
|
|
81
|
-
// CSRF helper
|
|
82
|
-
// -----------------------------
|
|
3
|
+
import { normaliseApiError } from '../utils/errors'; // Beachte den Pfad zu deiner errors.js
|
|
4
|
+
// --- Internal Helper for CSRF ---
|
|
83
5
|
function getCsrfToken() {
|
|
84
|
-
if (typeof document === 'undefined' || !document.cookie)
|
|
6
|
+
if (typeof document === 'undefined' || !document.cookie)
|
|
85
7
|
return null;
|
|
86
|
-
}
|
|
87
|
-
// Robust regex for CSRF token
|
|
88
8
|
const match = document.cookie.match(/(?:^|; )csrftoken=([^;]+)/);
|
|
89
9
|
return match ? match[1] : null;
|
|
90
10
|
}
|
|
91
11
|
// -----------------------------
|
|
92
|
-
//
|
|
93
|
-
// -----------------------------
|
|
94
|
-
const hasJsonWebAuthn = typeof window !== 'undefined' &&
|
|
95
|
-
typeof window.PublicKeyCredential !== 'undefined' &&
|
|
96
|
-
typeof window.PublicKeyCredential.parseCreationOptionsFromJSON === 'function' &&
|
|
97
|
-
typeof window.PublicKeyCredential.parseRequestOptionsFromJSON === 'function';
|
|
98
|
-
function ensureWebAuthnSupport() {
|
|
99
|
-
if (typeof window === 'undefined' ||
|
|
100
|
-
typeof navigator === 'undefined' ||
|
|
101
|
-
!window.PublicKeyCredential ||
|
|
102
|
-
!navigator.credentials) {
|
|
103
|
-
throw normaliseApiError(new Error('Auth.PASSKEY_NOT_SUPPORTED'), 'Auth.PASSKEY_NOT_SUPPORTED');
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// -----------------------------
|
|
107
|
-
// User-related helpers
|
|
12
|
+
// Session & User Core
|
|
108
13
|
// -----------------------------
|
|
109
14
|
export async function fetchCurrentUser() {
|
|
110
15
|
const res = await apiClient.get(`${USERS_BASE}/current/`);
|
|
@@ -112,7 +17,6 @@ export async function fetchCurrentUser() {
|
|
|
112
17
|
}
|
|
113
18
|
export async function updateUserProfile(data) {
|
|
114
19
|
try {
|
|
115
|
-
// CHANGED: axios -> apiClient
|
|
116
20
|
const res = await apiClient.patch(`${USERS_BASE}/current/`, data);
|
|
117
21
|
return res.data;
|
|
118
22
|
}
|
|
@@ -120,98 +24,98 @@ export async function updateUserProfile(data) {
|
|
|
120
24
|
throw normaliseApiError(error, 'Auth.PROFILE_UPDATE_FAILED');
|
|
121
25
|
}
|
|
122
26
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
27
|
+
export async function fetchHeadlessSession() {
|
|
28
|
+
const res = await apiClient.get(`${HEADLESS_BASE}/auth/session`);
|
|
29
|
+
return res.data;
|
|
30
|
+
}
|
|
31
|
+
export async function logoutSession() {
|
|
127
32
|
try {
|
|
128
|
-
|
|
33
|
+
const headers = {};
|
|
34
|
+
const token = getCsrfToken();
|
|
35
|
+
if (token)
|
|
36
|
+
headers['X-CSRFToken'] = token;
|
|
37
|
+
await apiClient.delete(`${HEADLESS_BASE}/auth/session`, { headers });
|
|
129
38
|
}
|
|
130
39
|
catch (error) {
|
|
131
|
-
|
|
40
|
+
// 401/404 beim Logout ignorieren wir
|
|
41
|
+
if (error.response && [401, 404, 410].includes(error.response.status))
|
|
42
|
+
return;
|
|
43
|
+
console.error('Logout error:', error);
|
|
132
44
|
}
|
|
133
45
|
}
|
|
134
|
-
|
|
46
|
+
// -----------------------------
|
|
47
|
+
// Authentication: Password & MFA
|
|
48
|
+
// -----------------------------
|
|
49
|
+
export async function loginWithPassword(email, password) {
|
|
50
|
+
var _a, _b, _c;
|
|
135
51
|
try {
|
|
136
|
-
await apiClient.post(`${HEADLESS_BASE}/auth/
|
|
52
|
+
await apiClient.post(`${HEADLESS_BASE}/auth/login`, { email, password });
|
|
53
|
+
// Nach erfolgreichem Login User holen
|
|
54
|
+
const user = await fetchCurrentUser();
|
|
55
|
+
return { user, needsMfa: false };
|
|
137
56
|
}
|
|
138
57
|
catch (error) {
|
|
139
|
-
|
|
58
|
+
const status = (_a = error.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
59
|
+
const body = (_b = error.response) === null || _b === void 0 ? void 0 : _b.data;
|
|
60
|
+
// Prüfen auf Allauth Headless MFA Flow (401 + flows)
|
|
61
|
+
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) || [];
|
|
62
|
+
const mfaFlow = Array.isArray(flows) ? flows.find((f) => f.id === 'mfa_authenticate') : null;
|
|
63
|
+
if (status === 401 && mfaFlow && mfaFlow.is_pending) {
|
|
64
|
+
return { needsMfa: true, availableTypes: mfaFlow.types || [] };
|
|
65
|
+
}
|
|
66
|
+
// 409 = Already logged in
|
|
67
|
+
if (status === 409) {
|
|
68
|
+
const user = await fetchCurrentUser();
|
|
69
|
+
return { user, needsMfa: false };
|
|
70
|
+
}
|
|
71
|
+
throw normaliseApiError(error, 'Auth.LOGIN_FAILED');
|
|
140
72
|
}
|
|
141
73
|
}
|
|
142
|
-
export async function
|
|
74
|
+
export async function authenticateWithMFA({ code, credential }) {
|
|
75
|
+
const payload = {};
|
|
76
|
+
if (code)
|
|
77
|
+
payload.code = code;
|
|
78
|
+
if (credential)
|
|
79
|
+
payload.credential = credential;
|
|
143
80
|
try {
|
|
144
|
-
await apiClient.post(`${HEADLESS_BASE}/
|
|
145
|
-
|
|
146
|
-
new_password: newPassword,
|
|
147
|
-
});
|
|
81
|
+
const res = await apiClient.post(`${HEADLESS_BASE}/auth/2fa/authenticate`, payload);
|
|
82
|
+
return res.data;
|
|
148
83
|
}
|
|
149
84
|
catch (error) {
|
|
150
|
-
throw normaliseApiError(error, 'Auth.
|
|
85
|
+
throw normaliseApiError(error, 'Auth.MFA_AUTHENTICATE_FAILED');
|
|
151
86
|
}
|
|
152
87
|
}
|
|
153
88
|
// -----------------------------
|
|
154
|
-
//
|
|
89
|
+
// Password Management
|
|
155
90
|
// -----------------------------
|
|
156
|
-
export async function
|
|
91
|
+
export async function requestPasswordReset(email) {
|
|
157
92
|
try {
|
|
158
|
-
|
|
159
|
-
const csrfToken = getCsrfToken();
|
|
160
|
-
if (csrfToken) {
|
|
161
|
-
headers['X-CSRFToken'] = csrfToken;
|
|
162
|
-
}
|
|
163
|
-
// CHANGED: axios -> apiClient
|
|
164
|
-
await apiClient.delete(`${HEADLESS_BASE}/auth/session`, { headers });
|
|
93
|
+
await apiClient.post(`${USERS_BASE}/reset-request/`, { email });
|
|
165
94
|
}
|
|
166
95
|
catch (error) {
|
|
167
|
-
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
// eslint-disable-next-line no-console
|
|
171
|
-
console.error('Logout error:', error);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
export async function fetchHeadlessSession() {
|
|
175
|
-
const res = await apiClient.get(`${HEADLESS_BASE}/auth/session`);
|
|
176
|
-
return res.data;
|
|
177
|
-
}
|
|
178
|
-
// -----------------------------
|
|
179
|
-
// Social login
|
|
180
|
-
// -----------------------------
|
|
181
|
-
export function startSocialLogin(provider) {
|
|
182
|
-
if (typeof window === 'undefined') {
|
|
183
|
-
throw normaliseApiError(new Error('Auth.SOCIAL_LOGIN_NOT_IN_BROWSER'), 'Auth.SOCIAL_LOGIN_NOT_IN_BROWSER');
|
|
96
|
+
throw normaliseApiError(error, 'Auth.RESET_REQUEST_FAILED');
|
|
184
97
|
}
|
|
185
|
-
window.location.href = `/accounts/${provider}/login/?process=login`;
|
|
186
98
|
}
|
|
187
|
-
|
|
188
|
-
// Invitation / access code
|
|
189
|
-
// -----------------------------
|
|
190
|
-
export async function validateAccessCode(code) {
|
|
99
|
+
export async function resetPasswordWithKey(key, newPassword) {
|
|
191
100
|
try {
|
|
192
|
-
|
|
193
|
-
return res.data;
|
|
101
|
+
await apiClient.post(`${HEADLESS_BASE}/auth/password/reset/key`, { key, password: newPassword });
|
|
194
102
|
}
|
|
195
103
|
catch (error) {
|
|
196
|
-
throw normaliseApiError(error, 'Auth.
|
|
104
|
+
throw normaliseApiError(error, 'Auth.RESET_WITH_KEY_FAILED');
|
|
197
105
|
}
|
|
198
106
|
}
|
|
199
|
-
export async function
|
|
200
|
-
const payload = { email };
|
|
201
|
-
if (accessCode) {
|
|
202
|
-
payload.access_code = accessCode;
|
|
203
|
-
}
|
|
107
|
+
export async function changePassword(currentPassword, newPassword) {
|
|
204
108
|
try {
|
|
205
|
-
|
|
206
|
-
|
|
109
|
+
await apiClient.post(`${HEADLESS_BASE}/account/password/change`, {
|
|
110
|
+
current_password: currentPassword,
|
|
111
|
+
new_password: newPassword,
|
|
112
|
+
});
|
|
207
113
|
}
|
|
208
114
|
catch (error) {
|
|
209
|
-
throw normaliseApiError(error, 'Auth.
|
|
115
|
+
throw normaliseApiError(error, 'Auth.PASSWORD_CHANGE_FAILED');
|
|
210
116
|
}
|
|
211
117
|
}
|
|
212
|
-
//
|
|
213
|
-
// Custom password-reset via uid/token
|
|
214
|
-
// -----------------------------
|
|
118
|
+
// Custom UID/Token Reset Flow
|
|
215
119
|
export async function verifyResetToken(uid, token) {
|
|
216
120
|
try {
|
|
217
121
|
const res = await apiClient.get(`${USERS_BASE}/password-reset/${uid}/${token}/`);
|
|
@@ -231,100 +135,36 @@ export async function setNewPassword(uid, token, newPassword) {
|
|
|
231
135
|
}
|
|
232
136
|
}
|
|
233
137
|
// -----------------------------
|
|
234
|
-
// WebAuthn / Passkeys
|
|
138
|
+
// WebAuthn / Passkeys (API Calls Only)
|
|
235
139
|
// -----------------------------
|
|
236
|
-
async function
|
|
237
|
-
|
|
140
|
+
export async function getPasskeyRegistrationOptions() {
|
|
141
|
+
var _a;
|
|
238
142
|
const res = await apiClient.get(`${HEADLESS_BASE}/account/authenticators/webauthn`, {
|
|
239
|
-
params:
|
|
143
|
+
params: { passwordless: true }
|
|
144
|
+
});
|
|
145
|
+
// Extrahiere nested data Struktur von Allauth
|
|
146
|
+
const data = ((_a = res.data) === null || _a === void 0 ? void 0 : _a.data) || res.data;
|
|
147
|
+
return data.creation_options || data;
|
|
148
|
+
}
|
|
149
|
+
export async function completePasskeyRegistration(credentialJson, name) {
|
|
150
|
+
const res = await apiClient.post(`${HEADLESS_BASE}/account/authenticators/webauthn`, {
|
|
151
|
+
credential: credentialJson,
|
|
152
|
+
name
|
|
240
153
|
});
|
|
241
|
-
const responseBody = res.data || {};
|
|
242
|
-
const payload = responseBody.data || responseBody;
|
|
243
|
-
const publicKeyJson = (payload.creation_options && payload.creation_options.publicKey) ||
|
|
244
|
-
payload.publicKey ||
|
|
245
|
-
payload;
|
|
246
|
-
return publicKeyJson;
|
|
247
|
-
}
|
|
248
|
-
async function registerPasskeyComplete(credentialJson, name = 'Passkey') {
|
|
249
|
-
const res = await apiClient.post(`${HEADLESS_BASE}/account/authenticators/webauthn`, { credential: credentialJson, name });
|
|
250
154
|
return res.data;
|
|
251
155
|
}
|
|
252
|
-
export async function
|
|
253
|
-
|
|
254
|
-
if (!hasJsonWebAuthn) {
|
|
255
|
-
throw normaliseApiError(new Error('Auth.PASSKEY_JSON_HELPERS_UNAVAILABLE'), 'Auth.PASSKEY_JSON_HELPERS_UNAVAILABLE');
|
|
256
|
-
}
|
|
257
|
-
const publicKeyJson = await registerPasskeyStart({ passwordless: true });
|
|
258
|
-
let credential;
|
|
259
|
-
try {
|
|
260
|
-
const publicKeyOptions = window.PublicKeyCredential.parseCreationOptionsFromJSON(publicKeyJson);
|
|
261
|
-
credential = await navigator.credentials.create({ publicKey: publicKeyOptions });
|
|
262
|
-
}
|
|
263
|
-
catch (err) {
|
|
264
|
-
if (err && err.name === 'NotAllowedError') {
|
|
265
|
-
throw normaliseApiError(err, 'Auth.PASSKEY_CREATION_CANCELLED');
|
|
266
|
-
}
|
|
267
|
-
throw err;
|
|
268
|
-
}
|
|
269
|
-
if (!credential) {
|
|
270
|
-
throw normaliseApiError(new Error('Auth.PASSKEY_CREATION_CANCELLED'), 'Auth.PASSKEY_CREATION_CANCELLED');
|
|
271
|
-
}
|
|
272
|
-
const credentialJson = serializeCredential(credential);
|
|
273
|
-
return registerPasskeyComplete(credentialJson, name);
|
|
274
|
-
}
|
|
275
|
-
async function loginWithPasskeyStart() {
|
|
276
|
-
ensureWebAuthnSupport();
|
|
156
|
+
export async function getPasskeyLoginOptions() {
|
|
157
|
+
var _a;
|
|
277
158
|
const res = await apiClient.get(`${HEADLESS_BASE}/auth/webauthn/login`);
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
const requestOptionsJson = (payload.request_options && payload.request_options.publicKey) ||
|
|
281
|
-
payload.request_options ||
|
|
282
|
-
payload;
|
|
283
|
-
return requestOptionsJson;
|
|
159
|
+
const data = ((_a = res.data) === null || _a === void 0 ? void 0 : _a.data) || res.data;
|
|
160
|
+
return data.request_options || data;
|
|
284
161
|
}
|
|
285
|
-
async function
|
|
286
|
-
const res = await apiClient.post(`${HEADLESS_BASE}/auth/webauthn/login`, {
|
|
162
|
+
export async function completePasskeyLogin(credentialJson) {
|
|
163
|
+
const res = await apiClient.post(`${HEADLESS_BASE}/auth/webauthn/login`, {
|
|
164
|
+
credential: credentialJson
|
|
165
|
+
});
|
|
287
166
|
return res.data;
|
|
288
167
|
}
|
|
289
|
-
export async function loginWithPasskey() {
|
|
290
|
-
ensureWebAuthnSupport();
|
|
291
|
-
if (!hasJsonWebAuthn) {
|
|
292
|
-
throw normaliseApiError(new Error('Auth.PASSKEY_JSON_HELPERS_UNAVAILABLE'), 'Auth.PASSKEY_JSON_HELPERS_UNAVAILABLE');
|
|
293
|
-
}
|
|
294
|
-
const requestOptionsJson = await loginWithPasskeyStart();
|
|
295
|
-
let assertion;
|
|
296
|
-
try {
|
|
297
|
-
const publicKeyOptions = window.PublicKeyCredential.parseRequestOptionsFromJSON(requestOptionsJson);
|
|
298
|
-
assertion = await navigator.credentials.get({ publicKey: publicKeyOptions });
|
|
299
|
-
}
|
|
300
|
-
catch (err) {
|
|
301
|
-
const e = new Error('Auth.PASSKEY_AUTH_CANCELLED');
|
|
302
|
-
e.code = 'Auth.PASSKEY_AUTH_CANCELLED';
|
|
303
|
-
throw e;
|
|
304
|
-
}
|
|
305
|
-
if (!assertion) {
|
|
306
|
-
const e = new Error('Auth.PASSKEY_AUTH_CANCELLED');
|
|
307
|
-
e.code = 'Auth.PASSKEY_AUTH_CANCELLED';
|
|
308
|
-
throw e;
|
|
309
|
-
}
|
|
310
|
-
const credentialJson = serializeCredential(assertion);
|
|
311
|
-
let postError = null;
|
|
312
|
-
try {
|
|
313
|
-
await loginWithPasskeyComplete(credentialJson);
|
|
314
|
-
}
|
|
315
|
-
catch (err) {
|
|
316
|
-
postError = err;
|
|
317
|
-
}
|
|
318
|
-
try {
|
|
319
|
-
const user = await fetchCurrentUser();
|
|
320
|
-
return { user };
|
|
321
|
-
}
|
|
322
|
-
catch (err) {
|
|
323
|
-
if (postError)
|
|
324
|
-
throw normaliseApiError(postError, 'Auth.PASSKEY_FAILED');
|
|
325
|
-
throw normaliseApiError(err, 'Auth.PASSKEY_FAILED');
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
168
|
export async function fetchPasskeys() {
|
|
329
169
|
try {
|
|
330
170
|
const res = await apiClient.get(`${USERS_BASE}/passkeys/`);
|
|
@@ -336,7 +176,6 @@ export async function fetchPasskeys() {
|
|
|
336
176
|
}
|
|
337
177
|
export async function deletePasskey(id) {
|
|
338
178
|
try {
|
|
339
|
-
// CHANGED: axios -> apiClient
|
|
340
179
|
await apiClient.delete(`${USERS_BASE}/passkeys/${id}/`);
|
|
341
180
|
}
|
|
342
181
|
catch (error) {
|
|
@@ -344,77 +183,24 @@ export async function deletePasskey(id) {
|
|
|
344
183
|
}
|
|
345
184
|
}
|
|
346
185
|
// -----------------------------
|
|
347
|
-
//
|
|
348
|
-
// -----------------------------
|
|
349
|
-
export async function authenticateWithMFA({ code, credential }) {
|
|
350
|
-
const payload = {};
|
|
351
|
-
if (code)
|
|
352
|
-
payload.code = code;
|
|
353
|
-
if (credential)
|
|
354
|
-
payload.credential = credential;
|
|
355
|
-
try {
|
|
356
|
-
const res = await apiClient.post(`${HEADLESS_BASE}/auth/2fa/authenticate`, payload);
|
|
357
|
-
return res.data;
|
|
358
|
-
}
|
|
359
|
-
catch (error) {
|
|
360
|
-
throw normaliseApiError(error, 'Auth.MFA_AUTHENTICATE_FAILED');
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
// -----------------------------
|
|
364
|
-
// Authentication: password
|
|
365
|
-
// -----------------------------
|
|
366
|
-
export async function loginWithPassword(email, password) {
|
|
367
|
-
var _a, _b, _c;
|
|
368
|
-
try {
|
|
369
|
-
await apiClient.post(`${HEADLESS_BASE}/auth/login`, { email, password });
|
|
370
|
-
}
|
|
371
|
-
catch (error) {
|
|
372
|
-
const status = (_a = error.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
373
|
-
const body = (_b = error.response) === null || _b === void 0 ? void 0 : _b.data;
|
|
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) || [];
|
|
375
|
-
const mfaFlow = Array.isArray(flows)
|
|
376
|
-
? flows.find((f) => f.id === 'mfa_authenticate')
|
|
377
|
-
: null;
|
|
378
|
-
if (status === 401 && mfaFlow && mfaFlow.is_pending) {
|
|
379
|
-
return {
|
|
380
|
-
needsMfa: true,
|
|
381
|
-
availableTypes: mfaFlow.types || [],
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
if (status === 409) {
|
|
385
|
-
// Already logged in
|
|
386
|
-
}
|
|
387
|
-
else {
|
|
388
|
-
throw normaliseApiError(error, 'Auth.LOGIN_FAILED');
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
const user = await fetchCurrentUser();
|
|
392
|
-
return { user, needsMfa: false };
|
|
393
|
-
}
|
|
394
|
-
// -----------------------------
|
|
395
|
-
// Authenticators & MFA
|
|
186
|
+
// Authenticators (TOTP & Recovery)
|
|
396
187
|
// -----------------------------
|
|
397
188
|
export async function fetchAuthenticators() {
|
|
398
189
|
const res = await apiClient.get(`${HEADLESS_BASE}/account/authenticators`);
|
|
399
190
|
const body = res.data || {};
|
|
400
|
-
|
|
401
|
-
return items;
|
|
191
|
+
return Array.isArray(body.data) ? body.data : (Array.isArray(body) ? body : []);
|
|
402
192
|
}
|
|
403
193
|
export async function requestTotpKey() {
|
|
404
|
-
var _a;
|
|
194
|
+
var _a, _b, _c;
|
|
405
195
|
try {
|
|
406
196
|
const res = await apiClient.get(`${HEADLESS_BASE}/account/authenticators/totp`);
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
return {
|
|
410
|
-
exists: true,
|
|
411
|
-
authenticator: data,
|
|
412
|
-
};
|
|
197
|
+
const data = ((_a = res.data) === null || _a === void 0 ? void 0 : _a.data) || res.data;
|
|
198
|
+
return { exists: true, authenticator: data };
|
|
413
199
|
}
|
|
414
200
|
catch (error) {
|
|
415
|
-
|
|
416
|
-
if ((response === null ||
|
|
417
|
-
const meta = ((
|
|
201
|
+
// 404 bedeutet: Noch kein TOTP eingerichtet -> Wir bekommen das Secret zum Einrichten
|
|
202
|
+
if (((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) === 404) {
|
|
203
|
+
const meta = ((_c = error.response.data) === null || _c === void 0 ? void 0 : _c.meta) || {};
|
|
418
204
|
return {
|
|
419
205
|
exists: false,
|
|
420
206
|
secret: meta.secret,
|
|
@@ -426,8 +212,7 @@ export async function requestTotpKey() {
|
|
|
426
212
|
}
|
|
427
213
|
export async function activateTotp(code) {
|
|
428
214
|
try {
|
|
429
|
-
|
|
430
|
-
return res.data;
|
|
215
|
+
await apiClient.post(`${HEADLESS_BASE}/account/authenticators/totp`, { code });
|
|
431
216
|
}
|
|
432
217
|
catch (error) {
|
|
433
218
|
throw normaliseApiError(error, 'Auth.TOTP_ACTIVATE_FAILED');
|
|
@@ -435,67 +220,70 @@ export async function activateTotp(code) {
|
|
|
435
220
|
}
|
|
436
221
|
export async function deactivateTotp() {
|
|
437
222
|
try {
|
|
438
|
-
|
|
439
|
-
const res = await apiClient.delete(`${HEADLESS_BASE}/account/authenticators/totp`);
|
|
440
|
-
return res.data;
|
|
223
|
+
await apiClient.delete(`${HEADLESS_BASE}/account/authenticators/totp`);
|
|
441
224
|
}
|
|
442
225
|
catch (error) {
|
|
443
226
|
throw normaliseApiError(error, 'Auth.TOTP_DEACTIVATE_FAILED');
|
|
444
227
|
}
|
|
445
228
|
}
|
|
446
229
|
export async function fetchRecoveryCodes() {
|
|
447
|
-
var _a;
|
|
230
|
+
var _a, _b, _c;
|
|
448
231
|
try {
|
|
232
|
+
// Versuch, Codes zu laden
|
|
449
233
|
const res = await apiClient.get(`${HEADLESS_BASE}/account/authenticators/recovery-codes`);
|
|
450
|
-
|
|
451
|
-
const data = body.data || body;
|
|
452
|
-
return data;
|
|
234
|
+
return ((_a = res.data) === null || _a === void 0 ? void 0 : _a.data) || res.data;
|
|
453
235
|
}
|
|
454
236
|
catch (error) {
|
|
455
|
-
|
|
456
|
-
if (status === 404) {
|
|
237
|
+
// 404 -> Noch keine Codes -> Generieren
|
|
238
|
+
if (((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) === 404) {
|
|
457
239
|
const resPost = await apiClient.post(`${HEADLESS_BASE}/account/authenticators/recovery-codes`, {});
|
|
458
|
-
|
|
459
|
-
const data = body.data || body;
|
|
460
|
-
return data;
|
|
240
|
+
return ((_c = resPost.data) === null || _c === void 0 ? void 0 : _c.data) || resPost.data;
|
|
461
241
|
}
|
|
462
242
|
throw normaliseApiError(error, 'Auth.RECOVERY_CODES_FETCH_FAILED');
|
|
463
243
|
}
|
|
464
244
|
}
|
|
465
245
|
export async function generateRecoveryCodes() {
|
|
246
|
+
var _a;
|
|
466
247
|
try {
|
|
467
248
|
const res = await apiClient.post(`${HEADLESS_BASE}/account/authenticators/recovery-codes`, {});
|
|
468
|
-
|
|
469
|
-
const data = body.data || body;
|
|
470
|
-
return data;
|
|
249
|
+
return ((_a = res.data) === null || _a === void 0 ? void 0 : _a.data) || res.data;
|
|
471
250
|
}
|
|
472
251
|
catch (error) {
|
|
473
252
|
throw normaliseApiError(error, 'Auth.RECOVERY_CODES_GENERATE_FAILED');
|
|
474
253
|
}
|
|
475
254
|
}
|
|
476
|
-
|
|
255
|
+
// -----------------------------
|
|
256
|
+
// Invitations & Access Codes
|
|
257
|
+
// -----------------------------
|
|
258
|
+
export async function validateAccessCode(code) {
|
|
477
259
|
try {
|
|
478
|
-
const res = await apiClient.
|
|
260
|
+
const res = await apiClient.post(`${ACCESS_CODES_BASE}/validate/`, { code });
|
|
479
261
|
return res.data;
|
|
480
262
|
}
|
|
481
263
|
catch (error) {
|
|
482
|
-
|
|
483
|
-
if ((response === null || response === void 0 ? void 0 : response.status) === 404) {
|
|
484
|
-
const resPost = await apiClient.post(`${HEADLESS_BASE}/mfa/recovery_codes`, {});
|
|
485
|
-
return resPost.data;
|
|
486
|
-
}
|
|
487
|
-
throw normaliseApiError(error, 'Auth.RECOVERY_CODES_FETCH_FAILED');
|
|
264
|
+
throw normaliseApiError(error, 'Auth.ACCESS_CODE_INVALID_OR_INACTIVE');
|
|
488
265
|
}
|
|
489
266
|
}
|
|
490
|
-
export function
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
267
|
+
export async function requestInviteWithCode(email, accessCode) {
|
|
268
|
+
const payload = { email };
|
|
269
|
+
if (accessCode)
|
|
270
|
+
payload.access_code = accessCode;
|
|
271
|
+
try {
|
|
272
|
+
const res = await apiClient.post(`${USERS_BASE}/invite/`, payload);
|
|
273
|
+
return res.data;
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
throw normaliseApiError(error, 'Auth.INVITE_FAILED');
|
|
277
|
+
}
|
|
495
278
|
}
|
|
279
|
+
// -----------------------------
|
|
280
|
+
// Recovery Support (Admin/Support Side)
|
|
281
|
+
// -----------------------------
|
|
496
282
|
export async function requestMfaSupportHelp(emailOrIdentifier, message = '') {
|
|
497
|
-
const
|
|
498
|
-
|
|
283
|
+
const res = await apiClient.post(`${USERS_BASE}/mfa/support-help/`, {
|
|
284
|
+
email: emailOrIdentifier,
|
|
285
|
+
message
|
|
286
|
+
});
|
|
499
287
|
return res.data;
|
|
500
288
|
}
|
|
501
289
|
export async function fetchRecoveryRequests(status = 'pending') {
|
|
@@ -503,16 +291,23 @@ export async function fetchRecoveryRequests(status = 'pending') {
|
|
|
503
291
|
return res.data;
|
|
504
292
|
}
|
|
505
293
|
export async function approveRecoveryRequest(id, supportNote) {
|
|
506
|
-
const res = await apiClient.post(`/api/support/recovery-requests/${id}/approve/`, {
|
|
294
|
+
const res = await apiClient.post(`/api/support/recovery-requests/${id}/approve/`, {
|
|
295
|
+
support_note: supportNote || ''
|
|
296
|
+
});
|
|
507
297
|
return res.data;
|
|
508
298
|
}
|
|
509
299
|
export async function rejectRecoveryRequest(id, supportNote) {
|
|
510
|
-
const res = await apiClient.post(`/api/support/recovery-requests/${id}/reject/`, {
|
|
300
|
+
const res = await apiClient.post(`/api/support/recovery-requests/${id}/reject/`, {
|
|
301
|
+
support_note: supportNote || ''
|
|
302
|
+
});
|
|
511
303
|
return res.data;
|
|
512
304
|
}
|
|
513
305
|
export async function loginWithRecoveryPassword(email, password, token) {
|
|
514
306
|
try {
|
|
515
|
-
await apiClient.post(`/api/support/recovery-requests/recovery-login/${token}/`, {
|
|
307
|
+
await apiClient.post(`/api/support/recovery-requests/recovery-login/${token}/`, {
|
|
308
|
+
email,
|
|
309
|
+
password
|
|
310
|
+
});
|
|
516
311
|
}
|
|
517
312
|
catch (error) {
|
|
518
313
|
throw normaliseApiError(error, 'Auth.RECOVERY_LOGIN_FAILED');
|
|
@@ -520,39 +315,3 @@ export async function loginWithRecoveryPassword(email, password, token) {
|
|
|
520
315
|
const user = await fetchCurrentUser();
|
|
521
316
|
return { user, needsMfa: false };
|
|
522
317
|
}
|
|
523
|
-
// -----------------------------
|
|
524
|
-
// Aggregated API object
|
|
525
|
-
// -----------------------------
|
|
526
|
-
export const authApi = {
|
|
527
|
-
fetchCurrentUser,
|
|
528
|
-
updateUserProfile,
|
|
529
|
-
loginWithPassword,
|
|
530
|
-
requestPasswordReset,
|
|
531
|
-
changePassword,
|
|
532
|
-
logoutSession,
|
|
533
|
-
startSocialLogin,
|
|
534
|
-
fetchHeadlessSession,
|
|
535
|
-
verifyResetToken,
|
|
536
|
-
setNewPassword,
|
|
537
|
-
loginWithPasskey,
|
|
538
|
-
registerPasskey,
|
|
539
|
-
fetchPasskeys,
|
|
540
|
-
deletePasskey,
|
|
541
|
-
authenticateWithMFA,
|
|
542
|
-
fetchAuthenticators,
|
|
543
|
-
requestTotpKey,
|
|
544
|
-
activateTotp,
|
|
545
|
-
deactivateTotp,
|
|
546
|
-
fetchRecoveryCodes,
|
|
547
|
-
generateRecoveryCodes,
|
|
548
|
-
fetchOrGenerateRecoveryCodes,
|
|
549
|
-
validateAccessCode,
|
|
550
|
-
requestInviteWithCode,
|
|
551
|
-
isStrongSession,
|
|
552
|
-
requestMfaSupportHelp,
|
|
553
|
-
fetchRecoveryRequests,
|
|
554
|
-
approveRecoveryRequest,
|
|
555
|
-
rejectRecoveryRequest,
|
|
556
|
-
loginWithRecoveryPassword,
|
|
557
|
-
};
|
|
558
|
-
export default authApi;
|