Skip to content

Commit

Permalink
Show error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
tom-sherman committed Sep 11, 2024
1 parent 1947b03 commit 9add1fe
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 46 deletions.
5 changes: 3 additions & 2 deletions packages/frontpage/app/(auth)/login/_lib/action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export async function loginAction(_prevStart: unknown, formData: FormData) {
const handle = formData.get("handle") as string;
const result = await signIn(handle);
if (result && "error" in result) {
// throw new Error(result.error);
console.error(result.error);
return {
error: `An error occured while signing in (${result.error})`,
};
}
}
60 changes: 35 additions & 25 deletions packages/frontpage/app/(auth)/login/_lib/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,44 @@ import { loginAction } from "./action";
import { Label } from "@/lib/components/ui/label";
import { Input } from "@/lib/components/ui/input";
import { Button } from "@/lib/components/ui/button";
// import { Alert } from "@/lib/components/ui/alert";
import { useSearchParams } from "next/navigation";
import { Alert, AlertDescription, AlertTitle } from "@/lib/components/ui/alert";
import { CrossCircledIcon } from "@radix-ui/react-icons";

export function LoginForm() {
const [_, action, isPending] = useActionState(loginAction, null);
const [state, action, isPending] = useActionState(loginAction, null);
const searchParams = useSearchParams();
const error = state?.error ?? searchParams.get("error");

return (
<form
className="space-y-6"
action={action}
onSubmit={(event) => {
event.preventDefault();
startTransition(() => {
action(new FormData(event.currentTarget));
});
}}
>
<div>
<Label htmlFor="handle">Handle</Label>
<Input id="handle" name="handle" required placeholder="example.com" />
</div>
<div>
<Button type="submit" className="w-full" disabled={isPending}>
Sign in
</Button>
{/* {state?.error ? (
<Alert variant="destructive">{state.error}</Alert>
) : null} */}
</div>
</form>
<>
<form
className="space-y-6"
action={action}
onSubmit={(event) => {
event.preventDefault();
startTransition(() => {
action(new FormData(event.currentTarget));
});
}}
>
<div>
<Label htmlFor="handle">Handle</Label>
<Input id="handle" name="handle" required placeholder="example.com" />
</div>
<div>
<Button type="submit" className="w-full" disabled={isPending}>
Sign in
</Button>
</div>
</form>
{error ? (
<Alert variant="destructive">
<CrossCircledIcon className="h-4 w-4" />
<AlertTitle>Login error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
) : null}
</>
);
}
2 changes: 1 addition & 1 deletion packages/frontpage/app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LoginForm } from "./_lib/form";

export default function Component() {
export default function LoginPage() {
return (
<div className="flex items-center justify-center min-h-screen px-4 sm:px-6 lg:px-8">
<div className="w-full max-w-md space-y-6">
Expand Down
14 changes: 7 additions & 7 deletions packages/frontpage/lib/auth-sign-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function signIn(handle: string) {
const did = await getDidFromHandleOrDid(handle);
if (!did) {
return {
error: "DID_NOT_FOUND",
error: "DID_NOT_FOUND" as const,
};
}

Expand All @@ -35,7 +35,7 @@ export async function signIn(handle: string) {
const authServerUrl = meta.data.authorization_servers?.[0];
if (!authServerUrl) {
return {
error: "NO_AUTH_SERVER",
error: "NO_AUTH_SERVER" as const,
};
}

Expand All @@ -51,7 +51,7 @@ export async function signIn(handle: string) {
const authorizationEndpiont = authServer.authorization_endpoint;
if (!authorizationEndpiont) {
return {
error: "NO_AUTHORIZATION_ENDPOINT",
error: "NO_AUTHORIZATION_ENDPOINT" as const,
};
}

Expand Down Expand Up @@ -110,7 +110,7 @@ export async function signIn(handle: string) {
const dpopNonce = parResponse.headers.get("DPoP-Nonce");
if (!dpopNonce) {
return {
error: "MISSING_PAR_DPOP_NONCE",
error: "MISSING_PAR_DPOP_NONCE" as const,
};
}
// Try again with new nonce
Expand All @@ -120,22 +120,22 @@ export async function signIn(handle: string) {
if (!parResponse.ok) {
console.error("PAR error: ", await parResponse.text());
return {
error: "FAILED_TO_PUSH_AUTHORIZATION_REQUEST",
error: "FAILED_TO_PUSH_AUTHORIZATION_REQUEST" as const,
};
}

const dpopNonce = parResponse.headers.get("DPoP-Nonce");

if (!dpopNonce) {
return {
error: "MISSING_PAR_DPOP_NONCE",
error: "MISSING_PAR_DPOP_NONCE" as const,
};
}

const parResult = oauthParResponseSchema.safeParse(await parResponse.json());
if (!parResult.success) {
return {
error: "INVALID_PAR_RESPONSE",
error: "INVALID_PAR_RESPONSE" as const,
cause: parResult.error,
};
}
Expand Down
43 changes: 32 additions & 11 deletions packages/frontpage/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
revocationRequest,
parseWwwAuthenticateChallenges,
Client as OauthClient,
isOAuth2Error,
} from "oauth4webapi";
import { cookies, headers } from "next/headers";
import {
Expand Down Expand Up @@ -164,13 +165,39 @@ export const handlers = {
}

if (url.pathname.endsWith("/callback")) {
// TODO: Show error UI each time we throw/return an error
const iss = url.searchParams.get("iss");
const state = url.searchParams.get("state");
if (!iss || !state) {
throw new Error("Missing iss or state", { cause: { iss, state } });
}

// TODO: Cache this
const authServer = await processDiscoveryResponse(
new URL(iss),
await oauthDiscoveryRequest(new URL(iss)),
);

const callbackParams = validateAuthResponse(
authServer,
getOauthClientOptions(),
url.searchParams,
state,
);

if (isOAuth2Error(callbackParams)) {
let errorMessage;
if (callbackParams.error === "access_denied") {
errorMessage = "You have not authorized the app. Please try again.";
} else {
errorMessage = "An error occurred";
}

redirect(`/login?error=${errorMessage}`, RedirectType.replace);
}

const code = url.searchParams.get("code");
const iss = url.searchParams.get("iss");
if (!state || !code || !iss) {
console.error("missing params", { state, code, iss });
return new Response("Invalid request", { status: 400 });
if (!code || !state) {
throw new Error("Missing code");
}

const [row] = await db
Expand All @@ -196,12 +223,6 @@ export const handlers = {
throw new Error("Invalid state");
}

// TODO: Cache this
const authServer = await processDiscoveryResponse(
new URL(iss),
await oauthDiscoveryRequest(new URL(iss)),
);

const client = getClientMetadata();
const params = validateAuthResponse(
authServer,
Expand Down

0 comments on commit 9add1fe

Please sign in to comment.