diff --git a/app/api/auth/log-in/route.ts b/app/api/auth/log-in/route.ts new file mode 100644 index 0000000..b44de5b --- /dev/null +++ b/app/api/auth/log-in/route.ts @@ -0,0 +1,76 @@ +// app/api/auth/log-in/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import { z } from 'zod'; +import { createServerClient } from '@/lib/supabase/server'; + +// Validation schema for log-in +const logInSchema = z.object({ + email: z + .string() + .email('Invalid email address') + .transform((val) => val.toLowerCase().trim()), + password: z + .string() + .min(8, 'Password must be at least 8 characters') + .regex( + /^(?=.*[A-Z])(?=.*[!@#$%^&*])/, + 'Password must contain at least 1 uppercase letter and 1 special symbol', + ), +}); + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + + // Validate request body + const validatedData = logInSchema.parse(body); + + // Create Supabase client + const supabase = createServerClient(); + + // Log in user with Supabase Auth + const { data, error } = await supabase.auth.signInWithPassword({ + email: validatedData.email, + password: validatedData.password, + }); + + if (error) { + return NextResponse.json( + { + error: error.message, + }, + { status: 401 }, + ); + } + + return NextResponse.json( + { + message: 'Login successful', + user: { + id: data.user?.id, + email: data.user?.email, + }, + session: { + access_token: data.session?.access_token, + }, + }, + { status: 200 }, + ); + } catch (error: unknown) { + if (error instanceof z.ZodError) { + return NextResponse.json( + { + error: 'Validation failed', + details: (error as z.ZodError).errors, + }, + { status: 400 }, + ); + } + + console.error('Log-in error:', error); + return NextResponse.json( + { error: 'Internal server error' }, + { status: 500 }, + ); + } +} diff --git a/app/api/auth/sign-in/route.ts b/app/api/auth/sign-in/route.ts new file mode 100644 index 0000000..f870098 --- /dev/null +++ b/app/api/auth/sign-in/route.ts @@ -0,0 +1,79 @@ +// app/api/auth/sign-in/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import { z } from 'zod'; +import { createServerClient } from '@/lib/supabase/server'; + +// Validation schema matching acceptance criteria +const signInSchema = z + .object({ + email: z + .string() + .email('Invalid email address') + .transform((val) => val.toLowerCase().trim()), + password: z + .string() + .min(8, 'Password must be at least 8 characters') + .regex( + /^(?=.*[A-Z])(?=.*[!@#$%^&*])/, + 'Password must contain at least 1 uppercase letter and 1 special symbol', + ), + confirm_password: z.string(), + }) + .refine((data) => data.password === data.confirm_password, { + message: 'Passwords must match exactly', + path: ['confirm_password'], + }); + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + + // Validate request body + const validatedData = signInSchema.parse(body); + + // Create Supabase client + const supabase = createServerClient(); + + // Sign up user with Supabase Auth + const { data, error } = await supabase.auth.signUp({ + email: validatedData.email, + password: validatedData.password, + }); + + if (error) { + return NextResponse.json( + { + error: error.message, + }, + { status: 400 }, + ); + } + + return NextResponse.json( + { + message: 'User registered successfully', + user: { + id: data.user?.id, + email: data.user?.email, + }, + }, + { status: 201 }, + ); + } catch (error: unknown) { + if (error instanceof z.ZodError) { + return NextResponse.json( + { + error: 'Validation failed', + details: (error as z.ZodError).errors, + }, + { status: 400 }, + ); + } + + console.error('Sign-in error:', error); + return NextResponse.json( + { error: 'Internal server error' }, + { status: 500 }, + ); + } +} diff --git a/package-lock.json b/package-lock.json index c6d1afb..2a15345 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "react": "19.2.1", "react-dom": "19.2.1", "tailwind-merge": "^3.4.0", + "zod": "^4.3.6", "zustand": "^5.0.9" }, "devDependencies": { @@ -9203,10 +9204,9 @@ } }, "node_modules/zod": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", - "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", - "dev": true, + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 6d75842..9328629 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "react": "19.2.1", "react-dom": "19.2.1", "tailwind-merge": "^3.4.0", + "zod": "^4.3.6", "zustand": "^5.0.9" }, "devDependencies": {