From 9a7bcf73bbe068337ac6a2cfcb4a6442c92e230b Mon Sep 17 00:00:00 2001 From: Ngo Quoc Viet <123613986+NgoQuocViet2001@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:13:57 +0700 Subject: [PATCH] fix(auth-oauth1): use the signing key as the PLAINTEXT signature (#605) --- plugins/auth-oauth1/package.json | 3 +- plugins/auth-oauth1/src/index.ts | 5 ++- plugins/auth-oauth1/tests/plaintext.test.ts | 42 +++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 plugins/auth-oauth1/tests/plaintext.test.ts diff --git a/plugins/auth-oauth1/package.json b/plugins/auth-oauth1/package.json index 25f40aab..6277ec19 100644 --- a/plugins/auth-oauth1/package.json +++ b/plugins/auth-oauth1/package.json @@ -11,7 +11,8 @@ }, "scripts": { "build": "yaakcli build", - "dev": "yaakcli dev" + "dev": "yaakcli dev", + "test": "vp test --run tests" }, "dependencies": { "oauth-1.0a": "^2.2.6" diff --git a/plugins/auth-oauth1/src/index.ts b/plugins/auth-oauth1/src/index.ts index e104151e..e2f8c031 100644 --- a/plugins/auth-oauth1/src/index.ts +++ b/plugins/auth-oauth1/src/index.ts @@ -202,7 +202,10 @@ function hashFunction(signatureMethod: SigMethod) { return (base: string, privateKey: string) => crypto.createSign("RSA-SHA512").update(base).sign(privateKey, "base64"); case signatures.PLAINTEXT: - return (base: string) => base; + // RFC 5849 3.4.4: the PLAINTEXT signature IS the signing key, + // `encoded(consumer secret)&encoded(token secret)`. Returning the base + // string put the whole percent-encoded request into oauth_signature. + return (_base: string, key: string) => key; default: return (base: string, key: string) => crypto.createHmac("sha1", key).update(base).digest("base64"); diff --git a/plugins/auth-oauth1/tests/plaintext.test.ts b/plugins/auth-oauth1/tests/plaintext.test.ts new file mode 100644 index 00000000..f2517f60 --- /dev/null +++ b/plugins/auth-oauth1/tests/plaintext.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, test } from "vite-plus/test"; +import { plugin } from "../src"; + +function sign(values: Record): string { + const result = plugin.authentication!.onApply!( + {} as never, + { + values, + method: "GET", + url: "https://api.example.com/resource", + } as never, + ) as { setHeaders: { name: string; value: string }[] }; + const header = result.setHeaders[0]!.value; + const match = header.match(/oauth_signature="([^"]*)"/); + return decodeURIComponent(match![1]!); +} + +describe("PLAINTEXT signature", () => { + const base = { + signatureMethod: "PLAINTEXT", + consumerKey: "ck", + consumerSecret: "cs", + nonce: "abc123", + timestamp: "1700000000", + }; + + // RFC 5849 3.4.4: the PLAINTEXT signature is the signing key itself -- + // encoded(consumer secret) "&" encoded(token secret) -- not the base string. + test("is the signing key, not the signature base string", () => { + expect(sign({ ...base, tokenKey: "tk", tokenSecret: "ts" })).toBe("cs&ts"); + }); + + test("keeps the trailing separator when there is no token secret", () => { + expect(sign(base)).toBe("cs&"); + }); + + test("percent-encodes reserved characters in the secrets", () => { + expect(sign({ ...base, consumerSecret: "c s", tokenKey: "tk", tokenSecret: "t&s" })).toBe( + "c%20s&t%26s", + ); + }); +});