From e9db8b7a7a5463acf77e627640ffd41de09a226c Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Wed, 6 Sep 2023 15:03:59 -0500 Subject: [PATCH] Switch to email-based login --- src/app/sign-in/page.tsx | 8 ++--- src/auth.ts | 40 ------------------------ src/auth/SignInForm.tsx | 47 ++++++++++++++++++++++++++++ src/auth/index.ts | 57 ++++++++++++++++++++++++++++++++++ src/components/FieldSet.tsx | 4 ++- src/components/InfoBlock.tsx | 8 ++++- src/components/LoginButton.tsx | 14 --------- src/middleware.ts | 2 +- src/site/globals.css | 4 +-- 9 files changed, 121 insertions(+), 63 deletions(-) delete mode 100644 src/auth.ts create mode 100644 src/auth/SignInForm.tsx create mode 100644 src/auth/index.ts delete mode 100644 src/components/LoginButton.tsx diff --git a/src/app/sign-in/page.tsx b/src/app/sign-in/page.tsx index d36d974e..d475cd4a 100644 --- a/src/app/sign-in/page.tsx +++ b/src/app/sign-in/page.tsx @@ -1,21 +1,21 @@ import { auth } from '@/auth'; -import LoginButton from '@/components/LoginButton'; +import SignInForm from '@/auth/SignInForm'; import { cc } from '@/utility/css'; -import Link from 'next/link'; import { redirect } from 'next/navigation'; export default async function SignInPage() { const session = await auth(); + if (session?.user) { redirect('/'); } + return (
- - Home +
); } diff --git a/src/auth.ts b/src/auth.ts deleted file mode 100644 index 3c96f28c..00000000 --- a/src/auth.ts +++ /dev/null @@ -1,40 +0,0 @@ -import NextAuth, { type DefaultSession } from 'next-auth'; -import GitHub from 'next-auth/providers/github'; - -declare module 'next-auth' { - interface Session { - user: { - id: string - } & DefaultSession['user'] - } -} - -export const { - handlers: { GET, POST }, - auth, -} = NextAuth({ - providers: [GitHub({ - clientId: process.env.GITHUB_CLIENT_ID, - clientSecret: process.env.GITHUB_CLIENT_SECRET, - })], - callbacks: { - jwt({ token, profile }) { - if (profile) { - token.id = profile.id; - token.image = profile.avatar_url || profile.picture; - } - return token; - }, - authorized({ auth }) { - // this ensures there is a logged in user for -every- request - return ( - process.env.GITHUB_ADMIN_EMAIL !== undefined && - process.env.GITHUB_ADMIN_EMAIL === auth?.user?.email - ); - }, - }, - pages: { - // overrides the next-auth default sign-in page - signIn: '/sign-in', - }, -}); diff --git a/src/auth/SignInForm.tsx b/src/auth/SignInForm.tsx new file mode 100644 index 00000000..ba6c46f4 --- /dev/null +++ b/src/auth/SignInForm.tsx @@ -0,0 +1,47 @@ +'use client'; + +import FieldSet from '@/components/FieldSet'; +import InfoBlock from '@/components/InfoBlock'; +import { signIn } from 'next-auth/react'; +import { useState } from 'react'; + +export default function SignInForm() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + + return ( + +
+
+
+
+ +
+ ); +} +// \ No newline at end of file diff --git a/src/auth/index.ts b/src/auth/index.ts new file mode 100644 index 00000000..f8d2cdda --- /dev/null +++ b/src/auth/index.ts @@ -0,0 +1,57 @@ +import NextAuth, { User, type DefaultSession } from 'next-auth'; +import Credentials from 'next-auth/providers/credentials'; +import { NextResponse } from 'next/server'; + +declare module 'next-auth' { + interface Session { + user: { + id: string + } & DefaultSession['user'] + } +} + +export const { + handlers: { GET, POST }, + auth, + CSRF_experimental, +} = NextAuth({ + providers: [ + Credentials({ + credentials: { + email: { label: 'Email', type: 'text' }, + password: { label: 'Password', type: 'password' }, + }, + async authorize({ email, password }) { + if ( + process.env.ADMIN_EMAIL && process.env.ADMIN_EMAIL === email && + process.env.ADMIN_PASSWORD && process.env.ADMIN_PASSWORD === password + ) { + const user: User = { id: '1', email, name: 'Admin User' }; + return user; + } else { + return null; + } + }, + }), + ], + callbacks: { + authorized({ auth, request }) { + const url = new URL(request.url); + const { pathname } = url; + + const isUrlProtected = pathname.startsWith('/admin'); + const isLoggedIn = !!auth?.user; + const isAuthorized = !isUrlProtected || isLoggedIn; + + if (pathname === '/admin') { + url.pathname = '/admin/photos'; + return NextResponse.redirect(url); + } + + return isAuthorized; + }, + }, + pages: { + signIn: '/sign-in', + }, +}); diff --git a/src/components/FieldSet.tsx b/src/components/FieldSet.tsx index 8f8f4b64..1ce63584 100644 --- a/src/components/FieldSet.tsx +++ b/src/components/FieldSet.tsx @@ -5,6 +5,7 @@ export default function FieldSet({ onChange, required, readOnly, + type = 'text', }: { id: string label: string @@ -12,6 +13,7 @@ export default function FieldSet({ onChange?: (value: string) => void required?: boolean readOnly?: boolean + type?: 'text' | 'password' }) { return (
@@ -30,7 +32,7 @@ export default function FieldSet({ name={id} value={value} onChange={e => onChange?.(e.target.value)} - type="text" + type={type} autoComplete="off" readOnly={readOnly} className="w-full" diff --git a/src/components/InfoBlock.tsx b/src/components/InfoBlock.tsx index d86e29af..2386dd70 100644 --- a/src/components/InfoBlock.tsx +++ b/src/components/InfoBlock.tsx @@ -3,13 +3,18 @@ import { ReactNode } from 'react'; export default function InfoBlock({ children, + className, + padding = 'loose', }: { children: ReactNode + className?: string + padding?: 'loose' | 'normal'; } ) { return (
{children}
diff --git a/src/components/LoginButton.tsx b/src/components/LoginButton.tsx deleted file mode 100644 index 4cc2c12f..00000000 --- a/src/components/LoginButton.tsx +++ /dev/null @@ -1,14 +0,0 @@ -'use client'; - -import { signIn } from 'next-auth/react'; - -export default function LoginButton() { - return ( -
signIn('github', { callbackUrl: '/' })} - > - Sign in -
- ); -} \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts index 72ff19f6..4596922f 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,5 +1,5 @@ export { auth as middleware } from './auth'; export const config = { - matcher: ['/admin/:path*'], + matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], }; diff --git a/src/site/globals.css b/src/site/globals.css index d9259139..b2002f91 100644 --- a/src/site/globals.css +++ b/src/site/globals.css @@ -16,7 +16,7 @@ tracking-wider } button, .button, - input[type=text] { + input[type=text], input[type=password] { @apply px-2 py-1.5 border rounded-md @@ -25,7 +25,7 @@ font-mono text-base leading-none min-h-[2.25rem] } - input[type=text] { + input[type=text], input[type=password] { @apply min-w-[20rem] read-only:cursor-default read-only:bg-gray-100