diff mod_unified_push/mod_unified_push.lua @ 5149:fa56ed2bacab

mod_unified_push: Add support for multiple token backends, including stoage Now that we have ACLs by default, it is no longer necessary to be completely stateless. On 0.12, using storage has benefits over JWT, because it does not expose client JIDs to the push apps/services. In trunk, PASETO is stateless and does not expose client JIDs.
author Matthew Wild <mwild1@gmail.com>
date Sat, 14 Jan 2023 14:31:37 +0000
parents bf42f1401f1c
children 2b6c543c4d3a
line wrap: on
line diff
--- a/mod_unified_push/mod_unified_push.lua	Fri Jan 13 16:50:43 2023 +0000
+++ b/mod_unified_push/mod_unified_push.lua	Sat Jan 14 14:31:37 2023 +0000
@@ -37,33 +37,67 @@
 	return s;
 end
 
--- COMPAT w/0.12
-local function jwt_sign(data)
-	return jwt.sign(unified_push_secret, data);
+local push_store = module:open_store();
+
+local backends = {
+	jwt = {
+		sign = function (data)
+			return jwt.sign(unified_push_secret, data);
+		end;
+
+		verify = function (token)
+			local ok, result = jwt.verify(unified_push_secret, token);
+
+			if not ok then
+				return ok, result;
+			end
+			if result.exp and result.exp < os.time() then
+				return nil, "token-expired";
+			end
+			return ok, result;
+		end;
+	};
+
+	storage = {
+		sign = function (data)
+			local reg_id = id.long();
+			local user, host = jid.split(data.sub);
+			if host ~= module.host or not user then
+				return;
+			end
+			push_store:set(reg_id, data);
+			return reg_id;
+		end;
+		verify = function (token)
+			local data = push_store:get(token);
+			if not data then
+				return nil, "item-not-found";
+			elseif data.exp and data.exp < os.time() then
+				push_store:set(token, nil);
+				return nil, "token-expired";
+			end
+			return data;
+		end;
+	};
+};
+
+if pcall(require, "util.paseto") then
+	local sign, verify = require "util.paseto".init(unified_push_secret);
+	backends.paseto = { sign = sign, verify = verify };
 end
 
--- COMPAT w/0.12: add expiry check
-local function jwt_verify(token)
-	local ok, result = jwt.verify(unified_push_secret, token);
-
-	if not ok then
-		return ok, result;
-	end
-	if result.exp and result.exp < os.time() then
-		return nil, "token-expired";
-	end
-	return ok, result;
-end
+local backend = module:get_option_string("unified_push_backend", backends.paseto and "paseto" or "storage");
 
 local function register_route(params)
 	local expiry = os.time() + push_registration_ttl;
+	local token = backends[backend].sign({
+		instance = params.instance;
+		application = params.application;
+		sub = params.jid;
+		exp = expiry;
+	});
 	return {
-		url = module:http_url("push").."/"..urlencode(jwt_sign(unified_push_secret, {
-			instance = params.instance;
-			application = params.application;
-			sub = params.jid;
-			exp = expiry;
-		}));
+		url = module:http_url("push").."/"..urlencode(token);
 		expiry = expiry;
 	};
 end
@@ -105,7 +139,7 @@
 -- Handle incoming POST
 function handle_push(event, subpath)
 	module:log("debug", "Incoming push received!");
-	local ok, data = jwt_verify(subpath);
+	local ok, data = backends[backend].verify(subpath);
 	if not ok then
 		module:log("debug", "Received push to unacceptable token (%s)", data);
 		return 404;