mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-21 06:02:13 +00:00
fix(gateway): prefer bootstrap auth over tailscale (#59232)
* fix(gateway): prefer bootstrap auth over tailscale * fix(gateway): prefer bootstrap auth over tailscale (#59232) (thanks @ngutman)
This commit is contained in:
@@ -169,23 +169,58 @@ describe("resolveConnectAuthDecision", () => {
|
||||
expect(verifyDeviceToken).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns the original decision when device fallback does not apply", async () => {
|
||||
it("prefers a valid bootstrap token over an already successful shared auth path", async () => {
|
||||
const verifyBootstrapToken = vi.fn<VerifyBootstrapTokenFn>(async () => ({ ok: true }));
|
||||
const verifyDeviceToken = vi.fn<VerifyDeviceTokenFn>(async () => ({ ok: true }));
|
||||
const decision = await resolveConnectAuthDecision({
|
||||
state: createBaseState({
|
||||
authResult: { ok: true, method: "token" },
|
||||
authResult: { ok: true, method: "tailscale" },
|
||||
authOk: true,
|
||||
authMethod: "tailscale",
|
||||
bootstrapTokenCandidate: "bootstrap-token",
|
||||
deviceTokenCandidate: undefined,
|
||||
deviceTokenCandidateSource: undefined,
|
||||
}),
|
||||
hasDeviceIdentity: true,
|
||||
deviceId: "dev-1",
|
||||
publicKey: "pub-1",
|
||||
role: "operator",
|
||||
role: "node",
|
||||
scopes: [],
|
||||
verifyBootstrapToken: async () => ({ ok: false, reason: "bootstrap_token_invalid" }),
|
||||
verifyBootstrapToken,
|
||||
verifyDeviceToken,
|
||||
});
|
||||
expect(decision.authOk).toBe(true);
|
||||
expect(decision.authMethod).toBe("token");
|
||||
expect(decision.authMethod).toBe("bootstrap-token");
|
||||
expect(verifyBootstrapToken).toHaveBeenCalledOnce();
|
||||
expect(verifyDeviceToken).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps the original successful auth path when bootstrap validation fails", async () => {
|
||||
const verifyBootstrapToken = vi.fn<VerifyBootstrapTokenFn>(async () => ({
|
||||
ok: false,
|
||||
reason: "bootstrap_token_invalid",
|
||||
}));
|
||||
const verifyDeviceToken = vi.fn<VerifyDeviceTokenFn>(async () => ({ ok: true }));
|
||||
const decision = await resolveConnectAuthDecision({
|
||||
state: createBaseState({
|
||||
authResult: { ok: true, method: "tailscale" },
|
||||
authOk: true,
|
||||
authMethod: "tailscale",
|
||||
bootstrapTokenCandidate: "bootstrap-token",
|
||||
deviceTokenCandidate: undefined,
|
||||
deviceTokenCandidateSource: undefined,
|
||||
}),
|
||||
hasDeviceIdentity: true,
|
||||
deviceId: "dev-1",
|
||||
publicKey: "pub-1",
|
||||
role: "node",
|
||||
scopes: [],
|
||||
verifyBootstrapToken,
|
||||
verifyDeviceToken,
|
||||
});
|
||||
expect(decision.authOk).toBe(true);
|
||||
expect(decision.authMethod).toBe("tailscale");
|
||||
expect(verifyBootstrapToken).toHaveBeenCalledOnce();
|
||||
expect(verifyDeviceToken).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -170,13 +170,7 @@ export async function resolveConnectAuthDecision(params: {
|
||||
let authMethod = params.state.authMethod;
|
||||
|
||||
const bootstrapTokenCandidate = params.state.bootstrapTokenCandidate;
|
||||
if (
|
||||
params.hasDeviceIdentity &&
|
||||
params.deviceId &&
|
||||
params.publicKey &&
|
||||
!authOk &&
|
||||
bootstrapTokenCandidate
|
||||
) {
|
||||
if (params.hasDeviceIdentity && params.deviceId && params.publicKey && bootstrapTokenCandidate) {
|
||||
const tokenCheck = await params.verifyBootstrapToken({
|
||||
deviceId: params.deviceId,
|
||||
publicKey: params.publicKey,
|
||||
@@ -185,9 +179,14 @@ export async function resolveConnectAuthDecision(params: {
|
||||
scopes: params.scopes,
|
||||
});
|
||||
if (tokenCheck.ok) {
|
||||
// Prefer an explicit valid bootstrap token even when another auth path
|
||||
// (for example tailscale serve header auth) already succeeded. QR pairing
|
||||
// relies on the server classifying the handshake as bootstrap-token so the
|
||||
// initial node pairing can be silently auto-approved and the bootstrap
|
||||
// token can be revoked after approval.
|
||||
authOk = true;
|
||||
authMethod = "bootstrap-token";
|
||||
} else {
|
||||
} else if (!authOk) {
|
||||
authResult = { ok: false, reason: tokenCheck.reason ?? "bootstrap_token_invalid" };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user