From 01a459be375ad1ecc5892259a144f20bf3c85242 Mon Sep 17 00:00:00 2001 From: AshCorr Date: Fri, 19 Jun 2026 14:09:35 +0100 Subject: [PATCH 1/2] feat: Allow specifying first and last name during registration/signin --- .../e2e/sign_in_passcode_active_user.spec.ts | 48 +++++++++++++++++++ src/server/controllers/signInControllers.ts | 4 +- src/server/lib/okta/idx/interact.ts | 2 + src/server/lib/okta/openid-connect.ts | 2 + src/server/routes/oauth.ts | 10 ++++ src/server/routes/register.ts | 9 +++- 6 files changed, 73 insertions(+), 2 deletions(-) diff --git a/playwright/tests/e2e/sign_in_passcode_active_user.spec.ts b/playwright/tests/e2e/sign_in_passcode_active_user.spec.ts index 952a05551..fbd369781 100644 --- a/playwright/tests/e2e/sign_in_passcode_active_user.spec.ts +++ b/playwright/tests/e2e/sign_in_passcode_active_user.spec.ts @@ -134,6 +134,54 @@ test.describe('Sign In flow, with passcode', () => { expect(updatedUser.profile.emailValidated).toBe(true); }); + test('should sign in with passcode - preserve lastName & firstName', async ({ + request, + page, + }) => { + const { emailAddress } = await createTestUser(request, { + isUserEmailValidated: true, + }); + + await page.goto(`/signin?appClientId=${appClientId}`); + const element = await page.locator('input[name=email]').elementHandle(); + + // TODO: Use real form elements once implemented. + await element?.evaluate((element) => { + /* eslint-disable functional/immutable-data */ + const firstNameInput = document.createElement('input'); + firstNameInput.name = 'firstName'; + firstNameInput.value = 'TestFirstName'; + element.append(firstNameInput); + const secondNameInput = document.createElement('input'); + secondNameInput.name = 'secondName'; + secondNameInput.value = 'TestLastName'; + element.append(secondNameInput); + /* eslint-enable functional/immutable-data */ + }); + + await page.locator('input[name=email]').fill(emailAddress); + + const timeRequestWasMade = new Date(); + await page.locator('[data-cy="main-form-submit-button"]').click(); + + await expect(page).toHaveURL(/\/passcode/); + await expect(page.getByText('Enter your one-time code')).toBeVisible(); + + const { codes } = await checkForEmailAndGetDetails( + emailAddress, + timeRequestWasMade, + ); + + const code = codes?.[0].value; + await page.locator('input[name=code]').fill(code!); + + await expect(page).toHaveURL(/\/welcome\/existing/); + + const updatedUser = await getTestOktaUser(request, emailAddress); + expect(updatedUser.profile.firstName).toBe('TestFirstName'); + expect(updatedUser.profile.lastName).toBe('TestLastName'); + }); + test('selects password option to sign in from initial sign in page', async ({ request, page, diff --git a/src/server/controllers/signInControllers.ts b/src/server/controllers/signInControllers.ts index dcb9b2026..c6c476df5 100644 --- a/src/server/controllers/signInControllers.ts +++ b/src/server/controllers/signInControllers.ts @@ -201,7 +201,7 @@ export const oktaIdxApiSignInPasscodeController = async ({ loopDetectionFlag?: boolean; confirmationPagePath?: StartIdxFlowParams['authorizationCodeFlowOptions']['confirmationPagePath']; }): Promise => { - const { email = '' } = req.body; + const { email = '', firstName, secondName } = req.body; const referrerUrl = new URL(req.body.ref); const referrerPath = referrerUrl.pathname; const emailSentPage = referrerPath.includes('/iframed') @@ -249,6 +249,8 @@ export const oktaIdxApiSignInPasscodeController = async ({ extraData: { flow: 'sign-in-passcode', appLabel: res.locals.appLabel, + firstName, + lastName: secondName, }, }, }); diff --git a/src/server/lib/okta/idx/interact.ts b/src/server/lib/okta/idx/interact.ts index da9a48b7e..b3c1c4dac 100644 --- a/src/server/lib/okta/idx/interact.ts +++ b/src/server/lib/okta/idx/interact.ts @@ -95,6 +95,8 @@ export const interact = async ( codeVerifier, flow: extraData?.flow, appLabel: extraData?.appLabel, + firstName: extraData?.firstName, + lastName: extraData?.lastName, }, ); setAuthorizationStateCookie(authState, res); diff --git a/src/server/lib/okta/openid-connect.ts b/src/server/lib/okta/openid-connect.ts index 78a152104..27d4c7d5d 100644 --- a/src/server/lib/okta/openid-connect.ts +++ b/src/server/lib/okta/openid-connect.ts @@ -50,6 +50,8 @@ export interface AuthorizationState { // used to track the flow the user is in for basic metrics/analytics flow?: UserFlow; // password reset, and email verification flows outside of create account appLabel?: string; // used to track the app used to start the flow + firstName?: string; // used to persist the first name during the signin flow, so that we can set it after login + lastName?: string; // used to persist the last name during the signin flow, so that we can set it after login }; } diff --git a/src/server/routes/oauth.ts b/src/server/routes/oauth.ts index dbfda9d95..92afb3f43 100644 --- a/src/server/routes/oauth.ts +++ b/src/server/routes/oauth.ts @@ -426,6 +426,16 @@ const authenticationHandler = async ( } } + // Update the users first and last name if they exist in the authState + if (authState.data?.firstName || authState.data?.lastName) { + await updateUser(sub, { + profile: { + firstName: authState.data.firstName, + lastName: authState.data.lastName, + }, + }); + } + // clear any existing oauth application cookies if they exist checkAndDeleteOAuthTokenCookies(req, res); diff --git a/src/server/routes/register.ts b/src/server/routes/register.ts index 6b8e42a2a..1e9e31e3a 100644 --- a/src/server/routes/register.ts +++ b/src/server/routes/register.ts @@ -368,7 +368,12 @@ const oktaIdxCreateAccountOrSignIn = async ( req: Request, res: ResponseWithRequestState, ) => { - const { email = '', isCombinedSigninAndRegisterFlow = false } = req.body; + const { + email = '', + firstName, + secondName, + isCombinedSigninAndRegisterFlow = false, + } = req.body; const { queryParams: { appClientId, clientId }, @@ -406,6 +411,8 @@ const oktaIdxCreateAccountOrSignIn = async ( extraData: { flow: 'create-account', appLabel: res.locals.appLabel, + firstName, + lastName: secondName, }, }, consents, From 17de85c346d55b474636e763f660c0eca17274b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Jun 2026 09:40:50 +0000 Subject: [PATCH 2/2] fix: add error handling around updateUser call and tidy up test --- .../e2e/sign_in_passcode_active_user.spec.ts | 6 +++--- src/server/routes/oauth.ts | 16 ++++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/playwright/tests/e2e/sign_in_passcode_active_user.spec.ts b/playwright/tests/e2e/sign_in_passcode_active_user.spec.ts index fbd369781..414d8ba8c 100644 --- a/playwright/tests/e2e/sign_in_passcode_active_user.spec.ts +++ b/playwright/tests/e2e/sign_in_passcode_active_user.spec.ts @@ -134,7 +134,7 @@ test.describe('Sign In flow, with passcode', () => { expect(updatedUser.profile.emailValidated).toBe(true); }); - test('should sign in with passcode - preserve lastName & firstName', async ({ + test('should sign in with passcode - preserve firstName & lastName', async ({ request, page, }) => { @@ -161,7 +161,7 @@ test.describe('Sign In flow, with passcode', () => { await page.locator('input[name=email]').fill(emailAddress); - const timeRequestWasMade = new Date(); + const passcodeRequestTime = new Date(); await page.locator('[data-cy="main-form-submit-button"]').click(); await expect(page).toHaveURL(/\/passcode/); @@ -169,7 +169,7 @@ test.describe('Sign In flow, with passcode', () => { const { codes } = await checkForEmailAndGetDetails( emailAddress, - timeRequestWasMade, + passcodeRequestTime, ); const code = codes?.[0].value; diff --git a/src/server/routes/oauth.ts b/src/server/routes/oauth.ts index 92afb3f43..446e868ff 100644 --- a/src/server/routes/oauth.ts +++ b/src/server/routes/oauth.ts @@ -428,12 +428,16 @@ const authenticationHandler = async ( // Update the users first and last name if they exist in the authState if (authState.data?.firstName || authState.data?.lastName) { - await updateUser(sub, { - profile: { - firstName: authState.data.firstName, - lastName: authState.data.lastName, - }, - }); + try { + await updateUser(sub, { + profile: { + firstName: authState.data.firstName, + lastName: authState.data.lastName, + }, + }); + } catch (error) { + logger.error('OAuth authentication: failed to update user name', error); + } } // clear any existing oauth application cookies if they exist