Skip to content

Commit f19c744

Browse files
committed
feat: default service-key for single tenant
1 parent 39f7413 commit f19c744

File tree

12 files changed

+55
-71
lines changed

12 files changed

+55
-71
lines changed

.env.sample

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,12 @@ SERVER_REGION=region-of-where-your-service-is-running
1414
#######################################
1515
AUTH_JWT_SECRET=f023d3db-39dc-4ac9-87b2-b2be72e9162b
1616
AUTH_JWT_ALGORITHM=HS256
17-
AUTH_ENCRYPTION_KEY=encryptionkey
1817

1918

2019
#######################################
2120
# Single Tenant
2221
#######################################
2322
TENANT_ID=bjhaohmqunupljrqypxz
24-
ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMzUzMTk4NSwiZXhwIjoxOTI5MTA3OTg1fQ.mqfi__KnQB4v6PkIjkhzfwWrYyF94MEbSC6LnuvVniE
25-
SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjEzNTMxOTg1LCJleHAiOjE5MjkxMDc5ODV9.th84OKK0Iz8QchDyXZRrojmKSEZ-OuitQm_5DvLiSIc
26-
2723

2824
#######################################
2925
# Multi Tenancy
@@ -33,7 +29,8 @@ SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiw
3329
# MULTI_TENANT=true
3430
DATABASE_MULTITENANT_URL=postgresql://postgres:[email protected]:5433/postgres
3531
REQUEST_X_FORWARDED_HOST_REGEXP=
36-
ADMIN_API_KEYS=apikey
32+
SERVER_ADMIN_API_KEYS=apikey
33+
AUTH_ENCRYPTION_KEY=encryptionkey
3734

3835

3936
#######################################

docker-compose.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,7 @@ services:
2121
# Auth
2222
AUTH_JWT_SECRET: f023d3db-39dc-4ac9-87b2-b2be72e9162b
2323
AUTH_JWT_ALGORITHM: HS256
24-
AUTH_ENCRYPTION_KEY: encryptionkey
2524
# Single tenant Mode
26-
TENANT_ID: bjwdssmqcnupljrqypxz
27-
ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMzUzMTk4NSwiZXhwIjoxOTI5MTA3OTg1fQ.mqfi__KnQB4v6PkIjkhzfwWrYyF94MEbSC6LnuvVniE
28-
SERVICE_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjEzNTMxOTg1LCJleHAiOjE5MjkxMDc5ODV9.th84OKK0Iz8QchDyXZRrojmKSEZ-OuitQm_5DvLiSIc
2925
DATABASE_URL: postgres://postgres:postgres@tenant_db:5432/postgres
3026
DATABASE_POOL_URL: postgresql://postgres:postgres@pg_bouncer:6432/postgres
3127
# Migrations

src/auth/jwt.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { getJwtSecret as getJwtSecretForTenant } from '../database/tenant'
21
import jwt from 'jsonwebtoken'
32
import { getConfig } from '../config'
43

5-
const { isMultitenant, jwtSecret, jwtAlgorithm } = getConfig()
4+
const { jwtAlgorithm } = getConfig()
65

