Expose headers file. Added AppPermissions extration into AppBridge (#265)

* Expose headers file. Added AppPermissions extration into appbridg

* fix imports
This commit is contained in:
Lukasz Ostrowski 2023-07-24 13:45:57 +02:00 committed by GitHub
parent 0b378f19ff
commit b8935a84c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 83 additions and 2 deletions

View file

@ -0,0 +1,5 @@
---
"@saleor/app-sdk": minor
---
Added "appPermissions" field to AppBridgeState. Now, when app is mounted and handshake is complete, app will automatically extract permissions and save them.

View file

@ -0,0 +1,5 @@
---
"@saleor/app-sdk": minor
---
Exposed "@saleor/app-sdk/headers" path. It contains helper methods: getSaleorHeaders and getBaseUrl

View file

@ -50,6 +50,21 @@ type AppBridgeState = {
*/
saleorVersion?: string;
dashboardVersion?: string;
user?: {
/**
* Original permissions of the user that is using the app.
* *Not* the same permissions as the app itself.
*
* Can be used by app to check if user is authorized to perform
* domain specific actions
*/
permissions: Permission[];
email: string;
};
/**
* Permissions of the app itself
*/
appPermissions?: AppPermission[];
};
```

View file

@ -136,6 +136,11 @@
"types": "./verify-signature.d.ts",
"import": "./verify-signature.mjs",
"require": "./verify-signature.js"
},
"./headers": {
"types": "./headers.d.ts",
"import": "./headers.mjs",
"require": "./headers.js"
}
},
"publishConfig": {

View file

@ -1,5 +1,5 @@
import { LocaleCode } from "../locales";
import { Permission } from "../types";
import { AppPermission, Permission } from "../types";
import { ThemeType } from "./events";
export type AppBridgeState = {
@ -28,6 +28,7 @@ export type AppBridgeState = {
permissions: Permission[];
email: string;
};
appPermissions?: AppPermission[];
};
type Options = {

View file

@ -19,6 +19,9 @@ const domain = "saleor.domain.host";
const validJwtToken =
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk4ZTEzNDk4YmM5NThjM2QyNzk2NjY5Zjk0NzYxMzZkIn0.eyJpYXQiOjE2NjkxOTE4NDUsIm93bmVyIjoic2FsZW9yIiwiaXNzIjoiZGVtby5ldS5zYWxlb3IuY2xvdWQiLCJleHAiOjE2NjkyNzgyNDUsInRva2VuIjoic2JsRmVrWnVCSUdXIiwiZW1haWwiOiJhZG1pbkBleGFtcGxlLmNvbSIsInR5cGUiOiJ0aGlyZHBhcnR5IiwidXNlcl9pZCI6IlZYTmxjam95TWc9PSIsImlzX3N0YWZmIjp0cnVlLCJhcHAiOiJRWEJ3T2pJM05RPT0iLCJwZXJtaXNzaW9ucyI6W10sInVzZXJfcGVybWlzc2lvbnMiOlsiTUFOQUdFX1BBR0VfVFlQRVNfQU5EX0FUVFJJQlVURVMiLCJNQU5BR0VfUFJPRFVDVF9UWVBFU19BTkRfQVRUUklCVVRFUyIsIk1BTkFHRV9ESVNDT1VOVFMiLCJNQU5BR0VfUExVR0lOUyIsIk1BTkFHRV9TVEFGRiIsIk1BTkFHRV9QUk9EVUNUUyIsIk1BTkFHRV9TSElQUElORyIsIk1BTkFHRV9UUkFOU0xBVElPTlMiLCJNQU5BR0VfT0JTRVJWQUJJTElUWSIsIk1BTkFHRV9VU0VSUyIsIk1BTkFHRV9BUFBTIiwiTUFOQUdFX0NIQU5ORUxTIiwiTUFOQUdFX0dJRlRfQ0FSRCIsIkhBTkRMRV9QQVlNRU5UUyIsIklNUEVSU09OQVRFX1VTRVIiLCJNQU5BR0VfU0VUVElOR1MiLCJNQU5BR0VfUEFHRVMiLCJNQU5BR0VfTUVOVVMiLCJNQU5BR0VfQ0hFQ0tPVVRTIiwiSEFORExFX0NIRUNLT1VUUyIsIk1BTkFHRV9PUkRFUlMiXX0.PUyvuUlDvUBXMGSaexusdlkY5wF83M8tsjefVXOknaKuVgLbafvLOgx78YGVB4kdAybC7O3Yjs7IIFOzz5U80Q";
const validTokenWithAppPermissions =
"eyJhbGciOiJSUzI1NiIsImtpZCI6IjEiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE2ODk4NTIyOTEsIm93bmVyIjoic2FsZW9yIiwiaXNzIjoiaHR0cHM6Ly9oYWNrYXRob24tc2hpcHBpbmcuZXUuc2FsZW9yLmNsb3VkL2dyYXBocWwvIiwiZXhwIjoxNjg5OTM4NjkxLCJ0b2tlbiI6IjNHTWRUSVpab3FRSSIsImVtYWlsIjoibHVrYXN6Lm9zdHJvd3NraUBzYWxlb3IuaW8iLCJ0eXBlIjoidGhpcmRwYXJ0eSIsInVzZXJfaWQiOiJWWE5sY2pveU1nPT0iLCJpc19zdGFmZiI6dHJ1ZSwiYXBwIjoiUVhCd09qSXdNalE9IiwicGVybWlzc2lvbnMiOlsiTUFOQUdFX09SREVSUyIsIkhBTkRMRV9UQVhFUyIsIk1BTkFHRV9DSEFOTkVMUyJdLCJ1c2VyX3Blcm1pc3Npb25zIjpbIk1BTkFHRV9VU0VSUyIsIk1BTkFHRV9TRVRUSU5HUyIsIkhBTkRMRV9UQVhFUyIsIk1BTkFHRV9QQUdFUyIsIkhBTkRMRV9DSEVDS09VVFMiLCJNQU5BR0VfTUVOVVMiLCJNQU5BR0VfVFJBTlNMQVRJT05TIiwiTUFOQUdFX1BST0RVQ1RTIiwiTUFOQUdFX1RBWEVTIiwiTUFOQUdFX09CU0VSVkFCSUxJVFkiLCJNQU5BR0VfT1JERVJTX0lNUE9SVCIsIk1BTkFHRV9DSEFOTkVMUyIsIk1BTkFHRV9BUFBTIiwiSU1QRVJTT05BVEVfVVNFUiIsIk1BTkFHRV9QUk9EVUNUX1RZUEVTX0FORF9BVFRSSUJVVEVTIiwiSEFORExFX1BBWU1FTlRTIiwiTUFOQUdFX0NIRUNLT1VUUyIsIk1BTkFHRV9HSUZUX0NBUkQiLCJNQU5BR0VfU0hJUFBJTkciLCJNQU5BR0VfU1RBRkYiLCJNQU5BR0VfRElTQ09VTlRTIiwiTUFOQUdFX1BMVUdJTlMiLCJNQU5BR0VfT1JERVJTIiwiTUFOQUdFX1BBR0VfVFlQRVNfQU5EX0FUVFJJQlVURVMiXX0.zGglCWxuOBgGJKyyZ-6m9Th4_tGUMCMjF6W3UQhaTl5P_tQ2694Pcjwnr2zDzeF0Hl4J-gPWlyH4fLnfHIaJpDds9POtZv1D-bE5kChtkcUC1hfBUzb7iL8SwtQhtvSWy-XmsVDpQTMeD7q5McRSaKNPf3IzPXPJx-F_y5OGpgTukXoweVOufG7jcyrKWyePTqJn1evQTawQOYlzp3nj22uE4sn4UQvpbPgHIbcPohoJSdKigwAPaUqTIz_q8Mrpn4EBUezrs0_24E49kILt4K6Otupbba7rJxQe5664-o7FnSunp-2gtr6zdUaY5hV3bR84WjQZFtgCOgPVd_YT9Q";
Object.defineProperty(window.document, "referrer", {
value: origin,
writable: true,
@ -100,7 +103,7 @@ describe("AppBridge", () => {
it("authenticates", () => {
expect(appBridge.getState().ready).toBe(false);
const token = validJwtToken;
const token = validTokenWithAppPermissions;
fireEvent(
window,
new MessageEvent("message", {
@ -111,6 +114,11 @@ describe("AppBridge", () => {
expect(appBridge.getState().ready).toBe(true);
expect(appBridge.getState().token).toEqual(token);
expect(appBridge.getState().appPermissions).toEqual([
"MANAGE_ORDERS",
"HANDLE_TAXES",
"MANAGE_CHANNELS",
]);
});
it("subscribes to an event and returns unsubscribe function", () => {

View file

@ -1,6 +1,7 @@
import debugPkg from "debug";
import { LocaleCode } from "../locales";
import { extractAppPermissionsFromJwt } from "../util/extract-app-permissions-from-jwt";
import { extractUserFromJwt } from "../util/extract-user-from-jwt";
import { Actions, actions } from "./actions";
import { AppBridgeState, AppBridgeStateContainer } from "./app-bridge-state";
@ -23,6 +24,7 @@ function eventStateReducer(state: AppBridgeState, event: Events) {
switch (event.type) {
case EventType.handshake: {
const userJwtPayload = extractUserFromJwt(event.payload.token);
const appPermissions = extractAppPermissionsFromJwt(event.payload.token);
return {
...state,
@ -34,6 +36,7 @@ function eventStateReducer(state: AppBridgeState, event: Events) {
email: userJwtPayload.email,
permissions: userJwtPayload.userPermissions,
},
appPermissions,
};
}
case EventType.redirect: {

View file

@ -9,6 +9,9 @@ import {
const toStringOrUndefined = (value: string | string[] | undefined) =>
value ? value.toString() : undefined;
/**
* Extracts Saleor-specific headers from the response.
*/
export const getSaleorHeaders = (headers: { [name: string]: string | string[] | undefined }) => ({
domain: toStringOrUndefined(headers[SALEOR_DOMAIN_HEADER]),
authorizationBearer: toStringOrUndefined(headers[SALEOR_AUTHORIZATION_BEARER_HEADER]),
@ -17,6 +20,9 @@ export const getSaleorHeaders = (headers: { [name: string]: string | string[] |
saleorApiUrl: toStringOrUndefined(headers[SALEOR_API_URL_HEADER]),
});
/**
* Extracts the app's url from headers from the response.
*/
export const getBaseUrl = (headers: { [name: string]: string | string[] | undefined }): string => {
const { host, "x-forwarded-proto": xForwardedProto = "http" } = headers;

View file

@ -0,0 +1,24 @@
import { describe, expect,it } from "vitest";
import { extractAppPermissionsFromJwt } from "./extract-app-permissions-from-jwt";
/**
* Contains
* "permissions": [
"MANAGE_ORDERS",
"HANDLE_TAXES",
"MANAGE_CHANNELS"
],
https://jwt.io/
*/
const jwtWithPermissions =
"eyJhbGciOiJSUzI1NiIsImtpZCI6IjEiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE2ODk4NTIyOTEsIm93bmVyIjoic2FsZW9yIiwiaXNzIjoiaHR0cHM6Ly9oYWNrYXRob24tc2hpcHBpbmcuZXUuc2FsZW9yLmNsb3VkL2dyYXBocWwvIiwiZXhwIjoxNjg5OTM4NjkxLCJ0b2tlbiI6IjNHTWRUSVpab3FRSSIsImVtYWlsIjoibHVrYXN6Lm9zdHJvd3NraUBzYWxlb3IuaW8iLCJ0eXBlIjoidGhpcmRwYXJ0eSIsInVzZXJfaWQiOiJWWE5sY2pveU1nPT0iLCJpc19zdGFmZiI6dHJ1ZSwiYXBwIjoiUVhCd09qSXdNalE9IiwicGVybWlzc2lvbnMiOlsiTUFOQUdFX09SREVSUyIsIkhBTkRMRV9UQVhFUyIsIk1BTkFHRV9DSEFOTkVMUyJdLCJ1c2VyX3Blcm1pc3Npb25zIjpbIk1BTkFHRV9VU0VSUyIsIk1BTkFHRV9TRVRUSU5HUyIsIkhBTkRMRV9UQVhFUyIsIk1BTkFHRV9QQUdFUyIsIkhBTkRMRV9DSEVDS09VVFMiLCJNQU5BR0VfTUVOVVMiLCJNQU5BR0VfVFJBTlNMQVRJT05TIiwiTUFOQUdFX1BST0RVQ1RTIiwiTUFOQUdFX1RBWEVTIiwiTUFOQUdFX09CU0VSVkFCSUxJVFkiLCJNQU5BR0VfT1JERVJTX0lNUE9SVCIsIk1BTkFHRV9DSEFOTkVMUyIsIk1BTkFHRV9BUFBTIiwiSU1QRVJTT05BVEVfVVNFUiIsIk1BTkFHRV9QUk9EVUNUX1RZUEVTX0FORF9BVFRSSUJVVEVTIiwiSEFORExFX1BBWU1FTlRTIiwiTUFOQUdFX0NIRUNLT1VUUyIsIk1BTkFHRV9HSUZUX0NBUkQiLCJNQU5BR0VfU0hJUFBJTkciLCJNQU5BR0VfU1RBRkYiLCJNQU5BR0VfRElTQ09VTlRTIiwiTUFOQUdFX1BMVUdJTlMiLCJNQU5BR0VfT1JERVJTIiwiTUFOQUdFX1BBR0VfVFlQRVNfQU5EX0FUVFJJQlVURVMiXX0.zGglCWxuOBgGJKyyZ-6m9Th4_tGUMCMjF6W3UQhaTl5P_tQ2694Pcjwnr2zDzeF0Hl4J-gPWlyH4fLnfHIaJpDds9POtZv1D-bE5kChtkcUC1hfBUzb7iL8SwtQhtvSWy-XmsVDpQTMeD7q5McRSaKNPf3IzPXPJx-F_y5OGpgTukXoweVOufG7jcyrKWyePTqJn1evQTawQOYlzp3nj22uE4sn4UQvpbPgHIbcPohoJSdKigwAPaUqTIz_q8Mrpn4EBUezrs0_24E49kILt4K6Otupbba7rJxQe5664-o7FnSunp-2gtr6zdUaY5hV3bR84WjQZFtgCOgPVd_YT9Q";
describe("extractAppPermissionsFromJwt", () => {
it("Returns permissions field from JWT token as an array of AppPermission", () => {
const permissions = extractAppPermissionsFromJwt(jwtWithPermissions);
expect(permissions).toEqual(["MANAGE_ORDERS", "HANDLE_TAXES", "MANAGE_CHANNELS"]);
});
});

View file

@ -0,0 +1,9 @@
import * as jose from "jose";
import { AppPermission } from "../types";
export const extractAppPermissionsFromJwt = (jwtToken: string): AppPermission[] => {
const tokenDecoded = jose.decodeJwt(jwtToken);
return tokenDecoded.permissions as AppPermission[];
};