mirror of
https://github.com/anotherhadi/spilltea.git
synced 2026-08-21 10:55:48 +02:00
Merge pull request #12 from Chiloute/feat-jwt-decoder-plugin
feat: add plugin JWT Decoder
This commit is contained in:
@@ -0,0 +1,80 @@
|
|||||||
|
Plugin = {
|
||||||
|
name = "JWT Decoder",
|
||||||
|
description = [[
|
||||||
|
Decodes JWTs found in request headers and exposes the decoded payload inline.
|
||||||
|
|
||||||
|
For every request header whose value looks like a JWT (three dot-separated
|
||||||
|
base64url segments), the base64url-encoded payload is decoded and added back to
|
||||||
|
the request as a new header:
|
||||||
|
|
||||||
|
```
|
||||||
|
Authorization: Bearer eyJ... -> X-JWT-Decoded-Authorization: {"sub":"123",...}
|
||||||
|
X-Auth-Token: eyJ... -> X-JWT-Decoded-X-Auth-Token: {"sub":"123",...}
|
||||||
|
```
|
||||||
|
|
||||||
|
A leading `Bearer ` prefix is stripped from any header value before decoding,
|
||||||
|
so custom authorization headers (`Authorization-Test`, `X-Auth-Token`, ...)
|
||||||
|
are handled too. The decoded header is added to the outbound request, so it is visible
|
||||||
|
in the intercept, history and replay views (and forwarded upstream).
|
||||||
|
|
||||||
|
Pure Lua, no external dependencies.
|
||||||
|
]],
|
||||||
|
on_request = { sync = true },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Reverse lookup table: base64url character -> 6-bit value (built once at load time).
|
||||||
|
local B64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
|
local B64_DEC = {}
|
||||||
|
for i = 1, #B64_ALPHABET do
|
||||||
|
B64_DEC[B64_ALPHABET:sub(i, i)] = i - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Decode an unpadded base64url string. Returns the decoded string, or nil on invalid input.
|
||||||
|
local function b64url_decode(s)
|
||||||
|
if #s % 4 == 1 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local out, acc, bits = {}, 0, 0
|
||||||
|
for c in s:gmatch(".") do
|
||||||
|
local v = B64_DEC[c]
|
||||||
|
if not v then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
acc, bits = acc * 64 + v, bits + 6
|
||||||
|
if bits >= 8 then
|
||||||
|
bits = bits - 8
|
||||||
|
out[#out + 1] = string.char(math.floor(acc / 2 ^ bits))
|
||||||
|
acc = acc % 2 ^ bits
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.concat(out)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If value is a JWT, return its decoded payload
|
||||||
|
local function decode_jwt_payload(value)
|
||||||
|
-- Three dot-separated base64url segments; the signature may be empty (alg:none).
|
||||||
|
local payload = value:match("^[A-Za-z0-9_-]+%.([A-Za-z0-9_-]+)%.[A-Za-z0-9_-]*$")
|
||||||
|
if not payload or #payload > 8192 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local decoded = b64url_decode(payload)
|
||||||
|
if not decoded then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
-- Only accept plausible JSON payloads.
|
||||||
|
if decoded:match("^%s*{") == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
decoded = decoded:gsub("[%c]+", " ")
|
||||||
|
return decoded
|
||||||
|
end
|
||||||
|
|
||||||
|
function on_request(req)
|
||||||
|
for name, value in pairs(req.headers) do
|
||||||
|
local token = value:gsub("^[Bb][Ee][Aa][Rr][Ee][Rr]%s+", "")
|
||||||
|
local payload = decode_jwt_payload(token)
|
||||||
|
if payload then
|
||||||
|
req:set_header("X-JWT-Decoded-" .. name, payload)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user