Skip to content

Commit

Permalink
Merge branch '2893/multi-tenancy-v1' into 3149/mk/seed-operator-tenant
Browse files Browse the repository at this point in the history
# Conflicts:
#	packages/backend/jest.config.js
  • Loading branch information
mkurapov committed Dec 9, 2024
2 parents 1da190f + 07630c1 commit a97dca7
Show file tree
Hide file tree
Showing 14 changed files with 844 additions and 6 deletions.
2 changes: 2 additions & 0 deletions localenv/cloud-nine-wallet/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ services:
TIGERBEETLE_REPLICA_ADDRESSES: ${TIGERBEETLE_REPLICA_ADDRESSES-''}
AUTH_SERVER_GRANT_URL: ${CLOUD_NINE_AUTH_SERVER_DOMAIN:-http://cloud-nine-wallet-auth:3006}
AUTH_SERVER_INTROSPECTION_URL: http://cloud-nine-wallet-auth:3007
AUTH_ADMIN_API_URL: 'http://cloud-nine-wallet-auth:3003/graphql'
AUTH_ADMIN_API_SECRET: 'rPoZpe9tVyBNCigm05QDco7WLcYa0xMao7lO5KG1XG4='
ILP_ADDRESS: ${ILP_ADDRESS:-test.cloud-nine-wallet}
STREAM_SECRET: BjPXtnd00G2mRQwP/8ZpwyZASOch5sUXT5o0iR5b5wU=
API_SECRET: iyIgCprjb9uL8wFckR+pLEkJWMB7FJhgkvqhTQR/964=
Expand Down
2 changes: 2 additions & 0 deletions localenv/happy-life-bank/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ services:
USE_TIGERBEETLE: false
AUTH_SERVER_GRANT_URL: ${HAPPY_LIFE_BANK_AUTH_SERVER_DOMAIN:-http://happy-life-bank-auth:3006}
AUTH_SERVER_INTROSPECTION_URL: http://happy-life-bank-auth:3007
AUTH_ADMIN_API_URL: 'http://happy-life-bank-auth:4003/graphql'
AUTH_ADMIN_API_SECRET: 'rPoZpe9tVyBNCigm05QDco7WLcYa0xMao7lO5KG1XG4='
ILP_ADDRESS: test.happy-life-bank
ILP_CONNECTOR_URL: http://happy-life-bank-backend:4002
STREAM_SECRET: BjPXtnd00G2mRQwP/8ZpwyZASOch5sUXT5o0iR5b5wU=
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/jest.env.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ process.env.WEBHOOK_URL = 'http://127.0.0.1:4001/webhook'
process.env.STREAM_SECRET = '2/PxuRFV9PAp0yJlnAifJ+1OxujjjI16lN+DBnLNRLA='
process.env.USE_TIGERBEETLE = false
process.env.ENABLE_TELEMETRY = false
process.env.AUTH_ADMIN_API_URL = 'http://127.0.0.1:3003/graphql'
process.env.AUTH_ADMIN_API_SECRET = 'test-secret'
process.env.OPERATOR_TENANT_ID = 'cf5fd7d3-1eb1-4041-8e43-ba45747e9e5d'
process.env.OPERATOR_TENANT_SECRET =
'KQEXlZO65jUJXakXnLxGO7dk387mt71G9tZ42rULSNU='
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
exports.up = function (knex) {
return knex.schema.createTable('tenants', function (table) {
table.uuid('id').notNullable().primary()
table.string('email').notNullable()
table.string('email')
table.string('apiSecret').notNullable()
table.string('idpConsentUrl')
table.string('idpSecret')
table.string('publicName')
table.string('apiSecret')

table.timestamp('createdAt').defaultTo(knex.fn.now())
table.timestamp('updatedAt').defaultTo(knex.fn.now())
table.timestamp('deletedAt')
})
}

Expand Down
2 changes: 1 addition & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"dev": "ts-node-dev --inspect=0.0.0.0:9229 --respawn --transpile-only --require ./src/telemetry/index.ts src/index.ts"
},
"devDependencies": {
"@apollo/client": "^3.11.8",
"@graphql-codegen/cli": "5.0.2",
"@graphql-codegen/introspection": "4.0.3",
"@graphql-codegen/typescript": "4.0.6",
Expand Down Expand Up @@ -46,6 +45,7 @@
},
"dependencies": {
"@adonisjs/fold": "^8.2.0",
"@apollo/client": "^3.11.8",
"@apollo/server": "^4.11.2",
"@as-integrations/koa": "^1.1.1",
"@escape.tech/graphql-armor": "^2.4.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ export const Config = {

authServerGrantUrl: envString('AUTH_SERVER_GRANT_URL'),
authServerIntrospectionUrl: envString('AUTH_SERVER_INTROSPECTION_URL'),
authAdminApiUrl: envString('AUTH_ADMIN_API_URL'),
authAdminApiSecret: envString('AUTH_ADMIN_API_SECRET'),
authAdminApiSignatureVersion: envInt('AUTH_ADMIN_API_SIGNATURE_VERSION', 1),

outgoingPaymentWorkers: envInt('OUTGOING_PAYMENT_WORKERS', 1),
outgoingPaymentWorkerIdle: envInt('OUTGOING_PAYMENT_WORKER_IDLE', 10), // milliseconds
Expand Down
98 changes: 98 additions & 0 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { createClient } from 'tigerbeetle-node'
import { createClient as createIntrospectionClient } from 'token-introspection'
import net from 'net'
import dns from 'dns'
import { createHmac } from 'crypto'

import {
createAuthenticatedClient as createOpenPaymentsClient,
Expand All @@ -15,6 +16,17 @@ import {
} from '@interledger/open-payments'
import { StreamServer } from '@interledger/stream-receiver'
import axios from 'axios'
import {
ApolloClient,
ApolloLink,
createHttpLink,
InMemoryCache
} from '@apollo/client'
import { onError } from '@apollo/client/link/error'
import { setContext } from '@apollo/client/link/context'
import { canonicalize } from 'json-canonicalize'
import { print } from 'graphql/language/printer'

import { createAccountingService as createPsqlAccountingService } from './accounting/psql/service'
import { createAccountingService as createTigerbeetleAccountingService } from './accounting/tigerbeetle/service'
import { App, AppServices } from './app'
Expand Down Expand Up @@ -61,6 +73,7 @@ import {
} from './telemetry/service'
import { createWebhookService } from './webhook/service'
import { createInMemoryDataStore } from './middleware/cache/data-stores/in-memory'
import { createTenantService } from './tenants/service'

BigInt.prototype.toJSON = function () {
return this.toString()
Expand Down Expand Up @@ -131,6 +144,91 @@ export function initIocContainer(
})
})

container.singleton('apolloClient', async (deps) => {
const [logger, config] = await Promise.all([
deps.use('logger'),
deps.use('config')
])

const httpLink = createHttpLink({
uri: config.authAdminApiUrl
})

const errorLink = onError(({ graphQLErrors }) => {
if (graphQLErrors) {
logger.error(graphQLErrors)
graphQLErrors.map(({ extensions }) => {
if (extensions && extensions.code === 'UNAUTHENTICATED') {
logger.error('UNAUTHENTICATED')
}

if (extensions && extensions.code === 'FORBIDDEN') {
logger.error('FORBIDDEN')
}
})
}
})

const authLink = setContext((request, { headers }) => {
if (!config.authAdminApiSecret || !config.authAdminApiSignatureVersion)
return { headers }
const timestamp = Math.round(new Date().getTime() / 1000)
const version = config.authAdminApiSignatureVersion

const { query, variables, operationName } = request
const formattedRequest = {
variables,
operationName,
query: print(query)
}

const payload = `${timestamp}.${canonicalize(formattedRequest)}`
const hmac = createHmac('sha256', config.authAdminApiSecret)
hmac.update(payload)
const digest = hmac.digest('hex')

return {
headers: {
...headers,
signature: `t=${timestamp}, v${version}=${digest}`
}
}
})

const link = ApolloLink.from([errorLink, authLink, httpLink])

const client = new ApolloClient({
cache: new InMemoryCache({}),
link: link,
defaultOptions: {
query: {
fetchPolicy: 'no-cache'
},
mutate: {
fetchPolicy: 'no-cache'
},
watchQuery: {
fetchPolicy: 'no-cache'
}
}
})

return client
})

container.singleton('tenantCache', async () => {
return createInMemoryDataStore(config.localCacheDuration)
})

container.singleton('tenantService', async (deps) => {
return createTenantService({
logger: await deps.use('logger'),
knex: await deps.use('knex'),
apolloClient: await deps.use('apolloClient'),
tenantCache: await deps.use('tenantCache')
})
})

container.singleton('ratesService', async (deps) => {
const config = await deps.use('config')
return createRatesService({
Expand Down
13 changes: 13 additions & 0 deletions packages/backend/src/tenants/model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BaseModel } from '../shared/baseModel'
import { Pojo } from 'objection'

export class Tenant extends BaseModel {
public static get tableName(): string {
Expand All @@ -7,5 +8,17 @@ export class Tenant extends BaseModel {

public email!: string
public apiSecret!: string
public idpConsentUrl!: string
public idpSecret!: string
public publicName?: string

public deletedAt?: Date

$formatJson(json: Pojo): Pojo {
json = super.$formatJson(json)
return {
...json,
deletedAt: json.deletedAt.toISOString()
}
}
}
Loading

0 comments on commit a97dca7

Please sign in to comment.