Mercurial > prosody-modules
comparison mod_http_oauth2/mod_http_oauth2.lua @ 5607:ad9b8f659c96
mod_http_oauth2: Namespace the various codes to minimize confusion
Both for the programmer and in OAuth flows.
While unlikely, it should not be possible to cause weirdness e.g. by
typing a client id and authorization code into the device code entry.
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Wed, 19 Jul 2023 12:58:04 +0200 |
| parents | 17aa3bac7f3a |
| children | 1893ae742f66 |
comparison
equal
deleted
inserted
replaced
| 5606:39bb7232326d | 5607:ad9b8f659c96 |
|---|---|
| 396 if is_device then | 396 if is_device then |
| 397 -- reconstruct the device_code | 397 -- reconstruct the device_code |
| 398 code = b64url(hashes.hmac_sha256(verification_key, device_state.user_code)); | 398 code = b64url(hashes.hmac_sha256(verification_key, device_state.user_code)); |
| 399 end | 399 end |
| 400 end | 400 end |
| 401 local ok = codes:set(params.client_id .. "#" .. code, { | 401 local ok = codes:set("authorization_code:" .. params.client_id .. "#" .. code, { |
| 402 expires = os.time() + 600; | 402 expires = os.time() + 600; |
| 403 granted_jid = granted_jid; | 403 granted_jid = granted_jid; |
| 404 granted_scopes = granted_scopes; | 404 granted_scopes = granted_scopes; |
| 405 granted_role = granted_role; | 405 granted_role = granted_role; |
| 406 challenge = params.code_challenge; | 406 challenge = params.code_challenge; |
| 489 | 489 |
| 490 if not verify_client_secret(params.client_id, params.client_secret) then | 490 if not verify_client_secret(params.client_id, params.client_secret) then |
| 491 module:log("debug", "client_secret mismatch"); | 491 module:log("debug", "client_secret mismatch"); |
| 492 return oauth_error("invalid_client", "incorrect credentials"); | 492 return oauth_error("invalid_client", "incorrect credentials"); |
| 493 end | 493 end |
| 494 local code, err = codes:get(params.client_id .. "#" .. params.code); | 494 local code, err = codes:get("authorization_code:" .. params.client_id .. "#" .. params.code); |
| 495 if err then error(err); end | 495 if err then error(err); end |
| 496 -- MUST NOT use the authorization code more than once, so remove it to | 496 -- MUST NOT use the authorization code more than once, so remove it to |
| 497 -- prevent a second attempted use | 497 -- prevent a second attempted use |
| 498 -- TODO if a second attempt *is* made, revoke any tokens issued | 498 -- TODO if a second attempt *is* made, revoke any tokens issued |
| 499 codes:set(params.client_id .. "#" .. params.code, nil); | 499 codes:set("authorization_code:" .. params.client_id .. "#" .. params.code, nil); |
| 500 if not code or type(code) ~= "table" or code_expired(code) then | 500 if not code or type(code) ~= "table" or code_expired(code) then |
| 501 module:log("debug", "authorization_code invalid or expired: %q", code); | 501 module:log("debug", "authorization_code invalid or expired: %q", code); |
| 502 return oauth_error("invalid_client", "incorrect credentials"); | 502 return oauth_error("invalid_client", "incorrect credentials"); |
| 503 end | 503 end |
| 504 | 504 |
| 572 if not verify_client_secret(params.client_id, params.client_secret) then | 572 if not verify_client_secret(params.client_id, params.client_secret) then |
| 573 module:log("debug", "client_secret mismatch"); | 573 module:log("debug", "client_secret mismatch"); |
| 574 return oauth_error("invalid_client", "incorrect credentials"); | 574 return oauth_error("invalid_client", "incorrect credentials"); |
| 575 end | 575 end |
| 576 | 576 |
| 577 local code = codes:get(params.client_id .. "#" .. params.device_code); | 577 local code = codes:get("device_code:" .. params.device_code); |
| 578 if type(code) ~= "table" or code_expired(code) then | 578 if type(code) ~= "table" or code_expired(code) then |
| 579 return oauth_error("expired_token"); | 579 return oauth_error("expired_token"); |
| 580 elseif code.error then | 580 elseif code.error then |
| 581 return code.error; | 581 return code.error; |
| 582 elseif not code.granted_jid then | 582 elseif not code.granted_jid then |
| 583 return oauth_error("authorization_pending"); | 583 return oauth_error("authorization_pending"); |
| 584 end | 584 end |
| 585 codes:set(client.client_hash .. "#" .. params.device_code, nil); | 585 codes:set("device_code:" .. params.device_code, nil); |
| 586 | 586 |
| 587 return json.encode(new_access_token(code.granted_jid, code.granted_role, code.granted_scopes, client, code.id_token)); | 587 return json.encode(new_access_token(code.granted_jid, code.granted_role, code.granted_scopes, client, code.id_token)); |
| 588 end | 588 end |
| 589 | 589 |
| 590 -- RFC 7636 Proof Key for Code Exchange by OAuth Public Clients | 590 -- RFC 7636 Proof Key for Code Exchange by OAuth Public Clients |
| 880 -- Notify client of rejection | 880 -- Notify client of rejection |
| 881 if redirect_uri == device_uri then | 881 if redirect_uri == device_uri then |
| 882 local is_device, device_state = verify_device_token(params.state); | 882 local is_device, device_state = verify_device_token(params.state); |
| 883 if is_device then | 883 if is_device then |
| 884 local device_code = b64url(hashes.hmac_sha256(verification_key, device_state.user_code)); | 884 local device_code = b64url(hashes.hmac_sha256(verification_key, device_state.user_code)); |
| 885 local code = codes:get(params.client_id .. "#" .. device_code); | 885 local code = codes:get("device_code:" .. params.client_id .. "#" .. device_code); |
| 886 code.error = oauth_error("access_denied"); | 886 code.error = oauth_error("access_denied"); |
| 887 code.expires = os.time() + 60; | 887 code.expires = os.time() + 60; |
| 888 codes:set(params.client_id .. "#" .. device_code, code); | 888 codes:set("device_code:" .. params.client_id .. "#" .. device_code, code); |
| 889 end | 889 end |
| 890 end | 890 end |
| 891 return error_response(request, redirect_uri, oauth_error("access_denied")); | 891 return error_response(request, redirect_uri, oauth_error("access_denied")); |
| 892 end | 892 end |
| 893 -- else auth_state.consent == true | 893 -- else auth_state.consent == true |
| 967 | 967 |
| 968 -- TODO better code generator, this one should be easy to type from a | 968 -- TODO better code generator, this one should be easy to type from a |
| 969 -- screen onto a phone | 969 -- screen onto a phone |
| 970 local user_code = (id.tiny() .. "-" .. id.tiny()):upper(); | 970 local user_code = (id.tiny() .. "-" .. id.tiny()):upper(); |
| 971 local collisions = 0; | 971 local collisions = 0; |
| 972 while codes:get(user_code) do | 972 while codes:get("authorization_code:" .. device_uri .. "#" .. user_code) do |
| 973 collisions = collisions + 1; | 973 collisions = collisions + 1; |
| 974 if collisions > 10 then | 974 if collisions > 10 then |
| 975 return oauth_error("temporarily_unavailable"); | 975 return oauth_error("temporarily_unavailable"); |
| 976 end | 976 end |
| 977 user_code = (id.tiny() .. "-" .. id.tiny()):upper(); | 977 user_code = (id.tiny() .. "-" .. id.tiny()):upper(); |
| 979 -- device code should be derivable after consent but not guessable by the user | 979 -- device code should be derivable after consent but not guessable by the user |
| 980 local device_code = b64url(hashes.hmac_sha256(verification_key, user_code)); | 980 local device_code = b64url(hashes.hmac_sha256(verification_key, user_code)); |
| 981 local verification_uri = module:http_url() .. "/device"; | 981 local verification_uri = module:http_url() .. "/device"; |
| 982 local verification_uri_complete = verification_uri .. "?" .. http.formencode({ user_code = user_code }); | 982 local verification_uri_complete = verification_uri .. "?" .. http.formencode({ user_code = user_code }); |
| 983 | 983 |
| 984 local dc_ok = codes:set(params.client_id .. "#" .. device_code, { expires = os.time() + 1200 }); | 984 local dc_ok = codes:set("device_code:" .. params.client_id .. "#" .. device_code, { expires = os.time() + 1200 }); |
| 985 local uc_ok = codes:set(user_code, | 985 local uc_ok = codes:set("user_code:" .. user_code, |
| 986 { user_code = user_code; expires = os.time() + 600; client_id = params.client_id; | 986 { user_code = user_code; expires = os.time() + 600; client_id = params.client_id; |
| 987 scope = requested_scopes:concat(" ") }); | 987 scope = requested_scopes:concat(" ") }); |
| 988 if not dc_ok or not uc_ok then | 988 if not dc_ok or not uc_ok then |
| 989 return oauth_error("temporarily_unavailable"); | 989 return oauth_error("temporarily_unavailable"); |
| 990 end | 990 end |
| 1007 local params = strict_formdecode(request.url.query); | 1007 local params = strict_formdecode(request.url.query); |
| 1008 if not params or not params.user_code then | 1008 if not params or not params.user_code then |
| 1009 return render_page(templates.device, { client = false }); | 1009 return render_page(templates.device, { client = false }); |
| 1010 end | 1010 end |
| 1011 | 1011 |
| 1012 local device_info = codes:get(params.user_code); | 1012 local device_info = codes:get("user_code:" .. params.user_code); |
| 1013 if not device_info or code_expired(device_info) or not codes:set(params.user_code, nil) then | 1013 if not device_info or code_expired(device_info) or not codes:set("user_code:" .. params.user_code, nil) then |
| 1014 return render_error(oauth_error("expired_token", "Incorrect or expired code")); | 1014 return render_error(oauth_error("expired_token", "Incorrect or expired code")); |
| 1015 end | 1015 end |
| 1016 | 1016 |
| 1017 return { | 1017 return { |
| 1018 status_code = 303; | 1018 status_code = 303; |
