1
0
Эх сурвалжийг харах

Add session listing and revocation

KernelDeimos 1 жил өмнө
parent
commit
18b3e06fe8

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

@@ -0,0 +1,23 @@
+const eggspress = require("../../api/eggspress");
+const { UserActorType } = require("../../services/auth/Actor");
+const { Context } = require("../../util/context");
+
+module.exports = eggspress('/auth/list-sessions', {
+    subdomain: 'api',
+    auth2: true,
+    allowedMethods: ['GET'],
+}, async (req, res, next) => {
+    const x = Context.get();
+    const svc_auth = x.get('services').get('auth');
+
+    // Only users can list their own sessions
+    // apps, access tokens, etc should NEVER access this
+    const actor = x.get('actor');
+    if ( ! (actor.type instanceof UserActorType) ) {
+        throw APIError.create('forbidden');
+    }
+
+    const sessions = await svc_auth.list_sessions(actor);
+
+    res.json(sessions);
+});

+ 33 - 0
packages/backend/src/routers/auth/revoke-session.js

@@ -0,0 +1,33 @@
+const APIError = require("../../api/APIError");
+const eggspress = require("../../api/eggspress");
+const { UserActorType } = require("../../services/auth/Actor");
+const { Context } = require("../../util/context");
+
+module.exports = eggspress('/auth/revoke-session', {
+    subdomain: 'api',
+    auth2: true,
+    allowedMethods: ['POST'],
+}, async (req, res, next) => {
+    const x = Context.get();
+    const svc_auth = x.get('services').get('auth');
+
+    // Only users can list their own sessions
+    // apps, access tokens, etc should NEVER access this
+    const actor = x.get('actor');
+    if ( ! (actor.type instanceof UserActorType) ) {
+        throw APIError.create('forbidden');
+    }
+
+    // Ensure valid UUID
+    if ( ! req.body.uuid || typeof req.body.uuid !== 'string' ) {
+        throw APIError.create('field_invalid', null, {
+            key: 'uuid',
+            expected: 'string'
+        });
+    }
+
+    const sessions = await svc_auth.revoke_session(
+        actor, req.body.uuid);
+
+    res.json({ sessions });
+});

+ 2 - 1
packages/backend/src/routers/login.js

