import { createClerkClient, verifyToken } from '@clerk/backend'
import { log } from './utils'
interface ClerkSessionData {
id: string
userId: string
expireAt: number
}
interface ClerkUserData {
id: string
email: string
firstName: string
lastName: string
imageUrl: string | null
}
export async function verifyClerkToken(
token: string,
secretKey: string,
): Promise<ClerkSessionData> {
try {
const clerkClient = createClerkClient({ secretKey })
// Try JWT verification first (for session tokens)
try {
const payload = await verifyToken(token, { secretKey })
log.info('Successfully verified Clerk JWT token', {
userId: payload.sub,
sessionId: payload.sid,
})
// Get the session using the session ID from the JWT
const session = await clerkClient.sessions.getSession(payload.sid)
return session
} catch (jwtError) {
log.warn('JWT verification failed, trying direct session lookup', {
error: jwtError instanceof Error ? jwtError.message : String(jwtError),
})
throw jwtError
}
} catch (error) {
log.error('Error verifying Clerk token', {
error: error instanceof Error ? error.message : String(error),
})
throw new Error('Invalid authentication token')
}
}
export async function getClerkUser(
userId: string,
secretKey: string,
): Promise<ClerkUserData> {
try {
const clerkClient = createClerkClient({ secretKey })
const user = await clerkClient.users.getUser(userId)
return {
id: userId,
email: user.emailAddresses?.[0]?.emailAddress || 'unknown@example.com',
firstName: user.firstName || 'Unknown',
lastName: user.lastName || 'User',
imageUrl: user.imageUrl || null,
}
} catch (error) {
log.error('Error fetching Clerk user', {
userId,
error: error instanceof Error ? error.message : String(error),
})
throw new Error('Failed to fetch user information')
}
}
export async function refreshSessionToken(
sessionId: string,
secretKey: string,
templateName: string = 'default',
): Promise<string> {
const clerkClient = createClerkClient({ secretKey })
try {
// First verify the session is still active
const session = await clerkClient.sessions.getSession(sessionId)
if (session.status !== 'active') {
throw new Error(`Session is not active: ${session.status}`)
}
// Try to get a new token using the specified template
let newToken: string | undefined
try {
const tokenResult = await clerkClient.sessions.getToken(
sessionId,
templateName,
)
newToken = tokenResult.jwt
log.info(`Successfully used template: ${templateName}`)
} catch {
log.info(
`Template '${templateName}' not found, trying fallback templates...`,
)
// Try common template names or create a basic token
const fallbackTemplates = ['default', 'firebase', 'auth0', 'custom']
for (const template of fallbackTemplates) {
try {
const tokenResult = await clerkClient.sessions.getToken(
sessionId,
template,
)
newToken = tokenResult.jwt
log.info(`Successfully used fallback template: ${template}`)
break
} catch (e) {
log.error('Error getting token', {
template,
error: e instanceof Error ? e.message : String(e),
})
continue
}
}
if (!newToken) {
// If no templates work, the user needs to create one or we need a different approach
throw new Error(
`No valid JWT templates found. Please create a "${templateName}" template in Clerk Dashboard or use one of: ${fallbackTemplates.join(', ')}`,
)
}
}
log.info('Successfully refreshed session token')
return newToken
} catch (error) {
log.error('Token refresh failed', {
error: error instanceof Error ? error.message : String(error),
})
throw error
}
}
export async function getToken(
tokenManager: {
sessionToken: string
sessionId: string
updateToken: (newToken: string) => void
},
secretKey: string,
templateName: string = 'default',
): Promise<string> {
let token = tokenManager.sessionToken
// Check if token is available
if (!token) {
throw new Error('No session token available')
}
// Check token expiration and refresh if needed
try {
const payload = JSON.parse(atob(token.split('.')[1] ?? ''))
const expTime = payload.exp * 1000 // Convert to milliseconds
const now = Date.now()
if (expTime <= now) {
log.info('Token has expired, attempting refresh...')
token = await refreshSessionToken(
tokenManager.sessionId,
secretKey,
templateName,
)
tokenManager.updateToken(token)
} else if (expTime - now < 5 * 60 * 1000) {
log.info('Token expires soon, attempting refresh...')
token = await refreshSessionToken(
tokenManager.sessionId,
secretKey,
templateName,
)
tokenManager.updateToken(token)
}
} catch (error) {
log.warn('Could not parse token expiration', {
error: error instanceof Error ? error.message : String(error),
})
}
log.info('Returning session token', {
tokenPrefix: token?.substring(0, 20) + '...',
})
return token
}