[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of my-account/react/components/loaders/AddressesLoading.tsx from the my-account VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FunctionComponent } from 'react'
import React from 'react'
import {
  SkeletonPiece,
  SkeletonBox,
  BaseLoading,
} from 'vtex.my-account-commons'

import { headerConfig } from '../pages/Addresses'

const AddressesLoading: FunctionComponent<Props> = ({ data }) => {
  return (
    <BaseLoading queryData={data} headerConfig={headerConfig}>
      <main className="mt7 flex-ns flex-wrap-ns items-start-ns">
        {[0, 1].map(i => (
          <SkeletonBox shouldShowLowerButton key={i}>
            <div className="pv4 w5 h4">
              <div className="mb4">
                <SkeletonPiece width="70" size="3" />
              </div>
              <div className="mb4">
                <SkeletonPiece width="40" size="3" />
              </div>
              <SkeletonPiece width="30" size="3" />
            </div>
          </SkeletonBox>
        ))}
      </main>
    </BaseLoading>
  )
}

interface Props {
  data: unknown
}

export default AddressesLoading

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of my-account/react/components/loaders/AddressCreateLoading.tsx from the my-account VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FunctionComponent } from 'react'
import React from 'react'
import { BaseLoading } from 'vtex.my-account-commons'
import type { DataValue } from 'react-apollo'

import { headerConfig } from '../pages/AddressCreate'
import FormLoader from './FormLoader'

const AddressCreateLoading: FunctionComponent<Props> = ({ data }) => {
  return (
    <BaseLoading queryData={data} headerConfig={headerConfig}>
      <FormLoader />
    </BaseLoading>
  )
}

interface Props {
  data: DataValue<unknown>
}

export default AddressCreateLoading

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of my-account/react/components/loaders/ProfileEditLoading.tsx from the my-account VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FunctionComponent } from 'react'
import React from 'react'
import { BaseLoading } from 'vtex.my-account-commons'

import { headerConfig } from '../pages/ProfileEdit'
import FormLoader from './FormLoader'

const ProfileEditLoading: FunctionComponent<Props> = ({ data }) => {
  return (
    <BaseLoading queryData={data} headerConfig={headerConfig}>
      <FormLoader />
    </BaseLoading>
  )
}

interface Props {
  data: any
}

export default ProfileEditLoading

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of my-account/react/components/loaders/FormLoader.tsx from the my-account VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { SkeletonBox } from 'vtex.my-account-commons'

import Content from './FormContent'

const FormLoader = () => (
  <SkeletonBox shouldAllowGrowing maxWidthStep={6}>
    <Content />
  </SkeletonBox>
)

export default FormLoader

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of my-account/react/components/loaders/FormContent.tsx from the my-account VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { PureComponent } from 'react'
import { SkeletonPiece } from 'vtex.my-account-commons'

// eslint-disable-next-line react/prefer-stateless-function
class FormContent extends PureComponent {
  public render() {
    return (
      <>
        <div className="mb8 mt4">
          <SkeletonPiece width="100" size="3" />
          <SkeletonPiece width="100" size="3" />
        </div>
        <div className="mb9">
          <SkeletonPiece width="100" size="3" />
          <SkeletonPiece width="100" size="3" />
        </div>
        <div className="mb8">
          <SkeletonPiece width="40" size="3" />
          <SkeletonPiece width="40" size="3" />
        </div>
      </>
    )
  }
}

export default FormContent

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of my-account/react/components/Menu/ProfilePicture/UserPlaceholderPicture.tsx from the my-account VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'

const UserPlaceholderPicture = () => {
  return (
    <svg
      className="h-100 w-auto"
      width="110"
      height="110"
      viewBox="0 0 110 110"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <rect width="110" height="110" fill="black" fillOpacity="0" />
      <rect width="110" height="110" fill="black" fillOpacity="0" />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M55 110C85.3757 110 110 85.3757 110 55C110 24.6243 85.3757 0 55 0C24.6243 0 0 24.6243 0 55C0 85.3757 24.6243 110 55 110Z"
        fill="#D8D8D8"
      />
      <mask
        id="mask0"
        maskUnits="userSpaceOnUse"
        x="0"
        y="0"
        width="110"
        height="110"
      >
        <path
          fillRule="evenodd"
          clipRule="evenodd"
          d="M55 110C85.3757 110 110 85.3757 110 55C110 24.6243 85.3757 0 55 0C24.6243 0 0 24.6243 0 55C0 85.3757 24.6243 110 55 110Z"
          fill="white"
        />
      </mask>
      <g mask="url(#mask0)">
        <rect
          width="85.3731"
          height="96.8655"
          fill="black"
          fillOpacity="0"
          transform="translate(13.1367 21.3433)"
        />
        <path
          fillRule="evenodd"
          clipRule="evenodd"
          d="M66.4949 78.5818H45.1516C27.4705 78.5818 13.1367 93.0978 13.1367 111.004C13.1367 111.004 29.1442 118.209 55.8233 118.209C82.5024 118.209 98.5098 111.004 98.5098 111.004C98.5098 93.0978 84.1761 78.5818 66.4949 78.5818Z"
          fill="#979797"
        />
        <path
          fillRule="evenodd"
          clipRule="evenodd"
          d="M30.7969 46C30.7969 32.3824 42.0001 21.3433 55.82 21.3433C69.64 21.3433 80.8432 32.3824 80.8432 46C80.8432 59.6175 69.64 74.1791 55.82 74.1791C42.0001 74.1791 30.7969 59.6175 30.7969 46Z"
          fill="#979797"
        />
      </g>
    </svg>
  )
}

export default UserPlaceholderPicture

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of my-account/react/components/Menu/ProfilePicture/PictureRenderer.tsx from the my-account VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FunctionComponent } from 'react'
import React from 'react'

import UserPlaceholderPicture from './UserPlaceholderPicture'

const PictureRenderer: FunctionComponent<Props> = ({ imagePath }) => {
  const imageStyle = { backgroundImage: `url(${imagePath})` }

  return imagePath ? (
    <div className="h-100 w-100 br-100 cover" style={imageStyle} />
  ) : (
    <UserPlaceholderPicture />
  )
}

interface Props {
  imagePath?: string
}

export default PictureRenderer

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of vega-graphql/graphql/schema.graphql from the vega-graphql VTEX app.

[Assistant]
Here's the implementation:

