forked from maybe-finance/maybe
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
32 changed files
with
614 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { TellerApi } from '@maybe-finance/teller-api' | ||
import { getWebhookUrl } from './webhook' | ||
|
||
const teller = new TellerApi() | ||
|
||
export default teller | ||
|
||
export async function getTellerWebhookUrl() { | ||
const webhookUrl = await getWebhookUrl() | ||
return `${webhookUrl}/v1/teller/webhook` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
apps/server/src/app/middleware/validate-teller-signature.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import crypto from 'crypto' | ||
import type { RequestHandler } from 'express' | ||
import type { TellerTypes } from '@maybe-finance/teller-api' | ||
import env from '../../env' | ||
|
||
// https://teller.io/docs/api/webhooks#verifying-messages | ||
export const validateTellerSignature: RequestHandler = (req, res, next) => { | ||
const signatureHeader = req.headers['teller-signature'] as string | undefined | ||
|
||
if (!signatureHeader) { | ||
return res.status(401).send('No Teller-Signature header found') | ||
} | ||
|
||
const { timestamp, signatures } = parseTellerSignatureHeader(signatureHeader) | ||
const threeMinutesAgo = Math.floor(Date.now() / 1000) - 3 * 60 | ||
|
||
if (parseInt(timestamp) < threeMinutesAgo) { | ||
return res.status(408).send('Signature timestamp is too old') | ||
} | ||
|
||
const signedMessage = `${timestamp}.${JSON.stringify(req.body as TellerTypes.WebhookData)}` | ||
const expectedSignature = createHmacSha256(signedMessage, env.NX_TELLER_SIGNING_SECRET) | ||
|
||
if (!signatures.includes(expectedSignature)) { | ||
return res.status(401).send('Invalid webhook signature') | ||
} | ||
|
||
next() | ||
} | ||
|
||
const parseTellerSignatureHeader = ( | ||
header: string | ||
): { timestamp: string; signatures: string[] } => { | ||
const parts = header.split(',') | ||
const timestampPart = parts.find((p) => p.startsWith('t=')) | ||
const signatureParts = parts.filter((p) => p.startsWith('v1=')) | ||
|
||
if (!timestampPart) { | ||
throw new Error('No timestamp in Teller-Signature header') | ||
} | ||
|
||
const timestamp = timestampPart.split('=')[1] | ||
const signatures = signatureParts.map((p) => p.split('=')[1]) | ||
|
||
return { timestamp, signatures } | ||
} | ||
|
||
const createHmacSha256 = (message: string, secret: string): string => { | ||
return crypto.createHmac('sha256', secret).update(message).digest('hex') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
export * from './plaid' | ||
export * from './finicity' | ||
export * from './teller' | ||
export * from './vehicle' | ||
export * from './property' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './teller.webhook' | ||
export * from './teller.service' |
33 changes: 33 additions & 0 deletions
33
libs/server/features/src/providers/teller/teller.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import type { Logger } from 'winston' | ||
import type { AccountConnection, PrismaClient, User } from '@prisma/client' | ||
import type { IETL, SyncConnectionOptions } from '@maybe-finance/server/shared' | ||
import type { IInstitutionProvider } from '../../institution' | ||
import type { | ||
AccountConnectionSyncEvent, | ||
IAccountConnectionProvider, | ||
} from '../../account-connection' | ||
import _ from 'lodash' | ||
import axios from 'axios' | ||
import { v4 as uuid } from 'uuid' | ||
import { SharedUtil } from '@maybe-finance/shared' | ||
import { etl } from '@maybe-finance/server/shared' | ||
import type { TellerApi } from '@maybe-finance/teller-api' | ||
|
||
export interface ITellerConnect { | ||
generateConnectUrl(userId: User['id'], institutionId: string): Promise<{ link: string }> | ||
|
||
generateFixConnectUrl( | ||
userId: User['id'], | ||
accountConnectionId: AccountConnection['id'] | ||
): Promise<{ link: string }> | ||
} | ||
|
||
export class TellerService { | ||
constructor( | ||
private readonly logger: Logger, | ||
private readonly prisma: PrismaClient, | ||
private readonly teller: TellerApi, | ||
private readonly webhookUrl: string | Promise<string>, | ||
private readonly testMode: boolean | ||
) {} | ||
} |
34 changes: 34 additions & 0 deletions
34
libs/server/features/src/providers/teller/teller.webhook.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import type { Logger } from 'winston' | ||
import type { PrismaClient } from '@prisma/client' | ||
import type { TellerApi, TellerTypes } from '@maybe-finance/teller-api' | ||
import type { IAccountConnectionService } from '../../account-connection' | ||
|
||
export interface ITellerWebhookHandler { | ||
handleWebhook(data: TellerTypes.WebhookData): Promise<void> | ||
} | ||
|
||
export class TellerWebhookHandler implements ITellerWebhookHandler { | ||
constructor( | ||
private readonly logger: Logger, | ||
private readonly prisma: PrismaClient, | ||
private readonly teller: TellerApi, | ||
private readonly accountConnectionService: IAccountConnectionService | ||
) {} | ||
|
||
/** | ||
* Process Teller webhooks. These handlers should execute as quick as possible and | ||
* long-running operations should be performed in the background. | ||
*/ | ||
async handleWebhook(data: TellerTypes.WebhookData) { | ||
switch (data.type) { | ||
case 'webhook.test': { | ||
this.logger.info('Received Teller webhook test') | ||
break | ||
} | ||
default: { | ||
this.logger.warn('Unhandled Teller webhook', { data }) | ||
break | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,37 @@ | ||
import CryptoJS from 'crypto-js' | ||
import crypto from 'crypto' | ||
|
||
export interface ICryptoService { | ||
encrypt(plainText: string): string | ||
decrypt(encrypted: string): string | ||
} | ||
|
||
export class CryptoService implements ICryptoService { | ||
constructor(private readonly secret: string) {} | ||
private key: Buffer | ||
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) { | ||
return CryptoJS.AES.encrypt(plainText, this.secret).toString() | ||
const iv = crypto.randomBytes(this.ivLength) | ||
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) { | ||
return CryptoJS.AES.decrypt(encrypted, this.secret).toString(CryptoJS.enc.Utf8) | ||
const textParts = encrypted.split(':') | ||
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"extends": ["../../.eslintrc.json"], | ||
"ignorePatterns": ["!**/*"], | ||
"overrides": [ | ||
{ | ||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.ts", "*.tsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.js", "*.jsx"], | ||
"rules": {} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/* eslint-disable */ | ||
export default { | ||
displayName: 'teller-api', | ||
preset: '../../jest.preset.js', | ||
globals: { | ||
'ts-jest': { | ||
tsconfig: '<rootDir>/tsconfig.spec.json', | ||
}, | ||
}, | ||
testEnvironment: 'node', | ||
transform: { | ||
'^.+\\.[tj]sx?$': 'ts-jest', | ||
}, | ||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], | ||
coverageDirectory: '../../coverage/libs/teller-api', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './teller-api' | ||
export * as TellerTypes from './types' |
Oops, something went wrong.