76
interface jwtInterface {
87
sub?: string
@@ -21,19 +20,6 @@ export type SignedUploadToken = {
2120
exp: number
2221
}
2322

24-
/**
25-
* Gets the JWT secret key from the env PGRST_JWT_SECRET when running in single-tenant
26-
* or querying the multi-tenant database by the given tenantId
27-
* @param tenantId
28-
*/
29-
export async function getJwtSecret(tenantId: string): Promise<string> {
30-
let secret = jwtSecret
31-
if (isMultitenant) {
32-
secret = await getJwtSecretForTenant(tenantId)
33-
}
34-
return secret
35-
}
36-
3723
/**
3824
* Verifies if a JWT is valid
3925
* @param token

src/config.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import dotenv from 'dotenv'
2+
import jwt from 'jsonwebtoken'
23

34
export type StorageBackendType = 'file' | 's3'
45

@@ -8,7 +9,6 @@ type StorageConfigType = {
89
headersTimeout: number
910
adminApiKeys: string
1011
adminRequestIdHeader?: string
11-
anonKey: string
1212
encryptionKey: string
1313
uploadFileSizeLimit: number
1414
uploadFileSizeLimitStandard?: number
@@ -162,8 +162,8 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType {
162162
),
163163

164164
// Auth
165-
anonKey: getOptionalIfMultitenantConfigFromEnv('ANON_KEY') || '',
166-
serviceKey: getOptionalIfMultitenantConfigFromEnv('SERVICE_KEY') || '',
165+
serviceKey: getOptionalConfigFromEnv('SERVICE_KEY') || '',
166+
167167
encryptionKey: getOptionalConfigFromEnv('AUTH_ENCRYPTION_KEY', 'ENCRYPTION_KEY') || '',
168168
jwtSecret: getOptionalIfMultitenantConfigFromEnv('AUTH_JWT_SECRET', 'PGRST_JWT_SECRET') || '',
169169
jwtAlgorithm: getOptionalConfigFromEnv('AUTH_JWT_ALGORITHM', 'PGRST_JWT_ALGORITHM') || 'HS256',
@@ -330,5 +330,12 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType {
330330
),
331331
}
332332

333+
if (!config.isMultitenant && !config.serviceKey) {
334+
config.serviceKey = jwt.sign({ role: config.dbServiceRole }, config.jwtSecret, {
335+
expiresIn: '10y',
336+
algorithm: config.jwtAlgorithm as jwt.Algorithm,
337+
})
338+
}
339+
333340
return config
334341
}

src/database/tenant.ts

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { JwtPayload } from 'jsonwebtoken'
77
import { PubSubAdapter } from '../pubsub'
88

99
interface TenantConfig {
10-
anonKey: string
10+
anonKey?: string
1111
databaseUrl: string
1212
databasePoolUrl?: string
1313
maxConnections?: number
@@ -26,11 +26,23 @@ export interface Features {
2626
}
2727
}
2828

29-
const { isMultitenant, serviceKey, jwtSecret } = getConfig()
29+
const { isMultitenant, dbServiceRole, serviceKey, jwtSecret } = getConfig()
3030

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

33-
let singleTenantServiceKeyPayload: ({ role: string } & JwtPayload) | undefined = undefined
33+
const singleTenantServiceKey:
34+
| {
35+
jwt: string
36+
payload: { role: string } & JwtPayload
37+
}
38+
| undefined = !isMultitenant
39+
? {
40+
jwt: serviceKey,
41+
payload: {
42+
role: dbServiceRole,
43+
},
44+
}
45+
: undefined
3446

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

119-
/**
120-
* Get the anon key from the tenant config
121-
* @param tenantId
122-
*/
123-
export async function getAnonKey(tenantId: string): Promise<string> {
124-
const { anonKey } = await getTenantConfig(tenantId)
125-
return anonKey
126-
}
127-
128131
export async function getServiceKeyUser(tenantId: string) {
129-
let serviceKeyPayload: { role?: string } | undefined
130-
let tenantJwtSecret = jwtSecret
131-
let tenantServiceKey = serviceKey
132-
133132
if (isMultitenant) {
134133
const tenant = await getTenantConfig(tenantId)
135-
serviceKeyPayload = tenant.serviceKeyPayload
136-
tenantJwtSecret = tenant.jwtSecret
137-
tenantServiceKey = tenant.serviceKey
138-
} else {
139-
serviceKeyPayload = await getSingleTenantServiceKeyPayload()
140-
}
141134

142-
return {
143-
jwt: tenantServiceKey,
144-
payload: serviceKeyPayload,
145-
jwtSecret: tenantJwtSecret,
135+
return {
136+
jwt: tenant.serviceKey,
137+
payload: tenant.serviceKeyPayload,
138+
jwtSecret: tenant.jwtSecret,
139+
}
146140
}
147-
}
148141

149-
export async function getSingleTenantServiceKeyPayload() {
150-
if (singleTenantServiceKeyPayload) {
151-
return singleTenantServiceKeyPayload
142+
return {
143+
jwt: singleTenantServiceKey!.jwt,
144+
payload: singleTenantServiceKey!.payload,
145+
jwtSecret: jwtSecret,
152146
}
153-
154-
singleTenantServiceKeyPayload = await verifyJWT(serviceKey, jwtSecret)
155-
156-
return singleTenantServiceKeyPayload
157147
}
158148

159149
/**
@@ -170,7 +160,10 @@ export async function getServiceKey(tenantId: string): Promise<string> {
170160
* @param tenantId
171161
*/
172162
export async function getJwtSecret(tenantId: string): Promise<string> {
173-
const { jwtSecret } = await getTenantConfig(tenantId)
163+
if (isMultitenant) {
164+
const { jwtSecret } = await getTenantConfig(tenantId)
165+
return jwtSecret
166+
}
174167
return jwtSecret
175168
}
176169

src/http/plugins/jwt.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fastifyPlugin from 'fastify-plugin'
22
import { createResponse } from '../generic-routes'
3-
import { getJwtSecret, getOwner } from '../../auth'
3+
import { getOwner } from '../../auth'
4+
import { getJwtSecret } from '../../database/tenant'
45

56
declare module 'fastify' {
67
interface FastifyRequest {

src/http/routes/object/getSignedObject.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { FastifyInstance } from 'fastify'
22
import { FromSchema } from 'json-schema-to-ts'
33
import { getConfig } from '../../../config'
4-
import { getJwtSecret, SignedToken, verifyJWT } from '../../../auth'
4+
import { SignedToken, verifyJWT } from '../../../auth'
55
import { StorageBackendError } from '../../../storage'
6+
import { getJwtSecret } from '../../../database/tenant'
67

78
const { storageS3Bucket } = getConfig()
89

src/http/routes/object/uploadSignedObject.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { FastifyInstance } from 'fastify'
22
import { FromSchema } from 'json-schema-to-ts'
3-
import { getJwtSecret, SignedUploadToken, verifyJWT } from '../../../auth'
3+
import { SignedUploadToken, verifyJWT } from '../../../auth'
44
import { StorageBackendError } from '../../../storage'
5+
import { getJwtSecret } from '../../../database/tenant'
56

67
const uploadSignedObjectParamsSchema = {
78
type: 'object',

src/http/routes/render/renderSignedImage.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { getConfig } from '../../../config'
21
import { FromSchema } from 'json-schema-to-ts'
32
import { FastifyInstance } from 'fastify'
3+
import { getConfig } from '../../../config'
44
import { ImageRenderer } from '../../../storage/renderer'
5-
import { getJwtSecret, SignedToken, verifyJWT } from '../../../auth'
5+
import { SignedToken, verifyJWT } from '../../../auth'
66
import { StorageBackendError } from '../../../storage'
7+
import { getJwtSecret } from '../../../database/tenant'
78

89
const { storageS3Bucket } = getConfig()
910

src/storage/object.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { StorageBackendAdapter, ObjectMetadata, withOptionalVersion } from './backend'
22
import { Database, FindObjectFilters, SearchObjectOption } from './database'
33
import { mustBeValidKey } from './limits'
4-
import { getJwtSecret, signJWT } from '../auth'
4+
import { signJWT } from '../auth'
55
import { getConfig } from '../config'
66
import { FastifyRequest } from 'fastify'
77
import { Uploader } from './uploader'
@@ -15,6 +15,7 @@ import {
1515
} from '../queue'
1616
import { randomUUID } from 'crypto'
1717
import { StorageBackendError } from './errors'
18+
import { getJwtSecret } from '../database/tenant'
1819

1920
export interface UploadObjectOptions {
2021
objectName: string

src/test/bucket.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
'use strict'
22
import dotenv from 'dotenv'
33
import app from '../app'
4-
import { getConfig } from '../config'
54
import { S3Backend } from '../storage/backend'
65

76
dotenv.config({ path: '.env.test' })
8-
const { anonKey } = getConfig()
7+
const anonKey = process.env.ANON_KEY || ''
98

109
beforeAll(() => {
1110
jest.spyOn(S3Backend.prototype, 'deleteObjects').mockImplementation(() => {

src/test/object.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import { Knex } from 'knex'
1616

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

19-
const { anonKey, jwtSecret, serviceKey, tenantId } = getConfig()
19+
const { jwtSecret, serviceKey, tenantId } = getConfig()
20+
const anonKey = process.env.ANON_KEY || ''
2021

2122
let tnx: Knex.Transaction | undefined
2223
async function getSuperuserPostgrestClient() {

0 commit comments

Comments
 (0)