Skip to content

Commit 16207c3

Browse files
committed
fix: Explicitly guard restricted modes and username sign in
1 parent 45b2b6d commit 16207c3

2 files changed

Lines changed: 62 additions & 3 deletions

File tree

packages/ui/src/components/SignIn/SignInStart.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,11 +375,17 @@ function SignInStartInternal(): JSX.Element {
375375
} as any);
376376
}
377377
try {
378-
// Sign up if missing sign-in-or-sign-up flows do not currently support password
379-
// sign in, since this is not enumeration-safe.
378+
// Sign up if missing sign-in-or-sign-up flows only support public sign-up
379+
// instances and identifiers that can be verified out-of-band.
380380
const hasPassword = fields.some(f => f.name === 'password' && !!f.value);
381+
const signUpAttribute = getSignUpAttributeFromIdentifier(identifierField);
382+
const supportsSignUpIfMissing =
383+
signUpAttribute !== 'username' && userSettings.signUp.mode === SIGN_UP_MODES.PUBLIC;
381384
const shouldSignUpIfMissing =
382-
isCombinedFlow && userSettings.attackProtection.enumeration_protection.enabled && !hasPassword;
385+
isCombinedFlow &&
386+
userSettings.attackProtection.enumeration_protection.enabled &&
387+
supportsSignUpIfMissing &&
388+
!hasPassword;
383389

384390
const res = await safePasswordSignInForEnterpriseSSOInstance(
385391
signIn.create({

packages/ui/src/components/SignIn/__tests__/SignInStart.test.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,59 @@ describe('SignInStart', () => {
679679
);
680680
});
681681
});
682+
683+
it('does not pass signUpIfMissing when sign-up mode is restricted', async () => {
684+
const { wrapper, fixtures, props } = await createFixtures(f => {
685+
f.withEmailAddress();
686+
f.withEnumerationProtection();
687+
f.withRestrictedMode();
688+
});
689+
props.setProps({ withSignUp: true });
690+
fixtures.signIn.create.mockReturnValueOnce(Promise.resolve({ status: 'needs_first_factor' } as SignInResource));
691+
const { userEvent } = render(<SignInStart />, { wrapper });
692+
await userEvent.type(screen.getByLabelText(/email address/i), 'hello@clerk.com');
693+
await userEvent.click(screen.getByText('Continue'));
694+
expect(fixtures.signIn.create).toHaveBeenCalledWith(
695+
expect.not.objectContaining({
696+
signUpIfMissing: true,
697+
}),
698+
);
699+
});
700+
701+
it('does not pass signUpIfMissing when sign-up mode is waitlist', async () => {
702+
const { wrapper, fixtures, props } = await createFixtures(f => {
703+
f.withEmailAddress();
704+
f.withEnumerationProtection();
705+
f.withWaitlistMode();
706+
});
707+
props.setProps({ withSignUp: true });
708+
fixtures.signIn.create.mockReturnValueOnce(Promise.resolve({ status: 'needs_first_factor' } as SignInResource));
709+
const { userEvent } = render(<SignInStart />, { wrapper });
710+
await userEvent.type(screen.getByLabelText(/email address/i), 'hello@clerk.com');
711+
await userEvent.click(screen.getByText('Continue'));
712+
expect(fixtures.signIn.create).toHaveBeenCalledWith(
713+
expect.not.objectContaining({
714+
signUpIfMissing: true,
715+
}),
716+
);
717+
});
718+
719+
it('does not pass signUpIfMissing when the identifier is a username', async () => {
720+
const { wrapper, fixtures, props } = await createFixtures(f => {
721+
f.withUsername();
722+
f.withEnumerationProtection();
723+
});
724+
props.setProps({ withSignUp: true });
725+
fixtures.signIn.create.mockReturnValueOnce(Promise.resolve({ status: 'needs_first_factor' } as SignInResource));
726+
const { userEvent } = render(<SignInStart />, { wrapper });
727+
await userEvent.type(screen.getByLabelText(/username/i), 'hello');
728+
await userEvent.click(screen.getByText('Continue'));
729+
expect(fixtures.signIn.create).toHaveBeenCalledWith(
730+
expect.not.objectContaining({
731+
signUpIfMissing: true,
732+
}),
733+
);
734+
});
682735
});
683736

684737
describe('ticket flow', () => {

0 commit comments

Comments
 (0)