import { date } from 'alga-js' import fetchHelper from './fetchHelper' import sqliteHelper from './sqliteHelper' import setAuthCookie from './setAuthCookie' import refreshTokensRaw from './tokenRefreshCore' import { trimUser, trimRole, trimOrganization, trimClient, trimWarehouse } from './trimCookieData' /** * Backend (not auth) failures must NOT trigger the refresh-and-retry pattern the * ~1300 routes use (`try handleFunc → catch → refreshTokenHelper → handleFunc`). * When the last iDempiere call on this request died with 5xx / 502 / 504 / a * timeout / a network error, re-running the same query doubles the load on an * already stalled backend (2026-09-10: 6 → 79 calls/min of one heavy query, * 178 of 200 Jetty threads blocked). Only 401 (and 403, which some flows map to * an expired token) are worth a refresh. */ export function isRetryableAuthFailure(event: any): boolean { const last = event?.context?.lastIdempiereError if (!last) return true // no fetch error recorded → caller wants a plain refresh const status = Number(last.status) || 0 if (status === 401 || status === 403) return true return false } export default async function refreshTokenHelper(event: any) { let token = getCookie(event, 'logship_it') if (!isRetryableAuthFailure(event)) { const last = event.context.lastIdempiereError const status = Number(last?.status) || 0 const message = status ? `iDempiere responded ${status} on ${last?.url ?? 'request'} — not retrying (backend error, not an auth error).` : `iDempiere call ${last?.url ?? ''} failed (${last?.name ?? 'network/timeout'}) — not retrying.` throw createError({ statusCode: status >= 500 ? status : 502, statusMessage: message }) } const clientId = getCookie(event, 'logship_client_id') const roleId = getCookie(event, 'logship_role_id') const organizationId = getCookie(event, 'logship_organization_id') const warehouseId = getCookie(event, 'logship_warehouse_id') // Guarded two-tier refresh (auth/refresh → credential login). Throws without // calling iDempiere when the stored cookies cannot possibly authenticate — // see tokenRefreshCore.ts / authCooldown.ts. const resToken: any = await refreshTokensRaw(event) if(resToken.userId) { setAuthCookie(event, 'logship_user_id', resToken.userId) const resUser: any = await fetchHelper(event, 'models/ad_user/'+resToken.userId, 'GET', resToken.token, null) if(resUser) { setAuthCookie(event, 'logship_user', trimUser(resUser)) } if(clientId) { const resClient: any = await fetchHelper(event, 'models/ad_client/'+clientId, 'GET', resToken.token, null) if(resClient) { setAuthCookie(event, 'logship_client', trimClient(resClient)) } } if(roleId) { const resRole: any = await fetchHelper(event, 'models/ad_role/'+roleId, 'GET', resToken.token, null) if(resRole) { setAuthCookie(event, 'logship_role', trimRole(resRole)) } } if(organizationId) { const resOrg: any = await fetchHelper(event, 'models/ad_org/'+organizationId, 'GET', resToken.token, null) if(resOrg) { setAuthCookie(event, 'logship_organization', trimOrganization(resOrg)) } } if(warehouseId) { const resWarehouse: any = await fetchHelper(event, 'models/m_warehouse/'+warehouseId, 'GET', resToken.token, null) if(resWarehouse) { setAuthCookie(event, 'logship_warehouse', trimWarehouse(resWarehouse)) } } } if(resToken.token) { token = resToken.token setAuthCookie(event, 'logship_it', resToken.token) setAuthCookie(event, 'logship_rt', resToken.refresh_token) sqliteHelper(event, (db) => { const stmt = db.prepare("INSERT INTO refresh_tokens (refresh_token, token, expiration) VALUES (?, ?, ?)") stmt.run(resToken.refresh_token, resToken.token, date.add(date.now(''), 1)) stmt.finalize() }) return resToken.token } return token }