mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 04:31:10 +00:00
fix: detect ?token= and suggest #token= fragment syntax
When users visit the Control UI with ?token=<token>, they see "device identity required" with no hint about the correct URL format. This change: - Detects when token is read from query string vs URL fragment - Warns via console when ?token= is used - Shows an inline hint in the overview error area directing users to use #token=<token> instead Fixes #54842
This commit is contained in:
committed by
Peter Steinberger
parent
cfb7779584
commit
39c721d382
@@ -12,6 +12,7 @@ import {
|
||||
renderTopbarThemeModeToggle,
|
||||
switchChatSession,
|
||||
} from "./app-render.helpers.ts";
|
||||
import { warnQueryToken } from "./app-settings.ts";
|
||||
import type { AppViewState } from "./app-view-state.ts";
|
||||
import { loadAgentFileContent, loadAgentFiles, saveAgentFile } from "./controllers/agent-files.ts";
|
||||
import { loadAgentIdentities, loadAgentIdentity } from "./controllers/agent-identity.ts";
|
||||
@@ -737,6 +738,7 @@ export function renderApp(state: AppViewState) {
|
||||
cronEnabled: state.cronStatus?.enabled ?? null,
|
||||
cronNext,
|
||||
lastChannelsRefresh: state.channelsLastSuccess,
|
||||
warnQueryToken,
|
||||
usageResult: state.usageResult,
|
||||
sessionsResult: state.sessionsResult,
|
||||
skillsReport: state.skillsReport,
|
||||
|
||||
@@ -97,6 +97,9 @@ export function setLastActiveSessionKey(host: SettingsHost, next: string) {
|
||||
applySettings(host, { ...host.settings, lastActiveSessionKey: trimmed });
|
||||
}
|
||||
|
||||
/** Set to true when the token is read from a query string (?token=) instead of a URL fragment. */
|
||||
export let warnQueryToken = false;
|
||||
|
||||
export function applySettingsFromUrl(host: SettingsHost) {
|
||||
if (!window.location.search && !window.location.hash) {
|
||||
return;
|
||||
@@ -111,7 +114,9 @@ export function applySettingsFromUrl(host: SettingsHost) {
|
||||
// Prefer fragment tokens over query tokens. Fragments avoid server-side request
|
||||
// logs and referrer leakage; query-param tokens remain a one-time legacy fallback
|
||||
// for compatibility with older deep links.
|
||||
const tokenRaw = hashParams.get("token") ?? params.get("token");
|
||||
const queryToken = params.get("token");
|
||||
const hashToken = hashParams.get("token");
|
||||
const tokenRaw = hashToken ?? queryToken;
|
||||
const passwordRaw = params.get("password") ?? hashParams.get("password");
|
||||
const sessionRaw = params.get("session") ?? hashParams.get("session");
|
||||
const shouldResetSessionForToken = Boolean(
|
||||
@@ -125,6 +130,12 @@ export function applySettingsFromUrl(host: SettingsHost) {
|
||||
}
|
||||
|
||||
if (tokenRaw != null) {
|
||||
if (queryToken != null) {
|
||||
warnQueryToken = true;
|
||||
console.warn(
|
||||
"[openclaw] Auth token passed as query parameter (?token=). Use URL fragment instead: #token=<token>. Query parameters may appear in server logs.",
|
||||
);
|
||||
}
|
||||
const token = tokenRaw.trim();
|
||||
if (token && gatewayUrlChanged) {
|
||||
host.pendingGatewayToken = token;
|
||||
|
||||
@@ -266,6 +266,7 @@ function createOverviewProps(overrides: Partial<OverviewProps> = {}): OverviewPr
|
||||
cronEnabled: null,
|
||||
cronNext: null,
|
||||
lastChannelsRefresh: null,
|
||||
warnQueryToken: false,
|
||||
usageResult: null,
|
||||
sessionsResult: null,
|
||||
skillsReport: null,
|
||||
|
||||
@@ -37,6 +37,7 @@ export type OverviewProps = {
|
||||
cronEnabled: boolean | null;
|
||||
cronNext: number | null;
|
||||
lastChannelsRefresh: number | null;
|
||||
warnQueryToken: boolean;
|
||||
// New dashboard data
|
||||
usageResult: SessionsUsageResult | null;
|
||||
sessionsResult: SessionsListResult | null;
|
||||
@@ -191,6 +192,28 @@ export function renderOverview(props: OverviewProps) {
|
||||
`;
|
||||
})();
|
||||
|
||||
const queryTokenHint = (() => {
|
||||
if (props.connected || !props.lastError || !props.warnQueryToken) {
|
||||
return null;
|
||||
}
|
||||
const lower = props.lastError.toLowerCase();
|
||||
const authFailed =
|
||||
lower.includes("unauthorized") ||
|
||||
lower.includes("connect failed") ||
|
||||
lower.includes("device identity required");
|
||||
if (!authFailed) {
|
||||
return null;
|
||||
}
|
||||
return html`
|
||||
<div class="muted" style="margin-top: 8px">
|
||||
Auth token must be passed as a URL fragment:
|
||||
<span class="mono">#token=<token></span>. Query parameters (<span class="mono"
|
||||
>?token=</span
|
||||
>) may appear in server logs.
|
||||
</div>
|
||||
`;
|
||||
})();
|
||||
|
||||
const currentLocale = isSupportedLocale(props.settings.locale)
|
||||
? props.settings.locale
|
||||
: i18n.getLocale();
|
||||
@@ -377,6 +400,7 @@ export function renderOverview(props: OverviewProps) {
|
||||
? html`<div class="callout danger" style="margin-top: 14px;">
|
||||
<div>${props.lastError}</div>
|
||||
${pairingHint ?? ""} ${authHint ?? ""} ${insecureContextHint ?? ""}
|
||||
${queryTokenHint ?? ""}
|
||||
</div>`
|
||||
: html`
|
||||
<div class="callout" style="margin-top: 14px">
|
||||
|
||||
Reference in New Issue
Block a user