Bläddra i källkod

Improve session mgmt (part 1)

KernelDeimos 1 år sedan
förälder
incheckning
e436693d3e

+ 11 - 20
packages/backend/src/middleware/auth.js

@@ -17,35 +17,26 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 "use strict"
+const APIError = require('../api/APIError');
 const {jwt_auth} = require('../helpers');
+const { UserActorType } = require('../services/auth/Actor');
 const { DB_WRITE } = require('../services/database/consts');
 const { Context } = require('../util/context');
+const auth2 = require('./auth2');
 
 const auth = async (req, res, next)=>{
+    let auth2_ok = false;
     try{
-        let auth_res = await jwt_auth(req);
+        // Delegate to new middleware
+        await auth2(req, res, () => { auth2_ok = true; });
+        if ( ! auth2_ok ) return;
 
-        // is account suspended?
-        if(auth_res.user.suspended)
-            return res.status(401).send({error: 'Account suspended'});
-
-        // successful auth
-        req.user = auth_res.user;
-        req.token = auth_res.token;
-
-        // let's add it to the context too
-        try {
-        const x = Context.get();
-        x.set('user', req.user);
-        } catch (e) {
-        console.error(e);
+        // Everything using the old reference to the auth middleware
+        // should only allow session tokens
+        if ( ! (req.actor.type instanceof UserActorType) ) {
+            throw APIError.create('forbidden');
         }
 
-        // record as daily active users
-        const db = req.services.get('database').get(DB_WRITE, 'auth');
-        db.write('UPDATE `user` SET `last_activity_ts` = now() WHERE id=? LIMIT 1', [req.user.id]);
-
-        // go to next
         next();
     }
     // auth failed

+ 0 - 0
packages/backend/src/routers/auth/list-sessions.js


+ 88 - 0
packages/backend/src/services/auth/AuthService.js

@@ -34,6 +34,8 @@ class AuthService extends BaseService {
 
     async _init () {
         this.db = await this.services.get('database').get(DB_WRITE, 'auth');
+
+        this.sessions = {};
     }
 
     async authenticate_from_token (token) {
@@ -43,6 +45,7 @@ class AuthService extends BaseService {
         );
 
         if ( ! decoded.hasOwnProperty('type') ) {
+            throw new Error('legacy token');
             const user = await this.db.requireRead(
                 "SELECT * FROM `user` WHERE `uuid` = ?  LIMIT 1",
                 [decoded.uuid],
@@ -66,6 +69,25 @@ class AuthService extends BaseService {
             });
         }
 
+        if ( decoded.type === 'session' ) {
+            const session = this.get_session_(decoded.uuid);
+
+            if ( ! session ) {
+                throw APIError.create('token_auth_failed');
+            }
+
+            const user = await get_user({ uuid: decoded.user_uid });
+
+            const actor_type = new UserActorType({
+                user,
+            });
+
+            return new Actor({
+                user_uid: decoded.user_uid,
+                type: actor_type,
+            });
+        }
+
         if ( decoded.type === 'app-under-user' ) {
             const user = await get_user({ uuid: decoded.user_uid });
             if ( ! user ) {
@@ -149,6 +171,72 @@ class AuthService extends BaseService {
         return token;
     }
 
+    async create_session_ (user, meta = {}) {
+        this.log.info(`CREATING SESSION`);
+        const uuid = this.modules.uuidv4();
+        await this.db.write(
+            'INSERT INTO `sessions` ' +
+            '(`uuid`, `user_id`, `meta`) ' +
+            'VALUES (?, ?, ?)',
+            [uuid, user.id, JSON.stringify(meta)],
+        );
+        const session = { uuid, user_uid: user.uuid, meta };
+        this.sessions[uuid] = session;
+        return session;
+    }
+
+    async get_session_ (uuid) {
+        this.log.info(`USING SESSION`);
+        if ( this.sessions[uuid] ) {
+            return this.sessions[uuid];
+        }
+
+        const [session] = await this.db.read(
+            "SELECT * FROM `sessions` WHERE `uuid` = ? LIMIT 1",
+            [uuid],
+        );
+
+        return session;
+    }
+
+    async create_session_token (user, meta) {
+        const session = await this.create_session_(user, meta);
+
+        const token = this.modules.jwt.sign({
+            type: 'session',
+            version: '0.0.0',
+            uuid: session.uuid,
+            meta: session.meta,
+            user_uid: user.uuid,
+        }, this.global_config.jwt_secret);
+
+        return token;
+    }
+
+    async check_session (cur_token) {
+        const decoded = this.modules.jwt.verify(
+            cur_token, this.global_config.jwt_secret
+        );
+
+        if ( decoded.type && decoded.type !== 'session' ) {
+            // throw APIError.create('token_auth_failed');
+            return {};
+        }
+        
+        const user = await get_user({ uuid: decoded.user_uid });
+        if ( ! user ) {
+            return {};
+        }
+
+        if ( decoded.type ) return { user, token: cur_token };
+
+        this.log.info(`UPGRADING SESSION`);
+
+        // Upgrade legacy token
+        const token = await this.create_session_token(user);
+        return { user, token };
+    }
+
     async create_access_token (authorizer, permissions) {
         const jwt_obj = {};
         const authorizer_obj = {};

+ 6 - 1
packages/backend/src/services/database/SqliteDatabaseAccessService.js

@@ -42,7 +42,7 @@ class SqliteDatabaseAccessService extends BaseDatabaseAccessService {
         this.db = new Database(this.config.path);
 
         // Database upgrade logic
-        const TARGET_VERSION = 1;
+        const TARGET_VERSION = 2;
 
         if ( do_setup ) {
             this.log.noticeme(`SETUP: creating database at ${this.config.path}`);
@@ -50,6 +50,7 @@ class SqliteDatabaseAccessService extends BaseDatabaseAccessService {
                 '0001_create-tables.sql',
                 '0002_add-default-apps.sql',
                 '0003_user-permissions.sql',
+                '0004_sessions.sql',
             ].map(p => path_.join(__dirname, 'sqlite_setup', p));
             const fs = require('fs');
             for ( const filename of sql_files ) {
@@ -70,6 +71,10 @@ class SqliteDatabaseAccessService extends BaseDatabaseAccessService {
             upgrade_files.push('0003_user-permissions.sql');
         }
 
+        if ( user_version <= 1 ) {
+            upgrade_files.push('0004_sessions.sql');
+        }
+
         if ( upgrade_files.length > 0 ) {
             this.log.noticeme(`Database out of date: ${this.config.path}`);
             this.log.noticeme(`UPGRADING DATABASE: ${user_version} -> ${TARGET_VERSION}`);

+ 7 - 0
packages/backend/src/services/database/sqlite_setup/0004_sessions.sql

@@ -0,0 +1,7 @@
+CREATE TABLE `sessions` (
+    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
+    "user_id" INTEGER NOT NULL,
+    "uuid" TEXT NOT NULL,
+    "meta" JSON DEFAULT NULL,
+    FOREIGN KEY("user_id") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE
+);