Mercurial > prosody-modules
view mod_couchdb/couchdb/json.lib.lua @ 5193:2bb29ece216b
mod_http_oauth2: Implement stateless dynamic client registration
Replaces previous explicit registration that required either the
additional module mod_adhoc_oauth2_client or manually editing the
database. That method was enough to have something to test with, but
would not probably not scale easily.
Dynamic client registration allows creating clients on the fly, which
may be even easier in theory.
In order to not allow basically unauthenticated writes to the database,
we implement a stateless model here.
per_host_key := HMAC(config -> oauth2_registration_key, hostname)
client_id := JWT { client metadata } signed with per_host_key
client_secret := HMAC(per_host_key, client_id)
This should ensure everything we need to know is part of the client_id,
allowing redirects etc to be validated, and the client_secret can be
validated with only the client_id and the per_host_key.
A nonce injected into the client_id JWT should ensure nobody can submit
the same client metadata and retrieve the same client_secret
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Fri, 03 Mar 2023 21:14:19 +0100 |
parents | 316d7c8e1fb0 |
children |
line wrap: on
line source
local type = type; local t_insert, t_concat, t_remove = table.insert, table.concat, table.remove; local s_char = string.char; local tostring, tonumber = tostring, tonumber; local pairs, ipairs = pairs, ipairs; local next = next; local error = error; local newproxy, getmetatable = newproxy, getmetatable; local print = print; --module("json") local _M = {}; local null = newproxy and newproxy(true) or {}; if getmetatable and getmetatable(null) then getmetatable(null).__tostring = function() return "null"; end; end _M.null = null; local escapes = { ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"}; local unescapes = { ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", b = "\b", f = "\f", n = "\n", r = "\r", t = "\t"}; for i=0,31 do local ch = s_char(i); if not escapes[ch] then escapes[ch] = ("\\u%.4X"):format(i); end end local valid_types = { number = true, string = true, table = true, boolean = true }; local special_keys = { __array = true; __hash = true; }; local simplesave, tablesave, arraysave, stringsave; function stringsave(o, buffer) -- FIXME do proper utf-8 and binary data detection t_insert(buffer, "\""..(o:gsub(".", escapes)).."\""); end function arraysave(o, buffer) t_insert(buffer, "["); if next(o) then for i,v in ipairs(o) do simplesave(v, buffer); t_insert(buffer, ","); end t_remove(buffer); end t_insert(buffer, "]"); end function tablesave(o, buffer) local __array = {}; local __hash = {}; local hash = {}; for i,v in ipairs(o) do __array[i] = v; end for k,v in pairs(o) do local ktype, vtype = type(k), type(v); if valid_types[vtype] or v == null then if ktype == "string" and not special_keys[k] then hash[k] = v; elseif (valid_types[ktype] or k == null) and __array[k] == nil then __hash[k] = v; end end end if next(__hash) ~= nil or next(hash) ~= nil or next(__array) == nil then t_insert(buffer, "{"); local mark = #buffer; for k,v in pairs(hash) do stringsave(k, buffer); t_insert(buffer, ":"); simplesave(v, buffer); t_insert(buffer, ","); end if next(__hash) ~= nil then t_insert(buffer, "\"__hash\":["); for k,v in pairs(__hash) do simplesave(k, buffer); t_insert(buffer, ","); simplesave(v, buffer); t_insert(buffer, ","); end t_remove(buffer); t_insert(buffer, "]"); t_insert(buffer, ","); end if next(__array) then t_insert(buffer, "\"__array\":"); arraysave(__array, buffer); t_insert(buffer, ","); end if mark ~= #buffer then t_remove(buffer); end t_insert(buffer, "}"); else arraysave(__array, buffer); end end function simplesave(o, buffer) local t = type(o); if t == "number" then t_insert(buffer, tostring(o)); elseif t == "string" then stringsave(o, buffer); elseif t == "table" then tablesave(o, buffer); elseif t == "boolean" then t_insert(buffer, (o and "true" or "false")); else t_insert(buffer, "null"); end end function _M.encode(obj) local t = {}; simplesave(obj, t); return t_concat(t); end ----------------------------------- function _M.decode(json) local pos = 1; local current = {}; local stack = {}; local ch, peek; local function next() ch = json:sub(pos, pos); pos = pos+1; peek = json:sub(pos, pos); return ch; end local function skipwhitespace() while ch and (ch == "\r" or ch == "\n" or ch == "\t" or ch == " ") do next(); end end local function skiplinecomment() repeat next(); until not(ch) or ch == "\r" or ch == "\n"; skipwhitespace(); end local function skipstarcomment() next(); next(); -- skip '/', '*' while peek and ch ~= "*" and peek ~= "/" do next(); end if not peek then error("eof in star comment") end next(); next(); -- skip '*', '/' skipwhitespace(); end local function skipstuff() while true do skipwhitespace(); if ch == "/" and peek == "*" then skipstarcomment(); elseif ch == "/" and peek == "*" then skiplinecomment(); else return; end end end local readvalue; local function readarray() local t = {}; next(); -- skip '[' skipstuff(); if ch == "]" then next(); return t; end t_insert(t, readvalue()); while true do skipstuff(); if ch == "]" then next(); return t; end if not ch then error("eof while reading array"); elseif ch == "," then next(); elseif ch then error("unexpected character in array, comma expected"); end if not ch then error("eof while reading array"); end t_insert(t, readvalue()); end end local function checkandskip(c) local x = ch or "eof"; if x ~= c then error("unexpected "..x..", '"..c.."' expected"); end next(); end local function readliteral(lit, val) for c in lit:gmatch(".") do checkandskip(c); end return val; end local function readstring() local s = ""; checkandskip("\""); while ch do while ch and ch ~= "\\" and ch ~= "\"" do s = s..ch; next(); end if ch == "\\" then next(); if unescapes[ch] then s = s..unescapes[ch]; next(); elseif ch == "u" then local seq = ""; for i=1,4 do next(); if not ch then error("unexpected eof in string"); end if not ch:match("[0-9a-fA-F]") then error("invalid unicode escape sequence in string"); end seq = seq..ch; end s = s..s.char(tonumber(seq, 16)); -- FIXME do proper utf-8 next(); else error("invalid escape sequence in string"); end end if ch == "\"" then next(); return s; end end error("eof while reading string"); end local function readnumber() local s = ""; if ch == "-" then s = s..ch; next(); if not ch:match("[0-9]") then error("number format error"); end end if ch == "0" then s = s..ch; next(); if ch:match("[0-9]") then error("number format error"); end else while ch and ch:match("[0-9]") do s = s..ch; next(); end end if ch == "." then s = s..ch; next(); if not ch:match("[0-9]") then error("number format error"); end while ch and ch:match("[0-9]") do s = s..ch; next(); end if ch == "e" or ch == "E" then s = s..ch; next(); if ch == "+" or ch == "-" then s = s..ch; next(); if not ch:match("[0-9]") then error("number format error"); end while ch and ch:match("[0-9]") do s = s..ch; next(); end end end end return tonumber(s); end local function readmember(t) local k = readstring(); checkandskip(":"); t[k] = readvalue(); end local function fixobject(obj) local __array = obj.__array; if __array then obj.__array = nil; for i,v in ipairs(__array) do t_insert(obj, v); end end local __hash = obj.__hash; if __hash then obj.__hash = nil; local k; for i,v in ipairs(__hash) do if k ~= nil then obj[k] = v; k = nil; else k = v; end end end return obj; end local function readobject() local t = {}; next(); -- skip '{' skipstuff(); if ch == "}" then next(); return t; end if not ch then error("eof while reading object"); end readmember(t); while true do skipstuff(); if ch == "}" then next(); return fixobject(t); end if not ch then error("eof while reading object"); elseif ch == "," then next(); elseif ch then error("unexpected character in object, comma expected"); end if not ch then error("eof while reading object"); end readmember(t); end end function readvalue() skipstuff(); while ch do if ch == "{" then return readobject(); elseif ch == "[" then return readarray(); elseif ch == "\"" then return readstring(); elseif ch:match("[%-0-9%.]") then return readnumber(); elseif ch == "n" then return readliteral("null", null); elseif ch == "t" then return readliteral("true", true); elseif ch == "f" then return readliteral("false", false); end end error("eof while reading value"); end next(); return readvalue(); end function _M.test(object) local encoded = encode(object); local decoded = decode(encoded); local recoded = encode(decoded); if encoded ~= recoded then print("FAILED"); print("encoded:", encoded); print("recoded:", recoded); else print(encoded); end return encoded ~= recoded; end return _M;