-
Notifications
You must be signed in to change notification settings - Fork 8
CEXT-6151: Add ACL admin UI schema and helper to check permissions #426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0ef0a94
a182244
ea14acd
dcc5c57
9342478
02f79b2
cef1831
6cf97a8
f83ad7a
16efa4c
14c8d2a
38c6595
407d8cf
f42a608
92f523d
a17ddd4
d33ae24
8142c69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "@adobe/aio-commerce-lib-admin-ui-sdk": minor | ||
| "@adobe/aio-commerce-sdk": minor | ||
| --- | ||
|
|
||
| Add `@adobe/aio-commerce-lib-admin-ui-sdk` library for checking Admin UI SDK ACL resource permissions. | ||
| Add Admin UI SDK API exports to `@adobe/aio-commerce-sdk`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@adobe/aio-commerce-lib-app": minor | ||
| --- | ||
|
|
||
| Add optional `aclResource` support to Admin UI SDK menu item configuration. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| { | ||
| "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json", | ||
|
|
||
| "root": false, | ||
| "extends": "//", | ||
|
|
||
| "overrides": [ | ||
| { | ||
| "includes": ["source/**/endpoints.ts"], | ||
| "linter": { | ||
| "rules": { | ||
| "suspicious": { | ||
| // Endpoint functions must be async for consistent error handling — | ||
| // sync throws would not be catchable via .catch() on the returned Promise. | ||
| "useAwait": "off" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| # `@adobe/aio-commerce-lib-admin-ui-sdk` Documentation | ||
|
|
||
| ## Overview | ||
|
|
||
| This package provides utilities for interacting with the Admin UI SDK API: | ||
|
|
||
| - **[Permission Checking](#permission-checking)**: Check whether the current user has access to an ACL resource, with built-in caching and request deduplication | ||
| - **[API Client](#api-client)**: Create typed HTTP clients for the Admin UI SDK API | ||
|
|
||
| ## API Reference | ||
|
|
||
| For a complete list of all available types, functions, and classes, see the [API Reference](./api-reference/README.md). | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### Permission Checking | ||
|
|
||
| Use `getAdminUiSdkPermissionClient` to verify whether the current user has been granted a given ACL resource. Results are cached for 5 minutes by default, and concurrent requests for the same resource are deduplicated automatically. | ||
|
|
||
| ```typescript | ||
| import { AdobeCommerceHttpClient } from "@adobe/aio-commerce-lib-api"; | ||
| import { getAdminUiSdkPermissionClient } from "@adobe/aio-commerce-lib-admin-ui-sdk/api"; | ||
|
|
||
| const httpClient = new AdobeCommerceHttpClient({ | ||
| /* CommerceHttpClientParams */ | ||
| }); | ||
| const permissions = getAdminUiSdkPermissionClient({ httpClient }); | ||
|
|
||
| // Returns true if granted, false if denied or on error (fail-closed by default) | ||
| const allowed = await permissions.check("Vendor_Module::resource_name"); | ||
|
|
||
| // Throws AdminUiSdkPermissionDeniedError if denied, or AdminUiSdkPermissionError if the check fails | ||
| await permissions.require("Vendor_Module::resource_name"); | ||
|
|
||
| // Invalidate a cached result (e.g. after a role change) | ||
| permissions.invalidate("Vendor_Module::resource_name"); | ||
|
|
||
| // Invalidate all cached results | ||
| permissions.invalidate(); | ||
| ``` | ||
|
|
||
| By default, `check()` fails closed and returns `false` on network or response parsing errors. To opt out of fail-closed behavior for `check()` and receive the underlying error: | ||
|
|
||
| ```typescript | ||
| const permissions = getAdminUiSdkPermissionClient({ | ||
| httpClient, | ||
| denyOnError: false, | ||
| }); | ||
| ``` | ||
|
|
||
| To adjust the cache TTL or disable caching entirely: | ||
|
|
||
| ```typescript | ||
| const permissions = getAdminUiSdkPermissionClient({ | ||
| httpClient, | ||
| cacheTtlMs: 60_000, // 1 minute | ||
| }); | ||
|
|
||
| const permissionsNoCache = getAdminUiSdkPermissionClient({ | ||
| httpClient, | ||
| cacheTtlMs: 0, // disabled | ||
| }); | ||
| ``` | ||
|
|
||
| #### Error handling | ||
|
|
||
| ```typescript | ||
| import { | ||
| AdminUiSdkPermissionError, | ||
| AdminUiSdkPermissionDeniedError, | ||
| } from "@adobe/aio-commerce-lib-admin-ui-sdk/api"; | ||
|
|
||
| try { | ||
| await permissions.require("Vendor_Module::resource_name"); | ||
| } catch (error) { | ||
| if (error instanceof AdminUiSdkPermissionDeniedError) { | ||
| console.error(`Access denied for resource: ${error.resource}`); | ||
| } else if (error instanceof AdminUiSdkPermissionError) { | ||
| // Unauthorized, network, or response parsing error | ||
| console.error("Permission check failed", error); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### API Client | ||
|
|
||
| Use `createAdminUiSdkApiClient` for full access to the Admin UI SDK API: | ||
|
|
||
| ```typescript | ||
| import { createAdminUiSdkApiClient } from "@adobe/aio-commerce-lib-admin-ui-sdk/api"; | ||
|
|
||
| const client = createAdminUiSdkApiClient({ | ||
| baseUrl: "https://commerce.example.com", | ||
| // ...other CommerceHttpClientParams | ||
| }); | ||
| ``` | ||
|
|
||
| In install/uninstall actions where only a subset of operations is needed, prefer `createCustomAdminUiSdkApiClient` to keep the bundle lean: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While I understand this is to be more performant I don't think it is an issue in the app-management endpoints since they're not in the critical path of the applications. Therefore I'd remove it for the sake of simplicity. |
||
|
|
||
| ```typescript | ||
| import { | ||
| createCustomAdminUiSdkApiClient, | ||
| registerExtension, | ||
| unregisterExtension, | ||
| } from "@adobe/aio-commerce-lib-admin-ui-sdk/api"; | ||
|
|
||
| const client = createCustomAdminUiSdkApiClient(params, { | ||
| registerExtension, | ||
| unregisterExtension, | ||
| }); | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| { | ||
| "name": "@adobe/aio-commerce-lib-admin-ui-sdk", | ||
| "type": "module", | ||
| "author": "Adobe Inc.", | ||
| "version": "0.0.0", | ||
| "private": false, | ||
| "engines": { | ||
| "node": ">=22 <=24" | ||
| }, | ||
| "license": "Apache-2.0", | ||
| "description": "A library to interact with the Adobe Commerce Admin UI SDK API", | ||
| "keywords": [ | ||
| "aio", | ||
| "adobe-io", | ||
| "commerce", | ||
| "adobe-commerce", | ||
| "adobe-commerce-sdk", | ||
| "aio-commerce-sdk", | ||
| "admin-ui-sdk", | ||
| "app-builder" | ||
| ], | ||
| "bugs": { | ||
| "url": "https://github.com/adobe/aio-commerce-sdk/issues" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/adobe/aio-commerce-sdk.git", | ||
| "directory": "packages/aio-commerce-lib-admin-ui-sdk" | ||
| }, | ||
| "publishConfig": { | ||
| "exports": { | ||
| "./api": { | ||
| "import": { | ||
| "types": "./dist/es/index.d.mts", | ||
| "default": "./dist/es/index.mjs" | ||
| }, | ||
| "require": { | ||
| "types": "./dist/cjs/index.d.cts", | ||
| "default": "./dist/cjs/index.cjs" | ||
| } | ||
| }, | ||
| "./package.json": "./package.json" | ||
| } | ||
| }, | ||
| "exports": { | ||
| "./api": "./source/index.ts", | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "imports": { | ||
| "#*": "./source/*.ts", | ||
| "#test/*": "./test/*.ts" | ||
| }, | ||
| "files": [ | ||
| "dist", | ||
| "package.json", | ||
| "CHANGELOG.md", | ||
| "README.md" | ||
| ], | ||
| "scripts": { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing docs script and @aio-commerce-sdk/config-typedoc devDependency |
||
| "publint": "publint", | ||
| "build": "tsdown", | ||
| "prepack": "sdk-prepack", | ||
| "pack": "pnpm pack", | ||
| "postpack": "sdk-postpack", | ||
| "assist": "biome check --formatter-enabled=false --linter-enabled=false --assist-enabled=true --no-errors-on-unmatched", | ||
| "assist:apply": "biome check --write --formatter-enabled=false --linter-enabled=false --assist-enabled=true --no-errors-on-unmatched", | ||
| "check:ci": "biome ci --formatter-enabled=true --linter-enabled=true --assist-enabled=true --no-errors-on-unmatched", | ||
| "code:fix": "pnpm run lint:fix && pnpm run assist:apply && pnpm run format && pnpm run format:markdown", | ||
| "format": "biome format --write --no-errors-on-unmatched", | ||
| "format:markdown": "prettier --no-error-on-unmatched-pattern --write '**/*.md' \"!**/{CODE_OF_CONDUCT.md,COPYRIGHT,LICENSE,SECURITY.md,CONTRIBUTING.md}\"", | ||
| "format:check": "biome format --no-errors-on-unmatched", | ||
| "lint": "biome lint --no-errors-on-unmatched", | ||
| "lint:fix": "biome lint --write --no-errors-on-unmatched", | ||
| "typecheck": "tsc --noEmit && echo '✅ No type errors found.'", | ||
| "test": "vitest run --coverage", | ||
| "test:ui": "vitest --ui --coverage", | ||
| "test:watch": "vitest --watch --coverage" | ||
| }, | ||
| "dependencies": { | ||
| "@adobe/aio-commerce-lib-api": "workspace:*", | ||
| "@adobe/aio-commerce-lib-core": "workspace:*", | ||
| "ky": "catalog:", | ||
| "valibot": "catalog:" | ||
| }, | ||
| "devDependencies": { | ||
| "@aio-commerce-sdk/common-utils": "workspace:*", | ||
| "@aio-commerce-sdk/config-tsdown": "workspace:*", | ||
| "@aio-commerce-sdk/config-typescript": "workspace:*", | ||
| "@aio-commerce-sdk/config-vitest": "workspace:*", | ||
| "@aio-commerce-sdk/scripting-utils": "workspace:*", | ||
| "@aio-commerce-sdk/scripts": "workspace:*", | ||
| "msw": "catalog:", | ||
| "typescript": "catalog:" | ||
| }, | ||
| "sideEffects": false | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| /* | ||
| * Copyright 2026 Adobe. All rights reserved. | ||
| * This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. You may obtain a copy | ||
| * of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software distributed under | ||
| * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
| * OF ANY KIND, either express or implied. See the License for the specific language | ||
| * governing permissions and limitations under the License. | ||
| */ | ||
|
|
||
| import { parseOrThrow } from "@aio-commerce-sdk/common-utils/valibot"; | ||
|
|
||
| import { | ||
| ExtensionRegistrationParamsSchema, | ||
| UnregisterExtensionParamsSchema, | ||
| } from "./schema"; | ||
|
|
||
| import type { AdobeCommerceHttpClient } from "@adobe/aio-commerce-lib-api"; | ||
| import type { Options } from "ky"; | ||
| import type { | ||
| ExtensionRegistrationParams, | ||
| UnregisterExtensionParams, | ||
| } from "./schema"; | ||
|
|
||
| /** | ||
| * Registers an Admin UI SDK extension with Commerce via POST /V1/adminuisdk/extension. | ||
| * | ||
| * @param httpClient - The {@link AdobeCommerceHttpClient} to use to make the request. | ||
| * @param params - The extension registration parameters. | ||
| * @param fetchOptions - Optional Ky fetch options. | ||
| * | ||
| * @throws An `HTTPError` if the status code is not 2XX. | ||
| */ | ||
| export async function registerExtension( | ||
| httpClient: AdobeCommerceHttpClient, | ||
| params: ExtensionRegistrationParams, | ||
| fetchOptions?: Options, | ||
| ): Promise<void> { | ||
| const extension = parseOrThrow(ExtensionRegistrationParamsSchema, params); | ||
|
|
||
| await httpClient.post("adminuisdk/extension", { | ||
| ...fetchOptions, | ||
| json: { extension }, | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Unregisters an Admin UI SDK extension from Commerce via DELETE /V1/adminuisdk/extension/{workspaceName}/{extensionName}. | ||
| * | ||
| * @param httpClient - The {@link AdobeCommerceHttpClient} to use to make the request. | ||
| * @param params - The workspace and extension names. | ||
| * @param fetchOptions - Optional Ky fetch options. | ||
| * | ||
| * @throws An `HTTPError` if the status code is not 2XX. | ||
| */ | ||
| export async function unregisterExtension( | ||
| httpClient: AdobeCommerceHttpClient, | ||
| params: UnregisterExtensionParams, | ||
| fetchOptions?: Options, | ||
| ): Promise<void> { | ||
| const { workspaceName, extensionName } = parseOrThrow( | ||
| UnregisterExtensionParamsSchema, | ||
| params, | ||
| ); | ||
|
|
||
| await httpClient.delete( | ||
| `adminuisdk/extension/${workspaceName}/${extensionName}`, | ||
| fetchOptions, | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| /* | ||
| * Copyright 2026 Adobe. All rights reserved. | ||
| * This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. You may obtain a copy | ||
| * of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software distributed under | ||
| * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
| * OF ANY KIND, either express or implied. See the License for the specific language | ||
| * governing permissions and limitations under the License. | ||
| */ | ||
|
|
||
| import * as v from "valibot"; | ||
|
|
||
| /** Parameters for POST /V1/adminuisdk/extension. */ | ||
| export const ExtensionRegistrationParamsSchema = v.object({ | ||
| extensionName: v.string(), | ||
| extensionTitle: v.string(), | ||
| extensionUrl: v.string(), | ||
| extensionWorkspace: v.string(), | ||
| }); | ||
|
|
||
| /** Parameters for DELETE /V1/adminuisdk/extension/{workspaceName}/{extensionName}. */ | ||
| export const UnregisterExtensionParamsSchema = v.object({ | ||
| workspaceName: v.string(), | ||
| extensionName: v.string(), | ||
| }); | ||
|
|
||
| /** The parameters accepted by POST /V1/adminuisdk/extension. */ | ||
| export type ExtensionRegistrationParams = v.InferInput< | ||
| typeof ExtensionRegistrationParamsSchema | ||
| >; | ||
|
|
||
| /** The parameters accepted by DELETE /V1/adminuisdk/extension/{workspaceName}/{extensionName}. */ | ||
| export type UnregisterExtensionParams = v.InferInput< | ||
| typeof UnregisterExtensionParamsSchema | ||
| >; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| /* | ||
| * Copyright 2026 Adobe. All rights reserved. | ||
| * This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. You may obtain a copy | ||
| * of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software distributed under | ||
| * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
| * OF ANY KIND, either express or implied. See the License for the specific language | ||
| * governing permissions and limitations under the License. | ||
| */ | ||
|
|
||
| /** biome-ignore-all lint/performance/noBarrelFile: api sub-barrel */ | ||
|
|
||
| export * from "./extensions/endpoints"; | ||
| export * from "./permissions/endpoints"; | ||
|
|
||
| export type * from "./extensions/schema"; | ||
| export type * from "./permissions/schema"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we add experimental warning