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