diff --git a/cypress/integration/ete-okta/registration_1.2.cy.ts b/cypress/integration/ete-okta/registration_1.2.cy.ts
index 4acdb42afb..67c9afb1ea 100644
--- a/cypress/integration/ete-okta/registration_1.2.cy.ts
+++ b/cypress/integration/ete-okta/registration_1.2.cy.ts
@@ -138,7 +138,10 @@ describe('Registration flow - Split 1/2', () => {
cy.get('input[name="secondName"]').type('Last Name');
cy.get('input[name="password"]').type(randomPassword());
cy.get('button[type="submit"]').click();
- cy.url().should('contain', 'https://m.code.dev-theguardian.com/');
+ cy.url().should('contain', '/welcome/app/complete');
+ cy.url().should('contain', '/welcome/app/complete');
+ cy.contains(unregisteredEmail);
+ cy.contains('Guardian app');
// test the registration platform is set correctly
cy.getTestOktaUser(unregisteredEmail).then((oktaUser) => {
diff --git a/cypress/integration/ete-okta/registration_2.6.cy.ts b/cypress/integration/ete-okta/registration_2.6.cy.ts
index 2429c8e550..e2eaca7ed1 100644
--- a/cypress/integration/ete-okta/registration_2.6.cy.ts
+++ b/cypress/integration/ete-okta/registration_2.6.cy.ts
@@ -669,14 +669,17 @@ describe('Registration flow - Split 2/2', () => {
/welcome\/([^"]*)/,
).then(({ body, token }) => {
expect(body).to.have.string('Complete registration');
+ const appClientId = Cypress.env('OKTA_ANDROID_CLIENT_ID');
// manually adding the app prefix to the token
- cy.visit(`/welcome/al_${token}`);
+ cy.visit(`/welcome/al_${token}&appClientId=${appClientId}`);
cy.contains('Save and continue');
cy.get('input[name="password"]').type(randomPassword());
cy.get('button[type="submit"]').click();
- cy.url().should('contain', 'https://m.code.dev-theguardian.com/');
+ cy.url().should('contain', '/welcome/app/complete');
+ cy.contains(unregisteredEmail);
+ cy.contains('Guardian app');
});
});
});
diff --git a/src/client/pages/ReturnToApp.stories.tsx b/src/client/pages/ReturnToApp.stories.tsx
new file mode 100644
index 0000000000..2ae687a143
--- /dev/null
+++ b/src/client/pages/ReturnToApp.stories.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { Meta } from '@storybook/react';
+
+import { ReturnToApp } from './ReturnToApp';
+
+export default {
+ title: 'Pages/ReturnToApp',
+ component: ReturnToApp,
+ parameters: { layout: 'fullscreen' },
+} as Meta;
+
+export const Default = () => (
+
+);
+
+export const NoEmail = () => ;
+
+export const NoApp = () => ;
+
+export const NoEmailOrApp = () => ;
diff --git a/src/client/pages/ReturnToApp.tsx b/src/client/pages/ReturnToApp.tsx
new file mode 100644
index 0000000000..405fa9d228
--- /dev/null
+++ b/src/client/pages/ReturnToApp.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { MainLayout } from '@/client/layouts/Main';
+import { MainBodyText } from '@/client/components/MainBodyText';
+
+type ReturnToAppProps = {
+ email?: string;
+ appName?: string;
+};
+
+export const ReturnToApp = ({ email, appName: app }: ReturnToAppProps) => (
+
+
+ You have finished creating your Guardian account
+ {email ? (
+ <>
+ : {email}
+ >
+ ) : (
+ ''
+ )}
+ .
+
+
+ Open the {app ? app : 'Guardian'} app and sign in with your new
+ account.
+
+
+);
diff --git a/src/client/pages/ReturnToAppPage.tsx b/src/client/pages/ReturnToAppPage.tsx
new file mode 100644
index 0000000000..ca111f2d27
--- /dev/null
+++ b/src/client/pages/ReturnToAppPage.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import useClientState from '@/client/lib/hooks/useClientState';
+import { ReturnToApp } from '@/client/pages/ReturnToApp';
+
+export const ReturnToAppPage = () => {
+ const clientState = useClientState();
+ const { pageData = {} } = clientState;
+ const { email, appName } = pageData;
+
+ return ;
+};
diff --git a/src/client/routes.tsx b/src/client/routes.tsx
index afba83e185..dd707be62e 100644
--- a/src/client/routes.tsx
+++ b/src/client/routes.tsx
@@ -41,6 +41,7 @@ import { DeleteAccountEmailPasswordValidationPage } from './pages/DeleteAccountE
import { DeleteAccountCompletePage } from '@/client/pages/DeleteAccountCompletePage';
import { RegisterWithEmailPage } from './pages/RegisterWithEmailPage';
import { WelcomeSocialPage } from './pages/WelcomeSocialPage';
+import { ReturnToAppPage } from './pages/ReturnToAppPage';
export type RoutingConfig = {
clientState: ClientState;
@@ -257,6 +258,10 @@ const routes: Array<{
path: '/delete/email-sent',
element: ,
},
+ {
+ path: '/welcome/app/complete',
+ element: ,
+ },
];
interface Props {
diff --git a/src/server/lib/middleware/requestState.ts b/src/server/lib/middleware/requestState.ts
index 217b77a1d7..dcec684956 100644
--- a/src/server/lib/middleware/requestState.ts
+++ b/src/server/lib/middleware/requestState.ts
@@ -50,6 +50,10 @@ const getRequestState = async (
// eslint-disable-next-line functional/no-let
let isNativeApp: IsNativeApp;
+ // it is also useful to know the app name
+ // eslint-disable-next-line functional/no-let
+ let appName;
+
try {
if (!!queryParams.appClientId) {
const app = await getApp(queryParams.appClientId);
@@ -61,6 +65,17 @@ const getRequestState = async (
} else if (label.startsWith('ios_')) {
isNativeApp = 'ios';
}
+
+ switch (label) {
+ case 'android_live_app':
+ case 'ios_live_app':
+ appName = 'Guardian';
+ appName = 'Guardian';
+ break;
+ case 'ios_feast_app':
+ appName = 'Guardian Feast';
+ break;
+ }
}
} catch (error) {
logger.error('Error getting app info in request state', error, {
@@ -74,6 +89,7 @@ const getRequestState = async (
geolocation: getGeolocationRegion(req),
returnUrl: queryParams.returnUrl,
isNativeApp,
+ appName,
},
globalMessage: {},
csrf: {
diff --git a/src/server/routes/oauth.ts b/src/server/routes/oauth.ts
index d49c505501..3dd8f4e1d1 100644
--- a/src/server/routes/oauth.ts
+++ b/src/server/routes/oauth.ts
@@ -317,17 +317,15 @@ const authenticationHandler = async (
// temporary fix: if the user registered on the app (the token will be prefixed),
// and instead of ending up back in the app but in a mobile browser instead,
- // where we don't want to show the onboarding,
- // for now we simply redirect them to the default return url when app prefix is set,
- // and the confirmation page is one of the consent pages, then we redirect to the default return url,
- // which is normally the guardian home page, by setting authState.confirmationPage to undefined
- // this will be fixed when we either use a deep link with a custom scheme, or passwordless OTP flow
+ // where we don't want to show the onboarding flow.
+ // We simply redirect them to a page telling them to return to app, when app prefix is set.
+ // This will be fixed when we either use the passcode registration flow.
if (
authState.data?.hasAppPrefix &&
consentPages.some((page) => page.path === authState.confirmationPage)
) {
// eslint-disable-next-line functional/immutable-data
- authState.confirmationPage = undefined;
+ authState.confirmationPage = '/welcome/app/complete';
}
const returnUrl = authState.confirmationPage
diff --git a/src/server/routes/welcome.ts b/src/server/routes/welcome.ts
index 2d4bbadaa3..8d76210aba 100644
--- a/src/server/routes/welcome.ts
+++ b/src/server/routes/welcome.ts
@@ -31,6 +31,24 @@ import { updateRegistrationPlatform } from '../lib/registrationPlatform';
const { okta } = getConfiguration();
+// temp return to app page for app users who get stuck in browser
+router.get(
+ '/welcome/app/complete',
+ loginMiddlewareOAuth,
+ (req: Request, res: ResponseWithRequestState) => {
+ const html = renderer('/welcome/app/complete', {
+ pageTitle: 'Welcome',
+ requestState: mergeRequestState(res.locals, {
+ pageData: {
+ // email is type unknown, but we know it's a string
+ email: res.locals.oauthState.idToken?.claims.email as string,
+ },
+ }),
+ });
+ return res.type('html').send(html);
+ },
+);
+
// consent page for post social registration - google
router.get(
'/welcome/google',
diff --git a/src/shared/model/ClientState.ts b/src/shared/model/ClientState.ts
index f82ab12771..4254e3278c 100644
--- a/src/shared/model/ClientState.ts
+++ b/src/shared/model/ClientState.ts
@@ -36,6 +36,7 @@ export interface PageData {
browserName?: string;
isNativeApp?: IsNativeApp;
accountManagementUrl?: string;
+ appName?: string;
// token
token?: string;
diff --git a/src/shared/model/Routes.ts b/src/shared/model/Routes.ts
index 6669810726..1edd39b5a9 100644
--- a/src/shared/model/Routes.ts
+++ b/src/shared/model/Routes.ts
@@ -68,6 +68,7 @@ export const ValidRoutePathsArray = [
'/verify-email', //this can be removed once Jobs has been migrated
'/welcome',
'/welcome/:token',
+ '/welcome/app/complete',
'/welcome/complete',
'/welcome/email-sent',
'/welcome/expired',