Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/llms-txt-hub / apps/web/app/api/user/check-username/route.ts |
| Date | Not specified in source |
| Repository | Janady13/llms-txt-hub |
| Revision | e0f4bf978d4df168369c7601d9c5f5ea89e84377 |
| Branch | main |
| Path or archive member | apps/web/app/api/user/check-username/route.ts |
| Original SHA-256 | 825841b494e39d0ea040f8412222f918c86ca065974033ff5b8c6c7907ee5a8b |
| Git blob SHA | 366047a5cfe33af4ee1f1ede0034b66d699705e0 |
| Reading-copy SHA-256 | 825841b494e39d0ea040f8412222f918c86ca065974033ff5b8c6c7907ee5a8b |
Recovered source transcript
Download complete source text ↓139 lines · 4.2 KB
import { createClerkClient } from '@clerk/backend'
import { auth } from '@thedaviddias/auth'
import { logger } from '@thedaviddias/logging'
import { NextResponse } from 'next/server'
import { stripHtml } from '@/lib/security-utils-helpers'
const clerk = createClerkClient({
secretKey: process.env.CLERK_SECRET_KEY!
})
// Simple in-memory rate limiting for username checks
const rateLimitMap = new Map<string, { count: number; resetTime: number }>()
interface CheckRateLimitInput {
identifier: string
maxRequests?: number
windowMs?: number
}
/**
* Check whether the given identifier has exceeded its rate limit
*/
function checkRateLimit(input: CheckRateLimitInput): { allowed: boolean; resetTime?: number } {
const { identifier, maxRequests = 20, windowMs = 60 * 1000 } = input
const now = Date.now()
const userLimit = rateLimitMap.get(identifier)
if (!userLimit || now > userLimit.resetTime) {
// Reset or initialize
rateLimitMap.set(identifier, { count: 1, resetTime: now + windowMs })
return { allowed: true }
}
if (userLimit.count >= maxRequests) {
return { allowed: false, resetTime: userLimit.resetTime }
}
userLimit.count++
return { allowed: true }
}
/**
* Handle POST request to check username availability
*/
export async function POST(request: Request) {
try {
// Get the current user session
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Check rate limit
const rateLimit = checkRateLimit({ identifier: session.user.id })
if (!rateLimit.allowed) {
return NextResponse.json(
{ error: 'Too many requests. Please try again later.' },
{ status: 429 }
)
}
let body: { username?: unknown }
try {
body = await request.json()
} catch {
return NextResponse.json({ error: 'Invalid request body' }, { status: 400 })
}
const { username } = body
if (!username || typeof username !== 'string') {
return NextResponse.json({ error: 'Username is required' }, { status: 400 })
}
// Sanitize username input
const sanitizedUsername = stripHtml(username)
const trimmedUsername = sanitizedUsername.trim().toLowerCase()
// Additional security checks
if (trimmedUsername !== username.trim().toLowerCase()) {
return NextResponse.json({ error: 'Invalid characters in username' }, { status: 400 })
}
// Validate username format (same as frontend validation)
if (trimmedUsername.length < 3) {
return NextResponse.json({
available: false,
error: 'Username must be at least 3 characters'
})
}
if (trimmedUsername.length > 30) {
return NextResponse.json({
available: false,
error: 'Username must be 30 characters or less'
})
}
if (!/^[a-zA-Z0-9_-]+$/.test(trimmedUsername)) {
return NextResponse.json({
available: false,
error: 'Username can only contain letters, numbers, underscores, and hyphens'
})
}
// Get current user to allow checking their own username
const currentUser = await clerk.users.getUser(session.user.id)
// If it's the same as current username, it's available (no change)
if (currentUser.username === trimmedUsername) {
return NextResponse.json({ available: true })
}
// Check if username is taken by querying Clerk users
try {
const existingUsers = await clerk.users.getUserList({
username: [trimmedUsername],
limit: 1
})
const isAvailable = existingUsers.data.length === 0
return NextResponse.json({ available: isAvailable })
} catch (error) {
// If there's an error with the query, assume username might be taken
logger.error('Error checking username availability:', { data: error, tags: { type: 'api' } })
return NextResponse.json({ available: false, error: 'Unable to check username availability' })
}
} catch (error) {
logger.error('Error in check-username API:', {
data: error instanceof Error ? { message: error.message, name: error.name } : 'Unknown error',
tags: { type: 'api' }
})
return NextResponse.json({ error: 'Failed to check username' }, { status: 500 })
}
}