mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 19:31:24 +00:00
fix(security): centralize owner-only tool gating and scope maps
This commit is contained in:
@@ -186,95 +186,153 @@ export function buildGatewayConnectionDetails(
|
||||
};
|
||||
}
|
||||
|
||||
async function callGatewayWithScopes<T = Record<string, unknown>>(
|
||||
opts: CallGatewayBaseOptions,
|
||||
scopes: OperatorScope[],
|
||||
): Promise<T> {
|
||||
const timeoutMs =
|
||||
typeof opts.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : 10_000;
|
||||
const safeTimerTimeoutMs = Math.max(1, Math.min(Math.floor(timeoutMs), 2_147_483_647));
|
||||
const config = opts.config ?? loadConfig();
|
||||
const isRemoteMode = config.gateway?.mode === "remote";
|
||||
const remote = isRemoteMode ? config.gateway?.remote : undefined;
|
||||
const urlOverride =
|
||||
typeof opts.url === "string" && opts.url.trim().length > 0 ? opts.url.trim() : undefined;
|
||||
const explicitAuth = resolveExplicitGatewayAuth({ token: opts.token, password: opts.password });
|
||||
ensureExplicitGatewayAuth({
|
||||
urlOverride,
|
||||
auth: explicitAuth,
|
||||
errorHint: "Fix: pass --token or --password (or gatewayToken in tools).",
|
||||
configPath: opts.configPath ?? resolveConfigPath(process.env, resolveStateDir(process.env)),
|
||||
});
|
||||
const remoteUrl =
|
||||
typeof remote?.url === "string" && remote.url.trim().length > 0 ? remote.url.trim() : undefined;
|
||||
if (isRemoteMode && !urlOverride && !remoteUrl) {
|
||||
const configPath =
|
||||
opts.configPath ?? resolveConfigPath(process.env, resolveStateDir(process.env));
|
||||
throw new Error(
|
||||
[
|
||||
"gateway remote mode misconfigured: gateway.remote.url missing",
|
||||
`Config: ${configPath}`,
|
||||
"Fix: set gateway.remote.url, or set gateway.mode=local.",
|
||||
].join("\n"),
|
||||
);
|
||||
type GatewayRemoteSettings = {
|
||||
url?: string;
|
||||
token?: string;
|
||||
password?: string;
|
||||
tlsFingerprint?: string;
|
||||
};
|
||||
|
||||
type ResolvedGatewayCallContext = {
|
||||
config: OpenClawConfig;
|
||||
configPath: string;
|
||||
isRemoteMode: boolean;
|
||||
remote?: GatewayRemoteSettings;
|
||||
urlOverride?: string;
|
||||
remoteUrl?: string;
|
||||
explicitAuth: ExplicitGatewayAuth;
|
||||
};
|
||||
|
||||
function trimToUndefined(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const authToken = config.gateway?.auth?.token;
|
||||
const authPassword = config.gateway?.auth?.password;
|
||||
const connectionDetails = buildGatewayConnectionDetails({
|
||||
config,
|
||||
url: urlOverride,
|
||||
...(opts.configPath ? { configPath: opts.configPath } : {}),
|
||||
});
|
||||
const url = connectionDetails.url;
|
||||
const useLocalTls =
|
||||
config.gateway?.tls?.enabled === true && !urlOverride && !remoteUrl && url.startsWith("wss://");
|
||||
const tlsRuntime = useLocalTls ? await loadGatewayTlsRuntime(config.gateway?.tls) : undefined;
|
||||
const remoteTlsFingerprint =
|
||||
isRemoteMode && !urlOverride && remoteUrl && typeof remote?.tlsFingerprint === "string"
|
||||
? remote.tlsFingerprint.trim()
|
||||
: undefined;
|
||||
const overrideTlsFingerprint =
|
||||
typeof opts.tlsFingerprint === "string" ? opts.tlsFingerprint.trim() : undefined;
|
||||
const tlsFingerprint =
|
||||
overrideTlsFingerprint ||
|
||||
remoteTlsFingerprint ||
|
||||
(tlsRuntime?.enabled ? tlsRuntime.fingerprintSha256 : undefined);
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function resolveGatewayCallTimeout(timeoutValue: unknown): {
|
||||
timeoutMs: number;
|
||||
safeTimerTimeoutMs: number;
|
||||
} {
|
||||
const timeoutMs =
|
||||
typeof timeoutValue === "number" && Number.isFinite(timeoutValue) ? timeoutValue : 10_000;
|
||||
const safeTimerTimeoutMs = Math.max(1, Math.min(Math.floor(timeoutMs), 2_147_483_647));
|
||||
return { timeoutMs, safeTimerTimeoutMs };
|
||||
}
|
||||
|
||||
function resolveGatewayCallContext(opts: CallGatewayBaseOptions): ResolvedGatewayCallContext {
|
||||
const config = opts.config ?? loadConfig();
|
||||
const configPath =
|
||||
opts.configPath ?? resolveConfigPath(process.env, resolveStateDir(process.env));
|
||||
const isRemoteMode = config.gateway?.mode === "remote";
|
||||
const remote = isRemoteMode
|
||||
? (config.gateway?.remote as GatewayRemoteSettings | undefined)
|
||||
: undefined;
|
||||
const urlOverride = trimToUndefined(opts.url);
|
||||
const remoteUrl = trimToUndefined(remote?.url);
|
||||
const explicitAuth = resolveExplicitGatewayAuth({ token: opts.token, password: opts.password });
|
||||
return { config, configPath, isRemoteMode, remote, urlOverride, remoteUrl, explicitAuth };
|
||||
}
|
||||
|
||||
function ensureRemoteModeUrlConfigured(context: ResolvedGatewayCallContext): void {
|
||||
if (!context.isRemoteMode || context.urlOverride || context.remoteUrl) {
|
||||
return;
|
||||
}
|
||||
throw new Error(
|
||||
[
|
||||
"gateway remote mode misconfigured: gateway.remote.url missing",
|
||||
`Config: ${context.configPath}`,
|
||||
"Fix: set gateway.remote.url, or set gateway.mode=local.",
|
||||
].join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
function resolveGatewayCredentials(context: ResolvedGatewayCallContext): {
|
||||
token?: string;
|
||||
password?: string;
|
||||
} {
|
||||
const authToken = context.config.gateway?.auth?.token;
|
||||
const authPassword = context.config.gateway?.auth?.password;
|
||||
const token =
|
||||
explicitAuth.token ||
|
||||
(!urlOverride
|
||||
? isRemoteMode
|
||||
? typeof remote?.token === "string" && remote.token.trim().length > 0
|
||||
? remote.token.trim()
|
||||
: undefined
|
||||
: process.env.OPENCLAW_GATEWAY_TOKEN?.trim() ||
|
||||
process.env.CLAWDBOT_GATEWAY_TOKEN?.trim() ||
|
||||
(typeof authToken === "string" && authToken.trim().length > 0
|
||||
? authToken.trim()
|
||||
: undefined)
|
||||
context.explicitAuth.token ||
|
||||
(!context.urlOverride
|
||||
? context.isRemoteMode
|
||||
? trimToUndefined(context.remote?.token)
|
||||
: trimToUndefined(process.env.OPENCLAW_GATEWAY_TOKEN) ||
|
||||
trimToUndefined(process.env.CLAWDBOT_GATEWAY_TOKEN) ||
|
||||
trimToUndefined(authToken)
|
||||
: undefined);
|
||||
const password =
|
||||
explicitAuth.password ||
|
||||
(!urlOverride
|
||||
? process.env.OPENCLAW_GATEWAY_PASSWORD?.trim() ||
|
||||
process.env.CLAWDBOT_GATEWAY_PASSWORD?.trim() ||
|
||||
(isRemoteMode
|
||||
? typeof remote?.password === "string" && remote.password.trim().length > 0
|
||||
? remote.password.trim()
|
||||
: undefined
|
||||
: typeof authPassword === "string" && authPassword.trim().length > 0
|
||||
? authPassword.trim()
|
||||
: undefined)
|
||||
context.explicitAuth.password ||
|
||||
(!context.urlOverride
|
||||
? trimToUndefined(process.env.OPENCLAW_GATEWAY_PASSWORD) ||
|
||||
trimToUndefined(process.env.CLAWDBOT_GATEWAY_PASSWORD) ||
|
||||
(context.isRemoteMode
|
||||
? trimToUndefined(context.remote?.password)
|
||||
: trimToUndefined(authPassword))
|
||||
: undefined);
|
||||
return { token, password };
|
||||
}
|
||||
|
||||
const formatCloseError = (code: number, reason: string) => {
|
||||
const reasonText = reason?.trim() || "no close reason";
|
||||
const hint =
|
||||
code === 1006 ? "abnormal closure (no close frame)" : code === 1000 ? "normal closure" : "";
|
||||
const suffix = hint ? ` ${hint}` : "";
|
||||
return `gateway closed (${code}${suffix}): ${reasonText}\n${connectionDetails.message}`;
|
||||
};
|
||||
const formatTimeoutError = () =>
|
||||
`gateway timeout after ${timeoutMs}ms\n${connectionDetails.message}`;
|
||||
async function resolveGatewayTlsFingerprint(params: {
|
||||
opts: CallGatewayBaseOptions;
|
||||
context: ResolvedGatewayCallContext;
|
||||
url: string;
|
||||
}): Promise<string | undefined> {
|
||||
const { opts, context, url } = params;
|
||||
const useLocalTls =
|
||||
context.config.gateway?.tls?.enabled === true &&
|
||||
!context.urlOverride &&
|
||||
!context.remoteUrl &&
|
||||
url.startsWith("wss://");
|
||||
const tlsRuntime = useLocalTls
|
||||
? await loadGatewayTlsRuntime(context.config.gateway?.tls)
|
||||
: undefined;
|
||||
const overrideTlsFingerprint = trimToUndefined(opts.tlsFingerprint);
|
||||
const remoteTlsFingerprint =
|
||||
context.isRemoteMode && !context.urlOverride && context.remoteUrl
|
||||
? trimToUndefined(context.remote?.tlsFingerprint)
|
||||
: undefined;
|
||||
return (
|
||||
overrideTlsFingerprint ||
|
||||
remoteTlsFingerprint ||
|
||||
(tlsRuntime?.enabled ? tlsRuntime.fingerprintSha256 : undefined)
|
||||
);
|
||||
}
|
||||
|
||||
function formatGatewayCloseError(
|
||||
code: number,
|
||||
reason: string,
|
||||
connectionDetails: GatewayConnectionDetails,
|
||||
): string {
|
||||
const reasonText = reason?.trim() || "no close reason";
|
||||
const hint =
|
||||
code === 1006 ? "abnormal closure (no close frame)" : code === 1000 ? "normal closure" : "";
|
||||
const suffix = hint ? ` ${hint}` : "";
|
||||
return `gateway closed (${code}${suffix}): ${reasonText}\n${connectionDetails.message}`;
|
||||
}
|
||||
|
||||
function formatGatewayTimeoutError(
|
||||
timeoutMs: number,
|
||||
connectionDetails: GatewayConnectionDetails,
|
||||
): string {
|
||||
return `gateway timeout after ${timeoutMs}ms\n${connectionDetails.message}`;
|
||||
}
|
||||
|
||||
async function executeGatewayRequestWithScopes<T>(params: {
|
||||
opts: CallGatewayBaseOptions;
|
||||
scopes: OperatorScope[];
|
||||
url: string;
|
||||
token?: string;
|
||||
password?: string;
|
||||
tlsFingerprint?: string;
|
||||
timeoutMs: number;
|
||||
safeTimerTimeoutMs: number;
|
||||
connectionDetails: GatewayConnectionDetails;
|
||||
}): Promise<T> {
|
||||
const { opts, scopes, url, token, password, tlsFingerprint, timeoutMs, safeTimerTimeoutMs } =
|
||||
params;
|
||||
return await new Promise<T>((resolve, reject) => {
|
||||
let settled = false;
|
||||
let ignoreClose = false;
|
||||
@@ -327,20 +385,54 @@ async function callGatewayWithScopes<T = Record<string, unknown>>(
|
||||
}
|
||||
ignoreClose = true;
|
||||
client.stop();
|
||||
stop(new Error(formatCloseError(code, reason)));
|
||||
stop(new Error(formatGatewayCloseError(code, reason, params.connectionDetails)));
|
||||
},
|
||||
});
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
ignoreClose = true;
|
||||
client.stop();
|
||||
stop(new Error(formatTimeoutError()));
|
||||
stop(new Error(formatGatewayTimeoutError(timeoutMs, params.connectionDetails)));
|
||||
}, safeTimerTimeoutMs);
|
||||
|
||||
client.start();
|
||||
});
|
||||
}
|
||||
|
||||
async function callGatewayWithScopes<T = Record<string, unknown>>(
|
||||
opts: CallGatewayBaseOptions,
|
||||
scopes: OperatorScope[],
|
||||
): Promise<T> {
|
||||
const { timeoutMs, safeTimerTimeoutMs } = resolveGatewayCallTimeout(opts.timeoutMs);
|
||||
const context = resolveGatewayCallContext(opts);
|
||||
ensureExplicitGatewayAuth({
|
||||
urlOverride: context.urlOverride,
|
||||
auth: context.explicitAuth,
|
||||
errorHint: "Fix: pass --token or --password (or gatewayToken in tools).",
|
||||
configPath: context.configPath,
|
||||
});
|
||||
ensureRemoteModeUrlConfigured(context);
|
||||
const connectionDetails = buildGatewayConnectionDetails({
|
||||
config: context.config,
|
||||
url: context.urlOverride,
|
||||
...(opts.configPath ? { configPath: opts.configPath } : {}),
|
||||
});
|
||||
const url = connectionDetails.url;
|
||||
const tlsFingerprint = await resolveGatewayTlsFingerprint({ opts, context, url });
|
||||
const { token, password } = resolveGatewayCredentials(context);
|
||||
return await executeGatewayRequestWithScopes<T>({
|
||||
opts,
|
||||
scopes,
|
||||
url,
|
||||
token,
|
||||
password,
|
||||
tlsFingerprint,
|
||||
timeoutMs,
|
||||
safeTimerTimeoutMs,
|
||||
connectionDetails,
|
||||
});
|
||||
}
|
||||
|
||||
export async function callGatewayScoped<T = Record<string, unknown>>(
|
||||
opts: CallGatewayScopedOptions,
|
||||
): Promise<T> {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
authorizeOperatorScopesForMethod,
|
||||
isGatewayMethodClassified,
|
||||
resolveLeastPrivilegeOperatorScopesForMethod,
|
||||
} from "./method-scopes.js";
|
||||
import { coreGatewayHandlers } from "./server-methods.js";
|
||||
|
||||
describe("method scope resolution", () => {
|
||||
it("classifies sessions.resolve as read and poll as write", () => {
|
||||
@@ -48,3 +50,12 @@ describe("operator scope authorization", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("core gateway method classification", () => {
|
||||
it("classifies every exposed core gateway handler method", () => {
|
||||
const unclassified = Object.keys(coreGatewayHandlers).filter(
|
||||
(method) => !isGatewayMethodClassified(method),
|
||||
);
|
||||
expect(unclassified).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,110 +17,137 @@ export const CLI_DEFAULT_OPERATOR_SCOPES: OperatorScope[] = [
|
||||
PAIRING_SCOPE,
|
||||
];
|
||||
|
||||
const APPROVAL_METHODS = new Set([
|
||||
"exec.approval.request",
|
||||
"exec.approval.waitDecision",
|
||||
"exec.approval.resolve",
|
||||
]);
|
||||
|
||||
const NODE_ROLE_METHODS = new Set(["node.invoke.result", "node.event", "skills.bins"]);
|
||||
|
||||
const PAIRING_METHODS = new Set([
|
||||
"node.pair.request",
|
||||
"node.pair.list",
|
||||
"node.pair.approve",
|
||||
"node.pair.reject",
|
||||
"node.pair.verify",
|
||||
"device.pair.list",
|
||||
"device.pair.approve",
|
||||
"device.pair.reject",
|
||||
"device.pair.remove",
|
||||
"device.token.rotate",
|
||||
"device.token.revoke",
|
||||
"node.rename",
|
||||
]);
|
||||
const METHOD_SCOPE_GROUPS: Record<OperatorScope, readonly string[]> = {
|
||||
[APPROVALS_SCOPE]: [
|
||||
"exec.approval.request",
|
||||
"exec.approval.waitDecision",
|
||||
"exec.approval.resolve",
|
||||
],
|
||||
[PAIRING_SCOPE]: [
|
||||
"node.pair.request",
|
||||
"node.pair.list",
|
||||
"node.pair.approve",
|
||||
"node.pair.reject",
|
||||
"node.pair.verify",
|
||||
"device.pair.list",
|
||||
"device.pair.approve",
|
||||
"device.pair.reject",
|
||||
"device.pair.remove",
|
||||
"device.token.rotate",
|
||||
"device.token.revoke",
|
||||
"node.rename",
|
||||
],
|
||||
[READ_SCOPE]: [
|
||||
"health",
|
||||
"logs.tail",
|
||||
"channels.status",
|
||||
"status",
|
||||
"usage.status",
|
||||
"usage.cost",
|
||||
"tts.status",
|
||||
"tts.providers",
|
||||
"models.list",
|
||||
"agents.list",
|
||||
"agent.identity.get",
|
||||
"skills.status",
|
||||
"voicewake.get",
|
||||
"sessions.list",
|
||||
"sessions.preview",
|
||||
"sessions.resolve",
|
||||
"sessions.usage",
|
||||
"sessions.usage.timeseries",
|
||||
"sessions.usage.logs",
|
||||
"cron.list",
|
||||
"cron.status",
|
||||
"cron.runs",
|
||||
"system-presence",
|
||||
"last-heartbeat",
|
||||
"node.list",
|
||||
"node.describe",
|
||||
"chat.history",
|
||||
"config.get",
|
||||
"talk.config",
|
||||
"agents.files.list",
|
||||
"agents.files.get",
|
||||
],
|
||||
[WRITE_SCOPE]: [
|
||||
"send",
|
||||
"poll",
|
||||
"agent",
|
||||
"agent.wait",
|
||||
"wake",
|
||||
"talk.mode",
|
||||
"tts.enable",
|
||||
"tts.disable",
|
||||
"tts.convert",
|
||||
"tts.setProvider",
|
||||
"voicewake.set",
|
||||
"node.invoke",
|
||||
"chat.send",
|
||||
"chat.abort",
|
||||
"browser.request",
|
||||
"push.test",
|
||||
],
|
||||
[ADMIN_SCOPE]: [
|
||||
"channels.logout",
|
||||
"agents.create",
|
||||
"agents.update",
|
||||
"agents.delete",
|
||||
"skills.install",
|
||||
"skills.update",
|
||||
"cron.add",
|
||||
"cron.update",
|
||||
"cron.remove",
|
||||
"cron.run",
|
||||
"sessions.patch",
|
||||
"sessions.reset",
|
||||
"sessions.delete",
|
||||
"sessions.compact",
|
||||
"connect",
|
||||
"chat.inject",
|
||||
"web.login.start",
|
||||
"web.login.wait",
|
||||
"set-heartbeats",
|
||||
"system-event",
|
||||
"agents.files.set",
|
||||
],
|
||||
};
|
||||
|
||||
const ADMIN_METHOD_PREFIXES = ["exec.approvals."];
|
||||
const ADMIN_METHOD_PREFIXES = ["exec.approvals.", "config.", "wizard.", "update."] as const;
|
||||
|
||||
const READ_METHODS = new Set([
|
||||
"health",
|
||||
"logs.tail",
|
||||
"channels.status",
|
||||
"status",
|
||||
"usage.status",
|
||||
"usage.cost",
|
||||
"tts.status",
|
||||
"tts.providers",
|
||||
"models.list",
|
||||
"agents.list",
|
||||
"agent.identity.get",
|
||||
"skills.status",
|
||||
"voicewake.get",
|
||||
"sessions.list",
|
||||
"sessions.preview",
|
||||
"sessions.resolve",
|
||||
"cron.list",
|
||||
"cron.status",
|
||||
"cron.runs",
|
||||
"system-presence",
|
||||
"last-heartbeat",
|
||||
"node.list",
|
||||
"node.describe",
|
||||
"chat.history",
|
||||
"config.get",
|
||||
"talk.config",
|
||||
]);
|
||||
const METHOD_SCOPE_BY_NAME = new Map<string, OperatorScope>(
|
||||
Object.entries(METHOD_SCOPE_GROUPS).flatMap(([scope, methods]) =>
|
||||
methods.map((method) => [method, scope as OperatorScope]),
|
||||
),
|
||||
);
|
||||
|
||||
const WRITE_METHODS = new Set([
|
||||
"send",
|
||||
"poll",
|
||||
"agent",
|
||||
"agent.wait",
|
||||
"wake",
|
||||
"talk.mode",
|
||||
"tts.enable",
|
||||
"tts.disable",
|
||||
"tts.convert",
|
||||
"tts.setProvider",
|
||||
"voicewake.set",
|
||||
"node.invoke",
|
||||
"chat.send",
|
||||
"chat.abort",
|
||||
"browser.request",
|
||||
"push.test",
|
||||
]);
|
||||
|
||||
const ADMIN_METHODS = new Set([
|
||||
"channels.logout",
|
||||
"agents.create",
|
||||
"agents.update",
|
||||
"agents.delete",
|
||||
"skills.install",
|
||||
"skills.update",
|
||||
"cron.add",
|
||||
"cron.update",
|
||||
"cron.remove",
|
||||
"cron.run",
|
||||
"sessions.patch",
|
||||
"sessions.reset",
|
||||
"sessions.delete",
|
||||
"sessions.compact",
|
||||
]);
|
||||
function resolveScopedMethod(method: string): OperatorScope | undefined {
|
||||
const explicitScope = METHOD_SCOPE_BY_NAME.get(method);
|
||||
if (explicitScope) {
|
||||
return explicitScope;
|
||||
}
|
||||
if (ADMIN_METHOD_PREFIXES.some((prefix) => method.startsWith(prefix))) {
|
||||
return ADMIN_SCOPE;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function isApprovalMethod(method: string): boolean {
|
||||
return APPROVAL_METHODS.has(method);
|
||||
return resolveScopedMethod(method) === APPROVALS_SCOPE;
|
||||
}
|
||||
|
||||
export function isPairingMethod(method: string): boolean {
|
||||
return PAIRING_METHODS.has(method);
|
||||
return resolveScopedMethod(method) === PAIRING_SCOPE;
|
||||
}
|
||||
|
||||
export function isReadMethod(method: string): boolean {
|
||||
return READ_METHODS.has(method);
|
||||
return resolveScopedMethod(method) === READ_SCOPE;
|
||||
}
|
||||
|
||||
export function isWriteMethod(method: string): boolean {
|
||||
return WRITE_METHODS.has(method);
|
||||
return resolveScopedMethod(method) === WRITE_SCOPE;
|
||||
}
|
||||
|
||||
export function isNodeRoleMethod(method: string): boolean {
|
||||
@@ -128,36 +155,11 @@ export function isNodeRoleMethod(method: string): boolean {
|
||||
}
|
||||
|
||||
export function isAdminOnlyMethod(method: string): boolean {
|
||||
if (ADMIN_METHOD_PREFIXES.some((prefix) => method.startsWith(prefix))) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
method.startsWith("config.") ||
|
||||
method.startsWith("wizard.") ||
|
||||
method.startsWith("update.")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return ADMIN_METHODS.has(method);
|
||||
return resolveScopedMethod(method) === ADMIN_SCOPE;
|
||||
}
|
||||
|
||||
export function resolveRequiredOperatorScopeForMethod(method: string): OperatorScope | undefined {
|
||||
if (isApprovalMethod(method)) {
|
||||
return APPROVALS_SCOPE;
|
||||
}
|
||||
if (isPairingMethod(method)) {
|
||||
return PAIRING_SCOPE;
|
||||
}
|
||||
if (isReadMethod(method)) {
|
||||
return READ_SCOPE;
|
||||
}
|
||||
if (isWriteMethod(method)) {
|
||||
return WRITE_SCOPE;
|
||||
}
|
||||
if (isAdminOnlyMethod(method)) {
|
||||
return ADMIN_SCOPE;
|
||||
}
|
||||
return undefined;
|
||||
return resolveScopedMethod(method);
|
||||
}
|
||||
|
||||
export function resolveLeastPrivilegeOperatorScopesForMethod(method: string): OperatorScope[] {
|
||||
@@ -188,3 +190,10 @@ export function authorizeOperatorScopesForMethod(
|
||||
}
|
||||
return { allowed: false, missingScope: requiredScope };
|
||||
}
|
||||
|
||||
export function isGatewayMethodClassified(method: string): boolean {
|
||||
if (isNodeRoleMethod(method)) {
|
||||
return true;
|
||||
}
|
||||
return resolveRequiredOperatorScopeForMethod(method) !== undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user