From e29d813aee003a7514f82cfbdd3e5451e0bbff96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Dere=C5=BEi=C4=87?= Date: Mon, 8 Jan 2024 15:17:18 +0100 Subject: [PATCH] db authentication replaced by Google --- .env | 3 +- app/api/auth/[...nextauth]/route.ts | 1 + .../lib/auth.config.google.ts | 12 +++ app/lib/auth.google.ts | 29 ++++++ app/lib/loginActions.ts | 23 ----- app/login/page.tsx | 11 --- app/page.tsx | 13 ++- app/ui/LoginForm.tsx | 91 ------------------- auth.config.db.ts | 24 ----- auth.ts | 67 -------------- middleware.ts | 7 +- 11 files changed, 58 insertions(+), 223 deletions(-) create mode 100644 app/api/auth/[...nextauth]/route.ts rename auth.config.google.ts => app/lib/auth.config.google.ts (55%) create mode 100644 app/lib/auth.google.ts delete mode 100644 app/lib/loginActions.ts delete mode 100644 app/login/page.tsx delete mode 100644 app/ui/LoginForm.tsx delete mode 100644 auth.config.db.ts delete mode 100644 auth.ts diff --git a/.env b/.env index 44ab9ff..cc9943e 100644 --- a/.env +++ b/.env @@ -1,6 +1,5 @@ -AUTH_SECRET=Gh0jQ35oq6DR8HkLR3heA8EaEDtxYN/xkP6blvukZ0w= MONGODB_URI=mongodb://root:example@localhost:27017/ GOOGLE_ID=355397364527-adjrokm6hromcaaar0qfhk050mfr35ou.apps.googleusercontent.com GOOGLE_SECRET=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 -NEXT_AUTH_SECRET=Gh0jQ35oq6DR8HkLR3heA8EaEDtxYN/xkP6blvukZ0w= +AUTH_SECRET=Gh0jQ35oq6DR8HkLR3heA8EaEDtxYN/xkP6blvukZ0w= diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..d10f26c --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1 @@ +export { GET, POST } from '@/app/lib/auth.google'; \ No newline at end of file diff --git a/auth.config.google.ts b/app/lib/auth.config.google.ts similarity index 55% rename from auth.config.google.ts rename to app/lib/auth.config.google.ts index ebdab15..e5ccdba 100644 --- a/auth.config.google.ts +++ b/app/lib/auth.config.google.ts @@ -2,6 +2,18 @@ import NextAuth, { NextAuthConfig } from 'next-auth'; import GoogleProvider from 'next-auth/providers/google'; export const authConfig:NextAuthConfig = { +callbacks: { + async signIn({ account, profile }) { + if (account?.provider === "google") { + return profile?.email_verified === true && profile?.email?.endsWith("@google.com") === true + } + return true // Do different verification for other providers that don't have `email_verified` + }, + authorized({ auth, request: { nextUrl } }) { + const isLoggedIn = !!auth?.user; + return(isLoggedIn); + }, +}, providers: [ GoogleProvider({ clientId: process.env.GOOGLE_ID, diff --git a/app/lib/auth.google.ts b/app/lib/auth.google.ts new file mode 100644 index 0000000..8f44039 --- /dev/null +++ b/app/lib/auth.google.ts @@ -0,0 +1,29 @@ +import NextAuth, { NextAuthConfig } from 'next-auth'; +import GoogleProvider from 'next-auth/providers/google'; + +const authConfig: NextAuthConfig = { + callbacks: { + // This method verifies if the user is logged in or not + // It is called by Next-Auth when the midleware calls + // the `auth` method (exported below) + authorized({ auth, request: { nextUrl } }) { + const isLoggedIn = !!auth?.user; + return (isLoggedIn); + }, + }, + providers: [ + GoogleProvider({ + clientId: process.env.GOOGLE_ID, + clientSecret: process.env.GOOGLE_SECRET, + }), + ], + secret: process.env.AUTH_SECRET, + session: { + // Use JSON Web Tokens for session instead of database sessions. + // This option can be used with or without a database for users/accounts. + // Note: `jwt` is automatically set to `true` if no database is specified. + strategy: 'jwt' + }, +}; + +export const { auth, handlers: { GET, POST } } = NextAuth(authConfig); \ No newline at end of file diff --git a/app/lib/loginActions.ts b/app/lib/loginActions.ts deleted file mode 100644 index 1175d55..0000000 --- a/app/lib/loginActions.ts +++ /dev/null @@ -1,23 +0,0 @@ -'use server'; - -import { signIn } from '@/auth'; -import { AuthError } from 'next-auth'; - -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; - } - } \ No newline at end of file diff --git a/app/login/page.tsx b/app/login/page.tsx deleted file mode 100644 index eaa8df7..0000000 --- a/app/login/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import LoginForm from '@/app/ui/LoginForm'; - -export default function LoginPage() { - return ( -
-
- -
-
- ); -} \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index ec72ae1..8d6574a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -5,6 +5,8 @@ import { AddLocationButton } from './ui/AddLocationButton'; import clientPromise from './lib/mongodb'; import { BillingLocation } from './lib/db-types'; import { PageFooter } from './ui/PageFooter'; +import { auth } from '@/app/lib/auth.google'; +import { redirect } from 'next/navigation'; const getNextYearMonth = (yearMonth:number) => { return(yearMonth % 100 === 12 ? yearMonth + 89 : yearMonth + 1); @@ -12,9 +14,11 @@ const getNextYearMonth = (yearMonth:number) => { export const Page = async () => { + const session = await auth(); + const client = await clientPromise; const db = client.db("rezije"); - + const locations = await db.collection("lokacije") .find({}) .sort({ yearMonth: -1, name: 1 }) // sort by yearMonth descending @@ -57,6 +61,13 @@ export const Page = async () => { }) } +
    +
  • session.expires = { session?.expires }
  • +
  • session.user.id = { session?.user?.id }
  • +
  • session.user.email = { session?.user?.email }
  • +
  • session.user.name = { session?.user?.name }
  • +
  • session.user.image = { session?.user?.image }
  • +
); } diff --git a/app/ui/LoginForm.tsx b/app/ui/LoginForm.tsx deleted file mode 100644 index 299f3d8..0000000 --- a/app/ui/LoginForm.tsx +++ /dev/null @@ -1,91 +0,0 @@ -'use client'; - -import { lusitana } from '@/app/ui/fonts'; -import { - AtSymbolIcon, - KeyIcon, - ExclamationCircleIcon, -} from '@heroicons/react/24/outline'; -import { ArrowRightIcon } from '@heroicons/react/20/solid'; -import { Button } from './button'; -import { useFormState } from 'react-dom'; -import { authenticate } from '@/app/lib/loginActions'; - -export default function LoginForm() { - - const [errorMessage, dispatch] = useFormState(authenticate, undefined); - - return ( -
-
-

- Please log in to continue. -

-
-
- -
- - -
-
-
- -
- - -
-
-
- -
-
- {errorMessage && ( - <> - -

{errorMessage}

- - )} -
-
-
-
- ); -} - -function LoginButton() { - return ( - - ); -} diff --git a/auth.config.db.ts b/auth.config.db.ts deleted file mode 100644 index 23d5d94..0000000 --- a/auth.config.db.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @module auth.config.ts - * @description defines how user session is to be checked and redirects anonymous user to login page - */ -import type { NextAuthConfig } from 'next-auth'; - -export const authConfig = { - pages: { - signIn: '/login', - }, - // this will prevent users from accessing the dashboard pages unless they are logged in - callbacks: { - // The authorized callback is used to verify if the request is authorized to access a - // page via Next.js Middleware. It is called before a request is completed, and it - // receives an object with the auth and request properties. - // The auth property contains the user's session, and the request property contains - // the incoming request. - authorized({ auth, request: { nextUrl } }) { - const isLoggedIn = !!auth?.user; - return(isLoggedIn); - }, - }, - providers: [], // Add providers with an empty array for now -} satisfies NextAuthConfig; \ No newline at end of file diff --git a/auth.ts b/auth.ts deleted file mode 100644 index f134a8f..0000000 --- a/auth.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @module auth - * @description verifies user credentials during the log-in action (i.e. against a database) - * @exports exports `auth`, `signIn`, `signOut` actions - */ -import NextAuth from 'next-auth'; -import { authConfig } from './auth.config.db'; -import Credentials from 'next-auth/providers/credentials'; -import { z } from 'zod'; -// import bcrypt from 'bcrypt'; -import { User } from '@/app/lib/types/User'; - -const dummyUser:User = { - id: "1", - email: "nikola.derezic@gmail.com", - password: "123456", - name: "Nikola Derezic" -}; - -async function getUser(email: string): Promise { - // temporary use dummyUser instead of db - if(email === dummyUser.email) { - return dummyUser; - } - - return undefined; - - // try { - // const user = await sql`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) { - return null; - } - - const { email, password } = parsedCredentials.data; - - const user = await getUser(email); - - if (!user) return null; - - // const passwordsMatch = await bcrypt.compare(password, user.password); - const passwordsMatch = password === user.password; - - if (!passwordsMatch) return null; - - return user; - } - }) - ], -}); \ No newline at end of file diff --git a/middleware.ts b/middleware.ts index fca5472..9f1fc01 100644 --- a/middleware.ts +++ b/middleware.ts @@ -3,12 +3,11 @@ * @description hooks-up `next-auth` into the page processing pipeline */ -import NextAuth from 'next-auth'; -import { authConfig } from './auth.config.db'; +import { auth } from '@/app/lib/auth.google' -export default NextAuth(authConfig).auth; +export default auth; // middleware will call NextAuth's `auth` method, which will in turn call) see `auth.config.google.ts` export const config = { - // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher + // midleware will NOT be called for paths: ['/api/auth/*', '/_next/static/*', '/_next/image*'] matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'], }; \ No newline at end of file