@bagelink/auth 1.7.76 → 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
@@ -294,7 +294,7 @@ await sso.google.redirect({
294
294
  // router.ts
295
295
  {
296
296
  path: '/auth/callback',
297
- name: 'Callback',
297
+ name: 'AuthCallback',
298
298
  component: () => import('@bagelink/auth').then(m => m.Callback),
299
299
  }
300
300
  ```
@@ -308,6 +308,31 @@ await sso.google.redirect({
308
308
  - Okta
309
309
  - Facebook
310
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
+
311
336
  ## Advanced Configuration
312
337
 
313
338
  ### Security: Restrict Redirect Paths
package/dist/index.cjs CHANGED
@@ -1086,10 +1086,14 @@ const _sfc_main$4 = /* @__PURE__ */ vue.defineComponent({
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(() => router2.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(() => router2.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";
@@ -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",
@@ -2019,7 +2058,7 @@ function setupAutoRedirect() {
2019
2058
  if (!eventEmitter || !redirectConfig) return;
2020
2059
  eventEmitter.on(AuthState.LOGIN, async () => {
2021
2060
  if (!autoRedirectRouter) {
2022
- 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.");
2023
2062
  return;
2024
2063
  }
2025
2064
  const { performRedirect: performRedirect2 } = await Promise.resolve().then(() => redirect);
package/dist/index.mjs CHANGED
@@ -1084,10 +1084,14 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
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(() => router2.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(() => router2.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";
@@ -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",
@@ -2017,7 +2056,7 @@ function setupAutoRedirect() {
2017
2056
  if (!eventEmitter || !redirectConfig) return;
2018
2057
  eventEmitter.on(AuthState.LOGIN, async () => {
2019
2058
  if (!autoRedirectRouter) {
2020
- 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.");
2021
2060
  return;
2022
2061
  }
2023
2062
  const { performRedirect: performRedirect2 } = await Promise.resolve().then(() => redirect);
@@ -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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/auth",
3
3
  "type": "module",
4
- "version": "1.7.76",
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/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
@@ -175,7 +175,7 @@ function setupAutoRedirect() {
175
175
  eventEmitter.on(AuthState.LOGIN, async () => {
176
176
  // Only auto-redirect if router is available
177
177
  if (!autoRedirectRouter) {
178
- 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.')
179
179
  return
180
180
  }
181
181