186 lines
5.0 KiB
TypeScript
186 lines
5.0 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { auth } from '@/lib/auth'
|
|
import { netcupService, NetcupAuthError } from '@/lib/services/netcup-service'
|
|
|
|
// Store pending device auth sessions (in-memory for simplicity)
|
|
// In production, consider storing in Redis or database
|
|
const pendingAuthSessions = new Map<
|
|
string,
|
|
{
|
|
deviceCode: string
|
|
expiresAt: number
|
|
interval: number
|
|
}
|
|
>()
|
|
|
|
/**
|
|
* GET /api/v1/admin/netcup/auth
|
|
* Get current authentication status
|
|
*/
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const session = await auth()
|
|
|
|
if (!session || session.user.userType !== 'staff') {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
}
|
|
|
|
const status = await netcupService.getAuthStatus()
|
|
|
|
return NextResponse.json(status)
|
|
} catch (error) {
|
|
console.error('Error getting Netcup auth status:', error)
|
|
return NextResponse.json(
|
|
{ error: 'Failed to get auth status' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/admin/netcup/auth
|
|
* Initiate device auth flow or poll for token
|
|
*
|
|
* Body:
|
|
* - action: 'initiate' | 'poll' | 'disconnect'
|
|
* - sessionId?: string (for poll action)
|
|
*/
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const session = await auth()
|
|
|
|
if (!session || session.user.userType !== 'staff') {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
}
|
|
|
|
const body = await request.json()
|
|
const { action, sessionId } = body as {
|
|
action: 'initiate' | 'poll' | 'disconnect'
|
|
sessionId?: string
|
|
}
|
|
|
|
if (!action || !['initiate', 'poll', 'disconnect'].includes(action)) {
|
|
return NextResponse.json(
|
|
{ error: 'Invalid action. Must be: initiate, poll, or disconnect' },
|
|
{ status: 400 }
|
|
)
|
|
}
|
|
|
|
switch (action) {
|
|
case 'initiate': {
|
|
// Start device auth flow
|
|
const deviceAuth = await netcupService.initiateDeviceAuth()
|
|
|
|
// Store session for polling
|
|
const newSessionId = crypto.randomUUID()
|
|
pendingAuthSessions.set(newSessionId, {
|
|
deviceCode: deviceAuth.device_code,
|
|
expiresAt: Date.now() + deviceAuth.expires_in * 1000,
|
|
interval: deviceAuth.interval,
|
|
})
|
|
|
|
// Clean up expired sessions
|
|
for (const [id, sess] of pendingAuthSessions) {
|
|
if (sess.expiresAt < Date.now()) {
|
|
pendingAuthSessions.delete(id)
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
sessionId: newSessionId,
|
|
userCode: deviceAuth.user_code,
|
|
verificationUri: deviceAuth.verification_uri,
|
|
verificationUriComplete: deviceAuth.verification_uri_complete,
|
|
expiresIn: deviceAuth.expires_in,
|
|
interval: deviceAuth.interval,
|
|
})
|
|
}
|
|
|
|
case 'poll': {
|
|
if (!sessionId) {
|
|
return NextResponse.json(
|
|
{ error: 'Session ID required for polling' },
|
|
{ status: 400 }
|
|
)
|
|
}
|
|
|
|
const pendingSession = pendingAuthSessions.get(sessionId)
|
|
|
|
if (!pendingSession) {
|
|
return NextResponse.json(
|
|
{ error: 'Session not found or expired' },
|
|
{ status: 404 }
|
|
)
|
|
}
|
|
|
|
if (pendingSession.expiresAt < Date.now()) {
|
|
pendingAuthSessions.delete(sessionId)
|
|
return NextResponse.json(
|
|
{ error: 'Session expired' },
|
|
{ status: 410 }
|
|
)
|
|
}
|
|
|
|
try {
|
|
const tokens = await netcupService.pollForToken(pendingSession.deviceCode)
|
|
|
|
if (!tokens) {
|
|
// Still waiting for user authorization
|
|
return NextResponse.json({
|
|
success: false,
|
|
status: 'pending',
|
|
message: 'Waiting for user authorization',
|
|
})
|
|
}
|
|
|
|
// Success! Clean up session
|
|
pendingAuthSessions.delete(sessionId)
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
status: 'authenticated',
|
|
message: 'Successfully authenticated with Netcup',
|
|
})
|
|
} catch (error) {
|
|
if (error instanceof NetcupAuthError) {
|
|
pendingAuthSessions.delete(sessionId)
|
|
return NextResponse.json(
|
|
{ error: error.message },
|
|
{ status: 400 }
|
|
)
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
case 'disconnect': {
|
|
// Clear stored tokens
|
|
await netcupService.clearTokens()
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: 'Disconnected from Netcup',
|
|
})
|
|
}
|
|
|
|
default:
|
|
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in Netcup auth:', error)
|
|
|
|
if (error instanceof NetcupAuthError) {
|
|
return NextResponse.json(
|
|
{ error: error.message },
|
|
{ status: 400 }
|
|
)
|
|
}
|
|
|
|
return NextResponse.json(
|
|
{ error: 'Failed to process auth request' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|