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; |