@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 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, setAuthRouter, authGuard } from '@bagelink/auth'
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 for auto-redirect
41
- setAuthRouter(router)
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
- ### `authGuard()`
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
- Navigation guard for protected routes:
171
+ #### Option 1: Using `composeGuards` utility (recommended)
173
172
 
174
173
  ```typescript
175
- router.beforeEach(authGuard()) // No parameters needed!
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
- Protects routes with `meta: { auth: true }` and handles redirects automatically.
195
+ #### Option 2: Multiple `beforeEach` calls
179
196
 
180
- ### `setAuthRouter(router)`
197
+ ```typescript
198
+ auth.use(router, { guard: false })
181
199
 
182
- Connect router for auto-redirect:
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
- setAuthRouter(router) // Call after creating router
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: 'Callback',
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 router = vueRouter.useRouter();
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(() => router.push("/"), timeout);
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(() => router.push("/"), timeout);
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 router = vueRouter.useRouter();
1249
+ const router2 = vueRouter.useRouter();
1243
1250
  function switchForm(form) {
1244
1251
  if (form === "login") {
1245
- router.push("/login");
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 router = vueRouter.useRouter();
1293
+ const router2 = vueRouter.useRouter();
1287
1294
  function switchForm(form) {
1288
1295
  if (form === "signup") {
1289
- router.push("/signup");
1296
+ router2.push("/signup");
1290
1297
  } else if (form === "forgot-password") {
1291
- router.push("/forgot-password");
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 router = vueRouter.useRouter();
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
- router.push("/login");
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 router = vueRouter.useRouter();
1376
+ const router2 = vueRouter.useRouter();
1370
1377
  function switchForm(form) {
1371
1378
  if (form === "login") {
1372
- router.push("/login");
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(router, config) {
1395
- const redirect2 = router.currentRoute.value.query[config.queryKey];
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(router, config) {
1420
- const redirect2 = getRedirectUrl(router, config);
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 router.push(config.fallback);
1430
+ await router2.push(config.fallback);
1424
1431
  return;
1425
1432
  }
1426
- await router.push(redirect2);
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", "Callback"],
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 setAuthRouter(router) in your app setup.");
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 router = useRouter();
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(() => router.push("/"), timeout);
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(() => router.push("/"), timeout);
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 router = useRouter();
1247
+ const router2 = useRouter();
1241
1248
  function switchForm(form) {
1242
1249
  if (form === "login") {
1243
- router.push("/login");
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 router = useRouter();
1291
+ const router2 = useRouter();
1285
1292
  function switchForm(form) {
1286
1293
  if (form === "signup") {
1287
- router.push("/signup");
1294
+ router2.push("/signup");
1288
1295
  } else if (form === "forgot-password") {
1289
- router.push("/forgot-password");
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 router = useRouter();
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
- router.push("/login");
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 router = useRouter();
1374
+ const router2 = useRouter();
1368
1375
  function switchForm(form) {
1369
1376
  if (form === "login") {
1370
- router.push("/login");
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(router, config) {
1393
- const redirect2 = router.currentRoute.value.query[config.queryKey];
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(router, config) {
1418
- const redirect2 = getRedirectUrl(router, config);
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 router.push(config.fallback);
1428
+ await router2.push(config.fallback);
1422
1429
  return;
1423
1430
  }
1424
- await router.push(redirect2);
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", "Callback"],
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 setAuthRouter(router) in your app setup.");
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;
@@ -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', 'Callback']
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/auth",
3
3
  "type": "module",
4
- "version": "1.7.74",
4
+ "version": "1.7.78",
5
5
  "description": "Bagelink auth package",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -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(() => router.push('/'), timeout)
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
- setTimeout(() => router.push('/'), timeout)
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
  }
@@ -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', 'Callback']
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', 'Callback'],
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 setAuthRouter(router) in your app setup.')
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