diff --git a/packages/backend/src/middleware/auth.js b/packages/backend/src/middleware/auth.js index a231d8b9..0c430557 100644 --- a/packages/backend/src/middleware/auth.js +++ b/packages/backend/src/middleware/auth.js @@ -17,35 +17,26 @@ * along with this program. If not, see . */ "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 diff --git a/packages/backend/src/routers/auth/list-sessions.js b/packages/backend/src/routers/auth/list-sessions.js new file mode 100644 index 00000000..e69de29b diff --git a/packages/backend/src/services/auth/AuthService.js b/packages/backend/src/services/auth/AuthService.js index 29218622..539caf02 100644 --- a/packages/backend/src/services/auth/AuthService.js +++ b/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 = {}; diff --git a/packages/backend/src/services/database/SqliteDatabaseAccessService.js b/packages/backend/src/services/database/SqliteDatabaseAccessService.js index 90049c9f..c54d29ef 100644 --- a/packages/backend/src/services/database/SqliteDatabaseAccessService.js +++ b/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}`); diff --git a/packages/backend/src/services/database/sqlite_setup/0004_sessions.sql b/packages/backend/src/services/database/sqlite_setup/0004_sessions.sql new file mode 100644 index 00000000..a5a986ac --- /dev/null +++ b/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 +);