/** * Server-authoritative "may this session click-to-dial?" gate. * * Rule: the logged-in user must be AD_User.IsSystemUser = 'Y' AND have a * non-empty AD_User.SIP_Extension. Both live only in iDempiere (the trimmed * logship_user cookie carries neither — and in the app build that cookie is * client-supplied anyway), so the identity comes from the JWT in logship_it and * the flags from models/ad_user/{id}. Verdicts are cached 10 min per user; * the user update route calls invalidateDialer() when the extension changes. * * Fail-closed: any lookup error → canDial:false (never throws to the route). */ import { decodeJwt } from '../chatAccess' import refreshTokenHelper from '../refreshTokenHelper' const TTL_MS = 10 * 60 * 1000 export interface DialerInfo { userId: number name: string extension: string isSystemUser: boolean canDial: boolean orgId: number } const g = globalThis as any const cache: Map = g.__dialAccessCache ?? (g.__dialAccessCache = new Map()) export const invalidateDialer = (userId: number) => { cache.delete(Number(userId)) } const truthy = (v: any) => v === true || v === 'Y' || v === 'true' const fetchUser = async (userId: number, token: string) => { const config = useRuntimeConfig() return await $fetch(`${config.api.url}/models/ad_user/${userId}`, { headers: { Authorization: 'Bearer ' + token, Accept: 'application/json' }, retry: 0, timeout: 15000 }) } /** Digits only, max 10 — an extension is never anything else. */ export const cleanExtension = (v: any): string => String(v ?? '').replace(/\D+/g, '').slice(0, 10) export const resolveDialer = async (event: any): Promise => { const token = getCookie(event, 'logship_it') if (!token) return null let userId = 0 try { userId = Number(decodeJwt(token)?.AD_User_ID) } catch { return null } if (!userId) return null const hit = cache.get(userId) if (hit && hit.expires > Date.now()) return hit.info const build = (user: any): DialerInfo => { const isSystemUser = truthy(user?.IsSystemUser ?? user?.isSystemUser) const extension = cleanExtension(user?.SIP_Extension ?? user?.sip_Extension ?? user?.sIP_Extension) return { userId, name: String(user?.Name || user?.name || ''), extension, isSystemUser, canDial: isSystemUser && extension.length > 0, orgId: Number(user?.AD_Org_ID?.id || 0) } } try { let user: any try { user = await fetchUser(userId, token) } catch (err: any) { const status = Number(err?.status || err?.statusCode || err?.response?.status || 0) if (status !== 401 && status !== 403) throw err const fresh = await refreshTokenHelper(event) user = await fetchUser(userId, fresh) } const info = build(user) cache.set(userId, { info, expires: Date.now() + TTL_MS }) return info } catch (err: any) { console.warn('[dial] dialer lookup failed for user', userId, '-', err?.data?.message || err?.message || err) return null } }