```graphql
type SpecLocator {
  appId: String
  specName: String
}

type Query {
  spec (
    specName: String!,
    appId: String
  ): String

  specs: [SpecLocator]
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of vega-graphql/node/typings.d.ts from the vega-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { Apps, ServiceContext, VBase } from '@vtex/api'
import { StoreDashDataSource } from './dataSources/storedash'

declare global {
  interface Context extends ServiceContext {
    dataSources: DataSources
  }

  interface DataSources {
    storeDash: StoreDashDataSource
    apps: Apps
    vbase: VBase
  }

  type Resolver<P, A, I, R> = (root: P, args: A, ctx: Context, info: I) => Promise<R>

  interface StoreDashInput {
    aggregateBy?: string[]
    fields?: string[]
    from: string
    interval?: string
    operation?: string
    size?: number
    timezone?: string
    to?: string
  }

  type Maybe<T> = T | void

  interface Specs {
    [name: string]: string
  }

  interface SpecLocator {
    appId: string
    specName: string
  }

  interface Layout {
    specLocator: SpecLocator
    spec: string
  }

  interface LayoutContainer {
    cacheId: string
    layout: Layout[]
  }
}

export {}
```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of vega-graphql/node/package.json from the vega-graphql VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "dependencies": {
    "@vtex/api": "1.4.0",
    "apollo-datasource-rest": "^0.1.5",
    "apollo-server": "^2.1.0",
    "apollo-server-errors": "^2.0.2",
    "ramda": "^0.25.0"
  },
  "devDependencies": {
    "@types/bluebird": "^3.5.24",
    "@types/bluebird-global": "^3.5.9",
    "@types/node": "^10.7.1",
    "@types/ramda": "types/npm-ramda#dist",
    "tslint": "^5.11.0",
    "tslint-config-vtex": "^2.0.0",
    "typescript": "^3.1.3"
  },
  "scripts": {
    "lint": "tsc --noEmit && tslint -c tslint.json './**/*.ts'"
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of vega-graphql/node/index.ts from the vega-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import * as Bluebird from 'bluebird'

global.Promise = Bluebird

import { cache, dataSources } from './dataSources'
import { resolvers } from './resolvers'

export default {
  graphql: {
    cache,
    dataSources,
    resolvers,
  }
}
```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of vega-graphql/node/dataSources/index.ts from the vega-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { Apps, DiskCache, LRUCache, MultilayeredCache } from '@vtex/api'

import { CACHE_PATH } from '../common/globals'

const cacheStorage = new MultilayeredCache([
  new LRUCache<string, any>({
    max: 1000
  }),
  new DiskCache(CACHE_PATH)
])

export const dataSources = () => ({
  apps: new Apps(),
})

export const cache = () => cacheStorage
```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of vega-graphql/node/resolvers/spec.ts from the vega-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { isValidAppIdOrLocator } from '@vtex/api'
import { ApolloError } from 'apollo-server-errors'
import { compose, flatten, keys, map, mapObjIndexed, partial, values, zipObj } from 'ramda'

import { appsWithSpecs, getSpec, getSpecs } from '../common/spec'

const maybeSpecsToSpecLocators = (maybeSpecs: Maybe<Specs>, appId: string) => maybeSpecs
  ? compose(
    map((specName: string) => ({specName, appId})),
    keys
  )(maybeSpecs) as SpecLocator[]
  : []

export const specs = async (root, args: void, ctx: Context, info): Promise<SpecLocator[]> => {
  const {dataSources: {apps}} = ctx
  const appIdsWithStats = await appsWithSpecs(apps)
  const getSpecsForAppId = partial(getSpecs, [apps])
  const appSpecs = await Promise.map(appIdsWithStats, getSpecsForAppId)
  const specsByAppId = zipObj(appIdsWithStats, appSpecs)
  return compose(
    flatten,
    values,
    mapObjIndexed(maybeSpecsToSpecLocators)
  )(specsByAppId) as SpecLocator[]
}

interface SpecArgs {
  specName: string
  appId?: string
}

export const spec = async (root, args: SpecArgs, ctx: Context, info) => {
  const {dataSources: {apps}} = ctx
  const {specName, appId} = args

  if (appId && isValidAppIdOrLocator(appId)) {
    return getSpec(apps, appId, specName).then(JSON.stringify)
  }

  throw new ApolloError(`Vega spec ${specName} was not found for app ${appId}`)
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of vega-graphql/node/resolvers/index.ts from the vega-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { spec, specs } from './spec'

export const resolvers = {
  Query: {
    spec,
    specs,
  },
}
```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of vega-graphql/node/common/utils.ts from the vega-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { SPEC_PATH } from './globals'

export const cacheKey = (app: string) => `${app}/${SPEC_PATH}`.replace(/\W/g, '_')

export const toString = ({data}: {data: Buffer}) => data.toString()

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of vega-graphql/node/common/spec.ts from the vega-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { Apps, DiskCache, LRUCache, MultilayeredCache, removeBuild } from '@vtex/api'
import { find, keys, prop } from 'ramda'

import { CACHE_PATH, SPEC_PATH, VEGA_GRAPHQL } from './globals'
import { ignoreNotFound } from './notFound'
import { cacheKey, toString } from './utils'

const specStorage = new MultilayeredCache<string, Maybe<Specs>>([
  new LRUCache({
    max: 100
  }),
  new DiskCache(CACHE_PATH)
])

const equalsNoBuild = (appIdNoBuild: string) => (appId: string) => removeBuild(appIdNoBuild) === removeBuild(appId)

const findAppInDeps = (apps: Apps, appIdNoBuild: string): Promise<Maybe<string>> =>
  apps.getDependencies()
  .then(keys)
  .then(find(equalsNoBuild(appIdNoBuild)))

const fetchSpec = (apps: Apps, appId: string) => (): Promise<Maybe<Specs>> =>
  apps.getAppFile(appId, SPEC_PATH)
  .then(toString)
  .then(JSON.parse)
  .catch(ignoreNotFound(null))

export const getSpecs = async (apps: Apps, appId: string) => {
  const appIdWithBuild = await findAppInDeps(apps, removeBuild(appId))
  return appIdWithBuild && specStorage.get(cacheKey(appIdWithBuild), fetchSpec(apps, appIdWithBuild))
}

export const getSpec = (apps: Apps, appId: string, specName: string): Promise<string> =>
  getSpecs(apps, appId).then(prop(specName))

export const appsWithSpecs = (apps: Apps) => apps.getDependencies(VEGA_GRAPHQL).then(keys)
```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of vega-graphql/node/common/globals.ts from the vega-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
export const CACHE_PATH = '/cache/v0'

export const SPEC_PATH = 'dist/vtex.vega-graphql/spec0x.vega.json'

export const VEGA_GRAPHQL = 'vtex.vega-graphql@0.x'

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of vega-graphql/node/common/notFound.ts from the vega-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
export const ignoreNotFound = <T>(fallback: T) => (error: any): T => {
    if (error.response && error.response.status === 404) {
      return fallback
    }
    throw error
  }
```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of component-library/messages/pt.json from the component-library VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "admin/test": "teste",
  "store/test": "teste"
}
```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of component-library/messages/en.json from the component-library VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "admin/test": "test",
  "store/test": "test"
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of component-library/messages/es.json from the component-library VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "admin/test": "teste",
  "store/test": "teste"
}
```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of component-library/react/package.json from the component-library VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "scripts": {
    "lint": "tsc --noEmit && eslint --quiet --fix --ext ts,tsx .",
    "test": "vtex-test-tools test"
  },
  "dependencies": {
    "apollo-client": "^2.6.3",
    "react": "^16.8.6",
    "react-apollo": "^2.5.8",
    "react-dom": "^16.8.6",
    "react-intl": "^2.9.0"
  },
  "devDependencies": {
    "@types/graphql": "^14.2.2",
    "@types/react": "^16.8.22",
    "@types/react-intl": "^2.3.18",
    "@vtex/test-tools": "^0.3.2",
    "eslint": "5.x",
    "eslint-config-vtex-react": "4.x",
    "prettier": "^1.18.2",
    "prop-types": "^15.7.2",
    "typescript": "3.5.2"
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of component-library/react/__tests__/index.test.tsx from the component-library VTEX app.

[Assistant]
Here's the implementation:

```tsx
describe('Orders List Scenarios', () => {
  it('should work', () => {
    expect(1).toBe(1)
  })
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/configuration/schema.json from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "features": {
      "type": "array",
      "items": [
        {
          "type": "object",
          "properties": {
            "label": {
              "type": "string"
            },
            "value": {
              "type": "string"
            },
            "roles": {
              "type": "array",
              "items": [
                {
                  "type": "string"
                }
              ]
            }
          },
          "required": ["label", "value", "roles"]
        }
      ]
    }
  },
  "required": ["name", "features"]
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/docs/README.md from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```markdown
# Storefront Permissions

<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->

[![All Contributors](https://img.shields.io/badge/all_contributors-0-orange.svg?style=flat-square)](#contributors-)

<!-- ALL-CONTRIBUTORS-BADGE:END -->

> ℹ The **Storefront Permissions** app is part of VTEX’s [B2B Suite](https://developers.vtex.com/vtex-developer-docs/docs/vtex-b2b-suite) solution, which is a collection of apps that allow stores to manage organizations, storefront roles and permissions, and checkout settings for B2B commerce relationships. We recommend that you use it alongside the other apps in this suite for all functionalities to work as expected.

When navigating a B2B store as a customer, it is common for a main user from an organization to have other people under their structure, each with their own information and access privileges.

Within an organization, each user can have different roles, such as a professional buyer that places orders with budget limits from a predefined cost center or  a manager in charge of reviewing and approving orders. These roles can be associated with multiple permissions, depending on the actions this user needs to perform.

The **Storefront Permissions** app stores a predefined set of roles and app permissions related to what B2B users can access and do in your storefront, making this information available for other integrated apps to check. This is useful for stores who want to set specific app permissions for users with different roles in an organization.

## Available storefront roles

In the following table, you can see the available storefront roles, their key used for identification in the app’s code, and their description.

| **Role** | **Key** | **Description** |
|---|---|---|
| Store Admin | `store-admin` | Store administrator, also known as a user who has access to the VTEX Admin, including the permission to impersonate any company/buyer. |
| Sales Admin | `sales-admin` | Sales administrator user who can manage all sales users. |
| Sales Manager | `sales-manager` | Sales manager user who can manage sales users in the same organization, as well as assist or impersonate buyers during navigation or purchase. |
| Sales Representative | `sales-representative` | Sales representative user who can assist or impersonate buyers in the same cost center during navigation or purchase. |
| Organization Admin | `customer-admin` | Main organization user who manages the organization information, as well as its members and cost centers. |
| Organization Approver | `customer-approver` | Organization user who can take a saved cart or quote that was created by an **Organization Buyer** and use it to place an order. |
| Organization Buyer | `customer-buyer` | Organization user who has the ability to add items to cart. If the **B2B Quotes** app is installed, they are also able to save their cart for future use or create a quote. |

## How it works

**Storefront Permissions** communicates automatically with other [B2B Suite](https://developers.vtex.com/vtex-developer-docs/docs/vtex-b2b-suite) apps, such as [B2B Organizations](https://developers.vtex.com/vtex-developer-docs/docs/vtex-b2b-organizations), where it enables different organization management capabilities depending on each user’s role.

It also allows you to configure available permissions when developing your own app, associate them with the predefined roles, and have these permissions checked by other applications – if you perform the steps described in the [Advanced app integration](#advanced-app-integration-optional) section.

The **Storefront Permissions** app does not contain an interface – it operates “backstage”, storing the predefined roles and serving as a bridge to communicate with other apps in order to check user permissions. If you would like to manage roles and app permissions using the VTEX Admin interface, you must also install the [Storefront Permissions UI](https://developers.vtex.com/vtex-developer-docs/docs/vtex-storefront-permissions-ui) app. As an optional feature, you can install the [Admin Customers](https://developers.vtex.com/vtex-developer-docs/docs/vtex-admin-customers) app for additional customer management capabilities.

## Before you start

Make sure you have the [VTEX IO CLI (Command Line Interface)](https://developers.vtex.com/vtex-developer-docs/docs/vtex-io-documentation-vtex-io-cli-install) installed in your machine.

If you opt to develop your own app and [integrate](#advanced-app-integration-optional) it with **Storefront Permissions**, read our documentation on [Developing an app](https://developers.vtex.com/vtex-developer-docs/docs/vtex-io-documentation-developing-an-app).

## Installation

You can install the app by running `vtex install vtex.storefront-permissions` in your terminal, using the [VTEX IO CLI](https://developers.vtex.com/vtex-developer-docs/docs/vtex-io-documentation-vtex-io-cli-installation-and-command-reference).

## Advanced app integration [optional]

If you would like to develop your own app and integrate it with **Storefront Permissions**, follow the steps below.

1. Open your app’s repository.
2. In the `manifest.json` file, add `vtex.storefront-permissions` under the `builders` property, as follows.

    ```json
    "builders": {
    "vtex.storefront-permissions": "1.x"
    }
    ```

3. In the root folder, create a new folder named `vtex.storefront-permissions`.
4. In the `vtex.storefront-permissions` folder, create a `configuration.json` file. The content of this file should follow the format below. Keep in mind that you must replace the example information with the `name` of your app and its `features`.

    ```json
    {
      "name": "My awesome app",
      "features": [
        {
          "label": "View",
          "value": "view-awesome-things",
          "roles": ["store-admin","sales-admin","sales-manager","sales-representative","customer-admin","customer-approver","customer-buyer"]
        },
        {
          "label": "Create",
          "value": "create-awesome-things",
          "roles": ["store-admin","sales-admin","sales-manager","sales-representative"]
        },
        {
          "label": "Delete",
          "value": "delete-awesome-things",
          "roles": ["store-admin","sales-admin","sales-manager","sales-representative"]
        },
        {
          "label": "Special Access",
          "value": "allow-special-access",
          "roles": ["store-admin","sales-admin"]
        }
      ]
    }
    ```

    Below you can find a description of each property.

    | **Property** | **Type** | **Description** |
    |---|---|---|
    | `name` | string | Name of your app. |
    | `features` | array of objects | List of features related to your app – the functionalities of your app that you want to set permissions for users to be able to access or not. |
    |  ⤷  | object | Object containing information about each feature of your app. |
    |    ⤷ `label` | string | Name or description of the feature. |
    |    ⤷ `value` | string | Identifier key you want to use for the feature, as used in your app’s [custom storefront components](https://developers.vtex.com/vtex-developer-docs/docs/vtex-io-documentation-developing-custom-storefront-components). Do not use space or special characters in this field. |
    |    ⤷ `roles` | array of strings | List with the keys for all the roles you want to associate with the permission to use the feature by default. You can find more information about each role and its key in the [Available Storefront Roles section](#available-storefront-roles) of this documentation. |

5. Make sure you refer to the `value` – the identifier key – for each feature you want to set permissions for in your [custom storefront components](https://developers.vtex.com/vtex-developer-docs/docs/vtex-io-documentation-developing-custom-storefront-components)’ code, when developing your app.

    Example: in the B2B Organizations app, one of the permissions created is represented by the following object.

    ```json
    {
      "label": "Create Cost Center",
      "value": "create-cost-center-organization",
      "roles": ["store-admin","sales-admin","customer-admin"]
    }
    ```

    Therefore, in a [custom storefront component’s code](https://github.com/vtex-apps/b2b-organizations/blob/366a28add226eac5d9b104bb13a9f2cd1d574f02/react/components/CostCenterDetails.tsx) in TypeScript, whenever referring to this feature, it uses `create-cost-center-organization`.

Once you are done developing and installing your own app, if you have [Storefront Permissions UI](https://developers.vtex.com/vtex-developer-docs/docs/vtex-storefront-permissions-ui), the features of your app associated with each role will be automatically loaded on the **Storefront Permissions** page. For more details on this, read our documentation on the [Storefront Permissions UI](https://developers.vtex.com/vtex-developer-docs/docs/vtex-storefront-permissions-ui) app.

### Session Watcher

The session watcher is based in the [session transformation feature](https://developers.vtex.com/docs/guides/vtex-io-documentation-collecting-user-session-data#configuring-vtexsession) to update the cart fields with Buyer [Organization data](https://github.com/vtex-apps/b2b-organizations): `Shipping Address`, `Profile` and `Marketdata` ( `utmCampaign` with `organization.id` and `utmMedium` with `costCenter.id` )

To activate it, [use the mutation](#setSessionWatcher).

### GraphQL queries

Now that your app is integrated, you can write a GraphQL query on your app to check the current user's permission within the context of your app.

First, you need to associate your test user to a Role containing your app's permission by following the [B2B Organizations documentation](https://developers.vtex.com/vtex-developer-docs/docs/vtex-b2b-organizations#users).

It is not necessary to declare your app name nor user credentials, the query will take care of these details.

#### checkUserPermission

This query allows you to check which permissions a user has in the context of your app. When the query is performed, **Storefront Permissions** checks which app the request is coming from, and returns the current user's permissions for that specific app.

The response will be a list with all the permissions the current user has on the app.

Sample query:

```graphql
query permissions {
  checkUserPermission @context(provider: "vtex.storefront-permissions") {
    role {
      id
      name
      slug
    }
    permissions
  }
}
```

Sample response:

```graphql
{
  "data":{
    "checkUserPermission": {
      "role": {
        "id": "00549c22-f39d-11eb-82ac-0a55b612700f",
        "name": "Admin",
        "slug": "customer-admin"
      },
      "permissions": ["view-awesome-things","create-awesome-things","delete-awesome-things"]
    }
  }
}
```

#### hasUsers

This query allows you to check if there are any users associated with a specific role. The response will be a boolean, with `"hasUsers": true` if there are users with this role or `false` if there are not.

Sample query:

```graphql
query hasUsers {
  hasUsers(slug: "sales-admin")
    @context(provider: "vtex.storefront-permissions")
}
```

Sample response:

```graphql
{
  "data": {
    "hasUsers": true
  }
}
```

#### checkImpersonation

This query allows you to check if the current user is [impersonating](https://developers.vtex.com/vtex-developer-docs/docs/vtex-b2b-organizations#impersonate-users) another user, and retrieve information on the impersonated user.

Sample query:

```graphql
query checkImpersonation {
  checkImpersonation {
    firstName
    lastName
    email
    userId
    error
  }
}
```

Sample response:

```graphql
{
  "data": {
    "checkImpersonation": {
      "firstName": "Andrew",
      "lastName": "Smith",
      "email": "testemail@mail.com",
      "userId": "aaaaa-bbbb-cccc-dddd-eeeee",
      "error": null
    }
  }
}
```

#### getSessionWatcher

Using Session Watcher, **Storefront Permissions** detects changes to the user authentication to trigger changes to its cart and navigation, based on associated price tables, collections, profile information and shipping options.

This query fetches Session Watcher’s status, that is, whether it is active or not. The response will be a boolean, with `true` meaning that it is enabled or `false` meaning it is disabled.

Sample query:

```graphql
query getSessionWatcher {
  getSessionWatcher
}
```

Sample response:

```graphql
{
  "data": {
    "getSessionWatcher": false
  }
}
```

#### getOrganizationsPaginatedByEmail

This query allows you to retrieve a paginated list of organizations associated with a specific email. The response includes the organization data along with pagination details such as the current page, page size, and total number of results.

Sample query:

```graphql
query OrganizationsPaginated($email: String!, $page: Int, $pageSize: Int) {
  getOrganizationsPaginatedByEmail(
    email: $email
    page: $page
    pageSize: $pageSize
  ) {
    data {
      id
      organizationStatus
      costId
      orgId
      costCenterName
    }
    pagination {
      page
      pageSize
      total
    }
  }
}
```

Sample response:

{
  "data": {
    "getOrganizationsPaginatedByEmail": {
      "data": [
        {
          "id": "00000000-0000-0000-0000-000000000000",
          "clId": "00000000-0000-0000-0000-000000000000",
          "costId": "00000000-0000-0000-0000-000000000000",
          "orgId": "00000000-0000-0000-0000-000000000000"
        },
        {
          "id": "00000000-0000-0000-0000-000000000000",
          "clId": "00000000-0000-0000-0000-000000000000",
          "costId": "00000000-0000-0000-0000-000000000000",
          "orgId": "00000000-0000-0000-0000-000000000000"
        }
      ],
      "pagination": {
        "page": 1,
        "pageSize": 25,
        "total": 2
      }
    }
  }
}

### GraphQL mutation

#### impersonateUser

Using this mutation, you can inform the `userId` to [impersonate an user](https://developers.vtex.com/vtex-developer-docs/docs/vtex-b2b-organizations#impersonate-users). To remove impersonation, send an empty `userId` instead.

Sample mutation:

```graphql
mutation impersonateUser($userId: ID)
@context(provider: "vtex.storefront-permissions") {
  impersonateUser(userId: $userId) {
    status
    message
  }
}
```

#### <a href="#setSessionWatcher"></a> setSessionWatcher

If your account is not using `vtex.b2b-organizations` you may want to disable the Session Watcher to avoid unnecessary operations. To do so, set the `active` property to `false` in the mutation exemplified below.

The response will be a boolean, with `true` for a successful operation or `false` for a failure.

Sample mutation:

```graphql
mutation setSessionWatcher {
  sessionWatcher(active: false)
}
```

Sample response:

```graphql
{
  "data": {
    "sessionWatcher": true
  }
}
```

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/vtex.session/configuration.json from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "storefront-permissions": {
    "input": {
      "authentication": ["storeUserEmail"],
      "checkout": ["orderFormId"],
      "impersonate": ["storeUserEmail", "storeUserId"],
      "public": ["impersonate", "removeB2B", "b2bCurrentCostCenter"],
      "storefront-permissions": ["hash"]
    },
    "output": {
      "public": ["facets", "sc", "regionId"],
      "storefront-permissions": [
        "storeUserId",
        "storeUserEmail",
        "organization",
        "costcenter",
        "priceTables",
        "collections",
        "userId",
        "hash"
      ]
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/graphql/schema.graphql from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```graphql
type Query {
  getAppSettings: SettingsResponse
    @cacheControl(scope: PRIVATE)
    @settings(settingsType: "workspace")
    @withSender

  getRole(id: ID!): Role @cacheControl(scope: PRIVATE, maxAge: SHORT)

  hasUsers(slug: String!): Boolean @cacheControl(scope: PRIVATE, maxAge: SHORT)

  listRoles: [Role]
    @cacheControl(scope: PRIVATE, maxAge: SHORT)
    @settings(settingsType: "workspace")
    @withSession
    @withSender

  getFeaturesByModule(module: String!): Feature
    @settings(settingsType: "workspace")
    @cacheControl(scope: PRIVATE, maxAge: SHORT)

  listFeatures: [FullFeature]
    @settings(settingsType: "workspace")
    @cacheControl(scope: PRIVATE, maxAge: SHORT)
    @withSender

  getUser(id: ID!): User @cacheControl(scope: PRIVATE) @validateStoreUserAccess
  getB2BUser(id: ID!): User @cacheControl(scope: PRIVATE)

  checkCustomerSchema: Boolean @cacheControl(scope: PRIVATE) @withSender

  getUserByEmail(email: String!): [User]
    @cacheControl(scope: PRIVATE)
    @withSender
    @validateStoreUserAccess

  listAllUsers: [User]
    @cacheControl(scope: PRIVATE, maxAge: SHORT)
    @withSender
    @validateStoreUserAccess

  listUsers(organizationId: ID, costCenterId: ID, roleId: ID): [User]
    @cacheControl(scope: PRIVATE, maxAge: SHORT)
    @deprecated(
      reason: "This query is deprecated, use listUsersPaginated query instead."
    )
    @validateStoreUserAccess

  listUsersPaginated(
    organizationId: ID
    costCenterId: ID
    roleId: ID
    page: Int
    pageSize: Int
    search: String
    sortOrder: String
    sortedBy: String
  ): UserPagination
    @cacheControl(scope: PRIVATE, maxAge: SHORT)
    @validateStoreUserAccess

  checkImpersonation: UserImpersonation
    @settings(settingsType: "workspace")
    @withSession
    @cacheControl(scope: PRIVATE)

  checkUserPermission: UserPermissions
    @settings(settingsType: "workspace")
    @withSession
    @withSender
    @cacheControl(scope: PRIVATE)

  getSessionWatcher: Boolean @cacheControl(scope: PRIVATE)

  getUsersByEmail(email: String!, orgId: ID, costId: ID): [User]
    @cacheControl(scope: PRIVATE)
    @validateStoreUserAccess

  getActiveUserByEmail(email: String!): User
    @cacheControl(scope: PRIVATE)
    @validateStoreUserAccess

  getOrganizationsByEmail(email: String!): [Organization]
    @cacheControl(scope: PRIVATE)
    @validateStoreUserAccess

  getOrganizationsPaginatedByEmail(
    email: String!
    page: Int = 1
    pageSize: Int = 25
  ): OrganizationPaginated
    @cacheControl(scope: PRIVATE)
    @validateStoreUserAccess
}

type Mutation {
  sessionWatcher(active: Boolean!): Boolean @cacheControl(scope: PRIVATE)

  saveRole(
    id: ID
    name: String!
    slug: String
    features: [FeatureInput]
  ): MutationResponse
    @cacheControl(scope: PRIVATE)
    @withSender
    @validateStoreUserAccess

  deleteRole(id: ID!): MutationResponse
    @cacheControl(scope: PRIVATE)
    @withSender
    @validateStoreUserAccess

  saveUser(
    id: ID
    roleId: ID!
    userId: ID
    orgId: ID
    costId: ID
    clId: ID
    canImpersonate: Boolean = false
    name: String!
    email: String!
  ): MutationResponse
    @validateAdminUserAccess(adminPermission: "B2B_ORGANIZATIONS_EDIT")
    @cacheControl(scope: PRIVATE)

  updateUser(
    id: ID
    roleId: ID!
    userId: ID
    orgId: ID
    costId: ID
    clId: ID
    canImpersonate: Boolean = false
    email: String
    name: String
  ): MutationResponse
    @validateAdminUserAccess(adminPermission: "B2B_ORGANIZATIONS_EDIT")
    @cacheControl(scope: PRIVATE)

  addUser(
    id: ID
    roleId: ID!
    userId: ID
    orgId: ID
    costId: ID
    canImpersonate: Boolean = false
    name: String!
    email: String!
  ): MutationResponse
    @validateAdminUserAccess(adminPermission: "B2B_ORGANIZATIONS_EDIT")
    @cacheControl(scope: PRIVATE)

  addCostCenterToUser(costId: ID!, userId: ID!): MutationResponse
    @validateStoreUserAccess
    @cacheControl(scope: PRIVATE)

  addOrganizationToUser(
    orgId: ID!
    userId: ID!
    costId: ID!
    roleId: ID
  ): MutationResponse
    @validateAdminUserAccess(adminPermission: "B2B_ORGANIZATIONS_EDIT")
    @cacheControl(scope: PRIVATE)

  setActiveUserByOrganization(userId: ID): MutationResponse
    @validateStoreUserAccess
    @cacheControl(scope: PRIVATE)

  impersonateUser(userId: ID): MutationResponse
    @validateStoreUserAccess
    @cacheControl(scope: PRIVATE)

  deleteUser(id: ID!, userId: ID, email: String!): MutationResponse
    @validateAdminUserAccess(adminPermission: "B2B_ORGANIZATIONS_EDIT")
    @cacheControl(scope: PRIVATE)

  setCurrentOrganization(orgId: ID!, costId: ID!): MutationResponse
    @withSession
    @cacheControl(scope: PRIVATE)
    @withSender

  ignoreB2BSessionData(enabled: Boolean!): MutationResponse
    @withSession
    @cacheControl(scope: PRIVATE)

  setCurrentPriceTable(priceTable: String): MutationResponse
    @withSession
    @cacheControl(scope: PRIVATE)
    @withSender
    @validateStoreUserAccess
}

type UserImpersonation {
  firstName: String
  lastName: String
  email: String
  userId: ID
  error: String
}

type UserPermissions {
  role: SimpleRole
  permissions: [String]
}

type Pagination {
  page: Int
  pageSize: Int
  total: Int
}

type UserPagination {
  data: [User]
  pagination: Pagination
}

type User {
  id: ID
  roleId: ID
  userId: ID
  clId: ID
  orgId: ID
  costId: ID
  name: String!
  email: String!
  canImpersonate: Boolean
  active: Boolean
  selectedPriceTable: String
}

type Organization {
  id: ID
  clId: ID
  costId: ID
  orgId: ID
  roleId: ID
}

type OrganizationPaginated {
  data: [Organization]
  pagination: Pagination
}

type Profile {
  id: ID
  name: String
  features: [Feature]!
  roleId: ID!
  scoped: Boolean
}

input FeatureInput {
  module: String!
  features: [String]!
}

type FeatureType {
  module: String!
  features: [String]!
}

type FullFeature {
  module: String!
  name: String!
  features: [SingleFeature]!
}

type Feature {
  module: String!
  name: String!
  features: [String]!
}

type SingleFeature {
  label: String!
  value: String!
}

type Role {
  id: ID
  name: String!
  locked: Boolean
  slug: String
  features: [FeatureType]
}

type SimpleRole {
  id: ID
  name: String!
  slug: String
}

type MutationResponse {
  id: String
  status: String
  message: String
}

type SettingsResponse {
  adminSetup: Data
}

scalar Data

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/graphql/directives.graphql from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```graphql
directive @checkUserAccess on FIELD | FIELD_DEFINITION
directive @validateStoreUserAccess(
  adminPermission: String
) on FIELD | FIELD_DEFINITION
directive @checkAdminAccess on FIELD | FIELD_DEFINITION
directive @validateAdminUserAccess(
  adminPermission: String
) on FIELD | FIELD_DEFINITION
directive @withSession on FIELD_DEFINITION
directive @withSender on FIELD_DEFINITION
directive @withUserPermissions on FIELD_DEFINITION
directive @auditAccess on FIELD | FIELD_DEFINITION

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/service.json from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "stack": "nodejs",
  "memory": 256,
  "ttl": 60,
  "timeout": 45,
  "cpu": {
    "type": "shared",
    "value": 5,
    "scalingPercentage": 20
  },
  "minReplicas": 8,
  "maxReplicas": 80,
  "workers": 1,
  "routes": {
    "setProfile": {
      "path": "/_v/storefront-permissions/session/transform",
      "public": true
    },
    "checkPermissions": {
      "path": "/_v/storefront-permissions/check-permissions",
      "public": false
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/typings.d.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
interface ReqContext {
  account: string
  workspace: string
  authToken: string
  region: string
  production: boolean
  userAgent: string
}

interface Logger {
  log(
    content: string,
    level: LogLevel,
    details?: Record<string, unknown>
  ): PromiseLike<void>
}

interface OperationState {
  orderFormId: string
  ctx: ReqContext
  data?: OperationData
  logger: Logger
}

interface OperationData {
  orderForm?: any
  userProfileId: string
  cookie: string
}

type ProcessPaymentStep = (
  state: OperationState,
  next: () => Promise<void>
) => Promise<void>

type LogLevel = 'info' | 'error' | 'warning'

type Timings = { [middleware: string]: [number, number] }

declare module '*.json' {
  const value: any
  export default value
}

interface RegionsResponse {
  id: string
  sellers: Sellers[]
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/package.json from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "name": "vtex.checkout-ui-custom",
  "version": "3.2.1",
  "dependencies": {
    "@vtex/api": "6.50.1",
    "atob": "^2.1.2",
    "co-body": "^6.0.0",
    "cookie": "^0.3.1",
    "graphql": "^14.5.0",
    "graphql-tools": "^4.0.6",
    "qs": "^6.9.4",
    "ramda": "^0.25.0",
    "slugify": "^1.2.6"
  },
  "devDependencies": {
    "@types/atob": "^2.1.2",
    "@types/bluebird": "^3.5.25",
    "@types/co-body": "0.0.3",
    "@types/cookie": "^0.3.2",
    "@types/graphql": "^14.5.0",
    "@types/jsonwebtoken": "^8.5.0",
    "@types/node": "^12.0.0",
    "@types/ramda": "types/npm-ramda#dist",
    "@vtex/api": "6.50.1",
    "@vtex/prettier-config": "^0.3.1",
    "tslint": "^5.12.0",
    "tslint-config-prettier": "^1.18.0",
    "tslint-config-vtex": "^2.1.0",
    "typescript": "3.9.7",
    "vtex.graphql-server": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.graphql-server@1.67.6/public/_types/react",
    "vtex.profile-session": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.profile-session@0.8.5/public/_types/react",
    "vtex.search-session": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.search-session@0.2.10/public/_types/react",
    "vtex.storefront-permissions": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.storefront-permissions@1.29.8/public/@types/vtex.storefront-permissions",
    "vtex.styleguide": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.styleguide@9.146.3/public/@types/vtex.styleguide"
  },
  "scripts": {
    "lint": "tsc --noEmit && tslint -c tslint.json './**/*.ts'"
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/index.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import type {
  ClientsConfig,
  ServiceContext,
  ParamsContext,
  RecorderState,
  EventContext,
  IOContext,
  SegmentData,
} from '@vtex/api'
import { method, Service, AuthType, LRUCache } from '@vtex/api'

import { schemaDirectives } from './directives'
import { Clients } from './clients'
import { resolvers } from './resolvers'

const TIMEOUT_MS = 5000

const defaultClientOptions = {
  retries: 1,
  timeout: TIMEOUT_MS,
}

const memoryCache = new LRUCache<string, any>({ max: 1000 })

const clients: ClientsConfig<Clients> = {
  implementation: Clients,
  options: {
    default: defaultClientOptions,
    settings: {
      memoryCache,
    },
    b2bAdmin: {
      authType: AuthType.bearer,
      memoryCache,
    },
  },
}

declare global {
  type Context = ServiceContext<Clients>
  interface CustomIOContext extends IOContext {
    currentProfile: CurrentProfile
    segment?: SegmentData
    orderFormId?: string
  }

  interface CurrentProfile {
    email: string
    userId: string
  }
  interface StatusChangeContext extends EventContext<Clients> {
    body: {
      domain: string
      orderId: string
      currentState: string
      lastState: string
      currentChangeDate: string
      lastChangeDate: string
    }
  }

  interface State {
    code: number
  }
}

export default new Service<Clients, RecorderState, ParamsContext>({
  clients,
  graphql: {
    resolvers: {
      Mutation: resolvers.Mutation,
      Query: resolvers.Query,
    },
    schemaDirectives,
  },
  routes: {
    checkPermissions: method({
      GET: resolvers.Routes.checkPermissions,
    }),
    setProfile: method({
      POST: resolvers.Routes.setProfile,
    }),
  },
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/mdSchema.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
export default [
  {
    name: 'b2b_roles',
    version: 'v0.0.3',
    body: {
      properties: {
        name: {
          type: 'string',
          title: 'Role Name',
        },
        slug: {
          type: ['null', 'string'],
          title: 'Role Slug',
        },
        locked: {
          type: ['null', 'boolean'],
          title: 'Can delete',
          default: false,
        },
        features: {
          type: 'array',
          items: {
            type: 'object',
          },
          title: 'Features',
        },
      },
      'v-indexed': ['name', 'slug'],
      'v-cache': false,
    },
  },
  {
    name: 'b2b_profiles',
    version: 'v0.0.2',
    body: {
      properties: {
        roleId: {
          type: 'string',
          title: 'Role ID',
        },
        features: {
          type: 'array',
          items: {
            type: 'string',
          },
          title: 'Features',
        },
      },
      'v-indexed': ['roleId'],
      'v-cache': false,
    },
  },
  {
    name: 'b2b_users',
    version: 'v0.1.2',
    body: {
      properties: {
        roleId: {
          type: 'string',
          title: 'Role ID',
        },
        name: {
          type: 'string',
          title: 'Name',
        },
        email: {
          type: 'string',
          title: 'Email',
        },
        userId: {
          type: ['null', 'string'],
          title: 'User ID',
        },
        clId: {
          type: ['null', 'string'],
          title: 'CL ID',
        },
        orgId: {
          type: ['null', 'string'],
          title: 'Organization ID',
        },
        costId: {
          type: ['null', 'string'],
          title: 'Cost Center ID',
        },
        canImpersonate: {
          type: 'boolean',
          title: 'Can impersonate',
        },
        active: {
          type: 'boolean',
          title: 'Current Profile active',
        },
        selectedPriceTable: {
          type: ['null', 'string'],
          title: 'Selected Price Table',
        },
      },
      'v-immediate-indexing': true,
      'v-indexed': [
        'userId',
        'clId',
        'roleId',
        'orgId',
        'costId',
        'email',
        'canImpersonate',
        'active',
        'selectedPriceTable',
      ],
      'v-cache': false,
    },
  },
]

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/roleNames.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
const roleNames: any = {
  'en-US': {
    'customer-admin': 'Organization Admin',
    'customer-approver': 'Organization Approver',
    'customer-buyer': 'Organization Buyer',
    'sales-admin': 'Sales Admin',
    'sales-manager': 'Sales Manager',
    'sales-representative': 'Sales Representative',
    'store-admin': 'Store Admin',
  },
}

export default roleNames

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/public/metadata/messages/es-AR.json from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "name": "B2B Admin",
  "headline": "B2B Admin",
  "overview": "B2B Admin",
  "features": ["B2B Admin"],
  "websiteURL": "https://vtex.com/",
  "support": {
    "url": "https://support.vtex.com/hc/requests"
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/public/metadata/messages/pt-BR.json from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "name": "B2B Admin",
  "headline": "B2B Admin",
  "overview": "B2B Admin",
  "features": ["B2B Admin"],
  "websiteURL": "https://vtex.com/",
  "support": {
    "url": "https://support.vtex.com/hc/requests"
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/public/metadata/messages/en-US.json from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "name": "B2B Admin",
  "headline": "B2B Admin",
  "overview": "B2B Admin",
  "features": ["B2B Admin"],
  "websiteURL": "https://vtex.com/",
  "support": {
    "url": "https://support.vtex.com/hc/requests"
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/directives/auditAccess.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { GraphQLField } from 'graphql'
import { defaultFieldResolver } from 'graphql'
import { SchemaDirectiveVisitor } from 'graphql-tools'

import sendAuthMetric, { AuthMetric } from '../metrics/auth'

export class AuditAccess extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field

    field.resolve = async (
      root: any,
      args: any,
      context: Context,
      info: any
    ) => {
      this.sendAuthMetric(field, context)

      return resolve(root, args, context, info)
    }
  }

  private async sendAuthMetric(field: GraphQLField<any, any>, context: any) {
    const {
      vtex: { adminUserAuthToken, storeUserAuthToken, account, logger },
      request,
    } = context

    const operation = field.astNode?.name?.value ?? request.url
    const userAgent = request.headers['user-agent'] as string
    const forwardedHost = request.headers['x-forwarded-host'] as string
    const caller =
      context.vtex.sender ?? (request.headers['x-vtex-caller'] as string)

    const hasAdminToken = !!(
      adminUserAuthToken ?? (context?.headers.vtexidclientautcookie as string)
    )

    const hasStoreToken = !!storeUserAuthToken
    const hasApiToken = !!request.headers['vtex-api-apptoken']

    const authMetric = new AuthMetric(account, {
      caller,
      userAgent,
      forwardedHost,
      hasAdminToken,
      hasApiToken,
      hasStoreToken,
      operation,
    })

    await sendAuthMetric(logger, authMetric)
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/directives/checkUserAccess.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { AuthenticationError, ForbiddenError } from '@vtex/api'
import type { GraphQLField } from 'graphql'
import { defaultFieldResolver } from 'graphql'
import { SchemaDirectiveVisitor } from 'graphql-tools'

import sendAuthMetric, { AuthMetric } from '../metrics/auth'
import {
  validateAdminToken,
  validateAdminTokenOnHeader,
  validateApiToken,
  validateStoreToken,
} from './helper'

export class CheckUserAccess extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field

    field.resolve = async (
      root: any,
      args: any,
      context: Context,
      info: any
    ) => {
      const {
        vtex: { adminUserAuthToken, storeUserAuthToken, logger },
      } = context

      // now we emit a metric with all the collected data before we proceed
      const operation = field?.astNode?.name?.value ?? context?.request?.url
      const userAgent = context?.request?.headers['user-agent'] as string
      const caller = context?.request?.headers['x-vtex-caller'] as string
      const forwardedHost = context?.request?.headers[
        'x-forwarded-host'
      ] as string

      const metricFields = {
        operation,
        forwardedHost,
        caller,
        userAgent,
      }

      const { hasAdminToken, hasValidAdminToken, hasCurrentValidAdminToken } =
        await validateAdminToken(
          context,
          adminUserAuthToken as string,
          metricFields
        )

      const {
        hasAdminTokenOnHeader,
        hasValidAdminTokenOnHeader,
        hasCurrentValidAdminTokenOnHeader,
      } = await validateAdminTokenOnHeader(context, metricFields)

      const { hasApiToken, hasValidApiToken } = await validateApiToken(
        context,
        metricFields
      )

      const { hasStoreToken, hasValidStoreToken, hasCurrentValidStoreToken } =
        await validateStoreToken(context, storeUserAuthToken as string)

      const auditMetric = new AuthMetric(
        context?.vtex?.account,
        {
          operation,
          forwardedHost,
          caller,
          userAgent,
          hasAdminToken,
          hasValidAdminToken,
          hasApiToken,
          hasValidApiToken,
          hasStoreToken,
          hasCurrentValidStoreToken,
          hasValidStoreToken,
          hasAdminTokenOnHeader,
          hasValidAdminTokenOnHeader,
        },
        'CheckUserAccessAudit'
      )

      sendAuthMetric(logger, auditMetric)

      if (
        !hasAdminToken &&
        !hasApiToken &&
        !hasStoreToken &&
        !hasAdminTokenOnHeader
      ) {
        logger.warn({
          message: 'CheckUserAccess: No token provided',
          userAgent,
          caller,
          forwardedHost,
          operation,
          hasAdminToken,
          hasValidAdminToken,
          hasApiToken,
          hasValidApiToken,
          hasStoreToken,
          hasAdminTokenOnHeader,
          hasValidAdminTokenOnHeader,
        })
        throw new AuthenticationError('No token was provided')
      }

      if (
        !hasCurrentValidAdminToken &&
        !hasValidApiToken &&
        !hasCurrentValidStoreToken &&
        !hasCurrentValidAdminTokenOnHeader
      ) {
        logger.warn({
          message: `CheckUserAccess: Invalid token`,
          userAgent,
          caller,
          forwardedHost,
          operation,
          hasAdminToken,
          hasValidAdminToken,
          hasApiToken,
          hasValidApiToken,
          hasStoreToken,
          hasCurrentValidStoreToken,
          hasValidStoreToken,
          hasAdminTokenOnHeader,
          hasValidAdminTokenOnHeader,
        })
        throw new ForbiddenError('Unauthorized Access')
      }

      return resolve(root, args, context, info)
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/directives/withSender.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable max-params */
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { GraphQLField } from 'graphql'
import { defaultFieldResolver } from 'graphql'
import { SchemaDirectiveVisitor } from 'graphql-tools'

export class WithSender extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field

    field.resolve = async (root: any, args: any, context: any, info: any) => {
      context.vtex.sender =
        context?.graphql?.query?.senderApp ??
        context?.graphql?.query?.extensions?.persistedQuery?.sender ??
        context?.request?.header['x-b2b-senderapp'] ??
        null

      return resolve(root, args, context, info)
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/directives/helper.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { isUserPartOfBuyerOrg } from '../resolvers/Queries/Users'
import { LICENSE_MANAGER_ROLES, B2B_LM_PRODUCT_CODE } from '../utils/constants'

export const validateAdminToken = async (
  context: Context,
  adminUserAuthToken: string,
  metricFields: any = {},
  requiredRole: string = LICENSE_MANAGER_ROLES.B2B_ORGANIZATIONS_VIEW
): Promise<{
  hasAdminToken: boolean
  hasValidAdminToken: boolean
  hasCurrentValidAdminToken: boolean
  hasValidAdminRole: boolean
}> => {
  const {
    clients: { identity, lm },
    vtex: { account, logger },
  } = context

  // check if has admin token and if it is valid
  const hasAdminToken = !!adminUserAuthToken
  let hasValidAdminToken = false
  // this is used to check if the token is valid by current standards
  let hasCurrentValidAdminToken = false
  let hasValidAdminRole = false

  if (hasAdminToken) {
    try {
      const authUser = await identity.validateToken({
        token: adminUserAuthToken,
      })

      // we set this flag to true if the token is valid by current standards
      // in the future we should remove this line
      hasCurrentValidAdminToken = true

      if (
        authUser?.audience === 'admin' &&
        authUser &&
        authUser.account !== account
      ) {
        logger.warn({
          message: 'validateAdminToken: Token from another account',
          ...metricFields,
          authUserAccount: authUser.account,
          account,
          user: authUser.user,
        })
      }

      if (authUser?.audience === 'admin' && authUser?.account === account) {
        const hasAdminPermissions = await lm.getUserAdminPermissions(
          account,
          authUser.id
        )

        // Check for specific role using the new endpoint
        const roleCheckResponse = await lm.checkUserSpecificRole(
          account,
          authUser.id,
          B2B_LM_PRODUCT_CODE,
          requiredRole
        )

        hasValidAdminRole = !!roleCheckResponse

        // Only set hasValidAdminToken to true if BOTH admin permissions AND role are valid
        hasValidAdminToken = hasAdminPermissions && hasValidAdminRole
      }
    } catch (err) {
      // noop so we leave hasValidAdminToken as false
      logger.warn({
        message: 'Error validating admin token',
        err,
      })
    }
  }

  return {
    hasAdminToken,
    hasValidAdminToken,
    hasCurrentValidAdminToken,
    hasValidAdminRole,
  }
}

export const validateApiToken = async (
  context: Context,
  metricFields: any = {},
  requiredRole: string = LICENSE_MANAGER_ROLES.B2B_ORGANIZATIONS_VIEW
): Promise<{
  hasApiToken: boolean
  hasValidApiToken: boolean
  hasValidApiRole: boolean
}> => {
  const {
    clients: { identity, lm },
    vtex: { account, logger },
  } = context

  // check if has api token and if it is valid
  const apiToken = context?.headers['vtex-api-apptoken'] as string
  const appKey = context?.headers['vtex-api-appkey'] as string
  const hasApiToken = !!(apiToken?.length && appKey?.length)
  let hasValidApiToken = false

  let hasValidApiRole = false

  if (hasApiToken) {
    try {
      const { token } = await identity.getToken({
        appkey: appKey,
        apptoken: apiToken,
      })

      const authUser = await identity.validateToken({
        token,
      })

      // keeping this behavior for now, but we should remove it in the future as well
      context.cookies.set('VtexIdclientAutCookie', token)
      context.vtex.adminUserAuthToken = token

      if (
        authUser?.audience === 'admin' &&
        authUser &&
        authUser.account !== account
      ) {
        logger.warn({
          message: 'validateApiToken: Token from another account',
          ...metricFields,
          authUserAccount: authUser.account,
          account,
          user: authUser.user,
        })
      }

      if (authUser?.audience === 'admin' && authUser?.account === account) {
        const hasAdminPermissions = await lm.getUserAdminPermissions(
          account,
          authUser.id
        )

        // Check for specific role using the new endpoint
        const roleCheckResponse = await lm.checkUserSpecificRole(
          account,
          authUser.id,
          B2B_LM_PRODUCT_CODE,
          requiredRole
        )

        hasValidApiRole = !!roleCheckResponse

        // Only set hasValidApiToken to true if BOTH admin permissions AND role are valid
        hasValidApiToken = hasAdminPermissions && hasValidApiRole
      }
    } catch (err) {
      // noop so we leave hasValidApiToken as false
      logger.warn({
        message: 'Error validating API token',
        err,
      })
    }
  }

  return {
    hasApiToken,
    hasValidApiToken,
    hasValidApiRole,
  }
}

export const validateStoreToken = async (
  context: Context,
  storeUserAuthToken: string
): Promise<{
  hasStoreToken: boolean
  hasValidStoreToken: boolean
  hasCurrentValidStoreToken: boolean
}> => {
  const {
    clients: { vtexId },
    vtex: { logger },
  } = context

  // check if has store token and if it is valid
  const hasStoreToken = !!storeUserAuthToken
  let hasValidStoreToken = false
  // this is used to check if the token is valid by current standards
  let hasCurrentValidStoreToken = false

  if (hasStoreToken) {
    try {
      const authUser = await vtexId.getAuthenticatedUser(storeUserAuthToken)

      if (authUser?.user) {
        // we set this flag to true if the token is valid by current standards
        // in the future we should remove this line
        hasCurrentValidStoreToken = true

        const userIsPartOfBuyerOrg = await isUserPartOfBuyerOrg(
          authUser?.user,
          context
        )

        if (userIsPartOfBuyerOrg) {
          hasValidStoreToken = true
        }
      }
    } catch (err) {
      // noop so we leave hasValidStoreToken as false
      logger.warn({
        message: 'Error validating store token:',
        err,
      })
    }
  }

  return { hasStoreToken, hasValidStoreToken, hasCurrentValidStoreToken }
}

export const validateAdminTokenOnHeader = async (
  context: Context,
  metricFields: any = {},
  requiredRole: string = LICENSE_MANAGER_ROLES.B2B_ORGANIZATIONS_VIEW
): Promise<{
  hasAdminTokenOnHeader: boolean
  hasValidAdminTokenOnHeader: boolean
  hasCurrentValidAdminTokenOnHeader: boolean
  hasValidAdminRoleOnHeader: boolean
}> => {
  const adminUserAuthToken = context?.headers.vtexidclientautcookie as string
  const hasAdminTokenOnHeader = !!adminUserAuthToken?.length

  if (!hasAdminTokenOnHeader) {
    return {
      hasAdminTokenOnHeader: false,
      hasValidAdminTokenOnHeader: false,
      hasCurrentValidAdminTokenOnHeader: false,
      hasValidAdminRoleOnHeader: false,
    }
  }

  const {
    hasAdminToken,
    hasCurrentValidAdminToken,
    hasValidAdminToken,
    hasValidAdminRole,
  } = await validateAdminToken(
    context,
    adminUserAuthToken,
    metricFields,
    requiredRole
  )

  return {
    hasAdminTokenOnHeader: hasAdminToken,
    hasValidAdminTokenOnHeader: hasValidAdminToken,
    hasCurrentValidAdminTokenOnHeader: hasCurrentValidAdminToken,
    hasValidAdminRoleOnHeader: hasValidAdminRole,
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/directives/validateAdminUserAccess.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { SchemaDirectiveVisitor } from 'graphql-tools'
import { AuthenticationError, ForbiddenError } from '@vtex/api'
import type { GraphQLField } from 'graphql'
import { defaultFieldResolver } from 'graphql'

import type { AuthAuditMetric } from '../metrics/auth'
import sendAuthMetric, { AuthMetric } from '../metrics/auth'
import {
  validateAdminToken,
  validateAdminTokenOnHeader,
  validateApiToken,
} from './helper'
import { LICENSE_MANAGER_ROLES } from '../utils/constants'

export class ValidateAdminUserAccess extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field
    const { adminPermission } = this.args

    field.resolve = async (
      root: any,
      args: any,
      context: Context,
      info: any
    ) => {
      const {
        vtex: { adminUserAuthToken, logger },
      } = context

      // get metrics data
      const operation = field?.astNode?.name?.value ?? context?.request?.url
      const userAgent = context?.request?.headers['user-agent'] as string
      const caller = context?.request?.headers['x-vtex-caller'] as string
      const forwardedHost = context?.request?.headers[
        'x-forwarded-host'
      ] as string

      // set metric fields with initial data
      let metricFields: AuthAuditMetric = {
        operation,
        forwardedHost,
        caller,
        userAgent,
      }

      // Choose role based on parameter
      const requiredRole =
        adminPermission === 'B2B_ORGANIZATIONS_EDIT'
          ? LICENSE_MANAGER_ROLES.B2B_ORGANIZATIONS_EDIT
          : LICENSE_MANAGER_ROLES.B2B_ORGANIZATIONS_VIEW

      const { hasAdminToken, hasValidAdminToken } = await validateAdminToken(
        context,
        adminUserAuthToken as string,
        metricFields,
        requiredRole
      )

      // add admin token metrics
      metricFields = {
        ...metricFields,
        hasAdminToken,
        hasValidAdminToken,
      }

      // allow access if has valid admin token
      if (hasValidAdminToken) {
        sendAuthMetric(
          logger,
          new AuthMetric(
            context?.vtex?.account,
            metricFields,
            'ValidateAdminUserAccessAudit'
          )
        )

        return resolve(root, args, context, info)
      }

      // If there's no valid admin token on context, search for it on header
      const { hasAdminTokenOnHeader, hasValidAdminTokenOnHeader } =
        await validateAdminTokenOnHeader(context, metricFields, requiredRole)

      // add admin header token metrics
      metricFields = {
        ...metricFields,
        hasAdminTokenOnHeader,
        hasValidAdminTokenOnHeader,
      }

      // allow access if has valid admin token
      if (hasValidAdminTokenOnHeader) {
        sendAuthMetric(
          logger,
          new AuthMetric(
            context?.vtex?.account,
            metricFields,
            'ValidateAdminUserAccessAudit'
          )
        )

        return resolve(root, args, context, info)
      }

      const { hasApiToken, hasValidApiToken } = await validateApiToken(
        context,
        metricFields,
        requiredRole
      )

      // add API token metrics
      metricFields = {
        ...metricFields,
        hasApiToken,
        hasValidApiToken,
      }

      // allow access if has valid API token
      if (hasValidApiToken) {
        sendAuthMetric(
          logger,
          new AuthMetric(
            context?.vtex?.account,
            metricFields,
            'ValidateAdminUserAccessAudit'
          )
        )

        return resolve(root, args, context, info)
      }

      // deny access if no tokens were provided
      if (!hasAdminToken && !hasAdminTokenOnHeader && !hasApiToken) {
        logger.warn({
          message: 'ValidateAdminUserAccess: No token provided',
          ...metricFields,
        })
        throw new AuthenticationError('No token was provided')
      }

      // deny access if no valid tokens were provided
      logger.warn({
        message: 'ValidateAdminUserAccess: Invalid token',
        ...metricFields,
      })
      throw new ForbiddenError('Unauthorized Access')
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/directives/validateStoreUserAccess.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { AuthenticationError, ForbiddenError } from '@vtex/api'
import type { GraphQLField } from 'graphql'
import { defaultFieldResolver } from 'graphql'
import { SchemaDirectiveVisitor } from 'graphql-tools'

import type { AuthAuditMetric } from '../metrics/auth'
import sendAuthMetric, { AuthMetric } from '../metrics/auth'
import {
  validateAdminToken,
  validateAdminTokenOnHeader,
  validateApiToken,
  validateStoreToken,
} from './helper'
import { LICENSE_MANAGER_ROLES } from '../utils/constants'

export class ValidateStoreUserAccess extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field
    const { adminPermission } = this.args

    field.resolve = async (
      root: any,
      args: any,
      context: Context,
      info: any
    ) => {
      const {
        vtex: { adminUserAuthToken, storeUserAuthToken, logger },
      } = context

      // get metrics data
      const operation = field?.astNode?.name?.value ?? context?.request?.url
      const userAgent = context?.request?.headers['user-agent'] as string
      const caller = context?.request?.headers['x-vtex-caller'] as string
      const forwardedHost = context?.request?.headers[
        'x-forwarded-host'
      ] as string

      // set metric fields with initial data
      let metricFields: AuthAuditMetric = {
        operation,
        forwardedHost,
        caller,
        userAgent,
      }

      // Choose role based on parameter
      const requiredRole =
        adminPermission === 'B2B_ORGANIZATIONS_EDIT'
          ? LICENSE_MANAGER_ROLES.B2B_ORGANIZATIONS_EDIT
          : LICENSE_MANAGER_ROLES.B2B_ORGANIZATIONS_VIEW

      const { hasAdminToken, hasValidAdminToken } = await validateAdminToken(
        context,
        adminUserAuthToken as string,
        metricFields,
        requiredRole
      )

      // add admin token metrics
      metricFields = {
        ...metricFields,
        hasAdminToken,
        hasValidAdminToken,
      }

      // allow access if has valid admin token
      if (hasValidAdminToken) {
        sendAuthMetric(
          logger,
          new AuthMetric(
            context?.vtex?.account,
            metricFields,
            'ValidateStoreUserAccessAudit'
          )
        )

        return resolve(root, args, context, info)
      }

      // If there's no valid admin token on context, search for it on header
      const { hasAdminTokenOnHeader, hasValidAdminTokenOnHeader } =
        await validateAdminTokenOnHeader(context, metricFields, requiredRole)

      // add admin header token metrics
      metricFields = {
        ...metricFields,
        hasAdminTokenOnHeader,
        hasValidAdminTokenOnHeader,
      }

      // allow access if has valid admin token
      if (hasValidAdminTokenOnHeader) {
        sendAuthMetric(
          logger,
          new AuthMetric(
            context?.vtex?.account,
            metricFields,
            'ValidateStoreUserAccessAudit'
          )
        )

        return resolve(root, args, context, info)
      }

      const { hasApiToken, hasValidApiToken } = await validateApiToken(
        context,
        metricFields,
        requiredRole
      )

      // add API token metrics
      metricFields = {
        ...metricFields,
        hasApiToken,
        hasValidApiToken,
      }

      // allow access if has valid API token
      if (hasValidApiToken) {
        sendAuthMetric(
          logger,
          new AuthMetric(
            context?.vtex?.account,
            metricFields,
            'ValidateStoreUserAccessAudit'
          )
        )

        return resolve(root, args, context, info)
      }

      const { hasStoreToken, hasValidStoreToken } = await validateStoreToken(
        context,
        storeUserAuthToken as string
      )

      // add store token metrics
      metricFields = {
        ...metricFields,
        hasStoreToken,
        hasValidStoreToken,
      }

      // allow access if has valid store token
      if (hasValidStoreToken) {
        sendAuthMetric(
          logger,
          new AuthMetric(
            context?.vtex?.account,
            metricFields,
            'ValidateStoreUserAccessAudit'
          )
        )

        return resolve(root, args, context, info)
      }

      // deny access if no tokens were provided
      if (!hasAdminToken && !hasApiToken && !hasStoreToken) {
        logger.warn({
          message: 'ValidateStoreUserAccess: No token provided',
          ...metricFields,
        })
        throw new AuthenticationError('No token was provided')
      }

      // deny access if no valid tokens were provided
      logger.warn({
        message: `ValidateStoreUserAccess: Invalid token`,
        ...metricFields,
      })
      throw new ForbiddenError('Unauthorized Access')
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/directives/checkAdminAccess.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { SchemaDirectiveVisitor } from 'graphql-tools'
import { AuthenticationError, ForbiddenError } from '@vtex/api'
import type { GraphQLField } from 'graphql'
import { defaultFieldResolver } from 'graphql'

import sendAuthMetric, { AuthMetric } from '../metrics/auth'
import {
  validateAdminToken,
  validateAdminTokenOnHeader,
  validateApiToken,
} from './helper'

export class CheckAdminAccess extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field

    field.resolve = async (
      root: any,
      args: any,
      context: Context,
      info: any
    ) => {
      const {
        vtex: { adminUserAuthToken, storeUserAuthToken, logger },
      } = context

      // now we emit a metric with all the collected data before we proceed
      const operation = field.astNode?.name?.value ?? context.request.url
      const userAgent = context?.request?.headers['user-agent'] as string
      const caller = context?.request?.headers['x-vtex-caller'] as string
      const forwardedHost = context?.request?.headers[
        'x-forwarded-host'
      ] as string

      const metricFields = {
        operation,
        forwardedHost,
        caller,
        userAgent,
      }

      const { hasAdminToken, hasValidAdminToken, hasCurrentValidAdminToken } =
        await validateAdminToken(
          context,
          adminUserAuthToken as string,
          metricFields
        )

      const {
        hasAdminTokenOnHeader,
        hasValidAdminTokenOnHeader,
        hasCurrentValidAdminTokenOnHeader,
      } = await validateAdminTokenOnHeader(context, metricFields)

      const { hasApiToken, hasValidApiToken } = await validateApiToken(
        context,
        metricFields
      )

      const hasStoreToken = !!storeUserAuthToken // we don't need to validate store token

      const auditMetric = new AuthMetric(
        context?.vtex?.account,
        {
          operation,
          forwardedHost,
          caller,
          userAgent,
          hasAdminToken,
          hasValidAdminToken,
          hasApiToken,
          hasValidApiToken,
          hasStoreToken,
          hasAdminTokenOnHeader,
          hasValidAdminTokenOnHeader,
        },
        'CheckAdminAccessAudit'
      )

      sendAuthMetric(logger, auditMetric)

      if (!hasAdminToken && !hasApiToken && !hasAdminTokenOnHeader) {
        logger.warn({
          message: 'CheckAdminAccess: No token provided',
          userAgent,
          caller,
          forwardedHost,
          operation,
          hasAdminToken,
          hasValidAdminToken,
          hasApiToken,
          hasValidApiToken,
          hasStoreToken,
          hasAdminTokenOnHeader,
          hasValidAdminTokenOnHeader,
        })
        throw new AuthenticationError('No token was provided')
      }

      if (
        !hasCurrentValidAdminToken &&
        !hasValidApiToken &&
        !hasCurrentValidAdminTokenOnHeader
      ) {
        logger.warn({
          message: 'CheckAdminAccess: Invalid token',
          userAgent,
          caller,
          forwardedHost,
          operation,
          hasAdminToken,
          hasValidAdminToken,
          hasApiToken,
          hasValidApiToken,
          hasStoreToken,
          hasAdminTokenOnHeader,
          hasValidAdminTokenOnHeader,
        })
        throw new ForbiddenError('Unauthorized Access')
      }

      return resolve(root, args, context, info)
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/directives/index.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { WithSession } from './withSession'
import { WithSender } from './withSender'
import { WithUserPermissions } from './withUserPermissions'
import { CheckAdminAccess } from './checkAdminAccess'
import { CheckUserAccess } from './checkUserAccess'
import { ValidateAdminUserAccess } from './validateAdminUserAccess'
import { ValidateStoreUserAccess } from './validateStoreUserAccess'
import { AuditAccess } from './auditAccess'

export const schemaDirectives = {
  checkAdminAccess: CheckAdminAccess as any,
  checkUserAccess: CheckUserAccess as any,
  validateAdminUserAccess: ValidateAdminUserAccess as any,
  validateStoreUserAccess: ValidateStoreUserAccess as any,
  withSession: WithSession,
  withSender: WithSender,
  withUserPermissions: WithUserPermissions,
  auditAccess: AuditAccess as any,
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/directives/withSession.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-params */
import type { GraphQLField } from 'graphql'
import { defaultFieldResolver } from 'graphql'
import { SchemaDirectiveVisitor } from 'graphql-tools'

import sendSessionMetric, { SessionMetric } from '../metrics/session'

export class WithSession extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field

    field.resolve = async (root: any, args: any, context: any, info: any) => {
      const {
        clients: { session, logger },
      } = context

      const token =
        context.vtex.sessionToken ?? context.request.header?.sessiontoken

      context.vtex.sessionData = await session
        .getSession(token as string, ['*'])
        .then((currentSession: any) => {
          return currentSession.sessionData
        })
        .catch(() => null)

      // we emit a metric for cases where the sessionData is null
      // so we can identify use cases that are supposed to have sessionData
      // but do not have it. We currently have a high volume of logs generated
      // by such cases, so we need to identify and fix them.
      const operation = field.astNode?.name?.value ?? context.request.url
      const userAgent = context?.request?.headers['user-agent'] as string
      const caller = context?.request?.headers['x-vtex-caller'] as string
      const forwardedHost = context?.request?.headers[
        'x-forwarded-host'
      ] as string

      const hasSessionToken = !!context.vtex.sessionToken
      const hasSessionTokenOnHeader = !!context.request.header?.sessiontoken
      const hasSessionData = !!context.vtex.sessionData

      const auditMetric = new SessionMetric(context?.vtex?.account, {
        operation,
        forwardedHost,
        caller,
        userAgent,
        hasSessionToken,
        hasSessionTokenOnHeader,
        hasSessionData,
      })

      sendSessionMetric(logger, auditMetric)

      return resolve(root, args, context, info)
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/directives/withUserPermissions.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-params */
import type { GraphQLField } from 'graphql'
import { defaultFieldResolver } from 'graphql'
import { SchemaDirectiveVisitor } from 'graphql-tools'

import { checkUserPermission } from '../resolvers/Queries/Users'

export class WithUserPermissions extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field

    field.resolve = async (root: any, args: any, context: any, info: any) => {
      const {
        clients: { session },
      } = context

      context.vtex.sender = context?.graphql?.query?.senderApp ?? null
      context.vtex.sessionData = await session
        .getSession(context.vtex.sessionToken as string, ['*'])
        .then((currentSession: any) => {
          return currentSession.sessionData
        })
        .catch(() => null)

      context.vtex.userPermissions = await checkUserPermission(
        null,
        { skipError: true },
        context
      )

      return resolve(root, args, context, info)
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/metrics/session.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { Logger } from '@vtex/api/lib/service/logger/logger'

import type { Metric } from '../clients/metrics'
import { B2B_METRIC_NAME, sendMetric } from '../clients/metrics'

export interface SessionAuditMetric {
  operation: string
  forwardedHost: string
  caller: string
  userAgent: string
  hasSessionToken: boolean
  hasSessionTokenOnHeader: boolean
  hasSessionData: boolean
}

export class SessionMetric implements Metric {
  public readonly description: string
  public readonly kind: string
  public readonly account: string
  public readonly fields: SessionAuditMetric
  public readonly name = B2B_METRIC_NAME
  public error?: string

  constructor(account: string, fields: SessionAuditMetric) {
    this.account = account
    this.fields = fields
    this.kind = 'b2b-storefront-permissions-session-event'
    this.description = 'Session metric event'
  }
}

const sendSessionMetric = async (
  logger: Logger,
  sessionMetric: SessionMetric
) => {
  try {
    await sendMetric(sessionMetric)
  } catch (error) {
    logger.error({
      error,
      message: `Error to send metrics from session metric`,
    })
  }
}

export default sendSessionMetric

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/metrics/auth.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { Logger } from '@vtex/api/lib/service/logger/logger'

import type { Metric } from '../clients/metrics'
import { B2B_METRIC_NAME, sendMetric } from '../clients/metrics'

export interface AuthAuditMetric {
  operation: string
  forwardedHost: string
  caller: string
  userAgent: string
  role?: string
  permissions?: string[]
  hasAdminToken?: boolean
  hasValidAdminToken?: boolean
  hasStoreToken?: boolean
  hasCurrentValidStoreToken?: boolean
  hasValidStoreToken?: boolean
  hasApiToken?: boolean
  hasValidApiToken?: boolean
  hasAdminTokenOnHeader?: boolean
  hasValidAdminTokenOnHeader?: boolean
}

export class AuthMetric implements Metric {
  public readonly description: string
  public readonly kind: string
  public readonly account: string
  public readonly fields: AuthAuditMetric
  public readonly name = B2B_METRIC_NAME
  public error?: string

  constructor(account: string, fields: AuthAuditMetric, description?: string) {
    this.account = account
    this.fields = fields
    this.kind = 'b2b-storefront-permissions-auth-event'
    this.description = description ?? 'Auth metric event'
  }
}

const sendAuthMetric = async (logger: Logger, authMetric: AuthMetric) => {
  try {
    await sendMetric(authMetric)
  } catch (error) {
    logger.error({
      error,
      message: `Error to send metrics from auth metric`,
    })
  }
}

export default sendAuthMetric

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/clients/schema.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { JanusClient } from '@vtex/api'

import { getTokenToHeader } from './index'

const getRouteSchema = (dataEntity: string) =>
  `/api/dataentities/${dataEntity}/schemas`

export class Schema extends JanusClient {
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super(ctx, {
      ...options,
      headers: {
        ...options?.headers,
        ...getTokenToHeader(ctx),
        'x-vtex-user-agent': ctx.userAgent,
      },
    })
  }

  public async getLatestSchema(entityName: string) {
    const schemas = await this.http.get(getRouteSchema(entityName))

    return schemas && schemas.length > 0 ? schemas[schemas.length - 1] : null
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/clients/Organizations.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { GraphQLResponse, InstanceOptions, IOContext } from '@vtex/api'
import { AppGraphQLClient } from '@vtex/api'

import { QUERIES } from '../resolvers/Routes/utils'
import { getTokenToHeader } from './index'
import type {
  GetCostCenterType,
  GetOrganizationsByEmailResponse,
  GetOrganizationsPaginatedByEmailResponse,
} from '../typings/custom'

const getPersistedQuery = () => {
  return {
    persistedQuery: {
      provider: 'vtex.b2b-organizations-graphql@2.x',
      sender: 'vtex.storefront-permissions@3.x',
    },
  }
}

export class OrganizationsGraphQLClient extends AppGraphQLClient {
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super('vtex.b2b-organizations-graphql@2.x', ctx, options)
  }

  public getOrganizationById = async (orgId: string): Promise<unknown> => {
    return this.query({
      extensions: getPersistedQuery(),
      query: QUERIES.getOrganizationById,
      variables: {
        id: orgId,
      },
    })
  }

  public getB2BSettings = async (): Promise<unknown> => {
    return this.query({
      extensions: getPersistedQuery(),
      query: QUERIES.getB2BSettings,
      variables: {},
    })
  }

  public getCostCenterById = async (costId: string) => {
    return this.query({
      extensions: getPersistedQuery(),
      query: QUERIES.getCostCenterById,
      variables: {
        id: costId,
      },
    }) as Promise<GraphQLResponse<GetCostCenterType>>
  }

  public getMarketingTags = async (costId: string): Promise<unknown> => {
    return this.query({
      extensions: getPersistedQuery(),
      query: QUERIES.getMarketingTags,
      variables: {
        costId,
      },
    })
  }

  public getOrganizationsByEmail = async (email: string) => {
    return this.query({
      extensions: getPersistedQuery(),
      query: QUERIES.getOrganizationsByEmail,
      variables: { email },
    }) as Promise<GetOrganizationsByEmailResponse>
  }

  public getOrganizationsPaginatedByEmail = async (
    email: string,
    page: number,
    pageSize: number
  ) => {
    return this.query({
      extensions: getPersistedQuery(),
      query: QUERIES.getOrganizationsPaginatedByEmail,
      variables: {
        email,
        page,
        pageSize,
      },
    }) as Promise<{
      data: {
        getOrganizationsPaginatedByEmail: GetOrganizationsPaginatedByEmailResponse
      }
    }>
  }

  private query = async (param: {
    query: string
    variables: any
    extensions: any
  }) => {
    const { query, variables, extensions } = param

    return this.graphql.query(
      { query, variables, extensions },
      {
        headers: getTokenToHeader(this.context),
        params: {
          locale: this.context.locale,
        },
      }
    )
  }
}

```