← back to ClawCoder

src/lib/auth.ts

68 lines

import { NextRequest, NextResponse } from 'next/server'

const AUTH_USER = 'Steve'
const AUTH_PASS = 'DWSecure2024!'
const SESSION_COOKIE = 'clawcoder_session'
const SESSION_MAX_AGE = 30 * 24 * 60 * 60 * 1000 // 30 days

export interface SessionData {
  authenticated: boolean
  username: string
  loginTime: number
}

/**
 * Parse session from cookie value (base64-encoded JSON).
 * Returns null if invalid or expired.
 */
export function parseSession(cookieValue: string | undefined): SessionData | null {
  if (!cookieValue) return null
  try {
    const data = JSON.parse(Buffer.from(cookieValue, 'base64').toString()) as SessionData
    if (data.authenticated && (Date.now() - data.loginTime) < SESSION_MAX_AGE) {
      return data
    }
  } catch {
    // malformed cookie
  }
  return null
}

/**
 * Check Basic Auth header (for curl/API access).
 * Returns the username if valid, null otherwise.
 */
export function checkBasicAuth(request: NextRequest): string | null {
  const authHeader = request.headers.get('authorization')
  if (authHeader?.startsWith('Basic ')) {
    const decoded = Buffer.from(authHeader.slice(6), 'base64').toString()
    const [user, pass] = decoded.split(':')
    if (user === AUTH_USER && pass === AUTH_PASS) {
      return user
    }
  }
  return null
}

/**
 * Authenticate an API request. Checks session cookie first, then Basic Auth.
 * Returns the username if authenticated, or a 401 NextResponse if not.
 */
export function authenticateRequest(request: NextRequest): string | NextResponse {
  // Check session cookie
  const session = parseSession(request.cookies.get(SESSION_COOKIE)?.value)
  if (session) {
    return session.username
  }

  // Check Basic Auth
  const basicUser = checkBasicAuth(request)
  if (basicUser) {
    return basicUser
  }

  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

export { AUTH_USER, AUTH_PASS, SESSION_COOKIE, SESSION_MAX_AGE }