logout() ajoute `?next=<url OPS>` au flow d'invalidation Authentik : après déconnexion, Authentik renvoie vers erp.gigafibre.ca/ops → qui (non authentifié) redéclenche le login AVEC chemin de retour → après login on revient sur OPS, au lieu d'atterrir sur /if/user/#/library. Sans régression si Authentik ignore `next` (= comportement actuel). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
97 lines
4.7 KiB
JavaScript
97 lines
4.7 KiB
JavaScript
import { BASE_URL } from 'src/config/erpnext'
|
|
|
|
// Token is optional — in production, nginx injects it server-side.
|
|
// Only needed for local dev (VITE_ERP_TOKEN in .env).
|
|
const SERVICE_TOKEN = import.meta.env.VITE_ERP_TOKEN || ''
|
|
|
|
const isLocalHost = () => window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
|
|
|
|
// Nom d'utilisateur Authentik renvoyé par le dernier whoami — repli de recherche de permissions côté hub
|
|
// quand le courriel ne correspond à aucun compte OPS provisionné (voir stores/auth.js → loadPermissions).
|
|
let _authUsername = ''
|
|
export function getAuthUsername () { return _authUsername }
|
|
|
|
// Reconnexion auto sur session Authentik expirée, anti-boucle (1 reload / 20 s max).
|
|
let _lastReload = 0
|
|
function _reauth (status) {
|
|
// DEV local (pas d'Authentik devant) : un 401 ERPNext ne doit PAS déclencher un rechargement en boucle.
|
|
// On renvoie le 401 et l'UI affiche ses états vides. En prod (Authentik) le comportement est inchangé.
|
|
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
|
return new Response('{}', { status: status || 401 })
|
|
}
|
|
const now = Date.now()
|
|
if (now - _lastReload > 20000) {
|
|
_lastReload = now
|
|
window.location.reload()
|
|
}
|
|
return new Response('{}', { status: status || 401 })
|
|
}
|
|
|
|
export async function authFetch (url, opts = {}) {
|
|
opts.headers = SERVICE_TOKEN
|
|
? { ...opts.headers, Authorization: 'token ' + SERVICE_TOKEN }
|
|
: { ...opts.headers }
|
|
|
|
let res
|
|
try {
|
|
res = await fetch(url, opts)
|
|
} catch (e) {
|
|
// Coupure réseau transitoire (backend qui redémarre) OU timeout du garde net (session lente) → 1 retry court
|
|
await new Promise(r => setTimeout(r, 800))
|
|
try {
|
|
res = await fetch(url, opts)
|
|
} catch (e2) {
|
|
// 2e échec (réseau/timeout) : NE PAS planter l'appelant ni boucler sur reload → réponse vide, l'app reste réactive
|
|
return new Response('{}', { status: 504 })
|
|
}
|
|
}
|
|
|
|
// Détection « session Authentik expirée » (la cause du "il faut recharger") :
|
|
// - 401/403 explicites
|
|
// - réponse redirigée vers l'IdP (auth.targo.ca / /if/flow/)
|
|
// - page HTML de login renvoyée à la place du JSON attendu sur un endpoint app (/api, /auth, /hub)
|
|
const ct = (res.headers && res.headers.get && res.headers.get('content-type')) || ''
|
|
const redirectedToIdp = res.redirected && /auth\.targo\.ca|\/if\/flow\//.test(res.url || '')
|
|
const htmlForApi = res.status === 200 && ct.includes('text/html') && /\/(api|auth|hub)\//.test(String(url))
|
|
if (res.status === 401 || res.status === 403 || redirectedToIdp || htmlForApi) {
|
|
return _reauth(res.status)
|
|
}
|
|
return res
|
|
}
|
|
|
|
export async function getLoggedUser () {
|
|
// ⚠️ APERÇU LOCAL UNIQUEMENT : identité forcée via VITE_DEV_USER (gardé localhost + env). `import.meta.env.DEV`
|
|
// vaut false en build prod → esbuild élimine toute cette branche (et la valeur inlinée) du bundle livré.
|
|
if (import.meta.env.DEV && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') && import.meta.env.VITE_DEV_USER) return import.meta.env.VITE_DEV_USER
|
|
// First try nginx whoami endpoint (returns Authentik email header)
|
|
try {
|
|
const res = await fetch(BASE_URL + '/auth/whoami')
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
_authUsername = data.username || '' // mémorisé pour le repli par username (recherche de permissions)
|
|
if (data.email && data.email !== '') return data.email
|
|
// whoami joignable mais courriel VIDE en prod = identité Authentik non transmise → renvoyer '' (pas de repli
|
|
// trompeur sur le propriétaire du jeton ERP « Administrator »). App.vue affiche alors un message clair « fail-loud ».
|
|
if (!isLocalHost()) return ''
|
|
}
|
|
} catch {}
|
|
// Fallback: ask ERPNext (returns API token owner, usually "Administrator")
|
|
try {
|
|
const headers = SERVICE_TOKEN ? { Authorization: 'token ' + SERVICE_TOKEN } : {}
|
|
const res = await fetch(BASE_URL + '/api/method/frappe.auth.get_logged_user', { headers })
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
return data.message || 'authenticated'
|
|
}
|
|
} catch {}
|
|
return 'authenticated'
|
|
}
|
|
|
|
export async function logout () {
|
|
// Déconnexion Authentik PUIS retour sur OPS : le flow d'invalidation honore `?next=` → il renvoie vers OPS,
|
|
// qui (non authentifié) redéclenche le login Authentik AVEC chemin de retour → après login on revient sur OPS,
|
|
// au lieu d'atterrir sur la page par défaut d'Authentik (/if/user/#/library).
|
|
const back = window.location.origin + (import.meta.env.BASE_URL || '/') // ex. https://erp.gigafibre.ca/ops/
|
|
window.location.href = 'https://auth.targo.ca/if/flow/default-invalidation-flow/?next=' + encodeURIComponent(back)
|
|
}
|