feat: setup magic link login via next auth

This commit is contained in:
Henry
2023-10-30 17:56:54 +00:00
parent afc634aa2c
commit 6fb43dc25c
7 changed files with 147 additions and 23 deletions

View File

@@ -5,6 +5,17 @@
await import("./src/env.mjs");
/** @type {import("next").NextConfig} */
const config = {};
const config = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "storage.googleapis.com",
port: "",
pathname: "/**",
},
],
},
};
export default config;

View File

@@ -21,6 +21,7 @@
"@trpc/react-query": "^10.37.1",
"@trpc/server": "^10.37.1",
"drizzle-orm": "^0.28.5",
"formik": "^2.4.5",
"next": "^13.5.4",
"next-auth": "^4.24.4",
"react": "18.2.0",

View File

@@ -0,0 +1,52 @@
"use client";
import { Formik, Form, Field } from "formik";
import { signIn } from "next-auth/react";
interface FormValues {
email: string;
}
export function Login() {
return (
<Formik
initialValues={{
email: "",
}}
onSubmit={async (values: FormValues) => {
await signIn("email", { email: values.email });
}}
>
<Form className="space-y-6">
<div>
<label
htmlFor="email"
className="text-dark-1000 block text-sm font-normal leading-6"
>
Email address
</label>
<div className="mt-2">
<Field
id="email"
name="email"
type="email"
placeholder="you@example.com"
autoComplete="email"
required
className="bg-dark-500 focus:ring-dark-900 ring-dark-900 text-dark-1000 block w-full rounded-md border-0 bg-white/5 py-1.5 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6"
/>
</div>
</div>
<div className="pt-2">
<button
type="submit"
className="bg-dark-1000 text-dark-50 focus-visible:outline-offset- flex w-full justify-center rounded-md px-3 py-2 text-sm font-semibold leading-6 shadow-sm focus-visible:outline focus-visible:outline-2"
>
Sign up
</button>
</div>
</Form>
</Formik>
);
}

View File

@@ -0,0 +1,20 @@
import { Login } from "~/app/_components/auth";
export default function LoginPage() {
return (
<main className="bg-dark-50 flex h-screen flex-col items-center justify-center">
<h1 className="text-dark-1000 mb-6 text-lg font-normal tracking-tight">
kan
</h1>
<p className="text-dark-1000 mb-10 text-3xl">Get started</p>
<div className="bg-dark-400 border-dark-600 w-full rounded-md border px-10 py-10 sm:max-w-md">
<div className=" sm:mx-auto sm:w-full sm:max-w-sm">
<Login />
</div>
</div>
<p className="text-dark-1000 mt-10 text-center text-sm">
{"Already have an account?"}
</p>
</main>
);
}

View File

@@ -0,0 +1,12 @@
export default function VerifyPage() {
return (
<main className="bg-dark-50 flex h-screen flex-col items-center justify-center">
<p className="text-dark-1000 mb-10 text-3xl">Check your inbox</p>
<div className=" sm:mx-auto sm:w-full sm:max-w-sm">
<p className="text-dark-1000 text-md mt-5 text-center">
{`Click on the link we've sent to you@example.com to sign in.`}
</p>
</div>
</main>
);
}

View File

@@ -21,7 +21,7 @@ const NavButton: React.FC<{
href={href}
className={classNames(
current ? "bg-dark-400 text-white" : "bg-dark-400 text-white",
"text-dark-1000 group flex items-center gap-x-3 rounded-md p-1.5 text-sm font-normal leading-6",
"group flex items-center gap-x-3 rounded-md p-1.5 text-sm font-normal leading-6 text-dark-1000",
)}
>
{name}
@@ -34,26 +34,28 @@ export default async function Layout(props: { children: React.ReactNode }) {
if (!session?.user) redirect("api/auth/signin");
return (
<main className="bg-dark-50 flex h-screen flex-col items-center">
<div className="border-dark-600 m-auto flex h-16 w-full justify-between border-b px-5 py-2 align-middle">
<main className="flex h-screen flex-col items-center bg-dark-50">
<div className="m-auto flex h-16 w-full justify-between border-b border-dark-600 px-5 py-2 align-middle">
<div className="my-auto flex">
<h1 className="text-dark-1000 text-lg font-normal tracking-tight">
<h1 className="text-lg font-normal tracking-tight text-dark-1000">
kan
</h1>
</div>
</div>
<div className="flex h-full w-full">
<nav className="border-dark-600 w-64 border-r px-5 py-5">
<nav className="w-64 border-r border-dark-600 px-5 py-5">
<button className="flex items-center">
<Image
src={session?.user.image}
className="h-8 w-8 rounded-full bg-gray-50"
width={30}
height={30}
alt=""
/>
<p className="text-dark-1000 ml-2 text-sm">{session?.user.name}</p>
{session?.user.image ? (
<Image
src={session?.user.image ?? ""}
className="h-8 w-8 rounded-full bg-gray-50"
width={30}
height={30}
alt=""
/>
) : null}
<p className="ml-2 text-sm text-dark-1000">{session?.user.name}</p>
</button>
<div>

View File

@@ -45,16 +45,42 @@ export const authOptions: NextAuthOptions = {
}),
},
adapter: DrizzleAdapter(db, mysqlTable),
pages: {
signIn: "/auth/login",
// signOut: "/auth/signout",
// error: "/auth/error",
verifyRequest: "/auth/verify",
},
providers: [
/**
* ...add more providers here.
*
* Most other providers require a bit more work than the Discord provider. For example, the
* GitHub provider requires you to add the `refresh_token_expires_in` field to the Account
* model. Refer to the NextAuth.js docs for the provider you want to use. Example:
*
* @see https://next-auth.js.org/providers/github
*/
{
id: 'email',
type: 'email',
from: process.env.EMAIL_FROM ?? '',
server: {},
maxAge: 24 * 60 * 60,
name: 'Email',
options: {},
async sendVerificationRequest({ identifier: email, url }) {
await fetch(process.env.EMAIL_URL ?? '', {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.EMAIL_TOKEN}`,
},
body: JSON.stringify({
from: process.env.EMAIL_FROM,
to: [email],
subject: 'Login via email',
html: `<strong>${url}</strong>`,
}),
})
// if (!response.ok) {
// const { errors } = await response.json()
// throw new Error(JSON.stringify(errors))
// }
},
}
],
};