2020// --map jina=jina --user user_abc123 --user user_def456
2121
2222import { createCipheriv , createDecipheriv , randomBytes } from 'crypto'
23- import { readFileSync , writeFileSync } from 'fs'
23+ import { appendFileSync , readFileSync , writeFileSync } from 'fs'
2424import { resolve } from 'path'
2525import { eq , sql } from 'drizzle-orm'
2626import { index , json , jsonb , pgTable , text , timestamp , uniqueIndex } from 'drizzle-orm/pg-core'
@@ -155,6 +155,11 @@ async function decryptSecret(encryptedValue: string): Promise<string> {
155155}
156156
157157// ---------- Schema ----------
158+ const workspaceTable = pgTable ( 'workspace' , {
159+ id : text ( 'id' ) . primaryKey ( ) ,
160+ ownerId : text ( 'owner_id' ) . notNull ( ) ,
161+ } )
162+
158163const workflow = pgTable ( 'workflow' , {
159164 id : text ( 'id' ) . primaryKey ( ) ,
160165 userId : text ( 'user_id' ) . notNull ( ) ,
@@ -272,6 +277,7 @@ function parseToolInputValue(value: unknown): any[] {
272277type RawKeyRef = {
273278 rawValue : string
274279 blockName : string
280+ workflowId : string
275281 workflowName : string
276282 userId : string
277283}
@@ -289,10 +295,15 @@ const KEY_SOURCE_PRIORITY: Record<KeySource, number> = {
289295 personal : 2 ,
290296}
291297
298+ interface ResolveKeyContext {
299+ workspaceId : string
300+ workspaceOwnerId : string | null
301+ }
302+
292303async function resolveKey (
293304 ref : RawKeyRef ,
294- context : string ,
295- env : EnvLookup
305+ env : EnvLookup ,
306+ ctx : ResolveKeyContext
296307) : Promise < { key : string | null ; source : KeySource ; envVarFailed : boolean } > {
297308 if ( ! isEnvVarReference ( ref . rawValue ) ) {
298309 return { key : ref . rawValue , source : 'plaintext' , envVarFailed : false }
@@ -308,16 +319,24 @@ async function resolveKey(
308319 const encryptedValue = wsValue ?? personalValue
309320 const source : KeySource = wsValue ? 'workspace' : 'personal'
310321
322+ const logPrefix =
323+ `workspace=${ ctx . workspaceId } owner=${ ctx . workspaceOwnerId ?? 'unknown' } ` +
324+ ` workflow=${ ref . workflowId } user=${ ref . userId } `
325+
311326 if ( ! encryptedValue ) {
312- console . warn ( ` [WARN] Env var "${ varName } " not found (${ context } )` )
327+ console . warn (
328+ ` [WARN] Env var "${ varName } " not found — ${ logPrefix } "${ ref . blockName } " in "${ ref . workflowName } "`
329+ )
313330 return { key : null , source, envVarFailed : true }
314331 }
315332
316333 try {
317334 const decrypted = await decryptSecret ( encryptedValue )
318335 return { key : decrypted , source, envVarFailed : false }
319336 } catch ( error ) {
320- console . warn ( ` [WARN] Failed to decrypt env var "${ varName } " (${ context } ): ${ error } ` )
337+ console . warn (
338+ ` [WARN] Failed to decrypt env var "${ varName } " — ${ logPrefix } "${ ref . blockName } " in "${ ref . workflowName } ": ${ error } `
339+ )
321340 return { key : null , source, envVarFailed : true }
322341 }
323342}
@@ -379,8 +398,8 @@ async function run() {
379398 console . log ( `Found ${ workspaceIds . length } workspaces with candidate blocks\n` )
380399
381400 const outPath = resolve ( 'migrate-byok-workspace-ids.txt' )
382- writeFileSync ( outPath , ` ${ workspaceIds . join ( '\n' ) } \n` )
383- console . log ( `[DRY RUN] Wrote ${ workspaceIds . length } workspace IDs to ${ outPath } \n` )
401+ writeFileSync ( outPath , '' )
402+ console . log ( `[DRY RUN] Will write workspace IDs with keys to ${ outPath } \n` )
384403 } else {
385404 const raw = readFileSync ( resolve ( FROM_FILE ! ) , 'utf-8' )
386405 workspaceIds = raw
@@ -411,7 +430,16 @@ async function run() {
411430 ) } )${ userFilter } `
412431 )
413432
414- console . log ( `[Workspace ${ workspaceId } ] ${ blocks . length } blocks` )
433+ const wsRows = await db
434+ . select ( { ownerId : workspaceTable . ownerId } )
435+ . from ( workspaceTable )
436+ . where ( eq ( workspaceTable . id , workspaceId ) )
437+ . limit ( 1 )
438+ const workspaceOwnerId = wsRows [ 0 ] ?. ownerId ?? null
439+
440+ console . log (
441+ `[Workspace ${ workspaceId } ] ${ blocks . length } blocks, owner=${ workspaceOwnerId ?? 'unknown' } `
442+ )
415443
416444 // 2a. Extract all raw key references grouped by provider
417445 const providerKeys = new Map < string , RawKeyRef [ ] > ( )
@@ -427,6 +455,7 @@ async function run() {
427455 refs . push ( {
428456 rawValue : val ,
429457 blockName : block . blockName ,
458+ workflowId : block . workflowId ,
430459 workflowName : block . workflowName ,
431460 userId : block . userId ,
432461 } )
@@ -447,6 +476,7 @@ async function run() {
447476 refs . push ( {
448477 rawValue : toolApiKey ,
449478 blockName : `${ block . blockName } > tool "${ tool . title || toolType } "` ,
479+ workflowId : block . workflowId ,
450480 workflowName : block . workflowName ,
451481 userId : block . userId ,
452482 } )
@@ -461,6 +491,10 @@ async function run() {
461491 continue
462492 }
463493
494+ if ( DRY_RUN ) {
495+ appendFileSync ( resolve ( 'migrate-byok-workspace-ids.txt' ) , `${ workspaceId } \n` )
496+ }
497+
464498 // 2b. Load env vars only if this workspace has env var references
465499 const needsEnvVars = [ ...providerKeys . values ( ) ]
466500 . flat ( )
@@ -504,11 +538,21 @@ async function run() {
504538 for ( const [ providerId , refs ] of providerKeys ) {
505539 // Resolve all keys for this provider to check for conflicts
506540 const resolved : { ref : RawKeyRef ; key : string ; source : KeySource } [ ] = [ ]
541+ const resolveCtx : ResolveKeyContext = { workspaceId, workspaceOwnerId }
507542 for ( const ref of refs ) {
508- const context = `"${ ref . blockName } " in "${ ref . workflowName } "`
509- const { key, source, envVarFailed } = await resolveKey ( ref , context , envLookup )
543+ const { key, source, envVarFailed } = await resolveKey ( ref , envLookup , resolveCtx )
510544 if ( envVarFailed ) stats . envVarFailures ++
511- if ( key ?. trim ( ) ) resolved . push ( { ref, key : key . trim ( ) , source } )
545+ if ( ! key ?. trim ( ) ) continue
546+
547+ // For personal env vars, only use the workspace owner's — never another user's
548+ if ( source === 'personal' && ref . userId !== workspaceOwnerId ) {
549+ console . log (
550+ ` [SKIP-PERSONAL] Ignoring non-owner personal key from user=${ ref . userId } workflow=${ ref . workflowId } "${ ref . blockName } " in "${ ref . workflowName } "`
551+ )
552+ continue
553+ }
554+
555+ resolved . push ( { ref, key : key . trim ( ) , source } )
512556 }
513557
514558 if ( resolved . length === 0 ) continue
@@ -522,12 +566,18 @@ async function run() {
522566 stats . conflicts ++
523567 console . log ( ` [CONFLICT] provider "${ providerId } ": ${ distinctKeys . size } distinct keys` )
524568 for ( const { ref, key, source } of resolved ) {
569+ const isOwner = ref . userId === workspaceOwnerId ? ' (owner)' : ''
525570 const display = isEnvVarReference ( ref . rawValue )
526571 ? `${ ref . rawValue } -> ${ maskKey ( key ) } `
527572 : maskKey ( ref . rawValue )
528- console . log ( ` [${ source } ] "${ ref . blockName } " in "${ ref . workflowName } ": ${ display } ` )
573+ console . log (
574+ ` [${ source } ] user=${ ref . userId } ${ isOwner } workflow=${ ref . workflowId } "${ ref . blockName } " in "${ ref . workflowName } ": ${ display } `
575+ )
529576 }
530- console . log ( ` Using highest-priority key (${ resolved [ 0 ] . source } )` )
577+ const chosenIsOwner = resolved [ 0 ] . ref . userId === workspaceOwnerId ? ', owner' : ''
578+ console . log (
579+ ` Using highest-priority key (${ resolved [ 0 ] . source } ${ chosenIsOwner } , user=${ resolved [ 0 ] . ref . userId } )`
580+ )
531581 }
532582
533583 // Use the highest-priority resolved key
@@ -586,7 +636,10 @@ async function run() {
586636 console . log ( ` Env var resolution failures: ${ stats . envVarFailures } ` )
587637
588638 if ( DRY_RUN ) {
589- console . log ( '\n[DRY RUN] No changes were made to the database.' )
639+ console . log (
640+ `\n[DRY RUN] Wrote ${ stats . workspacesProcessed } workspace IDs (with keys) to migrate-byok-workspace-ids.txt`
641+ )
642+ console . log ( '[DRY RUN] No changes were made to the database.' )
590643 console . log ( 'Run without --dry-run to apply changes.' )
591644 } else {
592645 console . log ( '\nMigration completed successfully!' )
0 commit comments