diff --git a/docker-compose.yml b/docker-compose.yml index 15cb2d6..5da5471 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,8 @@ services: - OFFICE_IP_CHECK=${OFFICE_IP_CHECK:-disabled} - SESSION_DAYS=${SESSION_DAYS:-30} - NODE_ENV=production + - SETTINGS_URL=${SETTINGS_URL:-http://10.10.10.116:3080} + - SETTINGS_SECRET=${SETTINGS_SECRET} ports: - "3001:3001" healthcheck: diff --git a/src/routes/register.js b/src/routes/register.js index 590ae98..4af5e16 100644 --- a/src/routes/register.js +++ b/src/routes/register.js @@ -15,15 +15,14 @@ export async function registerRoutes(app) { const respond = () => reply.send({ sent: true }) - // Rate limit: max 3 initiation attempts per email within 15 minutes + // Rate limit: if a PIN was sent within the last 2 minutes, silently pretend we sent again. + // Prevents rapid-fire spam while still allowing genuine "I didn't get it" retries after cooldown. const { rows: [existing] } = await pool.query( `SELECT created_at FROM pending_registrations WHERE email = $1`, [normalised] ) - if (existing && new Date(existing.created_at) > new Date(Date.now() - 15 * 60_000)) { - // Silently allow the upsert below (which resets the PIN) but only if the row is - // old enough to reuse. If it was just created, send nothing and pretend we sent. - // This prevents rapid-fire spam while still allowing genuine retries. + if (existing && new Date(existing.created_at) > new Date(Date.now() - 2 * 60_000)) { + return respond() } let wfUser diff --git a/src/sync.js b/src/sync.js index 6e2c141..af6703b 100644 --- a/src/sync.js +++ b/src/sync.js @@ -1,5 +1,5 @@ import { pool } from './db.js' -import { findEmployeeByEmail, getEmployeeDepartments, getSyncConfig } from './workforce.js' +import { findEmployeeById, findEmployeeByEmail, getEmployeeDepartments, getSyncConfig } from './workforce.js' export async function syncAllWorkforceUsers() { const { rows: users } = await pool.query( @@ -10,12 +10,13 @@ export async function syncAllWorkforceUsers() { for (const user of users) { try { - // Look up by email (Workforce doesn't reliably expose GET /users/:id) - // Use stored email first; if not found try to detect via workforce_user_id match in dept lists - const wfUser = await findEmployeeByEmail(user.email) + // Prefer ID lookup — survives email changes in Workforce. + // Fall back to email lookup if the API doesn't support GET /users/:id. + let wfUser = await findEmployeeById(user.workforce_user_id) + if (!wfUser) wfUser = await findEmployeeByEmail(user.email) if (!wfUser || String(wfUser.id) !== user.workforce_user_id) { - // Not found or ID mismatch — deactivate + // Not found in Workforce — deactivate await pool.query('UPDATE users SET active = false WHERE id = $1', [user.id]) deactivated++ continue diff --git a/src/workforce.js b/src/workforce.js index e9e1a70..7792158 100644 --- a/src/workforce.js +++ b/src/workforce.js @@ -71,6 +71,17 @@ export async function findEmployeeByEmail(email) { return users.length > 0 ? users[0] : null } +// Try direct ID lookup first (standard REST); returns null if endpoint not supported +export async function findEmployeeById(wfUserId) { + try { + const data = await wfFetch(`/api/v2/users/${wfUserId}`) + // Some APIs wrap in { user: ... }, others return the object directly + return data?.id ? data : (data?.user ?? null) + } catch { + return null + } +} + export async function getAllDepartments() { return wfFetchPaged('/api/v2/departments') }