diff --git a/app/api-docs/page.tsx b/app/api-docs/page.tsx
new file mode 100644
index 0000000..f3841ef
--- /dev/null
+++ b/app/api-docs/page.tsx
@@ -0,0 +1,12 @@
+'use client'
+
+import SwaggerUI from 'swagger-ui-react'
+import 'swagger-ui-react/swagger-ui.css'
+
+export default function ApiDocs() {
+ return (
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/app/api/api-docs/route.ts b/app/api/api-docs/route.ts
new file mode 100644
index 0000000..432e70c
--- /dev/null
+++ b/app/api/api-docs/route.ts
@@ -0,0 +1,47 @@
+import { NextResponse } from 'next/server'
+import { createSwaggerSpec } from 'next-swagger-doc'
+
+const spec = createSwaggerSpec({
+ apiFolder: 'app/api',
+ definition: {
+ openapi: '3.0.0',
+ info: {
+ title: 'Pehchan API Documentation',
+ version: '1.0.0',
+ description: 'Documentation for Pehchan Authentication Service',
+ },
+ servers: [
+ {
+ url: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000',
+ description: 'API Server',
+ },
+ ],
+ tags: [
+ {
+ name: 'Authentication',
+ description: 'Authentication related endpoints'
+ },
+ {
+ name: 'User Profile',
+ description: 'User profile management endpoints'
+ },
+ {
+ name: 'Sessions',
+ description: 'Session management endpoints'
+ }
+ ],
+ components: {
+ securitySchemes: {
+ bearerAuth: {
+ type: 'http',
+ scheme: 'bearer',
+ bearerFormat: 'JWT'
+ }
+ }
+ }
+ },
+})
+
+export async function GET() {
+ return NextResponse.json(spec)
+}
diff --git a/app/api/auth/forgot-password/route.ts b/app/api/auth/forgot-password/route.ts
new file mode 100644
index 0000000..97bbb54
--- /dev/null
+++ b/app/api/auth/forgot-password/route.ts
@@ -0,0 +1,57 @@
+import { NextResponse } from 'next/server'
+import Sendgrid from '@sendgrid/mail'
+import { keycloakAdmin } from '@/lib/keycloak-admin'
+import jwt from 'jsonwebtoken'
+
+const RESET_TOKEN_SECRET = process.env.RESET_TOKEN_SECRET || 'your-secret-key'
+const APP_URL = process.env.NEXT_PUBLIC_APP_URL
+
+export async function POST(request: Request) {
+ try {
+ const { email } = await request.json()
+
+ // Find user in Keycloak
+ const user = await keycloakAdmin.getUserByEmail(email)
+ console.log(user)
+
+ if (!user) {
+ // Return success even if user not found (security best practice)
+ return NextResponse.json({ success: true })
+ }
+
+ // Generate reset token
+ const resetToken = jwt.sign(
+ {
+ userId: user.id,
+ email: user.email
+ },
+ RESET_TOKEN_SECRET,
+ { expiresIn: '1h' }
+ )
+
+ // Create reset URL
+ const resetUrl = `${APP_URL}/reset-password?token=${resetToken}`
+
+ // Send email using SendGrid
+ Sendgrid.setApiKey(process.env.SENDGRID_API_KEY as string)
+
+ const msg = {
+ to: email,
+ from: 'civicflow@codeforpakistan.org',
+ templateId: 'd-25d9e6f70e8e463f8a8297560b19b488',
+ dynamicTemplateData: {
+ resetUrl: resetUrl
+ }
+ }
+
+ await Sendgrid.send(msg)
+
+ return NextResponse.json({ success: true })
+ } catch (error) {
+ console.error('Password reset error:', error)
+ return NextResponse.json(
+ { error: 'Failed to process password reset request' },
+ { status: 500 }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts
index 52ad7e5..4d85ce5 100644
--- a/app/api/auth/login/route.ts
+++ b/app/api/auth/login/route.ts
@@ -1,6 +1,34 @@
import { NextResponse } from 'next/server'
import { KEYCLOAK_CONFIG, KEYCLOAK_URLS } from '@/lib/keycloak-config'
+/**
+ * @swagger
+ * /api/auth/login:
+ * post:
+ * tags: [Authentication]
+ * summary: Authenticate user
+ * description: Login with username and password
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * username:
+ * type: string
+ * password:
+ * type: string
+ * clientId:
+ * type: string
+ * redirectUri:
+ * type: string
+ * responses:
+ * 200:
+ * description: Login successful
+ * 401:
+ * description: Authentication failed
+ */
export async function POST(request: Request) {
try {
const { username, password, clientId, redirectUri } = await request.json()
diff --git a/app/api/auth/reset-password/route.ts b/app/api/auth/reset-password/route.ts
new file mode 100644
index 0000000..1c28623
--- /dev/null
+++ b/app/api/auth/reset-password/route.ts
@@ -0,0 +1,24 @@
+import { NextResponse } from 'next/server'
+import { keycloakAdmin } from '@/lib/keycloak-admin'
+import jwt from 'jsonwebtoken'
+
+const RESET_TOKEN_SECRET = process.env.RESET_TOKEN_SECRET || 'your-secret-key'
+
+export async function POST(request: Request) {
+ try {
+ const { token, password } = await request.json()
+ const decoded = jwt.verify(token, RESET_TOKEN_SECRET) as {
+ userId: string
+ email: string
+ }
+
+ await keycloakAdmin.resetUserPassword(decoded.userId, password)
+ return NextResponse.json({ success: true })
+ } catch (error) {
+ console.error('Password reset error:', error)
+ return NextResponse.json(
+ { error: 'Failed to reset password' },
+ { status: 500 }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/api/auth/sessions/route.ts b/app/api/auth/sessions/route.ts
index 32d5157..93a9105 100644
--- a/app/api/auth/sessions/route.ts
+++ b/app/api/auth/sessions/route.ts
@@ -3,6 +3,8 @@ import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
import { KEYCLOAK_URLS } from '@/lib/keycloak-config'
+export const dynamic = 'force-dynamic'
+
interface KeycloakSession {
id?: string
username?: string
diff --git a/app/api/auth/userinfo/route.ts b/app/api/auth/userinfo/route.ts
index 65e2f32..4554e0b 100644
--- a/app/api/auth/userinfo/route.ts
+++ b/app/api/auth/userinfo/route.ts
@@ -15,6 +15,40 @@ export async function OPTIONS() {
})
}
+/**
+ * @swagger
+ * /api/auth/userinfo:
+ * get:
+ * summary: Get user information
+ * description: Retrieve authenticated user's profile information
+ * security:
+ * - bearerAuth: []
+ * responses:
+ * 200:
+ * description: User information retrieved successfully
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * sub:
+ * type: string
+ * email:
+ * type: string
+ * email_verified:
+ * type: boolean
+ * name:
+ * type: string
+ * profile:
+ * type: object
+ * properties:
+ * cnic:
+ * type: string
+ * phone:
+ * type: string
+ * 401:
+ * description: Unauthorized
+ */
export async function GET(request: Request) {
try {
// Get the access token from Authorization header
diff --git a/app/api/profile/update/route.ts b/app/api/profile/update/route.ts
index b07434c..f347635 100644
--- a/app/api/profile/update/route.ts
+++ b/app/api/profile/update/route.ts
@@ -2,6 +2,36 @@ import { NextResponse } from 'next/server'
import { cookies } from 'next/headers'
import { supabase } from '@/lib/supabase'
+/**
+ * @swagger
+ * /api/profile/update:
+ * post:
+ * tags: [User Profile]
+ * summary: Update user profile
+ * description: Update authenticated user's profile information
+ * security:
+ * - bearerAuth: []
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * full_name:
+ * type: string
+ * phone:
+ * type: string
+ * cnic:
+ * type: string
+ * avatar_url:
+ * type: string
+ * responses:
+ * 200:
+ * description: Profile updated successfully
+ * 401:
+ * description: Unauthorized
+ */
export async function POST(request: Request) {
try {
const accessToken = cookies().get('access_token')?.value
diff --git a/app/forgot-password/page.tsx b/app/forgot-password/page.tsx
new file mode 100644
index 0000000..cc20e86
--- /dev/null
+++ b/app/forgot-password/page.tsx
@@ -0,0 +1,80 @@
+"use client"
+
+import { useState } from 'react'
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { useToast } from "@/hooks/use-toast"
+
+export default function ForgotPassword() {
+ const [email, setEmail] = useState('')
+ const [isLoading, setIsLoading] = useState(false)
+ const { toast } = useToast()
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ setIsLoading(true)
+
+ try {
+ const response = await fetch('/api/auth/forgot-password', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ email }),
+ })
+
+ if (!response.ok) {
+ throw new Error('Failed to send reset email')
+ }
+
+ toast({
+ title: "Success",
+ description: "If an account exists with this email, you will receive password reset instructions.",
+ })
+
+ } catch (error) {
+ toast({
+ variant: "destructive",
+ title: "Error",
+ description: "Failed to send reset email. Please try again.",
+ })
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ return (
+
+
+
+ Reset Password
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/app/login/page.tsx b/app/login/page.tsx
index 214a2c0..7283689 100644
--- a/app/login/page.tsx
+++ b/app/login/page.tsx
@@ -11,6 +11,8 @@ import { Input } from "@/components/ui/input"
import { Card, CardContent, CardFooter, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { useToast } from "@/hooks/use-toast"
import { Label } from "@/components/ui/label"
+import Link from 'next/link'
+
export default function LoginPage() {
const router = useRouter()
const { toast } = useToast()
@@ -126,6 +128,14 @@ export default function LoginPage() {
required
/>
+
+
+ Forgot password?
+
+