Skip to content

Commit

Permalink
πŸ› fix invitation API expiration timestamp creation (#166)
Browse files Browse the repository at this point in the history
* πŸ› fix: invitation API expiration timestamp creation

* 🎨 chore: update invitation service

* 🚨 chore: lint

* πŸ§ͺ chore: update unit tests

* πŸ§ͺ chore: update controller tests

* πŸ§ͺ chore: update tests
  • Loading branch information
rezk2ll authored Jan 13, 2025
1 parent e801680 commit b54e838
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 131 deletions.
2 changes: 1 addition & 1 deletion packages/matrix-identity-server/src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const tables: Record<Collections, string> = {
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<Record<Collections, string[]>> = {
Expand Down
29 changes: 16 additions & 13 deletions packages/tom-server/src/invitation-api/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 4 additions & 4 deletions packages/tom-server/src/invitation-api/middlewares/index.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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()
Expand All @@ -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' })
Expand Down
36 changes: 17 additions & 19 deletions packages/tom-server/src/invitation-api/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,29 +71,17 @@ export default class InvitationService implements IInvitationService {
* @param {string} authorization - Authorization token
* @returns {Promise<void>}
*/
accept = async (id: string, authorization: string): Promise<void> => {
accept = async (id: string): Promise<void> => {
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 })

Expand All @@ -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<string>} - Invitation link
*/
generateLink = async (payload: InvitationPayload): Promise<string> => {
generateLink = async (
payload: InvitationPayload,
authorization: string
): Promise<string> => {
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}`
Expand All @@ -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
})

Expand All @@ -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<string>} - Room ID
*/
Expand Down
64 changes: 18 additions & 46 deletions packages/tom-server/src/invitation-api/tests/controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
Expand All @@ -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'
)
Expand All @@ -207,56 +203,23 @@ 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)
})

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', () => {
Expand All @@ -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
}

Expand Down Expand Up @@ -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`)
Expand All @@ -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)
Expand Down
12 changes: 6 additions & 6 deletions packages/tom-server/src/invitation-api/tests/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ describe('the Invitation API middleware', () => {
{
contact: '+21652123456',
medium: 'phone',
expiration: Date.now() + TEST_DELAY
expiration: `${Date.now() + TEST_DELAY}`
}
])

Expand All @@ -152,7 +152,7 @@ describe('the Invitation API middleware', () => {
{
contact: '+21652123456',
medium: 'phone',
expiration: Date.now() - TEST_DELAY
expiration: `${Date.now() - TEST_DELAY}`
}
])

Expand Down Expand Up @@ -196,7 +196,7 @@ describe('the Invitation API middleware', () => {
{
contact: '+21652123456',
medium: 'phone',
expiration: Date.now() - TEST_DELAY
expiration: `${Date.now() - TEST_DELAY}`
}
])

Expand Down Expand Up @@ -228,7 +228,7 @@ describe('the Invitation API middleware', () => {
{
contact: '+21652123456',
medium: 'phone',
expiration: Date.now() + EXPIRATION
expiration: `${Date.now() + EXPIRATION}`
}
])

Expand Down Expand Up @@ -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}`
}
])

Expand All @@ -291,7 +291,7 @@ describe('the Invitation API middleware', () => {
{
contact: '+21652123456',
medium: 'phone',
expiration: Date.now() + EXPIRATION
expiration: `${Date.now() + EXPIRATION}`
}
])

Expand Down
Loading

0 comments on commit b54e838

Please sign in to comment.