Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
congle-engineer committed May 1, 2024
1 parent bebe628 commit c605478
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 6 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ POSTGRES_USER="default"
POSTGRES_HOST="ep-twilight-smoke-a4jn8ndx-pooler.us-east-1.aws.neon.tech"
POSTGRES_PASSWORD="Gsx0qRyla9cM"
POSTGRES_DATABASE="verceldb"

AUTH_SECRET=xioUk60MaJO4VjIUJVlv3+DMHDALl4MkG1olUKYLZzc=
21 changes: 21 additions & 0 deletions app/lib/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { z } from 'zod';
import { sql } from '@vercel/postgres';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { signIn } from '@/auth';
import { AuthError } from 'next-auth';

const FormSchema = z.object({
id: z.string(),
Expand Down Expand Up @@ -117,3 +119,22 @@ export async function deleteInvoice(id: string) {

revalidatePath('/dashboard/invoices');
}

export async function authenticate(
prevState: string | undefined,
formData: FormData
) {
try {
await signIn('credentials', formData);
} catch (error) {
if (error instanceof AuthError) {
switch (error.type) {
case 'CredentialsSignin':
return 'Invalid credentials.';
default:
return 'Something went wrong.';
}
}
throw error;
}
}
17 changes: 17 additions & 0 deletions app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import AcmeLogo from '@/app/ui/acme-logo';
import LoginForm from '@/app/ui/login-form';

export default function LoginPage() {
return (
<main className="flex items-center justify-center md:h-screen">
<div className="relative mx-auto flex w-full max-w-[400px] flex-col space-y-2.5 p-4 md:-mt-32">
<div className="flex h-20 w-full items-end rounded-lg bg-blue-500 p-3 md:h-36">
<div className="w-32 text-white md:w-36">
<AcmeLogo />
</div>
</div>
<LoginForm />
</div>
</main>
);
}
8 changes: 7 additions & 1 deletion app/ui/dashboard/sidenav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Link from 'next/link';
import NavLinks from '@/app/ui/dashboard/nav-links';
import AcmeLogo from '@/app/ui/acme-logo';
import { PowerIcon } from '@heroicons/react/24/outline';
import { signOut } from '@/auth';

export default function SideNav() {
return (
Expand All @@ -17,7 +18,12 @@ export default function SideNav() {
<div className="flex grow flex-row justify-between space-x-2 md:flex-col md:space-x-0 md:space-y-2">
<NavLinks />
<div className="hidden h-auto w-full grow rounded-md bg-gray-50 md:block"></div>
<form>
<form
action={async () => {
'use server'
await signOut();
}}
>
<button className="flex h-[48px] w-full grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3">
<PowerIcon className="w-6" />
<div className="hidden md:block">Sign Out</div>
Expand Down
24 changes: 20 additions & 4 deletions app/ui/login-form.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client';

import { lusitana } from '@/app/ui/fonts';
import {
AtSymbolIcon,
Expand All @@ -6,10 +8,14 @@ import {
} from '@heroicons/react/24/outline';
import { ArrowRightIcon } from '@heroicons/react/20/solid';
import { Button } from './button';
import { useFormState, useFormStatus } from 'react-dom';
import { authenticate } from '@/app/lib/actions';

export default function LoginForm() {
const [errorMessage, dispatch] = useFormState(authenticate, undefined);

return (
<form className="space-y-3">
<form action={dispatch} className="space-y-3">
<div className="flex-1 rounded-lg bg-gray-50 px-6 pb-4 pt-8">
<h1 className={`${lusitana.className} mb-3 text-2xl`}>
Please log in to continue.
Expand Down Expand Up @@ -56,17 +62,27 @@ export default function LoginForm() {
</div>
</div>
<LoginButton />
<div className="flex h-8 items-end space-x-1">
{/* Add form errors here */}
<div
className="flex h-8 items-end space-x-1"
aria-live="polite"
aria-atomic="true"
>
{errorMessage && (
<>
<ExclamationCircleIcon className="h-5 w-5 text-red-500" />
<p className="text-sm text-red-500">{errorMessage}</p>
</>
)}
</div>
</div>
</form>
);
}

function LoginButton() {
const { pending } = useFormStatus();
return (
<Button className="mt-4 w-full">
<Button className="mt-4 w-full" aria-disabled={pending}>
Log in <ArrowRightIcon className="ml-auto h-5 w-5 text-gray-50" />
</Button>
);
Expand Down
23 changes: 23 additions & 0 deletions auth.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { NextAuthConfig } from 'next-auth';

export const authConfig = {
pages: {
signIn: '/login'
},
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
if (isOnDashboard) {
if (isLoggedIn) {
return true;
}
return false;
} else if (isLoggedIn) {
return Response.redirect(new URL('/dashboard', nextUrl));
}
return true;
}
},
providers: []
} satisfies NextAuthConfig;
45 changes: 45 additions & 0 deletions auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials'
import { z } from 'zod';
import { sql } from '@vercel/postgres';
import type { User } from '@/app/lib/definitions';
import bcrypt from 'bcrypt';

async function getUser(email: string): Promise<User | undefined> {
try {
const user = await sql<User>`SELECT * FROM users WHERE email=${email}`;
return user.rows[0];
} catch (error) {
console.error('Failed to fetch user: ', error);
throw new Error('Failed to fetch user.');
}
}

export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
const parsedCredentials = z
.object({ email: z.string().email(), password: z.string().min(6) })
.safeParse(credentials);

if (parsedCredentials.success) {
const { email, password } = parsedCredentials.data;
const user = await getUser(email);
if (!user) {
return null;
}
const passwordMatch = await bcrypt.compare(password, user.password);
if (passwordMatch) {
return user;
}
}

console.log('Invalid credentials');
return null;
}
})
]
});
8 changes: 8 additions & 0 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';

