Include full contents of all nested repositories
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
185
letsbe-hub/src/app/api/v1/admin/netcup/auth/route.ts
Normal file
185
letsbe-hub/src/app/api/v1/admin/netcup/auth/route.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user