@bagelink/auth 1.7.76 → 1.7.80
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 +26 -1
- package/dist/index.cjs +60 -5
- package/dist/index.mjs +60 -5
- package/dist/types/redirect.d.ts +1 -1
- package/package.json +1 -1
- package/src/pages/Callback.vue +16 -3
- package/src/redirect.ts +4 -0
- package/src/sso.ts +54 -0
- package/src/types/redirect.ts +2 -2
- package/src/useAuth.ts +1 -1
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: '
|
|
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(() =>
|
|
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,8 +1103,10 @@ 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}`);
|
|
1108
|
+
console.log("[Callback] Query params:", { state, redirect: redirect2 });
|
|
1109
|
+
console.log("[Callback] Full route query:", route.query);
|
|
1104
1110
|
try {
|
|
1105
1111
|
const response = await sso2.handleCallback();
|
|
1106
1112
|
if (response === null) {
|
|
@@ -1108,7 +1114,12 @@ const _sfc_main$4 = /* @__PURE__ */ vue.defineComponent({
|
|
|
1108
1114
|
} else {
|
|
1109
1115
|
authResponse.value = response;
|
|
1110
1116
|
success.value = true;
|
|
1111
|
-
|
|
1117
|
+
console.log("[Callback] Login successful, redirect param:", redirect2);
|
|
1118
|
+
setTimeout(() => {
|
|
1119
|
+
const redirectPath = typeof redirect2 === "string" ? redirect2 : "/";
|
|
1120
|
+
console.log("[Callback] Manual fallback redirecting to:", redirectPath);
|
|
1121
|
+
router2.push(redirectPath);
|
|
1122
|
+
}, timeout);
|
|
1112
1123
|
}
|
|
1113
1124
|
} catch (err) {
|
|
1114
1125
|
const errorMessage = err instanceof Error ? err.message : "Authentication failed";
|
|
@@ -1418,11 +1429,15 @@ function isValidRedirect(redirectUrl, allowedPaths) {
|
|
|
1418
1429
|
}
|
|
1419
1430
|
async function performRedirect(router2, config) {
|
|
1420
1431
|
const redirect2 = getRedirectUrl(router2, config);
|
|
1432
|
+
console.log("[PerformRedirect] Current route query:", router2.currentRoute.value.query);
|
|
1433
|
+
console.log("[PerformRedirect] Redirect URL:", redirect2);
|
|
1434
|
+
console.log("[PerformRedirect] Config fallback:", config.fallback);
|
|
1421
1435
|
if (redirect2 !== config.fallback && !isValidRedirect(redirect2, config.allowedPaths)) {
|
|
1422
1436
|
console.warn("[Auth] Invalid redirect URL detected, using fallback:", redirect2);
|
|
1423
1437
|
await router2.push(config.fallback);
|
|
1424
1438
|
return;
|
|
1425
1439
|
}
|
|
1440
|
+
console.log("[PerformRedirect] Redirecting to:", redirect2);
|
|
1426
1441
|
await router2.push(redirect2);
|
|
1427
1442
|
}
|
|
1428
1443
|
function buildLoginQuery(currentPath, config) {
|
|
@@ -1584,6 +1599,16 @@ function createSSOProvider(config) {
|
|
|
1584
1599
|
const auth = getAuthApi();
|
|
1585
1600
|
const redirectUri = options.redirectUri ?? getDefaultRedirectUri();
|
|
1586
1601
|
const state = options.state ?? generateState();
|
|
1602
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1603
|
+
const currentParams = queryParams();
|
|
1604
|
+
const redirectUrl = currentParams.redirect;
|
|
1605
|
+
if (redirectUrl) {
|
|
1606
|
+
console.log("[SSO] Preserving redirect URL:", redirectUrl, "with state:", state);
|
|
1607
|
+
sessionStorage.setItem(`oauth_redirect:${state}`, redirectUrl);
|
|
1608
|
+
} else {
|
|
1609
|
+
console.log("[SSO] No redirect URL found in current params:", currentParams);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1587
1612
|
if (typeof sessionStorage !== "undefined") {
|
|
1588
1613
|
sessionStorage.setItem(getStateKey(), state);
|
|
1589
1614
|
sessionStorage.setItem(`oauth_provider:${state}`, config.id);
|
|
@@ -1602,6 +1627,13 @@ function createSSOProvider(config) {
|
|
|
1602
1627
|
const redirectUri = options.redirectUri ?? getDefaultRedirectUri();
|
|
1603
1628
|
const state = options.state ?? generateState();
|
|
1604
1629
|
const timeout2 = options.popupTimeout ?? 9e4;
|
|
1630
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1631
|
+
const currentParams = queryParams();
|
|
1632
|
+
const redirectUrl = currentParams.redirect;
|
|
1633
|
+
if (redirectUrl) {
|
|
1634
|
+
sessionStorage.setItem(`oauth_redirect:${state}`, redirectUrl);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1605
1637
|
if (typeof sessionStorage !== "undefined") {
|
|
1606
1638
|
sessionStorage.setItem(getStateKey(), state);
|
|
1607
1639
|
sessionStorage.setItem(`oauth_provider:${state}`, config.id);
|
|
@@ -1818,6 +1850,20 @@ function handleOAuthCallback() {
|
|
|
1818
1850
|
if (!provider || !isSupportedProvider(provider)) {
|
|
1819
1851
|
throw new Error("Unable to determine OAuth provider. State may have expired.");
|
|
1820
1852
|
}
|
|
1853
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1854
|
+
const storedRedirect = sessionStorage.getItem(`oauth_redirect:${state}`);
|
|
1855
|
+
if (storedRedirect) {
|
|
1856
|
+
console.log("[SSO] Restoring redirect URL:", storedRedirect);
|
|
1857
|
+
const url = new URL(window.location.href);
|
|
1858
|
+
url.searchParams.set("redirect", storedRedirect);
|
|
1859
|
+
window.history.replaceState({}, "", url.toString());
|
|
1860
|
+
console.log("[SSO] URL updated to:", url.toString());
|
|
1861
|
+
sessionStorage.removeItem(`oauth_redirect:${state}`);
|
|
1862
|
+
} else {
|
|
1863
|
+
console.log("[SSO] No stored redirect URL found for state:", state);
|
|
1864
|
+
console.log("[SSO] SessionStorage keys:", Object.keys(sessionStorage));
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1821
1867
|
return ssoProviders[provider].callback(code, state);
|
|
1822
1868
|
}
|
|
1823
1869
|
function handleOAuthLinkCallback() {
|
|
@@ -1829,6 +1875,15 @@ function handleOAuthLinkCallback() {
|
|
|
1829
1875
|
if (!provider || !isSupportedProvider(provider)) {
|
|
1830
1876
|
throw new Error("Unable to determine OAuth provider. State may have expired.");
|
|
1831
1877
|
}
|
|
1878
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1879
|
+
const storedRedirect = sessionStorage.getItem(`oauth_redirect:${state}`);
|
|
1880
|
+
if (storedRedirect) {
|
|
1881
|
+
const url = new URL(window.location.href);
|
|
1882
|
+
url.searchParams.set("redirect", storedRedirect);
|
|
1883
|
+
window.history.replaceState({}, "", url.toString());
|
|
1884
|
+
sessionStorage.removeItem(`oauth_redirect:${state}`);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1832
1887
|
return ssoProviders[provider].link(code, state);
|
|
1833
1888
|
}
|
|
1834
1889
|
var AuthState = /* @__PURE__ */ ((AuthState2) => {
|
|
@@ -1891,7 +1946,7 @@ function accountToUser(account) {
|
|
|
1891
1946
|
const DEFAULT_REDIRECT_CONFIG = {
|
|
1892
1947
|
queryKey: "redirect",
|
|
1893
1948
|
fallback: "/",
|
|
1894
|
-
noAuthRoutes: ["Login", "Signup", "ForgotPassword", "ResetPassword", "
|
|
1949
|
+
noAuthRoutes: ["Login", "Signup", "ForgotPassword", "ResetPassword", "AuthCallback"],
|
|
1895
1950
|
authenticatedRedirect: "/",
|
|
1896
1951
|
loginRoute: "Login",
|
|
1897
1952
|
authMetaKey: "auth",
|
|
@@ -2019,7 +2074,7 @@ function setupAutoRedirect() {
|
|
|
2019
2074
|
if (!eventEmitter || !redirectConfig) return;
|
|
2020
2075
|
eventEmitter.on(AuthState.LOGIN, async () => {
|
|
2021
2076
|
if (!autoRedirectRouter) {
|
|
2022
|
-
console.warn("[Auth] Auto-redirect enabled but router not set. Call
|
|
2077
|
+
console.warn("[Auth] Auto-redirect enabled but router not set. Call auth.use(router) in your app setup.");
|
|
2023
2078
|
return;
|
|
2024
2079
|
}
|
|
2025
2080
|
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(() =>
|
|
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,8 +1101,10 @@ 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}`);
|
|
1106
|
+
console.log("[Callback] Query params:", { state, redirect: redirect2 });
|
|
1107
|
+
console.log("[Callback] Full route query:", route.query);
|
|
1102
1108
|
try {
|
|
1103
1109
|
const response = await sso2.handleCallback();
|
|
1104
1110
|
if (response === null) {
|
|
@@ -1106,7 +1112,12 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
|
|
|
1106
1112
|
} else {
|
|
1107
1113
|
authResponse.value = response;
|
|
1108
1114
|
success.value = true;
|
|
1109
|
-
|
|
1115
|
+
console.log("[Callback] Login successful, redirect param:", redirect2);
|
|
1116
|
+
setTimeout(() => {
|
|
1117
|
+
const redirectPath = typeof redirect2 === "string" ? redirect2 : "/";
|
|
1118
|
+
console.log("[Callback] Manual fallback redirecting to:", redirectPath);
|
|
1119
|
+
router2.push(redirectPath);
|
|
1120
|
+
}, timeout);
|
|
1110
1121
|
}
|
|
1111
1122
|
} catch (err) {
|
|
1112
1123
|
const errorMessage = err instanceof Error ? err.message : "Authentication failed";
|
|
@@ -1416,11 +1427,15 @@ function isValidRedirect(redirectUrl, allowedPaths) {
|
|
|
1416
1427
|
}
|
|
1417
1428
|
async function performRedirect(router2, config) {
|
|
1418
1429
|
const redirect2 = getRedirectUrl(router2, config);
|
|
1430
|
+
console.log("[PerformRedirect] Current route query:", router2.currentRoute.value.query);
|
|
1431
|
+
console.log("[PerformRedirect] Redirect URL:", redirect2);
|
|
1432
|
+
console.log("[PerformRedirect] Config fallback:", config.fallback);
|
|
1419
1433
|
if (redirect2 !== config.fallback && !isValidRedirect(redirect2, config.allowedPaths)) {
|
|
1420
1434
|
console.warn("[Auth] Invalid redirect URL detected, using fallback:", redirect2);
|
|
1421
1435
|
await router2.push(config.fallback);
|
|
1422
1436
|
return;
|
|
1423
1437
|
}
|
|
1438
|
+
console.log("[PerformRedirect] Redirecting to:", redirect2);
|
|
1424
1439
|
await router2.push(redirect2);
|
|
1425
1440
|
}
|
|
1426
1441
|
function buildLoginQuery(currentPath, config) {
|
|
@@ -1582,6 +1597,16 @@ function createSSOProvider(config) {
|
|
|
1582
1597
|
const auth = getAuthApi();
|
|
1583
1598
|
const redirectUri = options.redirectUri ?? getDefaultRedirectUri();
|
|
1584
1599
|
const state = options.state ?? generateState();
|
|
1600
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1601
|
+
const currentParams = queryParams();
|
|
1602
|
+
const redirectUrl = currentParams.redirect;
|
|
1603
|
+
if (redirectUrl) {
|
|
1604
|
+
console.log("[SSO] Preserving redirect URL:", redirectUrl, "with state:", state);
|
|
1605
|
+
sessionStorage.setItem(`oauth_redirect:${state}`, redirectUrl);
|
|
1606
|
+
} else {
|
|
1607
|
+
console.log("[SSO] No redirect URL found in current params:", currentParams);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1585
1610
|
if (typeof sessionStorage !== "undefined") {
|
|
1586
1611
|
sessionStorage.setItem(getStateKey(), state);
|
|
1587
1612
|
sessionStorage.setItem(`oauth_provider:${state}`, config.id);
|
|
@@ -1600,6 +1625,13 @@ function createSSOProvider(config) {
|
|
|
1600
1625
|
const redirectUri = options.redirectUri ?? getDefaultRedirectUri();
|
|
1601
1626
|
const state = options.state ?? generateState();
|
|
1602
1627
|
const timeout2 = options.popupTimeout ?? 9e4;
|
|
1628
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1629
|
+
const currentParams = queryParams();
|
|
1630
|
+
const redirectUrl = currentParams.redirect;
|
|
1631
|
+
if (redirectUrl) {
|
|
1632
|
+
sessionStorage.setItem(`oauth_redirect:${state}`, redirectUrl);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1603
1635
|
if (typeof sessionStorage !== "undefined") {
|
|
1604
1636
|
sessionStorage.setItem(getStateKey(), state);
|
|
1605
1637
|
sessionStorage.setItem(`oauth_provider:${state}`, config.id);
|
|
@@ -1816,6 +1848,20 @@ function handleOAuthCallback() {
|
|
|
1816
1848
|
if (!provider || !isSupportedProvider(provider)) {
|
|
1817
1849
|
throw new Error("Unable to determine OAuth provider. State may have expired.");
|
|
1818
1850
|
}
|
|
1851
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1852
|
+
const storedRedirect = sessionStorage.getItem(`oauth_redirect:${state}`);
|
|
1853
|
+
if (storedRedirect) {
|
|
1854
|
+
console.log("[SSO] Restoring redirect URL:", storedRedirect);
|
|
1855
|
+
const url = new URL(window.location.href);
|
|
1856
|
+
url.searchParams.set("redirect", storedRedirect);
|
|
1857
|
+
window.history.replaceState({}, "", url.toString());
|
|
1858
|
+
console.log("[SSO] URL updated to:", url.toString());
|
|
1859
|
+
sessionStorage.removeItem(`oauth_redirect:${state}`);
|
|
1860
|
+
} else {
|
|
1861
|
+
console.log("[SSO] No stored redirect URL found for state:", state);
|
|
1862
|
+
console.log("[SSO] SessionStorage keys:", Object.keys(sessionStorage));
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1819
1865
|
return ssoProviders[provider].callback(code, state);
|
|
1820
1866
|
}
|
|
1821
1867
|
function handleOAuthLinkCallback() {
|
|
@@ -1827,6 +1873,15 @@ function handleOAuthLinkCallback() {
|
|
|
1827
1873
|
if (!provider || !isSupportedProvider(provider)) {
|
|
1828
1874
|
throw new Error("Unable to determine OAuth provider. State may have expired.");
|
|
1829
1875
|
}
|
|
1876
|
+
if (typeof window !== "undefined" && typeof sessionStorage !== "undefined") {
|
|
1877
|
+
const storedRedirect = sessionStorage.getItem(`oauth_redirect:${state}`);
|
|
1878
|
+
if (storedRedirect) {
|
|
1879
|
+
const url = new URL(window.location.href);
|
|
1880
|
+
url.searchParams.set("redirect", storedRedirect);
|
|
1881
|
+
window.history.replaceState({}, "", url.toString());
|
|
1882
|
+
sessionStorage.removeItem(`oauth_redirect:${state}`);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1830
1885
|
return ssoProviders[provider].link(code, state);
|
|
1831
1886
|
}
|
|
1832
1887
|
var AuthState = /* @__PURE__ */ ((AuthState2) => {
|
|
@@ -1889,7 +1944,7 @@ function accountToUser(account) {
|
|
|
1889
1944
|
const DEFAULT_REDIRECT_CONFIG = {
|
|
1890
1945
|
queryKey: "redirect",
|
|
1891
1946
|
fallback: "/",
|
|
1892
|
-
noAuthRoutes: ["Login", "Signup", "ForgotPassword", "ResetPassword", "
|
|
1947
|
+
noAuthRoutes: ["Login", "Signup", "ForgotPassword", "ResetPassword", "AuthCallback"],
|
|
1893
1948
|
authenticatedRedirect: "/",
|
|
1894
1949
|
loginRoute: "Login",
|
|
1895
1950
|
authMetaKey: "auth",
|
|
@@ -2017,7 +2072,7 @@ function setupAutoRedirect() {
|
|
|
2017
2072
|
if (!eventEmitter || !redirectConfig) return;
|
|
2018
2073
|
eventEmitter.on(AuthState.LOGIN, async () => {
|
|
2019
2074
|
if (!autoRedirectRouter) {
|
|
2020
|
-
console.warn("[Auth] Auto-redirect enabled but router not set. Call
|
|
2075
|
+
console.warn("[Auth] Auto-redirect enabled but router not set. Call auth.use(router) in your app setup.");
|
|
2021
2076
|
return;
|
|
2022
2077
|
}
|
|
2023
2078
|
const { performRedirect: performRedirect2 } = await Promise.resolve().then(() => redirect);
|
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/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,8 +43,10 @@ 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
|
|
48
|
+
console.log('[Callback] Query params:', { state, redirect })
|
|
49
|
+
console.log('[Callback] Full route query:', route.query)
|
|
44
50
|
|
|
45
51
|
try {
|
|
46
52
|
const response = await sso.handleCallback()
|
|
@@ -49,7 +55,14 @@ async function handleCallback() {
|
|
|
49
55
|
} else {
|
|
50
56
|
authResponse.value = response
|
|
51
57
|
success.value = true
|
|
52
|
-
|
|
58
|
+
console.log('[Callback] Login successful, redirect param:', redirect)
|
|
59
|
+
// Auto-redirect will handle navigation, but fallback to manual redirect
|
|
60
|
+
// if auto-redirect is disabled or router not connected
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
const redirectPath = typeof redirect === 'string' ? redirect : '/'
|
|
63
|
+
console.log('[Callback] Manual fallback redirecting to:', redirectPath)
|
|
64
|
+
router.push(redirectPath)
|
|
65
|
+
}, timeout)
|
|
53
66
|
}
|
|
54
67
|
} catch (err: unknown) {
|
|
55
68
|
const errorMessage = err instanceof Error ? err.message : 'Authentication failed'
|
package/src/redirect.ts
CHANGED
|
@@ -64,6 +64,9 @@ export async function performRedirect(
|
|
|
64
64
|
config: NormalizedRedirectConfig,
|
|
65
65
|
): Promise<void> {
|
|
66
66
|
const redirect = getRedirectUrl(router, config)
|
|
67
|
+
console.log('[PerformRedirect] Current route query:', router.currentRoute.value.query)
|
|
68
|
+
console.log('[PerformRedirect] Redirect URL:', redirect)
|
|
69
|
+
console.log('[PerformRedirect] Config fallback:', config.fallback)
|
|
67
70
|
|
|
68
71
|
// Always validate redirect URL for security
|
|
69
72
|
if (redirect !== config.fallback && !isValidRedirect(redirect, config.allowedPaths)) {
|
|
@@ -72,6 +75,7 @@ export async function performRedirect(
|
|
|
72
75
|
return
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
console.log('[PerformRedirect] Redirecting to:', redirect)
|
|
75
79
|
await router.push(redirect)
|
|
76
80
|
}
|
|
77
81
|
|
package/src/sso.ts
CHANGED
|
@@ -314,6 +314,19 @@ 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
|
+
console.log('[SSO] Preserving redirect URL:', redirectUrl, 'with state:', state)
|
|
324
|
+
sessionStorage.setItem(`oauth_redirect:${state}`, redirectUrl)
|
|
325
|
+
} else {
|
|
326
|
+
console.log('[SSO] No redirect URL found in current params:', currentParams)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
317
330
|
// Store state AND provider in sessionStorage for verification
|
|
318
331
|
if (typeof sessionStorage !== 'undefined') {
|
|
319
332
|
sessionStorage.setItem(getStateKey(), state)
|
|
@@ -338,6 +351,16 @@ function createSSOProvider(config: SSOProviderConfig): SSOProviderInstance {
|
|
|
338
351
|
const state = options.state ?? generateState()
|
|
339
352
|
const timeout = options.popupTimeout ?? 90000
|
|
340
353
|
|
|
354
|
+
// Preserve redirect URL from current location (for popup flow too)
|
|
355
|
+
if (typeof window !== 'undefined' && typeof sessionStorage !== 'undefined') {
|
|
356
|
+
const currentParams = queryParams()
|
|
357
|
+
const redirectUrl = currentParams.redirect
|
|
358
|
+
if (redirectUrl) {
|
|
359
|
+
// Store redirect URL to restore after OAuth callback
|
|
360
|
+
sessionStorage.setItem(`oauth_redirect:${state}`, redirectUrl)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
341
364
|
// Store state AND provider in sessionStorage for verification
|
|
342
365
|
if (typeof sessionStorage !== 'undefined') {
|
|
343
366
|
sessionStorage.setItem(getStateKey(), state)
|
|
@@ -615,6 +638,24 @@ function handleOAuthCallback(): Promise<AuthenticationResponse | null> {
|
|
|
615
638
|
throw new Error('Unable to determine OAuth provider. State may have expired.')
|
|
616
639
|
}
|
|
617
640
|
|
|
641
|
+
// Restore redirect URL to current location if it was stored
|
|
642
|
+
if (typeof window !== 'undefined' && typeof sessionStorage !== 'undefined') {
|
|
643
|
+
const storedRedirect = sessionStorage.getItem(`oauth_redirect:${state}`)
|
|
644
|
+
if (storedRedirect) {
|
|
645
|
+
// Add redirect param back to URL so auto-redirect can pick it up
|
|
646
|
+
console.log('[SSO] Restoring redirect URL:', storedRedirect)
|
|
647
|
+
const url = new URL(window.location.href)
|
|
648
|
+
url.searchParams.set('redirect', storedRedirect)
|
|
649
|
+
window.history.replaceState({}, '', url.toString())
|
|
650
|
+
console.log('[SSO] URL updated to:', url.toString())
|
|
651
|
+
// Clean up
|
|
652
|
+
sessionStorage.removeItem(`oauth_redirect:${state}`)
|
|
653
|
+
} else {
|
|
654
|
+
console.log('[SSO] No stored redirect URL found for state:', state)
|
|
655
|
+
console.log('[SSO] SessionStorage keys:', Object.keys(sessionStorage))
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
618
659
|
return ssoProviders[provider].callback(code, state)
|
|
619
660
|
}
|
|
620
661
|
|
|
@@ -636,5 +677,18 @@ function handleOAuthLinkCallback(): Promise<void> {
|
|
|
636
677
|
throw new Error('Unable to determine OAuth provider. State may have expired.')
|
|
637
678
|
}
|
|
638
679
|
|
|
680
|
+
// Restore redirect URL to current location if it was stored
|
|
681
|
+
if (typeof window !== 'undefined' && typeof sessionStorage !== 'undefined') {
|
|
682
|
+
const storedRedirect = sessionStorage.getItem(`oauth_redirect:${state}`)
|
|
683
|
+
if (storedRedirect) {
|
|
684
|
+
// Add redirect param back to URL
|
|
685
|
+
const url = new URL(window.location.href)
|
|
686
|
+
url.searchParams.set('redirect', storedRedirect)
|
|
687
|
+
window.history.replaceState({}, '', url.toString())
|
|
688
|
+
// Clean up
|
|
689
|
+
sessionStorage.removeItem(`oauth_redirect:${state}`)
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
639
693
|
return ssoProviders[provider].link(code, state)
|
|
640
694
|
}
|
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
|
@@ -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
|
|
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
|
|