export default NextAuth(authConfig).auth;

export const config = {
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)']
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"bcrypt": "^5.1.1",
"clsx": "^2.0.0",
"next": "^14.0.2",
"next-auth": "^5.0.0-beta.17",
"postcss": "8.4.31",
"react": "18.2.0",
"react-dom": "18.2.0",
Expand Down
64 changes: 63 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"

"@auth/[email protected]":
version "0.30.0"
resolved "https://registry.npmjs.org/@auth/core/-/core-0.30.0.tgz"
integrity sha512-8AE4m/nk+4EIiVCJwxZAsJeAQuzpEC8M8768mmKVn60CGDdupKQkVhxbRlm5Qh7eNRCoFFME+0DvtaX2aXrYaA==
dependencies:
"@panva/hkdf" "^1.1.1"
"@types/cookie" "0.6.0"
cookie "0.6.0"
jose "^5.1.3"
oauth4webapi "^2.4.0"
preact "10.11.3"
preact-render-to-string "5.2.3"

"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.22.13":
version "7.22.13"
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz"
Expand Down Expand Up @@ -392,6 +405,11 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"

"@panva/hkdf@^1.1.1":
version "1.1.1"
resolved "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.1.1.tgz"
integrity sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==

"@pkgr/utils@^2.3.1":
version "2.4.2"
resolved "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz"
Expand Down Expand Up @@ -430,6 +448,11 @@
dependencies:
"@types/node" "*"

"@types/[email protected]":
version "0.6.0"
resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz"
integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==

"@types/json-schema@^7.0.12", "@types/json-schema@^7.0.9":
version "7.0.14"
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz"
Expand Down Expand Up @@ -1138,6 +1161,11 @@ convert-source-map@^2.0.0:
resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==

[email protected]:
version "0.6.0"
resolved "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==

cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
Expand Down Expand Up @@ -2423,6 +2451,11 @@ jju@~1.4.0:
resolved "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz"
integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==

jose@^5.1.3:
version "5.2.4"
resolved "https://registry.npmjs.org/jose/-/jose-5.2.4.tgz"
integrity sha512-6ScbIk2WWCeXkmzF6bRPmEuaqy1m8SbsRFMa/FLrSCkGIhj8OLVG/IH+XHVmNMx/KUo8cVWEE6oKR4dJ+S0Rkg==

"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
Expand Down Expand Up @@ -2680,7 +2713,14 @@ natural-compare@^1.4.0:
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==

next@^14.0.2:
next-auth@^5.0.0-beta.17:
version "5.0.0-beta.17"
resolved "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.17.tgz"
integrity sha512-XA/7JtAjOgDfAeotJPFUsFZGGItZwzZrxLt9Gc9fE7EchLk6zydZfuZ22Vvwixs3IilkN644D5IoD5tEOAFGCQ==
dependencies:
"@auth/core" "0.30.0"

next@^14, next@^14.0.2:
version "14.0.2"
resolved "https://registry.npmjs.org/next/-/next-14.0.2.tgz"
integrity sha512-jsAU2CkYS40GaQYOiLl9m93RTv2DA/tTJ0NRlmZIBIL87YwQ/xR8k796z7IqgM3jydI8G25dXvyYMC9VDIevIg==
Expand Down Expand Up @@ -2776,6 +2816,11 @@ npmlog@^5.0.1:
gauge "^3.0.0"
set-blocking "^2.0.0"

oauth4webapi@^2.4.0:
version "2.10.4"
resolved "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.10.4.tgz"
integrity sha512-DSoj8QoChzOCQlJkRmYxAJCIpnXFW32R0Uq7avyghIeB6iJq0XAblOD7pcq3mx4WEBDwMuKr0Y1qveCBleG2Xw==

object-assign@^4.0.1, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
Expand Down Expand Up @@ -3095,6 +3140,18 @@ postgres-interval@^1.1.0:
dependencies:
xtend "^4.0.0"

[email protected]:
version "5.2.3"
resolved "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz"
integrity sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==
dependencies:
pretty-format "^3.8.0"

preact@>=10, [email protected]:
version "10.11.3"
resolved "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz"
integrity sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==

prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
Expand All @@ -3118,6 +3175,11 @@ prettier@^3.0, prettier@^3.0.3, "prettier@>= 1.16.0", "prettier@>=3.0.0 <4":
resolved "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz"
integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==

pretty-format@^3.8.0:
version "3.8.0"
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz"
integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==

prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
Expand Down

0 comments on commit c605478

Please sign in to comment.