@@ -89,7 +89,8 @@ router.post('/login', express.json(), body_parser_error_handler, async (req, res
             return res.status(400).send('Incorrect password.')
         // check password
         if(await bcrypt.compare(req.body.password, user.password)){
-            const token = await jwt.sign({uuid: user.uuid}, config.jwt_secret)
+            const svc_auth = req.services.get('auth');
+            const token = await svc_auth.create_session_token(user);
             //set cookie
             // res.cookie(config.cookie_name, token);
             res.cookie(config.cookie_name, token, {

+ 11 - 5
packages/backend/src/routers/signup.js

@@ -52,6 +52,7 @@ module.exports = eggspress(['/signup'], {
     const validator = require('validator')
     let uuid_user;
 
+    const svc_auth = Context.get('services').get('auth');
     const svc_authAudit = Context.get('services').get('auth-audit');
     svc_authAudit.record({
         requester: Context.get('requester'),
@@ -67,9 +68,11 @@ module.exports = eggspress(['/signup'], {
 
     // check if user is already logged in
     if ( req.body.is_temp && req.cookies[config.cookie_name] ) {
-        const token = req.cookies[config.cookie_name];
-        const decoded = await jwt.verify(token, config.jwt_secret);
-        const user = await get_user({ uuid: decoded.uuid });
+        const { user, token } = await svc_auth.check_session(
+            req.cookies[config.cookie_name]
+        );
+        // const decoded = await jwt.verify(token, config.jwt_secret);
+        // const user = await get_user({ uuid: decoded.uuid });
         if ( user ) {
             return res.send({
                 token: token,
@@ -233,17 +236,20 @@ module.exports = eggspress(['/signup'], {
         db.write('UPDATE `user` SET `last_activity_ts` = now() WHERE id=? LIMIT 1', [pseudo_user.id]);
         invalidate_cached_user_by_id(pseudo_user.id);
     }
-    // create token for login
-    const token = await jwt.sign({uuid: user_uuid}, config.jwt_secret);
 
     // user id
     // todo if pseudo user, assign directly no need to do another DB lookup
     const user_id = (pseudo_user === undefined) ? insert_res.insertId : pseudo_user.id;
+
     const [user] = await db.read(
         'SELECT * FROM `user` WHERE `id` = ? LIMIT 1',
         [user_id]
     );
 
+    // create token for login
+    const token = await svc_auth.create_session_token(user);
+        // jwt.sign({uuid: user_uuid}, config.jwt_secret);
+
     //-------------------------------------------------------------
     // email confirmation
     //-------------------------------------------------------------

+ 2 - 0
packages/backend/src/services/PuterAPIService.js

@@ -33,6 +33,8 @@ class PuterAPIService extends BaseService {
         app.use(require('../routers/auth/grant-user-user'));
         app.use(require('../routers/auth/revoke-user-user'));
         app.use(require('../routers/auth/list-permissions'))
+        app.use(require('../routers/auth/list-sessions'))
+        app.use(require('../routers/auth/revoke-session'))
         app.use(require('../routers/auth/check-app'))
         app.use(require('../routers/auth/app-uid-from-origin'))
         app.use(require('../routers/auth/create-access-token'))

+ 41 - 4
packages/backend/src/services/auth/AuthService.js

@@ -70,7 +70,7 @@ class AuthService extends BaseService {
         }
 
         if ( decoded.type === 'session' ) {
-            const session = this.get_session_(decoded.uuid);
+            const session = await this.get_session_(decoded.uuid);
 
             if ( ! session ) {
                 throw APIError.create('token_auth_failed');
@@ -80,6 +80,7 @@ class AuthService extends BaseService {
 
             const actor_type = new UserActorType({
                 user,
+                session: session.uuid,
             });
 
             return new Actor({
@@ -218,8 +219,9 @@ class AuthService extends BaseService {
             cur_token, this.global_config.jwt_secret
         );
 
+        console.log('\x1B[36;1mDECODED SESSION', decoded);
+
         if ( decoded.type && decoded.type !== 'session' ) {
-            // throw APIError.create('token_auth_failed');
             return {};
         }
         
@@ -228,12 +230,22 @@ class AuthService extends BaseService {
             return {};
         }
 
-        if ( decoded.type ) return { user, token: cur_token };
+        if ( decoded.type ) {
+            // Ensure session exists
+            const session = await this.get_session_(decoded.uuid);
+            if ( ! session ) {
+                return {};
+            }
+
+            // Return the session
+            return { user, token: cur_token };
+        }
 
         this.log.info(`UPGRADING SESSION`);
 
         // Upgrade legacy token
-        const token = await this.create_session_token(user);
+        // TODO: phase this out
+        const { token } = await this.create_session_token(user);
         return { user, token };
     }
 
@@ -294,6 +306,31 @@ class AuthService extends BaseService {
         return jwt;
     }
 
+    async list_sessions (actor) {
+        // We won't take the cached sessions here because it's
+        // possible the user has sessions on other servers
+        const sessions = await this.db.read(
+            'SELECT uuid, meta FROM `sessions` WHERE `user_id` = ?',
+            [actor.type.user.id],
+        );
+
+        sessions.forEach(session => {
+            if ( session.uuid === actor.type.session ) {
+                session.current = true;
+            }
+        });
+
+        return sessions;
+    }
+
+    async revoke_session (actor, uuid) {
+        delete this.sessions[uuid];
+        await this.db.write(
+            `DELETE FROM sessions WHERE uuid = ? AND user_id = ?`,
+            [uuid, actor.type.user.id]
+        );
+    }
+
     async get_user_app_token_from_origin (origin) {
         origin = this._origin_from_url(origin);
         const app_uid = await this._app_uid_from_origin(origin);