@bagelink/auth 1.4.178 → 1.4.182
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/README.md +11 -8
- package/dist/index.cjs +461 -34
- package/dist/index.d.cts +297 -8
- package/dist/index.d.mts +297 -8
- package/dist/index.d.ts +297 -8
- package/dist/index.mjs +450 -35
- package/package.json +1 -1
- package/src/api.ts +54 -36
- package/src/index.ts +1 -0
- package/src/sso.ts +565 -0
- package/src/types.ts +35 -2
- package/src/useAuth.ts +87 -5
- package/src/utils.ts +3 -3
package/README.md
CHANGED
|
@@ -28,18 +28,14 @@ bun add @bagelink/auth
|
|
|
28
28
|
### 1. Initialize Auth
|
|
29
29
|
|
|
30
30
|
```typescript
|
|
31
|
-
import { initAuth } from '@bagelink/auth'
|
|
32
|
-
import axios from 'axios'
|
|
31
|
+
import { initAuth, AuthState } from '@bagelink/auth'
|
|
33
32
|
|
|
34
33
|
const auth = initAuth({
|
|
35
|
-
axios: axios,
|
|
36
34
|
baseURL: 'https://api.example.com'
|
|
37
35
|
})
|
|
38
36
|
|
|
39
37
|
// Listen to auth events
|
|
40
|
-
auth.on(AuthState.LOGIN, () =>
|
|
41
|
-
console.log('User logged in!')
|
|
42
|
-
})
|
|
38
|
+
auth.on(AuthState.LOGIN, () => console.log('User logged in!'))
|
|
43
39
|
```
|
|
44
40
|
|
|
45
41
|
### 2. Use in Vue Components
|
|
@@ -50,17 +46,23 @@ import { useAuth } from '@bagelink/auth'
|
|
|
50
46
|
|
|
51
47
|
const {
|
|
52
48
|
user, // Primary state - use this!
|
|
49
|
+
sso, // SSO providers
|
|
53
50
|
getIsLoggedIn,
|
|
54
51
|
login,
|
|
55
52
|
logout
|
|
56
53
|
} = useAuth()
|
|
57
54
|
|
|
58
|
-
const
|
|
55
|
+
const handlePasswordLogin = async () => {
|
|
59
56
|
await login({
|
|
60
57
|
email: 'user@example.com',
|
|
61
58
|
password: 'password'
|
|
62
59
|
})
|
|
63
60
|
}
|
|
61
|
+
|
|
62
|
+
const handleSSOLogin = async () => {
|
|
63
|
+
// SSO is just this simple!
|
|
64
|
+
await sso.google.redirect()
|
|
65
|
+
}
|
|
64
66
|
</script>
|
|
65
67
|
|
|
66
68
|
<template>
|
|
@@ -70,7 +72,8 @@ const handleLogin = async () => {
|
|
|
70
72
|
<button @click="logout">Logout</button>
|
|
71
73
|
</div>
|
|
72
74
|
<div v-else>
|
|
73
|
-
<button @click="
|
|
75
|
+
<button @click="handlePasswordLogin">Login with Password</button>
|
|
76
|
+
<button @click="handleSSOLogin">Login with Google</button>
|
|
74
77
|
</div>
|
|
75
78
|
</template>
|
|
76
79
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -7,12 +7,12 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
|
|
|
7
7
|
|
|
8
8
|
const axios__default = /*#__PURE__*/_interopDefaultCompat(axios);
|
|
9
9
|
|
|
10
|
-
function createAxiosInstance(baseURL
|
|
10
|
+
function createAxiosInstance(baseURL) {
|
|
11
11
|
return axios__default.create({
|
|
12
|
-
baseURL
|
|
12
|
+
baseURL,
|
|
13
|
+
withCredentials: true,
|
|
13
14
|
headers: {
|
|
14
|
-
"Content-Type": "application/json"
|
|
15
|
-
"withCredentials": true
|
|
15
|
+
"Content-Type": "application/json"
|
|
16
16
|
}
|
|
17
17
|
});
|
|
18
18
|
}
|
|
@@ -49,16 +49,12 @@ class EventEmitter {
|
|
|
49
49
|
|
|
50
50
|
class AuthApi {
|
|
51
51
|
api;
|
|
52
|
-
constructor(
|
|
53
|
-
this.api =
|
|
52
|
+
constructor(baseURL = "") {
|
|
53
|
+
this.api = createAxiosInstance(baseURL);
|
|
54
54
|
this.setupInterceptors();
|
|
55
55
|
}
|
|
56
56
|
setupInterceptors() {
|
|
57
57
|
this.api.interceptors.request.use((config) => {
|
|
58
|
-
const sessionToken = localStorage.getItem("session_token");
|
|
59
|
-
if (sessionToken !== null && config.headers) {
|
|
60
|
-
config.headers.Authorization = `Bearer ${sessionToken}`;
|
|
61
|
-
}
|
|
62
58
|
const urlParams = new URLSearchParams(window.location.search);
|
|
63
59
|
const resetToken = urlParams.get("token");
|
|
64
60
|
if (resetToken !== null && config.headers) {
|
|
@@ -80,45 +76,67 @@ class AuthApi {
|
|
|
80
76
|
* Register a new account
|
|
81
77
|
*/
|
|
82
78
|
async register(data) {
|
|
83
|
-
|
|
79
|
+
return this.api.post("/authentication/register", {
|
|
84
80
|
...data,
|
|
85
81
|
email: data.email.toLowerCase()
|
|
86
82
|
});
|
|
87
|
-
if (response.data.session_token) {
|
|
88
|
-
localStorage.setItem("session_token", response.data.session_token);
|
|
89
|
-
}
|
|
90
|
-
return response;
|
|
91
83
|
}
|
|
92
84
|
/**
|
|
93
85
|
* Login with password
|
|
94
86
|
*/
|
|
95
87
|
async login(email, password) {
|
|
96
|
-
|
|
88
|
+
return this.api.post("/authentication/login/password", {
|
|
97
89
|
email: email.toLowerCase(),
|
|
98
90
|
password
|
|
99
91
|
});
|
|
100
|
-
if (response.data.session_token) {
|
|
101
|
-
localStorage.setItem("session_token", response.data.session_token);
|
|
102
|
-
}
|
|
103
|
-
return response;
|
|
104
92
|
}
|
|
105
93
|
/**
|
|
106
94
|
* Logout and clear session
|
|
107
95
|
*/
|
|
108
96
|
async logout() {
|
|
109
|
-
|
|
110
|
-
localStorage.removeItem("session_token");
|
|
111
|
-
return response;
|
|
97
|
+
return this.api.post("/authentication/logout", {});
|
|
112
98
|
}
|
|
113
99
|
/**
|
|
114
100
|
* Refresh current session
|
|
115
101
|
*/
|
|
116
102
|
async refreshSession() {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
103
|
+
return this.api.post("/authentication/refresh", {});
|
|
104
|
+
}
|
|
105
|
+
// ============================================
|
|
106
|
+
// SSO Authentication Methods
|
|
107
|
+
// ============================================
|
|
108
|
+
/**
|
|
109
|
+
* Initiate SSO login flow
|
|
110
|
+
* Returns authorization URL to redirect user to
|
|
111
|
+
*/
|
|
112
|
+
async initiateSSO(data) {
|
|
113
|
+
return this.api.post(`/authentication/sso/${data.provider}/initiate`, {
|
|
114
|
+
redirect_uri: data.redirect_uri,
|
|
115
|
+
state: data.state
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Complete SSO login after callback from provider
|
|
120
|
+
*/
|
|
121
|
+
async ssoCallback(data) {
|
|
122
|
+
return this.api.post(`/authentication/sso/${data.provider}/callback`, {
|
|
123
|
+
code: data.code,
|
|
124
|
+
state: data.state
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Link an SSO provider to existing account
|
|
129
|
+
*/
|
|
130
|
+
async linkSSOProvider(data) {
|
|
131
|
+
return this.api.post(`/authentication/sso/${data.provider}/link`, {
|
|
132
|
+
code: data.code
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Unlink an SSO provider from account
|
|
137
|
+
*/
|
|
138
|
+
async unlinkSSOProvider(provider) {
|
|
139
|
+
return this.api.delete(`/authentication/sso/${provider}/unlink`);
|
|
122
140
|
}
|
|
123
141
|
// ============================================
|
|
124
142
|
// Current User (Me) Methods
|
|
@@ -139,9 +157,7 @@ class AuthApi {
|
|
|
139
157
|
* Delete current user account
|
|
140
158
|
*/
|
|
141
159
|
async deleteCurrentUser() {
|
|
142
|
-
|
|
143
|
-
localStorage.removeItem("session_token");
|
|
144
|
-
return response;
|
|
160
|
+
return this.api.delete("/authentication/me");
|
|
145
161
|
}
|
|
146
162
|
// ============================================
|
|
147
163
|
// Account Management (Admin)
|
|
@@ -251,6 +267,356 @@ class AuthApi {
|
|
|
251
267
|
}
|
|
252
268
|
}
|
|
253
269
|
|
|
270
|
+
let authApiRef = null;
|
|
271
|
+
function setAuthContext(authApi) {
|
|
272
|
+
authApiRef = authApi;
|
|
273
|
+
}
|
|
274
|
+
function getAuthApi() {
|
|
275
|
+
if (!authApiRef) {
|
|
276
|
+
throw new Error("SSO auth context not initialized. Make sure to call useAuth() before using SSO methods.");
|
|
277
|
+
}
|
|
278
|
+
return authApiRef;
|
|
279
|
+
}
|
|
280
|
+
class SSOError extends Error {
|
|
281
|
+
constructor(message, code) {
|
|
282
|
+
super(message);
|
|
283
|
+
this.code = code;
|
|
284
|
+
this.name = "SSOError";
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
class PopupBlockedError extends SSOError {
|
|
288
|
+
constructor() {
|
|
289
|
+
super("Popup was blocked. Please allow popups for this site.", "POPUP_BLOCKED");
|
|
290
|
+
this.name = "PopupBlockedError";
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
class PopupClosedError extends SSOError {
|
|
294
|
+
constructor() {
|
|
295
|
+
super("Popup was closed by user", "POPUP_CLOSED");
|
|
296
|
+
this.name = "PopupClosedError";
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
class PopupTimeoutError extends SSOError {
|
|
300
|
+
constructor() {
|
|
301
|
+
super("Popup authentication timed out", "POPUP_TIMEOUT");
|
|
302
|
+
this.name = "PopupTimeoutError";
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
class StateMismatchError extends SSOError {
|
|
306
|
+
constructor() {
|
|
307
|
+
super("State mismatch - possible CSRF attack", "STATE_MISMATCH");
|
|
308
|
+
this.name = "StateMismatchError";
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function generateState() {
|
|
312
|
+
const array = new Uint8Array(32);
|
|
313
|
+
crypto.getRandomValues(array);
|
|
314
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
315
|
+
}
|
|
316
|
+
function openPopup(url, width = 500, height = 600) {
|
|
317
|
+
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
318
|
+
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
319
|
+
return window.open(
|
|
320
|
+
url,
|
|
321
|
+
"oauth-popup",
|
|
322
|
+
`width=${width},height=${height},left=${left},top=${top},toolbar=no,menubar=no,location=no,status=no`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
function waitForPopupCallback(popup, provider, timeoutMs = 9e4) {
|
|
326
|
+
return new Promise((resolve, reject) => {
|
|
327
|
+
let done = false;
|
|
328
|
+
const finish = (fn) => {
|
|
329
|
+
if (!done) {
|
|
330
|
+
done = true;
|
|
331
|
+
fn();
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
const timer = setTimeout(() => {
|
|
335
|
+
finish(() => {
|
|
336
|
+
reject(new PopupTimeoutError());
|
|
337
|
+
});
|
|
338
|
+
}, timeoutMs);
|
|
339
|
+
function onMessage(ev) {
|
|
340
|
+
try {
|
|
341
|
+
if (ev.origin !== window.location.origin) return;
|
|
342
|
+
const data = ev.data || {};
|
|
343
|
+
if (data.type !== "auth:complete" || data.provider !== provider) return;
|
|
344
|
+
cleanup();
|
|
345
|
+
if (data.error) {
|
|
346
|
+
reject(new SSOError(data.error, "OAUTH_ERROR"));
|
|
347
|
+
} else if (data.code) {
|
|
348
|
+
resolve({ code: data.code, state: data.state });
|
|
349
|
+
}
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const pollInterval = setInterval(() => {
|
|
354
|
+
try {
|
|
355
|
+
if (popup.closed) {
|
|
356
|
+
cleanup();
|
|
357
|
+
reject(new PopupClosedError());
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const url = new URL(popup.location.href);
|
|
361
|
+
if (url.origin === window.location.origin) {
|
|
362
|
+
const code = url.searchParams.get("code");
|
|
363
|
+
const state = url.searchParams.get("state") ?? void 0;
|
|
364
|
+
const error = url.searchParams.get("error");
|
|
365
|
+
if (code || error) {
|
|
366
|
+
cleanup();
|
|
367
|
+
try {
|
|
368
|
+
popup.close();
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
if (error) {
|
|
372
|
+
reject(new SSOError(error, "OAUTH_ERROR"));
|
|
373
|
+
} else if (code) {
|
|
374
|
+
resolve({ code, state });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
} catch {
|
|
379
|
+
}
|
|
380
|
+
}, 150);
|
|
381
|
+
function cleanup() {
|
|
382
|
+
finish(() => {
|
|
383
|
+
clearInterval(pollInterval);
|
|
384
|
+
clearTimeout(timer);
|
|
385
|
+
window.removeEventListener("message", onMessage);
|
|
386
|
+
try {
|
|
387
|
+
popup.close();
|
|
388
|
+
} catch {
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
window.addEventListener("message", onMessage);
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
function createSSOProvider(config) {
|
|
396
|
+
const getDefaultRedirectUri = () => {
|
|
397
|
+
if (typeof window !== "undefined") {
|
|
398
|
+
return `${window.location.origin}/auth/callback`;
|
|
399
|
+
}
|
|
400
|
+
return `/auth/callback`;
|
|
401
|
+
};
|
|
402
|
+
const getStateKey = () => `oauth_state:${config.id}`;
|
|
403
|
+
return {
|
|
404
|
+
...config,
|
|
405
|
+
async redirect(options = {}) {
|
|
406
|
+
const auth = getAuthApi();
|
|
407
|
+
const redirectUri = options.redirectUri ?? getDefaultRedirectUri();
|
|
408
|
+
const state = options.state ?? generateState();
|
|
409
|
+
if (typeof sessionStorage !== "undefined") {
|
|
410
|
+
sessionStorage.setItem(getStateKey(), state);
|
|
411
|
+
sessionStorage.setItem(`oauth_provider:${state}`, config.id);
|
|
412
|
+
}
|
|
413
|
+
const authUrl = await auth.initiateSSO({
|
|
414
|
+
provider: config.id,
|
|
415
|
+
redirect_uri: redirectUri,
|
|
416
|
+
state,
|
|
417
|
+
scopes: options.scopes ?? config.defaultScopes,
|
|
418
|
+
params: options.params
|
|
419
|
+
});
|
|
420
|
+
window.location.href = authUrl;
|
|
421
|
+
},
|
|
422
|
+
async popup(options = {}) {
|
|
423
|
+
const auth = getAuthApi();
|
|
424
|
+
const redirectUri = options.redirectUri ?? getDefaultRedirectUri();
|
|
425
|
+
const state = options.state ?? generateState();
|
|
426
|
+
const timeout = options.popupTimeout ?? 9e4;
|
|
427
|
+
if (typeof sessionStorage !== "undefined") {
|
|
428
|
+
sessionStorage.setItem(getStateKey(), state);
|
|
429
|
+
sessionStorage.setItem(`oauth_provider:${state}`, config.id);
|
|
430
|
+
}
|
|
431
|
+
const authUrl = await auth.initiateSSO({
|
|
432
|
+
provider: config.id,
|
|
433
|
+
redirect_uri: redirectUri,
|
|
434
|
+
state,
|
|
435
|
+
scopes: options.scopes ?? config.defaultScopes,
|
|
436
|
+
params: options.params
|
|
437
|
+
});
|
|
438
|
+
const { width = 500, height = 600 } = options.popupDimensions ?? {};
|
|
439
|
+
const popupWindow = openPopup(authUrl, width, height);
|
|
440
|
+
if (!popupWindow) {
|
|
441
|
+
throw new PopupBlockedError();
|
|
442
|
+
}
|
|
443
|
+
const result = await waitForPopupCallback(popupWindow, config.id, timeout);
|
|
444
|
+
return auth.loginWithSSO({
|
|
445
|
+
provider: config.id,
|
|
446
|
+
code: result.code,
|
|
447
|
+
state: result.state
|
|
448
|
+
});
|
|
449
|
+
},
|
|
450
|
+
async callback(code, state) {
|
|
451
|
+
const auth = getAuthApi();
|
|
452
|
+
if (typeof sessionStorage !== "undefined" && state) {
|
|
453
|
+
const storedState = sessionStorage.getItem(getStateKey());
|
|
454
|
+
sessionStorage.removeItem(getStateKey());
|
|
455
|
+
sessionStorage.removeItem(`oauth_provider:${state}`);
|
|
456
|
+
if (storedState && storedState !== state) {
|
|
457
|
+
throw new StateMismatchError();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return auth.loginWithSSO({
|
|
461
|
+
provider: config.id,
|
|
462
|
+
code,
|
|
463
|
+
state
|
|
464
|
+
});
|
|
465
|
+
},
|
|
466
|
+
async link(code) {
|
|
467
|
+
const auth = getAuthApi();
|
|
468
|
+
await auth.linkSSOProvider({
|
|
469
|
+
provider: config.id,
|
|
470
|
+
code
|
|
471
|
+
});
|
|
472
|
+
},
|
|
473
|
+
async unlink() {
|
|
474
|
+
const auth = getAuthApi();
|
|
475
|
+
await auth.unlinkSSOProvider(config.id);
|
|
476
|
+
},
|
|
477
|
+
async getAuthUrl(options = {}) {
|
|
478
|
+
const auth = getAuthApi();
|
|
479
|
+
const redirectUri = options.redirectUri ?? getDefaultRedirectUri();
|
|
480
|
+
const state = options.state ?? generateState();
|
|
481
|
+
return auth.initiateSSO({
|
|
482
|
+
provider: config.id,
|
|
483
|
+
redirect_uri: redirectUri,
|
|
484
|
+
state,
|
|
485
|
+
scopes: options.scopes ?? config.defaultScopes,
|
|
486
|
+
params: options.params
|
|
487
|
+
});
|
|
488
|
+
},
|
|
489
|
+
supportsPopup: true
|
|
490
|
+
// Default, can be overridden per provider
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
const sso = {
|
|
494
|
+
/**
|
|
495
|
+
* Google OAuth Provider
|
|
496
|
+
* https://developers.google.com/identity/protocols/oauth2
|
|
497
|
+
*/
|
|
498
|
+
google: createSSOProvider({
|
|
499
|
+
id: "google",
|
|
500
|
+
name: "Google",
|
|
501
|
+
color: "#4285F4",
|
|
502
|
+
icon: "google",
|
|
503
|
+
defaultScopes: ["openid", "email", "profile"],
|
|
504
|
+
metadata: {
|
|
505
|
+
authDomain: "accounts.google.com",
|
|
506
|
+
buttonText: "Continue with Google"
|
|
507
|
+
}
|
|
508
|
+
}),
|
|
509
|
+
/**
|
|
510
|
+
* Microsoft OAuth Provider (Azure AD / Microsoft Entra ID)
|
|
511
|
+
* https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
|
|
512
|
+
*/
|
|
513
|
+
microsoft: createSSOProvider({
|
|
514
|
+
id: "microsoft",
|
|
515
|
+
name: "Microsoft",
|
|
516
|
+
color: "#00A4EF",
|
|
517
|
+
icon: "microsoft",
|
|
518
|
+
defaultScopes: ["openid", "email", "profile", "User.Read"],
|
|
519
|
+
metadata: {
|
|
520
|
+
authDomain: "login.microsoftonline.com",
|
|
521
|
+
buttonText: "Continue with Microsoft"
|
|
522
|
+
}
|
|
523
|
+
}),
|
|
524
|
+
/**
|
|
525
|
+
* GitHub OAuth Provider
|
|
526
|
+
* https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
|
|
527
|
+
*/
|
|
528
|
+
github: createSSOProvider({
|
|
529
|
+
id: "github",
|
|
530
|
+
name: "GitHub",
|
|
531
|
+
color: "#24292E",
|
|
532
|
+
icon: "github",
|
|
533
|
+
defaultScopes: ["read:user", "user:email"],
|
|
534
|
+
metadata: {
|
|
535
|
+
authDomain: "github.com",
|
|
536
|
+
buttonText: "Continue with GitHub"
|
|
537
|
+
}
|
|
538
|
+
}),
|
|
539
|
+
/**
|
|
540
|
+
* Okta OAuth Provider
|
|
541
|
+
* https://developer.okta.com/docs/guides/implement-grant-type/authcode/main/
|
|
542
|
+
*/
|
|
543
|
+
okta: createSSOProvider({
|
|
544
|
+
id: "okta",
|
|
545
|
+
name: "Okta",
|
|
546
|
+
color: "#007DC1",
|
|
547
|
+
icon: "okta",
|
|
548
|
+
defaultScopes: ["openid", "email", "profile"],
|
|
549
|
+
metadata: {
|
|
550
|
+
buttonText: "Continue with Okta"
|
|
551
|
+
}
|
|
552
|
+
}),
|
|
553
|
+
/**
|
|
554
|
+
* Apple Sign In Provider
|
|
555
|
+
* https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api
|
|
556
|
+
* Note: Apple works best with redirect flow on web
|
|
557
|
+
*/
|
|
558
|
+
apple: {
|
|
559
|
+
...createSSOProvider({
|
|
560
|
+
id: "apple",
|
|
561
|
+
name: "Apple",
|
|
562
|
+
color: "#000000",
|
|
563
|
+
icon: "apple",
|
|
564
|
+
defaultScopes: ["name", "email"],
|
|
565
|
+
metadata: {
|
|
566
|
+
authDomain: "appleid.apple.com",
|
|
567
|
+
buttonText: "Continue with Apple"
|
|
568
|
+
}
|
|
569
|
+
}),
|
|
570
|
+
supportsPopup: false,
|
|
571
|
+
// Apple prefers redirect on web
|
|
572
|
+
// Override popup to use redirect for better UX
|
|
573
|
+
async popup(options) {
|
|
574
|
+
return this.redirect(options);
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
/**
|
|
578
|
+
* Facebook OAuth Provider
|
|
579
|
+
* https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow
|
|
580
|
+
*/
|
|
581
|
+
facebook: createSSOProvider({
|
|
582
|
+
id: "facebook",
|
|
583
|
+
name: "Facebook",
|
|
584
|
+
color: "#1877F2",
|
|
585
|
+
icon: "facebook",
|
|
586
|
+
defaultScopes: ["email", "public_profile"],
|
|
587
|
+
metadata: {
|
|
588
|
+
authDomain: "www.facebook.com",
|
|
589
|
+
buttonText: "Continue with Facebook"
|
|
590
|
+
}
|
|
591
|
+
})
|
|
592
|
+
};
|
|
593
|
+
const ssoProviders = Object.values(sso);
|
|
594
|
+
function getSSOProvider(provider) {
|
|
595
|
+
return sso[provider];
|
|
596
|
+
}
|
|
597
|
+
function getAllSSOProviders() {
|
|
598
|
+
return ssoProviders;
|
|
599
|
+
}
|
|
600
|
+
function isSupportedProvider(provider) {
|
|
601
|
+
return provider in sso;
|
|
602
|
+
}
|
|
603
|
+
async function handleOAuthCallback() {
|
|
604
|
+
if (typeof window === "undefined") {
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
608
|
+
const code = urlParams.get("code");
|
|
609
|
+
const state = urlParams.get("state");
|
|
610
|
+
if (!code || !state) {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
const provider = sessionStorage.getItem(`oauth_provider:${state}`);
|
|
614
|
+
if (!provider || !isSupportedProvider(provider)) {
|
|
615
|
+
throw new Error("Unable to determine OAuth provider. State may have expired.");
|
|
616
|
+
}
|
|
617
|
+
return sso[provider].callback(code, state);
|
|
618
|
+
}
|
|
619
|
+
|
|
254
620
|
var AuthState = /* @__PURE__ */ ((AuthState2) => {
|
|
255
621
|
AuthState2["LOGIN"] = "login";
|
|
256
622
|
AuthState2["LOGOUT"] = "logout";
|
|
@@ -291,7 +657,7 @@ function accountToUser(account) {
|
|
|
291
657
|
metadata: account.entity.metadata
|
|
292
658
|
};
|
|
293
659
|
}
|
|
294
|
-
const emailMethod = account.authentication_methods
|
|
660
|
+
const emailMethod = account.authentication_methods.find(
|
|
295
661
|
(m) => m.type === "password" || m.type === "email_token"
|
|
296
662
|
);
|
|
297
663
|
return {
|
|
@@ -310,11 +676,10 @@ let authApi = null;
|
|
|
310
676
|
let eventEmitter = null;
|
|
311
677
|
const accountInfo = vue.ref(null);
|
|
312
678
|
function initAuth({
|
|
313
|
-
axios,
|
|
314
679
|
baseURL
|
|
315
680
|
}) {
|
|
316
681
|
if (authApi === null) {
|
|
317
|
-
authApi = new AuthApi(
|
|
682
|
+
authApi = new AuthApi(baseURL);
|
|
318
683
|
}
|
|
319
684
|
if (eventEmitter === null) {
|
|
320
685
|
eventEmitter = new EventEmitter();
|
|
@@ -350,6 +715,29 @@ function useAuth() {
|
|
|
350
715
|
}
|
|
351
716
|
const api = authApi;
|
|
352
717
|
const emitter = eventEmitter;
|
|
718
|
+
const authMethods = {
|
|
719
|
+
initiateSSO: async (params) => {
|
|
720
|
+
const { data } = await api.initiateSSO(params);
|
|
721
|
+
return data.authorization_url;
|
|
722
|
+
},
|
|
723
|
+
loginWithSSO: async (params) => {
|
|
724
|
+
const { data } = await api.ssoCallback(params);
|
|
725
|
+
if (data.success === true && data.requires_verification !== true) {
|
|
726
|
+
await checkAuth();
|
|
727
|
+
}
|
|
728
|
+
emitter.emit(AuthState.LOGIN);
|
|
729
|
+
return data;
|
|
730
|
+
},
|
|
731
|
+
linkSSOProvider: async (params) => {
|
|
732
|
+
await api.linkSSOProvider(params);
|
|
733
|
+
await checkAuth();
|
|
734
|
+
},
|
|
735
|
+
unlinkSSOProvider: async (provider) => {
|
|
736
|
+
await api.unlinkSSOProvider(provider);
|
|
737
|
+
await checkAuth();
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
setAuthContext(authMethods);
|
|
353
741
|
const user = vue.computed(() => accountToUser(accountInfo.value));
|
|
354
742
|
const getFullName = () => {
|
|
355
743
|
return user.value?.name ?? "";
|
|
@@ -488,11 +876,33 @@ function useAuth() {
|
|
|
488
876
|
}
|
|
489
877
|
await api.revokeAllSessions(id);
|
|
490
878
|
}
|
|
879
|
+
async function initiateSSO(params) {
|
|
880
|
+
const { data } = await api.initiateSSO(params);
|
|
881
|
+
return data.authorization_url;
|
|
882
|
+
}
|
|
883
|
+
async function loginWithSSO(params) {
|
|
884
|
+
const { data } = await api.ssoCallback(params);
|
|
885
|
+
if (data.success === true && data.requires_verification !== true) {
|
|
886
|
+
await checkAuth();
|
|
887
|
+
}
|
|
888
|
+
emitter.emit(AuthState.LOGIN);
|
|
889
|
+
return data;
|
|
890
|
+
}
|
|
891
|
+
async function linkSSOProvider(params) {
|
|
892
|
+
await api.linkSSOProvider(params);
|
|
893
|
+
await checkAuth();
|
|
894
|
+
}
|
|
895
|
+
async function unlinkSSOProvider(provider) {
|
|
896
|
+
await api.unlinkSSOProvider(provider);
|
|
897
|
+
await checkAuth();
|
|
898
|
+
}
|
|
491
899
|
return {
|
|
492
900
|
// Primary State (use this!)
|
|
493
901
|
user,
|
|
494
902
|
// Full account info (for advanced use cases)
|
|
495
903
|
accountInfo,
|
|
904
|
+
// SSO Providers (ready to use!)
|
|
905
|
+
sso,
|
|
496
906
|
// Getters
|
|
497
907
|
getFullName,
|
|
498
908
|
getIsLoggedIn,
|
|
@@ -507,6 +917,11 @@ function useAuth() {
|
|
|
507
917
|
signup,
|
|
508
918
|
checkAuth,
|
|
509
919
|
refreshSession,
|
|
920
|
+
// SSO Authentication (lower-level - prefer using sso.google.redirect())
|
|
921
|
+
initiateSSO,
|
|
922
|
+
loginWithSSO,
|
|
923
|
+
linkSSOProvider,
|
|
924
|
+
unlinkSSOProvider,
|
|
510
925
|
// Profile Actions
|
|
511
926
|
updateProfile,
|
|
512
927
|
deleteCurrentUser,
|
|
@@ -531,6 +946,18 @@ function useAuth() {
|
|
|
531
946
|
|
|
532
947
|
exports.AuthApi = AuthApi;
|
|
533
948
|
exports.AuthState = AuthState;
|
|
949
|
+
exports.PopupBlockedError = PopupBlockedError;
|
|
950
|
+
exports.PopupClosedError = PopupClosedError;
|
|
951
|
+
exports.PopupTimeoutError = PopupTimeoutError;
|
|
952
|
+
exports.SSOError = SSOError;
|
|
953
|
+
exports.StateMismatchError = StateMismatchError;
|
|
534
954
|
exports.accountToUser = accountToUser;
|
|
955
|
+
exports.getAllSSOProviders = getAllSSOProviders;
|
|
956
|
+
exports.getSSOProvider = getSSOProvider;
|
|
957
|
+
exports.handleOAuthCallback = handleOAuthCallback;
|
|
535
958
|
exports.initAuth = initAuth;
|
|
959
|
+
exports.isSupportedProvider = isSupportedProvider;
|
|
960
|
+
exports.setAuthContext = setAuthContext;
|
|
961
|
+
exports.sso = sso;
|
|
962
|
+
exports.ssoProviders = ssoProviders;
|
|
536
963
|
exports.useAuth = useAuth;
|