Skip to content

Commit

Permalink
add custom signin page
Browse files Browse the repository at this point in the history
added some form components from shadcn, upgraded to newest next-auth for better error treatment, refactored auth configs a bit
  • Loading branch information
matyson committed Oct 11, 2024
1 parent 6d9c909 commit 55e4a9c
Show file tree
Hide file tree
Showing 10 changed files with 475 additions and 54 deletions.
3 changes: 3 additions & 0 deletions apps/spu-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@hookform/resolvers": "^3.9.0",
"@sophys-web/api": "workspace:*",
"@sophys-web/auth": "workspace:*",
"@sophys-web/ui": "workspace:*",
Expand All @@ -24,8 +25,10 @@
"@trpc/server": "11.0.0-rc.441",
"lucide-react": "^0.396.0",
"next": "^14.2.3",
"next-auth": "5.0.0-beta.22",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"server-only": "^0.0.1",
"superjson": "2.2.1",
"zod": "^3.23.8"
Expand Down
41 changes: 41 additions & 0 deletions apps/spu-ui/src/app/actions/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"use server";

import { AuthError } from "next-auth";
import { z } from "zod";
import { signIn as naSignIn } from "@sophys-web/auth";

const SigninSchema = z.object({
email: z.string().email().min(2),
password: z.string().min(2),
});

export async function signIn(
data: z.infer<typeof SigninSchema>,
callbackUrl?: string,
) {
const parsed = await SigninSchema.safeParseAsync(data);

if (!parsed.success) {
return { error: "Invalid data" };
}

try {
await naSignIn("credentials", {
email: parsed.data.email,
password: parsed.data.password,
redirectTo: callbackUrl || "/",
});
} catch (error) {
if (error instanceof AuthError) {
switch (error.type) {
case "CredentialsSignin":
return { error: "Invalid credentials" };
case "AccessDenied":
return { error: "Access denied" };
default:
return { error: "Something went wrong!" };
}
}
throw error;
}
}
105 changes: 105 additions & 0 deletions apps/spu-ui/src/app/auth/signin/form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"use client";

import { useSearchParams } from "next/navigation";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "@sophys-web/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@sophys-web/ui/form";
import { Input } from "@sophys-web/ui/input";
import { toast } from "@sophys-web/ui/sonner";
import { signIn } from "../../actions/auth";

const FormSchema = z.object({
email: z
.string()
.min(2, { message: "Email is required" })
.email({ message: "Input must be a valid e-mail" }),
password: z.string().min(2, { message: "Password is required" }),
});

export function SignInForm() {
const params = useSearchParams();
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
email: "",
password: "",
},
});

const onSubmit = async (data: z.infer<typeof FormSchema>) => {
toast.info("Signing in...");
const res = await signIn(
{
email: data.email,
password: data.password,
},
params.get("callbackUrl") || "/",
);

if (res?.error) {
const error = res.error;
toast.error(error);
form.reset();
}

if (!res?.error) {
toast.success("Signed in successfully");
}
};

return (
<Form {...form}>
<form
className="my-auto flex flex-col space-y-6 rounded-sm border p-24 shadow-lg"
onSubmit={form.handleSubmit(onSubmit)}
>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
placeholder="[email protected]"
{...field}
disabled={form.formState.isSubmitting}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
type="password"
{...field}
disabled={form.formState.isSubmitting}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button disabled={form.formState.isSubmitting} type="submit">
Sign in
</Button>
</form>
</Form>
);
}
9 changes: 9 additions & 0 deletions apps/spu-ui/src/app/auth/signin/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { SignInForm } from "./form";

export default function SignInPage() {
return (
<div className="flex items-center justify-center gap-6 p-24">
<SignInForm />
</div>
);
}
2 changes: 1 addition & 1 deletion packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"dependencies": {
"@t3-oss/env-nextjs": "^0.10.1",
"next": "^14.2.3",
"next-auth": "5.0.0-beta.19",
"next-auth": "5.0.0-beta.22",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"zod": "^3.23.8"
Expand Down
83 changes: 41 additions & 42 deletions packages/auth/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { DefaultSession, NextAuthConfig } from "next-auth";
import type { DefaultJWT } from "next-auth/jwt";
import { DefaultSession, NextAuthConfig } from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { z } from "zod";
import { env } from "../env";
Expand Down Expand Up @@ -77,51 +77,48 @@ export const authConfig: NextAuthConfig = {
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
try {
const parsed = await z
.object({
email: z.string().email(),
password: z.string(),
})
.safeParseAsync(credentials);

if (!parsed.success) {
return null;
}

const name = parsed.data?.email.split("@")[0];

const formData = new FormData();
formData.append("username", name);
formData.append("password", parsed.data?.password);

const res = await fetch(
`${env.BLUESKY_HTTPSERVER_URL}/api/auth/provider/ldap/token`,
{
method: "POST",
body: formData,
},
);
const parsed = await z
.object({
email: z.string().email(),
password: z.string(),
})
.safeParseAsync(credentials);

if (!parsed.success) {
throw parsed.error;
}

if (!res.ok) {
return null;
}
const name = parsed.data?.email.split("@")[0];

const data = await res.json();
const parsedToken = await blueskyTokenSchema.parseAsync(data);
const formData = new FormData();
formData.append("username", name);
formData.append("password", parsed.data?.password);

const user = {
id: name,
name,
email: parsed.data?.email,
blueskyAccessToken: parsedToken.access_token,
blueskyRefreshToken: parsedToken.refresh_token,
expires_in: parsedToken.expires_in,
};
return user;
} catch (error) {
const res = await fetch(
`${env.BLUESKY_HTTPSERVER_URL}/api/auth/provider/ldap/token`,
{
method: "POST",
body: formData,
},
);

if (!res.ok) {
return null;
}

const data = await res.json();
const parsedToken = await blueskyTokenSchema.parseAsync(data);

const user = {
id: name,
name,
email: parsed.data?.email,
blueskyAccessToken: parsedToken.access_token,
blueskyRefreshToken: parsedToken.refresh_token,
expires_in: parsedToken.expires_in,
};

return user;
},
}),
],
Expand All @@ -139,7 +136,6 @@ export const authConfig: NextAuthConfig = {
if (user) {
token.bluesky_access_token = user.blueskyAccessToken;
token.bluesky_refresh_token = user.blueskyRefreshToken;

const jwt = {
id: token.sub,
name: user.name,
Expand Down Expand Up @@ -174,4 +170,7 @@ export const authConfig: NextAuthConfig = {
}
},
},
pages: {
signIn: "/auth/signin",
},
} satisfies NextAuthConfig;
6 changes: 5 additions & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,20 @@
"typescript": "^5.3.3"
},
"dependencies": {
"@hookform/resolvers": "^3.9.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.396.0",
"next-themes": "^0.3.0",
"react-hook-form": "^7.53.0",
"sonner": "^1.5.0",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8"
},
"prettier": "@sophys-web/prettier-config"
}
Loading

0 comments on commit 55e4a9c

Please sign in to comment.