diff --git a/src/backend/src/middleware/configurable_auth.js b/src/backend/src/middleware/configurable_auth.js index 364acaf41..49d2e9373 100644 --- a/src/backend/src/middleware/configurable_auth.js +++ b/src/backend/src/middleware/configurable_auth.js @@ -20,6 +20,7 @@ const APIError = require('../api/APIError'); const config = require('../config'); const { LegacyTokenError } = require('../services/auth/AuthService'); const { Context } = require('../util/context'); +const jwt = require('jsonwebtoken'); // The "/whoami" endpoint is a special case where we want to allow // a legacy token to be used for authentication. The "/whoami" @@ -47,7 +48,7 @@ const configurable_auth = options => async (req, res, next) => { const optional = options?.optional; // Request might already have been authed (PreAuthService) - if ( req.actor ) next(); + if ( req.actor ) return next(); // === Getting the Token === // This step came from jwt_auth in src/helpers.js @@ -55,15 +56,18 @@ const configurable_auth = options => async (req, res, next) => { // auth middleware, it makes more sense to put it here. let token; + let tokenSource; // Auth token in body if ( req.body && req.body.auth_token ) { token = req.body.auth_token; + tokenSource = 'body'; } // HTTML Auth header else if ( req.header && req.header('Authorization') && !req.header('Authorization').startsWith('Basic ') && req.header('Authorization') !== 'Bearer' ) { // Bearer with no space is something office does token = req.header('Authorization'); token = token.replace('Bearer ', '').trim(); + tokenSource = 'header'; if ( token === 'undefined' ) { APIError.create('unexpected_undefined', null, { msg: 'The Authorization token cannot be the string "undefined"', @@ -74,16 +78,19 @@ const configurable_auth = options => async (req, res, next) => { else if ( req.cookies && req.cookies[config.cookie_name] ) { token = req.cookies[config.cookie_name]; + tokenSource = 'cookie'; } // Auth token in URL else if ( req.query && req.query.auth_token ) { token = req.query.auth_token; + tokenSource = 'query'; } // Socket else if ( req.handshake && req.handshake.query && req.handshake.query.auth_token ) { token = req.handshake.query.auth_token; + tokenSource = 'socket'; } if ( !token || token.startsWith('Basic ') ) { @@ -110,6 +117,20 @@ const configurable_auth = options => async (req, res, next) => { const services = context.get('services'); const svc_auth = services.get('auth'); + // Debug: log token source and decoded type before creating Actor (for session_required / hasHttpPowers debugging) + if ( process.env.DEBUG ) { + let decodedForLog; + try { + decodedForLog = jwt.decode(token); + } catch ( _ ) { /* ignore */ } + console.log('decodedForLog?', decodedForLog); + const tokenType = decodedForLog && decodedForLog.t != null ? decodedForLog.t : '(no type or invalid jwt)'; + const tokenPreview = typeof token === 'string' && token.length > 20 + ? `${token.slice(0, 12)}...${token.slice(-8)}` + : '(short)'; + console.log(`[configurable_auth] token used for Actor: [${req.url}] source=${tokenSource}, decoded.type=${tokenType}, preview=${tokenPreview}`); + } + let actor; try { actor = await svc_auth.authenticate_from_token(token); diff --git a/src/gui/src/UI/Dashboard/TabSecurity.js b/src/gui/src/UI/Dashboard/TabSecurity.js index fbefd99ae..b9990ba96 100644 --- a/src/gui/src/UI/Dashboard/TabSecurity.js +++ b/src/gui/src/UI/Dashboard/TabSecurity.js @@ -152,10 +152,11 @@ const TabSecurity = { const password_confirm_promise = new TeePromise(); const try_password = async () => { const value = $win.find('.password-entry').val(); + // Do not send Authorization: user-protected endpoints use session cookie (hasHttpPowers) const resp = await fetch(`${window.api_origin}/user-protected/disable-2fa`, { method: 'POST', + credentials: 'include', headers: { - Authorization: `Bearer ${puter.authToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ diff --git a/src/gui/src/UI/Settings/UITabSecurity.js b/src/gui/src/UI/Settings/UITabSecurity.js index 1f961e1e4..af2ca184d 100644 --- a/src/gui/src/UI/Settings/UITabSecurity.js +++ b/src/gui/src/UI/Settings/UITabSecurity.js @@ -86,10 +86,11 @@ export default { const password_confirm_promise = new TeePromise(); const try_password = async () => { const value = password_entry.get('value'); + // No Authorization header: user-protected endpoints use session cookie (hasHttpPowers) const resp = await fetch(`${window.api_origin}/user-protected/disable-2fa`, { method: 'POST', + credentials: 'include', headers: { - Authorization: `Bearer ${puter.authToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ diff --git a/src/gui/src/UI/UIWindowChangePassword.js b/src/gui/src/UI/UIWindowChangePassword.js index ae0722d78..db210ea09 100644 --- a/src/gui/src/UI/UIWindowChangePassword.js +++ b/src/gui/src/UI/UIWindowChangePassword.js @@ -114,13 +114,12 @@ async function UIWindowChangePassword (options) { $(el_window).find('.form-error-msg').hide(); + // Do not send Authorization: user-protected endpoints use session cookie (hasHttpPowers) $.ajax({ url: `${window.api_origin }/user-protected/change-password`, type: 'POST', async: true, - headers: { - 'Authorization': `Bearer ${window.auth_token}`, - }, + xhrFields: { withCredentials: true }, contentType: 'application/json', data: JSON.stringify({ password: current_password, diff --git a/src/gui/src/UI/UIWindowChangeUsername.js b/src/gui/src/UI/UIWindowChangeUsername.js index 8e27757e7..9bbb72fda 100644 --- a/src/gui/src/UI/UIWindowChangeUsername.js +++ b/src/gui/src/UI/UIWindowChangeUsername.js @@ -182,12 +182,12 @@ async function UIWindowChangeUsername (options) { const new_username = $(el_window).find('.new-username').val(); const body = { new_username }; if ( password !== undefined && password !== '' ) body.password = password; + // Do not send Authorization: user-protected endpoints use session cookie (hasHttpPowers) return fetch(apiUrl, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', - ...(window.auth_token ? { 'Authorization': `Bearer ${window.auth_token}` } : {}), }, body: JSON.stringify(body), });