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',