Mercurial > prosody-modules
comparison mod_http_oauth2/mod_http_oauth2.lua @ 5279:2b858cccac8f
mod_http_oauth2: Add support for refresh tokens
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Fri, 24 Mar 2023 14:29:07 +0000 |
parents | d94dba396f9f |
children | eb482defd9b0 |
comparison
equal
deleted
inserted
replaced
5278:d94dba396f9f | 5279:2b858cccac8f |
---|---|
59 return resp; | 59 return resp; |
60 end | 60 end |
61 | 61 |
62 local tokens = module:depends("tokenauth"); | 62 local tokens = module:depends("tokenauth"); |
63 | 63 |
64 local default_access_ttl = module:get_option_number("oauth2_access_token_ttl", 86400); | |
65 local default_refresh_ttl = module:get_option_number("oauth2_refresh_token_ttl", nil); | |
66 | |
64 -- Used to derive client_secret from client_id, set to enable stateless dynamic registration. | 67 -- Used to derive client_secret from client_id, set to enable stateless dynamic registration. |
65 local registration_key = module:get_option_string("oauth2_registration_key"); | 68 local registration_key = module:get_option_string("oauth2_registration_key"); |
66 local registration_algo = module:get_option_string("oauth2_registration_algorithm", "HS256"); | 69 local registration_algo = module:get_option_string("oauth2_registration_algorithm", "HS256"); |
67 local registration_options = module:get_option("oauth2_registration_options", { default_ttl = 60 * 60 * 24 * 90 }); | 70 local registration_options = module:get_option("oauth2_registration_options", { default_ttl = 60 * 60 * 24 * 90 }); |
68 | 71 |
150 -- client needs to be revoked | 153 -- client needs to be revoked |
151 local function client_subset(client) | 154 local function client_subset(client) |
152 return { name = client.client_name; uri = client.client_uri }; | 155 return { name = client.client_name; uri = client.client_uri }; |
153 end | 156 end |
154 | 157 |
155 local function new_access_token(token_jid, role, scope, ttl, client, id_token) | 158 local function new_access_token(token_jid, role, scope_string, client, id_token, refresh_token_info) |
156 local token_data = {}; | 159 local token_data = { oauth2_scopes = scope_string, oauth2_client = nil }; |
157 if client then | 160 if client then |
158 token_data.oauth2_client = client_subset(client); | 161 token_data.oauth2_client = client_subset(client); |
159 end | 162 end |
160 if next(token_data) == nil then | 163 if next(token_data) == nil then |
161 token_data = nil; | 164 token_data = nil; |
162 end | 165 end |
163 local token = tokens.create_jid_token(token_jid, token_jid, role, ttl, token_data, "oauth2"); | 166 |
167 local refresh_token; | |
168 local access_token, access_token_info | |
169 -- No existing refresh token, and we're issuing a time-limited access token? | |
170 -- Create a refresh token (unless refresh_token_info == false) | |
171 if refresh_token_info == false or not default_access_ttl then | |
172 -- Caller does not want a refresh token, or access tokens are not configured to expire | |
173 -- So, just create a standalone access token | |
174 access_token, access_token_info = tokens.create_jid_token(token_jid, token_jid, role, default_access_ttl, token_data, "oauth2"); | |
175 else | |
176 -- We're issuing both a refresh and an access token | |
177 if not refresh_token_info then | |
178 refresh_token, refresh_token_info = tokens.create_jid_token(token_jid, token_jid, role, default_refresh_ttl, token_data, "oauth2-refresh"); | |
179 else | |
180 refresh_token = refresh_token_info.token; | |
181 end | |
182 access_token, access_token_info = tokens.create_sub_token(token_jid, refresh_token_info.id, role, default_access_ttl, token_data, "oauth2"); | |
183 end | |
184 local expires_at = access_token_info.expires; | |
164 return { | 185 return { |
165 token_type = "bearer"; | 186 token_type = "bearer"; |
166 access_token = token; | 187 access_token = access_token; |
167 expires_in = ttl; | 188 expires_in = expires_at and (expires_at - os.time()) or nil; |
168 scope = scope; | 189 scope = scope_string; |
169 id_token = id_token; | 190 id_token = id_token; |
170 -- TODO: include refresh_token when implemented | 191 refresh_token = refresh_token; |
171 }; | 192 }; |
172 end | 193 end |
173 | 194 |
174 local function get_redirect_uri(client, query_redirect_uri) -- record client, string : string | 195 local function get_redirect_uri(client, query_redirect_uri) -- record client, string : string |
175 if not query_redirect_uri then | 196 if not query_redirect_uri then |
203 return oauth_error("invalid_grant", "incorrect credentials"); | 224 return oauth_error("invalid_grant", "incorrect credentials"); |
204 end | 225 end |
205 | 226 |
206 local granted_jid = jid.join(request_username, request_host, request_resource); | 227 local granted_jid = jid.join(request_username, request_host, request_resource); |
207 local granted_scopes, granted_role = filter_scopes(request_username, params.scope); | 228 local granted_scopes, granted_role = filter_scopes(request_username, params.scope); |
208 return json.encode(new_access_token(granted_jid, granted_role, granted_scopes, nil, nil)); | 229 return json.encode(new_access_token(granted_jid, granted_role, granted_scopes, nil)); |
209 end | 230 end |
210 | 231 |
211 function response_type_handlers.code(client, params, granted_jid, id_token) | 232 function response_type_handlers.code(client, params, granted_jid, id_token) |
212 local request_username, request_host = jid.split(granted_jid); | 233 local request_username, request_host = jid.split(granted_jid); |
213 if not request_host or request_host ~= module.host then | 234 if not request_host or request_host ~= module.host then |
268 local request_username, request_host = jid.split(granted_jid); | 289 local request_username, request_host = jid.split(granted_jid); |
269 if not request_host or request_host ~= module.host then | 290 if not request_host or request_host ~= module.host then |
270 return oauth_error("invalid_request", "invalid JID"); | 291 return oauth_error("invalid_request", "invalid JID"); |
271 end | 292 end |
272 local granted_scopes, granted_role = filter_scopes(request_username, params.scope); | 293 local granted_scopes, granted_role = filter_scopes(request_username, params.scope); |
273 local token_info = new_access_token(granted_jid, granted_role, granted_scopes, nil, client, nil); | 294 local token_info = new_access_token(granted_jid, granted_role, granted_scopes, client, nil); |
274 | 295 |
275 local redirect = url.parse(get_redirect_uri(client, params.redirect_uri)); | 296 local redirect = url.parse(get_redirect_uri(client, params.redirect_uri)); |
276 token_info.state = params.state; | 297 token_info.state = params.state; |
277 redirect.fragment = http.formencode(token_info); | 298 redirect.fragment = http.formencode(token_info); |
278 | 299 |
317 if not code or type(code) ~= "table" or code_expired(code) then | 338 if not code or type(code) ~= "table" or code_expired(code) then |
318 module:log("debug", "authorization_code invalid or expired: %q", code); | 339 module:log("debug", "authorization_code invalid or expired: %q", code); |
319 return oauth_error("invalid_client", "incorrect credentials"); | 340 return oauth_error("invalid_client", "incorrect credentials"); |
320 end | 341 end |
321 | 342 |
322 return json.encode(new_access_token(code.granted_jid, code.granted_role, code.granted_scopes, nil, client, code.id_token)); | 343 return json.encode(new_access_token(code.granted_jid, code.granted_role, code.granted_scopes, client, code.id_token)); |
344 end | |
345 | |
346 function grant_type_handlers.refresh_token(params) | |
347 if not params.client_id then return oauth_error("invalid_request", "missing 'client_id'"); end | |
348 if not params.client_secret then return oauth_error("invalid_request", "missing 'client_secret'"); end | |
349 if not params.refresh_token then return oauth_error("invalid_request", "missing 'refresh_token'"); end | |
350 | |
351 local client_ok, client = jwt_verify(params.client_id); | |
352 if not client_ok then | |
353 return oauth_error("invalid_client", "incorrect credentials"); | |
354 end | |
355 | |
356 if not verify_client_secret(params.client_id, params.client_secret) then | |
357 module:log("debug", "client_secret mismatch"); | |
358 return oauth_error("invalid_client", "incorrect credentials"); | |
359 end | |
360 | |
361 local refresh_token_info = tokens.get_token_info(params.refresh_token); | |
362 if not refresh_token_info or refresh_token_info.purpose ~= "oauth2-refresh" then | |
363 return oauth_error("invalid_grant", "invalid refresh token"); | |
364 end | |
365 | |
366 -- new_access_token() requires the actual token | |
367 refresh_token_info.token = params.refresh_token; | |
368 | |
369 return json.encode(new_access_token(token_info.jid, token_info.role, token_info.data.oauth2_scopes, client, nil, token_info)); | |
323 end | 370 end |
324 | 371 |
325 -- Used to issue/verify short-lived tokens for the authorization process below | 372 -- Used to issue/verify short-lived tokens for the authorization process below |
326 local new_user_token, verify_user_token = jwt.init("HS256", random.bytes(32), nil, { default_ttl = 600 }); | 373 local new_user_token, verify_user_token = jwt.init("HS256", random.bytes(32), nil, { default_ttl = 600 }); |
327 | 374 |
457 location = redirect_uri; | 504 location = redirect_uri; |
458 }; | 505 }; |
459 }; | 506 }; |
460 end | 507 end |
461 | 508 |
462 local allowed_grant_type_handlers = module:get_option_set("allowed_oauth2_grant_types", {"authorization_code", "password"}) | 509 local allowed_grant_type_handlers = module:get_option_set("allowed_oauth2_grant_types", {"authorization_code", "password", "refresh_token"}) |
463 for handler_type in pairs(grant_type_handlers) do | 510 for handler_type in pairs(grant_type_handlers) do |
464 if not allowed_grant_type_handlers:contains(handler_type) then | 511 if not allowed_grant_type_handlers:contains(handler_type) then |
465 module:log("debug", "Grant type %q disabled", handler_type); | 512 module:log("debug", "Grant type %q disabled", handler_type); |
466 grant_type_handlers[handler_type] = nil; | 513 grant_type_handlers[handler_type] = nil; |
467 else | 514 else |