diff --git a/CHANGELOG.md b/CHANGELOG.md index 1151c62..b94498b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added +- Bucket restrictions when uploading files + ## [0.7.4] - 2025-06-16 ### Changed diff --git a/manifest.json b/manifest.json index 8bc185f..c4c10ff 100644 --- a/manifest.json +++ b/manifest.json @@ -15,6 +15,13 @@ "path": "/api/vtexid/pub/authenticated/user" } }, + { + "name": "outbound-access", + "attrs": { + "host": "{{account}}.vtexcommercestable.com.br", + "path": "/api/license-manager/pvt/accounts/{{account}}/products/*" + } + }, { "name": "sphinx-is-admin" } diff --git a/node/config/allowList.ts b/node/config/allowList.ts index 1cd8739..058e679 100644 --- a/node/config/allowList.ts +++ b/node/config/allowList.ts @@ -1,5 +1,4 @@ -export const ALLOW_LIST = [ - 'storecomponents', +export const ALLOW_LIST = [ 'adidasuy', 'amakha', 'ambientegourmetb2b', diff --git a/node/config/bucketRestrictions.ts b/node/config/bucketRestrictions.ts new file mode 100644 index 0000000..be16b12 --- /dev/null +++ b/node/config/bucketRestrictions.ts @@ -0,0 +1,11 @@ +export const BUCKET_RESTRICTIONS = [ + + // Only users with permission to access the page that allows logo uploads + // can upload files to the logo bucket + { + bucket: 'logo', + productCode: '13', //UI resources + resourceCode: 'MarketplaceNetwork', //Access the Marketplace Network + errorMessage: 'User does not have access to upload logo files', + } +] \ No newline at end of file diff --git a/node/directives/auth.ts b/node/directives/auth.ts index 792bd6f..a86bbb8 100644 --- a/node/directives/auth.ts +++ b/node/directives/auth.ts @@ -1,37 +1,71 @@ import { defaultFieldResolver, GraphQLField } from 'graphql' import { SchemaDirectiveVisitor } from 'graphql-tools' - +import axios from 'axios' import { ALLOW_LIST } from '../config/allowList' +import { BUCKET_RESTRICTIONS } from '../config/bucketRestrictions' -export const authFromCookie = async (ctx: any, operationName: string) => { - const { - clients: { sphinx, vtexID }, - vtex: { authToken }, - } = ctx +async function getUserEmail(ctx: any): Promise { + const { clients: { vtexID }, vtex: { authToken } } = ctx const vtexIdToken = ctx.cookies.get('VtexIdclientAutCookie') ?? ctx.request.header.vtexidclientautcookie - if (!vtexIdToken) { - return 'User must be logged to access this resource' - } + if (!vtexIdToken) throw new Error('User must be logged to access this resource') - const { user: email } = (await vtexID.getIdUser(vtexIdToken, authToken)) || { - user: '', - } + const { user: email } = (await vtexID.getIdUser(vtexIdToken, authToken)) || { user: '' } + + if (!email) throw new Error('Could not find user specified by token.') + + return email +} + +async function getUserCanAccessResource( + authToken: string, account: string, userEmail: string, productCode: string, resourceCode: string): Promise { + + const url = `http://${account}.vtexcommercestable.com.br/api/license-manager/pvt/accounts/${account}/products/${productCode}/logins/${userEmail}/resources/${resourceCode}/granted` + + const req = await axios.request({ + headers: { 'Authorization': authToken }, + method: 'get', + url, + }) + + return req.data +} - if (!email) { - return 'Could not find user specified by token.' +async function checkAuthorizationRestrictions( + { ctx, operationName, args, email }: { ctx: any, operationName: string, args: any, email: string }) { + + const { account, authToken } = ctx.vtex + const { sphinx } = ctx.clients + + if (operationName === 'uploadFile' && ALLOW_LIST.includes(account)) return true + + if (operationName === 'uploadFile') { + const restriction = BUCKET_RESTRICTIONS.find( + ({ bucket }) => bucket === args.bucket) + + if (restriction) { + const canAccess = await getUserCanAccessResource( + authToken, + account, + email, + restriction.productCode, + restriction.resourceCode + ) + + if (canAccess) return true + throw new Error(restriction.errorMessage) + } } if (operationName === 'deleteFile') { // Only admin users can delete files - const isAdminUser = await sphinx.isAdmin(email) + const isAdmin = await sphinx.isAdmin(email) - if (!isAdminUser) { - return 'User is not admin and can not access resource.' - } + if (isAdmin) return true + throw new Error('User is not admin and can not access resource.') } return true @@ -41,28 +75,11 @@ export class Authorization extends SchemaDirectiveVisitor { public visitFieldDefinition(field: GraphQLField) { const { resolve = defaultFieldResolver } = field - // eslint-disable-next-line max-params field.resolve = async (root, args, ctx, info) => { const operationName = info.fieldName - let isAllowed = false - - if (operationName === 'uploadFile') { - const isInAllowList = ALLOW_LIST.includes(ctx.vtex.account) - - if (isInAllowList) { - isAllowed = true - } - } - - if (!isAllowed) { - const cookieAllowsAccess = await authFromCookie(ctx, operationName) - - if (cookieAllowsAccess !== true) { - throw new Error(cookieAllowsAccess) - } - } - + const email = await getUserEmail(ctx) + await checkAuthorizationRestrictions({ ctx, operationName, args, email }) return resolve(root, args, ctx, info) } } -} +} \ No newline at end of file