Software /
code /
prosody
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; |