fix(login): persona click signs in directly + friendly error copy (#3)
This commit was merged in pull request #3.
This commit is contained in:
+1
-1
@@ -56,7 +56,7 @@ describe("api client", () => {
|
||||
]);
|
||||
const r = await api.ping();
|
||||
expect(r.ok).toBe(false);
|
||||
expect(r.reason).toMatch(/401/);
|
||||
expect(r.reason).toMatch(/rejected|401|sign-in/i);
|
||||
});
|
||||
|
||||
it("workItems() returns the items array", async () => {
|
||||
|
||||
+11
-5
@@ -19,6 +19,14 @@ const DEFAULT_CONFIG: ApiConfig = {
|
||||
|
||||
const TOKEN_KEY = "fm.mc.token.v1";
|
||||
|
||||
function friendlyAuthError(status: number, kind: string): string {
|
||||
if (status === 502 || status === 504) return `${kind} unavailable: the EA2 backend is not reachable from this environment right now. Try again shortly.`;
|
||||
if (status === 503) return `${kind} unavailable: the auth service is temporarily down. Try again shortly.`;
|
||||
if (status === 404) return `${kind} is not wired to this environment yet.`;
|
||||
if (status === 401 || status === 403) return `${kind} rejected: check the email and password.`;
|
||||
return `${kind} failed (HTTP ${status}). Try again or contact the operator on call.`;
|
||||
}
|
||||
|
||||
let inflightLogin: Promise<string> | null = null;
|
||||
|
||||
async function login(cfg: ApiConfig, signal?: AbortSignal): Promise<string> {
|
||||
@@ -30,7 +38,7 @@ async function login(cfg: ApiConfig, signal?: AbortSignal): Promise<string> {
|
||||
body: JSON.stringify({ email: cfg.email }),
|
||||
signal,
|
||||
});
|
||||
if (!r.ok) throw new Error(`dev-login ${r.status}`);
|
||||
if (!r.ok) throw new Error(friendlyAuthError(r.status, "Developer sign-in"));
|
||||
const body = (await r.json()) as { access_token: string };
|
||||
sessionStorage.setItem(TOKEN_KEY, body.access_token);
|
||||
return body.access_token;
|
||||
@@ -213,8 +221,7 @@ export const api = {
|
||||
};
|
||||
const r = await instrumentedFetch("POST", `${this.config.baseUrl}/api/v1/auth/login`, init, { email, password });
|
||||
if (!r.ok) {
|
||||
const text = await r.text().catch(() => "");
|
||||
throw new Error(`Login failed: ${r.status} ${text.slice(0, 100)}`);
|
||||
throw new Error(friendlyAuthError(r.status, "Sign-in"));
|
||||
}
|
||||
const data = await r.json() as { access_token: string; refresh_token?: string };
|
||||
this.setBearer(data.access_token);
|
||||
@@ -230,8 +237,7 @@ export const api = {
|
||||
};
|
||||
const r = await instrumentedFetch("POST", `${this.config.baseUrl}/api/v1/auth/dev-login`, init, { email });
|
||||
if (!r.ok) {
|
||||
const text = await r.text().catch(() => "");
|
||||
throw new Error(`Dev-login failed: ${r.status} ${text.slice(0, 100)}`);
|
||||
throw new Error(friendlyAuthError(r.status, "Developer sign-in"));
|
||||
}
|
||||
const data = await r.json() as { access_token: string; refresh_token?: string };
|
||||
this.setBearer(data.access_token);
|
||||
|
||||
+15
-2
@@ -206,9 +206,22 @@ export default function Login() {
|
||||
key={p.key}
|
||||
type="button"
|
||||
className="persona-chip"
|
||||
onClick={() => setEmail(p.email)}
|
||||
onClick={async () => {
|
||||
setEmail(p.email);
|
||||
if (!devLoginEnabled) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
await loginAs(p.email, undefined, { method: "dev" });
|
||||
setScene("landing");
|
||||
} catch (err) {
|
||||
setError((err as Error).message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}}
|
||||
disabled={loading}
|
||||
title={p.email}
|
||||
title={`Sign in as ${p.title}`}
|
||||
>
|
||||
{p.title}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user