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