Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ on:
pull_request:

jobs:
fmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: JohnnyMorganz/stylua-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: latest
args: --check jsonwebtoken/ spec/

spec:
strategy:
matrix:
Expand Down
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,12 @@ test:
lint:
luacheck jsonwebtoken spec

.PHONY: test lint
# Format Lua sources in place with stylua.
fmt:
stylua jsonwebtoken/ spec/

# Verify formatting without writing; fails if anything is out of style.
fmt-check:
stylua --check jsonwebtoken/ spec/

.PHONY: test lint fmt fmt-check
19 changes: 8 additions & 11 deletions jsonwebtoken/base64url.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

local base64url = {}

local ALPHABET =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
local ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"

local ENCODE = {} -- 6-bit value -> character
local DECODE = {} -- character -> 6-bit value
Expand All @@ -22,8 +21,7 @@ function base64url.encode(s)
for i = 1, whole, 3 do
local a, b, c = s:byte(i, i + 2)
local v = a << 16 | b << 8 | c
out[#out + 1] = ENCODE[v >> 18] .. ENCODE[v >> 12 & 63]
.. ENCODE[v >> 6 & 63] .. ENCODE[v & 63]
out[#out + 1] = ENCODE[v >> 18] .. ENCODE[v >> 12 & 63] .. ENCODE[v >> 6 & 63] .. ENCODE[v & 63]
end
local rem = #s % 3
if rem == 1 then
Expand All @@ -32,8 +30,7 @@ function base64url.encode(s)
elseif rem == 2 then
local a, b = s:byte(whole + 1, whole + 2)
local v = a << 8 | b
out[#out + 1] = ENCODE[v >> 10] .. ENCODE[v >> 4 & 63]
.. ENCODE[v << 2 & 63]
out[#out + 1] = ENCODE[v >> 10] .. ENCODE[v >> 4 & 63] .. ENCODE[v << 2 & 63]
end
return table.concat(out)
end
Expand All @@ -51,23 +48,23 @@ function base64url.decode(s)
local out = {}
local whole = #s - rem
for i = 1, whole, 4 do
local a, b, c, d = DECODE[s:sub(i, i)], DECODE[s:sub(i + 1, i + 1)],
DECODE[s:sub(i + 2, i + 2)], DECODE[s:sub(i + 3, i + 3)]
local a, b, c, d =
DECODE[s:sub(i, i)], DECODE[s:sub(i + 1, i + 1)], DECODE[s:sub(i + 2, i + 2)], DECODE[s:sub(i + 3, i + 3)]
if not (a and b and c and d) then
return nil
end
local v = a << 18 | b << 12 | c << 6 | d
out[#out + 1] = string.char(v >> 16, v >> 8 & 255, v & 255)
end
if rem == 2 then
local a, b = DECODE[s:sub(whole + 1, whole + 1)],
DECODE[s:sub(whole + 2, whole + 2)]
local a, b = DECODE[s:sub(whole + 1, whole + 1)], DECODE[s:sub(whole + 2, whole + 2)]
if not (a and b) then
return nil
end
out[#out + 1] = string.char(a << 2 | b >> 4)
elseif rem == 3 then
local a, b, c = DECODE[s:sub(whole + 1, whole + 1)],
local a, b, c =
DECODE[s:sub(whole + 1, whole + 1)],
DECODE[s:sub(whole + 2, whole + 2)],
DECODE[s:sub(whole + 3, whole + 3)]
if not (a and b and c) then
Expand Down
30 changes: 9 additions & 21 deletions jsonwebtoken/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ end
local function checked_alg(opts)
local alg = opts.alg or DEFAULT_ALG
if not HMAC_HASH[alg] then
error(("Unsupported algorithm %q (HS256, HS384 or HS512)")
:format(tostring(alg)), 3)
error(("Unsupported algorithm %q (HS256, HS384 or HS512)"):format(tostring(alg)), 3)
end
return alg
end
Expand Down Expand Up @@ -93,15 +92,13 @@ function jwt.sign(claims, secret, opts)
if opts.header then
for k, v in pairs(opts.header) do
if k == "alg" or k == "typ" then
error(("%q cannot be overridden through opts.header")
:format(k), 2)
error(("%q cannot be overridden through opts.header"):format(k), 2)
end
header[k] = v
end
end

local signing_input = base64url.encode(json.encode(header))
.. "." .. base64url.encode(json.encode(claims))
local signing_input = base64url.encode(json.encode(header)) .. "." .. base64url.encode(json.encode(claims))
local signature = sha2.hmac(HMAC_HASH[alg], secret, signing_input)
return signing_input .. "." .. base64url.encode(signature)
end
Expand Down Expand Up @@ -136,8 +133,7 @@ function jwt.verify(token, secret, opts)
opts = opts or {}
local alg = checked_alg(opts)

local header_b64, claims_b64, signature_b64 =
token:match("^([^.]+)%.([^.]+)%.([^.]+)$")
local header_b64, claims_b64, signature_b64 = token:match("^([^.]+)%.([^.]+)%.([^.]+)$")
if not header_b64 then
return fail("malformed", "Token is not three dot-separated parts")
end
Expand All @@ -148,17 +144,14 @@ function jwt.verify(token, secret, opts)
return fail("malformed", "Header is not base64url-encoded JSON")
end
if header.alg ~= alg then
return fail("invalid_algorithm",
("Token is signed with %s, expected %s")
:format(tostring(header.alg), alg))
return fail("invalid_algorithm", ("Token is signed with %s, expected %s"):format(tostring(header.alg), alg))
end

local signature = base64url.decode(signature_b64)
if not signature then
return fail("malformed", "Signature is not valid base64url")
end
local expected = sha2.hmac(HMAC_HASH[alg], secret,
header_b64 .. "." .. claims_b64)
local expected = sha2.hmac(HMAC_HASH[alg], secret, header_b64 .. "." .. claims_b64)
if not constant_time_equal(signature, expected) then
return fail("invalid_signature", "Signature does not match")
end
Expand Down Expand Up @@ -190,18 +183,13 @@ function jwt.verify(token, secret, opts)
end

if opts.iss ~= nil and claims.iss ~= opts.iss then
return fail("invalid_issuer",
("Issuer is %s, expected %s")
:format(tostring(claims.iss), opts.iss))
return fail("invalid_issuer", ("Issuer is %s, expected %s"):format(tostring(claims.iss), opts.iss))
end
if opts.aud ~= nil and not audience_matches(claims.aud, opts.aud) then
return fail("invalid_audience",
("Audience does not include %s"):format(opts.aud))
return fail("invalid_audience", ("Audience does not include %s"):format(opts.aud))
end
if opts.sub ~= nil and claims.sub ~= opts.sub then
return fail("invalid_subject",
("Subject is %s, expected %s")
:format(tostring(claims.sub), opts.sub))
return fail("invalid_subject", ("Subject is %s, expected %s"):format(tostring(claims.sub), opts.sub))
end

return claims
Expand Down
19 changes: 15 additions & 4 deletions jsonwebtoken/json.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ local json = {}
-- Encoding --------------------------------------------------------------

local ESCAPE = {
['"'] = '\\"', ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t",
['"'] = '\\"',
["\\"] = "\\\\",
["\b"] = "\\b",
["\f"] = "\\f",
["\n"] = "\\n",
["\r"] = "\\r",
["\t"] = "\\t",
}

local function escape_char(c)
Expand Down Expand Up @@ -97,8 +102,14 @@ end
-- Decoding --------------------------------------------------------------

local UNESCAPE = {
['"'] = '"', ["\\"] = "\\", ["/"] = "/",
b = "\b", f = "\f", n = "\n", r = "\r", t = "\t",
['"'] = '"',
["\\"] = "\\",
["/"] = "/",
b = "\b",
f = "\f",
n = "\n",
r = "\r",
t = "\t",
}

local function fail_at(pos, msg)
Expand Down
Loading
Loading