-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from codeforpakistan/werk
SG Integration
- Loading branch information
Showing
10 changed files
with
307 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { KEYCLOAK_URLS, getKeycloakHeaders } from '@/lib/keycloak-config' | ||
import { cookies } from 'next/headers' | ||
import { NextResponse } from 'next/server' | ||
|
||
export async function GET() { | ||
try { | ||
const accessToken = cookies().get('access_token')?.value | ||
|
||
if (!accessToken) { | ||
return NextResponse.json({ error: 'No access token found' }, { status: 401 }) | ||
} | ||
|
||
const response = await fetch(KEYCLOAK_URLS.USERINFO, { | ||
headers: getKeycloakHeaders(accessToken) | ||
}) | ||
|
||
if (!response.ok) { | ||
throw new Error('Failed to fetch user services') | ||
} | ||
|
||
const data = await response.json() | ||
return NextResponse.json(data) | ||
} catch (error) { | ||
return NextResponse.json( | ||
{ error: 'Failed to fetch user services' }, | ||
{ status: 500 } | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { keycloakAdmin } from '@/lib/keycloak-admin' | ||
import { cookies } from 'next/headers' | ||
import { NextResponse } from 'next/server' | ||
import { KEYCLOAK_URLS } from '@/lib/keycloak-config' | ||
|
||
interface KeycloakSession { | ||
id?: string | ||
username?: string | ||
userId?: string | ||
ipAddress?: string | ||
start?: number | ||
lastAccess?: number | ||
clients?: { [key: string]: string } | ||
} | ||
|
||
export async function GET() { | ||
try { | ||
const accessToken = cookies().get('access_token')?.value | ||
if (!accessToken) { | ||
return NextResponse.json({ error: 'No access token found' }, { status: 401 }) | ||
} | ||
|
||
// Get user info to get the user ID | ||
const userInfoResponse = await fetch( | ||
`${KEYCLOAK_URLS.USERINFO}`, | ||
{ | ||
headers: { | ||
'Authorization': `Bearer ${accessToken}`, | ||
} | ||
} | ||
) | ||
const userInfo = await userInfoResponse.json() | ||
|
||
// Get sessions using the admin client | ||
const sessions = await keycloakAdmin.getUserSessions(userInfo.sub) | ||
|
||
console.log('Sessions:', sessions) | ||
|
||
// Transform the sessions data to match our interface | ||
const formattedSessions = sessions.map((session: KeycloakSession) => { | ||
// Get the first client name from the clients object, if it exists | ||
const clientId = session.clients ? Object.keys(session.clients)[0] : undefined | ||
const clientName = clientId && session.clients?.[clientId] | ||
|
||
return { | ||
clientId, | ||
clientName, | ||
active: true, | ||
lastAccess: session.lastAccess ? new Date(session.lastAccess).toISOString() : undefined, | ||
started: session.start ? new Date(session.start).toISOString() : undefined, | ||
ipAddress: session.ipAddress | ||
} | ||
}) | ||
|
||
return NextResponse.json(formattedSessions) | ||
|
||
} catch (error: any) { | ||
console.error('Session fetch error:', error) | ||
return NextResponse.json( | ||
{ error: error.message || 'Failed to fetch user sessions' }, | ||
{ status: 500 } | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { NextResponse } from 'next/server' | ||
import Sendgrid from '@sendgrid/mail' | ||
|
||
export async function POST(request: Request) { | ||
try { | ||
const { email, code } = await request.json() | ||
|
||
if (!email || !code) { | ||
return NextResponse.json( | ||
{ error: 'Email and code are required' }, | ||
{ status: 400 } | ||
) | ||
} | ||
|
||
Sendgrid.setApiKey(process.env.SENDGRID_API_KEY as string) | ||
|
||
const msg = { | ||
to: email, | ||
from: '[email protected]', // Replace with your verified sender | ||
templateId: 'd-070a0738fac147eb878f87b86eed664c', | ||
dynamicTemplateData: { | ||
VCODE: code | ||
} | ||
} | ||
|
||
await Sendgrid.send(msg) | ||
|
||
return NextResponse.json({ success: true }) | ||
} catch (error) { | ||
console.error('Error sending email:', error) | ||
return NextResponse.json( | ||
{ error: 'Failed to send verification email' }, | ||
{ status: 500 } | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { useEffect, useState } from 'react' | ||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card' | ||
import { Shield, ExternalLink } from 'lucide-react' | ||
import { Badge } from '@/components/ui/badge' | ||
|
||
interface ClientSession { | ||
clientId: string | ||
clientName?: string | ||
active: boolean | ||
lastAccess: string | ||
started: string | ||
ipAddress: string | ||
} | ||
|
||
export function UserServices() { | ||
const [sessions, setSessions] = useState<ClientSession[]>([]) | ||
const [loading, setLoading] = useState(true) | ||
|
||
useEffect(() => { | ||
const fetchSessions = async () => { | ||
try { | ||
const response = await fetch('/api/auth/sessions') | ||
const data = await response.json() | ||
setSessions(Array.isArray(data) ? data : []) | ||
} catch (error) { | ||
console.error('Failed to fetch sessions:', error) | ||
setSessions([]) | ||
} finally { | ||
setLoading(false) | ||
} | ||
} | ||
|
||
fetchSessions() | ||
}, []) | ||
|
||
return ( | ||
<Card> | ||
<CardHeader> | ||
<CardTitle className="flex items-center"> | ||
<Shield className="mr-2 h-4 w-4" /> | ||
Connected Services | ||
</CardTitle> | ||
</CardHeader> | ||
<CardContent> | ||
{loading ? ( | ||
<div>Loading active services...</div> | ||
) : ( | ||
<div className="space-y-2"> | ||
{sessions.length === 0 ? ( | ||
<div className="text-muted-foreground text-sm"> | ||
No active services found | ||
</div> | ||
) : ( | ||
sessions.map((session) => ( | ||
<div key={session.clientId} className="flex justify-between items-center p-3 rounded-lg bg-muted"> | ||
<div className="flex flex-col"> | ||
<div className="flex items-center space-x-2"> | ||
<ExternalLink className="h-4 w-4 text-muted-foreground" /> | ||
<span className="font-medium">{session.clientName}</span> | ||
</div> | ||
<span className="text-xs text-muted-foreground">IP: {session.ipAddress}</span> | ||
<span className="text-xs text-muted-foreground"> | ||
Started: {new Date(session.started).toLocaleString()} | ||
</span> | ||
</div> | ||
<Badge className="bg-green-500 text-white">Active</Badge> | ||
</div> | ||
)) | ||
)} | ||
</div> | ||
)} | ||
</CardContent> | ||
</Card> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export async function getAccessToken() { | ||
const response = await fetch('/api/auth/token') | ||
if (!response.ok) { | ||
throw new Error('Failed to get access token') | ||
} | ||
const { accessToken } = await response.json() | ||
return accessToken | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.