Skip to content

Commit c8d38ee

Browse files
committed
feat: implement authentication system with NextAuth - Created auth.ts for handling user authentication and session management - Added API routes for login, registration, password reset, and email verification - Integrated Google authentication and cookie management - Developed user interface components for sign-in, sign-up, and password recovery - Established middleware for protected routes and session management
1 parent 817a75a commit c8d38ee

37 files changed

Lines changed: 3317 additions & 8 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import { handlers } from '@/auth'; // Referring to the auth.ts we just created
2+
export const { GET, POST } = handlers;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { NextResponse } from 'next/server';
2+
import { googleAuth } from '@/lib/api/auth';
3+
import { z } from 'zod';
4+
5+
const googleSchema = z.object({
6+
token: z.string().min(1, 'Google token is required'),
7+
});
8+
9+
export async function POST(req: Request) {
10+
try {
11+
const body = await req.json();
12+
const { token } = googleSchema.parse(body);
13+
const apiRes = await googleAuth({ token });
14+
return NextResponse.json(apiRes, { status: 200 });
15+
} catch (error) {
16+
if (error instanceof z.ZodError) {
17+
return NextResponse.json({ message: error.message }, { status: 400 });
18+
}
19+
if (typeof error === 'object' && error !== null && 'message' in error) {
20+
return NextResponse.json(
21+
{ message: String((error as { message: unknown }).message) },
22+
{ status: 500 }
23+
);
24+
}
25+
return NextResponse.json(
26+
{ message: 'Internal Server Error' },
27+
{ status: 500 }
28+
);
29+
}
30+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { forgotPassword } from '@/lib/api/auth';
2+
import { NextResponse } from 'next/server';
3+
import { z } from 'zod';
4+
5+
const forgotPasswordSchema = z.object({
6+
email: z.string().email('Invalid email address'),
7+
});
8+
9+
export async function POST(req: Request) {
10+
try {
11+
const body = await req.json();
12+
const { email } = forgotPasswordSchema.parse(body);
13+
14+
const response = await forgotPassword({ email });
15+
16+
return NextResponse.json(response, { status: 200 });
17+
} catch (error) {
18+
if (error instanceof z.ZodError) {
19+
return NextResponse.json({ message: error.message }, { status: 400 });
20+
}
21+
if (typeof error === 'object' && error !== null && 'message' in error) {
22+
return NextResponse.json(
23+
{ message: String((error as { message: unknown }).message) },
24+
{ status: 500 }
25+
);
26+
}
27+
return NextResponse.json(
28+
{ message: 'Internal Server Error' },
29+
{ status: 500 }
30+
);
31+
}
32+
}

app/api/auth/login-user/route.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { cookies } from 'next/headers';
3+
import { login } from '@/lib/api/auth';
4+
5+
export async function POST(req: NextRequest) {
6+
try {
7+
const body = await req.json();
8+
const { email, password } = body;
9+
10+
if (!email || !password) {
11+
return NextResponse.json(
12+
{ message: 'Email and password are required' },
13+
{ status: 400 }
14+
);
15+
}
16+
17+
// Call the login function
18+
const response = await login({ email, password });
19+
20+
if (response && response.accessToken) {
21+
// Set cookies using Next.js cookies
22+
const cookieStore = await cookies();
23+
24+
// Set access token cookie
25+
cookieStore.set('accessToken', response.accessToken, {
26+
httpOnly: true,
27+
secure: process.env.NODE_ENV === 'production',
28+
sameSite: 'strict',
29+
maxAge: 3600, // 1 hour
30+
path: '/',
31+
});
32+
33+
// Set refresh token cookie
34+
if (response.refreshToken) {
35+
cookieStore.set('refreshToken', response.refreshToken, {
36+
httpOnly: true,
37+
secure: process.env.NODE_ENV === 'production',
38+
sameSite: 'strict',
39+
maxAge: 604800, // 7 days
40+
path: '/',
41+
});
42+
}
43+
44+
return NextResponse.json(
45+
{
46+
success: true,
47+
message: 'Login successful',
48+
data: {
49+
accessToken: response.accessToken,
50+
refreshToken: response.refreshToken,
51+
},
52+
},
53+
{ status: 200 }
54+
);
55+
} else {
56+
return NextResponse.json(
57+
{ message: 'Invalid credentials' },
58+
{ status: 401 }
59+
);
60+
}
61+
} catch {
62+
return NextResponse.json(
63+
{ message: 'An error occurred during login' },
64+
{ status: 500 }
65+
);
66+
}
67+
}

app/api/auth/register/route.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { register } from '@/lib/api/auth';
2+
import { NextResponse } from 'next/server';
3+
import { z } from 'zod';
4+
5+
const userSchema = z.object({
6+
name: z.string().min(2, 'Name must be at least 2 characters'),
7+
email: z.string().email('Invalid email address'),
8+
password: z.string().min(8, 'Password must be at least 8 characters'),
9+
});
10+
11+
export async function POST(req: Request) {
12+
try {
13+
const body = await req.json();
14+
const { name, email, password } = userSchema.parse(body);
15+
// Compose RegisterRequest
16+
const registerData = {
17+
email,
18+
password,
19+
firstName: name, // or split name if needed
20+
lastName: name, // You may want to parse this from name or add to schema
21+
username: email.split('@')[0],
22+
};
23+
const response = await register(registerData);
24+
return NextResponse.json(response, { status: 201 });
25+
} catch (error) {
26+
if (error instanceof z.ZodError) {
27+
return NextResponse.json({ message: error.message }, { status: 400 });
28+
}
29+
// Handle other types of errors and ensure we always return a string message
30+
const errorMessage =
31+
error instanceof Error ? error.message : 'An unexpected error occurred';
32+
return NextResponse.json({ message: errorMessage }, { status: 500 });
33+
}
34+
}

app/api/auth/resend-otp/route.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { resendOtp } from '@/lib/api/auth';
2+
import { NextResponse } from 'next/server';
3+
import { z } from 'zod';
4+
5+
export async function POST(req: Request) {
6+
try {
7+
const { email } = await req.json();
8+
9+
const response = await resendOtp({ email });
10+
11+
return NextResponse.json(response, { status: 200 });
12+
} catch (error) {
13+
if (error instanceof z.ZodError) {
14+
return NextResponse.json({ message: error.message }, { status: 400 });
15+
}
16+
if (typeof error === 'object' && error !== null && 'message' in error) {
17+
return NextResponse.json(
18+
{ message: String((error as { message: unknown }).message) },
19+
{ status: 500 }
20+
);
21+
}
22+
return NextResponse.json(
23+
{ message: 'Internal Server Error' },
24+
{ status: 500 }
25+
);
26+
}
27+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { resetPassword } from '@/lib/api/auth';
2+
import { NextResponse } from 'next/server';
3+
import { z } from 'zod';
4+
5+
const resetPasswordSchema = z.object({
6+
token: z.string().min(1, 'Token is required'),
7+
newPassword: z.string().min(8, 'Password must be at least 8 characters'),
8+
});
9+
10+
export async function POST(req: Request) {
11+
try {
12+
const body = await req.json();
13+
const { token, newPassword } = resetPasswordSchema.parse(body);
14+
15+
const response = await resetPassword({ token, newPassword });
16+
17+
return NextResponse.json(response, { status: 200 });
18+
} catch (error) {
19+
if (error instanceof z.ZodError) {
20+
return NextResponse.json({ message: error.message }, { status: 400 });
21+
}
22+
if (typeof error === 'object' && error !== null && 'message' in error) {
23+
return NextResponse.json(
24+
{ message: String((error as { message: unknown }).message) },
25+
{ status: 500 }
26+
);
27+
}
28+
return NextResponse.json(
29+
{ message: 'Internal Server Error' },
30+
{ status: 500 }
31+
);
32+
}
33+
}

app/api/auth/test/route.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { NextResponse } from 'next/server';
2+
3+
export async function GET() {
4+
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api';
5+
6+
try {
7+
// Test if the backend is accessible
8+
const response = await fetch(`${apiUrl}/auth/me`, {
9+
method: 'GET',
10+
headers: {
11+
'Content-Type': 'application/json',
12+
},
13+
});
14+
15+
return NextResponse.json({
16+
success: true,
17+
apiUrl,
18+
backendStatus: response.status,
19+
backendOk: response.ok,
20+
message: 'Backend connectivity test completed',
21+
});
22+
} catch (error) {
23+
return NextResponse.json(
24+
{
25+
success: false,
26+
apiUrl,
27+
error: error instanceof Error ? error.message : 'Unknown error',
28+
message: 'Backend connectivity test failed',
29+
},
30+
{ status: 500 }
31+
);
32+
}
33+
}
34+
35+
export async function POST(req: Request) {
36+
try {
37+
const body = await req.json();
38+
const { email, password } = body;
39+
40+
const apiUrl =
41+
process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api';
42+
43+
// Test login endpoint
44+
const response = await fetch(`${apiUrl}/auth/login`, {
45+
method: 'POST',
46+
headers: {
47+
'Content-Type': 'application/json',
48+
},
49+
body: JSON.stringify({ email, password }),
50+
});
51+
52+
const data = await response.json();
53+
54+
return NextResponse.json({
55+
success: response.ok,
56+
status: response.status,
57+
data,
58+
apiUrl,
59+
});
60+
} catch (error) {
61+
return NextResponse.json(
62+
{
63+
success: false,
64+
error: error instanceof Error ? error.message : 'Unknown error',
65+
},
66+
{ status: 500 }
67+
);
68+
}
69+
}

app/api/auth/verify-otp/route.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { verifyOtp } from '@/lib/api/auth';
2+
import { NextResponse } from 'next/server';
3+
import { z } from 'zod';
4+
5+
export async function POST(req: Request) {
6+
try {
7+
const { email, otp } = await req.json();
8+
9+
const response = await verifyOtp({ email, otp });
10+
11+
return NextResponse.json(response, { status: 200 });
12+
} catch (error) {
13+
if (error instanceof z.ZodError) {
14+
return NextResponse.json({ message: error.message }, { status: 400 });
15+
}
16+
if (typeof error === 'object' && error !== null && 'message' in error) {
17+
return NextResponse.json(
18+
{ message: String((error as { message: unknown }).message) },
19+
{ status: 500 }
20+
);
21+
}
22+
return NextResponse.json(
23+
{ message: 'Internal Server Error' },
24+
{ status: 500 }
25+
);
26+
}
27+
}

0 commit comments

Comments
 (0)