Expose headers file. Added AppPermissions extration into AppBridge (#265)
* Expose headers file. Added AppPermissions extration into appbridg * fix imports
This commit is contained in:
parent
0b378f19ff
commit
b8935a84c5
10 changed files with 83 additions and 2 deletions
5
.changeset/five-teachers-push.md
Normal file
5
.changeset/five-teachers-push.md
Normal 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.
|
5
.changeset/sixty-frogs-breathe.md
Normal file
5
.changeset/sixty-frogs-breathe.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@saleor/app-sdk": minor
|
||||
---
|
||||
|
||||
Exposed "@saleor/app-sdk/headers" path. It contains helper methods: getSaleorHeaders and getBaseUrl
|
|
@ -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[];
|
||||
};
|
||||
```
|
||||
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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", () => {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
24
src/util/extract-app-permissions-from-jwt.test.ts
Normal file
24
src/util/extract-app-permissions-from-jwt.test.ts
Normal 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"]);
|
||||
});
|
||||
});
|
9
src/util/extract-app-permissions-from-jwt.ts
Normal file
9
src/util/extract-app-permissions-from-jwt.ts
Normal 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[];
|
||||
};
|
Loading…
Reference in a new issue