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;