diff --git a/apps/api/src/app/controllers/auth.controller.ts b/apps/api/src/app/controllers/auth.controller.ts index f30a8bdd3..d50a4b35a 100644 --- a/apps/api/src/app/controllers/auth.controller.ts +++ b/apps/api/src/app/controllers/auth.controller.ts @@ -819,7 +819,7 @@ const verification = createRoute( res.log.error({ err }, '[AUTH][PLACEHOLDER_USER][ERROR] Error destroying session'); } const searchParams = new URLSearchParams({ error: 'InvalidRegistration' }); - sendJson(res, { error: false, redirect: `/auth/login/?${searchParams.toString()}` }); + sendJson(res, { error: false, redirect: `/auth/login?${searchParams.toString()}` }); }); return; } diff --git a/apps/api/src/app/controllers/canvas.controller.ts b/apps/api/src/app/controllers/canvas.controller.ts index c47e3b138..3f5b56183 100644 --- a/apps/api/src/app/controllers/canvas.controller.ts +++ b/apps/api/src/app/controllers/canvas.controller.ts @@ -91,7 +91,7 @@ const callbackHandler = createRoute(routeDefinition.callbackHandler.validators, if (returnParams.error) { res.status(401); } - res.redirect(`/canvas-auth/?${new URLSearchParams(returnParams).toString().replaceAll('+', '%20')}`); + res.redirect(`/canvas-auth?${new URLSearchParams(returnParams).toString().replaceAll('+', '%20')}`); }); const appHandler = createRoute(routeDefinition.appHandler.validators, async ({ body = {}, query }, _req, res) => { diff --git a/apps/api/src/app/controllers/desktop-app.controller.ts b/apps/api/src/app/controllers/desktop-app.controller.ts index c6247e2f9..e293f50ee 100644 --- a/apps/api/src/app/controllers/desktop-app.controller.ts +++ b/apps/api/src/app/controllers/desktop-app.controller.ts @@ -123,7 +123,7 @@ const initAuthMiddleware = createRoute(routeDefinition.initAuthMiddleware.valida // cookies, so the frontend must pass returnUrl forward to /api/auth/sso/start where it can // be threaded into SAML RelayState (and the OIDC returnUrl cookie). setCookie(redirectUrlCookie.name, desktopReturnUrl, redirectUrlCookie.options); - redirect(res, `/auth/login/?returnUrl=${encodeURIComponent(desktopReturnUrl)}`); + redirect(res, `/auth/login?returnUrl=${encodeURIComponent(desktopReturnUrl)}`); return; } next(); diff --git a/apps/api/src/app/controllers/oauth.controller.ts b/apps/api/src/app/controllers/oauth.controller.ts index d986d91fd..564baa711 100644 --- a/apps/api/src/app/controllers/oauth.controller.ts +++ b/apps/api/src/app/controllers/oauth.controller.ts @@ -97,7 +97,7 @@ const salesforceOauthCallback = createRoute( : 'There was an error authenticating with Salesforce.'; res.log.warn({ ...queryParams, requestId: res.locals.requestId, queryParams }, '[OAUTH][ERROR] %s', queryParams.error); // eslint-disable-next-line @typescript-eslint/no-explicit-any - return res.redirect(`/oauth-link/?${new URLSearchParams(returnParams as any).toString().replaceAll('+', '%20')}`); + return res.redirect(`/oauth-link?${new URLSearchParams(returnParams as any).toString().replaceAll('+', '%20')}`); } else if (!orgAuth) { returnParams.error = 'Authentication Error'; returnParams.message = queryParams.error_description @@ -105,7 +105,7 @@ const salesforceOauthCallback = createRoute( : 'There was an error authenticating with Salesforce.'; res.log.warn({ ...queryParams, requestId: res.locals.requestId, queryParams }, '[OAUTH][ERROR] Missing orgAuth from session'); // eslint-disable-next-line @typescript-eslint/no-explicit-any - return res.redirect(`/oauth-link/?${new URLSearchParams(returnParams as any).toString().replaceAll('+', '%20')}`); + return res.redirect(`/oauth-link?${new URLSearchParams(returnParams as any).toString().replaceAll('+', '%20')}`); } const { code_verifier, nonce, state, loginUrl, orgGroupId } = orgAuth; @@ -146,7 +146,7 @@ const salesforceOauthCallback = createRoute( returnParams.data = JSON.stringify(salesforceOrg); // eslint-disable-next-line @typescript-eslint/no-explicit-any - return res.redirect(`/oauth-link/?${new URLSearchParams(returnParams as any).toString().replaceAll('+', '%20')}`); + return res.redirect(`/oauth-link?${new URLSearchParams(returnParams as any).toString().replaceAll('+', '%20')}`); } catch (ex) { let errorLogObj: Record = { err: ex }; @@ -170,7 +170,7 @@ const salesforceOauthCallback = createRoute( res.log.warn(errorLogObj, '[OAUTH][ERROR]'); // eslint-disable-next-line @typescript-eslint/no-explicit-any - return res.redirect(`/oauth-link/?${new URLSearchParams(returnParams as any).toString().replaceAll('+', '%20')}`); + return res.redirect(`/oauth-link?${new URLSearchParams(returnParams as any).toString().replaceAll('+', '%20')}`); } }, ); diff --git a/apps/api/src/app/controllers/web-extension.controller.ts b/apps/api/src/app/controllers/web-extension.controller.ts index 161d6d0b7..b164ea362 100644 --- a/apps/api/src/app/controllers/web-extension.controller.ts +++ b/apps/api/src/app/controllers/web-extension.controller.ts @@ -106,7 +106,7 @@ const initAuthMiddleware = createRoute(routeDefinition.initAuthMiddleware.valida if (!req.session.user) { const { redirectUrl: redirectUrlCookie } = getCookieConfig(ENV.USE_SECURE_COOKIES); setCookie(redirectUrlCookie.name, `${ENV.JETSTREAM_SERVER_URL}/web-extension/auth`, redirectUrlCookie.options); - redirect(res, '/auth/login/'); + redirect(res, '/auth/login'); return; } next(); diff --git a/apps/api/src/app/routes/redirect.routes.ts b/apps/api/src/app/routes/redirect.routes.ts index 9f52b6d9e..4103b7a64 100644 --- a/apps/api/src/app/routes/redirect.routes.ts +++ b/apps/api/src/app/routes/redirect.routes.ts @@ -52,7 +52,7 @@ routes.get('/', (req, res, next) => { ); } - res.redirect(`${ENV.JETSTREAM_SERVER_URL}/auth/login/?${params.toString()}`); + res.redirect(`${ENV.JETSTREAM_SERVER_URL}/auth/login?${params.toString()}`); }); export default routes; diff --git a/apps/api/src/app/routes/route.middleware.ts b/apps/api/src/app/routes/route.middleware.ts index d40444387..f72cb6f9d 100644 --- a/apps/api/src/app/routes/route.middleware.ts +++ b/apps/api/src/app/routes/route.middleware.ts @@ -22,6 +22,7 @@ import express, { Request } from 'express'; import multer from 'multer'; import { randomBytes } from 'node:crypto'; import os from 'node:os'; +import { posix as pathPosix } from 'node:path'; import pino from 'pino'; import { v4 as uuid } from 'uuid'; import * as salesforceOrgsDb from '../db/salesforce-org.db'; @@ -79,6 +80,31 @@ export function notFoundMiddleware(_: express.Request, __: express.Response, nex next(error); } +/** + * Strips trailing slashes from GET/HEAD requests via 301 redirect — needed because + * Next.js export with trailingSlash:false emits foo.html (not foo/index.html), and + * Express static won't auto-redirect /foo/ → /foo. Preserves old bookmarks and + * indexed URLs. + * + * Collapses runs of slashes before stripping the trailing one — otherwise a request + * like `//evil.com/` would yield `Location: //evil.com`, which browsers follow as a + * protocol-relative URL to evil.com (open redirect). + */ +export function stripTrailingSlashRedirect(req: express.Request, res: express.Response, next: express.NextFunction) { + if (req.method !== 'GET' && req.method !== 'HEAD') { + return next(); + } + if (req.path === '/' || !req.path.endsWith('/')) { + return next(); + } + const newPath = pathPosix.normalize(req.path.replace(/\/+/g, '/')).replace(/\/$/, ''); + if (!newPath.startsWith('/') || newPath.startsWith('//')) { + return next(); + } + const query = req.url.slice(req.path.length); + res.redirect(301, newPath + query); +} + export function destroySessionIfPendingVerificationIsExpired(req: express.Request, _: express.Response, next: express.NextFunction) { if (req.session?.pendingVerification?.length) { const { exp } = req.session.pendingVerification[0]; diff --git a/apps/api/src/app/utils/response.handlers.ts b/apps/api/src/app/utils/response.handlers.ts index 93fc86357..bcbfb2a72 100644 --- a/apps/api/src/app/utils/response.handlers.ts +++ b/apps/api/src/app/utils/response.handlers.ts @@ -243,7 +243,7 @@ export async function uncaughtErrorHandler(err: any, req: express.Request, res: } else if (err instanceof AuthError) { deferredErrorBody.errorType = err.type; deferredErrorBody.logout = true; - deferredErrorBody.logoutUrl = `${ENV.JETSTREAM_SERVER_URL}/auth/login/?${new URLSearchParams({ error: err.type }).toString()}`; + deferredErrorBody.logoutUrl = `${ENV.JETSTREAM_SERVER_URL}/auth/login?${new URLSearchParams({ error: err.type }).toString()}`; } writeDeferredResponse(res, deferredErrorBody); @@ -298,7 +298,7 @@ export async function uncaughtErrorHandler(err: any, req: express.Request, res: }); } const params = new URLSearchParams({ error: err.type }).toString(); - return res.redirect(`${ENV.JETSTREAM_SERVER_URL}/auth/login/?${params}`); + return res.redirect(`${ENV.JETSTREAM_SERVER_URL}/auth/login?${params}`); } else if (err instanceof UserFacingError) { // Attempt to use response code from 3rd party request if we have it available const statusCode = err.apiRequestError?.status || status || 400; diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index ebfb9f463..ebf010c1a 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -48,6 +48,7 @@ import { redirectIfPendingVerificationMiddleware, setCacheControlForApiRoutes, setPermissionPolicy, + stripTrailingSlashRedirect, } from './app/routes/route.middleware'; import { healthCheck, uncaughtErrorHandler } from './app/utils/response.handlers'; import { buildCspDirectives, buildHstsConfig } from './app/utils/security-headers'; @@ -302,21 +303,18 @@ if (ENV.NODE_ENV === 'production' && !ENV.CI && cluster.isPrimary) { app.use('/.well-known', express.static(join(__dirname, './assets/.well-known'))); app.use('/assets', express.static(join(__dirname, './assets'), { maxAge: '1m' })); app.use('/fonts', express.static(join(__dirname, './assets/fonts'))); - app.use(express.static(join(__dirname, '../landing'))); + // extensions: ['html'] lets Express serve foo.html for a /foo request — needed because + // Next.js export with trailingSlash:false emits flat .html files (not directory index.html). + app.use(stripTrailingSlashRedirect, express.static(join(__dirname, '../landing'), { extensions: ['html'] })); // Load the landing site's 404 page so uncaughtErrorHandler can serve it inline - // with a real 404 status (instead of redirecting to /404/, which logged as 302 - // and masked which URLs were actually missing). Next.js export emits either - // `404/index.html` (trailingSlash) or `404.html` depending on build config. + // with a real 404 status (instead of redirecting to /404, which logged as 302 + // and masked which URLs were actually missing). let notFoundHtml: string | null = null; try { - notFoundHtml = readFileSync(join(__dirname, '../landing/404/index.html'), 'utf8'); - } catch { - try { - notFoundHtml = readFileSync(join(__dirname, '../landing/404.html'), 'utf8'); - } catch (error) { - logger.error({ err: error }, '[404] Failed to read landing 404 page — 404 responses will fall back to plain text'); - } + notFoundHtml = readFileSync(join(__dirname, '../landing/404.html'), 'utf8'); + } catch (error) { + logger.error({ err: error }, '[404] Failed to read landing 404 page — 404 responses will fall back to plain text'); } app.locals.notFoundHtml = notFoundHtml; diff --git a/apps/docs/docs/getting-started/desktop-app.mdx b/apps/docs/docs/getting-started/desktop-app.mdx index 17108db28..8f951eb9f 100644 --- a/apps/docs/docs/getting-started/desktop-app.mdx +++ b/apps/docs/docs/getting-started/desktop-app.mdx @@ -28,4 +28,4 @@ Some features are currently not available in the desktop application. We are wor The Desktop Application provides the full power of Jetstream outside of your web-browser. The key benefit is that none of your Salesforce credentials or data is processed through the Jetstream server. -Visit the [download page](https://getjetstream.app/desktop-app/) to get the latest version of the desktop application for your operating system. +Visit the [download page](https://getjetstream.app/desktop-app) to get the latest version of the desktop application for your operating system. diff --git a/apps/docs/docs/getting-started/overview.mdx b/apps/docs/docs/getting-started/overview.mdx index e1b08fcd8..8db9b4573 100644 --- a/apps/docs/docs/getting-started/overview.mdx +++ b/apps/docs/docs/getting-started/overview.mdx @@ -17,14 +17,14 @@ If you have questions or want to talk with a human, you can reach support by ema :::tip -If you haven't created a Jetstream account, you can [sign up here](https://getjetstream.app/auth/signup/). +If you haven't created a Jetstream account, you can [sign up here](https://getjetstream.app/auth/signup). ::: :::tip -To get the most out of Jetstream, sign up for a paid plan. [View features included in Pro here](https://getjetstream.app/pricing/). +To get the most out of Jetstream, sign up for a paid plan. [View features included in Pro here](https://getjetstream.app/pricing). ::: diff --git a/apps/docs/docs/getting-started/security.mdx b/apps/docs/docs/getting-started/security.mdx index 8c0c76c13..89ecf3d3e 100644 --- a/apps/docs/docs/getting-started/security.mdx +++ b/apps/docs/docs/getting-started/security.mdx @@ -13,10 +13,10 @@ Jetstream is designed with security and privacy in mind. We take the protection ## Additional Resources -- [Data Processing Agreement](https://getjetstream.app/dpa/) -- [Data Sub-Processors](https://getjetstream.app/subprocessors/) -- [Security and Privacy](https://getjetstream.app/privacy/) -- [Terms of Service](https://getjetstream.app/terms-of-service/) +- [Data Processing Agreement](https://getjetstream.app/dpa) +- [Data Sub-Processors](https://getjetstream.app/subprocessors) +- [Security and Privacy](https://getjetstream.app/privacy) +- [Terms of Service](https://getjetstream.app/terms-of-service) ## Web Application Security diff --git a/apps/docs/docs/team-management/team-management.mdx b/apps/docs/docs/team-management/team-management.mdx index 19fd33745..105578683 100644 --- a/apps/docs/docs/team-management/team-management.mdx +++ b/apps/docs/docs/team-management/team-management.mdx @@ -9,7 +9,7 @@ slug: /team-management :::info -This feature is available on our Team and Enterprise plans. [Learn more about our plans and pricing](https://getjetstream.app/pricing/). +This feature is available on our Team and Enterprise plans. [Learn more about our plans and pricing](https://getjetstream.app/pricing). ::: diff --git a/apps/docs/docusaurus.config.ts b/apps/docs/docusaurus.config.ts index 89dffd248..07f16b97c 100644 --- a/apps/docs/docusaurus.config.ts +++ b/apps/docs/docusaurus.config.ts @@ -117,15 +117,15 @@ const config: Config = { title: 'Legal', items: [ { - href: 'https://getjetstream.app/terms-of-service/', + href: 'https://getjetstream.app/terms-of-service', label: 'Terms of Service', }, { - href: 'https://getjetstream.app/subprocessors/', + href: 'https://getjetstream.app/subprocessors', label: 'Data Sub-Processors', }, { - href: 'https://getjetstream.app/privacy/', + href: 'https://getjetstream.app/privacy', label: 'Privacy Policy', }, ], diff --git a/apps/docs/src/shared-components/RequiresProPlan.tsx b/apps/docs/src/shared-components/RequiresProPlan.tsx index 04c205679..0c556d87a 100644 --- a/apps/docs/src/shared-components/RequiresProPlan.tsx +++ b/apps/docs/src/shared-components/RequiresProPlan.tsx @@ -6,7 +6,7 @@ export const RequiresProPlan = () => {

This feature is only available on the Pro plan.{' '} - + Upgrade . diff --git a/apps/jetstream-e2e/src/tests/authentication/external-auth/external-auth-logged-in.spec.ts b/apps/jetstream-e2e/src/tests/authentication/external-auth/external-auth-logged-in.spec.ts index bd7dfb3c0..19fa103ef 100644 --- a/apps/jetstream-e2e/src/tests/authentication/external-auth/external-auth-logged-in.spec.ts +++ b/apps/jetstream-e2e/src/tests/authentication/external-auth/external-auth-logged-in.spec.ts @@ -17,12 +17,12 @@ test.describe('Desktop / Web-Extension Authentication', () => { const deviceId = uuid(); const token = uuid(); - await page.goto(`/desktop-app/auth/?deviceId=${deviceId}&token=${token}`); + await page.goto(`/desktop-app/auth?deviceId=${deviceId}&token=${token}`); await expect(page.getByText('You are successfully authenticated, you can close this tab.')).toBeVisible(); }); test('Desktop Authentication - Missing query params', async ({ page, teamCreationUtils1User }) => { - await page.goto(`/desktop-app/auth/`); + await page.goto(`/desktop-app/auth`); await expect(page.getByText('Error communicating with desktop application, is the application open?')).toBeVisible(); }); @@ -80,7 +80,7 @@ test.describe('Desktop / Web-Extension Authentication', () => { // TODO: we don't have a way to test this currently since the extension is not installed test('Web Extension Authentication - Extension not installed', async ({ page, teamCreationUtils1User }) => { - await page.goto(`/web-extension/auth/`); + await page.goto(`/web-extension/auth`); await expect(page.getByText('Authentication in progress...')).toBeVisible(); }); @@ -354,13 +354,13 @@ test.describe('Desktop / Web-Extension Authentication - Not Logged In', () => { const deviceId = uuid(); const token = uuid(); - await page.goto(`/desktop-app/auth/?deviceId=${deviceId}&token=${token}`); - expect(page.url()).toContain('/auth/login/'); + await page.goto(`/desktop-app/auth?deviceId=${deviceId}&token=${token}`); + expect(page.url()).toContain('/auth/login'); }); test('Web Extension - Extension not installed', async ({ page }) => { - await page.goto(`/web-extension/auth/`); - expect(page.url()).toContain('/auth/login/'); + await page.goto(`/web-extension/auth`); + expect(page.url()).toContain('/auth/login'); }); }); @@ -379,18 +379,18 @@ test.describe('Desktop / Web-Extension Authentication - No Access', () => { const deviceId = uuid(); const token = uuid(); - await page.goto(`/desktop-app/auth/?deviceId=${deviceId}&token=${token}`); + await page.goto(`/desktop-app/auth?deviceId=${deviceId}&token=${token}`); await expect(page.getByText('You do not have a valid subscription to use the desktop application')).toBeVisible(); }); test('Desktop Authentication - Missing query params', async ({ page, newUser: _newUser }) => { - await page.goto(`/desktop-app/auth/`); + await page.goto(`/desktop-app/auth`); await expect(page.getByText('Error communicating with desktop application, is the application open?')).toBeVisible(); }); // TODO: we don't have a way to test this currently since the extension is not installed test('Web Extension - Extension not installed', async ({ page, newUser: _newUser }) => { - await page.goto(`/web-extension/auth/`); + await page.goto(`/web-extension/auth`); await expect(page.getByText('Authentication in progress...')).toBeVisible(); }); }); diff --git a/apps/jetstream-e2e/src/tests/authentication/team/team.spec.ts b/apps/jetstream-e2e/src/tests/authentication/team/team.spec.ts index 7aa57b8f8..228478717 100644 --- a/apps/jetstream-e2e/src/tests/authentication/team/team.spec.ts +++ b/apps/jetstream-e2e/src/tests/authentication/team/team.spec.ts @@ -234,7 +234,7 @@ test.describe('Team Dashboard', () => { await member1Page.reload(); expect(member1Page.url()).toContain('/auth/login'); - await authenticationPage.loginAndVerifyEmail(member1.user.email, member1.user.password, true, '**/auth/mfa-enroll/'); + await authenticationPage.loginAndVerifyEmail(member1.user.email, member1.user.password, true, '**/auth/mfa-enroll**'); await expect(member1Page.getByRole('heading', { name: 'Scan the QR code with your' })).toBeVisible(); await authenticationPage.enrollInOtp(member1.user.email); @@ -422,7 +422,7 @@ test.describe('Team Dashboard', () => { expect(page.url()).toContain('/auth/login'); const authenticationPage = new AuthenticationPage(page); - await authenticationPage.loginAndVerifyEmail(user1.email, user1.password, true, '**/auth/mfa-enroll/'); + await authenticationPage.loginAndVerifyEmail(user1.email, user1.password, true, '**/auth/mfa-enroll**'); await expect(page.getByRole('heading', { name: 'Scan the QR code with your' })).toBeVisible(); await authenticationPage.enrollInOtp(user1.email); @@ -590,7 +590,7 @@ test.describe('Team Dashboard', () => { await test.step('Navigating to app with abandoned enrollment redirects to enrollment page', async () => { const newPage = await userContext.newPage(); await newPage.goto('/app'); - expect(newPage.url()).toContain('/auth/mfa-enroll/'); + expect(newPage.url()).toContain('/auth/mfa-enroll'); await expect(newPage.getByRole('heading', { name: 'Scan the QR code with your' })).toBeVisible(); await newPage.close(); }); @@ -601,7 +601,7 @@ test.describe('Team Dashboard', () => { const auth = new AuthenticationPage(newPage); await auth.fillOutLoginForm(user.email, user.password); await delay(1000); // ensure session is initialized - await auth.verifyEmail(user.email, false, '**/auth/mfa-enroll/'); + await auth.verifyEmail(user.email, false, '**/auth/mfa-enroll**'); await expect(newPage.getByRole('heading', { name: 'Scan the QR code with your' })).toBeVisible(); await newPage.getByRole('link', { name: 'Logout' }).click(); await context.close(); @@ -613,7 +613,7 @@ test.describe('Team Dashboard', () => { const auth = new AuthenticationPage(newPage); await auth.fillOutLoginForm(user.email, user.password); await delay(1000); // ensure session is initialized - await auth.verifyEmail(user.email, false, '**/auth/mfa-enroll/'); + await auth.verifyEmail(user.email, false, '**/auth/mfa-enroll**'); await expect(newPage.getByRole('heading', { name: 'Scan the QR code with your' })).toBeVisible(); await auth.enrollInOtp(user.email); expect(newPage.url()).toContain('/app'); diff --git a/apps/jetstream/src/app/components/settings/Settings.tsx b/apps/jetstream/src/app/components/settings/Settings.tsx index e23c5ef9d..dea012c82 100644 --- a/apps/jetstream/src/app/components/settings/Settings.tsx +++ b/apps/jetstream/src/app/components/settings/Settings.tsx @@ -139,7 +139,7 @@ export const Settings = () => { await deleteUserProfile(reason); eraseCookies(); - window.location.href = '/goodbye/'; + window.location.href = '/goodbye'; } catch { // error deleting everything from server fireToast({ diff --git a/apps/landing/next.config.js b/apps/landing/next.config.js index 172b66a10..e7069a8e3 100644 --- a/apps/landing/next.config.js +++ b/apps/landing/next.config.js @@ -31,7 +31,7 @@ const nextConfig = { }, ]; }, - trailingSlash: true, + trailingSlash: false, nx: { // Set this to true if you would like to use SVGR // See: https://github.com/gregberge/svgr diff --git a/apps/landing/pages/privacy/index.tsx b/apps/landing/pages/privacy/index.tsx index 9c8c0a7b1..370ed2e3c 100644 --- a/apps/landing/pages/privacy/index.tsx +++ b/apps/landing/pages/privacy/index.tsx @@ -146,7 +146,7 @@ export default function Page() {

Refer to our{' '} - + data sub-processors {' '} for information about our vendors. diff --git a/apps/landing/utils/environment.ts b/apps/landing/utils/environment.ts index e5dfb4229..eeac1f094 100644 --- a/apps/landing/utils/environment.ts +++ b/apps/landing/utils/environment.ts @@ -31,8 +31,8 @@ export const ROUTES = { }, AUTH: { _root_path: '/auth/', - login: '/auth/login/', - signup: '/auth/signup/', + login: '/auth/login', + signup: '/auth/signup', resetPassword: '/auth/password-reset', resetPasswordVerify: '/auth/password-reset/verify', verify: `/auth/verify`, diff --git a/libs/shared/ui-router/src/lib/ui-router.ts b/libs/shared/ui-router/src/lib/ui-router.ts index 3e654d2ec..5a216b9b3 100644 --- a/libs/shared/ui-router/src/lib/ui-router.ts +++ b/libs/shared/ui-router/src/lib/ui-router.ts @@ -63,7 +63,7 @@ export const APP_ROUTES: RouteMap = { DESCRIPTION: 'Welcome to Jetstream', }, DESKTOP_APPLICATION: { - ROUTE: 'https://getjetstream.app/desktop-app/', + ROUTE: 'https://getjetstream.app/desktop-app', SEARCH_PARAM: undefined, DOCS: 'https://docs.getjetstream.app/desktop-app', TITLE: 'Desktop Application', @@ -71,7 +71,7 @@ export const APP_ROUTES: RouteMap = { NEW_UNTIL: new Date(2025, 9, 31, 23, 59, 59).getTime(), // October 31, 2025 }, BROWSER_EXTENSION: { - ROUTE: 'https://getjetstream.app/browser-extensions/', + ROUTE: 'https://getjetstream.app/browser-extensions', SEARCH_PARAM: undefined, DOCS: 'https://docs.getjetstream.app/browser-extension', TITLE: 'Browser Extension', diff --git a/libs/test/e2e-utils/src/lib/pageObjectModels/AuthenticationPage.model.ts b/libs/test/e2e-utils/src/lib/pageObjectModels/AuthenticationPage.model.ts index 564c04fe0..70387a10c 100644 --- a/libs/test/e2e-utils/src/lib/pageObjectModels/AuthenticationPage.model.ts +++ b/libs/test/e2e-utils/src/lib/pageObjectModels/AuthenticationPage.model.ts @@ -14,11 +14,11 @@ export class AuthenticationPage { readonly page: Page; readonly routes = { - signup: (wildcard = false) => `/auth/signup/${wildcard ? '*' : ''}`, - login: (wildcard = false) => `/auth/login/${wildcard ? '*' : ''}`, - passwordReset: (wildcard = false) => `/auth/password-reset/${wildcard ? '*' : ''}`, - passwordResetVerify: (wildcard = false) => `/auth/password-reset/verify/${wildcard ? '*' : ''}`, - mfaVerify: (wildcard = false) => `/auth/verify/${wildcard ? '*' : ''}`, + signup: (wildcard = false) => `/auth/signup${wildcard ? '**' : ''}`, + login: (wildcard = false) => `/auth/login${wildcard ? '**' : ''}`, + passwordReset: (wildcard = false) => `/auth/password-reset${wildcard ? '**' : ''}`, + passwordResetVerify: (wildcard = false) => `/auth/password-reset/verify${wildcard ? '**' : ''}`, + mfaVerify: (wildcard = false) => `/auth/verify${wildcard ? '**' : ''}`, } as const; readonly signInFromHomePageButton: Locator; diff --git a/scripts/stripe-update-billing-portal-config.mjs b/scripts/stripe-update-billing-portal-config.mjs index d1b58d58f..4cdf3189d 100644 --- a/scripts/stripe-update-billing-portal-config.mjs +++ b/scripts/stripe-update-billing-portal-config.mjs @@ -90,8 +90,8 @@ async function upsertProBillingPortal(existingPortalId, proPriceIds, proProductI // active: true, business_profile: { headline: 'Jetstream partners with Stripe for billing', - privacy_policy_url: 'https://getjetstream.app/privacy/', - terms_of_service_url: 'https://getjetstream.app/terms-of-service/', + privacy_policy_url: 'https://getjetstream.app/privacy', + terms_of_service_url: 'https://getjetstream.app/terms-of-service', }, default_return_url: null, features: { @@ -160,8 +160,8 @@ async function upsertTeamBillingPortal(existingPortalId, teamPriceIds, teamProdu // active: true, business_profile: { headline: 'Jetstream partners with Stripe for billing', - privacy_policy_url: 'https://getjetstream.app/privacy/', - terms_of_service_url: 'https://getjetstream.app/terms-of-service/', + privacy_policy_url: 'https://getjetstream.app/privacy', + terms_of_service_url: 'https://getjetstream.app/terms-of-service', }, default_return_url: null, features: { @@ -217,8 +217,8 @@ async function upsertManualBillingPortal(existingPortalId) { // active: true, business_profile: { headline: 'Jetstream partners with Stripe for billing', - privacy_policy_url: 'https://getjetstream.app/privacy/', - terms_of_service_url: 'https://getjetstream.app/terms-of-service/', + privacy_policy_url: 'https://getjetstream.app/privacy', + terms_of_service_url: 'https://getjetstream.app/terms-of-service', }, default_return_url: null, features: {