1
- import crypto from 'node:crypto'
2
1
import { getConfig , JwksConfig , JwksConfigKey , JwksConfigKeyOCT } from '../../config'
3
- import { decrypt , encrypt , verifyJWT } from '../auth'
2
+ import { decrypt , verifyJWT } from '../auth'
4
3
import { multitenantKnex } from './multitenant-db'
5
4
import { JwtPayload } from 'jsonwebtoken'
6
5
import { PubSubAdapter } from '../pubsub'
7
6
import { createMutexByKey } from '../concurrency'
8
- import { LRUCache } from 'lru-cache'
9
- import objectSizeOf from 'object-sizeof'
10
7
import { ERRORS } from '@internal/errors'
11
8
import { DBMigration } from '@internal/database/migrations'
12
9
import { JWKSManager } from './jwks-manager'
13
10
import { JWKSManagerStoreKnex } from './jwks-manager/store-knex'
11
+ import { S3CredentialsManagerStoreKnex } from '../../storage/protocols/s3/credentials-manager/store-knex'
12
+ import { S3CredentialsManager } from '../../storage/protocols/s3/credentials-manager'
14
13
15
14
interface TenantConfig {
16
15
anonKey ?: string
@@ -51,28 +50,16 @@ export enum TenantMigrationStatus {
51
50
FAILED_STALE = 'FAILED_STALE' ,
52
51
}
53
52
54
- interface S3Credentials {
55
- accessKey : string
56
- secretKey : string
57
- claims : { role : string ; sub ?: string ; [ key : string ] : unknown }
58
- }
59
-
60
53
const { isMultitenant, dbServiceRole, serviceKey, jwtSecret } = getConfig ( )
61
54
62
55
const tenantConfigCache = new Map < string , TenantConfig > ( )
63
56
64
- const tenantS3CredentialsCache = new LRUCache < string , S3Credentials > ( {
65
- maxSize : 1024 * 1024 * 50 , // 50MB
66
- ttl : 1000 * 60 * 60 , // 1 hour
67
- sizeCalculation : ( value ) => objectSizeOf ( value ) ,
68
- updateAgeOnGet : true ,
69
- allowStale : false ,
70
- } )
71
-
72
- const tenantMutex = createMutexByKey ( )
73
- const s3CredentialsMutex = createMutexByKey ( )
57
+ const tenantMutex = createMutexByKey < TenantConfig > ( )
74
58
75
59
export const jwksManager = new JWKSManager ( new JWKSManagerStoreKnex ( multitenantKnex ) )
60
+ export const s3CredentialsManager = new S3CredentialsManager (
61
+ new S3CredentialsManagerStoreKnex ( multitenantKnex )
62
+ )
76
63
77
64
const singleTenantServiceKey :
78
65
| {
@@ -107,15 +94,15 @@ export async function getTenantConfig(tenantId: string): Promise<TenantConfig> {
107
94
}
108
95
109
96
if ( tenantConfigCache . has ( tenantId ) ) {
110
- return tenantConfigCache . get ( tenantId ) as TenantConfig
97
+ return tenantConfigCache . get ( tenantId ) !
111
98
}
112
99
113
100
return tenantMutex ( tenantId , async ( ) => {
114
101
if ( tenantConfigCache . has ( tenantId ) ) {
115
- return tenantConfigCache . get ( tenantId ) as TenantConfig
102
+ return tenantConfigCache . get ( tenantId ) !
116
103
}
117
104
118
- const tenant = await multitenantKnex ( 'tenants' ) . first ( ) . where ( 'id' , tenantId )
105
+ const tenant = await multitenantKnex . table ( 'tenants' ) . first ( ) . where ( 'id' , tenantId )
119
106
if ( ! tenant ) {
120
107
throw ERRORS . MissingTenantConfig ( tenantId )
121
108
}
@@ -173,7 +160,7 @@ export async function getTenantConfig(tenantId: string): Promise<TenantConfig> {
173
160
}
174
161
tenantConfigCache . set ( tenantId , config )
175
162
176
- return tenantConfigCache . get ( tenantId )
163
+ return tenantConfigCache . get ( tenantId ) !
177
164
} )
178
165
}
179
166
@@ -249,7 +236,6 @@ export async function getFeatures(tenantId: string): Promise<Features> {
249
236
}
250
237
251
238
const TENANTS_UPDATE_CHANNEL = 'tenants_update'
252
- const TENANTS_S3_CREDENTIALS_UPDATE_CHANNEL = 'tenants_s3_credentials_update'
253
239
254
240
/**
255
241
* Keeps the in memory config cache up to date
@@ -258,130 +244,6 @@ export async function listenForTenantUpdate(pubSub: PubSubAdapter): Promise<void
258
244
await pubSub . subscribe ( TENANTS_UPDATE_CHANNEL , ( cacheKey ) => {
259
245
tenantConfigCache . delete ( cacheKey )
260
246
} )
261
- await pubSub . subscribe ( TENANTS_S3_CREDENTIALS_UPDATE_CHANNEL , ( cacheKey ) => {
262
- tenantS3CredentialsCache . delete ( cacheKey )
263
- } )
264
-
247
+ await s3CredentialsManager . listenForTenantUpdate ( pubSub )
265
248
await jwksManager . listenForTenantUpdate ( pubSub )
266
249
}
267
-
268
- /**
269
- * Create S3 Credential for a tenant
270
- * @param tenantId
271
- * @param data
272
- */
273
- export async function createS3Credentials (
274
- tenantId : string ,
275
- data : { description : string ; claims ?: S3Credentials [ 'claims' ] }
276
- ) {
277
- const existingCount = await countS3Credentials ( tenantId )
278
-
279
- if ( existingCount >= 50 ) {
280
- throw ERRORS . MaximumCredentialsLimit ( )
281
- }
282
-
283
- const secretAccessKeyId = crypto . randomBytes ( 32 ) . toString ( 'hex' ) . slice ( 0 , 32 )
284
- const secretAccessKey = crypto . randomBytes ( 64 ) . toString ( 'hex' ) . slice ( 0 , 64 )
285
-
286
- if ( data . claims ) {
287
- delete data . claims . iss
288
- delete data . claims . issuer
289
- delete data . claims . exp
290
- delete data . claims . iat
291
- }
292
-
293
- data . claims = {
294
- ...( data . claims || { } ) ,
295
- role : data . claims ?. role ?? dbServiceRole ,
296
- issuer : `supabase.storage.${ tenantId } ` ,
297
- sub : data . claims ?. sub ,
298
- }
299
-
300
- const credentials = await multitenantKnex
301
- . table ( 'tenants_s3_credentials' )
302
- . insert ( {
303
- tenant_id : tenantId ,
304
- description : data . description ,
305
- access_key : secretAccessKeyId ,
306
- secret_key : encrypt ( secretAccessKey ) ,
307
- claims : JSON . stringify ( data . claims ) ,
308
- } )
309
- . returning ( 'id' )
310
-
311
- return {
312
- id : credentials [ 0 ] . id ,
313
- access_key : secretAccessKeyId ,
314
- secret_key : secretAccessKey ,
315
- }
316
- }
317
-
318
- export async function getS3CredentialsByAccessKey (
319
- tenantId : string ,
320
- accessKey : string
321
- ) : Promise < S3Credentials > {
322
- const cacheKey = `${ tenantId } :${ accessKey } `
323
- const cachedCredentials = tenantS3CredentialsCache . get ( cacheKey )
324
-
325
- if ( cachedCredentials ) {
326
- return cachedCredentials
327
- }
328
-
329
- return s3CredentialsMutex ( cacheKey , async ( ) => {
330
- const cachedCredentials = tenantS3CredentialsCache . get ( cacheKey )
331
-
332
- if ( cachedCredentials ) {
333
- return cachedCredentials
334
- }
335
-
336
- const data = await multitenantKnex
337
- . table ( 'tenants_s3_credentials' )
338
- . select ( 'access_key' , 'secret_key' , 'claims' )
339
- . where ( 'tenant_id' , tenantId )
340
- . where ( 'access_key' , accessKey )
341
- . first ( )
342
-
343
- if ( ! data ) {
344
- throw ERRORS . MissingS3Credentials ( )
345
- }
346
-
347
- const secretKey = decrypt ( data . secret_key )
348
-
349
- tenantS3CredentialsCache . set ( cacheKey , {
350
- accessKey : data . access_key ,
351
- secretKey : secretKey ,
352
- claims : data . claims ,
353
- } )
354
-
355
- return {
356
- accessKey : data . access_key ,
357
- secretKey : secretKey ,
358
- claims : data . claims ,
359
- }
360
- } )
361
- }
362
-
363
- export function deleteS3Credential ( tenantId : string , credentialId : string ) {
364
- return multitenantKnex
365
- . table ( 'tenants_s3_credentials' )
366
- . where ( 'tenant_id' , tenantId )
367
- . where ( 'id' , credentialId )
368
- . delete ( )
369
- . returning ( 'id' )
370
- }
371
-
372
- export function listS3Credentials ( tenantId : string ) {
373
- return multitenantKnex
374
- . table ( 'tenants_s3_credentials' )
375
- . select ( 'id' , 'description' , 'access_key' , 'created_at' )
376
- . where ( 'tenant_id' , tenantId )
377
- . orderBy ( 'created_at' , 'asc' )
378
- }
379
-
380
- export async function countS3Credentials ( tenantId : string ) {
381
- const data = await multitenantKnex
382
- . table ( 'tenants_s3_credentials' )
383
- . count < { count : number } > ( 'id' )
384
- . where ( 'tenant_id' , tenantId )
385
-
386
- return Number ( data ?. count || 0 )
387
- }
0 commit comments