From c4ece25c612412e3c20e59d2edb04bffb716105a Mon Sep 17 00:00:00 2001 From: smanylov Date: Fri, 28 Nov 2025 15:28:15 +0700 Subject: [PATCH] add auth, logout --- package.json | 2 +- src/app/actions/auth.ts | 64 +++++++++++++++++++++++++++++++++----- src/app/lib/definitions.ts | 42 ++++++++++++++++--------- src/app/lib/session.ts | 27 +++++++++++++--- src/app/ui/nav-links.tsx | 22 +++++++++---- src/proxy.ts | 2 +- 6 files changed, 125 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 17fb2a6..6459c6e 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev -p 2999", "build": "next build", "start": "next start", "lint": "eslint" diff --git a/src/app/actions/auth.ts b/src/app/actions/auth.ts index c2e6d5a..e011703 100644 --- a/src/app/actions/auth.ts +++ b/src/app/actions/auth.ts @@ -1,25 +1,70 @@ 'use server' -import { SignupFormSchema, FormState } from '@/app/lib/definitions' -import { createSession, deleteSession } from '@/app/lib/session' +import { SignupFormSchema, loginFormSchema } from '@/app/lib/definitions'; +import { createSession, deleteSession, getSessionToken } from '@/app/lib/session'; import { redirect } from 'next/navigation' export async function logout() { - await deleteSession() + const token = await getSessionToken(); + + try { + await fetch('http://localhost:8080/api/auth/logout', { + method: 'POST', + headers: { + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": `Bearer ${token}`, + } + }); + } catch (error) { + throw error; + } + + await deleteSession(); + redirect('/login'); } export async function authorization( state: string | undefined, formData: FormData, ) { + const validatedFields = loginFormSchema.safeParse({ + email: formData.get('email'), + password: formData.get('password'), + }) + + if (!validatedFields.success) { + return 'error'; + } + try { - console.log(formData); + const { email, password } = validatedFields.data; + const response = await fetch('http://localhost:8080/api/auth/login', { + method: 'POST', + body: JSON.stringify({ + email: email, + password: password + }), + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }); + if (response.ok) { + let parsed = await response.json(); + await createSession(parsed.token); + } else { + throw new Error('Something went wrong. from server'); + } + } catch (error) { if (error) { - return 'Something went wrong.'; + return 'Something went wrong. from server'; } throw error; } + + redirect('/pages/dashboard'); } export async function registration( @@ -59,8 +104,13 @@ export async function registration( "Accept": "application/json" } }); - let parsed = await response.json(); - await createSession(parsed.token); + + if (response.ok) { + let parsed = await response.json(); + await createSession(parsed.token); + } else { + throw new Error('Something went wrong. from server'); + } } catch (error) { if (error) { const errors: Record = {} diff --git a/src/app/lib/definitions.ts b/src/app/lib/definitions.ts index 66dbf87..b08bc60 100644 --- a/src/app/lib/definitions.ts +++ b/src/app/lib/definitions.ts @@ -1,5 +1,21 @@ import * as z from 'zod' +export type FormState = + | { + errors?: { + name?: string[] + email?: string[] + password?: string[] + } + message?: string + } + | undefined + +export type FormAction = ( + state: FormState, + formData: FormData +) => Promise + export const SignupFormSchema = z .object({ full_name: z @@ -21,18 +37,14 @@ export const SignupFormSchema = z path: ['confirm_password'], }); -export type FormState = - | { - errors?: { - name?: string[] - email?: string[] - password?: string[] - } - message?: string - } - | undefined - -export type FormAction = ( - state: FormState, - formData: FormData -) => Promise \ No newline at end of file +export const loginFormSchema = z + .object({ + email: z + .string() + .min(1, { error: 'Email must be not empty' }) + .trim(), + password: z + .string() + .min(1, { error: 'Password must be not empty' }) + .trim(), + }) \ No newline at end of file diff --git a/src/app/lib/session.ts b/src/app/lib/session.ts index fe30b5c..2497f84 100644 --- a/src/app/lib/session.ts +++ b/src/app/lib/session.ts @@ -25,10 +25,10 @@ export async function decrypt(session: string | undefined = '') { } } -export async function createSession(userId: string) { - const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) - const session = await encrypt({ userId, expiresAt }) - const cookieStore = await cookies() +export async function createSession(token: string) { + const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); + const session = await encrypt({ token, expiresAt }); + const cookieStore = await cookies(); cookieStore.set('session', session, { httpOnly: true, @@ -39,6 +39,25 @@ export async function createSession(userId: string) { }) } +export async function getSessionToken(): Promise { + const cookieStore = await cookies(); + const sessionCookie = cookieStore.get('session')?.value; + + if (!sessionCookie) { + return null; + } + + const payload = await decrypt(sessionCookie); + + if (payload && typeof payload === 'object' && 'token' in payload) { + return payload.token as string; + } + + return null; +} + + + export async function deleteSession() { const cookieStore = await cookies() cookieStore.delete('session') diff --git a/src/app/ui/nav-links.tsx b/src/app/ui/nav-links.tsx index 32e64ce..8a04abe 100644 --- a/src/app/ui/nav-links.tsx +++ b/src/app/ui/nav-links.tsx @@ -6,6 +6,7 @@ import clsx from 'clsx'; import styles from './ui.module.scss' import Logo from '@/app/ui/logo'; import DropDownList from '@/app/ui/nav-link-dropdown'; +import { logout } from '@/app/actions/auth'; const links = [ { @@ -42,12 +43,7 @@ const links = [ name: 'API', href: '/pages/page1', img: 'M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0L19.2 12l-4.6-4.6L16 6l6 6-6 6-1.4-1.4z' - }, - { - name: 'Выход', - href: '/pages/page1', - img: 'M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z' - }, + } ]; export default function NavLinks() { @@ -95,6 +91,20 @@ export default function NavLinks() { ); })} + { + e.preventDefault(); + logout(); + }} + > + + + +

Выход

+ diff --git a/src/proxy.ts b/src/proxy.ts index c81b965..49b94f7 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -15,7 +15,7 @@ export default async function proxy(request: NextRequest) { console.log(!!cookie); const session = await decrypt(cookie); - if (isProtectedRoute && !session?.userId) { + if (isProtectedRoute && !session?.token) { return NextResponse.redirect(new URL('/login', request.nextUrl)) }