Add SETTINGS_URL/SECRET to compose, fix sync email-change and rate-limit bugs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f08d53d702
commit
42be29b2ce
4 changed files with 23 additions and 10 deletions
|
|
@ -12,6 +12,8 @@ services:
|
||||||
- OFFICE_IP_CHECK=${OFFICE_IP_CHECK:-disabled}
|
- OFFICE_IP_CHECK=${OFFICE_IP_CHECK:-disabled}
|
||||||
- SESSION_DAYS=${SESSION_DAYS:-30}
|
- SESSION_DAYS=${SESSION_DAYS:-30}
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
|
- SETTINGS_URL=${SETTINGS_URL:-http://10.10.10.116:3080}
|
||||||
|
- SETTINGS_SECRET=${SETTINGS_SECRET}
|
||||||
ports:
|
ports:
|
||||||
- "3001:3001"
|
- "3001:3001"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,14 @@ export async function registerRoutes(app) {
|
||||||
|
|
||||||
const respond = () => reply.send({ sent: true })
|
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(
|
const { rows: [existing] } = await pool.query(
|
||||||
`SELECT created_at FROM pending_registrations WHERE email = $1`,
|
`SELECT created_at FROM pending_registrations WHERE email = $1`,
|
||||||
[normalised]
|
[normalised]
|
||||||
)
|
)
|
||||||
if (existing && new Date(existing.created_at) > new Date(Date.now() - 15 * 60_000)) {
|
if (existing && new Date(existing.created_at) > new Date(Date.now() - 2 * 60_000)) {
|
||||||
// Silently allow the upsert below (which resets the PIN) but only if the row is
|
return respond()
|
||||||
// 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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let wfUser
|
let wfUser
|
||||||
|
|
|
||||||
11
src/sync.js
11
src/sync.js
|
|
@ -1,5 +1,5 @@
|
||||||
import { pool } from './db.js'
|
import { pool } from './db.js'
|
||||||
import { findEmployeeByEmail, getEmployeeDepartments, getSyncConfig } from './workforce.js'
|
import { findEmployeeById, findEmployeeByEmail, getEmployeeDepartments, getSyncConfig } from './workforce.js'
|
||||||
|
|
||||||
export async function syncAllWorkforceUsers() {
|
export async function syncAllWorkforceUsers() {
|
||||||
const { rows: users } = await pool.query(
|
const { rows: users } = await pool.query(
|
||||||
|
|
@ -10,12 +10,13 @@ export async function syncAllWorkforceUsers() {
|
||||||
|
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
try {
|
try {
|
||||||
// Look up by email (Workforce doesn't reliably expose GET /users/:id)
|
// Prefer ID lookup — survives email changes in Workforce.
|
||||||
// Use stored email first; if not found try to detect via workforce_user_id match in dept lists
|
// Fall back to email lookup if the API doesn't support GET /users/:id.
|
||||||
const wfUser = await findEmployeeByEmail(user.email)
|
let wfUser = await findEmployeeById(user.workforce_user_id)
|
||||||
|
if (!wfUser) wfUser = await findEmployeeByEmail(user.email)
|
||||||
|
|
||||||
if (!wfUser || String(wfUser.id) !== user.workforce_user_id) {
|
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])
|
await pool.query('UPDATE users SET active = false WHERE id = $1', [user.id])
|
||||||
deactivated++
|
deactivated++
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,17 @@ export async function findEmployeeByEmail(email) {
|
||||||
return users.length > 0 ? users[0] : null
|
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() {
|
export async function getAllDepartments() {
|
||||||
return wfFetchPaged('/api/v2/departments')
|
return wfFetchPaged('/api/v2/departments')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue