Spaces:
Running
Running
| import crypto from 'node:crypto'; | |
| import fs from 'node:fs/promises'; | |
| import path from 'node:path'; | |
| import { CODEXMOBILE_STATE_DIR, statePath } from './runtime-paths.js'; | |
| export const DATA_DIR = CODEXMOBILE_STATE_DIR; | |
| const STATE_FILE = statePath('auth-state.json'); | |
| const FIXED_PAIRING_CODE_FILE = statePath('pairing-code.txt'); | |
| const PAIRING_CODE_PATTERN = /^\d{6}$/; | |
| let authState = null; | |
| let fixedPairingCode = false; | |
| let pairingCode = createPairingCode(); | |
| function createPairingCode() { | |
| return String(crypto.randomInt(100000, 999999)); | |
| } | |
| function hashToken(token) { | |
| return crypto.createHash('sha256').update(token).digest('hex'); | |
| } | |
| async function readState() { | |
| try { | |
| const raw = await fs.readFile(STATE_FILE, 'utf8'); | |
| const parsed = JSON.parse(raw); | |
| return { | |
| devices: Array.isArray(parsed.devices) ? parsed.devices : [] | |
| }; | |
| } catch (error) { | |
| if (error.code !== 'ENOENT') { | |
| console.warn('[auth] Failed to read auth state, starting fresh:', error.message); | |
| } | |
| return { devices: [] }; | |
| } | |
| } | |
| async function writeState() { | |
| await fs.mkdir(DATA_DIR, { recursive: true }); | |
| await fs.writeFile(STATE_FILE, JSON.stringify(authState, null, 2), 'utf8'); | |
| } | |
| async function readFixedPairingCode() { | |
| const envCode = String(process.env.CODEXMOBILE_PAIRING_CODE || '').trim(); | |
| if (envCode) { | |
| if (PAIRING_CODE_PATTERN.test(envCode)) { | |
| return envCode; | |
| } | |
| console.warn('[auth] Ignoring CODEXMOBILE_PAIRING_CODE because it is not a 6 digit code.'); | |
| } | |
| try { | |
| const fileCode = (await fs.readFile(FIXED_PAIRING_CODE_FILE, 'utf8')).trim(); | |
| if (PAIRING_CODE_PATTERN.test(fileCode)) { | |
| return fileCode; | |
| } | |
| console.warn(`[auth] Ignoring ${FIXED_PAIRING_CODE_FILE} because it is not a 6 digit code.`); | |
| } catch (error) { | |
| if (error.code !== 'ENOENT') { | |
| console.warn('[auth] Failed to read fixed pairing code:', error.message); | |
| } | |
| } | |
| return null; | |
| } | |
| export async function initializeAuth() { | |
| authState = await readState(); | |
| const configuredPairingCode = await readFixedPairingCode(); | |
| if (configuredPairingCode) { | |
| pairingCode = configuredPairingCode; | |
| fixedPairingCode = true; | |
| } | |
| await writeState(); | |
| return { pairingCode, fixedPairingCode, trustedDevices: authState.devices.length }; | |
| } | |
| export function getPairingCode() { | |
| return pairingCode; | |
| } | |
| export function getTrustedDeviceCount() { | |
| return authState?.devices?.length || 0; | |
| } | |
| export function extractBearerToken(req) { | |
| const header = req.headers.authorization || ''; | |
| if (header.toLowerCase().startsWith('bearer ')) { | |
| return header.slice(7).trim(); | |
| } | |
| const fallback = req.headers['x-codexmobile-token']; | |
| return typeof fallback === 'string' ? fallback.trim() : ''; | |
| } | |
| export async function verifyToken(token, metadata = {}) { | |
| if (!token || !authState) { | |
| return false; | |
| } | |
| const tokenHash = hashToken(token); | |
| const device = authState.devices.find((entry) => entry.tokenHash === tokenHash); | |
| if (!device) { | |
| return false; | |
| } | |
| device.lastSeenAt = new Date().toISOString(); | |
| device.lastRemoteAddress = metadata.remoteAddress || device.lastRemoteAddress || null; | |
| await writeState(); | |
| return true; | |
| } | |
| export async function pairDevice({ code, deviceName, userAgent, remoteAddress }) { | |
| if (!code || String(code).trim() !== pairingCode) { | |
| return null; | |
| } | |
| const token = crypto.randomBytes(32).toString('base64url'); | |
| const now = new Date().toISOString(); | |
| const device = { | |
| id: crypto.randomUUID(), | |
| name: deviceName || 'iPhone', | |
| tokenHash: hashToken(token), | |
| createdAt: now, | |
| lastSeenAt: now, | |
| userAgent: userAgent || null, | |
| lastRemoteAddress: remoteAddress || null | |
| }; | |
| authState.devices.push(device); | |
| if (!fixedPairingCode) { | |
| pairingCode = createPairingCode(); | |
| } | |
| await writeState(); | |
| return { | |
| token, | |
| device: { | |
| id: device.id, | |
| name: device.name, | |
| createdAt: device.createdAt | |
| } | |
| }; | |
| } | |