Skip to content

Commit bf2fc00

Browse files
authored
Merge pull request #6139 from Shopify/handle_access_denied_file_upload
Handle access denied file upload error
2 parents 997d4bb + f2fed6f commit bf2fc00

File tree

4 files changed

+49
-1
lines changed

4 files changed

+49
-1
lines changed

packages/store/src/services/store/errors/errors.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,15 @@ describe('Unauthorized Error Codes', () => {
225225
"You are not authorized to copy data between these stores\n\nTo export data from \"source.myshopify.com\"\n• You'll need the 'bulk data > export' permission\n\nTo import data to \"target.myshopify.com\"\n• You'll need the 'bulk data > import' permission",
226226
)
227227
})
228+
229+
test('should create STAGED_UPLOAD_ACCESS_DENIED error', () => {
230+
const error = new OperationError('upload', ErrorCodes.STAGED_UPLOAD_ACCESS_DENIED)
231+
232+
expect(error).toBeInstanceOf(OperationError)
233+
expect(error.operation).toBe('upload')
234+
expect(error.code).toBe(ErrorCodes.STAGED_UPLOAD_ACCESS_DENIED)
235+
expect(error.message).toBe(
236+
"You don't have permission to upload files to this store.\n\nYou'll need the 'bulk data > import' permission to upload files.",
237+
)
238+
})
228239
})

packages/store/src/services/store/errors/errors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const ErrorCodes = {
3030
UNAUTHORIZED_IMPORT: 'UNAUTHORIZED_IMPORT',
3131
UNAUTHORIZED_COPY: 'UNAUTHORIZED_COPY',
3232
MISSING_EA_ACCESS: 'MISSING_EA_ACCESS',
33+
STAGED_UPLOAD_ACCESS_DENIED: 'STAGED_UPLOAD_ACCESS_DENIED',
3334
} as const
3435

3536
interface ErrorParams {
@@ -132,6 +133,8 @@ function generateErrorMessage(code: string, params?: ErrorParams, requestId?: st
132133
)
133134
case ErrorCodes.MISSING_EA_ACCESS:
134135
return `This command is in Early Access and is not yet available for the requested store(s).`
136+
case ErrorCodes.STAGED_UPLOAD_ACCESS_DENIED:
137+
return `You don't have permission to upload files to this store.\n\nYou'll need the 'bulk data > import' permission to upload files.`
135138

136139
default:
137140
return 'An error occurred'

packages/store/src/services/store/utils/file-uploader.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,5 +177,26 @@ describe('FileUploader', () => {
177177
params: {details: '403 Forbidden. Access denied'},
178178
})
179179
})
180+
181+
test('should throw staged upload access denied error when GraphQL returns ACCESS_DENIED', async () => {
182+
const accessDeniedError = {
183+
errors: [
184+
{
185+
message: 'Access denied for stagedUploadsCreate field.',
186+
extensions: {
187+
code: 'ACCESS_DENIED',
188+
},
189+
},
190+
],
191+
}
192+
vi.mocked(createStagedUploadAdmin).mockRejectedValue(accessDeniedError)
193+
194+
const promise = fileUploader.uploadSqliteFile(mockFilePath, mockStoreFqdn)
195+
await expect(promise).rejects.toThrow(OperationError)
196+
await expect(promise).rejects.toMatchObject({
197+
operation: 'upload',
198+
code: ErrorCodes.STAGED_UPLOAD_ACCESS_DENIED,
199+
})
200+
})
180201
})
181202
})

packages/store/src/services/store/utils/file-uploader.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,15 @@ export class FileUploader {
2121
fileSize: sizeOfFile.toString(),
2222
}
2323

24-
const stagedUploadResponse = await createStagedUploadAdmin(storeFqdn, [uploadInput], 'unstable')
24+
let stagedUploadResponse
25+
try {
26+
stagedUploadResponse = await createStagedUploadAdmin(storeFqdn, [uploadInput], 'unstable')
27+
} catch (error) {
28+
if (this.isAccessDeniedError(error)) {
29+
throw new OperationError('upload', ErrorCodes.STAGED_UPLOAD_ACCESS_DENIED)
30+
}
31+
throw error
32+
}
2533

2634
if (!stagedUploadResponse.stagedUploadsCreate?.stagedTargets?.length) {
2735
throw new OperationError('upload', ErrorCodes.STAGED_UPLOAD_FAILED, {
@@ -76,6 +84,11 @@ export class FileUploader {
7684
return finalResourceUrl
7785
}
7886

87+
private isAccessDeniedError(error: unknown): boolean {
88+
const errors = (error as {errors?: {extensions?: {code?: string}}[]})?.errors
89+
return errors?.some((err) => err.extensions?.code === 'ACCESS_DENIED') ?? false
90+
}
91+
7992
private async validateSqliteFile(filePath: string): Promise<void> {
8093
try {
8194
if (!fileExistsSync(filePath)) {

0 commit comments

Comments
 (0)