Skip to content

Commit 5d41696

Browse files
committed
feat: add authentication middleware
1 parent 67b8d72 commit 5d41696

File tree

13 files changed

+843
-55
lines changed

13 files changed

+843
-55
lines changed

app/(auth)/login/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ export default async function Page({
55
}: {
66
searchParams: Promise<{
77
email?: string
8+
from?: string
89
}>
910
}) {
10-
const { email } = await searchParams
11+
const { email, from = '/' } = await searchParams
1112
return (
1213
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
1314
<div className="w-full max-w-sm">
14-
<LoginForm email={email} />
15+
<LoginForm email={email} from={from} />
1516
</div>
1617
</div>
1718
)

app/(protected)/borrows/page.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ import {
2525
} from '@/components/ui/pagination'
2626

2727
import { getListBorrows } from '@/lib/api/borrow'
28+
import { Verify } from '@/lib/firebase/firebase'
2829
import { Borrow } from '@/lib/types/borrow'
2930
import { Book, Calendar, LibraryIcon, User } from 'lucide-react'
30-
import { cookies } from 'next/headers'
3131
import Link from 'next/link'
3232

3333
const formatDate = (date: string): string => {
@@ -60,8 +60,9 @@ export default async function Borrows({
6060
const limit = Number(sp?.limit ?? 20)
6161
const library_id = sp?.library_id
6262

63-
const cookieStore = await cookies()
64-
const token = cookieStore.get('auth')?.value
63+
const headers = await Verify({
64+
from: '/borrows',
65+
})
6566

6667
const res = await getListBorrows(
6768
{
@@ -72,9 +73,7 @@ export default async function Borrows({
7273
...(library_id ? { library_id } : {}),
7374
},
7475
{
75-
headers: {
76-
Authorization: `Bearer ${token}`,
77-
},
76+
headers,
7877
}
7978
)
8079

app/(protected)/layout.tsx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
1-
import { cookies } from 'next/headers'
2-
import { redirect } from 'next/navigation'
3-
41
export default async function ProtectedLayout({
52
children,
63
}: Readonly<{ children: React.ReactNode }>) {
7-
// check if the user is authenticated
8-
// if not, redirect to the login page
9-
// REF: https://nextjs.org/docs/app/api-reference/functions/cookies
10-
const c = await cookies()
11-
const token = c.get('auth')
12-
if (!token) {
13-
redirect('/login')
14-
}
4+
console.log('layout is useless now: ')
155
return <>{children}</>
166
}

components/login-form.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,21 @@ import Link from 'next/link'
1818
export function LoginForm({
1919
className,
2020
...props
21-
}: React.ComponentPropsWithoutRef<'div'> & { email?: string }) {
21+
}: React.ComponentPropsWithoutRef<'div'> & { email?: string; from: string }) {
2222
const initialState = {
2323
error: '',
2424
email: props.email ?? '',
2525
password: '',
26+
// to pass "from" to login form for redirecting after login
27+
from: props.from,
2628
}
27-
const [state, action, isPending] = useActionState(login, initialState, '/')
29+
30+
const [state, action, isPending] = useActionState(
31+
login,
32+
initialState,
33+
props.from
34+
)
35+
2836
return (
2937
<div className={cn('flex flex-col gap-6', className)} {...props}>
3038
<Card>
@@ -78,7 +86,11 @@ export function LoginForm({
7886
</div>
7987
<div className="mt-4 text-center text-sm">
8088
Don&apos;t have an account?{' '}
81-
<Link href="/signup" className="underline underline-offset-4">
89+
<Link
90+
href="/signup"
91+
className="underline underline-offset-4"
92+
replace
93+
>
8294
Sign up
8395
</Link>
8496
</div>

firebase.config.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
import { initializeApp } from 'firebase/app'
1+
// import { initializeApp } from 'firebase/app'
2+
// // import admin from 'firebase-admin'
23

3-
const firebaseConfig = {
4-
apiKey: 'AIzaSyAyqBa-fgy4kVnBGQb6MEENYIcCvxvmqMc',
5-
authDomain: 'librarease-dev.firebaseapp.com',
6-
projectId: 'librarease-dev',
7-
storageBucket: 'librarease-dev.firebasestorage.app',
8-
messagingSenderId: '329808199046',
9-
appId: '1:329808199046:web:33be1f2dde6b1b0d24c007',
10-
measurementId: 'G-VJY7LPK50P',
11-
}
12-
13-
export const app = initializeApp(firebaseConfig)
4+
// export const app = initializeApp(
5+
// {
6+
// apiKey: process.env.FIREBASE_API_KEY,
7+
// authDomain: process.env.FIREBASE_AUTH_DOMAIN,
8+
// projectId: process.env.FIREBASE_PROJECT_ID,
9+
// },
10+
// 'client'
11+
// )

lib/actions/login.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use server'
22

3-
import { app } from '@/firebase.config'
3+
import { app } from '../firebase/client'
44
import {
55
getAuth,
66
signInWithEmailAndPassword,
@@ -15,10 +15,11 @@ type formState = {
1515
error: string
1616
email: string
1717
password: string
18+
from: string
1819
}
1920

2021
export async function login(
21-
_: formState,
22+
formState: formState,
2223
formData: FormData
2324
): Promise<formState> {
2425
const email = formData.get('email') as string
@@ -33,16 +34,18 @@ export async function login(
3334
error: 'Something went wrong',
3435
email,
3536
password,
37+
from: formState.from,
3638
}
3739
}
3840

3941
const user = userCredentials.user
42+
const sessionName = process.env.SESSION_COOKIE_NAME as string
4043
const token = await user.getIdToken()
41-
4244
const cookieStore = await cookies()
43-
cookieStore.set('auth', token, {
45+
cookieStore.delete(sessionName)
46+
cookieStore.set(sessionName, token, {
4447
maxAge: 60 * 60 * 24 * 7,
4548
})
4649

47-
redirect('/', RedirectType.replace)
50+
redirect(formState.from, RedirectType.replace)
4851
}

lib/firebase/admin.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import admin from 'firebase-admin'
2+
import { applicationDefault } from 'firebase-admin/app'
3+
4+
const adminApp = !admin.apps.length
5+
? admin.initializeApp(
6+
{
7+
// export GOOGLE_APPLICATION_CREDENTIALS="/path/to/serviceAccountKey.json"
8+
credential: applicationDefault(),
9+
},
10+
'admin'
11+
)
12+
: admin.app('admin')
13+
// }
14+
15+
export { adminApp }

lib/firebase/client.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { initializeApp } from 'firebase/app'
2+
3+
export const app = initializeApp(
4+
{
5+
apiKey: process.env.FIREBASE_API_KEY,
6+
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
7+
projectId: process.env.FIREBASE_PROJECT_ID,
8+
},
9+
'client'
10+
)

lib/firebase/firebase.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { cookies } from 'next/headers'
2+
import { adminApp } from './admin'
3+
import { redirect, RedirectType } from 'next/navigation'
4+
5+
export async function Verify({ from }: { from: string }) {
6+
const cookieStore = await cookies()
7+
const sessionName = process.env.SESSION_COOKIE_NAME as string
8+
const session = cookieStore.get(sessionName)
9+
try {
10+
const decoded = await adminApp
11+
.auth()
12+
.verifyIdToken(session?.value as string)
13+
14+
const headers = new Headers({})
15+
headers.set('X-Client-Id', process.env.CLIENT_ID as string)
16+
headers.set('X-Uid', decoded.uid)
17+
18+
return headers
19+
} catch (error) {
20+
if (error instanceof Error && 'code' in error) {
21+
switch (error.code) {
22+
case 'auth/id-token-expired':
23+
console.log('Token expired')
24+
break
25+
case 'auth/id-token-revoked':
26+
console.log('Token revoked')
27+
break
28+
case 'auth/argument-error':
29+
console.log('Token invalid')
30+
break
31+
default:
32+
console.log('Something went wrong')
33+
break
34+
}
35+
36+
redirect(`login?from=${encodeURIComponent(from)}`, RedirectType.replace)
37+
}
38+
}
39+
}

middleware.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { NextResponse } from 'next/server'
2+
import { NextRequest, MiddlewareConfig } from 'next/server'
3+
4+
export async function middleware(request: NextRequest) {
5+
if (!request.cookies.has(process.env.SESSION_COOKIE_NAME!)) {
6+
const loginUrl = new URL('/login', request.url)
7+
loginUrl.searchParams.set('from', request.nextUrl.pathname)
8+
return NextResponse.redirect(loginUrl)
9+
}
10+
}
11+
12+
export const config: MiddlewareConfig = {
13+
matcher: ['/subscriptions/:path*', '/borrows/:path*'],
14+
}

0 commit comments

Comments
 (0)