From bb9edc4f65ecd23eb47f83acc749f586e31cf991 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Thu, 11 Apr 2024 21:33:44 -0400 Subject: [PATCH] Add automatic token migration --- packages/backend/src/config.js | 4 +++ packages/backend/src/middleware/auth2.js | 30 +++++++++++++++++++ packages/backend/src/routers/whoami.js | 17 +++++++++-- .../backend/src/services/auth/AuthService.js | 30 +++++++++++++++---- src/initgui.js | 2 +- 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/packages/backend/src/config.js b/packages/backend/src/config.js index 580a5ca7..55b13fad 100644 --- a/packages/backend/src/config.js +++ b/packages/backend/src/config.js @@ -97,6 +97,10 @@ if (config.server_id) { config.contact_email = 'hey@' + config.domain; +// TODO: default value will be changed to false in a future release; +// details to follow in a future announcement. +config.legacy_token_migrate = true; + module.exports = config; // NEW_CONFIG_LOADING diff --git a/packages/backend/src/middleware/auth2.js b/packages/backend/src/middleware/auth2.js index d9eaa2f3..8b592b96 100644 --- a/packages/backend/src/middleware/auth2.js +++ b/packages/backend/src/middleware/auth2.js @@ -18,8 +18,24 @@ */ const APIError = require("../api/APIError"); const config = require("../config"); +const { UserActorType } = require("../services/auth/Actor"); +const { LegacyTokenError } = require("../services/auth/AuthService"); const { Context } = require("../util/context"); +// The "/whoami" endpoint is a special case where we want to allow +// a legacy token to be used for authentication. The "/whoami" +// endpoint will then return a new token for further requests. +// +const is_whoami = (req) => { + if ( ! config.legacy_token_migrate ) return; + + if ( req.path !== '/whoami' ) return; + + // const subdomain = req.subdomains[res.subdomains.length - 1]; + // if ( subdomain !== 'api' ) return; + return true; +} + // TODO: Allow auth middleware to be used without requiring // authentication. This will allow us to use the auth middleware // in endpoints that do not require authentication, but can @@ -70,6 +86,20 @@ const auth2 = async (req, res, next) => { e.write(res); return; } + if ( e instanceof LegacyTokenError && is_whoami(req) ) { + const new_info = await svc_auth.check_session(token, { + req, + from_upgrade: true, + }) + context.set('actor', new_info.actor); + context.set('user', new_info.user); + req.new_token = new_info.token; + req.token = new_info.token; + req.user = new_info.user; + req.actor = new_info.actor; + next(); + return; + } const re = APIError.create('token_auth_failed'); re.write(res); return; diff --git a/packages/backend/src/routers/whoami.js b/packages/backend/src/routers/whoami.js index e0266123..61ce36de 100644 --- a/packages/backend/src/routers/whoami.js +++ b/packages/backend/src/routers/whoami.js @@ -54,6 +54,7 @@ const WHOAMI_GET = eggspress('/whoami', { is_temp: (req.user.password === null && req.user.email === null), taskbar_items: await get_taskbar_items(req.user), referral_code: req.user.referral_code, + ...(req.new_token ? { token: req.token } : {}) }; if ( ! is_user ) { @@ -65,6 +66,7 @@ const WHOAMI_GET = eggspress('/whoami', { delete details.desktop_bg_color; delete details.desktop_bg_fit; delete details.taskbar_items; + delete details.token; } res.send(details); @@ -76,8 +78,19 @@ const WHOAMI_GET = eggspress('/whoami', { const WHOAMI_POST = new express.Router(); WHOAMI_POST.post('/whoami', auth, fs, express.json(), async (req, response, next)=>{ // check subdomain - if(require('../helpers').subdomain(req) !== 'api') - next(); + if(require('../helpers').subdomain(req) !== 'api') { + return; + } + + const actor = Context.get('actor'); + if ( ! actor ) { + throw Error('actor not found in context'); + } + + const is_user = actor.type instanceof UserActorType; + if ( ! is_user ) { + throw Error('actor is not a user'); + } let desktop_items = []; diff --git a/packages/backend/src/services/auth/AuthService.js b/packages/backend/src/services/auth/AuthService.js index c0034542..a1cfc4bd 100644 --- a/packages/backend/src/services/auth/AuthService.js +++ b/packages/backend/src/services/auth/AuthService.js @@ -25,6 +25,8 @@ const { DB_WRITE } = require("../database/consts"); const APP_ORIGIN_UUID_NAMESPACE = '33de3768-8ee0-43e9-9e73-db192b97a5d8'; +const LegacyTokenError = class extends Error {}; + class AuthService extends BaseService { static MODULES = { jwt: require('jsonwebtoken'), @@ -45,7 +47,7 @@ class AuthService extends BaseService { ); if ( ! decoded.hasOwnProperty('type') ) { - throw new Error('legacy token'); + throw new LegacyTokenError(); const user = await this.db.requireRead( "SELECT * FROM `user` WHERE `uuid` = ? LIMIT 1", [decoded.uuid], @@ -251,7 +253,7 @@ class AuthService extends BaseService { user_uid: user.uuid, }, this.global_config.jwt_secret); - return token; + return { session, token }; } async check_session (cur_token, meta) { @@ -264,13 +266,17 @@ class AuthService extends BaseService { if ( decoded.type && decoded.type !== 'session' ) { return {}; } + + const is_legacy = ! decoded.type; - const user = await get_user({ uuid: decoded.user_uid }); + const user = await get_user({ uuid: + is_legacy ? decoded.uuid : decoded.user_uid + }); if ( ! user ) { return {}; } - if ( decoded.type ) { + if ( ! is_legacy ) { // Ensure session exists const session = await this.get_session_(decoded.uuid); if ( ! session ) { @@ -285,8 +291,19 @@ class AuthService extends BaseService { // Upgrade legacy token // TODO: phase this out - const { token } = await this.create_session_token(user, meta); - return { user, token }; + const { session, token } = await this.create_session_token(user, meta); + + const actor_type = new UserActorType({ + user, + session, + }); + + const actor = new Actor({ + user_uid: user.uuid, + type: actor_type, + }); + + return { actor, user, token }; } async create_access_token (authorizer, permissions) { @@ -430,4 +447,5 @@ class AuthService extends BaseService { module.exports = { AuthService, + LegacyTokenError, }; diff --git a/src/initgui.js b/src/initgui.js index dfb1e3b0..1cc6ac4a 100644 --- a/src/initgui.js +++ b/src/initgui.js @@ -366,7 +366,7 @@ window.initgui = async function(){ } while(!is_verified) } - update_auth_data(window.auth_token, whoami); + update_auth_data(whoami.token || window.auth_token, whoami); // ------------------------------------------------------------------------------------- // Load desktop, only if we're not embedded in a popup