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 |
