fix(07-05): pay IN-01/IN-02/IN-03 — dead export gone, wrangler comment truthful, 900 s boundary pinned
- IN-01: delete the never-constructed ParsedInitData export (sole repo hit was its own declaration); the three read-helper catches now carry Intent: comments stating malformed-vs-tampered precedence and the chat-degrade law - IN-02: wrangler.jsonc line 2 states the post-fact truth — the chat_sessions namespace id below is live, no re-creation - IN-03: new T4 vector 'age 900 s → ok:true (the boundary is INCLUSIVE)' pins the strictly-greater comparator's inclusive edge (T-07-08); suite 126/126
This commit is contained in:
@@ -56,15 +56,6 @@ export interface WebAppInitChat {
|
||||
photo_url?: string;
|
||||
}
|
||||
|
||||
/** Shape of a fully parsed initData payload (hash/signature excluded from
|
||||
* rawFields — they are auth metadata, not payload fields). */
|
||||
export interface ParsedInitData {
|
||||
user: WebAppInitUser;
|
||||
chat?: WebAppInitChat;
|
||||
authDate: number;
|
||||
rawFields: Map<string, string>;
|
||||
}
|
||||
|
||||
export interface ValidateInitDataOptions {
|
||||
/** "Now" in seconds-since-epoch — injectable for deterministic tests. */
|
||||
nowSeconds?: number;
|
||||
@@ -95,8 +86,9 @@ function parseFields(raw: string): Map<string, string> {
|
||||
try {
|
||||
fields.set(key, decodeURIComponent(value));
|
||||
} catch {
|
||||
// Undecodable percent-escape — keep raw so HMAC compare fails ("tampered")
|
||||
// rather than silently dropping the field.
|
||||
// Intent: an undecodable percent-escape keeps the RAW value so the HMAC
|
||||
// compare fails ("tampered") rather than silently dropping the field —
|
||||
// malformed-vs-tampered precedence (D-14).
|
||||
fields.set(key, value);
|
||||
}
|
||||
}
|
||||
@@ -160,6 +152,8 @@ export async function validateInitData(
|
||||
user = parsed as WebAppInitUser;
|
||||
}
|
||||
} catch {
|
||||
// Intent: a malformed user payload is the "malformed" class, not a
|
||||
// tampered one — the field never reaches the HMAC path.
|
||||
user = undefined;
|
||||
}
|
||||
}
|
||||
@@ -209,6 +203,8 @@ export async function validateInitData(
|
||||
chat = parsedChat as WebAppInitChat;
|
||||
}
|
||||
} catch {
|
||||
// Intent: a malformed chat payload degrades to "no chat context" (a
|
||||
// private chat) — it never fails an otherwise-valid private-chat payload.
|
||||
chat = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +188,14 @@ test("T4 freshness boundary: age 901 s → reason stale", async () => {
|
||||
assert.equal(res.reason, "stale");
|
||||
});
|
||||
|
||||
test("T4 freshness boundary: age 900 s → ok:true (the boundary is INCLUSIVE)", async () => {
|
||||
const validateInitData = await loadValidator();
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const raw = signedUser(now - 900);
|
||||
const res = await validateInitData(raw, TEST_TOKEN);
|
||||
assert.equal(res.ok, true, `expected ok:true at exactly 900 s, got ${JSON.stringify(res)}`);
|
||||
});
|
||||
|
||||
/* ————— T5 — field order independence (RESEARCH Pitfall 1) ————— */
|
||||
|
||||
test("T5 fields arriving out of alphabetical order still validate", async () => {
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
// KV namespace id is filled in plan 03 task 1 after `npx wrangler kv namespace create chat_sessions`
|
||||
// chat_sessions namespace created via `npx wrangler kv namespace create chat_sessions` — the id below is live.
|
||||
"name": "araharah-bot",
|
||||
"main": "src/webhook.ts",
|
||||
"compatibility_date": "2026-10-22",
|
||||
|
||||
Reference in New Issue
Block a user