diff --git a/apps/web/__tests__/test/feature/auth-guard.cy.ts b/apps/web/__tests__/test/feature/auth-guard.cy.ts new file mode 100644 index 000000000..3180aa6b9 --- /dev/null +++ b/apps/web/__tests__/test/feature/auth-guard.cy.ts @@ -0,0 +1,82 @@ +import { MyAccountPageObject } from '../../support/pageObjects/MyAccountPageObject'; +import { paths } from '../../../utils/paths'; + +const guardedRoutes = [ + paths.accountPersonalData, + paths.accountBillingDetails, + paths.accountShippingDetails, + paths.accountMyOrders, + paths.accountMyWishlist, + paths.accountReturns, + paths.accountNewReturn + '/1/accessKey' +]; + +describe('Auth Guard', () => { + beforeEach(() => { + cy.clearCookie('pwa-session-id'); + }); + + it('should redirect from accountPersonalData to login page if user is not authorized', () => { + cy.visit(paths.accountPersonalData); + cy.url().should('include', paths.authLogin); + }); + + it('should redirect from accountBillingDetails to login page if user is not authorized', () => { + cy.visit(paths.accountBillingDetails); + cy.url().should('include', paths.authLogin); + }); + + it('should redirect from accountShippingDetails to login page if user is not authorized', () => { + cy.visit(paths.accountShippingDetails); + cy.url().should('include', paths.authLogin); + }); + + it('should redirect from accountMyOrders to login page if user is not authorized', () => { + cy.visit(paths.accountMyOrders); + cy.url().should('include', paths.authLogin); + }); + + it('should redirect from accountMyWishlist to login page if user is not authorized', () => { + cy.visit(paths.accountMyWishlist); + cy.url().should('include', paths.authLogin); + }); + + it('should redirect from accountReturns to login page if user is not authorized', () => { + cy.visit(paths.accountReturns); + cy.url().should('include', paths.authLogin); + }); + + it('should redirect from accountNewReturn to login page if user is not authorized', () => { + cy.visit(paths.accountNewReturn + '/1/accessKey'); + cy.url().should('include', paths.authLogin); + }); + + it('should allow access to authorized users', () => { + const myAccount = new MyAccountPageObject(); + + cy.intercept('/plentysystems/doLogin').as('doLogin'); + cy.visitAndHydrate(paths.authLogin); + myAccount.successLogin(); + + cy.wait('@doLogin'); + + guardedRoutes.forEach(route => { + cy.visitAndHydrate(route); + cy.url().should('include', route); + }); + }); + + it('should redirect back to protected page after successful login', () => { + const myAccount = new MyAccountPageObject(); + + cy.visit(paths.accountPersonalData); + cy.url().should('include', `${paths.authLogin}?redirect=${paths.accountPersonalData}`); + + cy.intercept('/plentysystems/doLogin').as('doLogin'); + myAccount.successLogin(); + + cy.wait('@doLogin'); + cy.url().should('include', paths.accountPersonalData); + }); + +}); \ No newline at end of file diff --git a/apps/web/app.vue b/apps/web/app.vue index dc420610b..abe02b4fb 100644 --- a/apps/web/app.vue +++ b/apps/web/app.vue @@ -16,8 +16,6 @@ const { setVsfLocale } = useLocalization(); const route = useRoute(); const { locale } = useI18n(); const { setStaticPageMeta } = useCanonical(); -const { isAuthorized } = useCustomer(); -const localePath = useLocalePath(); await setInitialDataSSR(); setVsfLocale(locale.value); @@ -25,30 +23,10 @@ setVsfLocale(locale.value); if (route?.meta.pageType === 'static') setStaticPageMeta(); usePageTitle(); -const authOnlyRoutes = new Set([ - localePath(paths.accountPersonalData), - localePath(paths.accountBillingDetails), - localePath(paths.accountShippingDetails), - localePath(paths.accountMyOrders), - localePath(paths.accountMyWishlist), - localePath(paths.accountReturns), - localePath(paths.accountNewReturn), -]); - -const watchAuthRoutes = (authenticated: boolean) => { - if (authOnlyRoutes.has(localePath(route.path)) && !authenticated) navigateTo(localePath(paths.home)); -}; - onNuxtReady(async () => { bodyClass.value = 'hydrated'; // Need this class for cypress testing - watchAuthRoutes(isAuthorized.value); }); -watch( - () => isAuthorized.value, - (authenticated: boolean) => watchAuthRoutes(authenticated), -); - watch( () => locale.value, async (locale: string) => { diff --git a/apps/web/middleware/auth-guard.ts b/apps/web/middleware/auth-guard.ts new file mode 100644 index 000000000..6255e20d5 --- /dev/null +++ b/apps/web/middleware/auth-guard.ts @@ -0,0 +1,24 @@ +/** + * This middleware is used to check if the user is authorized. + * + * Use this auth guard to protect routes that require the user to be logged in. + * + * If the user is not authorized, the user will be redirected to the login page. + */ + +export default defineNuxtRouteMiddleware(async (to) => { + const { isAuthorized, getSession } = useCustomer(); + const localePath = useLocalePath(); + + await getSession(); + + if (!isAuthorized.value) { + const targetUrl = to.fullPath; + return navigateTo({ + path: localePath(paths.authLogin), + query: { + redirect: targetUrl, + }, + }); + } +}); diff --git a/apps/web/pages/login.vue b/apps/web/pages/login.vue index 83d74cd33..85c5b5463 100644 --- a/apps/web/pages/login.vue +++ b/apps/web/pages/login.vue @@ -1,7 +1,7 @@ @@ -11,9 +11,17 @@ definePageMeta({ }); const router = useRouter(); -const returnToPreviousPage = () => { +const localePath = useLocalePath(); +const isLogin = ref(true); + +const navigateAfterAuth = () => { + const redirectUrl = router.currentRoute.value.query.redirect as string; + + if (redirectUrl) { + router.push(localePath(redirectUrl)); + return; + } + router.go(-1); }; - -const isLogin = ref(true); diff --git a/apps/web/pages/my-account/billing-details.vue b/apps/web/pages/my-account/billing-details.vue index 2d4dffec5..4660905b4 100644 --- a/apps/web/pages/my-account/billing-details.vue +++ b/apps/web/pages/my-account/billing-details.vue @@ -20,5 +20,6 @@ import { AddressType } from '@plentymarkets/shop-api'; definePageMeta({ layout: 'account', pageType: 'static', + middleware: ['auth-guard'], }); diff --git a/apps/web/pages/my-account/index.vue b/apps/web/pages/my-account/index.vue index 0a670da23..3ce1a1709 100644 --- a/apps/web/pages/my-account/index.vue +++ b/apps/web/pages/my-account/index.vue @@ -2,5 +2,6 @@ definePageMeta({ layout: 'account', pageType: 'static', + middleware: ['auth-guard'], }); diff --git a/apps/web/pages/my-account/my-orders.vue b/apps/web/pages/my-account/my-orders.vue index a3e9f5727..c8d8efefb 100644 --- a/apps/web/pages/my-account/my-orders.vue +++ b/apps/web/pages/my-account/my-orders.vue @@ -172,6 +172,7 @@ const isDesktop = computed(() => viewport.isGreaterOrEquals('lg')); definePageMeta({ layout: 'account', pageType: 'static', + middleware: ['auth-guard'], }); onMounted(() => setMaxVisiblePages(isDesktop.value)); diff --git a/apps/web/pages/my-account/my-orders/[id].vue b/apps/web/pages/my-account/my-orders/[id].vue index e0398610e..6dfa27e10 100644 --- a/apps/web/pages/my-account/my-orders/[id].vue +++ b/apps/web/pages/my-account/my-orders/[id].vue @@ -162,6 +162,12 @@ const { locale } = useI18n(); const { isOpen } = useDisclosure({ initialValue: true }); const { fetchOrder, data } = useCustomerOrder(route.params.id as string); +definePageMeta({ + layout: 'account', + pageType: 'static', + middleware: ['auth-guard'], +}); + onMounted(async () => { // without nextTick data on first click does not load data await nextTick(); diff --git a/apps/web/pages/my-account/new-return/[id]/[accessKey].vue b/apps/web/pages/my-account/new-return/[id]/[accessKey].vue index 9f8acaa80..900f29070 100644 --- a/apps/web/pages/my-account/new-return/[id]/[accessKey].vue +++ b/apps/web/pages/my-account/new-return/[id]/[accessKey].vue @@ -59,6 +59,7 @@ const { t, locale } = useI18n(); definePageMeta({ layout: 'account', pageType: 'static', + middleware: ['auth-guard'], }); const { currentReturnOrder, hasMinimumQuantitySelected, hasQuantityAndNoReasonsSelected, selectAll, cleanReturnData } = useReturnOrder(); diff --git a/apps/web/pages/my-account/personal-data.vue b/apps/web/pages/my-account/personal-data.vue index 564d98932..8f43c9487 100644 --- a/apps/web/pages/my-account/personal-data.vue +++ b/apps/web/pages/my-account/personal-data.vue @@ -71,6 +71,7 @@ import { unrefElement } from '@vueuse/core'; definePageMeta({ layout: 'account', pageType: 'static', + middleware: ['auth-guard'], }); const { isOpen, open, close } = useDisclosure(); const lastActiveElement = ref(); diff --git a/apps/web/pages/my-account/returns.vue b/apps/web/pages/my-account/returns.vue index 0636f55a4..4e3fe24f7 100644 --- a/apps/web/pages/my-account/returns.vue +++ b/apps/web/pages/my-account/returns.vue @@ -115,6 +115,7 @@ import { useDisclosure, SfLoaderCircular } from '@storefront-ui/vue'; definePageMeta({ layout: 'account', pageType: 'static', + middleware: ['auth-guard'], }); const { data, fetchCustomerReturns, loading } = useCustomerReturns(); diff --git a/apps/web/pages/my-account/shipping-details.vue b/apps/web/pages/my-account/shipping-details.vue index 6ed0f262a..d5a9bd49c 100644 --- a/apps/web/pages/my-account/shipping-details.vue +++ b/apps/web/pages/my-account/shipping-details.vue @@ -20,5 +20,6 @@ import { AddressType } from '@plentymarkets/shop-api'; definePageMeta({ layout: 'account', pageType: 'static', + middleware: ['auth-guard'], }); diff --git a/apps/web/pages/my-account/wishlist.vue b/apps/web/pages/my-account/wishlist.vue index 0b71a7e91..7b4aefe86 100644 --- a/apps/web/pages/my-account/wishlist.vue +++ b/apps/web/pages/my-account/wishlist.vue @@ -17,5 +17,6 @@ definePageMeta({ layout: 'account', pageType: 'static', + middleware: ['auth-guard'], });