@bagelink/auth 1.7.74 → 1.7.78
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 +92 -14
- package/dist/index.cjs +152 -26
- package/dist/index.mjs +152 -26
- package/dist/router.d.ts +15 -1
- package/dist/types/redirect.d.ts +1 -1
- package/dist/useAuth.d.ts +46 -12
- package/package.json +1 -1
- package/src/pages/Callback.vue +12 -3
- package/src/router.ts +44 -1
- package/src/sso.ts +46 -0
- package/src/types/redirect.ts +2 -2
- package/src/useAuth.ts +72 -16
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ npm install @bagelink/auth
|
|
|
25
25
|
```typescript
|
|
26
26
|
// main.ts
|
|
27
27
|
import { createApp } from 'vue'
|
|
28
|
-
import { createAuth
|
|
28
|
+
import { createAuth } from '@bagelink/auth'
|
|
29
29
|
import router from './router'
|
|
30
30
|
|
|
31
31
|
const auth = createAuth({
|
|
@@ -37,11 +37,8 @@ const auth = createAuth({
|
|
|
37
37
|
}
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
// Connect router
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Install auth guard
|
|
44
|
-
router.beforeEach(authGuard())
|
|
40
|
+
// Connect router (automatically sets up auth guard)
|
|
41
|
+
auth.use(router)
|
|
45
42
|
|
|
46
43
|
const app = createApp(App)
|
|
47
44
|
app.use(router)
|
|
@@ -167,22 +164,78 @@ Returns auth composable with:
|
|
|
167
164
|
}
|
|
168
165
|
```
|
|
169
166
|
|
|
170
|
-
###
|
|
167
|
+
### Custom Guard Composition
|
|
168
|
+
|
|
169
|
+
For multi-tenant apps or advanced permission logic, disable the auto-guard and compose your own:
|
|
171
170
|
|
|
172
|
-
|
|
171
|
+
#### Option 1: Using `composeGuards` utility (recommended)
|
|
173
172
|
|
|
174
173
|
```typescript
|
|
175
|
-
|
|
174
|
+
import { composeGuards } from '@bagelink/auth'
|
|
175
|
+
|
|
176
|
+
// Disable auto-guard
|
|
177
|
+
auth.use(router, { guard: false })
|
|
178
|
+
|
|
179
|
+
// Custom org access guard
|
|
180
|
+
const orgAccessGuard = () => async (to, from, next) => {
|
|
181
|
+
if (to.meta.requiresOrg) {
|
|
182
|
+
const hasAccess = await checkOrgAccess(to.params.orgId)
|
|
183
|
+
if (!hasAccess) return next('/no-access')
|
|
184
|
+
}
|
|
185
|
+
next()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Compose all guards in sequence
|
|
189
|
+
router.beforeEach(composeGuards([
|
|
190
|
+
auth.routerGuard(), // Auth first
|
|
191
|
+
orgAccessGuard(), // Then org check
|
|
192
|
+
]))
|
|
176
193
|
```
|
|
177
194
|
|
|
178
|
-
|
|
195
|
+
#### Option 2: Multiple `beforeEach` calls
|
|
179
196
|
|
|
180
|
-
|
|
197
|
+
```typescript
|
|
198
|
+
auth.use(router, { guard: false })
|
|
181
199
|
|
|
182
|
-
|
|
200
|
+
// Guards run in order they're registered
|
|
201
|
+
router.beforeEach(auth.routerGuard())
|
|
202
|
+
|
|
203
|
+
router.beforeEach(async (to, from, next) => {
|
|
204
|
+
// Custom org/tenant check
|
|
205
|
+
if (to.meta.requiresOrg && !await checkOrgAccess(to.params.orgId)) {
|
|
206
|
+
return next('/no-access')
|
|
207
|
+
}
|
|
208
|
+
next()
|
|
209
|
+
})
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### Option 3: Manual composition
|
|
183
213
|
|
|
184
214
|
```typescript
|
|
185
|
-
|
|
215
|
+
auth.use(router, { guard: false })
|
|
216
|
+
|
|
217
|
+
router.beforeEach(async (to, from, next) => {
|
|
218
|
+
// Run auth guard first
|
|
219
|
+
const authPassed = await new Promise<boolean>((resolve) => {
|
|
220
|
+
auth.routerGuard()(to, from, (result?: any) => {
|
|
221
|
+
if (result !== undefined) {
|
|
222
|
+
next(result)
|
|
223
|
+
resolve(false)
|
|
224
|
+
} else {
|
|
225
|
+
resolve(true)
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
if (!authPassed) return
|
|
231
|
+
|
|
232
|
+
// Your custom logic
|
|
233
|
+
if (to.meta.requiresOrg && !await checkOrgAccess(to.params.orgId)) {
|
|
234
|
+
return next('/no-access')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
next()
|
|
238
|
+
})
|
|
186
239
|
```
|
|
187
240
|
|
|
188
241
|
## Components
|
|
@@ -241,7 +294,7 @@ await sso.google.redirect({
|
|
|
241
294
|
// router.ts
|
|
242
295
|
{
|
|
243
296
|
path: '/auth/callback',
|
|
244
|
-
name: '
|
|
297
|
+
name: 'AuthCallback',
|
|
245
298
|
component: () => import('@bagelink/auth').then(m => m.Callback),
|
|
246
299
|
}
|
|
247
300
|
```
|
|
@@ -255,6 +308,31 @@ await sso.google.redirect({
|
|
|
255
308
|
- Okta
|
|
256
309
|
- Facebook
|
|
257
310
|
|
|
311
|
+
### SSO Redirect Preservation
|
|
312
|
+
|
|
313
|
+
When users access a protected route and authenticate via SSO, the original URL is automatically preserved:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// User tries to access /dashboard (protected)
|
|
317
|
+
// ↓ Redirected to /login?redirect=/dashboard
|
|
318
|
+
// ↓ User clicks "Login with Google"
|
|
319
|
+
// ↓ Goes to Google OAuth
|
|
320
|
+
// ↓ Returns to /auth/callback
|
|
321
|
+
// ↓ Automatically redirected to /dashboard ✅
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**How it works:**
|
|
325
|
+
1. The `redirect` query param is stored in `sessionStorage` before SSO redirect
|
|
326
|
+
2. After OAuth callback, it's restored to the URL
|
|
327
|
+
3. Auto-redirect (or manual fallback) uses it to navigate to the original destination
|
|
328
|
+
|
|
329
|
+
**Manual SSO with redirect:**
|
|
330
|
+
```typescript
|
|
331
|
+
// On login page with ?redirect=/dashboard
|
|
332
|
+
await sso.google.redirect()
|
|
333
|
+
// Redirect param is automatically preserved across the OAuth flow
|
|
334
|
+
```
|
|
335
|
+
|
|
258
336
|
## Advanced Configuration
|
|
259
337
|
|
|
260
338
|
### Security: Restrict Redirect Paths
|
package/dist/index.cjs
CHANGED
|
@@ -1079,17 +1079,21 @@ const _sfc_main$4 = /* @__PURE__ */ vue.defineComponent({
|
|
|
1079
1079
|
const authResponse = vue.ref(null);
|
|
1080
1080
|
const { sso: sso2, user, accountInfo: accountInfo2 } = useAuth();
|
|
1081
1081
|
const route = vueRouter.useRoute();
|
|
1082
|
-
const
|
|
1082
|
+
const router2 = vueRouter.useRouter();
|
|
1083
1083
|
const providerInfo = vue.computed(() => {
|
|
1084
1084
|
if (provider.value === null) return null;
|
|
1085
1085
|
return providers[provider.value];
|
|
1086
1086
|
});
|
|
1087
1087
|
async function linkCallback() {
|
|
1088
1088
|
isLinking.value = true;
|
|
1089
|
+
const { redirect: redirect2 } = route.query;
|
|
1089
1090
|
try {
|
|
1090
1091
|
await sso2.handleLinkCallback();
|
|
1091
1092
|
success.value = true;
|
|
1092
|
-
setTimeout(() =>
|
|
1093
|
+
setTimeout(() => {
|
|
1094
|
+
const redirectPath = typeof redirect2 === "string" ? redirect2 : "/";
|
|
1095
|
+
router2.push(redirectPath);
|
|
1096
|
+
}, timeout);
|
|
1093
1097
|
} catch (err) {
|
|
1094
1098
|
const errorMessage = err instanceof Error ? err.message : "Failed to link account";
|
|
1095
1099
|
error.value = errorMessage;
|
|
@@ -1099,7 +1103,7 @@ const _sfc_main$4 = /* @__PURE__ */ vue.defineComponent({
|
|
|
1099
1103
|
}
|
|
1100
1104
|
async function handleCallback() {
|
|
1101
1105
|
var _a;
|
|
1102
|
-
const { state } = route.query;
|
|
1106
|
+
const { state, redirect: redirect2 } = route.query;
|
|
1103
1107
|
provider.value = sessionStorage.getItem(`oauth_provider:${state}`);
|
|
1104
1108
|
try {
|
|
1105
1109
|
const response = await sso2.handleCallback();
|
|
@@ -1108,7 +1112,10 @@ const _sfc_main$4 = /* @__PURE__ */ vue.defineComponent({
|
|
|
1108
1112
|
} else {
|
|
1109
1113
|
authResponse.value = response;
|
|
1110
1114
|
success.value = true;
|
|
1111
|
-
setTimeout(() =>
|
|
1115
|
+
setTimeout(() => {
|
|
1116
|
+
const redirectPath = typeof redirect2 === "string" ? redirect2 : "/";
|
|
1117
|
+
router2.push(redirectPath);
|
|
1118
|
+
}, timeout);
|
|
1112
1119
|
}
|
|
1113
1120
|
} catch (err) {
|
|
1114
1121
|
const errorMessage = err instanceof Error ? err.message : "Authentication failed";
|
|
@@ -1239,10 +1246,10 @@ const _sfc_main$3 = /* @__PURE__ */ vue.defineComponent({
|
|
|
1239
1246
|
cardShadow: { type: Boolean, default: true }
|
|
1240
1247
|
},
|
|
1241
1248
|
setup(__props) {
|
|
1242
|
-
const
|
|
1249
|
+
const router2 = vueRouter.useRouter();
|
|
1243
1250
|
function switchForm(form) {
|
|
1244
1251
|
if (form === "login") {
|
|
1245
|
-
|
|
1252
|
+
router2.push("/login");
|
|
1246
1253
|
}
|
|
1247
1254
|
}
|
|
1248
1255
|
return (_ctx, _cache) => {
|
|
@@ -1283,12 +1290,12 @@ const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
|
|
|
1283
1290
|
cardShadow: { type: Boolean, default: true }
|
|
1284
1291
|
},
|
|
1285
1292
|
setup(__props) {
|
|
1286
|
-
const
|
|
1293
|
+
const router2 = vueRouter.useRouter();
|
|
1287
1294
|
function switchForm(form) {
|
|
1288
1295
|
if (form === "signup") {
|
|
1289
|
-
|
|
1296
|
+
router2.push("/signup");
|
|
1290
1297
|
} else if (form === "forgot-password") {
|
|
1291
|
-
|
|
1298
|
+
router2.push("/forgot-password");
|
|
1292
1299
|
}
|
|
1293
1300
|
}
|
|
1294
1301
|
return (_ctx, _cache) => {
|
|
@@ -1329,12 +1336,12 @@ const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
|
|
|
1329
1336
|
cardShadow: { type: Boolean, default: true }
|
|
1330
1337
|
},
|
|
1331
1338
|
setup(__props) {
|
|
1332
|
-
const
|
|
1339
|
+
const router2 = vueRouter.useRouter();
|
|
1333
1340
|
const route = vueRouter.useRoute();
|
|
1334
1341
|
const token = vue.computed(() => route.query.token);
|
|
1335
1342
|
function switchForm(form) {
|
|
1336
1343
|
if (form === "login") {
|
|
1337
|
-
|
|
1344
|
+
router2.push("/login");
|
|
1338
1345
|
}
|
|
1339
1346
|
}
|
|
1340
1347
|
return (_ctx, _cache) => {
|
|
@@ -1366,10 +1373,10 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
|
|
|
1366
1373
|
cardShadow: { type: Boolean, default: true }
|
|
1367
1374
|
},
|
|
1368
1375
|
setup(__props) {
|
|
1369
|
-
const
|
|
1376
|
+
const router2 = vueRouter.useRouter();
|
|
1370
1377
|
function switchForm(form) {
|
|
1371
1378
|
if (form === "login") {
|
|
1372
|
-
|
|
1379
|
+
router2.push("/login");
|
|
1373
1380
|
}
|
|
1374
1381
|
}
|
|
1375
1382
|
return (_ctx, _cache) => {
|
|
@@ -1391,8 +1398,8 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
|
|
|
1391
1398
|
};
|
|
1392
1399
|
}
|
|
1393
1400
|
});
|
|
1394
|
-
function getRedirectUrl(
|
|
1395
|
-
const redirect2 =
|
|
1401
|
+
function getRedirectUrl(router2, config) {
|
|
1402
|
+
const redirect2 = router2.currentRoute.value.query[config.queryKey];
|
|
1396
1403
|
return redirect2 || config.fallback;
|
|
1397
1404
|
}
|
|
1398
1405
|
function isValidRedirect(redirectUrl, allowedPaths) {
|
|
@@ -1416,14 +1423,14 @@ function isValidRedirect(redirectUrl, allowedPaths) {
|
|
|
1416
1423
|
}
|
|
1417
1424
|
return true;
|
|
1418
1425
|
}
|
|
1419
|
-
async function performRedirect(
|
|
1420
|
-
const redirect2 = getRedirectUrl(
|
|
1426
|
+
async function performRedirect(router2, config) {
|
|
1427
|
+
const redirect2 = getRedirectUrl(router2, config);
|
|
1421
1428
|
if (redirect2 !== config.fallback && !isValidRedirect(redirect2, config.allowedPaths)) {
|
|
1422
1429
|
console.warn("[Auth] Invalid redirect URL detected, using fallback:", redirect2);
|
|
1423
|
-
await
|
|
1430
|
+
await router2.push(config.fallback);
|
|
1424
1431
|
return;
|
|
1425
1432
|
}
|
|
1426
|
-
await
|
|
1433
|
+
await router2.push(redirect2);
|
|
1427
1434
|
}
|
|
1428
1435
|
function buildLoginQuery(currentPath, config) {
|
|
1429
1436
|
if (!config.preserveRedirect) {
|
|
@@ -1584,6 +1591,13 @@ function createSSOProvider(config) {
|
|
|
1584
1591
|
const auth = getAuthApi();
|
|
1585
1592
|
const redirectUri = options.redirectUri ?? getDefaultRedirectUri();
|
|
1586
1593
|
const state = options.state ?? generateState();
|
|
1594
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1595
|
+
const currentParams = queryParams();
|
|
1596
|
+
const redirectUrl = currentParams.redirect;
|
|
1597
|
+
if (redirectUrl) {
|
|
1598
|
+
sessionStorage.setItem(`oauth_redirect:${state}`, redirectUrl);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1587
1601
|
if (typeof sessionStorage !== "undefined") {
|
|
1588
1602
|
sessionStorage.setItem(getStateKey(), state);
|
|
1589
1603
|
sessionStorage.setItem(`oauth_provider:${state}`, config.id);
|
|
@@ -1602,6 +1616,13 @@ function createSSOProvider(config) {
|
|
|
1602
1616
|
const redirectUri = options.redirectUri ?? getDefaultRedirectUri();
|
|
1603
1617
|
const state = options.state ?? generateState();
|
|
1604
1618
|
const timeout2 = options.popupTimeout ?? 9e4;
|
|
1619
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1620
|
+
const currentParams = queryParams();
|
|
1621
|
+
const redirectUrl = currentParams.redirect;
|
|
1622
|
+
if (redirectUrl) {
|
|
1623
|
+
sessionStorage.setItem(`oauth_redirect:${state}`, redirectUrl);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1605
1626
|
if (typeof sessionStorage !== "undefined") {
|
|
1606
1627
|
sessionStorage.setItem(getStateKey(), state);
|
|
1607
1628
|
sessionStorage.setItem(`oauth_provider:${state}`, config.id);
|
|
@@ -1818,6 +1839,15 @@ function handleOAuthCallback() {
|
|
|
1818
1839
|
if (!provider || !isSupportedProvider(provider)) {
|
|
1819
1840
|
throw new Error("Unable to determine OAuth provider. State may have expired.");
|
|
1820
1841
|
}
|
|
1842
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1843
|
+
const storedRedirect = sessionStorage.getItem(`oauth_redirect:${state}`);
|
|
1844
|
+
if (storedRedirect) {
|
|
1845
|
+
const url = new URL(window.location.href);
|
|
1846
|
+
url.searchParams.set("redirect", storedRedirect);
|
|
1847
|
+
window.history.replaceState({}, "", url.toString());
|
|
1848
|
+
sessionStorage.removeItem(`oauth_redirect:${state}`);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1821
1851
|
return ssoProviders[provider].callback(code, state);
|
|
1822
1852
|
}
|
|
1823
1853
|
function handleOAuthLinkCallback() {
|
|
@@ -1829,6 +1859,15 @@ function handleOAuthLinkCallback() {
|
|
|
1829
1859
|
if (!provider || !isSupportedProvider(provider)) {
|
|
1830
1860
|
throw new Error("Unable to determine OAuth provider. State may have expired.");
|
|
1831
1861
|
}
|
|
1862
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1863
|
+
const storedRedirect = sessionStorage.getItem(`oauth_redirect:${state}`);
|
|
1864
|
+
if (storedRedirect) {
|
|
1865
|
+
const url = new URL(window.location.href);
|
|
1866
|
+
url.searchParams.set("redirect", storedRedirect);
|
|
1867
|
+
window.history.replaceState({}, "", url.toString());
|
|
1868
|
+
sessionStorage.removeItem(`oauth_redirect:${state}`);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1832
1871
|
return ssoProviders[provider].link(code, state);
|
|
1833
1872
|
}
|
|
1834
1873
|
var AuthState = /* @__PURE__ */ ((AuthState2) => {
|
|
@@ -1891,7 +1930,7 @@ function accountToUser(account) {
|
|
|
1891
1930
|
const DEFAULT_REDIRECT_CONFIG = {
|
|
1892
1931
|
queryKey: "redirect",
|
|
1893
1932
|
fallback: "/",
|
|
1894
|
-
noAuthRoutes: ["Login", "Signup", "ForgotPassword", "ResetPassword", "
|
|
1933
|
+
noAuthRoutes: ["Login", "Signup", "ForgotPassword", "ResetPassword", "AuthCallback"],
|
|
1895
1934
|
authenticatedRedirect: "/",
|
|
1896
1935
|
loginRoute: "Login",
|
|
1897
1936
|
authMetaKey: "auth",
|
|
@@ -1908,10 +1947,8 @@ let authApi = null;
|
|
|
1908
1947
|
let eventEmitter = null;
|
|
1909
1948
|
let redirectConfig = null;
|
|
1910
1949
|
let autoRedirectRouter = null;
|
|
1950
|
+
let cachedAuthGuard = null;
|
|
1911
1951
|
const accountInfo = vue.ref(null);
|
|
1912
|
-
function setAuthRouter(router) {
|
|
1913
|
-
autoRedirectRouter = router;
|
|
1914
|
-
}
|
|
1915
1952
|
function getRedirectConfig() {
|
|
1916
1953
|
if (!redirectConfig) {
|
|
1917
1954
|
throw new Error("Redirect config not initialized. Did you call createAuth with redirect config?");
|
|
@@ -1948,6 +1985,69 @@ function createAuth(params) {
|
|
|
1948
1985
|
eventEmitter.removeAllListeners(event);
|
|
1949
1986
|
}
|
|
1950
1987
|
},
|
|
1988
|
+
/**
|
|
1989
|
+
* Connect external dependencies like Vue Router
|
|
1990
|
+
* Automatically sets up router guard when router is provided
|
|
1991
|
+
* @param dependency - Vue Router instance or other plugins
|
|
1992
|
+
* @param options - Configuration options
|
|
1993
|
+
* @param options.guard - Whether to automatically set up auth guard (default: true)
|
|
1994
|
+
* @example
|
|
1995
|
+
* ```ts
|
|
1996
|
+
* // Auto setup (default)
|
|
1997
|
+
* auth.use(router)
|
|
1998
|
+
*
|
|
1999
|
+
* // Manual guard control (for custom composition)
|
|
2000
|
+
* auth.use(router, { guard: false })
|
|
2001
|
+
* router.beforeEach(async (to, from, next) => {
|
|
2002
|
+
* // Custom logic first
|
|
2003
|
+
* if (!hasOrgAccess(to)) return next('/no-access')
|
|
2004
|
+
* // Then run auth guard
|
|
2005
|
+
* return auth.routerGuard()(to, from, next)
|
|
2006
|
+
* })
|
|
2007
|
+
* ```
|
|
2008
|
+
*/
|
|
2009
|
+
use(dependency, options = {}) {
|
|
2010
|
+
const { guard = true } = options;
|
|
2011
|
+
if (dependency && (dependency.beforeEach || dependency.push || dependency.currentRoute)) {
|
|
2012
|
+
autoRedirectRouter = dependency;
|
|
2013
|
+
if (guard) {
|
|
2014
|
+
dependency.beforeEach(authInstance.routerGuard());
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
return authInstance;
|
|
2018
|
+
},
|
|
2019
|
+
/**
|
|
2020
|
+
* Create a Vue Router navigation guard for authentication
|
|
2021
|
+
* Protects routes requiring authentication and handles redirect logic
|
|
2022
|
+
* Note: Automatically called by auth.use(router), only use directly for custom setups
|
|
2023
|
+
* @example
|
|
2024
|
+
* ```ts
|
|
2025
|
+
* // Automatic (recommended)
|
|
2026
|
+
* auth.use(router)
|
|
2027
|
+
*
|
|
2028
|
+
* // Manual (for custom setups)
|
|
2029
|
+
* router.beforeEach(auth.routerGuard())
|
|
2030
|
+
* ```
|
|
2031
|
+
*/
|
|
2032
|
+
routerGuard() {
|
|
2033
|
+
if (cachedAuthGuard === null) {
|
|
2034
|
+
cachedAuthGuard = async (to, from, next) => {
|
|
2035
|
+
const { authGuard: authGuard2 } = await Promise.resolve().then(() => router);
|
|
2036
|
+
const guard = authGuard2();
|
|
2037
|
+
cachedAuthGuard = guard;
|
|
2038
|
+
return guard(to, from, next);
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
return cachedAuthGuard;
|
|
2042
|
+
},
|
|
2043
|
+
/**
|
|
2044
|
+
* Vue plugin install method
|
|
2045
|
+
* Makes auth available globally as $auth
|
|
2046
|
+
* @example
|
|
2047
|
+
* ```ts
|
|
2048
|
+
* app.use(auth)
|
|
2049
|
+
* ```
|
|
2050
|
+
*/
|
|
1951
2051
|
install(app) {
|
|
1952
2052
|
app.config.globalProperties.$auth = useAuth();
|
|
1953
2053
|
}
|
|
@@ -1958,7 +2058,7 @@ function setupAutoRedirect() {
|
|
|
1958
2058
|
if (!eventEmitter || !redirectConfig) return;
|
|
1959
2059
|
eventEmitter.on(AuthState.LOGIN, async () => {
|
|
1960
2060
|
if (!autoRedirectRouter) {
|
|
1961
|
-
console.warn("[Auth] Auto-redirect enabled but router not set. Call
|
|
2061
|
+
console.warn("[Auth] Auto-redirect enabled but router not set. Call auth.use(router) in your app setup.");
|
|
1962
2062
|
return;
|
|
1963
2063
|
}
|
|
1964
2064
|
const { performRedirect: performRedirect2 } = await Promise.resolve().then(() => redirect);
|
|
@@ -2218,7 +2318,6 @@ const useAuth$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
|
|
|
2218
2318
|
__proto__: null,
|
|
2219
2319
|
createAuth,
|
|
2220
2320
|
getRedirectConfig,
|
|
2221
|
-
setAuthRouter,
|
|
2222
2321
|
useAuth
|
|
2223
2322
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
2224
2323
|
let authInitialized = false;
|
|
@@ -2256,6 +2355,33 @@ function authGuard() {
|
|
|
2256
2355
|
}
|
|
2257
2356
|
};
|
|
2258
2357
|
}
|
|
2358
|
+
function composeGuards(guards) {
|
|
2359
|
+
return async (to, from, next) => {
|
|
2360
|
+
let guardIndex = 0;
|
|
2361
|
+
const runNextGuard = async () => {
|
|
2362
|
+
if (guardIndex >= guards.length) {
|
|
2363
|
+
next();
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
const guard = guards[guardIndex];
|
|
2367
|
+
guardIndex++;
|
|
2368
|
+
await guard(to, from, (result) => {
|
|
2369
|
+
if (result !== void 0) {
|
|
2370
|
+
next(result);
|
|
2371
|
+
} else {
|
|
2372
|
+
runNextGuard();
|
|
2373
|
+
}
|
|
2374
|
+
});
|
|
2375
|
+
};
|
|
2376
|
+
await runNextGuard();
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
const router = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2380
|
+
__proto__: null,
|
|
2381
|
+
authGuard,
|
|
2382
|
+
composeGuards,
|
|
2383
|
+
resetAuthState
|
|
2384
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
2259
2385
|
function createAuthRoutes(config = {}) {
|
|
2260
2386
|
const {
|
|
2261
2387
|
basePath = "",
|
|
@@ -2342,6 +2468,7 @@ exports.StateMismatchError = StateMismatchError;
|
|
|
2342
2468
|
exports.accountToUser = accountToUser;
|
|
2343
2469
|
exports.authGuard = authGuard;
|
|
2344
2470
|
exports.buildLoginQuery = buildLoginQuery;
|
|
2471
|
+
exports.composeGuards = composeGuards;
|
|
2345
2472
|
exports.createAuth = createAuth;
|
|
2346
2473
|
exports.createAuthGuard = createAuthGuard;
|
|
2347
2474
|
exports.createAuthRoutes = createAuthRoutes;
|
|
@@ -2356,7 +2483,6 @@ exports.performRedirect = performRedirect;
|
|
|
2356
2483
|
exports.providers = providers;
|
|
2357
2484
|
exports.resetAuthState = resetAuthState;
|
|
2358
2485
|
exports.setAuthContext = setAuthContext;
|
|
2359
|
-
exports.setAuthRouter = setAuthRouter;
|
|
2360
2486
|
exports.sso = sso;
|
|
2361
2487
|
exports.ssoProvidersList = ssoProvidersList;
|
|
2362
2488
|
exports.useAuth = useAuth;
|
package/dist/index.mjs
CHANGED
|
@@ -1077,17 +1077,21 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
|
|
|
1077
1077
|
const authResponse = ref(null);
|
|
1078
1078
|
const { sso: sso2, user, accountInfo: accountInfo2 } = useAuth();
|
|
1079
1079
|
const route = useRoute();
|
|
1080
|
-
const
|
|
1080
|
+
const router2 = useRouter();
|
|
1081
1081
|
const providerInfo = computed(() => {
|
|
1082
1082
|
if (provider.value === null) return null;
|
|
1083
1083
|
return providers[provider.value];
|
|
1084
1084
|
});
|
|
1085
1085
|
async function linkCallback() {
|
|
1086
1086
|
isLinking.value = true;
|
|
1087
|
+
const { redirect: redirect2 } = route.query;
|
|
1087
1088
|
try {
|
|
1088
1089
|
await sso2.handleLinkCallback();
|
|
1089
1090
|
success.value = true;
|
|
1090
|
-
setTimeout(() =>
|
|
1091
|
+
setTimeout(() => {
|
|
1092
|
+
const redirectPath = typeof redirect2 === "string" ? redirect2 : "/";
|
|
1093
|
+
router2.push(redirectPath);
|
|
1094
|
+
}, timeout);
|
|
1091
1095
|
} catch (err) {
|
|
1092
1096
|
const errorMessage = err instanceof Error ? err.message : "Failed to link account";
|
|
1093
1097
|
error.value = errorMessage;
|
|
@@ -1097,7 +1101,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
|
|
|
1097
1101
|
}
|
|
1098
1102
|
async function handleCallback() {
|
|
1099
1103
|
var _a;
|
|
1100
|
-
const { state } = route.query;
|
|
1104
|
+
const { state, redirect: redirect2 } = route.query;
|
|
1101
1105
|
provider.value = sessionStorage.getItem(`oauth_provider:${state}`);
|
|
1102
1106
|
try {
|
|
1103
1107
|
const response = await sso2.handleCallback();
|
|
@@ -1106,7 +1110,10 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
|
|
|
1106
1110
|
} else {
|
|
1107
1111
|
authResponse.value = response;
|
|
1108
1112
|
success.value = true;
|
|
1109
|
-
setTimeout(() =>
|
|
1113
|
+
setTimeout(() => {
|
|
1114
|
+
const redirectPath = typeof redirect2 === "string" ? redirect2 : "/";
|
|
1115
|
+
router2.push(redirectPath);
|
|
1116
|
+
}, timeout);
|
|
1110
1117
|
}
|
|
1111
1118
|
} catch (err) {
|
|
1112
1119
|
const errorMessage = err instanceof Error ? err.message : "Authentication failed";
|
|
@@ -1237,10 +1244,10 @@ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
|
|
|
1237
1244
|
cardShadow: { type: Boolean, default: true }
|
|
1238
1245
|
},
|
|
1239
1246
|
setup(__props) {
|
|
1240
|
-
const
|
|
1247
|
+
const router2 = useRouter();
|
|
1241
1248
|
function switchForm(form) {
|
|
1242
1249
|
if (form === "login") {
|
|
1243
|
-
|
|
1250
|
+
router2.push("/login");
|
|
1244
1251
|
}
|
|
1245
1252
|
}
|
|
1246
1253
|
return (_ctx, _cache) => {
|
|
@@ -1281,12 +1288,12 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
|
|
1281
1288
|
cardShadow: { type: Boolean, default: true }
|
|
1282
1289
|
},
|
|
1283
1290
|
setup(__props) {
|
|
1284
|
-
const
|
|
1291
|
+
const router2 = useRouter();
|
|
1285
1292
|
function switchForm(form) {
|
|
1286
1293
|
if (form === "signup") {
|
|
1287
|
-
|
|
1294
|
+
router2.push("/signup");
|
|
1288
1295
|
} else if (form === "forgot-password") {
|
|
1289
|
-
|
|
1296
|
+
router2.push("/forgot-password");
|
|
1290
1297
|
}
|
|
1291
1298
|
}
|
|
1292
1299
|
return (_ctx, _cache) => {
|
|
@@ -1327,12 +1334,12 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
1327
1334
|
cardShadow: { type: Boolean, default: true }
|
|
1328
1335
|
},
|
|
1329
1336
|
setup(__props) {
|
|
1330
|
-
const
|
|
1337
|
+
const router2 = useRouter();
|
|
1331
1338
|
const route = useRoute();
|
|
1332
1339
|
const token = computed(() => route.query.token);
|
|
1333
1340
|
function switchForm(form) {
|
|
1334
1341
|
if (form === "login") {
|
|
1335
|
-
|
|
1342
|
+
router2.push("/login");
|
|
1336
1343
|
}
|
|
1337
1344
|
}
|
|
1338
1345
|
return (_ctx, _cache) => {
|
|
@@ -1364,10 +1371,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
1364
1371
|
cardShadow: { type: Boolean, default: true }
|
|
1365
1372
|
},
|
|
1366
1373
|
setup(__props) {
|
|
1367
|
-
const
|
|
1374
|
+
const router2 = useRouter();
|
|
1368
1375
|
function switchForm(form) {
|
|
1369
1376
|
if (form === "login") {
|
|
1370
|
-
|
|
1377
|
+
router2.push("/login");
|
|
1371
1378
|
}
|
|
1372
1379
|
}
|
|
1373
1380
|
return (_ctx, _cache) => {
|
|
@@ -1389,8 +1396,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
1389
1396
|
};
|
|
1390
1397
|
}
|
|
1391
1398
|
});
|
|
1392
|
-
function getRedirectUrl(
|
|
1393
|
-
const redirect2 =
|
|
1399
|
+
function getRedirectUrl(router2, config) {
|
|
1400
|
+
const redirect2 = router2.currentRoute.value.query[config.queryKey];
|
|
1394
1401
|
return redirect2 || config.fallback;
|
|
1395
1402
|
}
|
|
1396
1403
|
function isValidRedirect(redirectUrl, allowedPaths) {
|
|
@@ -1414,14 +1421,14 @@ function isValidRedirect(redirectUrl, allowedPaths) {
|
|
|
1414
1421
|
}
|
|
1415
1422
|
return true;
|
|
1416
1423
|
}
|
|
1417
|
-
async function performRedirect(
|
|
1418
|
-
const redirect2 = getRedirectUrl(
|
|
1424
|
+
async function performRedirect(router2, config) {
|
|
1425
|
+
const redirect2 = getRedirectUrl(router2, config);
|
|
1419
1426
|
if (redirect2 !== config.fallback && !isValidRedirect(redirect2, config.allowedPaths)) {
|
|
1420
1427
|
console.warn("[Auth] Invalid redirect URL detected, using fallback:", redirect2);
|
|
1421
|
-
await
|
|
1428
|
+
await router2.push(config.fallback);
|
|
1422
1429
|
return;
|
|
1423
1430
|
}
|
|
1424
|
-
await
|
|
1431
|
+
await router2.push(redirect2);
|
|
1425
1432
|
}
|
|
1426
1433
|
function buildLoginQuery(currentPath, config) {
|
|
1427
1434
|
if (!config.preserveRedirect) {
|
|
@@ -1582,6 +1589,13 @@ function createSSOProvider(config) {
|
|
|
1582
1589
|
const auth = getAuthApi();
|
|
1583
1590
|
const redirectUri = options.redirectUri ?? getDefaultRedirectUri();
|
|
1584
1591
|
const state = options.state ?? generateState();
|
|
1592
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1593
|
+
const currentParams = queryParams();
|
|
1594
|
+
const redirectUrl = currentParams.redirect;
|
|
1595
|
+
if (redirectUrl) {
|
|
1596
|
+
sessionStorage.setItem(`oauth_redirect:${state}`, redirectUrl);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1585
1599
|
if (typeof sessionStorage !== "undefined") {
|
|
1586
1600
|
sessionStorage.setItem(getStateKey(), state);
|
|
1587
1601
|
sessionStorage.setItem(`oauth_provider:${state}`, config.id);
|
|
@@ -1600,6 +1614,13 @@ function createSSOProvider(config) {
|
|
|
1600
1614
|
const redirectUri = options.redirectUri ?? getDefaultRedirectUri();
|
|
1601
1615
|
const state = options.state ?? generateState();
|
|
1602
1616
|
const timeout2 = options.popupTimeout ?? 9e4;
|
|
1617
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1618
|
+
const currentParams = queryParams();
|
|
1619
|
+
const redirectUrl = currentParams.redirect;
|
|
1620
|
+
if (redirectUrl) {
|
|
1621
|
+
sessionStorage.setItem(`oauth_redirect:${state}`, redirectUrl);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1603
1624
|
if (typeof sessionStorage !== "undefined") {
|
|
1604
1625
|
sessionStorage.setItem(getStateKey(), state);
|
|
1605
1626
|
sessionStorage.setItem(`oauth_provider:${state}`, config.id);
|
|
@@ -1816,6 +1837,15 @@ function handleOAuthCallback() {
|
|
|
1816
1837
|
if (!provider || !isSupportedProvider(provider)) {
|
|
1817
1838
|
throw new Error("Unable to determine OAuth provider. State may have expired.");
|
|
1818
1839
|
}
|
|
1840
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1841
|
+
const storedRedirect = sessionStorage.getItem(`oauth_redirect:${state}`);
|
|
1842
|
+
if (storedRedirect) {
|
|
1843
|
+
const url = new URL(window.location.href);
|
|
1844
|
+
url.searchParams.set("redirect", storedRedirect);
|
|
1845
|
+
window.history.replaceState({}, "", url.toString());
|
|
1846
|
+
sessionStorage.removeItem(`oauth_redirect:${state}`);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1819
1849
|
return ssoProviders[provider].callback(code, state);
|
|
1820
1850
|
}
|
|
1821
1851
|
function handleOAuthLinkCallback() {
|
|
@@ -1827,6 +1857,15 @@ function handleOAuthLinkCallback() {
|
|
|
1827
1857
|
if (!provider || !isSupportedProvider(provider)) {
|
|
1828
1858
|
throw new Error("Unable to determine OAuth provider. State may have expired.");
|
|
1829
1859
|
}
|
|
1860
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1861
|
+
const storedRedirect = sessionStorage.getItem(`oauth_redirect:${state}`);
|
|
1862
|
+
if (storedRedirect) {
|
|
1863
|
+
const url = new URL(window.location.href);
|
|
1864
|
+
url.searchParams.set("redirect", storedRedirect);
|
|
1865
|
+
window.history.replaceState({}, "", url.toString());
|
|
1866
|
+
sessionStorage.removeItem(`oauth_redirect:${state}`);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1830
1869
|
return ssoProviders[provider].link(code, state);
|
|
1831
1870
|
}
|
|
1832
1871
|
var AuthState = /* @__PURE__ */ ((AuthState2) => {
|
|
@@ -1889,7 +1928,7 @@ function accountToUser(account) {
|
|
|
1889
1928
|
const DEFAULT_REDIRECT_CONFIG = {
|
|
1890
1929
|
queryKey: "redirect",
|
|
1891
1930
|
fallback: "/",
|
|
1892
|
-
noAuthRoutes: ["Login", "Signup", "ForgotPassword", "ResetPassword", "
|
|
1931
|
+
noAuthRoutes: ["Login", "Signup", "ForgotPassword", "ResetPassword", "AuthCallback"],
|
|
1893
1932
|
authenticatedRedirect: "/",
|
|
1894
1933
|
loginRoute: "Login",
|
|
1895
1934
|
authMetaKey: "auth",
|
|
@@ -1906,10 +1945,8 @@ let authApi = null;
|
|
|
1906
1945
|
let eventEmitter = null;
|
|
1907
1946
|
let redirectConfig = null;
|
|
1908
1947
|
let autoRedirectRouter = null;
|
|
1948
|
+
let cachedAuthGuard = null;
|
|
1909
1949
|
const accountInfo = ref(null);
|
|
1910
|
-
function setAuthRouter(router) {
|
|
1911
|
-
autoRedirectRouter = router;
|
|
1912
|
-
}
|
|
1913
1950
|
function getRedirectConfig() {
|
|
1914
1951
|
if (!redirectConfig) {
|
|
1915
1952
|
throw new Error("Redirect config not initialized. Did you call createAuth with redirect config?");
|
|
@@ -1946,6 +1983,69 @@ function createAuth(params) {
|
|
|
1946
1983
|
eventEmitter.removeAllListeners(event);
|
|
1947
1984
|
}
|
|
1948
1985
|
},
|
|
1986
|
+
/**
|
|
1987
|
+
* Connect external dependencies like Vue Router
|
|
1988
|
+
* Automatically sets up router guard when router is provided
|
|
1989
|
+
* @param dependency - Vue Router instance or other plugins
|
|
1990
|
+
* @param options - Configuration options
|
|
1991
|
+
* @param options.guard - Whether to automatically set up auth guard (default: true)
|
|
1992
|
+
* @example
|
|
1993
|
+
* ```ts
|
|
1994
|
+
* // Auto setup (default)
|
|
1995
|
+
* auth.use(router)
|
|
1996
|
+
*
|
|
1997
|
+
* // Manual guard control (for custom composition)
|
|
1998
|
+
* auth.use(router, { guard: false })
|
|
1999
|
+
* router.beforeEach(async (to, from, next) => {
|
|
2000
|
+
* // Custom logic first
|
|
2001
|
+
* if (!hasOrgAccess(to)) return next('/no-access')
|
|
2002
|
+
* // Then run auth guard
|
|
2003
|
+
* return auth.routerGuard()(to, from, next)
|
|
2004
|
+
* })
|
|
2005
|
+
* ```
|
|
2006
|
+
*/
|
|
2007
|
+
use(dependency, options = {}) {
|
|
2008
|
+
const { guard = true } = options;
|
|
2009
|
+
if (dependency && (dependency.beforeEach || dependency.push || dependency.currentRoute)) {
|
|
2010
|
+
autoRedirectRouter = dependency;
|
|
2011
|
+
if (guard) {
|
|
2012
|
+
dependency.beforeEach(authInstance.routerGuard());
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
return authInstance;
|
|
2016
|
+
},
|
|
2017
|
+
/**
|
|
2018
|
+
* Create a Vue Router navigation guard for authentication
|
|
2019
|
+
* Protects routes requiring authentication and handles redirect logic
|
|
2020
|
+
* Note: Automatically called by auth.use(router), only use directly for custom setups
|
|
2021
|
+
* @example
|
|
2022
|
+
* ```ts
|
|
2023
|
+
* // Automatic (recommended)
|
|
2024
|
+
* auth.use(router)
|
|
2025
|
+
*
|
|
2026
|
+
* // Manual (for custom setups)
|
|
2027
|
+
* router.beforeEach(auth.routerGuard())
|
|
2028
|
+
* ```
|
|
2029
|
+
*/
|
|
2030
|
+
routerGuard() {
|
|
2031
|
+
if (cachedAuthGuard === null) {
|
|
2032
|
+
cachedAuthGuard = async (to, from, next) => {
|
|
2033
|
+
const { authGuard: authGuard2 } = await Promise.resolve().then(() => router);
|
|
2034
|
+
const guard = authGuard2();
|
|
2035
|
+
cachedAuthGuard = guard;
|
|
2036
|
+
return guard(to, from, next);
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
return cachedAuthGuard;
|
|
2040
|
+
},
|
|
2041
|
+
/**
|
|
2042
|
+
* Vue plugin install method
|
|
2043
|
+
* Makes auth available globally as $auth
|
|
2044
|
+
* @example
|
|
2045
|
+
* ```ts
|
|
2046
|
+
* app.use(auth)
|
|
2047
|
+
* ```
|
|
2048
|
+
*/
|
|
1949
2049
|
install(app) {
|
|
1950
2050
|
app.config.globalProperties.$auth = useAuth();
|
|
1951
2051
|
}
|
|
@@ -1956,7 +2056,7 @@ function setupAutoRedirect() {
|
|
|
1956
2056
|
if (!eventEmitter || !redirectConfig) return;
|
|
1957
2057
|
eventEmitter.on(AuthState.LOGIN, async () => {
|
|
1958
2058
|
if (!autoRedirectRouter) {
|
|
1959
|
-
console.warn("[Auth] Auto-redirect enabled but router not set. Call
|
|
2059
|
+
console.warn("[Auth] Auto-redirect enabled but router not set. Call auth.use(router) in your app setup.");
|
|
1960
2060
|
return;
|
|
1961
2061
|
}
|
|
1962
2062
|
const { performRedirect: performRedirect2 } = await Promise.resolve().then(() => redirect);
|
|
@@ -2216,7 +2316,6 @@ const useAuth$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
|
|
|
2216
2316
|
__proto__: null,
|
|
2217
2317
|
createAuth,
|
|
2218
2318
|
getRedirectConfig,
|
|
2219
|
-
setAuthRouter,
|
|
2220
2319
|
useAuth
|
|
2221
2320
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
2222
2321
|
let authInitialized = false;
|
|
@@ -2254,6 +2353,33 @@ function authGuard() {
|
|
|
2254
2353
|
}
|
|
2255
2354
|
};
|
|
2256
2355
|
}
|
|
2356
|
+
function composeGuards(guards) {
|
|
2357
|
+
return async (to, from, next) => {
|
|
2358
|
+
let guardIndex = 0;
|
|
2359
|
+
const runNextGuard = async () => {
|
|
2360
|
+
if (guardIndex >= guards.length) {
|
|
2361
|
+
next();
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
const guard = guards[guardIndex];
|
|
2365
|
+
guardIndex++;
|
|
2366
|
+
await guard(to, from, (result) => {
|
|
2367
|
+
if (result !== void 0) {
|
|
2368
|
+
next(result);
|
|
2369
|
+
} else {
|
|
2370
|
+
runNextGuard();
|
|
2371
|
+
}
|
|
2372
|
+
});
|
|
2373
|
+
};
|
|
2374
|
+
await runNextGuard();
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
const router = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2378
|
+
__proto__: null,
|
|
2379
|
+
authGuard,
|
|
2380
|
+
composeGuards,
|
|
2381
|
+
resetAuthState
|
|
2382
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
2257
2383
|
function createAuthRoutes(config = {}) {
|
|
2258
2384
|
const {
|
|
2259
2385
|
basePath = "",
|
|
@@ -2341,6 +2467,7 @@ export {
|
|
|
2341
2467
|
accountToUser,
|
|
2342
2468
|
authGuard,
|
|
2343
2469
|
buildLoginQuery,
|
|
2470
|
+
composeGuards,
|
|
2344
2471
|
createAuth,
|
|
2345
2472
|
createAuthGuard,
|
|
2346
2473
|
createAuthRoutes,
|
|
@@ -2355,7 +2482,6 @@ export {
|
|
|
2355
2482
|
providers,
|
|
2356
2483
|
resetAuthState,
|
|
2357
2484
|
setAuthContext,
|
|
2358
|
-
setAuthRouter,
|
|
2359
2485
|
sso,
|
|
2360
2486
|
ssoProvidersList,
|
|
2361
2487
|
useAuth
|
package/dist/router.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NavigationGuardNext, RouteLocationNormalized } from 'vue-router';
|
|
1
|
+
import { NavigationGuard, NavigationGuardNext, RouteLocationNormalized } from 'vue-router';
|
|
2
2
|
/**
|
|
3
3
|
* Reset auth initialization state
|
|
4
4
|
* Useful for testing or app reload scenarios
|
|
@@ -20,3 +20,17 @@ export declare function resetAuthState(): void;
|
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
22
|
export declare function authGuard(): (to: RouteLocationNormalized, _from: RouteLocationNormalized, next: NavigationGuardNext) => Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Compose multiple navigation guards into one
|
|
25
|
+
* Guards are executed in order, stopping at the first one that calls next with a value
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* router.beforeEach(composeGuards([
|
|
30
|
+
* authGuard(),
|
|
31
|
+
* orgAccessGuard(),
|
|
32
|
+
* featureFlagGuard(),
|
|
33
|
+
* ]))
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function composeGuards(guards: NavigationGuard[]): NavigationGuard;
|
package/dist/types/redirect.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface RedirectConfig {
|
|
|
16
16
|
/**
|
|
17
17
|
* Routes that require NO authentication (login, signup, forgot password, etc)
|
|
18
18
|
* Authenticated users will be automatically redirected away from these pages
|
|
19
|
-
* @default ['Login', 'Signup', 'ForgotPassword', 'ResetPassword', '
|
|
19
|
+
* @default ['Login', 'Signup', 'ForgotPassword', 'ResetPassword', 'AuthCallback']
|
|
20
20
|
*/
|
|
21
21
|
noAuthRoutes?: string[];
|
|
22
22
|
/**
|
package/dist/useAuth.d.ts
CHANGED
|
@@ -9,18 +9,6 @@ interface InitParams {
|
|
|
9
9
|
*/
|
|
10
10
|
redirect?: RedirectConfig;
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Set the router instance for auto-redirect functionality
|
|
14
|
-
* Call this in your app setup after creating the router
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```ts
|
|
18
|
-
* const auth = createAuth({ ... })
|
|
19
|
-
* const router = createRouter({ ... })
|
|
20
|
-
* setAuthRouter(router)
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
export declare function setAuthRouter(router: any): void;
|
|
24
12
|
/**
|
|
25
13
|
* Get the current redirect configuration
|
|
26
14
|
* Used internally by router guard
|
|
@@ -30,6 +18,52 @@ export declare function createAuth(params: InitParams): {
|
|
|
30
18
|
on<K extends AuthState>(event: K, handler: AuthEventMap[K]): void;
|
|
31
19
|
off<K extends AuthState>(event: K, handler: AuthEventMap[K]): void;
|
|
32
20
|
removeAllListeners<K extends AuthState>(event?: K): void;
|
|
21
|
+
/**
|
|
22
|
+
* Connect external dependencies like Vue Router
|
|
23
|
+
* Automatically sets up router guard when router is provided
|
|
24
|
+
* @param dependency - Vue Router instance or other plugins
|
|
25
|
+
* @param options - Configuration options
|
|
26
|
+
* @param options.guard - Whether to automatically set up auth guard (default: true)
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* // Auto setup (default)
|
|
30
|
+
* auth.use(router)
|
|
31
|
+
*
|
|
32
|
+
* // Manual guard control (for custom composition)
|
|
33
|
+
* auth.use(router, { guard: false })
|
|
34
|
+
* router.beforeEach(async (to, from, next) => {
|
|
35
|
+
* // Custom logic first
|
|
36
|
+
* if (!hasOrgAccess(to)) return next('/no-access')
|
|
37
|
+
* // Then run auth guard
|
|
38
|
+
* return auth.routerGuard()(to, from, next)
|
|
39
|
+
* })
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
use(dependency: any, options?: {
|
|
43
|
+
guard?: boolean;
|
|
44
|
+
}): /*elided*/ any;
|
|
45
|
+
/**
|
|
46
|
+
* Create a Vue Router navigation guard for authentication
|
|
47
|
+
* Protects routes requiring authentication and handles redirect logic
|
|
48
|
+
* Note: Automatically called by auth.use(router), only use directly for custom setups
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* // Automatic (recommended)
|
|
52
|
+
* auth.use(router)
|
|
53
|
+
*
|
|
54
|
+
* // Manual (for custom setups)
|
|
55
|
+
* router.beforeEach(auth.routerGuard())
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
routerGuard(): any;
|
|
59
|
+
/**
|
|
60
|
+
* Vue plugin install method
|
|
61
|
+
* Makes auth available globally as $auth
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* app.use(auth)
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
33
67
|
install(app: App): void;
|
|
34
68
|
};
|
|
35
69
|
export declare function useAuth(): {
|
package/package.json
CHANGED
package/src/pages/Callback.vue
CHANGED
|
@@ -26,10 +26,14 @@ const providerInfo = computed(() => {
|
|
|
26
26
|
|
|
27
27
|
async function linkCallback() {
|
|
28
28
|
isLinking.value = true
|
|
29
|
+
const { redirect } = route.query
|
|
29
30
|
try {
|
|
30
31
|
await sso.handleLinkCallback()
|
|
31
32
|
success.value = true
|
|
32
|
-
setTimeout(() =>
|
|
33
|
+
setTimeout(() => {
|
|
34
|
+
const redirectPath = typeof redirect === 'string' ? redirect : '/'
|
|
35
|
+
router.push(redirectPath)
|
|
36
|
+
}, timeout)
|
|
33
37
|
} catch (err: unknown) {
|
|
34
38
|
const errorMessage = err instanceof Error ? err.message : 'Failed to link account'
|
|
35
39
|
error.value = errorMessage
|
|
@@ -39,7 +43,7 @@ async function linkCallback() {
|
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
async function handleCallback() {
|
|
42
|
-
const { state } = route.query
|
|
46
|
+
const { state, redirect } = route.query
|
|
43
47
|
provider.value = sessionStorage.getItem(`oauth_provider:${state}`) as SSOProvider
|
|
44
48
|
|
|
45
49
|
try {
|
|
@@ -49,7 +53,12 @@ async function handleCallback() {
|
|
|
49
53
|
} else {
|
|
50
54
|
authResponse.value = response
|
|
51
55
|
success.value = true
|
|
52
|
-
|
|
56
|
+
// Auto-redirect will handle navigation, but fallback to manual redirect
|
|
57
|
+
// if auto-redirect is disabled or router not connected
|
|
58
|
+
setTimeout(() => {
|
|
59
|
+
const redirectPath = typeof redirect === 'string' ? redirect : '/'
|
|
60
|
+
router.push(redirectPath)
|
|
61
|
+
}, timeout)
|
|
53
62
|
}
|
|
54
63
|
} catch (err: unknown) {
|
|
55
64
|
const errorMessage = err instanceof Error ? err.message : 'Authentication failed'
|
package/src/router.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
|
|
1
|
+
import type { NavigationGuard, NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
|
|
2
2
|
import { buildLoginQuery } from './redirect'
|
|
3
3
|
import { useAuth, getRedirectConfig } from './useAuth'
|
|
4
4
|
|
|
@@ -84,3 +84,46 @@ export function authGuard() {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Compose multiple navigation guards into one
|
|
90
|
+
* Guards are executed in order, stopping at the first one that calls next with a value
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* router.beforeEach(composeGuards([
|
|
95
|
+
* authGuard(),
|
|
96
|
+
* orgAccessGuard(),
|
|
97
|
+
* featureFlagGuard(),
|
|
98
|
+
* ]))
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export function composeGuards(guards: NavigationGuard[]): NavigationGuard {
|
|
102
|
+
return async (to, from, next) => {
|
|
103
|
+
let guardIndex = 0
|
|
104
|
+
|
|
105
|
+
const runNextGuard = async (): Promise<void> => {
|
|
106
|
+
if (guardIndex >= guards.length) {
|
|
107
|
+
// All guards passed, allow navigation
|
|
108
|
+
next()
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const guard = guards[guardIndex]
|
|
113
|
+
guardIndex++
|
|
114
|
+
|
|
115
|
+
// Run the current guard
|
|
116
|
+
await guard(to, from, (result?: any) => {
|
|
117
|
+
if (result !== undefined) {
|
|
118
|
+
// Guard blocked or redirected, stop here
|
|
119
|
+
next(result)
|
|
120
|
+
} else {
|
|
121
|
+
// Guard passed, run next guard
|
|
122
|
+
runNextGuard()
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await runNextGuard()
|
|
128
|
+
}
|
|
129
|
+
}
|
package/src/sso.ts
CHANGED
|
@@ -314,6 +314,16 @@ function createSSOProvider(config: SSOProviderConfig): SSOProviderInstance {
|
|
|
314
314
|
const redirectUri = options.redirectUri ?? getDefaultRedirectUri()
|
|
315
315
|
const state = options.state ?? generateState()
|
|
316
316
|
|
|
317
|
+
// Preserve redirect URL from current location
|
|
318
|
+
if (typeof window !== 'undefined' && typeof sessionStorage !== 'undefined') {
|
|
319
|
+
const currentParams = queryParams()
|
|
320
|
+
const redirectUrl = currentParams.redirect
|
|
321
|
+
if (redirectUrl) {
|
|
322
|
+
// Store redirect URL to restore after OAuth callback
|
|
323
|
+
sessionStorage.setItem(`oauth_redirect:${state}`, redirectUrl)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
317
327
|
// Store state AND provider in sessionStorage for verification
|
|
318
328
|
if (typeof sessionStorage !== 'undefined') {
|
|
319
329
|
sessionStorage.setItem(getStateKey(), state)
|
|
@@ -338,6 +348,16 @@ function createSSOProvider(config: SSOProviderConfig): SSOProviderInstance {
|
|
|
338
348
|
const state = options.state ?? generateState()
|
|
339
349
|
const timeout = options.popupTimeout ?? 90000
|
|
340
350
|
|
|
351
|
+
// Preserve redirect URL from current location (for popup flow too)
|
|
352
|
+
if (typeof window !== 'undefined' && typeof sessionStorage !== 'undefined') {
|
|
353
|
+
const currentParams = queryParams()
|
|
354
|
+
const redirectUrl = currentParams.redirect
|
|
355
|
+
if (redirectUrl) {
|
|
356
|
+
// Store redirect URL to restore after OAuth callback
|
|
357
|
+
sessionStorage.setItem(`oauth_redirect:${state}`, redirectUrl)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
341
361
|
// Store state AND provider in sessionStorage for verification
|
|
342
362
|
if (typeof sessionStorage !== 'undefined') {
|
|
343
363
|
sessionStorage.setItem(getStateKey(), state)
|
|
@@ -615,6 +635,19 @@ function handleOAuthCallback(): Promise<AuthenticationResponse | null> {
|
|
|
615
635
|
throw new Error('Unable to determine OAuth provider. State may have expired.')
|
|
616
636
|
}
|
|
617
637
|
|
|
638
|
+
// Restore redirect URL to current location if it was stored
|
|
639
|
+
if (typeof window !== 'undefined' && typeof sessionStorage !== 'undefined') {
|
|
640
|
+
const storedRedirect = sessionStorage.getItem(`oauth_redirect:${state}`)
|
|
641
|
+
if (storedRedirect) {
|
|
642
|
+
// Add redirect param back to URL so auto-redirect can pick it up
|
|
643
|
+
const url = new URL(window.location.href)
|
|
644
|
+
url.searchParams.set('redirect', storedRedirect)
|
|
645
|
+
window.history.replaceState({}, '', url.toString())
|
|
646
|
+
// Clean up
|
|
647
|
+
sessionStorage.removeItem(`oauth_redirect:${state}`)
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
618
651
|
return ssoProviders[provider].callback(code, state)
|
|
619
652
|
}
|
|
620
653
|
|
|
@@ -636,5 +669,18 @@ function handleOAuthLinkCallback(): Promise<void> {
|
|
|
636
669
|
throw new Error('Unable to determine OAuth provider. State may have expired.')
|
|
637
670
|
}
|
|
638
671
|
|
|
672
|
+
// Restore redirect URL to current location if it was stored
|
|
673
|
+
if (typeof window !== 'undefined' && typeof sessionStorage !== 'undefined') {
|
|
674
|
+
const storedRedirect = sessionStorage.getItem(`oauth_redirect:${state}`)
|
|
675
|
+
if (storedRedirect) {
|
|
676
|
+
// Add redirect param back to URL
|
|
677
|
+
const url = new URL(window.location.href)
|
|
678
|
+
url.searchParams.set('redirect', storedRedirect)
|
|
679
|
+
window.history.replaceState({}, '', url.toString())
|
|
680
|
+
// Clean up
|
|
681
|
+
sessionStorage.removeItem(`oauth_redirect:${state}`)
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
639
685
|
return ssoProviders[provider].link(code, state)
|
|
640
686
|
}
|
package/src/types/redirect.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface RedirectConfig {
|
|
|
19
19
|
/**
|
|
20
20
|
* Routes that require NO authentication (login, signup, forgot password, etc)
|
|
21
21
|
* Authenticated users will be automatically redirected away from these pages
|
|
22
|
-
* @default ['Login', 'Signup', 'ForgotPassword', 'ResetPassword', '
|
|
22
|
+
* @default ['Login', 'Signup', 'ForgotPassword', 'ResetPassword', 'AuthCallback']
|
|
23
23
|
*/
|
|
24
24
|
noAuthRoutes?: string[]
|
|
25
25
|
|
|
@@ -77,7 +77,7 @@ export interface NormalizedRedirectConfig extends Required<Omit<RedirectConfig,
|
|
|
77
77
|
export const DEFAULT_REDIRECT_CONFIG: NormalizedRedirectConfig = {
|
|
78
78
|
queryKey: 'redirect',
|
|
79
79
|
fallback: '/',
|
|
80
|
-
noAuthRoutes: ['Login', 'Signup', 'ForgotPassword', 'ResetPassword', '
|
|
80
|
+
noAuthRoutes: ['Login', 'Signup', 'ForgotPassword', 'ResetPassword', 'AuthCallback'],
|
|
81
81
|
authenticatedRedirect: '/',
|
|
82
82
|
loginRoute: 'Login',
|
|
83
83
|
authMetaKey: 'auth',
|
package/src/useAuth.ts
CHANGED
|
@@ -24,6 +24,7 @@ let authApi: AuthApi | null = null
|
|
|
24
24
|
let eventEmitter: EventEmitter | null = null
|
|
25
25
|
let redirectConfig: NormalizedRedirectConfig | null = null
|
|
26
26
|
let autoRedirectRouter: any = null // Router instance for auto-redirect
|
|
27
|
+
let cachedAuthGuard: any = null // Cached router guard
|
|
27
28
|
const accountInfo = ref<AccountInfo | null>(null)
|
|
28
29
|
|
|
29
30
|
interface InitParams {
|
|
@@ -35,21 +36,6 @@ interface InitParams {
|
|
|
35
36
|
redirect?: RedirectConfig
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
/**
|
|
39
|
-
* Set the router instance for auto-redirect functionality
|
|
40
|
-
* Call this in your app setup after creating the router
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* ```ts
|
|
44
|
-
* const auth = createAuth({ ... })
|
|
45
|
-
* const router = createRouter({ ... })
|
|
46
|
-
* setAuthRouter(router)
|
|
47
|
-
* ```
|
|
48
|
-
*/
|
|
49
|
-
export function setAuthRouter(router: any) {
|
|
50
|
-
autoRedirectRouter = router
|
|
51
|
-
}
|
|
52
|
-
|
|
53
39
|
/**
|
|
54
40
|
* Get the current redirect configuration
|
|
55
41
|
* Used internally by router guard
|
|
@@ -101,6 +87,76 @@ export function createAuth(params: InitParams) {
|
|
|
101
87
|
}
|
|
102
88
|
},
|
|
103
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Connect external dependencies like Vue Router
|
|
92
|
+
* Automatically sets up router guard when router is provided
|
|
93
|
+
* @param dependency - Vue Router instance or other plugins
|
|
94
|
+
* @param options - Configuration options
|
|
95
|
+
* @param options.guard - Whether to automatically set up auth guard (default: true)
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* // Auto setup (default)
|
|
99
|
+
* auth.use(router)
|
|
100
|
+
*
|
|
101
|
+
* // Manual guard control (for custom composition)
|
|
102
|
+
* auth.use(router, { guard: false })
|
|
103
|
+
* router.beforeEach(async (to, from, next) => {
|
|
104
|
+
* // Custom logic first
|
|
105
|
+
* if (!hasOrgAccess(to)) return next('/no-access')
|
|
106
|
+
* // Then run auth guard
|
|
107
|
+
* return auth.routerGuard()(to, from, next)
|
|
108
|
+
* })
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
use(dependency: any, options: { guard?: boolean } = {}) {
|
|
112
|
+
const { guard = true } = options
|
|
113
|
+
|
|
114
|
+
// Detect if it's a router by checking for common router properties
|
|
115
|
+
if (dependency && (dependency.beforeEach || dependency.push || dependency.currentRoute)) {
|
|
116
|
+
autoRedirectRouter = dependency
|
|
117
|
+
// Automatically set up the auth guard unless disabled
|
|
118
|
+
if (guard) {
|
|
119
|
+
dependency.beforeEach(authInstance.routerGuard())
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return authInstance
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Create a Vue Router navigation guard for authentication
|
|
127
|
+
* Protects routes requiring authentication and handles redirect logic
|
|
128
|
+
* Note: Automatically called by auth.use(router), only use directly for custom setups
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* // Automatic (recommended)
|
|
132
|
+
* auth.use(router)
|
|
133
|
+
*
|
|
134
|
+
* // Manual (for custom setups)
|
|
135
|
+
* router.beforeEach(auth.routerGuard())
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
routerGuard() {
|
|
139
|
+
// Return factory that lazily loads authGuard to avoid circular dependency
|
|
140
|
+
if (cachedAuthGuard === null) {
|
|
141
|
+
cachedAuthGuard = async (to: any, from: any, next: any) => {
|
|
142
|
+
const { authGuard } = await import('./router')
|
|
143
|
+
const guard = authGuard()
|
|
144
|
+
// Cache the actual guard for next time
|
|
145
|
+
cachedAuthGuard = guard
|
|
146
|
+
return guard(to, from, next)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return cachedAuthGuard
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Vue plugin install method
|
|
154
|
+
* Makes auth available globally as $auth
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* app.use(auth)
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
104
160
|
install(app: App) {
|
|
105
161
|
// Make auth available globally
|
|
106
162
|
app.config.globalProperties.$auth = useAuth()
|
|
@@ -119,7 +175,7 @@ function setupAutoRedirect() {
|
|
|
119
175
|
eventEmitter.on(AuthState.LOGIN, async () => {
|
|
120
176
|
// Only auto-redirect if router is available
|
|
121
177
|
if (!autoRedirectRouter) {
|
|
122
|
-
console.warn('[Auth] Auto-redirect enabled but router not set. Call
|
|
178
|
+
console.warn('[Auth] Auto-redirect enabled but router not set. Call auth.use(router) in your app setup.')
|
|
123
179
|
return
|
|
124
180
|
}
|
|
125
181
|
|