Fix API base path: use /loreal-sla-calculator/api prefix for all fetch calls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-03-10 20:05:07 +00:00
parent e54222190b
commit 7ba7cebf56
2 changed files with 28 additions and 24 deletions

49
auth.js
View file

@ -16,6 +16,8 @@ const msalConfig = {
const loginRequest = { scopes: ['User.Read'] };
const API_BASE = '/loreal-sla-calculator/api';
let msalInstance = null;
let currentAccessToken = null; // In-memory only — never persisted
let pendingResetToken = null; // ?reset_token= from URL
@ -55,7 +57,6 @@ async function initAuth() {
const msalResponse = await msalInstance.handleRedirectPromise();
if (msalResponse && msalResponse.idToken) {
// Got a fresh MSAL token — exchange it for an app JWT
await exchangeMsalToken(msalResponse.idToken);
return;
}
@ -75,7 +76,7 @@ async function initAuth() {
async function tryRefresh() {
try {
const res = await fetch('/api/auth/refresh', { method: 'POST', credentials: 'include' });
const res = await fetch(`${API_BASE}/auth/refresh`, { method: 'POST', credentials: 'include' });
if (!res.ok) return false;
const data = await res.json();
currentAccessToken = data.accessToken;
@ -94,22 +95,27 @@ setInterval(async () => {
// ── SSO path ──────────────────────────────────────────────────────────────────
async function authSsoLogin() {
if (!msalInstance) {
msalInstance = new msal.PublicClientApplication(msalConfig);
}
// Try silent SSO first; if it fails, do a full redirect
try {
const silent = await msalInstance.ssoSilent(loginRequest);
await exchangeMsalToken(silent.idToken);
} catch {
await msalInstance.loginRedirect(loginRequest);
if (!msalInstance) {
msalInstance = new msal.PublicClientApplication(msalConfig);
}
// Try silent SSO first; if it fails, do a full redirect
try {
const silent = await msalInstance.ssoSilent(loginRequest);
await exchangeMsalToken(silent.idToken);
} catch {
await msalInstance.loginRedirect(loginRequest);
}
} catch (e) {
console.error('SSO login error:', e);
showAuthView('choice');
}
}
async function exchangeMsalToken(idToken) {
showAuthView('loading');
try {
const res = await fetch('/api/auth/sso', {
const res = await fetch(`${API_BASE}/auth/sso`, {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
@ -139,7 +145,7 @@ async function authEmailLogin(event) {
btn.textContent = 'Signing in…';
try {
const res = await fetch('/api/auth/login', {
const res = await fetch(`${API_BASE}/auth/login`, {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
@ -180,7 +186,7 @@ async function authRegister(event) {
btn.textContent = 'Creating account…';
try {
const res = await fetch('/api/auth/register', {
const res = await fetch(`${API_BASE}/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
@ -211,7 +217,7 @@ async function authForgotPassword(event) {
msgEl.className = 'hidden mb-4 p-3 text-sm rounded-lg';
try {
await fetch('/api/auth/forgot-password', {
await fetch(`${API_BASE}/auth/forgot-password`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
@ -232,7 +238,7 @@ async function authForgotPassword(event) {
async function handleResetTokenParam(token) {
showAuthView('loading');
try {
const res = await fetch(`/api/auth/validate-reset-token/${token}`);
const res = await fetch(`${API_BASE}/auth/validate-reset-token/${token}`);
const data = await res.json();
if (!res.ok || !data.valid) {
showAuthView('login');
@ -264,7 +270,7 @@ async function authResetPassword(event) {
btn.textContent = 'Saving…';
try {
const res = await fetch('/api/auth/reset-password', {
const res = await fetch(`${API_BASE}/auth/reset-password`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: pendingResetToken, password }),
@ -287,10 +293,9 @@ async function authResetPassword(event) {
async function handleVerifyEmail(token) {
showAuthView('loading');
try {
const res = await fetch(`/api/auth/verify-email/${token}`);
const res = await fetch(`${API_BASE}/auth/verify-email/${token}`);
const data = await res.json();
if (res.ok) {
// Show login with a success pre-fill message
const errEl = document.getElementById('authLoginError');
if (errEl) {
errEl.textContent = 'Email verified! You can now sign in.';
@ -308,13 +313,13 @@ async function handleVerifyEmail(token) {
async function authResendVerification() {
if (!pendingVerifyEmail) return;
try {
await fetch('/api/auth/register', {
await fetch(`${API_BASE}/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: pendingVerifyEmail, _resend: true }),
});
} catch {
// Silently ignore — user sees no feedback change
// Silently ignore
}
}
@ -336,14 +341,13 @@ function onAuthSuccess(user) {
async function signOut() {
try {
await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' });
await fetch(`${API_BASE}/auth/logout`, { method: 'POST', credentials: 'include' });
} catch {
// Ignore network errors on logout
}
currentAccessToken = null;
// Also clear MSAL session if it was used
if (msalInstance) {
try {
await msalInstance.logoutSilent();
@ -352,7 +356,6 @@ async function signOut() {
}
}
// Show auth overlay again
const overlay = document.getElementById('authOverlay');
if (overlay) overlay.style.display = '';

View file

@ -8,7 +8,8 @@ let CONFIG = null;
// ── API fetch helper ──────────────────────────────────────────────────────────
// Automatically includes the in-memory access token as a Bearer header.
// On 401, attempts one silent token refresh before giving up.
async function apiFetch(url, options = {}) {
async function apiFetch(path, options = {}) {
const url = `/loreal-sla-calculator/api${path}`;
const headers = { ...(options.headers || {}) };
if (currentAccessToken) headers['Authorization'] = `Bearer ${currentAccessToken}`;