Skip to content

Commit

Permalink
magic code :)
Browse files Browse the repository at this point in the history
  • Loading branch information
filipweilid committed Mar 15, 2023
1 parent 118c845 commit b3da47b
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 13 deletions.
26 changes: 21 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@trpc/server": "^10.9.0",
"next": "^13.2.1",
"next-auth": "^4.19.0",
"nodemailer": "^6.9.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"superjson": "1.9.1",
Expand All @@ -27,6 +28,7 @@
"devDependencies": {
"@types/eslint": "^8.21.1",
"@types/node": "^18.14.0",
"@types/nodemailer": "^6.4.7",
"@types/prettier": "^2.7.2",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
Expand Down
2 changes: 1 addition & 1 deletion src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const AuthShowcase: React.FC = () => {

const { data: secretMessage } = api.example.getSecretMessage.useQuery(
undefined, // no input
{ enabled: sessionData?.user !== undefined },
{ enabled: sessionData?.user !== undefined }
);

return (
Expand Down
41 changes: 34 additions & 7 deletions src/pages/signin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,47 @@ import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
import { getCsrfToken } from "next-auth/react";

import { getCsrfToken, signIn } from "next-auth/react";
import { useState } from "react";
import { useRouter } from "next/router";
export default function SignIn({
csrfToken,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
const [email, setEmail] = useState("");
const router = useRouter();
if (!csrfToken) return <div>loading...</div>;

let url = "http://localhost:3000"; // dev client should use localhost
if (process.env.VERCEL_URL) url = `https://${process.env.VERCEL_URL}`; // SSR should use vercel url

const handleSignIn = async () => {
void signIn("email", {
email,
redirect: false,
callbackUrl: `${url}/`,
});

await router.push(
`${url}/verify-code?email=${email}&csrfToken=${csrfToken}`
);
};

return (
<form method="post" action="/api/auth/signin/email">
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
<>
<label>
Email address
<input type="email" id="email" name="email" />
<input
type="email"
id="email"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<button type="submit">Sign in with Email</button>
</form>
<button onClick={() => void handleSignIn()} type="submit">
Sign in with Email
</button>
</>
);
}

Expand Down
19 changes: 19 additions & 0 deletions src/pages/verify-code.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//pages/auth/email.tsx
import { useRouter } from "next/router";

export default function VerifyCode() {
const { query } = useRouter();
return (
<form action="/api/auth/callback/email" method="get">
<h1>Enter your code</h1>
<input type="hidden" name="callbackUrl" value={query.callbackUrl} />
<input type="hidden" name="email" value={query.email} />
<label>
Code
<input aria-label="code" type="text" name="token" />
</label>

<button type="submit">Sign in</button>
</form>
);
}
28 changes: 28 additions & 0 deletions src/server/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { env } from "~/env.mjs";
import { prisma } from "~/server/db";
import EmailProvider from "next-auth/providers/email";
import { createTransport } from "nodemailer";
import crypto from "node:crypto"; // Requires Node.js 18+

/**
* Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
Expand Down Expand Up @@ -50,10 +52,36 @@ export const authOptions: NextAuthOptions = {
EmailProvider({
server: env.EMAIL_SERVER,
from: env.EMAIL_FROM,
generateVerificationToken() {
const random = crypto.getRandomValues(new Uint8Array(8));
return Buffer.from(random).toString("hex").slice(0, 6);
},
async sendVerificationRequest(params) {
const { identifier, provider, token } = params;
const url = new URL(params.url);
const signInURL = new URL(
`/auth/email?${url.searchParams as unknown as string}`,
url.origin
);
const escapedHost = signInURL.host.replace(/\./g, "&#8203;.");

const result = await createTransport(provider.server).sendMail({
to: identifier,
from: provider.from,
subject: `Sign in to ${signInURL.host}`,
text: `Sign in on ${signInURL.host} using the verification code: ${token}`,
html: `<body style="background: #f9f9f9;"><table width="100%" border="0" cellspacing="20" cellpadding="0" style="background: #fff; max-width: 600px; margin: auto; border-radius: 10px;"> <tr> <td align="center" style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: #444;"> Sign in to <strong>${escapedHost}</strong> using this verification code: ${token}</td></tr><tr> <td align="center" style="padding: 20px 0;"> <table border="0" cellspacing="0" cellpadding="0"> <tr> <td align="center" style="border-radius: 5px;"></td></tr></table> </td></tr><tr> <td align="center" style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: #444;"> If you did not request this email you can safely ignore it. </td></tr></table></body>`,
});
const failed = result.rejected.concat(result.pending).filter(Boolean);
if (failed.length) {
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`);
}
},
}),
],
pages: {
signIn: "/signin",
verifyRequest: "/verify-code",
},
};

Expand Down

0 comments on commit b3da47b

Please sign in to comment.