Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions packages/auth/src/Auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,45 @@ describe('Auth', () => {
);
});

it('forwards the email as login_hint for direct email login (PLT-1666)', async () => {
const mockOidcUser = {
id_token: 'token',
access_token: 'access',
refresh_token: 'refresh',
expired: false,
profile: { sub: 'user-123', email: 'test@example.com', nickname: 'tester' },
};

(decodeJwtPayload as jest.Mock).mockReturnValue({
username: 'username123',
passport: undefined,
});

mockUserManager.signinPopup.mockResolvedValue(mockOidcUser);

const auth = Object.create(Auth.prototype) as Auth;
(auth as any).userManager = mockUserManager;
(auth as any).config = {
popupOverlayOptions: { disableHeadlessLoginPromptOverlay: true },
};
getDetailMock.mockReturnValue('runtime-id-value');

await (auth as any).loginWithPopup({
directLoginMethod: 'email',
email: 'test@example.com',
});

expect(mockUserManager.signinPopup).toHaveBeenCalledWith(
expect.objectContaining({
extraQueryParams: expect.objectContaining({
direct: 'email',
email: 'test@example.com',
login_hint: 'test@example.com',
}),
}),
);
});

it('rejects when signinPopup rejects', async () => {
const error = new Error('Authentication failed');
mockUserManager.signinPopup.mockRejectedValue(error);
Expand Down
6 changes: 6 additions & 0 deletions packages/auth/src/Auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,12 @@ export class Auth {
if (emailValue) {
params.direct = directLoginOptions.directLoginMethod;
params.email = emailValue;
// Standard OIDC login_hint so the hosted (Ory/Hydra) login path also
// receives the email: Hydra does not relay the custom `email` param to
// the login UI, but it does relay login_hint onto the login request, so
// the branded page can resume at the OTP screen (PLT-1666). The Auth0
// path keeps using `email` above and is unaffected.
params.login_hint = emailValue;
}
} else {
params.direct = directLoginOptions.directLoginMethod;
Expand Down
5 changes: 5 additions & 0 deletions packages/auth/src/login/standalone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,11 @@ async function buildAuthorizationUrl(
if (directLoginOptions.email) {
url.searchParams.set('direct', 'email');
url.searchParams.set('email', directLoginOptions.email);
// Standard OIDC login_hint so the hosted (Ory/Hydra) login path receives the
// email too — Hydra relays login_hint onto the login request (the custom
// `email` param is dropped), letting the branded page resume at the OTP
// screen (PLT-1666). The Auth0 path keeps using `email` and is unaffected.
url.searchParams.set('login_hint', directLoginOptions.email);
}
} else {
url.searchParams.set('direct', directLoginOptions.directLoginMethod);
Expand Down
Loading