mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 23:01:24 +00:00
fix(gateway): require shared auth before device bypass
This commit is contained in:
@@ -377,8 +377,63 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
isControlUi && configSnapshot.gateway?.controlUi?.dangerouslyDisableDeviceAuth === true;
|
||||
const allowControlUiBypass = allowInsecureControlUi || disableControlUiDeviceAuth;
|
||||
const device = disableControlUiDeviceAuth ? null : deviceRaw;
|
||||
|
||||
const authResult = await authorizeGatewayConnect({
|
||||
auth: resolvedAuth,
|
||||
connectAuth: connectParams.auth,
|
||||
req: upgradeReq,
|
||||
trustedProxies,
|
||||
});
|
||||
let authOk = authResult.ok;
|
||||
let authMethod =
|
||||
authResult.method ?? (resolvedAuth.mode === "password" ? "password" : "token");
|
||||
const sharedAuthResult = hasSharedAuth
|
||||
? await authorizeGatewayConnect({
|
||||
auth: { ...resolvedAuth, allowTailscale: false },
|
||||
connectAuth: connectParams.auth,
|
||||
req: upgradeReq,
|
||||
trustedProxies,
|
||||
})
|
||||
: null;
|
||||
const sharedAuthOk =
|
||||
sharedAuthResult?.ok === true &&
|
||||
(sharedAuthResult.method === "token" || sharedAuthResult.method === "password");
|
||||
const rejectUnauthorized = () => {
|
||||
setHandshakeState("failed");
|
||||
logWsControl.warn(
|
||||
`unauthorized conn=${connId} remote=${remoteAddr ?? "?"} client=${clientLabel} ${connectParams.client.mode} v${connectParams.client.version} reason=${authResult.reason ?? "unknown"}`,
|
||||
);
|
||||
const authProvided: AuthProvidedKind = connectParams.auth?.token
|
||||
? "token"
|
||||
: connectParams.auth?.password
|
||||
? "password"
|
||||
: "none";
|
||||
const authMessage = formatGatewayAuthFailureMessage({
|
||||
authMode: resolvedAuth.mode,
|
||||
authProvided,
|
||||
reason: authResult.reason,
|
||||
client: connectParams.client,
|
||||
});
|
||||
setCloseCause("unauthorized", {
|
||||
authMode: resolvedAuth.mode,
|
||||
authProvided,
|
||||
authReason: authResult.reason,
|
||||
allowTailscale: resolvedAuth.allowTailscale,
|
||||
client: connectParams.client.id,
|
||||
clientDisplayName: connectParams.client.displayName,
|
||||
mode: connectParams.client.mode,
|
||||
version: connectParams.client.version,
|
||||
});
|
||||
send({
|
||||
type: "res",
|
||||
id: frame.id,
|
||||
ok: false,
|
||||
error: errorShape(ErrorCodes.INVALID_REQUEST, authMessage),
|
||||
});
|
||||
close(1008, truncateCloseReason(authMessage));
|
||||
};
|
||||
if (!device) {
|
||||
const canSkipDevice = allowControlUiBypass ? hasSharedAuth : hasTokenAuth;
|
||||
const canSkipDevice = sharedAuthOk;
|
||||
|
||||
if (isControlUi && !allowControlUiBypass) {
|
||||
const errorMessage = "control ui requires HTTPS or localhost (secure context)";
|
||||
@@ -399,8 +454,12 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow token-authenticated connections (e.g., control-ui) to skip device identity
|
||||
// Allow shared-secret authenticated connections (e.g., control-ui) to skip device identity
|
||||
if (!canSkipDevice) {
|
||||
if (!authOk && hasSharedAuth) {
|
||||
rejectUnauthorized();
|
||||
return;
|
||||
}
|
||||
setHandshakeState("failed");
|
||||
setCloseCause("device-required", {
|
||||
client: connectParams.client.id,
|
||||
@@ -567,15 +626,6 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
}
|
||||
}
|
||||
|
||||
const authResult = await authorizeGatewayConnect({
|
||||
auth: resolvedAuth,
|
||||
connectAuth: connectParams.auth,
|
||||
req: upgradeReq,
|
||||
trustedProxies,
|
||||
});
|
||||
let authOk = authResult.ok;
|
||||
let authMethod =
|
||||
authResult.method ?? (resolvedAuth.mode === "password" ? "password" : "token");
|
||||
if (!authOk && connectParams.auth?.token && device) {
|
||||
const tokenCheck = await verifyDeviceToken({
|
||||
deviceId: device.id,
|
||||
@@ -589,42 +639,11 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
}
|
||||
}
|
||||
if (!authOk) {
|
||||
setHandshakeState("failed");
|
||||
logWsControl.warn(
|
||||
`unauthorized conn=${connId} remote=${remoteAddr ?? "?"} client=${clientLabel} ${connectParams.client.mode} v${connectParams.client.version} reason=${authResult.reason ?? "unknown"}`,
|
||||
);
|
||||
const authProvided: AuthProvidedKind = connectParams.auth?.token
|
||||
? "token"
|
||||
: connectParams.auth?.password
|
||||
? "password"
|
||||
: "none";
|
||||
const authMessage = formatGatewayAuthFailureMessage({
|
||||
authMode: resolvedAuth.mode,
|
||||
authProvided,
|
||||
reason: authResult.reason,
|
||||
client: connectParams.client,
|
||||
});
|
||||
setCloseCause("unauthorized", {
|
||||
authMode: resolvedAuth.mode,
|
||||
authProvided,
|
||||
authReason: authResult.reason,
|
||||
allowTailscale: resolvedAuth.allowTailscale,
|
||||
client: connectParams.client.id,
|
||||
clientDisplayName: connectParams.client.displayName,
|
||||
mode: connectParams.client.mode,
|
||||
version: connectParams.client.version,
|
||||
});
|
||||
send({
|
||||
type: "res",
|
||||
id: frame.id,
|
||||
ok: false,
|
||||
error: errorShape(ErrorCodes.INVALID_REQUEST, authMessage),
|
||||
});
|
||||
close(1008, truncateCloseReason(authMessage));
|
||||
rejectUnauthorized();
|
||||
return;
|
||||
}
|
||||
|
||||
const skipPairing = allowControlUiBypass && hasSharedAuth;
|
||||
const skipPairing = allowControlUiBypass && sharedAuthOk;
|
||||
if (device && devicePublicKey && !skipPairing) {
|
||||
const requirePairing = async (reason: string, _paired?: { deviceId: string }) => {
|
||||
const pairing = await requestDevicePairing({
|
||||
|
||||
Reference in New Issue
Block a user