mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 07:25:19 +02:00
fix failing integration tests
This commit is contained in:
parent
2eed43f6e0
commit
6dd3110bb1
11 changed files with 53 additions and 64 deletions
|
@ -85,7 +85,6 @@ export const authOptions = {
|
||||||
strategy: 'jwt' as SessionStrategy,
|
strategy: 'jwt' as SessionStrategy,
|
||||||
maxAge: 1 * 24 * 60 * 60, // 1 Day
|
maxAge: 1 * 24 * 60 * 60, // 1 Day
|
||||||
},
|
},
|
||||||
|
|
||||||
providers: [
|
providers: [
|
||||||
CredentialsProvider({
|
CredentialsProvider({
|
||||||
name: 'Credentials',
|
name: 'Credentials',
|
||||||
|
|
|
@ -9,7 +9,6 @@ import {
|
||||||
} from '@maybe-finance/server/features'
|
} from '@maybe-finance/server/features'
|
||||||
import { InMemoryQueueFactory, PgService, type IQueueFactory } from '@maybe-finance/server/shared'
|
import { InMemoryQueueFactory, PgService, type IQueueFactory } from '@maybe-finance/server/shared'
|
||||||
import { createLogger, transports } from 'winston'
|
import { createLogger, transports } from 'winston'
|
||||||
import isCI from 'is-ci'
|
|
||||||
import nock from 'nock'
|
import nock from 'nock'
|
||||||
import Decimal from 'decimal.js'
|
import Decimal from 'decimal.js'
|
||||||
import { startServer, stopServer } from './utils/server'
|
import { startServer, stopServer } from './utils/server'
|
||||||
|
@ -27,7 +26,7 @@ const prisma = new PrismaClient()
|
||||||
// For TypeScript support
|
// For TypeScript support
|
||||||
const plaid = jest.mocked(_plaid) // eslint-disable-line
|
const plaid = jest.mocked(_plaid) // eslint-disable-line
|
||||||
|
|
||||||
const auth0Id = isCI ? 'auth0|61afd38f678a0c006895f046' : 'auth0|61afd340678a0c006895f000'
|
const authId = '__TEST_USER_ID__'
|
||||||
let axios: AxiosInstance
|
let axios: AxiosInstance
|
||||||
let user: User
|
let user: User
|
||||||
|
|
||||||
|
@ -38,7 +37,7 @@ if (process.env.IS_VSCODE_DEBUG === 'true') {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// Clears old user and data, creates new user
|
// Clears old user and data, creates new user
|
||||||
user = await resetUser(auth0Id)
|
user = await resetUser(authId)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('/v1/accounts API', () => {
|
describe('/v1/accounts API', () => {
|
||||||
|
|
|
@ -2,7 +2,6 @@ import type { AxiosInstance } from 'axios'
|
||||||
import type { SharedType } from '@maybe-finance/shared'
|
import type { SharedType } from '@maybe-finance/shared'
|
||||||
import type { Prisma, AccountConnection, AccountSyncStatus, User } from '@prisma/client'
|
import type { Prisma, AccountConnection, AccountSyncStatus, User } from '@prisma/client'
|
||||||
import type { ItemRemoveResponse } from 'plaid'
|
import type { ItemRemoveResponse } from 'plaid'
|
||||||
import isCI from 'is-ci'
|
|
||||||
import { startServer, stopServer } from './utils/server'
|
import { startServer, stopServer } from './utils/server'
|
||||||
import { getAxiosClient } from './utils/axios'
|
import { getAxiosClient } from './utils/axios'
|
||||||
import prisma from '../lib/prisma'
|
import prisma from '../lib/prisma'
|
||||||
|
@ -18,7 +17,7 @@ jest.mock('plaid')
|
||||||
// For TypeScript support
|
// For TypeScript support
|
||||||
const plaid = jest.mocked(_plaid)
|
const plaid = jest.mocked(_plaid)
|
||||||
|
|
||||||
const auth0Id = isCI ? 'auth0|61afd38f678a0c006895f046' : 'auth0|61afd340678a0c006895f000'
|
const authId = '__TEST_USER_ID__'
|
||||||
let axios: AxiosInstance
|
let axios: AxiosInstance
|
||||||
let user: User | null
|
let user: User | null
|
||||||
let connection: AccountConnection
|
let connection: AccountConnection
|
||||||
|
@ -45,7 +44,7 @@ afterAll(async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
user = await resetUser(auth0Id)
|
user = await resetUser(authId)
|
||||||
|
|
||||||
connectionData = {
|
connectionData = {
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -1,38 +1,37 @@
|
||||||
import type { AxiosResponse } from 'axios'
|
import type { AxiosResponse } from 'axios'
|
||||||
import type { SharedType } from '@maybe-finance/shared'
|
import type { SharedType } from '@maybe-finance/shared'
|
||||||
import { superjson } from '@maybe-finance/shared'
|
import { superjson } from '@maybe-finance/shared'
|
||||||
import env from '../../../env'
|
|
||||||
import isCI from 'is-ci'
|
|
||||||
import Axios from 'axios'
|
import Axios from 'axios'
|
||||||
|
import { encode } from 'next-auth/jwt'
|
||||||
|
|
||||||
// Fetches Auth0 access token (JWT) and prepares Axios client to use it on each request
|
|
||||||
export async function getAxiosClient() {
|
export async function getAxiosClient() {
|
||||||
const tenantUrl = isCI
|
const baseUrl = 'http://127.0.0.1:53333/v1'
|
||||||
? 'REPLACE_THIS-staging.us.auth0.com'
|
const jwt = await encode({
|
||||||
: 'REPLACE_THIS-development.us.auth0.com'
|
maxAge: 1 * 24 * 60 * 60,
|
||||||
|
secret: process.env.NEXTAUTH_SECRET || 'CHANGE_ME',
|
||||||
const {
|
token: {
|
||||||
data: { access_token: token },
|
sub: '__TEST_USER_ID__',
|
||||||
} = await Axios.request({
|
user: '__TEST_USER_ID__',
|
||||||
method: 'POST',
|
'https://maybe.co/email': 'REPLACE_THIS',
|
||||||
url: `https://${tenantUrl}/oauth/token`,
|
firstName: 'REPLACE_THIS',
|
||||||
headers: { 'content-type': 'application/json' },
|
lastName: 'REPLACE_THIS',
|
||||||
data: {
|
name: 'REPLACE_THIS',
|
||||||
grant_type: 'password',
|
|
||||||
username: 'REPLACE_THIS',
|
|
||||||
password: 'REPLACE_THIS',
|
|
||||||
audience: 'https://maybe-finance-api/v1',
|
|
||||||
scope: '',
|
|
||||||
client_id: isCI ? 'REPLACE_THIS' : 'REPLACE_THIS',
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const defaultHeaders = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Credentials': true,
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
|
}
|
||||||
|
const axiosOptions = {
|
||||||
|
baseURL: baseUrl,
|
||||||
|
headers: defaultHeaders,
|
||||||
|
}
|
||||||
|
|
||||||
const axios = Axios.create({
|
const axios = Axios.create({
|
||||||
baseURL: 'http://127.0.0.1:53333/v1',
|
...axiosOptions,
|
||||||
validateStatus: () => true, // Tests should determine whether status is correct, not Axios
|
validateStatus: () => true, // Tests should determine whether status is correct, not Axios
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
axios.interceptors.response.use((response: AxiosResponse<SharedType.BaseResponse>) => {
|
axios.interceptors.response.use((response: AxiosResponse<SharedType.BaseResponse>) => {
|
||||||
|
|
|
@ -2,17 +2,20 @@ import cookieParser from 'cookie-parser'
|
||||||
import { decode } from 'next-auth/jwt'
|
import { decode } from 'next-auth/jwt'
|
||||||
|
|
||||||
const SECRET = process.env.NEXTAUTH_SECRET ?? 'REPLACE_THIS'
|
const SECRET = process.env.NEXTAUTH_SECRET ?? 'REPLACE_THIS'
|
||||||
|
|
||||||
export const validateAuthJwt = async (req, res, next) => {
|
export const validateAuthJwt = async (req, res, next) => {
|
||||||
cookieParser(SECRET)(req, res, async (err) => {
|
cookieParser(SECRET)(req, res, async (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return res.status(500).json({ message: 'Internal Server Error' })
|
return res.status(500).json({ message: 'Internal Server Error' })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.cookies && 'next-auth.session-token' in req.cookies) {
|
const cookieName = req.secure
|
||||||
|
? '__Secure-next-auth.session-token'
|
||||||
|
: 'next-auth.session-token'
|
||||||
|
|
||||||
|
if (req.cookies && cookieName in req.cookies) {
|
||||||
try {
|
try {
|
||||||
const token = await decode({
|
const token = await decode({
|
||||||
token: req.cookies['next-auth.session-token'],
|
token: req.cookies[cookieName],
|
||||||
secret: SECRET,
|
secret: SECRET,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -26,6 +29,18 @@ export const validateAuthJwt = async (req, res, next) => {
|
||||||
console.error('Error in token validation', error)
|
console.error('Error in token validation', error)
|
||||||
return res.status(500).json({ message: 'Internal Server Error' })
|
return res.status(500).json({ message: 'Internal Server Error' })
|
||||||
}
|
}
|
||||||
|
} else if (req.headers.authorization) {
|
||||||
|
const token = req.headers.authorization.split(' ')[1]
|
||||||
|
const decoded = await decode({
|
||||||
|
token,
|
||||||
|
secret: SECRET,
|
||||||
|
})
|
||||||
|
if (decoded) {
|
||||||
|
req.user = decoded
|
||||||
|
return next()
|
||||||
|
} else {
|
||||||
|
return res.status(401).json({ message: 'Unauthorized' })
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return res.status(401).json({ message: 'Unauthorized' })
|
return res.status(401).json({ message: 'Unauthorized' })
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,8 +48,6 @@ const envSchema = z.object({
|
||||||
NX_SENTRY_DSN: z.string().optional(),
|
NX_SENTRY_DSN: z.string().optional(),
|
||||||
NX_SENTRY_ENV: z.string().optional(),
|
NX_SENTRY_ENV: z.string().optional(),
|
||||||
|
|
||||||
NX_LD_SDK_KEY: z.string().default('REPLACE_THIS'),
|
|
||||||
|
|
||||||
NX_POLYGON_API_KEY: z.string().default(''),
|
NX_POLYGON_API_KEY: z.string().default(''),
|
||||||
|
|
||||||
NX_PORT: z.string().default('3333'),
|
NX_PORT: z.string().default('3333'),
|
||||||
|
|
|
@ -68,8 +68,8 @@ describe('Finicity', () => {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
name: 'TEST_FINICITY',
|
name: 'TEST_FINICITY',
|
||||||
type: 'finicity',
|
type: 'finicity',
|
||||||
finicityInstitutionId: 'REPLACE_THIS',
|
finicityInstitutionId: '101732',
|
||||||
finicityInstitutionLoginId: 'REPLACE_THIS',
|
finicityInstitutionLoginId: '6000483842',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,6 @@ const envSchema = z.object({
|
||||||
NX_SENTRY_DSN: z.string().optional(),
|
NX_SENTRY_DSN: z.string().optional(),
|
||||||
NX_SENTRY_ENV: z.string().optional(),
|
NX_SENTRY_ENV: z.string().optional(),
|
||||||
|
|
||||||
NX_LD_SDK_KEY: z.string().default('REPLACE_THIS'),
|
|
||||||
|
|
||||||
NX_REDIS_URL: z.string().default('redis://localhost:6379'),
|
NX_REDIS_URL: z.string().default('redis://localhost:6379'),
|
||||||
|
|
||||||
NX_POLYGON_API_KEY: z.string().default(''),
|
NX_POLYGON_API_KEY: z.string().default(''),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import crypto from 'crypto'
|
import CryptoJS from 'crypto-js'
|
||||||
|
|
||||||
export interface ICryptoService {
|
export interface ICryptoService {
|
||||||
encrypt(plainText: string): string
|
encrypt(plainText: string): string
|
||||||
|
@ -6,32 +6,13 @@ export interface ICryptoService {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CryptoService implements ICryptoService {
|
export class CryptoService implements ICryptoService {
|
||||||
private key: Buffer
|
constructor(private readonly secret: string) {}
|
||||||
private ivLength = 16 // Initialization vector length. For AES, this is always 16
|
|
||||||
|
|
||||||
constructor(private readonly secret: string) {
|
|
||||||
// Ensure the key length is suitable for AES-256
|
|
||||||
this.key = crypto.createHash('sha256').update(String(this.secret)).digest()
|
|
||||||
}
|
|
||||||
|
|
||||||
encrypt(plainText: string) {
|
encrypt(plainText: string) {
|
||||||
const iv = crypto.randomBytes(this.ivLength)
|
return CryptoJS.AES.encrypt(plainText, this.secret).toString()
|
||||||
const cipher = crypto.createCipheriv('aes-256-cbc', this.key, iv)
|
|
||||||
let encrypted = cipher.update(plainText, 'utf8', 'hex')
|
|
||||||
encrypted += cipher.final('hex')
|
|
||||||
|
|
||||||
// Include the IV at the start of the encrypted result
|
|
||||||
return iv.toString('hex') + ':' + encrypted
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decrypt(encrypted: string) {
|
decrypt(encrypted: string) {
|
||||||
const textParts = encrypted.split(':')
|
return CryptoJS.AES.decrypt(encrypted, this.secret).toString(CryptoJS.enc.Utf8)
|
||||||
const iv = Buffer.from(textParts.shift()!, 'hex')
|
|
||||||
const encryptedText = textParts.join(':')
|
|
||||||
const decipher = crypto.createDecipheriv('aes-256-cbc', this.key, iv)
|
|
||||||
let decrypted = decipher.update(encryptedText, 'hex', 'utf8')
|
|
||||||
decrypted += decipher.final('utf8')
|
|
||||||
|
|
||||||
return decrypted
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,7 @@
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"bull": "^4.10.2",
|
"bull": "^4.10.2",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
|
"cookie": "^0.6.0",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
|
|
@ -7988,7 +7988,7 @@ cookie@0.5.0, cookie@^0.5.0:
|
||||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
|
||||||
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
|
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
|
||||||
|
|
||||||
cookie@0.6.0:
|
cookie@0.6.0, cookie@^0.6.0:
|
||||||
version "0.6.0"
|
version "0.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
|
||||||
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
|
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue