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