11import { sealAccountTokens } from '@sim/db/script-migrations/0037_seal_account_tokens'
22import { openText } from '@sim/db/sealed-text'
33import { readTestDatabaseUrl } from '@sim/db/testing/test-infrastructure'
4+ import { seal , sealingKeyId } from '@sim/security/encryption'
45import { generateId } from '@sim/utils/id'
56import postgres , { type Sql } from 'postgres'
67import { afterAll , beforeAll , beforeEach , describe , expect , it } from 'vitest'
78
89const databaseUrl = readTestDatabaseUrl ( )
10+ const KEY_HEX = 'a' . repeat ( 64 )
11+ const KEY_ID = sealingKeyId ( Buffer . from ( KEY_HEX , 'hex' ) )
12+ const ACCESS_CONTEXT = 'account.access_token'
13+ const REFRESH_CONTEXT = 'account.refresh_token'
914
1015interface StoredRow {
1116 id : string
@@ -22,7 +27,7 @@ describe('sealing stored account tokens in PostgreSQL', () => {
2227 const previousKey = process . env . ENCRYPTION_KEY
2328
2429 beforeAll ( async ( ) => {
25- process . env . ENCRYPTION_KEY = 'a' . repeat ( 64 )
30+ process . env . ENCRYPTION_KEY = KEY_HEX
2631 admin = postgres ( databaseUrl , { max : 1 , onnotice : ( ) => undefined } )
2732 await admin . unsafe ( `CREATE SCHEMA "${ schemaName } "` )
2833 await admin . unsafe (
@@ -69,37 +74,55 @@ describe('sealing stored account tokens in PostgreSQL', () => {
6974 const accessOnly = await insertAccount ( 'access-only' , null )
7075 const before = await storedRows ( )
7176
72- expect ( await sealAccountTokens ( sql , { mode : 'seal' , batchSize : 2 , pauseMs : 0 } ) ) . toEqual ( {
77+ expect (
78+ await sealAccountTokens ( sql , {
79+ mode : 'seal' ,
80+ expectedKeyId : KEY_ID ,
81+ batchSize : 2 ,
82+ pauseMs : 0 ,
83+ } )
84+ ) . toEqual ( {
85+ keyId : KEY_ID ,
7386 scanned : 6 ,
7487 pending : 6 ,
7588 updated : 6 ,
89+ foreignKey : 0 ,
7690 } )
7791
7892 const after = await storedRows ( )
7993 for ( const row of after ) {
8094 const original = before . find ( ( candidate ) => candidate . id === row . id )
8195 expect ( row . updated_at ) . toBe ( original ?. updated_at )
8296 expect ( row . access_token ) . toMatch ( / ^ e n c : v 1 : / )
83- expect ( openText ( row . access_token ?? '' ) ) . toBe ( original ?. access_token )
97+ expect ( openText ( row . access_token ?? '' , ACCESS_CONTEXT ) ) . toBe ( original ?. access_token )
8498 if ( row . id === accessOnly ) {
8599 expect ( row . refresh_token ) . toBeNull ( )
86100 } else {
87101 expect ( row . refresh_token ) . toMatch ( / ^ e n c : v 1 : / )
88- expect ( openText ( row . refresh_token ?? '' ) ) . toBe ( original ?. refresh_token )
102+ expect ( openText ( row . refresh_token ?? '' , REFRESH_CONTEXT ) ) . toBe ( original ?. refresh_token )
89103 }
90104 }
91105 } )
92106
93107 it ( 'finds nothing left to do on a rerun or a dry run after a completed pass' , async ( ) => {
94108 await insertAccount ( 'access' , 'refresh' )
95- await sealAccountTokens ( sql , { mode : 'seal' , pauseMs : 0 } )
109+ await sealAccountTokens ( sql , { mode : 'seal' , expectedKeyId : KEY_ID , pauseMs : 0 } )
96110 const sealed = await storedRows ( )
97111
98- expect ( await sealAccountTokens ( sql , { mode : 'seal' , pauseMs : 0 } ) ) . toMatchObject ( {
112+ expect (
113+ await sealAccountTokens ( sql , { mode : 'seal' , expectedKeyId : KEY_ID , pauseMs : 0 } )
114+ ) . toMatchObject ( {
99115 pending : 0 ,
100116 updated : 0 ,
101117 } )
102- expect ( await sealAccountTokens ( sql , { mode : 'seal' , dryRun : true , pauseMs : 0 } ) ) . toMatchObject ( {
118+ expect (
119+ await sealAccountTokens ( sql , {
120+ mode : 'seal' ,
121+ expectedKeyId : KEY_ID ,
122+ dryRun : true ,
123+ pauseMs : 0 ,
124+ } )
125+ ) . toMatchObject ( {
103126 pending : 0 ,
104127 } )
105128 expect ( await storedRows ( ) ) . toEqual ( sealed )
@@ -109,10 +132,19 @@ describe('sealing stored account tokens in PostgreSQL', () => {
109132 await insertAccount ( 'access' , 'refresh' )
110133 const before = await storedRows ( )
111134
112- expect ( await sealAccountTokens ( sql , { mode : 'seal' , dryRun : true , pauseMs : 0 } ) ) . toEqual ( {
135+ expect (
136+ await sealAccountTokens ( sql , {
137+ mode : 'seal' ,
138+ expectedKeyId : KEY_ID ,
139+ dryRun : true ,
140+ pauseMs : 0 ,
141+ } )
142+ ) . toEqual ( {
143+ keyId : KEY_ID ,
113144 scanned : 1 ,
114145 pending : 1 ,
115146 updated : 0 ,
147+ foreignKey : 0 ,
116148 } )
117149 expect ( await storedRows ( ) ) . toEqual ( before )
118150 } )
@@ -121,11 +153,47 @@ describe('sealing stored account tokens in PostgreSQL', () => {
121153 await insertAccount ( 'ya29.access:with:colons' , 'refresh' )
122154 await insertAccount ( null , 'refresh-only' )
123155 const plaintext = await storedRows ( )
124- await sealAccountTokens ( sql , { mode : 'seal' , pauseMs : 0 } )
156+ await sealAccountTokens ( sql , { mode : 'seal' , expectedKeyId : KEY_ID , pauseMs : 0 } )
125157
126- expect ( await sealAccountTokens ( sql , { mode : 'unseal' , pauseMs : 0 } ) ) . toMatchObject ( {
158+ expect (
159+ await sealAccountTokens ( sql , { mode : 'unseal' , expectedKeyId : KEY_ID , pauseMs : 0 } )
160+ ) . toMatchObject ( {
127161 updated : 2 ,
128162 } )
129163 expect ( await storedRows ( ) ) . toEqual ( plaintext )
130164 } )
165+
166+ it ( 'refuses to start under a key the fleet does not use, before touching any row' , async ( ) => {
167+ await insertAccount ( 'access' , 'refresh' )
168+ const before = await storedRows ( )
169+
170+ for ( const dryRun of [ false , true ] ) {
171+ await expect (
172+ sealAccountTokens ( sql , { mode : 'seal' , expectedKeyId : 'ffffffff' , dryRun, pauseMs : 0 } )
173+ ) . rejects . toThrow ( KEY_ID )
174+ }
175+ await expect ( sealAccountTokens ( sql , { mode : 'seal' , pauseMs : 0 } ) ) . rejects . toThrow ( )
176+ expect ( await storedRows ( ) ) . toEqual ( before )
177+ } )
178+
179+ it ( 'skips and reports rows sealed under another key instead of failing the pass' , async ( ) => {
180+ const otherKey = Buffer . from ( 'b' . repeat ( 64 ) , 'hex' )
181+ const foreignAccess = seal ( 'foreign-access' , otherKey , ACCESS_CONTEXT )
182+ const foreign = await insertAccount ( foreignAccess , 'foreign-refresh' )
183+ await insertAccount ( 'own-access' , 'own-refresh' )
184+ await sealAccountTokens ( sql , { mode : 'seal' , expectedKeyId : KEY_ID , pauseMs : 0 } )
185+
186+ expect (
187+ await sealAccountTokens ( sql , { mode : 'unseal' , expectedKeyId : KEY_ID , pauseMs : 0 } )
188+ ) . toMatchObject ( { scanned : 2 , updated : 1 , foreignKey : 1 } )
189+ const rows = await storedRows ( )
190+ expect ( rows . find ( ( row ) => row . id === foreign ) ) . toMatchObject ( {
191+ access_token : foreignAccess ,
192+ refresh_token : 'foreign-refresh' ,
193+ } )
194+ expect ( rows . find ( ( row ) => row . id !== foreign ) ) . toMatchObject ( {
195+ access_token : 'own-access' ,
196+ refresh_token : 'own-refresh' ,
197+ } )
198+ } )
131199} )
0 commit comments