Comparison

plugins/mod_tokenauth.lua @ 12953:ebe3b2f96cad

mod_tokenauth: Switch to new token format (invalidates existing tokens!) The new format has the following properties: - 5 bytes longer than the previous format - The token now has separate 'id' and 'secret' parts - the token itself is no longer stored in the DB, and the secret part is hashed - The only variable length field (JID) has been moved to the end - The 'secret-token:' prefix (RFC 8959) is now included Compatibility with the old token format was not maintained, and all previously issued tokens are invalid after this commit (they will be removed from the DB if used).
author Matthew Wild <mwild1@gmail.com>
date Tue, 21 Mar 2023 14:33:29 +0000
parent 12952:a668bc1aa39d
child 12959:e331210beeb2
comparison
equal deleted inserted replaced
12952:a668bc1aa39d 12953:ebe3b2f96cad
1 local base64 = require "util.encodings".base64;
2 local hashes = require "util.hashes";
1 local id = require "util.id"; 3 local id = require "util.id";
2 local jid = require "util.jid"; 4 local jid = require "util.jid";
3 local base64 = require "util.encodings".base64; 5 local random = require "util.random";
4 local usermanager = require "core.usermanager"; 6 local usermanager = require "core.usermanager";
5 local generate_identifier = require "util.id".short; 7 local generate_identifier = require "util.id".short;
6 8
7 local token_store = module:open_store("auth_tokens", "map"); 9 local token_store = module:open_store("auth_tokens", "map");
8 10
27 29
28 if (token_data and type(token_data) ~= "table") or (token_purpose and type(token_purpose) ~= "string") then 30 if (token_data and type(token_data) ~= "table") or (token_purpose and type(token_purpose) ~= "string") then
29 return nil, "bad-request"; 31 return nil, "bad-request";
30 end 32 end
31 33
34 local token_id = id.short();
35
32 local token_info = { 36 local token_info = {
37 id = token_id;
38
33 owner = actor_jid; 39 owner = actor_jid;
34 created = os.time(); 40 created = os.time();
35 expires = token_ttl and (os.time() + token_ttl) or nil; 41 expires = token_ttl and (os.time() + token_ttl) or nil;
36 jid = token_jid; 42 jid = token_jid;
37 purpose = token_purpose; 43 purpose = token_purpose;
39 resource = token_resource; 45 resource = token_resource;
40 role = token_role; 46 role = token_role;
41 data = token_data; 47 data = token_data;
42 }; 48 };
43 49
44 local token_id = id.long(); 50 local token_secret = random.bytes(18);
45 local token = base64.encode("1;"..jid.join(token_username, token_host)..";"..token_id); 51 local token = "secret-token:"..base64.encode("2;"..token_id..";"..token_secret..";"..jid.join(token_username, token_host));
46 token_store:set(token_username, token_id, token_info); 52 token_store:set(token_username, token_id, {
53 secret_sha256 = hashes.sha256(token_secret, true);
54 token_info = token_info
55 });
47 56
48 return token, token_info; 57 return token, token_info;
49 end 58 end
50 59
51 local function parse_token(encoded_token) 60 local function parse_token(encoded_token)
52 if not encoded_token then return nil; end 61 if not encoded_token then return nil; end
53 local token = base64.decode(encoded_token); 62 local encoded_data = encoded_token:match("^secret%-token:(.+)$");
63 if not encoded_data then return nil; end
64 local token = base64.decode(encoded_data);
54 if not token then return nil; end 65 if not token then return nil; end
55 local token_jid, token_id = token:match("^1;([^;]+);(.+)$"); 66 local token_id, token_secret, token_jid = token:match("^2;([^;]+);([^;]+);(.+)$");
56 if not token_jid then return nil; end 67 if not token_id then return nil; end
57 local token_user, token_host = jid.split(token_jid); 68 local token_user, token_host = jid.split(token_jid);
58 return token_id, token_user, token_host; 69 return token_id, token_user, token_host, token_secret;
59 end 70 end
60 71
61 local function _get_parsed_token_info(token_id, token_user, token_host) 72 local function _get_validated_token_info(token_id, token_user, token_host, token_secret)
62 if token_host ~= module.host then 73 if token_host ~= module.host then
63 return nil, "invalid-host"; 74 return nil, "invalid-host";
64 end 75 end
65 76
66 local token_info, err = token_store:get(token_user, token_id); 77 local token, err = token_store:get(token_user, token_id);
67 if not token_info then 78 if not token then
68 if err then 79 if err then
69 return nil, "internal-error"; 80 return nil, "internal-error";
70 end 81 end
71 return nil, "not-authorized"; 82 return nil, "not-authorized";
83 elseif not token.secret_sha256 then -- older token format
84 token_store:set(token_user, token_id, nil);
85 return nil, "not-authorized";
72 end 86 end
87
88 -- Check provided secret
89 if not hashes.equals(hashes.sha256(token_secret, true), token.secret_sha256) then
90 return nil, "not-authorized";
91 end
92
93 local token_info = token.token_info;
73 94
74 if token_info.expires and token_info.expires < os.time() then 95 if token_info.expires and token_info.expires < os.time() then
75 token_store:set(token_user, token_id, nil); 96 token_store:set(token_user, token_id, nil);
76 return nil, "not-authorized"; 97 return nil, "not-authorized";
77 end 98 end
85 106
86 return token_info 107 return token_info
87 end 108 end
88 109
89 function get_token_info(token) 110 function get_token_info(token)
90 local token_id, token_user, token_host = parse_token(token); 111 local token_id, token_user, token_host, token_secret = parse_token(token);
91 if not token_id then 112 if not token_id then
92 module:log("warn", "Failed to verify access token: %s", token_user); 113 module:log("warn", "Failed to verify access token: %s", token_user);
93 return nil, "invalid-token-format"; 114 return nil, "invalid-token-format";
94 end 115 end
95 return _get_parsed_token_info(token_id, token_user, token_host); 116 return _get_validated_token_info(token_id, token_user, token_host, token_secret);
96 end 117 end
97 118
98 function get_token_session(token, resource) 119 function get_token_session(token, resource)
99 local token_id, token_user, token_host = parse_token(token); 120 local token_id, token_user, token_host = parse_token(token);
100 if not token_id then 121 if not token_id then
101 module:log("warn", "Failed to verify access token: %s", token_user); 122 module:log("warn", "Failed to verify access token: %s", token_user);
102 return nil, "invalid-token-format"; 123 return nil, "invalid-token-format";
103 end 124 end
104 125
105 local token_info, err = _get_parsed_token_info(token_id, token_user, token_host); 126 local token_info, err = _get_validated_token_info(token_id, token_user, token_host);
106 if not token_info then return nil, err; end 127 if not token_info then return nil, err; end
107 128
108 return { 129 return {
109 username = token_user; 130 username = token_user;
110 host = token_host; 131 host = token_host;