From 95f89ba03e70a799dbf251c14603df21432c7dc9 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Tue, 13 Feb 2024 01:49:40 +0100 Subject: [PATCH 1/2] refactor: Use objectionjs instead of knex for factories --- packages/backend/test/factories/config.js | 6 ++---- packages/backend/test/factories/connection.js | 6 ++---- packages/backend/test/factories/execution-step.js | 4 ++-- packages/backend/test/factories/execution.js | 6 ++---- packages/backend/test/factories/flow.js | 3 ++- packages/backend/test/factories/permission.js | 6 ++---- packages/backend/test/factories/role.js | 4 +++- packages/backend/test/factories/step.js | 3 ++- packages/backend/test/factories/user.js | 3 ++- 9 files changed, 19 insertions(+), 22 deletions(-) diff --git a/packages/backend/test/factories/config.js b/packages/backend/test/factories/config.js index fe21d765d2..49801b8b87 100644 --- a/packages/backend/test/factories/config.js +++ b/packages/backend/test/factories/config.js @@ -1,4 +1,5 @@ import { faker } from '@faker-js/faker'; +import Config from '../../src/models/config'; export const createConfig = async (params = {}) => { const configData = { @@ -6,10 +7,7 @@ export const createConfig = async (params = {}) => { value: params?.value || { data: 'sampleConfig' }, }; - const [config] = await global.knex - .table('config') - .insert(configData) - .returning('*'); + const config = await Config.query().insert(configData).returning('*'); return config; }; diff --git a/packages/backend/test/factories/connection.js b/packages/backend/test/factories/connection.js index aff7abff9b..b7188ee08c 100644 --- a/packages/backend/test/factories/connection.js +++ b/packages/backend/test/factories/connection.js @@ -1,5 +1,6 @@ import appConfig from '../../src/config/app'; import { AES } from 'crypto-js'; +import Connection from '../../src/models/connection'; export const createConnection = async (params = {}) => { params.key = params?.key || 'deepl'; @@ -16,10 +17,7 @@ export const createConnection = async (params = {}) => { appConfig.encryptionKey ).toString(); - const [connection] = await global.knex - .table('connections') - .insert(params) - .returning('*'); + const connection = await Connection.query().insert(params).returning('*'); return connection; }; diff --git a/packages/backend/test/factories/execution-step.js b/packages/backend/test/factories/execution-step.js index 01b9daa5ab..a470fcc16d 100644 --- a/packages/backend/test/factories/execution-step.js +++ b/packages/backend/test/factories/execution-step.js @@ -1,3 +1,4 @@ +import ExecutionStep from '../../src/models/execution-step'; import { createExecution } from './execution'; import { createStep } from './step'; @@ -8,8 +9,7 @@ export const createExecutionStep = async (params = {}) => { params.dataIn = params?.dataIn || { dataIn: 'dataIn' }; params.dataOut = params?.dataOut || { dataOut: 'dataOut' }; - const [executionStep] = await global.knex - .table('executionSteps') + const executionStep = await ExecutionStep.query() .insert(params) .returning('*'); diff --git a/packages/backend/test/factories/execution.js b/packages/backend/test/factories/execution.js index 67ea634052..ade693c118 100644 --- a/packages/backend/test/factories/execution.js +++ b/packages/backend/test/factories/execution.js @@ -1,3 +1,4 @@ +import Execution from '../../src/models/execution'; import { createFlow } from './flow'; export const createExecution = async (params = {}) => { @@ -6,10 +7,7 @@ export const createExecution = async (params = {}) => { params.createdAt = params?.createdAt || new Date().toISOString(); params.updatedAt = params?.updatedAt || new Date().toISOString(); - const [execution] = await global.knex - .table('executions') - .insert(params) - .returning('*'); + const execution = await Execution.query().insert(params).returning('*'); return execution; }; diff --git a/packages/backend/test/factories/flow.js b/packages/backend/test/factories/flow.js index 09cebbbdef..2e576828a1 100644 --- a/packages/backend/test/factories/flow.js +++ b/packages/backend/test/factories/flow.js @@ -1,3 +1,4 @@ +import Flow from '../../src/models/flow'; import { createUser } from './user'; export const createFlow = async (params = {}) => { @@ -6,7 +7,7 @@ export const createFlow = async (params = {}) => { params.createdAt = params?.createdAt || new Date().toISOString(); params.updatedAt = params?.updatedAt || new Date().toISOString(); - const [flow] = await global.knex.table('flows').insert(params).returning('*'); + const flow = await Flow.query().insert(params).returning('*'); return flow; }; diff --git a/packages/backend/test/factories/permission.js b/packages/backend/test/factories/permission.js index bc604adf74..a81da49354 100644 --- a/packages/backend/test/factories/permission.js +++ b/packages/backend/test/factories/permission.js @@ -1,3 +1,4 @@ +import Permission from '../../src/models/permission'; import { createRole } from './role'; export const createPermission = async (params = {}) => { @@ -6,10 +7,7 @@ export const createPermission = async (params = {}) => { params.subject = params?.subject || 'User'; params.conditions = params?.conditions || ['isCreator']; - const [permission] = await global.knex - .table('permissions') - .insert(params) - .returning('*'); + const permission = await Permission.query().insert(params).returning('*'); return permission; }; diff --git a/packages/backend/test/factories/role.js b/packages/backend/test/factories/role.js index f2ed88bc42..5091fbd1b5 100644 --- a/packages/backend/test/factories/role.js +++ b/packages/backend/test/factories/role.js @@ -1,8 +1,10 @@ +import Role from '../../src/models/role'; + export const createRole = async (params = {}) => { params.name = params?.name || 'Viewer'; params.key = params?.key || 'viewer'; - const [role] = await global.knex.table('roles').insert(params).returning('*'); + const role = await Role.query().insert(params).returning('*'); return role; }; diff --git a/packages/backend/test/factories/step.js b/packages/backend/test/factories/step.js index 9fb0ec8416..fd230764d0 100644 --- a/packages/backend/test/factories/step.js +++ b/packages/backend/test/factories/step.js @@ -1,3 +1,4 @@ +import Step from '../../src/models/step'; import { createFlow } from './flow'; export const createStep = async (params = {}) => { @@ -16,7 +17,7 @@ export const createStep = async (params = {}) => { params.appKey = params?.appKey || (params.type === 'action' ? 'deepl' : 'webhook'); - const [step] = await global.knex.table('steps').insert(params).returning('*'); + const step = await Step.query().insert(params).returning('*'); return step; }; diff --git a/packages/backend/test/factories/user.js b/packages/backend/test/factories/user.js index 6ae60d36fc..11dbc062f6 100644 --- a/packages/backend/test/factories/user.js +++ b/packages/backend/test/factories/user.js @@ -1,5 +1,6 @@ import { createRole } from './role'; import { faker } from '@faker-js/faker'; +import User from '../../src/models/user'; export const createUser = async (params = {}) => { params.roleId = params?.roleId || (await createRole()).id; @@ -7,7 +8,7 @@ export const createUser = async (params = {}) => { params.email = params?.email || faker.internet.email(); params.password = params?.password || faker.internet.password(); - const [user] = await global.knex.table('users').insert(params).returning('*'); + const user = await User.query().insert(params).returning('*'); return user; }; From 9f0e0ca65663819809ee515aa3b6f3d77e2e685f Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Tue, 13 Feb 2024 02:04:50 +0100 Subject: [PATCH 2/2] feat: Implement users/me API endpoint --- .../api/v1/users/get-current-user.js | 5 +++ .../api/v1/users/get-current-user.test.js | 26 +++++++++++++++ .../backend/src/helpers/authentication.js | 8 +++++ packages/backend/src/models/user.js | 11 +++++++ packages/backend/src/routes/api/v1/users.js | 9 ++++++ packages/backend/src/routes/index.js | 2 ++ packages/backend/test/payloads/user.js | 32 +++++++++++++++++++ 7 files changed, 93 insertions(+) create mode 100644 packages/backend/src/controllers/api/v1/users/get-current-user.js create mode 100644 packages/backend/src/controllers/api/v1/users/get-current-user.test.js create mode 100644 packages/backend/src/routes/api/v1/users.js create mode 100644 packages/backend/test/payloads/user.js diff --git a/packages/backend/src/controllers/api/v1/users/get-current-user.js b/packages/backend/src/controllers/api/v1/users/get-current-user.js new file mode 100644 index 0000000000..7008168842 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/users/get-current-user.js @@ -0,0 +1,5 @@ +import { renderObject } from '../../../../helpers/renderer.js'; + +export default async (request, response) => { + renderObject(response, request.currentUser); +}; diff --git a/packages/backend/src/controllers/api/v1/users/get-current-user.test.js b/packages/backend/src/controllers/api/v1/users/get-current-user.test.js new file mode 100644 index 0000000000..7d33b244f6 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/users/get-current-user.test.js @@ -0,0 +1,26 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import request from 'supertest'; +import app from '../../../../app.js'; +import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; +import { createUser } from '../../../../../test/factories/user'; +import userPayload from '../../../../../test/payloads/user'; + +describe('GET /api/v1/users/me', () => { + let role, currentUser, token; + + beforeEach(async () => { + currentUser = await createUser(); + role = await currentUser.$relatedQuery('role'); + token = createAuthTokenByUserId(currentUser.id); + }); + + it('should return current user info', async () => { + const response = await request(app) + .get('/api/v1/users/me') + .set('Authorization', token) + .expect(200); + + const expectedPayload = userPayload(currentUser, role); + expect(response.body).toEqual(expectedPayload); + }); +}); diff --git a/packages/backend/src/helpers/authentication.js b/packages/backend/src/helpers/authentication.js index b3ca3b9ea1..010856eebc 100644 --- a/packages/backend/src/helpers/authentication.js +++ b/packages/backend/src/helpers/authentication.js @@ -28,6 +28,14 @@ export const isAuthenticated = async (_parent, _args, req) => { } }; +export const authenticateUser = async (request, response, next) => { + if (await isAuthenticated(null, null, request)) { + next(); + } else { + return response.status(401).end(); + } +}; + const isAuthenticatedRule = rule()(isAuthenticated); export const authenticationRules = { diff --git a/packages/backend/src/models/user.js b/packages/backend/src/models/user.js index b1a82a7128..16eda3c20d 100644 --- a/packages/backend/src/models/user.js +++ b/packages/backend/src/models/user.js @@ -143,6 +143,17 @@ class User extends Base { }, }); + $formatJson(json) { + json = super.$formatJson(json); + + delete json.password; + delete json.deletedAt; + delete json.resetPasswordToken; + delete json.resetPasswordTokenSentAt; + + return json; + } + login(password) { return bcrypt.compare(password, this.password); } diff --git a/packages/backend/src/routes/api/v1/users.js b/packages/backend/src/routes/api/v1/users.js new file mode 100644 index 0000000000..2bd2ab94cb --- /dev/null +++ b/packages/backend/src/routes/api/v1/users.js @@ -0,0 +1,9 @@ +import { Router } from 'express'; +import { authenticateUser } from '../../../helpers/authentication.js'; +import getCurrentUserAction from '../../../controllers/api/v1/users/get-current-user.js'; + +const router = Router(); + +router.get('/me', authenticateUser, getCurrentUserAction); + +export default router; diff --git a/packages/backend/src/routes/index.js b/packages/backend/src/routes/index.js index 7d1d2bdf8a..215a732632 100644 --- a/packages/backend/src/routes/index.js +++ b/packages/backend/src/routes/index.js @@ -4,6 +4,7 @@ import webhooksRouter from './webhooks.js'; import paddleRouter from './paddle.ee.js'; import healthcheckRouter from './healthcheck.js'; import automatischRouter from './api/v1/automatisch.js'; +import usersRouter from './api/v1/users.js'; const router = Router(); @@ -12,5 +13,6 @@ router.use('/webhooks', webhooksRouter); router.use('/paddle', paddleRouter); router.use('/healthcheck', healthcheckRouter); router.use('/api/v1/automatisch', automatischRouter); +router.use('/api/v1/users', usersRouter); export default router; diff --git a/packages/backend/test/payloads/user.js b/packages/backend/test/payloads/user.js new file mode 100644 index 0000000000..073dde49e2 --- /dev/null +++ b/packages/backend/test/payloads/user.js @@ -0,0 +1,32 @@ +const userPayload = (currentUser, role) => { + return { + data: { + createdAt: currentUser.createdAt.toISOString(), + email: currentUser.email, + fullName: currentUser.fullName, + id: currentUser.id, + permissions: [], + role: { + createdAt: role.createdAt.toISOString(), + description: null, + id: role.id, + isAdmin: role.isAdmin, + key: role.key, + name: role.name, + updatedAt: role.updatedAt.toISOString(), + }, + roleId: role.id, + trialExpiryDate: currentUser.trialExpiryDate.toISOString(), + updatedAt: currentUser.updatedAt.toISOString(), + }, + meta: { + count: 1, + currentPage: null, + isArray: false, + totalPages: null, + type: 'User', + }, + }; +}; + +export default userPayload;