Skip to content

Commit

Permalink
Merge pull request #112 from rezk2ll/#99-update
Browse files Browse the repository at this point in the history
🎨 added update recovery words route (#99)
  • Loading branch information
guimard authored Jul 12, 2024
2 parents b38abac + dd7727f commit c7d605c
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 19 deletions.
2 changes: 1 addition & 1 deletion docs/openapi.json

Large diffs are not rendered by default.

60 changes: 58 additions & 2 deletions packages/tom-server/src/vault-api/controllers/vault.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { type NextFunction, type Request, type Response } from 'express'
import { type TwakeDB } from '../../db'
import { type tokenDetail } from '../middlewares/auth'
import { VaultAPIError, type expressAppHandler } from '../utils'
import { getRecoveryWords, methodNotAllowed, saveRecoveryWords } from './vault'
import {
getRecoveryWords,
methodNotAllowed,
saveRecoveryWords,
updateRecoveryWords
} from './vault'

const words = 'This is a test sentence'

Expand All @@ -14,7 +19,8 @@ describe('Vault controllers', () => {
const dbManager: Partial<TwakeDB> = {
get: jest.fn(),
insert: jest.fn(),
deleteWhere: jest.fn()
deleteWhere: jest.fn(),
update: jest.fn()
}
let mockRequest: ITestRequest
let mockResponse: Partial<Response>
Expand Down Expand Up @@ -69,6 +75,7 @@ describe('Vault controllers', () => {
// Testing saveRecoveryWords
it('should return response with status code 201 on save success', async () => {
jest.spyOn(dbManager, 'insert').mockResolvedValue([{ words }])
jest.spyOn(dbManager, 'get').mockResolvedValue([])
const handler: expressAppHandler = saveRecoveryWords(dbManager as TwakeDB)
handler(mockRequest as Request, mockResponse as Response, nextFunction)
await new Promise(process.nextTick)
Expand All @@ -78,12 +85,34 @@ describe('Vault controllers', () => {
it('should call next function to throw error on saving failed', async () => {
const errorMsg = 'Insert failed'
jest.spyOn(dbManager, 'insert').mockRejectedValue(new Error(errorMsg))
jest.spyOn(dbManager, 'get').mockResolvedValue([])
const handler: expressAppHandler = saveRecoveryWords(dbManager as TwakeDB)
handler(mockRequest as Request, mockResponse as Response, nextFunction)
await new Promise(process.nextTick)
expect(nextFunction).toHaveBeenCalledWith(new Error(errorMsg))
})

it('should return a 409 response when recovery words already exists', async () => {
jest
.spyOn(dbManager, 'get')
.mockResolvedValue([{ words: 'Another sentence for the same user' }])
const handler: expressAppHandler = saveRecoveryWords(dbManager as TwakeDB)
handler(mockRequest as Request, mockResponse as Response, nextFunction)
await new Promise(process.nextTick)
expect(mockResponse.statusCode).toEqual(409)
expect(dbManager.insert).not.toHaveBeenCalled()
})

it('should return a 400 error if the body does not contain recovery words', async () => {
jest.spyOn(dbManager, 'get').mockResolvedValue([])
const handler: expressAppHandler = saveRecoveryWords(dbManager as TwakeDB)
const emptyRequest = { ...mockRequest, body: {} }
handler(emptyRequest as Request, mockResponse as Response, nextFunction)
await new Promise(process.nextTick)
expect(mockResponse.statusCode).toEqual(400)
expect(dbManager.insert).not.toHaveBeenCalled()
})

// Testing getRecoveryWords

it('should return response with status code 200 on get success', async () => {
Expand Down Expand Up @@ -127,4 +156,31 @@ describe('Vault controllers', () => {
await new Promise(process.nextTick)
expect(nextFunction).toHaveBeenCalledWith(new Error(errorMsg))
})

it('should return a 200 response on update success', async () => {
jest
.spyOn(dbManager, 'get')
.mockResolvedValue([{ userId: 'test', words: 'some recovery words' }])
const handler: expressAppHandler = updateRecoveryWords(dbManager as TwakeDB)
handler(mockRequest as Request, mockResponse as Response, nextFunction)
await new Promise(process.nextTick)
expect(mockResponse.statusCode).toEqual(200)
})

it('should throw a 404 error when no recovery words were found', async () => {
jest.spyOn(dbManager, 'get').mockResolvedValue([])
const handler: expressAppHandler = updateRecoveryWords(dbManager as TwakeDB)
handler(mockRequest as Request, mockResponse as Response, nextFunction)
await new Promise(process.nextTick)
expect(mockResponse.statusCode).toEqual(404)
})

it('should throw a 400 error when the body does not contain recovery words', async () => {
jest.spyOn(dbManager, 'get').mockResolvedValue([{ userId: 'test' }])
const handler: expressAppHandler = updateRecoveryWords(dbManager as TwakeDB)
const emptyRequest = { ...mockRequest, body: {} }
handler(emptyRequest as Request, mockResponse as Response, nextFunction)
await new Promise(process.nextTick)
expect(mockResponse.statusCode).toEqual(400)
})
})
86 changes: 74 additions & 12 deletions packages/tom-server/src/vault-api/controllers/vault.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable no-useless-return */
import { type TwakeDB } from '../../db'
import { type Collections } from '../../types'
import { VaultAPIError, type expressAppHandler } from '../utils'

export type VaultController = (db: TwakeDB) => expressAppHandler
Expand All @@ -7,20 +10,38 @@ export const methodNotAllowed: expressAppHandler = (req, res, next) => {
throw new VaultAPIError('Method not allowed', 405)
}

/**
* Save use recovery words
*
* @param {TwakeDB} db - the database instance
* @retuns {expressAppHandler} - the express handler
*/
export const saveRecoveryWords = (db: TwakeDB): expressAppHandler => {
return (req, res, next) => {
const data: Record<string, string> = {
userId: req.token.content.sub,
words: req.body.words
}
// @ts-expect-error 'recoveryWords' isn't declared in Collection
db.insert('recoveryWords', data)
.then((_) => {
res.status(201).json({ message: 'Saved recovery words sucessfully' })
})
.catch((err) => {
next(err)
return async (req, res, next) => {
const { words } = req.body
const userId = req.token.content.sub

try {
if (words === undefined || words.length === 0) {
res.status(400).json({ error: 'Missing recovery words' })
return
}

const data = await db.get('recoveryWords' as Collections, ['words'], {
userId
})

if (data.length > 0) {
res.status(409).json({ error: 'User already has recovery words' })
return
} else {
await db.insert('recoveryWords' as Collections, { userId, words })
res.status(201).json({ message: 'Saved recovery words successfully' })
return
}
} catch (err) {
next(err)
}
}
}

Expand Down Expand Up @@ -79,3 +100,44 @@ export const deleteRecoveryWords = (db: TwakeDB): expressAppHandler => {
})
}
}

/**
* Update recovery words in database
*
* @param {TwakeDB} db - the database instance
* @returns {expressAppHandler} - the express controller handler
*/
export const updateRecoveryWords = (db: TwakeDB): expressAppHandler => {
return async (req, res, next) => {
const userId: string = req.token.content.sub
const { words } = req.body

try {
if (words === undefined || words.length === 0) {
res.status(400).json({ message: 'Missing recovery sentence' })
return
}

const data = await db.get('recoveryWords' as Collections, ['words'], {
userId
})

if (data.length === 0) {
res.status(404).json({ message: 'User has no recovery sentence' })
return
}

await db.update(
'recoveryWords' as Collections,
{ words },
'userId',
userId
)

res.status(200).json({ message: 'Updated recovery words successfully' })
return
} catch (err) {
next(err)
}
}
}
20 changes: 16 additions & 4 deletions packages/tom-server/src/vault-api/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ describe('Vault API server', () => {
})

it('reject not allowed method with 405', async () => {
const response = await request(app).put(endpoint)
const response = await request(app).patch(endpoint)
expect(response.statusCode).toBe(405)
expect(response.body).toStrictEqual({
expect(response.body).toStrictEqual({
error: 'Method not allowed'
})
})
Expand All @@ -145,7 +145,7 @@ describe('Vault API server', () => {
.set('Authorization', `Bearer ${accessToken}`)
expect(response.statusCode).toBe(201)
expect(response.body).toStrictEqual({
message: 'Saved recovery words sucessfully'
message: 'Saved recovery words successfully'
})
})

Expand Down Expand Up @@ -206,7 +206,7 @@ describe('Vault API server', () => {
.set('Authorization', `Bearer ${unsavedToken}`)
expect(response.statusCode).toBe(201)
expect(response.body).toStrictEqual({
message: 'Saved recovery words sucessfully'
message: 'Saved recovery words successfully'
})
await removeUserInAccessTokenTable(unsavedToken)
await removeUserInRecoveryWordsTable(matrixServerResponseBody.user_id)
Expand Down Expand Up @@ -236,6 +236,18 @@ describe('Vault API server', () => {
})
})

it('should update words in the dabase if the connected user have some', async () => {
const response = await request(app)
.put(endpoint)
.send({ words })
.set('Authorization', `Bearer ${accessToken}`)

expect(response.statusCode).toBe(200)
expect(response.body).toStrictEqual({
message: 'Updated recovery words successfully'
})
})

it('should reject if more than 100 requests are done in less than 10 seconds on get words', async () => {
let response
let token
Expand Down
44 changes: 44 additions & 0 deletions packages/tom-server/src/vault-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getRecoveryWords,
methodNotAllowed,
saveRecoveryWords,
updateRecoveryWords,
type VaultController
} from './controllers/vault'
import isAuth, { type tokenDetail } from './middlewares/auth'
Expand Down Expand Up @@ -155,6 +156,49 @@ export default class TwakeVaultAPI {
* $ref: '#/components/responses/InternalServerError'
*/
.delete(...this._middlewares(deleteRecoveryWords))
/**
* @openapi
* '/_twake/recoveryWords':
* put:
* tags:
* - Vault API
* description: Update stored connected user recovery words in database
* requestBody:
* description: Object containing the recovery words of the connected user
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* words:
* type: string
* description: The new recovery words of the connected user
* required:
* - words
* example:
* words: This is the updated recovery sentence of rtyler
* responses:
* 200:
* description: Success
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* description: Message indicating that words have been successfully updated
* example:
* message: Updated recovery words sucessfully
* 401:
* $ref: '#/components/responses/Unauthorized'
* 500:
* $ref: '#/components/responses/InternalServerError'
* 400:
* description: Bad request
*/
.put(...this._middlewares(updateRecoveryWords))
.all(allowCors, methodNotAllowed, errorMiddleware)
}

Expand Down

0 comments on commit c7d605c

Please sign in to comment.