Prechádzať zdrojové kódy

refactor: include session ids prefix to have distinct tokens

Nicolas Meienberger 2 rokov pred
rodič
commit
fb9251d16e

+ 10 - 0
__mocks__/redis.ts

@@ -12,5 +12,15 @@ export const createClient = jest.fn(() => {
     quit: jest.fn(),
     del: (key: string) => values.delete(key),
     ttl: (key: string) => expirations.get(key),
+    keys: (key: string) => {
+      const keys = [];
+      // eslint-disable-next-line no-restricted-syntax
+      for (const [k] of values) {
+        if (k.startsWith(key)) {
+          keys.push(k);
+        }
+      }
+      return keys;
+    },
   };
 });

+ 5 - 0
src/server/common/get-server-auth-session.ts

@@ -1,9 +1,14 @@
 import { type GetServerSidePropsContext } from 'next';
 import jwt from 'jsonwebtoken';
+import { v4 } from 'uuid';
 import { getConfig } from '../core/TipiConfig';
 import TipiCache from '../core/TipiCache';
 import { Logger } from '../core/Logger';
 
+export const generateSessionId = (prefix: string) => {
+  return `${prefix}-${v4()}`;
+};
+
 export const getServerAuthSession = async (ctx: { req: GetServerSidePropsContext['req']; res: GetServerSidePropsContext['res'] }) => {
   const { req } = ctx;
   const token = req.headers.authorization?.split(' ')[1];

+ 14 - 0
src/server/core/TipiCache/TipiCache.ts

@@ -48,6 +48,20 @@ class TipiCache {
     return client.del(key);
   }
 
+  public async delByValue(value: string, prefix = '') {
+    const client = await this.getClient();
+    const keys = await client.keys(`${prefix}*`);
+
+    const promises = keys.map(async (key) => {
+      const val = await client.get(key);
+      if (val === value) {
+        await client.del(key);
+      }
+    });
+
+    return Promise.all(promises);
+  }
+
   public async close() {
     return this.client.quit();
   }

+ 6 - 6
src/server/services/auth/auth.service.test.ts

@@ -3,8 +3,8 @@ import fs from 'fs-extra';
 import * as argon2 from 'argon2';
 import jwt from 'jsonwebtoken';
 import { faker } from '@faker-js/faker';
-import { v4 } from 'uuid';
 import { TotpAuthenticator } from '@/server/utils/totp';
+import { generateSessionId } from '@/server/common/get-server-auth-session';
 import { encrypt } from '../../utils/encryption';
 import { setConfig } from '../../core/TipiConfig';
 import { createUser } from '../../tests/user.factory';
@@ -90,7 +90,7 @@ describe('Test: verifyTotp', () => {
 
     const encryptedTotpSecret = encrypt(totpSecret, salt);
     const user = await createUser({ email, totp_enabled: true, totp_secret: encryptedTotpSecret, salt }, db);
-    const totpSessionId = v4();
+    const totpSessionId = generateSessionId('otp');
     const otp = TotpAuthenticator.generate(totpSecret);
 
     await TipiCache.set(totpSessionId, user.id.toString());
@@ -110,7 +110,7 @@ describe('Test: verifyTotp', () => {
     const totpSecret = TotpAuthenticator.generateSecret();
     const encryptedTotpSecret = encrypt(totpSecret, salt);
     const user = await createUser({ email, totp_enabled: true, totp_secret: encryptedTotpSecret, salt }, db);
-    const totpSessionId = v4();
+    const totpSessionId = generateSessionId('otp');
     await TipiCache.set(totpSessionId, user.id.toString());
 
     // act & assert
@@ -124,7 +124,7 @@ describe('Test: verifyTotp', () => {
     const totpSecret = TotpAuthenticator.generateSecret();
     const encryptedTotpSecret = encrypt(totpSecret, salt);
     const user = await createUser({ email, totp_enabled: true, totp_secret: encryptedTotpSecret, salt }, db);
-    const totpSessionId = v4();
+    const totpSessionId = generateSessionId('otp');
     const otp = TotpAuthenticator.generate(totpSecret);
 
     await TipiCache.set(totpSessionId, user.id.toString());
@@ -135,7 +135,7 @@ describe('Test: verifyTotp', () => {
 
   it('should throw if the user does not exist', async () => {
     // arrange
-    const totpSessionId = v4();
+    const totpSessionId = generateSessionId('otp');
     await TipiCache.set(totpSessionId, '1234');
 
     // act & assert
@@ -149,7 +149,7 @@ describe('Test: verifyTotp', () => {
     const totpSecret = TotpAuthenticator.generateSecret();
     const encryptedTotpSecret = encrypt(totpSecret, salt);
     const user = await createUser({ email, totp_enabled: false, totp_secret: encryptedTotpSecret, salt }, db);
-    const totpSessionId = v4();
+    const totpSessionId = generateSessionId('otp');
     const otp = TotpAuthenticator.generate(totpSecret);
 
     await TipiCache.set(totpSessionId, user.id.toString());

+ 7 - 7
src/server/services/auth/auth.service.ts

@@ -1,9 +1,9 @@
 import { PrismaClient } from '@prisma/client';
 import * as argon2 from 'argon2';
-import { v4 } from 'uuid';
 import jwt from 'jsonwebtoken';
 import validator from 'validator';
 import { TotpAuthenticator } from '@/server/utils/totp';
+import { generateSessionId } from '@/server/common/get-server-auth-session';
 import { getConfig } from '../../core/TipiConfig';
 import TipiCache from '../../core/TipiCache';
 import { fileExists, unlinkFile } from '../../common/fs.helpers';
@@ -46,10 +46,10 @@ export class AuthServiceClass {
       throw new Error('Wrong password');
     }
 
-    const session = v4();
+    const session = generateSessionId('auth');
 
     if (user.totp_enabled) {
-      const totpSessionId = v4();
+      const totpSessionId = generateSessionId('otp');
       await TipiCache.set(totpSessionId, user.id.toString());
       return { totpSessionId };
     }
@@ -94,7 +94,7 @@ export class AuthServiceClass {
       throw new Error('Invalid TOTP code');
     }
 
-    const session = v4();
+    const session = generateSessionId('otp');
     const token = jwt.sign({ id: user.id, session }, getConfig().jwtSecret, { expiresIn: '7d' });
 
     await TipiCache.set(session, user.id.toString());
@@ -131,7 +131,7 @@ export class AuthServiceClass {
     const newTotpSecret = TotpAuthenticator.generateSecret();
 
     if (!salt) {
-      salt = v4();
+      salt = generateSessionId('');
     }
 
     const encryptedTotpSecret = encrypt(newTotpSecret, salt);
@@ -241,7 +241,7 @@ export class AuthServiceClass {
     const hash = await argon2.hash(password);
     const newUser = await this.prisma.user.create({ data: { username: email, password: hash, operator: true } });
 
-    const session = v4();
+    const session = generateSessionId('auth');
     const token = jwt.sign({ id: newUser.id, session }, getConfig().jwtSecret, { expiresIn: '1d' });
 
     await TipiCache.set(session, newUser.id.toString());
@@ -294,7 +294,7 @@ export class AuthServiceClass {
     // Expire token in 6 seconds
     await TipiCache.set(session, userId, 6);
 
-    const newSession = v4();
+    const newSession = generateSessionId('auth');
     const token = jwt.sign({ id: userId, session: newSession }, getConfig().jwtSecret, { expiresIn: '1d' });
     await TipiCache.set(newSession, userId);