Skip to content
Closed
35 changes: 35 additions & 0 deletions packages/angular/src/lib/auth/forms/sign-in-auth-form.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,41 @@ describe("<fui-sign-in-auth-form />", () => {
expect(screen.getByText(errorMessage)).toBeInTheDocument();
});

it("should show provider guidance for auth/invalid-password", async () => {
const guidanceMessage =
"This account may have been created using a different sign-in method. " +
"Try signing in with another method or reset your password.";
const error = new mockFirebaseUIError("Invalid credentials");
error.code = "auth/invalid-password";
mockSignInWithEmailAndPassword.mockRejectedValue(error);

const { fixture } = await render(SignInAuthFormComponent, {
imports: [
CommonModule,
SignInAuthFormComponent,
TanStackField,
TanStackAppField,
FormInputComponent,
FormSubmitComponent,
FormErrorMessageComponent,
FormActionComponent,
PoliciesComponent,
],
});

const component = fixture.componentInstance;

component.form.setFieldValue("email", "test@example.com");
component.form.setFieldValue("password", "wrongpassword");
fixture.detectChanges();

await component.form.handleSubmit();
await fixture.whenStable();
fixture.detectChanges();

expect(screen.getByText(guidanceMessage)).toBeInTheDocument();
});

it("should handle unknown errors and display generic error message", async () => {
mockSignInWithEmailAndPassword.mockRejectedValue(new Error("Network error"));

Expand Down
7 changes: 7 additions & 0 deletions packages/angular/src/lib/auth/forms/sign-in-auth-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ export class SignInAuthFormComponent {
noAccountLabel = injectTranslation("prompts", "noAccount");
signUpLabel = injectTranslation("labels", "signUp");
unknownErrorLabel = injectTranslation("errors", "unknownError");
providerMismatchGuidanceMessage =
"This account may have been created using a different sign-in method. " +
"Try signing in with another method or reset your password.";

/** Event emitter for forgot password action. */
forgotPassword = input<EventEmitter<void>>();
Expand Down Expand Up @@ -137,6 +140,10 @@ export class SignInAuthFormComponent {
return;
} catch (error) {
if (error instanceof FirebaseUIError) {
if (error.code === "auth/invalid-password") {
return this.providerMismatchGuidanceMessage;
}

return error.message;
}

Expand Down
23 changes: 23 additions & 0 deletions packages/react/src/auth/forms/sign-in-auth-form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { signInWithEmailAndPassword } from "@firebase-oss/ui-core";
import { createFirebaseUIProvider, createMockUI } from "~/tests/utils";
import { registerLocale } from "@firebase-oss/ui-translations";
import type { UserCredential } from "firebase/auth";
import { FirebaseError } from "firebase/app";
import { FirebaseUIProvider } from "~/context";

vi.mock("firebase/auth", async () => {
Expand Down Expand Up @@ -115,6 +116,28 @@ describe("useSignInAuthFormAction", () => {

expect(signInWithEmailAndPasswordMock).toHaveBeenCalledWith(mockUI.get(), "test@example.com", "password123");
});

it("should throw guidance message when FirebaseUIError code is auth/invalid-password", async () => {
const { FirebaseUIError } = await import("@firebase-oss/ui-core");
const signInWithEmailAndPasswordMock = vi.mocked(signInWithEmailAndPassword);
const mockUI = createMockUI();

signInWithEmailAndPasswordMock.mockRejectedValue(
new FirebaseUIError(mockUI.get(), new FirebaseError("auth/invalid-password", "Invalid password"))
);

const { result } = renderHook(() => useSignInAuthFormAction(), {
wrapper: ({ children }) => createFirebaseUIProvider({ children, ui: mockUI }),
});

await expect(async () => {
await act(async () => {
await result.current({ email: "test@example.com", password: "password123" });
});
}).rejects.toThrow(
"This account may have been created using a different sign-in method. Try signing in with another method or reset your password."
);
});
});

describe("useSignInAuthForm", () => {
Expand Down
9 changes: 9 additions & 0 deletions packages/react/src/auth/forms/sign-in-auth-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ export function useSignInAuthFormAction() {
return await signInWithEmailAndPassword(ui, email, password);
} catch (error) {
if (error instanceof FirebaseUIError) {
// Improve UX for users who previously signed up via OAuth and
// attempt Email/Password sign-in.
if (error.code === "auth/invalid-password") {
Comment thread
MichaelVerdon marked this conversation as resolved.
throw new Error(
"This account may have been created using a different sign-in method. " +
"Try signing in with another method or reset your password."
);
}

throw new Error(error.message);
}

Expand Down
13 changes: 2 additions & 11 deletions packages/react/src/components/form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,7 @@ describe("form export", () => {
render(
<hook.AppForm>
<hook.AppField name="foo">
{(field) => (
<field.Input
label="Foo"
action={
<button type="button" data-testid="test-action">
Action
</button>
}
/>
)}
{(field) => <field.Input label="Foo" action={<button data-testid="test-action">Action</button>} />}
</hook.AppField>
</hook.AppForm>
);
Expand Down Expand Up @@ -190,7 +181,7 @@ describe("form export", () => {
<hook.AppForm>
<hook.AppField
validators={{
onSubmit: () => {
onSubmitAsync: async () => {
return "error!";
},
}}
Expand Down