File size: 3,951 Bytes
90f0300
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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
    }
  };
}