Kaynağa Gözat

Add automatic token migration

KernelDeimos 1 yıl önce
ebeveyn
işleme
bb9edc4f65

+ 4 - 0
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

+ 30 - 0
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;

+ 15 - 2
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 = [];
 

+ 24 - 6
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,
 };

+ 1 - 1
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