fix: server-side API token injection + ticket modal empty state
- Move ERPNext API token from JS bundle to nginx proxy_set_header (token only lives on server, never in client code) - Switch ops + field apps from auth.targo.ca to id.gigafibre.ca SSO - Fix "Aucun contenu" showing on tickets that have comments but no description (check comments.length in v-if condition) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
11cd38f93c
commit
1ed86e37ad
|
|
@ -22,7 +22,7 @@ echo "==> Installing dependencies..."
|
||||||
npm ci --silent
|
npm ci --silent
|
||||||
|
|
||||||
echo "==> Building PWA (base=/field/)..."
|
echo "==> Building PWA (base=/field/)..."
|
||||||
VITE_ERP_TOKEN="b273a666c86d2d0:06120709db5e414" DEPLOY_BASE=/field/ npx quasar build -m pwa
|
DEPLOY_BASE=/field/ npx quasar build -m pwa
|
||||||
|
|
||||||
if [ "$1" = "local" ]; then
|
if [ "$1" = "local" ]; then
|
||||||
echo "==> Deploying to local $DEST..."
|
echo "==> Deploying to local $DEST..."
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ services:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.field.rule=Host(`erp.gigafibre.ca`) && PathPrefix(`/field`)"
|
- "traefik.http.routers.field.rule=Host(`erp.gigafibre.ca`) && PathPrefix(`/field`)"
|
||||||
- "traefik.http.routers.field.entrypoints=web,websecure"
|
- "traefik.http.routers.field.entrypoints=web,websecure"
|
||||||
- "traefik.http.routers.field.middlewares=authentik@file,field-strip@docker"
|
- "traefik.http.routers.field.middlewares=authentik-client@file,field-strip@docker"
|
||||||
- "traefik.http.routers.field.service=field"
|
- "traefik.http.routers.field.service=field"
|
||||||
- "traefik.http.routers.field.tls.certresolver=letsencrypt"
|
- "traefik.http.routers.field.tls.certresolver=letsencrypt"
|
||||||
- "traefik.http.routers.field.priority=200"
|
- "traefik.http.routers.field.priority=200"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,19 @@ server {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
|
# ERPNext API proxy — token injected server-side (never in JS bundle)
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass https://erp.gigafibre.ca;
|
||||||
|
proxy_ssl_verify off;
|
||||||
|
proxy_set_header Host erp.gigafibre.ca;
|
||||||
|
proxy_set_header Authorization "token b273a666c86d2d0:06120709db5e414";
|
||||||
|
proxy_set_header X-Authentik-Email $http_x_authentik_email;
|
||||||
|
proxy_set_header X-Authentik-Username $http_x_authentik_username;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
|
}
|
||||||
|
|
||||||
|
# SPA fallback
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
import { BASE_URL } from 'src/config/erpnext'
|
import { BASE_URL } from 'src/config/erpnext'
|
||||||
|
|
||||||
const SERVICE_TOKEN = import.meta.env.VITE_ERP_TOKEN || window.__ERP_TOKEN__ || ''
|
// 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 || ''
|
||||||
|
|
||||||
export function authFetch (url, opts = {}) {
|
export function authFetch (url, opts = {}) {
|
||||||
opts.headers = {
|
if (SERVICE_TOKEN) {
|
||||||
...opts.headers,
|
opts.headers = {
|
||||||
Authorization: 'token ' + SERVICE_TOKEN,
|
...opts.headers,
|
||||||
|
Authorization: 'token ' + SERVICE_TOKEN,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opts.headers = { ...opts.headers }
|
||||||
}
|
}
|
||||||
opts.redirect = 'manual'
|
opts.redirect = 'manual'
|
||||||
if (opts.method && opts.method !== 'GET') {
|
if (opts.method && opts.method !== 'GET') {
|
||||||
|
|
@ -22,9 +28,8 @@ export function authFetch (url, opts = {}) {
|
||||||
|
|
||||||
export async function getLoggedUser () {
|
export async function getLoggedUser () {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(BASE_URL + '/api/method/frappe.auth.get_logged_user', {
|
const headers = SERVICE_TOKEN ? { Authorization: 'token ' + SERVICE_TOKEN } : {}
|
||||||
headers: { Authorization: 'token ' + SERVICE_TOKEN },
|
const res = await fetch(BASE_URL + '/api/method/frappe.auth.get_logged_user', { headers })
|
||||||
})
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
return data.message || 'authenticated'
|
return data.message || 'authenticated'
|
||||||
|
|
@ -34,5 +39,5 @@ export async function getLoggedUser () {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logout () {
|
export async function logout () {
|
||||||
window.location.href = 'https://auth.targo.ca/if/flow/default-invalidation-flow/'
|
window.location.href = 'https://id.gigafibre.ca/if/flow/default-invalidation-flow/'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ echo "==> Installing dependencies..."
|
||||||
npm ci --silent
|
npm ci --silent
|
||||||
|
|
||||||
echo "==> Building PWA (base=/ops/)..."
|
echo "==> Building PWA (base=/ops/)..."
|
||||||
VITE_ERP_TOKEN="b273a666c86d2d0:06120709db5e414" DEPLOY_BASE=/ops/ npx quasar build -m pwa
|
DEPLOY_BASE=/ops/ npx quasar build -m pwa
|
||||||
|
|
||||||
if [ "$1" = "local" ]; then
|
if [ "$1" = "local" ]; then
|
||||||
# ── Local deploy ──
|
# ── Local deploy ──
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ services:
|
||||||
# Main router: erp.gigafibre.ca/ops/* with Authentik + StripPrefix
|
# Main router: erp.gigafibre.ca/ops/* with Authentik + StripPrefix
|
||||||
- "traefik.http.routers.ops.rule=Host(`erp.gigafibre.ca`) && PathPrefix(`/ops`)"
|
- "traefik.http.routers.ops.rule=Host(`erp.gigafibre.ca`) && PathPrefix(`/ops`)"
|
||||||
- "traefik.http.routers.ops.entrypoints=web,websecure"
|
- "traefik.http.routers.ops.entrypoints=web,websecure"
|
||||||
- "traefik.http.routers.ops.middlewares=authentik@file,ops-strip@docker"
|
- "traefik.http.routers.ops.middlewares=authentik-client@file,ops-strip@docker"
|
||||||
- "traefik.http.routers.ops.service=ops"
|
- "traefik.http.routers.ops.service=ops"
|
||||||
- "traefik.http.routers.ops.tls.certresolver=letsencrypt"
|
- "traefik.http.routers.ops.tls.certresolver=letsencrypt"
|
||||||
- "traefik.http.routers.ops.priority=200"
|
- "traefik.http.routers.ops.priority=200"
|
||||||
|
|
@ -34,7 +34,7 @@ services:
|
||||||
# Authentik outpost callback (required for login redirect)
|
# Authentik outpost callback (required for login redirect)
|
||||||
- "traefik.http.routers.ops-ak.rule=Host(`erp.gigafibre.ca`) && PathPrefix(`/outpost.goauthentik.io/`)"
|
- "traefik.http.routers.ops-ak.rule=Host(`erp.gigafibre.ca`) && PathPrefix(`/outpost.goauthentik.io/`)"
|
||||||
- "traefik.http.routers.ops-ak.entrypoints=web,websecure"
|
- "traefik.http.routers.ops-ak.entrypoints=web,websecure"
|
||||||
- "traefik.http.routers.ops-ak.middlewares=authentik@file"
|
- "traefik.http.routers.ops-ak.middlewares=authentik-client@file"
|
||||||
- "traefik.http.routers.ops-ak.service=ops"
|
- "traefik.http.routers.ops-ak.service=ops"
|
||||||
- "traefik.http.routers.ops-ak.tls.certresolver=letsencrypt"
|
- "traefik.http.routers.ops-ak.tls.certresolver=letsencrypt"
|
||||||
- "traefik.http.routers.ops-ak.priority=250"
|
- "traefik.http.routers.ops-ak.priority=250"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,19 @@ server {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
|
# ERPNext API proxy — token injected server-side (never in JS bundle)
|
||||||
|
# To rotate: edit this file + docker restart ops-frontend
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass https://erp.gigafibre.ca;
|
||||||
|
proxy_ssl_verify off;
|
||||||
|
proxy_set_header Host erp.gigafibre.ca;
|
||||||
|
proxy_set_header Authorization "token b273a666c86d2d0:06120709db5e414";
|
||||||
|
proxy_set_header X-Authentik-Email $http_x_authentik_email;
|
||||||
|
proxy_set_header X-Authentik-Username $http_x_authentik_username;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
|
}
|
||||||
|
|
||||||
# SPA fallback — all routes serve index.html
|
# SPA fallback — all routes serve index.html
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,19 @@
|
||||||
import { BASE_URL } from 'src/config/erpnext'
|
import { BASE_URL } from 'src/config/erpnext'
|
||||||
|
|
||||||
const SERVICE_TOKEN = import.meta.env.VITE_ERP_TOKEN || window.__ERP_TOKEN__ || ''
|
// 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 || ''
|
||||||
|
|
||||||
export function authFetch (url, opts = {}) {
|
export function authFetch (url, opts = {}) {
|
||||||
opts.headers = {
|
if (SERVICE_TOKEN) {
|
||||||
...opts.headers,
|
opts.headers = {
|
||||||
Authorization: 'token ' + SERVICE_TOKEN,
|
...opts.headers,
|
||||||
|
Authorization: 'token ' + SERVICE_TOKEN,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opts.headers = { ...opts.headers }
|
||||||
}
|
}
|
||||||
opts.redirect = 'manual'
|
opts.redirect = 'manual'
|
||||||
// For state-changing requests, omit cookies to avoid CSRF check
|
|
||||||
// (token auth doesn't require CSRF, but session cookies trigger it)
|
|
||||||
if (opts.method && opts.method !== 'GET') {
|
if (opts.method && opts.method !== 'GET') {
|
||||||
opts.credentials = 'omit'
|
opts.credentials = 'omit'
|
||||||
}
|
}
|
||||||
|
|
@ -24,9 +28,8 @@ export function authFetch (url, opts = {}) {
|
||||||
|
|
||||||
export async function getLoggedUser () {
|
export async function getLoggedUser () {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(BASE_URL + '/api/method/frappe.auth.get_logged_user', {
|
const headers = SERVICE_TOKEN ? { Authorization: 'token ' + SERVICE_TOKEN } : {}
|
||||||
headers: { Authorization: 'token ' + SERVICE_TOKEN },
|
const res = await fetch(BASE_URL + '/api/method/frappe.auth.get_logged_user', { headers })
|
||||||
})
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
return data.message || 'authenticated'
|
return data.message || 'authenticated'
|
||||||
|
|
@ -36,5 +39,5 @@ export async function getLoggedUser () {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logout () {
|
export async function logout () {
|
||||||
window.location.href = 'https://auth.targo.ca/if/flow/default-invalidation-flow/'
|
window.location.href = 'https://id.gigafibre.ca/if/flow/default-invalidation-flow/'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@
|
||||||
<div class="thread-body" v-html="c.content"></div>
|
<div class="thread-body" v-html="c.content"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!doc.description && !doc.resolution_details && !comms.length" class="text-center text-grey-5 q-pa-lg">
|
<div v-if="!doc.description && !doc.resolution_details && !comms.length && !comments.length" class="text-center text-grey-5 q-pa-lg">
|
||||||
Aucun contenu pour ce ticket
|
Aucun contenu pour ce ticket
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user