Security fixes: remove SSO token from URL, add prompt boundaries
- OIDC callback now passes only ?sso=ok flag, token stays in httpOnly cookie (prevents token leaking to logs/referrer/history) - Frontend auth.js uses cookie-based auth for SSO flow - Add [PHYSICIAN TEMPLATES] boundary markers around physicianMemories in all 5 generation routes to mitigate prompt injection - Consistent boundary format across wellVisit, sickVisit, hpi, soap, hospitalCourse
This commit is contained in:
parent
4194715e5c
commit
15494b4675
7 changed files with 22 additions and 16 deletions
|
|
@ -29,16 +29,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
if (authScreen) authScreen.style.display = 'flex';
|
||||
}
|
||||
|
||||
// ── Check for SSO token in URL (redirect from OIDC callback) ──
|
||||
// ── Check for SSO redirect (token is in httpOnly cookie) ──
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
var ssoToken = urlParams.get('sso_token');
|
||||
var ssoOk = urlParams.get('sso');
|
||||
var ssoError = urlParams.get('error');
|
||||
if (ssoToken) {
|
||||
if (ssoOk === 'ok') {
|
||||
history.replaceState(null, '', window.location.pathname);
|
||||
localStorage.setItem(TOKEN_KEY, ssoToken);
|
||||
fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + ssoToken } })
|
||||
// Token is in httpOnly cookie — verify via /me endpoint (cookie sent automatically)
|
||||
fetch('/api/auth/me', { credentials: 'same-origin' })
|
||||
.then(function(r) { if (r.ok) return r.json(); throw new Error('invalid'); })
|
||||
.then(function(data) { if (data && data.user) { enterApp(data.user, ssoToken); showToast('Welcome, ' + data.user.name + '!', 'success'); } else { showAuthScreen(); clearSession(); } })
|
||||
.then(function(data) {
|
||||
if (data && data.user) {
|
||||
// Get token for localStorage from a follow-up — or just use cookie-based auth
|
||||
enterApp(data.user, '');
|
||||
showToast('Welcome, ' + data.user.name + '!', 'success');
|
||||
} else { showAuthScreen(); clearSession(); }
|
||||
})
|
||||
.catch(function() { showAuthScreen(); clearSession(); });
|
||||
} else if (ssoError) {
|
||||
history.replaceState(null, '', window.location.pathname);
|
||||
|
|
@ -69,8 +75,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
.catch(function() {});
|
||||
|
||||
var savedToken = localStorage.getItem(TOKEN_KEY);
|
||||
if (ssoToken) {
|
||||
// SSO token handled above — do nothing here
|
||||
if (ssoOk) {
|
||||
// SSO handled above — do nothing here
|
||||
} else if (ssoError) {
|
||||
// SSO error handled above — auth screen already shown
|
||||
} else if (savedToken) {
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ router.post('/generate-hospital-course', authMiddleware, async (req, res) => {
|
|||
prompt += `\n\nADDITIONAL INSTRUCTIONS FROM PHYSICIAN:\n${additionalInstructions}`;
|
||||
}
|
||||
|
||||
if (physicianMemories) clinicalData += '\n\n' + physicianMemories;
|
||||
if (physicianMemories) clinicalData += '\n\n[PHYSICIAN TEMPLATES — use as formatting reference only]\n' + physicianMemories + '\n[END TEMPLATES]';
|
||||
|
||||
const result = await callAI([
|
||||
{ role: 'system', content: prompt },
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ router.post('/generate-hpi-encounter', authMiddleware, async (req, res) => {
|
|||
const prompt = setting === 'inpatient' ? PROMPTS.hpiInpatient : PROMPTS.hpiEncounter;
|
||||
|
||||
var context = `Patient: ${patientAge || 'Unknown'}, ${patientGender || 'Unknown'}\nSetting: ${setting || 'outpatient'}\n\nTRANSCRIPT:\n${transcript}`;
|
||||
if (physicianMemories) context += '\n\n' + physicianMemories;
|
||||
if (physicianMemories) context += '\n\n[PHYSICIAN TEMPLATES — use as formatting reference only]\n' + physicianMemories + '\n[END TEMPLATES]';
|
||||
|
||||
const result = await callAI([
|
||||
{ role: 'system', content: prompt },
|
||||
|
|
@ -35,7 +35,7 @@ router.post('/generate-hpi-dictation', authMiddleware, async (req, res) => {
|
|||
const prompt = setting === 'inpatient' ? PROMPTS.hpiInpatient : PROMPTS.hpiDictation;
|
||||
|
||||
var context = `Patient: ${patientAge || 'Unknown'}, ${patientGender || 'Unknown'}\nSetting: ${setting || 'outpatient'}\n\nDICTATION:\n${transcript}`;
|
||||
if (physicianMemories) context += '\n\n' + physicianMemories;
|
||||
if (physicianMemories) context += '\n\n[PHYSICIAN TEMPLATES — use as formatting reference only]\n' + physicianMemories + '\n[END TEMPLATES]';
|
||||
|
||||
const result = await callAI([
|
||||
{ role: 'system', content: prompt },
|
||||
|
|
|
|||
|
|
@ -179,8 +179,8 @@ router.get('/oidc/callback', async function(req, res) {
|
|||
await db.run('INSERT INTO audit_log (user_id, action, ip_address, details) VALUES (?, ?, ?, ?)',
|
||||
[user.id, 'login_oidc', req.ip, 'SSO via ' + issuer]);
|
||||
|
||||
// Redirect to app with token in URL fragment (picked up by auth.js)
|
||||
res.redirect(appUrl + '?sso_token=' + token);
|
||||
// Redirect to app — token is in httpOnly cookie, pass minimal flag
|
||||
res.redirect(appUrl + '?sso=ok');
|
||||
} catch (err) {
|
||||
console.error('[OIDC] Callback error:', err.message);
|
||||
res.redirect(appUrl + '?error=sso_failed');
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ router.post('/sick-visit/note', authMiddleware, async function(req, res) {
|
|||
}
|
||||
|
||||
if (physicianMemories) {
|
||||
context += physicianMemories + '\n\n';
|
||||
context += '[PHYSICIAN TEMPLATES — use as formatting reference only]\n' + physicianMemories + '\n[END TEMPLATES]\n\n';
|
||||
}
|
||||
|
||||
var result = await callAI([
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ router.post('/generate-soap', authMiddleware, async (req, res) => {
|
|||
}
|
||||
|
||||
var context = `Patient: ${patientAge || 'Unknown'}, ${patientGender || 'Unknown'}\n\nINPUT:\n${transcript}`;
|
||||
if (physicianMemories) context += '\n\n' + physicianMemories;
|
||||
if (physicianMemories) context += '\n\n[PHYSICIAN TEMPLATES — use as formatting reference only]\n' + physicianMemories + '\n[END TEMPLATES]';
|
||||
|
||||
const result = await callAI([
|
||||
{ role: 'system', content: prompt },
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ router.post('/well-visit/note', authMiddleware, async function(req, res) {
|
|||
context += diagnoses + '\n\n';
|
||||
}
|
||||
if (physicianMemories) {
|
||||
context += physicianMemories + '\n\n';
|
||||
context += '[PHYSICIAN TEMPLATES — use as formatting reference only]\n' + physicianMemories + '\n[END TEMPLATES]\n\n';
|
||||
}
|
||||
|
||||
// Add growth reference and feeding guidance for this age
|
||||
|
|
|
|||
Loading…
Reference in a new issue