@forge/bridge
5.15.2-next.0-experimental-5b726e65.16.0-next.1
out/permissions/permissionsUtil.js~
out/permissions/permissionsUtil.jsModified+52
Index: package/out/permissions/permissionsUtil.js
===================================================================
--- package/out/permissions/permissionsUtil.js
+++ package/out/permissions/permissionsUtil.js
@@ -2,8 +2,12 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkPermissions = exports.createPermissionUtils = void 0;
const egress_1 = require("@forge/egress");
const view_1 = require("../view");
+/**
+ * Helper function to extract URL string from external URL permissions.
+ * Matches the implementation in @forge/api for consistency.
+ */
function extractUrlString(url) {
if (typeof url === 'string') {
return url;
}
@@ -11,10 +15,21 @@
return url.address;
}
return url.remote || '';
}
+/**
+ * Resource types that can be loaded externally
+ */
const RESOURCE_TYPES = ['fonts', 'styles', 'frames', 'images', 'media', 'scripts'];
+/**
+ * Fetch types for external requests
+ */
const FETCH_TYPES = ['backend', 'client'];
+/**
+ * Create permission utilities from runtime permissions
+ * @param runtimePermissions - The runtime permissions from context
+ * @returns Permission utilities or null if permissions are not available
+ */
function createPermissionUtils(runtimePermissions) {
if (!runtimePermissions) {
return null;
}
@@ -26,23 +41,29 @@
var _a;
const fetchUrls = (_a = external.fetch) === null || _a === void 0 ? void 0 : _a[type];
if (!(fetchUrls === null || fetchUrls === void 0 ? void 0 : fetchUrls.length))
return false;
+ // Extract URLs and create egress filter
const allowList = fetchUrls.map(extractUrlString).filter((u) => u.length > 0);
if (allowList.length === 0)
return false;
const egressFilter = new egress_1.EgressFilteringService(allowList);
+ // Backend: hostname-only matching, Client: CSP validation (includes paths)
+ // Type assertion needed because node_modules may have outdated types
const egressFilterWithCSP = egressFilter;
return type === 'client' ? egressFilterWithCSP.isValidUrlCSP(url) : egressFilter.isValidUrl(url);
},
canLoadResource: (type, url) => {
const resourceUrls = external[type];
if (!(resourceUrls === null || resourceUrls === void 0 ? void 0 : resourceUrls.length))
return false;
+ // Extract URLs and create egress filter
const allowList = resourceUrls.map(extractUrlString).filter((u) => u.length > 0);
if (allowList.length === 0)
return false;
const egressFilter = new egress_1.EgressFilteringService(allowList);
+ // All resources use CSP validation (checks protocol + hostname + paths)
+ // Type assertion needed because node_modules may have outdated types
const egressFilterWithCSP = egressFilter;
return egressFilterWithCSP.isValidUrlCSP(url);
},
getScopes: () => scopeArray,
@@ -50,15 +71,21 @@
hasAnyPermissions: () => scopeArray.length > 0 || Object.keys(external).length > 0
};
}
exports.createPermissionUtils = createPermissionUtils;
+/**
+ * Check if required scopes are granted
+ */
function checkScopes(requiredScopes, permissionUtils) {
if (!(requiredScopes === null || requiredScopes === void 0 ? void 0 : requiredScopes.length)) {
return undefined;
}
const missingScopes = requiredScopes.filter((scope) => !permissionUtils.hasScope(scope));
return missingScopes.length > 0 ? missingScopes : undefined;
}
+/**
+ * Check if required fetch permissions are granted
+ */
function checkFetchPermissions(requiredFetch, permissionUtils) {
if (!(requiredFetch === null || requiredFetch === void 0 ? void 0 : requiredFetch.fetch)) {
return undefined;
}
@@ -74,8 +101,11 @@
}
});
return Object.keys(missingFetch).length > 0 ? missingFetch : undefined;
}
+/**
+ * Check if required resource permissions are granted
+ */
function checkResourcePermissions(requiredExternal, permissionUtils) {
const missingResources = {};
RESOURCE_TYPES.forEach((type) => {
const requiredUrls = requiredExternal === null || requiredExternal === void 0 ? void 0 : requiredExternal[type];
@@ -87,8 +117,11 @@
}
});
return Object.keys(missingResources).length > 0 ? missingResources : undefined;
}
+/**
+ * Check if required external permissions are granted
+ */
function checkExternalPermissions(requiredExternal, permissionUtils) {
if (!requiredExternal) {
return undefined;
}
@@ -105,20 +138,26 @@
Object.assign(missingExternal, missingResources);
}
return missingExternal;
}
+// Throws if a field that should be a plain object but is something else.
+// Only used for external and external.fetch, which are guarded above.
function validateObjectField(value, fieldPath) {
if (value !== undefined) {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
throw new TypeError(`${fieldPath} should be an object, not ${Array.isArray(value) ? 'an array' : `a ${typeof value}`}`);
}
}
}
+// Throws if a field that should be an array but is something else.
function validateArrayField(value, fieldPath) {
if (value !== undefined && !Array.isArray(value)) {
throw new TypeError(`${fieldPath} should be an array, not a ${typeof value}`);
}
}
+/**
+ * Validates that fields in requiredPermissions have the correct types.
+ */
function validatePermissionShape(requiredPermissions) {
validateArrayField(requiredPermissions.scopes, 'scopes');
const external = requiredPermissions.external;
if (external === undefined)
@@ -133,38 +172,51 @@
for (const type of RESOURCE_TYPES) {
validateArrayField(external[type], `external.${type}`);
}
}
+/**
+ * Check if required permissions are granted
+ * @param requiredPermissions - The permissions required to check
+ * @param runtimePermissions - The runtime permissions from context (optional, will be fetched from view.getContext() if not provided)
+ * @returns Promise resolving to permission check result with granted status and missing permissions
+ */
async function checkPermissions(requiredPermissions, runtimePermissions) {
var _a;
+ // Early return for falsy required permissions
if (!requiredPermissions) {
return { granted: false, missing: null };
}
validatePermissionShape(requiredPermissions);
+ // Early return for empty permissions (no requirements = all granted)
if (!((_a = requiredPermissions.scopes) === null || _a === void 0 ? void 0 : _a.length) && !requiredPermissions.external) {
return { granted: true, missing: null };
}
+ // If runtimePermissions is not provided, fetch it from context
let permissionsToCheck = runtimePermissions;
if (!permissionsToCheck) {
const context = await view_1.view.getContext();
permissionsToCheck = context.permissions;
}
const permissionUtils = createPermissionUtils(permissionsToCheck);
if (!permissionUtils) {
+ // If permissions are not available, return not granted
return { granted: false, missing: null };
}
const missing = {};
let hasAllRequiredPermissions = true;
+ // Check scopes
const missingScopes = checkScopes(requiredPermissions.scopes, permissionUtils);
if (missingScopes) {
missing.scopes = missingScopes;
hasAllRequiredPermissions = false;
}
+ // Check external permissions
const missingExternal = checkExternalPermissions(requiredPermissions.external, permissionUtils);
if (missingExternal) {
missing.external = missingExternal;
hasAllRequiredPermissions = false;
}
+ // Note: Content permissions are not supported in the current RuntimePermissions type
return {
granted: hasAllRequiredPermissions,
missing: hasAllRequiredPermissions ? null : missing
};