Skip to content

Commit

Permalink
feat: default service-key for single tenant
Browse files Browse the repository at this point in the history
  • Loading branch information
fenos committed Jan 15, 2024
1 parent 39f7413 commit f19c744
Show file tree
Hide file tree
Showing 12 changed files with 55 additions and 71 deletions.
7 changes: 2 additions & 5 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,12 @@ SERVER_REGION=region-of-where-your-service-is-running
#######################################
AUTH_JWT_SECRET=f023d3db-39dc-4ac9-87b2-b2be72e9162b
AUTH_JWT_ALGORITHM=HS256
AUTH_ENCRYPTION_KEY=encryptionkey


#######################################
# Single Tenant
#######################################
TENANT_ID=bjhaohmqunupljrqypxz
ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMzUzMTk4NSwiZXhwIjoxOTI5MTA3OTg1fQ.mqfi__KnQB4v6PkIjkhzfwWrYyF94MEbSC6LnuvVniE
SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjEzNTMxOTg1LCJleHAiOjE5MjkxMDc5ODV9.th84OKK0Iz8QchDyXZRrojmKSEZ-OuitQm_5DvLiSIc


#######################################
# Multi Tenancy
Expand All @@ -33,7 +29,8 @@ SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiw
# MULTI_TENANT=true
DATABASE_MULTITENANT_URL=postgresql://postgres:[email protected]:5433/postgres
REQUEST_X_FORWARDED_HOST_REGEXP=
ADMIN_API_KEYS=apikey
SERVER_ADMIN_API_KEYS=apikey
AUTH_ENCRYPTION_KEY=encryptionkey


#######################################
Expand Down
4 changes: 0 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@ services:
# Auth
AUTH_JWT_SECRET: f023d3db-39dc-4ac9-87b2-b2be72e9162b
AUTH_JWT_ALGORITHM: HS256
AUTH_ENCRYPTION_KEY: encryptionkey
# Single tenant Mode
TENANT_ID: bjwdssmqcnupljrqypxz
ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMzUzMTk4NSwiZXhwIjoxOTI5MTA3OTg1fQ.mqfi__KnQB4v6PkIjkhzfwWrYyF94MEbSC6LnuvVniE
SERVICE_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjEzNTMxOTg1LCJleHAiOjE5MjkxMDc5ODV9.th84OKK0Iz8QchDyXZRrojmKSEZ-OuitQm_5DvLiSIc
DATABASE_URL: postgres://postgres:postgres@tenant_db:5432/postgres
DATABASE_POOL_URL: postgresql://postgres:postgres@pg_bouncer:6432/postgres
# Migrations
Expand Down
16 changes: 1 addition & 15 deletions src/auth/jwt.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { getJwtSecret as getJwtSecretForTenant } from '../database/tenant'
import jwt from 'jsonwebtoken'
import { getConfig } from '../config'

const { isMultitenant, jwtSecret, jwtAlgorithm } = getConfig()
const { jwtAlgorithm } = getConfig()

