-
-
Notifications
You must be signed in to change notification settings - Fork 146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding PasswordLess login to sign in options #785
Changes from 9 commits
02c8e8d
ae40a01
370c1d2
2646d89
e5e9344
0e18498
25289b9
5623b4b
930c40b
70a1151
0ffa731
1c92eb6
7b4f464
8d2bca5
d5fb822
570ecb1
19379a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
"use client"; | ||
|
||
import type { NextPage } from "next"; | ||
import { useSearchParams } from "next/navigation"; | ||
import { PostAuthPage } from "../page"; | ||
|
||
const Page: NextPage = () => { | ||
const errorHeadings = { | ||
// sign in link already used or expired | ||
verification: { | ||
heading: "The sign in link is no longer valid", | ||
subHeading: "It may have been used already or it may have expired", | ||
}, | ||
// issue with email provider. SNS may be down or something similiar | ||
emailsignin: { | ||
heading: "Well this is embarrassing", | ||
subHeading: "Something unexpected happened, we will look into it", | ||
}, | ||
// if specific error param is not found give the generic | ||
unknown: { | ||
heading: "Oops... Not sure what happened there", | ||
subHeading: "Please try again later", | ||
}, | ||
}; | ||
// Checking does the error query param exist and if it doesnt replacing with unknown | ||
const error = (useSearchParams().get("error")?.toLowerCase() ?? | ||
"unknown") as keyof typeof errorHeadings; | ||
// Checking does the error query param has been covered in errorHeading if not falling back to unknown | ||
return PostAuthPage( | ||
errorHeadings[error] ? errorHeadings[error] : errorHeadings.unknown, | ||
); | ||
}; | ||
|
||
export default Page; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
"use client"; | ||
|
||
import type { NextPage } from "next"; | ||
import Link from "next/link"; | ||
import Image from "next/image"; | ||
import { useTheme } from "next-themes"; | ||
import { THEME_MODES } from "@/components/Theme/ThemeToggle/ThemeToggle"; | ||
import { useEffect, useState } from "react"; | ||
|
||
export const PostAuthPage = (content: { | ||
heading: string; | ||
subHeading: string; | ||
}) => { | ||
const [mounted, setMounted] = useState(false); | ||
const { resolvedTheme } = useTheme(); | ||
|
||
// useEffect only happens on client not server | ||
useEffect(() => { | ||
setMounted(true); | ||
}, []); | ||
|
||
// if on server dont render. needed to prevent a hydration mismatch error | ||
if (!mounted) return null; | ||
|
||
return ( | ||
<main className="flex w-full flex-grow flex-col justify-center bg-neutral-100 px-4 py-20 dark:bg-black sm:px-6 lg:py-40"> | ||
<div className="flex flex-shrink-0 justify-center"> | ||
<Link href="/"> | ||
<span className="sr-only">Codú</span> | ||
<Image | ||
// Uses black codu logo if in light mode and white codu logo if in dark mode | ||
src={ | ||
resolvedTheme === THEME_MODES.LIGHT | ||
? "/images/codu-black.png" | ||
: "/images/codu.png" | ||
} | ||
alt="Codú logo" | ||
height={60} | ||
width={189} | ||
/> | ||
</Link> | ||
</div> | ||
<div className="py-16"> | ||
<div className="text-center"> | ||
<p className="bg-gradient-to-r from-orange-400 to-pink-600 bg-clip-text text-xl font-semibold uppercase leading-6 tracking-wide text-transparent"> | ||
{content.heading}{" "} | ||
</p> | ||
<h1 className="mt-2 text-4xl font-extrabold tracking-tight text-black dark:text-white sm:text-5xl"> | ||
{content.subHeading}{" "} | ||
</h1> | ||
<div className="mt-6"> | ||
<Link | ||
className="bg-gradient-to-r from-orange-400 to-pink-600 bg-clip-text text-base font-semibold tracking-wide text-transparent" | ||
href="/" | ||
> | ||
Return home<span aria-hidden="true"> →</span> | ||
</Link> | ||
</div> | ||
</div> | ||
</div> | ||
</main> | ||
); | ||
}; | ||
|
||
const Auth: NextPage = () => { | ||
return PostAuthPage({ | ||
heading: "Sign in email has been sent", | ||
subHeading: "See you soon 🚀", | ||
}); | ||
}; | ||
|
||
export default Auth; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ import { MoonIcon, SunIcon } from "@heroicons/react/20/solid"; | |
import { useTheme } from "next-themes"; | ||
import { useEffect, useState } from "react"; | ||
|
||
const THEME_MODES = { | ||
export const THEME_MODES = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needed this exported so I could use it to determine the coco log that should be displayed. ie black or white |
||
DARK: "dark", | ||
LIGHT: "light", | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,9 @@ GITHUB_ID= ### Replace with GitHub OAuth ID (https://github.com/settings/applic | |
GITHUB_SECRET= ### Replace with GitHub OAuth Secret (https://github.com/settings/applications/new) | ||
NEXTAUTH_URL=http://localhost:3000/api/auth | ||
DATABASE_URL=postgresql://postgres:[email protected]:5432/postgres | ||
|
||
EMAIL_SERVER_USER= ### Replace with SNS username | ||
EMAIL_SERVER_PASSWORD= ### Replace with SNS password | ||
EMAIL_SERVER_HOST= ### Replace with AWS host | ||
EMAIL_SERVER_PORT=587 | ||
EMAIL_FROM= ### Replace with email address to deliver passwordless email from | ||
JohnAllenTech marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,35 @@ | ||
import { type NextAuthOptions, getServerSession } from "next-auth"; | ||
import { type NextAuthOptions, getServerSession, Theme } from "next-auth"; | ||
import GitHubProvider from "next-auth/providers/github"; | ||
import EmailProvider, { | ||
SendVerificationRequestParams, | ||
} from "next-auth/providers/email"; | ||
import { PrismaAdapter } from "@next-auth/prisma-adapter"; | ||
import { createWelcomeEmailTemplate } from "@/utils/createEmailTemplate"; | ||
import * as Sentry from "@sentry/nextjs"; | ||
|
||
import prisma from "@/server/db/client"; | ||
import sendEmail from "@/utils/sendEmail"; | ||
import { manageNewsletterSubscription } from "./lib/newsletter"; | ||
import { createTransport } from "nodemailer"; | ||
import { createPasswordLessEmailTemplate } from "@/utils/createPasswordLessEmailTemplate"; | ||
|
||
const sendPasswordLessEmail = async (params: SendVerificationRequestParams) => { | ||
const { identifier, url, provider } = params; | ||
const transport = createTransport(provider.server); | ||
|
||
const result = await transport.sendMail({ | ||
to: identifier, | ||
from: provider.from, | ||
subject: `Sign in to Codú 🚀`, | ||
/** Email Text body (fallback for email clients that don't render HTML, e.g. feature phones) */ | ||
text: `Sign in to Codú 🚀\n\n`, | ||
html: createPasswordLessEmailTemplate(url), | ||
}); | ||
const failed = result.rejected.concat(result.pending).filter(Boolean); | ||
if (failed.length) { | ||
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`); | ||
} | ||
}; | ||
|
||
export const authOptions: NextAuthOptions = { | ||
adapter: PrismaAdapter(prisma), | ||
|
@@ -15,10 +38,24 @@ export const authOptions: NextAuthOptions = { | |
clientId: process.env.GITHUB_ID || "", | ||
clientSecret: process.env.GITHUB_SECRET || "", | ||
}), | ||
EmailProvider({ | ||
server: { | ||
host: process.env.EMAIL_SERVER_HOST, | ||
port: process.env.EMAIL_SERVER_PORT, | ||
auth: { | ||
user: process.env.EMAIL_SERVER_USER, | ||
pass: process.env.EMAIL_SERVER_PASSWORD, | ||
}, | ||
}, | ||
sendVerificationRequest: sendPasswordLessEmail, | ||
from: process.env.EMAIL_FROM, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this a new variable? Could we use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah good point. Lemme change that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually even better I think its not needed. Will confirm There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @NiallJoeMaher sorted in #19379a8 |
||
}), | ||
], | ||
pages: { | ||
signIn: "/get-started", | ||
newUser: "/settings", | ||
verifyRequest: "/auth", | ||
error: "/auth/error", // (used for any errors which occur during auth | ||
}, | ||
callbacks: { | ||
async session({ session, user }) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this is the correct way to go about alpha stuff. Further guidance needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take a look later today and make a little guide so you can see how I was handling it.