Skip to content

Commit 6336948

Browse files
committed
fix(data-drains): refresh GCS token per retry, tighten Azure key regex
- gcs: rebuild Authorization header per attempt via buildHeaders so token refresh from google-auth-library kicks in if a 5xx retry crosses the hour-long token lifetime - azure_blob: pin account-key regex to {0,2} trailing '=' (base64 of 64 bytes = exactly 88 chars with up to two '=' pad chars)
1 parent 871b1e4 commit 6336948

2 files changed

Lines changed: 27 additions & 18 deletions

File tree

apps/sim/lib/data-drains/destinations/azure_blob.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ const ACCOUNT_NAME_RE = /^[a-z0-9]{3,24}$/
1919
*/
2020
const CONTAINER_NAME_RE = /^[a-z0-9]([a-z0-9]|-(?!-))+[a-z0-9]$/
2121

22-
/** Azure storage account keys are 64 raw bytes => exactly 88 base64 chars (with padding). */
23-
const ACCOUNT_KEY_RE = /^[A-Za-z0-9+/]+=*$/
22+
/** Azure storage account keys are 64 raw bytes => exactly 88 base64 chars (0-2 trailing `=`). */
23+
const ACCOUNT_KEY_RE = /^[A-Za-z0-9+/]+={0,2}$/
2424

2525
/** Public cloud default; sovereign clouds (Gov/China/legacy DE) are validated via allowlist. */
2626
const DEFAULT_ENDPOINT_SUFFIX = 'blob.core.windows.net'

apps/sim/lib/data-drains/destinations/gcs.ts

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,11 @@ interface RetryRequestInput {
122122
bucket: string
123123
url: string
124124
method: string
125-
headers: Record<string, string>
125+
/**
126+
* Built per attempt so the OAuth access token is refreshed if it expired
127+
* between retries (google-auth-library caches and refreshes on demand).
128+
*/
129+
buildHeaders: () => Promise<Record<string, string>>
126130
body?: BodyInit | Buffer
127131
signal: AbortSignal
128132
/** HTTP statuses to treat as success in addition to 2xx. */
@@ -136,10 +140,11 @@ async function fetchWithRetry(input: RetryRequestInput): Promise<void> {
136140
const perAttempt = AbortSignal.any([input.signal, AbortSignal.timeout(PER_ATTEMPT_TIMEOUT_MS)])
137141
let response: Response
138142
try {
143+
const headers = await input.buildHeaders()
139144
response = await fetch(input.url, {
140145
method: input.method,
141146
body: input.body as BodyInit | undefined,
142-
headers: input.headers,
147+
headers,
143148
signal: perAttempt,
144149
})
145150
} catch (error) {
@@ -200,22 +205,24 @@ async function uploadObject(action: string, input: UploadInput): Promise<void> {
200205
`GCS custom metadata is ${metadataBytes} bytes, exceeds the ${MAX_CUSTOM_METADATA_BYTES}-byte per-object limit`
201206
)
202207
}
203-
const token = await getAccessToken(input.jwt)
204208
const url = `${GCS_HOST}/upload/storage/v1/b/${encodeURIComponent(input.bucket)}/o?uploadType=media&name=${encodeURIComponent(input.objectName)}`
205-
const headers: Record<string, string> = {
206-
Authorization: `Bearer ${token}`,
207-
'Content-Type': input.contentType,
208-
'User-Agent': USER_AGENT,
209-
}
210-
for (const [key, value] of Object.entries(input.metadata)) {
211-
headers[`x-goog-meta-${key}`] = value
212-
}
213209
await fetchWithRetry({
214210
action,
215211
bucket: input.bucket,
216212
url,
217213
method: 'POST',
218-
headers,
214+
buildHeaders: async () => {
215+
const token = await getAccessToken(input.jwt)
216+
const headers: Record<string, string> = {
217+
Authorization: `Bearer ${token}`,
218+
'Content-Type': input.contentType,
219+
'User-Agent': USER_AGENT,
220+
}
221+
for (const [key, value] of Object.entries(input.metadata)) {
222+
headers[`x-goog-meta-${key}`] = value
223+
}
224+
return headers
225+
},
219226
body: input.body,
220227
signal: input.signal,
221228
})
@@ -227,16 +234,18 @@ async function deleteObject(input: {
227234
jwt: JWT
228235
signal: AbortSignal
229236
}): Promise<void> {
230-
const token = await getAccessToken(input.jwt)
231237
const url = `${GCS_HOST}/storage/v1/b/${encodeURIComponent(input.bucket)}/o/${encodeURIComponent(input.objectName)}`
232238
await fetchWithRetry({
233239
action: 'delete-object',
234240
bucket: input.bucket,
235241
url,
236242
method: 'DELETE',
237-
headers: {
238-
Authorization: `Bearer ${token}`,
239-
'User-Agent': USER_AGENT,
243+
buildHeaders: async () => {
244+
const token = await getAccessToken(input.jwt)
245+
return {
246+
Authorization: `Bearer ${token}`,
247+
'User-Agent': USER_AGENT,
248+
}
240249
},
241250
signal: input.signal,
242251
successStatuses: [404],

0 commit comments

Comments
 (0)