Rewrite Signup & Signin to make it more robust (#6)

* remove explicit CSRF-token handling

* use NEXTAUTH_SECRET secret to better comply with nextAuth standards

* update next.js and nextAuth

* remove ECR github action

* add name to mail-from
This commit is contained in:
Matthias Nannt
2022-07-20 14:19:42 +02:00
committed by GitHub
parent 3afe4a8a97
commit c20167342f
10 changed files with 55 additions and 122 deletions

View File

@@ -1,5 +1,5 @@
# Modify this variables according to your setup and needs
SECRET=RANDOM_STRING
NEXTAUTH_SECRET=RANDOM_STRING
DATABASE_URL='postgresql://user@localhost:5432/snoopforms?schema=public'
NEXTAUTH_URL=http://localhost:3000

View File

@@ -1,49 +0,0 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Deploy to Amazon ECR
on:
push:
branches:
- main
env:
AWS_REGION: eu-west-1 # set this to your preferred AWS region, e.g. us-west-1
ECR_REPOSITORY: snoophub # set this to your Amazon ECR repository name
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@13d241b293754004c80624b5567555c4a39ffbe3
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@aaf69d68aa3fb14c1d5a6be9ac61fe15b48453a2
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: latest
run: |
# Build a docker container and
# push it to ECR so that it can
# be deployed to ECS.
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

View File

@@ -30,8 +30,6 @@ export default function BaseLayoutAuthorized({
const { data: session, status } = useSession();
const [loading, setLoading] = useState(true);
console.log(status);
useEffect(() => {
if (status !== "loading") {
if (!session) {

View File

@@ -20,9 +20,13 @@ export const sendEmail = async (emailData: sendEmailData) => {
user: serverRuntimeConfig.smtpUser,
pass: serverRuntimeConfig.smtpPassword,
},
// logger: true,
// debug: true,
});
const emailDefaults = {
from: serverRuntimeConfig.mailFrom || "noreply@snoopforms.com",
from: `snoopForms <${
serverRuntimeConfig.mailFrom || "noreply@snoopforms.com"
}>`,
};
await transporter.sendMail({ ...emailDefaults, ...emailData });
};
@@ -30,7 +34,7 @@ export const sendEmail = async (emailData: sendEmailData) => {
export const sendVerificationEmail = async (user) => {
const token = jwt.sign(
{ id: user.id },
serverRuntimeConfig.secret + user.email,
serverRuntimeConfig.nextauthSecret + user.email,
{
expiresIn: "1d",
}

View File

@@ -5,7 +5,7 @@ const nextConfig = {
reactStrictMode: false,
serverRuntimeConfig: {
// Will only be available on the server side
secret: process.env.SECRET,
nextauthSecret: process.env.NEXTAUTH_SECRET,
nextauthUrl: process.env.NEXTAUTH_URL,
mailFrom: process.env.MAIL_FROM,
smtpHost: process.env.SMTP_HOST,

View File

@@ -15,7 +15,7 @@
"@editorjs/paragraph": "^2.8.0",
"@headlessui/react": "^1.6.1",
"@heroicons/react": "^1.0.6",
"@prisma/client": "^4.0.0",
"@prisma/client": "^4.1.0",
"@snoopforms/react": "^0.0.3",
"babel-plugin-superjson-next": "^0.4.3",
"bcryptjs": "^2.4.3",
@@ -47,7 +47,7 @@
"eslint": "8.15.0",
"eslint-config-next": "12.1.6",
"postcss": "^8.4.13",
"prisma": "^4.0.0",
"prisma": "^4.1.0",
"tailwindcss": "^3.0.24",
"ts-node": "^10.7.0",
"typescript": "4.6.4"

View File

@@ -23,12 +23,12 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
email: {
label: "Email Address",
type: "email",
placeholder: "lisa@example.com",
placeholder: "Your email address",
},
password: {
label: "Password",
type: "password",
placeholder: "Your super secure password",
placeholder: "Your password",
},
},
async authorize(credentials, _req) {
@@ -66,7 +66,9 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
return {
id: user.id,
email: user.email,
name: user.name,
firstname: user.firstname,
lastname: user.firstname,
emailVerified: user.emailVerified,
};
},
}),
@@ -109,7 +111,7 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
const isValid = await new Promise((resolve) => {
jwt.verify(
credentials?.token,
serverRuntimeConfig.secret + user.email,
serverRuntimeConfig.nextauthSecret + user.email,
(err) => {
if (err) resolve(false);
if (!err) resolve(true);
@@ -150,7 +152,6 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
}
},
},
secret: serverRuntimeConfig.secret,
pages: {
signIn: "/auth/signin",
signOut: "/auth/logout",

View File

@@ -1,17 +1,26 @@
import { getCsrfToken } from "next-auth/react";
import { useRouter } from "next/router";
import { XCircleIcon } from "@heroicons/react/solid";
import { GetServerSideProps } from "next";
import { signIn } from "next-auth/react";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
interface props {
csrfToken: string;
}
export default function SignIn({ csrfToken }: props) {
export default function SignInPage() {
const router = useRouter();
const { error } = router.query;
const handleSubmit = async (e) => {
e.preventDefault();
await signIn("credentials", {
callbackUrl: router.query.callbackUrl?.toString(),
email: e.target.elements.email.value,
password: e.target.elements.password.value,
});
router.push(
`/auth/verification-requested?email=${encodeURIComponent(
e.target.elements.email.value
)}`
);
};
return (
<div className="flex min-h-screen bg-ui-gray-light">
<div className="flex flex-col justify-center flex-1 px-4 py-12 mx-auto sm:px-6 lg:flex-none lg:px-20 xl:px-24">
@@ -49,15 +58,11 @@ export default function SignIn({ csrfToken }: props) {
<div className="mt-8">
<div className="mt-6">
<form
onSubmit={handleSubmit}
method="post"
action="/api/auth/callback/credentials"
className="space-y-6"
>
<input
name="csrfToken"
type="hidden"
defaultValue={csrfToken}
/>
<div>
<label
htmlFor="email"
@@ -121,10 +126,3 @@ export default function SignIn({ csrfToken }: props) {
</div>
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const csrfToken = await getCsrfToken(context);
return {
props: { csrfToken },
};
};

View File

@@ -1,17 +1,11 @@
import { getCsrfToken } from "next-auth/react";
import { XCircleIcon } from "@heroicons/react/solid";
import { GetServerSideProps } from "next";
import Image from "next/image";
import Link from "next/link";
import { createUser } from "../../lib/users";
import { useState } from "react";
import { useRouter } from "next/router";
import { useState } from "react";
import { createUser } from "../../lib/users";
interface props {
csrfToken: string;
}
export default function SignIn({ csrfToken }: props) {
export default function SignUpPage() {
const router = useRouter();
const [error, setError] = useState<string>("");
@@ -74,11 +68,6 @@ export default function SignIn({ csrfToken }: props) {
</p>
<div className="mt-6">
<form onSubmit={handleSubmit} className="space-y-6">
<input
name="csrfToken"
type="hidden"
defaultValue={csrfToken}
/>
<div>
<label
htmlFor="firstname"
@@ -145,7 +134,6 @@ export default function SignIn({ csrfToken }: props) {
id="password"
name="password"
type="password"
autoComplete="current-password"
required
className="block w-full px-3 py-2 border rounded-md shadow-sm appearance-none placeholder-ui-gray-medium border-ui-gray-medium focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm"
/>
@@ -174,10 +162,3 @@ export default function SignIn({ csrfToken }: props) {
</div>
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const csrfToken = await getCsrfToken(context);
return {
props: { csrfToken },
};
};

View File

@@ -235,22 +235,22 @@
resolved "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.0.2.tgz"
integrity sha512-MSAs9t3Go7GUkMhpKC44T58DJ5KGk2vBo+h1cqQeqlMfdGkxaVB78ZWpv9gYi/g2fa4sopag9gJsNvS8XGgWJA==
"@prisma/client@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.0.0.tgz#ed2f46930a1da0d8ae88d7965485973576b04270"
integrity sha512-g1h2OGoRo7anBVQ9Cw3gsbjwPtvf7i0pkGxKeZICtwkvE5CZXW+xZF4FZdmrViYkKaAShbISL0teNpu9ecpf4g==
"@prisma/client@^4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.1.0.tgz#7c5341b2276c083821e432536ff97d8cc8f8b936"
integrity sha512-MvfPGAd42vHTiCYxwS6N+2U3F+ukoJ48D2QRnX1zSPJHBkh1CBtshl75daKzvVfgQwSouzSQeugKDej5di+E/g==
dependencies:
"@prisma/engines-version" "3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11"
"@prisma/engines-version" "4.1.0-48.8d8414deb360336e4698a65aa45a1fbaf1ce13d8"
"@prisma/engines-version@3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11":
version "3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11.tgz#4b5efe5eee2feef12910e4627a572cd96ed83236"
integrity sha512-PiZhdD624SrYEjyLboI0X7OugNbxUzDJx9v/6ldTKuqNDVUCmRH/Z00XwDi/dgM4FlqOSO+YiUsSiSKjxxG8cw==
"@prisma/engines-version@4.1.0-48.8d8414deb360336e4698a65aa45a1fbaf1ce13d8":
version "4.1.0-48.8d8414deb360336e4698a65aa45a1fbaf1ce13d8"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.1.0-48.8d8414deb360336e4698a65aa45a1fbaf1ce13d8.tgz#ce00e6377126e491a8b1e0e2039c97e2924bd6d9"
integrity sha512-cRRJwpHFGFJZvtHbY3GZjMffNBEjjZk68ztn+S2hDgPCGB4H66IK26roK94GJxBodSehwRJ0wGyebC2GoIH1JQ==
"@prisma/engines@3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11":
version "3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11.tgz#82f0018153cffa05d61422f9c0c7b0479b180f75"
integrity sha512-u/rG4lDHALolWBLr3yebZ+N2qImp3SDMcu7bHNJuRDaYvYEXy/MqfNRNEgd9GoPsXL3gofYf0VzJf2AmCG3YVw==
"@prisma/engines@4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.1.0.tgz#da8002d4b75c92e3fd564ba5b0bd04847ffb3c4c"
integrity sha512-quqHXD3P83NBLVtRlts4SgKHmqgA8GMiyDTJ7af03Wg0gl6F5t65mBYvIuwmD+52vHm42JtIsp/fAO9YIV0JBA==
"@rushstack/eslint-patch@^1.1.3":
version "1.1.3"
@@ -2199,12 +2199,12 @@ pretty-format@^3.8.0:
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz"
integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==
prisma@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.0.0.tgz#4ddb8fcd4f64d33aff8c198a6986dcce66dc8152"
integrity sha512-Dtsar03XpCBkcEb2ooGWO/WcgblDTLzGhPcustbehwlFXuTMliMDRzXsfygsgYwQoZnAUKRd1rhpvBNEUziOVw==
prisma@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.1.0.tgz#5d43597f0f2603a4a75d9586346c74110d8e221f"
integrity sha512-iwqpAT6In1uvMSwQAM3PqmaBdhh2OaQ/2t+n3RjpW4vAKP3R7E1T34FZUU4zGOWtMWm5dt0sPThQkT/h87r6gw==
dependencies:
"@prisma/engines" "3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11"
"@prisma/engines" "4.1.0"
prop-types@^15.8.1:
version "15.8.1"