diff --git a/packages/matrix-identity-server/src/db/index.ts b/packages/matrix-identity-server/src/db/index.ts index c63b39ca..aa11cef9 100644 --- a/packages/matrix-identity-server/src/db/index.ts +++ b/packages/matrix-identity-server/src/db/index.ts @@ -52,7 +52,7 @@ const tables: Record = { userPolicies: 'user_id text, policy_name text, accepted integer', userQuotas: 'user_id varchar(64) PRIMARY KEY, size int', invitations: - 'id varchar(64) PRIMARY KEY, sender varchar(64), recepient varchar(64), medium varchar(64), expiration int, accessed int, room_id varchar(64)' + 'id varchar(64) PRIMARY KEY, sender varchar(64), recepient varchar(64), medium varchar(64), expiration varchar(64), accessed int, room_id varchar(64)' } const indexes: Partial> = { diff --git a/packages/tom-server/src/invitation-api/controllers/index.ts b/packages/tom-server/src/invitation-api/controllers/index.ts index f3200300..0c158092 100644 --- a/packages/tom-server/src/invitation-api/controllers/index.ts +++ b/packages/tom-server/src/invitation-api/controllers/index.ts @@ -78,14 +78,7 @@ export default class InvitationApiController { return } - const { authorization } = req.headers - - if (!authorization) { - res.status(400).json({ message: 'Authorization header is required' }) - return - } - - await this.invitationService.accept(id, authorization) + await this.invitationService.accept(id) res.redirect( 301, @@ -148,11 +141,21 @@ export default class InvitationApiController { throw Error('Sender is required') } - const link = await this.invitationService.generateLink({ - sender, - recepient, - medium - }) + const { authorization } = req.headers + + if (!authorization) { + res.status(400).json({ message: 'Authorization header is required' }) + return + } + + const link = await this.invitationService.generateLink( + { + sender, + recepient, + medium + }, + authorization + ) res.status(200).json({ link }) } catch (err) { diff --git a/packages/tom-server/src/invitation-api/middlewares/index.ts b/packages/tom-server/src/invitation-api/middlewares/index.ts index ba31a5a8..3d68bdc2 100644 --- a/packages/tom-server/src/invitation-api/middlewares/index.ts +++ b/packages/tom-server/src/invitation-api/middlewares/index.ts @@ -1,7 +1,7 @@ import { TwakeLogger } from '@twake/logger' import { TwakeDB } from '../../types' import type { NextFunction, Request, Response } from 'express' -import { InvitationRequestPayload } from '../types' +import type { Invitation, InvitationRequestPayload } from '../types' import validator from 'validator' export default class invitationApiMiddleware { @@ -125,13 +125,13 @@ export default class invitationApiMiddleware { body: { contact } }: { body: InvitationRequestPayload } = req - const invitations = await this.db.get( + const invitations = (await this.db.get( 'invitations', ['id', 'expiration'], { recepient: contact } - ) + )) as unknown as Invitation[] if (!invitations || !invitations.length) { next() @@ -141,7 +141,7 @@ export default class invitationApiMiddleware { const lastInvitation = invitations[invitations.length - 1] const { expiration } = lastInvitation - if (Date.now() - +expiration < this.ONE_HOUR) { + if (Date.now() - parseInt(expiration) < this.ONE_HOUR) { res .status(400) .json({ message: 'you already sent an invitation to this contact' }) diff --git a/packages/tom-server/src/invitation-api/services/index.ts b/packages/tom-server/src/invitation-api/services/index.ts index c16a99af..668833fb 100644 --- a/packages/tom-server/src/invitation-api/services/index.ts +++ b/packages/tom-server/src/invitation-api/services/index.ts @@ -71,29 +71,17 @@ export default class InvitationService implements IInvitationService { * @param {string} authorization - Authorization token * @returns {Promise} */ - accept = async (id: string, authorization: string): Promise => { + accept = async (id: string): Promise => { try { const invitation = await this._getInvitationById(id) - let room_id: string | undefined = invitation.room_id - const { expiration, medium, sender, recepient } = invitation + const { expiration } = invitation - if (expiration < Date.now()) { + if (parseInt(expiration) < Date.now()) { throw Error('Invitation expired') } - if (!room_id) { - const payload = { medium, recepient, sender } - room_id = await this._createPrivateRoom(authorization) - - if (!room_id) { - throw Error('Failed to create room') - } - - await this._storeMatrixInvite(payload, authorization, room_id) - } - - await this.db.update('invitations', { accessed: 1, room_id }, 'id', id) + await this.db.update('invitations', { accessed: 1 }, 'id', id) } catch (error) { this.logger.error(`Failed to accept invitation`, { error }) @@ -105,10 +93,21 @@ export default class InvitationService implements IInvitationService { * Generates an invitation link * * @param {invitationPayload} payload - Invitation payload + * @param {string} authorization - Authorization token * @returns {Promise} - Invitation link */ - generateLink = async (payload: InvitationPayload): Promise => { + generateLink = async ( + payload: InvitationPayload, + authorization: string + ): Promise => { try { + const room_id = await this._createPrivateRoom(authorization) + + if (!room_id) { + throw Error('Failed to create room') + } + + await this._storeMatrixInvite(payload, authorization, room_id) const token = await this._createInvitation(payload) return `${this.config.base_url}${PATH}/${token}` @@ -134,7 +133,7 @@ export default class InvitationService implements IInvitationService { await this.db.insert('invitations', { ...payload, id: token, - expiration: Date.now() + this.EXPIRATION, + expiration: `${Date.now() + this.EXPIRATION}`, accessed: 0 }) @@ -149,7 +148,6 @@ export default class InvitationService implements IInvitationService { /** * Creates a private room * - * @param {invitationPayload} payload - Invitation payload * @param {string} authorization - Authorization token * @returns {Promise} - Room ID */ diff --git a/packages/tom-server/src/invitation-api/tests/controller.test.ts b/packages/tom-server/src/invitation-api/tests/controller.test.ts index 4da11aa6..35cdcadd 100644 --- a/packages/tom-server/src/invitation-api/tests/controller.test.ts +++ b/packages/tom-server/src/invitation-api/tests/controller.test.ts @@ -162,15 +162,13 @@ describe('the invitation API controller', () => { sender: 'test', recepient: 'test', medium: 'phone', - expiration: Date.now() + EXPIRATION, + expiration: `${Date.now() + EXPIRATION}`, accessed: 0, room_id: 'test' } ]) - const response = await supertest(app) - .get(`${PATH}/test`) - .set('Authorization', 'Bearer test') + const response = await supertest(app).get(`${PATH}/test`) expect(response.status).toBe(301) }) @@ -182,19 +180,17 @@ describe('the invitation API controller', () => { sender: 'test', recepient: 'test', medium: 'phone', - expiration: Date.now() + EXPIRATION, + expiration: `${Date.now() + EXPIRATION}`, accessed: 0, room_id: 'test' } ]) - await supertest(app) - .get(`${PATH}/test`) - .set('Authorization', 'Bearer test') + await supertest(app).get(`${PATH}/test`) expect(dbMock.update).toHaveBeenCalledWith( 'invitations', - { accessed: 1, room_id: 'test' }, + { accessed: 1 }, 'id', 'test' ) @@ -207,14 +203,12 @@ describe('the invitation API controller', () => { sender: 'test', recepient: 'test', medium: 'phone', - expiration: Date.now() - EXPIRATION, + expiration: `${Date.now() - EXPIRATION}`, accessed: 0 } ]) - const response = await supertest(app) - .get(`${PATH}/test`) - .set('Authorization', 'Bearer test') + const response = await supertest(app).get(`${PATH}/test`) expect(response.status).toBe(500) }) @@ -222,41 +216,10 @@ describe('the invitation API controller', () => { it('should return a 500 if the invitation does not exist', async () => { dbMock.get.mockResolvedValue([]) - const response = await supertest(app) - .get(`${PATH}/test`) - .set('Authorization', 'Bearer test') + const response = await supertest(app).get(`${PATH}/test`) expect(response.status).toBe(500) }) - - it('should create a room if the invitation does not have a room_id', async () => { - dbMock.get.mockResolvedValue([ - { - id: 'test', - sender: 'test', - recepient: 'test', - medium: 'phone', - expiration: Date.now() + EXPIRATION, - accessed: 0 - } - ]) - - global.fetch = jest.fn().mockResolvedValue({ - status: 200, - json: jest.fn().mockResolvedValue({ room_id: 'test' }) - }) - - await supertest(app) - .get(`${PATH}/test`) - .set('Authorization', 'Bearer test') - - expect(dbMock.update).toHaveBeenCalledWith( - 'invitations', - { accessed: 1, room_id: 'test' }, - 'id', - 'test' - ) - }) }) describe('the listInvitations method', () => { @@ -266,7 +229,7 @@ describe('the invitation API controller', () => { sender: 'test', recepient: 'test', medium: 'phone', - expiration: Date.now() + EXPIRATION, + expiration: `${Date.now() + EXPIRATION}`, accessed: 0 } @@ -295,6 +258,11 @@ describe('the invitation API controller', () => { describe('the generateInvitationLink method', () => { it('should attempt to generate an invitation link', async () => { + global.fetch = jest.fn().mockResolvedValue({ + status: 200, + json: jest.fn().mockResolvedValue({ room_id: 'test' }) + }) + dbMock.insert.mockResolvedValue({ id: 'test' }) const response = await supertest(app) .post(`${PATH}/generate`) @@ -309,6 +277,10 @@ describe('the invitation API controller', () => { }) it('should attempt to save the invitation in the db', async () => { + global.fetch = jest.fn().mockResolvedValue({ + status: 200, + json: jest.fn().mockResolvedValue({ room_id: 'test' }) + }) dbMock.insert.mockResolvedValue({ id: 'test' }) await supertest(app) diff --git a/packages/tom-server/src/invitation-api/tests/middleware.test.ts b/packages/tom-server/src/invitation-api/tests/middleware.test.ts index 3bcddf75..0185cc16 100644 --- a/packages/tom-server/src/invitation-api/tests/middleware.test.ts +++ b/packages/tom-server/src/invitation-api/tests/middleware.test.ts @@ -132,7 +132,7 @@ describe('the Invitation API middleware', () => { { contact: '+21652123456', medium: 'phone', - expiration: Date.now() + TEST_DELAY + expiration: `${Date.now() + TEST_DELAY}` } ]) @@ -152,7 +152,7 @@ describe('the Invitation API middleware', () => { { contact: '+21652123456', medium: 'phone', - expiration: Date.now() - TEST_DELAY + expiration: `${Date.now() - TEST_DELAY}` } ]) @@ -196,7 +196,7 @@ describe('the Invitation API middleware', () => { { contact: '+21652123456', medium: 'phone', - expiration: Date.now() - TEST_DELAY + expiration: `${Date.now() - TEST_DELAY}` } ]) @@ -228,7 +228,7 @@ describe('the Invitation API middleware', () => { { contact: '+21652123456', medium: 'phone', - expiration: Date.now() + EXPIRATION + expiration: `${Date.now() + EXPIRATION}` } ]) @@ -268,7 +268,7 @@ describe('the Invitation API middleware', () => { { contact: '+21652123456', medium: 'phone', - expiration: Date.now() - EXPIRATION + TEST_DELAY + expiration: `${Date.now() - EXPIRATION + TEST_DELAY}` } ]) @@ -291,7 +291,7 @@ describe('the Invitation API middleware', () => { { contact: '+21652123456', medium: 'phone', - expiration: Date.now() + EXPIRATION + expiration: `${Date.now() + EXPIRATION}` } ]) diff --git a/packages/tom-server/src/invitation-api/tests/service.test.ts b/packages/tom-server/src/invitation-api/tests/service.test.ts index 7e507687..6f1e0006 100644 --- a/packages/tom-server/src/invitation-api/tests/service.test.ts +++ b/packages/tom-server/src/invitation-api/tests/service.test.ts @@ -41,7 +41,7 @@ describe('the Invitation API service', () => { sender: 'test', recepient: 'test', medium: 'test', - expiration: 123456789, + expiration: '123456789', accessed: 0 } ]) @@ -54,7 +54,7 @@ describe('the Invitation API service', () => { sender: 'test', recepient: 'test', medium: 'test', - expiration: 123456789, + expiration: '123456789', accessed: false } ]) @@ -131,44 +131,39 @@ describe('the Invitation API service', () => { sender: 'test', recepient: 'test', medium: 'phone', - expiration: Date.now() + 123456789, + expiration: `${Date.now() + 123456789}`, room_id: 'test', accessed: 0 } ]) - await invitationService.accept('test', AUTHORIZATION) + await invitationService.accept('test') expect(dbMock.update).toHaveBeenCalledWith( 'invitations', - { accessed: 1, room_id: 'test' }, + { accessed: 1 }, 'id', 'test' ) }) it('should create a room if the invitation does not have one', async () => { - global.fetch = jest.fn().mockResolvedValue({ - status: 200, - json: jest.fn().mockResolvedValue({ room_id: 'test' }) - }) - dbMock.get.mockResolvedValue([ { id: 'test', sender: 'test', recepient: 'test', medium: 'phone', - expiration: Date.now() + 123456789, + expiration: `${Date.now() + 123456789}`, accessed: 0 } ]) - await invitationService.accept('test', AUTHORIZATION) + await invitationService.accept('test') expect(dbMock.update).toHaveBeenCalledWith( 'invitations', - { accessed: 1, room_id: 'test' }, + { accessed: 1 }, 'id', 'test' ) @@ -177,17 +172,17 @@ describe('the Invitation API service', () => { it('should throw an error if the invitation is not found', async () => { dbMock.get.mockResolvedValue([]) - await expect( - invitationService.accept('test', AUTHORIZATION) - ).rejects.toThrow('Failed to accept invitation') + await expect(invitationService.accept('test')).rejects.toThrow( + 'Failed to accept invitation' + ) }) it('should throw an error if the database operation fails', async () => { dbMock.get.mockRejectedValue(new Error('test')) - await expect( - invitationService.accept('test', AUTHORIZATION) - ).rejects.toThrow('Failed to accept invitation') + await expect(invitationService.accept('test')).rejects.toThrow( + 'Failed to accept invitation' + ) }) it('should throw an error if the invitation is expired', async () => { @@ -197,26 +192,33 @@ describe('the Invitation API service', () => { sender: 'test', recepient: 'test', medium: 'phone', - expiration: Date.now() - 123456789, + expiration: `${Date.now() - 123456789}`, accessed: 0 } ]) - await expect( - invitationService.accept('test', AUTHORIZATION) - ).rejects.toThrow('Failed to accept invitation') + await expect(invitationService.accept('test')).rejects.toThrow( + 'Failed to accept invitation' + ) }) }) describe('the generateLink method', () => { it('should generate an invitation link', async () => { + global.fetch = jest.fn().mockResolvedValue({ + status: 200, + json: jest.fn().mockResolvedValue({ room_id: 'test' }) + }) dbMock.insert.mockResolvedValue({ id: 'test' }) - const result = await invitationService.generateLink({ - sender: 'test', - recepient: 'test', - medium: 'phone' - }) + const result = await invitationService.generateLink( + { + sender: 'test', + recepient: 'test', + medium: 'phone' + }, + AUTHORIZATION + ) expect(result.startsWith('http://localhost/_twake/v1/invite/')).toBe(true) }) @@ -225,22 +227,32 @@ describe('the Invitation API service', () => { dbMock.insert.mockRejectedValue(new Error('test')) await expect( - invitationService.generateLink({ - sender: 'test', - recepient: 'test', - medium: 'phone' - }) + invitationService.generateLink( + { + sender: 'test', + recepient: 'test', + medium: 'phone' + }, + AUTHORIZATION + ) ).rejects.toThrow('Failed to generate invitation link') }) it('should insert the invitation into the database', async () => { + global.fetch = jest.fn().mockResolvedValue({ + status: 200, + json: jest.fn().mockResolvedValue({ room_id: 'test' }) + }) dbMock.insert.mockResolvedValue({ id: 'test' }) - await invitationService.generateLink({ - sender: 'test', - recepient: 'test', - medium: 'phone' - }) + await invitationService.generateLink( + { + sender: 'test', + recepient: 'test', + medium: 'phone' + }, + AUTHORIZATION + ) expect(dbMock.insert).toHaveBeenCalledWith( 'invitations', diff --git a/packages/tom-server/src/invitation-api/types.ts b/packages/tom-server/src/invitation-api/types.ts index a2f060e2..c7c810de 100644 --- a/packages/tom-server/src/invitation-api/types.ts +++ b/packages/tom-server/src/invitation-api/types.ts @@ -1,8 +1,11 @@ export interface IInvitationService { invite: (payload: InvitationPayload, authToken: string) => Promise - accept: (token: string, authToken: string) => Promise + accept: (token: string) => Promise list: (userId: string) => Promise - generateLink: (payload: InvitationPayload) => Promise + generateLink: ( + payload: InvitationPayload, + authToken: string + ) => Promise } export type medium = 'email' | 'phone' @@ -12,7 +15,7 @@ export interface Invitation { sender: string recepient: string medium: medium - expiration: number + expiration: string accessed: boolean room_id?: string } diff --git a/packages/tom-server/src/utils.ts b/packages/tom-server/src/utils.ts index b8b931b2..2c373a78 100644 --- a/packages/tom-server/src/utils.ts +++ b/packages/tom-server/src/utils.ts @@ -8,5 +8,5 @@ export const tables = { userQuotas: 'user_id varchar(64) PRIMARY KEY, size int', rooms: 'id varchar(64) PRIMARY KEY, filter varchar(64)', invitations: - 'id varchar(64) PRIMARY KEY, sender varchar(64), recepient varchar(64), medium varchar(64), expiration int, accessed int, room_id varchar(64)' + 'id varchar(64) PRIMARY KEY, sender varchar(64), recepient varchar(64), medium varchar(64), expiration varchar(64), accessed int, room_id varchar(64)' }