interface jwtInterface {
sub?: string
Expand All @@ -21,19 +20,6 @@ export type SignedUploadToken = {
exp: number
}

/**
* Gets the JWT secret key from the env PGRST_JWT_SECRET when running in single-tenant
* or querying the multi-tenant database by the given tenantId
* @param tenantId
*/
export async function getJwtSecret(tenantId: string): Promise<string> {
let secret = jwtSecret
if (isMultitenant) {
secret = await getJwtSecretForTenant(tenantId)
}
return secret
}

/**
* Verifies if a JWT is valid
* @param token
Expand Down
13 changes: 10 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import dotenv from 'dotenv'
import jwt from 'jsonwebtoken'

export type StorageBackendType = 'file' | 's3'

Expand All @@ -8,7 +9,6 @@ type StorageConfigType = {
headersTimeout: number
adminApiKeys: string
adminRequestIdHeader?: string
anonKey: string
encryptionKey: string
uploadFileSizeLimit: number
uploadFileSizeLimitStandard?: number
Expand Down Expand Up @@ -162,8 +162,8 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType {
),

// Auth
anonKey: getOptionalIfMultitenantConfigFromEnv('ANON_KEY') || '',
serviceKey: getOptionalIfMultitenantConfigFromEnv('SERVICE_KEY') || '',
serviceKey: getOptionalConfigFromEnv('SERVICE_KEY') || '',

encryptionKey: getOptionalConfigFromEnv('AUTH_ENCRYPTION_KEY', 'ENCRYPTION_KEY') || '',
jwtSecret: getOptionalIfMultitenantConfigFromEnv('AUTH_JWT_SECRET', 'PGRST_JWT_SECRET') || '',
jwtAlgorithm: getOptionalConfigFromEnv('AUTH_JWT_ALGORITHM', 'PGRST_JWT_ALGORITHM') || 'HS256',
Expand Down Expand Up @@ -330,5 +330,12 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType {
),
}

if (!config.isMultitenant && !config.serviceKey) {
config.serviceKey = jwt.sign({ role: config.dbServiceRole }, config.jwtSecret, {
expiresIn: '10y',
algorithm: config.jwtAlgorithm as jwt.Algorithm,
})
}

return config
}
63 changes: 28 additions & 35 deletions src/database/tenant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { JwtPayload } from 'jsonwebtoken'
import { PubSubAdapter } from '../pubsub'

interface TenantConfig {
anonKey: string
anonKey?: string
databaseUrl: string
databasePoolUrl?: string
maxConnections?: number
Expand All @@ -26,11 +26,23 @@ export interface Features {
}
}

const { isMultitenant, serviceKey, jwtSecret } = getConfig()
const { isMultitenant, dbServiceRole, serviceKey, jwtSecret } = getConfig()

const tenantConfigCache = new Map<string, TenantConfig>()

let singleTenantServiceKeyPayload: ({ role: string } & JwtPayload) | undefined = undefined
const singleTenantServiceKey:
| {
jwt: string
payload: { role: string } & JwtPayload
}
| undefined = !isMultitenant
? {
jwt: serviceKey,
payload: {
role: dbServiceRole,
},
}
: undefined

/**
* Runs migrations in a specific tenant
Expand Down Expand Up @@ -116,44 +128,22 @@ export async function getTenantConfig(tenantId: string): Promise<TenantConfig> {
return config
}

/**
* Get the anon key from the tenant config
* @param tenantId
*/
export async function getAnonKey(tenantId: string): Promise<string> {
const { anonKey } = await getTenantConfig(tenantId)
return anonKey
}

export async function getServiceKeyUser(tenantId: string) {
let serviceKeyPayload: { role?: string } | undefined
let tenantJwtSecret = jwtSecret
let tenantServiceKey = serviceKey

if (isMultitenant) {
const tenant = await getTenantConfig(tenantId)
serviceKeyPayload = tenant.serviceKeyPayload
tenantJwtSecret = tenant.jwtSecret
tenantServiceKey = tenant.serviceKey
} else {
serviceKeyPayload = await getSingleTenantServiceKeyPayload()
}

return {
jwt: tenantServiceKey,
payload: serviceKeyPayload,
jwtSecret: tenantJwtSecret,
return {
jwt: tenant.serviceKey,
payload: tenant.serviceKeyPayload,
jwtSecret: tenant.jwtSecret,
}
}
}

export async function getSingleTenantServiceKeyPayload() {
if (singleTenantServiceKeyPayload) {
return singleTenantServiceKeyPayload
return {
jwt: singleTenantServiceKey!.jwt,

Check warning on line 143 in src/database/tenant.ts

View workflow job for this annotation

GitHub Actions / Test / OS ubuntu-20.04 / Node 20

Forbidden non-null assertion
payload: singleTenantServiceKey!.payload,

Check warning on line 144 in src/database/tenant.ts

View workflow job for this annotation

GitHub Actions / Test / OS ubuntu-20.04 / Node 20

Forbidden non-null assertion
jwtSecret: jwtSecret,
}

singleTenantServiceKeyPayload = await verifyJWT(serviceKey, jwtSecret)

return singleTenantServiceKeyPayload
}

/**
Expand All @@ -170,7 +160,10 @@ export async function getServiceKey(tenantId: string): Promise<string> {
* @param tenantId
*/
export async function getJwtSecret(tenantId: string): Promise<string> {
const { jwtSecret } = await getTenantConfig(tenantId)
if (isMultitenant) {
const { jwtSecret } = await getTenantConfig(tenantId)
return jwtSecret
}
return jwtSecret
}

Expand Down
3 changes: 2 additions & 1 deletion src/http/plugins/jwt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fastifyPlugin from 'fastify-plugin'
import { createResponse } from '../generic-routes'
import { getJwtSecret, getOwner } from '../../auth'
import { getOwner } from '../../auth'
import { getJwtSecret } from '../../database/tenant'

declare module 'fastify' {
interface FastifyRequest {
Expand Down
3 changes: 2 additions & 1 deletion src/http/routes/object/getSignedObject.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { FastifyInstance } from 'fastify'
import { FromSchema } from 'json-schema-to-ts'
import { getConfig } from '../../../config'
import { getJwtSecret, SignedToken, verifyJWT } from '../../../auth'
import { SignedToken, verifyJWT } from '../../../auth'
import { StorageBackendError } from '../../../storage'
import { getJwtSecret } from '../../../database/tenant'

const { storageS3Bucket } = getConfig()

Expand Down
3 changes: 2 additions & 1 deletion src/http/routes/object/uploadSignedObject.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { FastifyInstance } from 'fastify'
import { FromSchema } from 'json-schema-to-ts'
import { getJwtSecret, SignedUploadToken, verifyJWT } from '../../../auth'
import { SignedUploadToken, verifyJWT } from '../../../auth'
import { StorageBackendError } from '../../../storage'
import { getJwtSecret } from '../../../database/tenant'

const uploadSignedObjectParamsSchema = {
type: 'object',
Expand Down
5 changes: 3 additions & 2 deletions src/http/routes/render/renderSignedImage.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { getConfig } from '../../../config'
import { FromSchema } from 'json-schema-to-ts'
import { FastifyInstance } from 'fastify'
import { getConfig } from '../../../config'
import { ImageRenderer } from '../../../storage/renderer'
import { getJwtSecret, SignedToken, verifyJWT } from '../../../auth'
import { SignedToken, verifyJWT } from '../../../auth'
import { StorageBackendError } from '../../../storage'
import { getJwtSecret } from '../../../database/tenant'

const { storageS3Bucket } = getConfig()

Expand Down
3 changes: 2 additions & 1 deletion src/storage/object.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { StorageBackendAdapter, ObjectMetadata, withOptionalVersion } from './backend'
import { Database, FindObjectFilters, SearchObjectOption } from './database'
import { mustBeValidKey } from './limits'
import { getJwtSecret, signJWT } from '../auth'
import { signJWT } from '../auth'
import { getConfig } from '../config'
import { FastifyRequest } from 'fastify'
import { Uploader } from './uploader'
Expand All @@ -15,6 +15,7 @@ import {
} from '../queue'
import { randomUUID } from 'crypto'
import { StorageBackendError } from './errors'
import { getJwtSecret } from '../database/tenant'

export interface UploadObjectOptions {
objectName: string
Expand Down
3 changes: 1 addition & 2 deletions src/test/bucket.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
'use strict'
import dotenv from 'dotenv'
import app from '../app'
import { getConfig } from '../config'
import { S3Backend } from '../storage/backend'

dotenv.config({ path: '.env.test' })
const { anonKey } = getConfig()
const anonKey = process.env.ANON_KEY || ''

beforeAll(() => {
jest.spyOn(S3Backend.prototype, 'deleteObjects').mockImplementation(() => {
Expand Down
3 changes: 2 additions & 1 deletion src/test/object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import { Knex } from 'knex'

dotenv.config({ path: '.env.test' })

const { anonKey, jwtSecret, serviceKey, tenantId } = getConfig()
const { jwtSecret, serviceKey, tenantId } = getConfig()
const anonKey = process.env.ANON_KEY || ''

let tnx: Knex.Transaction | undefined
async function getSuperuserPostgrestClient() {
Expand Down

0 comments on commit f19c744

Please sign in to comment.