aboutsummaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
authorMatthew Wild <mwild1@gmail.com>2023-03-26 16:46:48 +0100
committerMatthew Wild <mwild1@gmail.com>2023-03-26 16:46:48 +0100
commit8743ea29d59256a4a8e4b4a63ad458fb04b512af (patch)
treee3c4c3a9da0d1c34e0cd10b4afd7868ed371ee88 /plugins
parent295c27aa996abe685e99f3f7e35784a516c3e0a0 (diff)
downloadprosody-8743ea29d59256a4a8e4b4a63ad458fb04b512af.tar.gz
prosody-8743ea29d59256a4a8e4b4a63ad458fb04b512af.zip
mod_tokenauth: Support for creating sub-tokens
Properties of sub-tokens: - They share the same id as their parent token - Sub-tokens may not have their own sub-tokens (but may have sibling tokens) - They always have the same or shorter lifetime compared to their parent token - Revoking a parent token revokes all sub-tokens - Sub-tokens always have the same JID as the parent token - They do not have their own 'accessed' property - accessing a sub-token updates the parent token's accessed time Although this is a generic API, it is designed to at least fill the needs of OAuth2 refresh + access tokens (where the parent token is the refresh token and the sub-tokens are access tokens).
Diffstat (limited to 'plugins')
-rw-r--r--plugins/mod_tokenauth.lua132
1 files changed, 110 insertions, 22 deletions
diff --git a/plugins/mod_tokenauth.lua b/plugins/mod_tokenauth.lua
index 3661d8bf..fe10d621 100644
--- a/plugins/mod_tokenauth.lua
+++ b/plugins/mod_tokenauth.lua
@@ -65,6 +65,74 @@ function create_jid_token(actor_jid, token_jid, token_role, token_ttl, token_dat
return token, token_info;
end
+function create_sub_token(actor_jid, parent_id, token_role, token_ttl, token_data, token_purpose)
+ local username, host = jid.split(actor_jid);
+ if host ~= module.host then
+ return nil, "invalid-host";
+ end
+
+ if (token_data and type(token_data) ~= "table") or (token_purpose and type(token_purpose) ~= "string") then
+ return nil, "bad-request";
+ end
+
+ -- Find parent token
+ local parent_token = token_store:get(username, parent_id);
+ if not parent_token then return nil; end
+ local token_info = parent_token.token_info;
+
+ local now = os.time();
+ local expires = token_info.expires; -- Default to same expiry as parent token
+ if token_ttl then
+ if expires then
+ -- Parent token has an expiry, so limit to that or shorter
+ expires = math.min(now + token_ttl, expires);
+ else
+ -- Parent token never expires, just add whatever expiry is requested
+ expires = now + token_ttl;
+ end
+ end
+
+ local sub_token_info = {
+ id = parent_id;
+ type = "subtoken";
+ role = token_role or token_info.role;
+ jid = token_info.jid;
+ created = now;
+ expires = expires;
+ purpose = token_purpose or token_info.purpose;
+ data = token_data;
+ };
+
+ local sub_tokens = parent_token.sub_tokens;
+ if not sub_tokens then
+ sub_tokens = {};
+ parent_token.sub_tokens = sub_tokens;
+ end
+
+ local sub_token_secret = random.bytes(18);
+ sub_tokens[hashes.sha256(sub_token_secret, true)] = sub_token_info;
+
+ local sub_token = "secret-token:"..base64.encode("2;"..token_info.id..";"..sub_token_secret..";"..token_info.jid);
+
+ local ok, err = token_store:set(username, parent_id, parent_token);
+ if not ok then
+ return nil, err;
+ end
+
+ return sub_token, sub_token_info;
+end
+
+local function clear_expired_sub_tokens(username, token_id)
+ local sub_tokens = token_store:get_key(username, token_id, "sub_tokens");
+ if not sub_tokens then return; end
+ local now = os.time();
+ for secret, info in pairs(sub_tokens) do
+ if info.expires < now then
+ sub_tokens[secret] = nil;
+ end
+ end
+end
+
local function parse_token(encoded_token)
if not encoded_token then return nil; end
local encoded_data = encoded_token:match("^secret%-token:(.+)$");
@@ -77,6 +145,41 @@ local function parse_token(encoded_token)
return token_id, token_user, token_host, token_secret;
end
+local function _validate_token_info(token_user, token_id, token_info, sub_token_info)
+ local now = os.time();
+ if token_info.expires and token_info.expires < now then
+ if token_info.type == "subtoken" then
+ clear_expired_sub_tokens(token_user, token_id);
+ else
+ token_store:set(token_user, token_id, nil);
+ end
+ return nil, "not-authorized";
+ end
+
+ if token_info.type ~= "subtoken" then
+ local account_info = usermanager.get_account_info(token_user, module.host);
+ local password_updated_at = account_info and account_info.password_updated;
+ if password_updated_at and password_updated_at > token_info.created then
+ token_store:set(token_user, token_id, nil);
+ return nil, "not-authorized";
+ end
+
+ -- Update last access time if necessary
+ local last_accessed = token_info.accessed;
+ if not last_accessed or (now - last_accessed) > access_time_granularity then
+ token_info.accessed = now;
+ token_store:set_key(token_user, token_id, "token_info", token_info);
+ end
+ end
+
+ if sub_token_info then
+ -- Parent token validated, now validate (and return) the subtoken
+ return _validate_token_info(token_user, token_id, sub_token_info);
+ end
+
+ return token_info
+end
+
local function _get_validated_token_info(token_id, token_user, token_host, token_secret)
if token_host ~= module.host then
return nil, "invalid-host";
@@ -94,32 +197,17 @@ local function _get_validated_token_info(token_id, token_user, token_host, token
end
-- Check provided secret
- if not hashes.equals(hashes.sha256(token_secret, true), token.secret_sha256) then
- return nil, "not-authorized";
- end
-
- local token_info = token.token_info;
-
- local now = os.time();
- if token_info.expires and token_info.expires < now then
- token_store:set(token_user, token_id, nil);
- return nil, "not-authorized";
- end
-
- local account_info = usermanager.get_account_info(token_user, module.host);
- local password_updated_at = account_info and account_info.password_updated;
- if password_updated_at and password_updated_at > token_info.created then
- token_store:set(token_user, token_id, nil);
+ local secret_hash = hashes.sha256(token_secret, true);
+ if not hashes.equals(secret_hash, token.secret_sha256) then
+ local sub_token_info = token.sub_tokens and token.sub_tokens[secret_hash];
+ if sub_token_info then
+ return _validate_token_info(token_user, token_id, token.token_info, sub_token_info);
+ end
return nil, "not-authorized";
end
- local last_accessed = token_info.accessed;
- if not last_accessed or (now - last_accessed) > access_time_granularity then
- token_info.accessed = now;
- token_store:set(token_user, token_id, token_info);
- end
+ return _validate_token_info(token_user, token_id, token.token_info);
- return token_info
end
function get_token_info(token)