@@ -22,6 +22,7 @@ import (
22
22
"github.com/prometheus/client_golang/prometheus"
23
23
"github.com/stretchr/testify/assert"
24
24
"github.com/stretchr/testify/require"
25
+ "golang.org/x/oauth2"
25
26
"golang.org/x/xerrors"
26
27
27
28
"cdr.dev/slog"
@@ -882,6 +883,92 @@ func TestUserOAuth2Github(t *testing.T) {
882
883
require .Equal (t ,user .ID ,userID ,"user_id is different, a new user was likely created" )
883
884
require .Equal (t ,user .Email ,newEmail )
884
885
})
886
+ t .Run ("DeviceFlow" ,func (t * testing.T ) {
887
+ t .Parallel ()
888
+ client := coderdtest .New (t ,& coderdtest.Options {
889
+ GithubOAuth2Config :& coderd.GithubOAuth2Config {
890
+ OAuth2Config :& testutil.OAuth2Config {},
891
+ AllowOrganizations : []string {"coder" },
892
+ AllowSignups :true ,
893
+ ListOrganizationMemberships :func (_ context.Context ,_ * http.Client ) ([]* github.Membership ,error ) {
894
+ return []* github.Membership {{
895
+ State :& stateActive ,
896
+ Organization :& github.Organization {
897
+ Login :github .String ("coder" ),
898
+ },
899
+ }},nil
900
+ },
901
+ AuthenticatedUser :func (_ context.Context ,_ * http.Client ) (* github.User ,error ) {
902
+ return & github.User {
903
+ ID :github .Int64 (100 ),
904
+ Login :github .String ("testuser" ),
905
+ Name :github .String ("The Right Honorable Sir Test McUser" ),
906
+ },nil
907
+ },
908
+ ListEmails :func (_ context.Context ,_ * http.Client ) ([]* github.UserEmail ,error ) {
909
+ return []* github.UserEmail {{
910
+ Email :github .String ("testuser@coder.com" ),
911
+ Verified :github .Bool (true ),
912
+ Primary :github .Bool (true ),
913
+ }},nil
914
+ },
915
+ DeviceFlowEnabled :true ,
916
+ ExchangeDeviceCode :func (_ context.Context ,_ string ) (* oauth2.Token ,error ) {
917
+ return & oauth2.Token {
918
+ AccessToken :"access_token" ,
919
+ RefreshToken :"refresh_token" ,
920
+ Expiry :time .Now ().Add (time .Hour ),
921
+ },nil
922
+ },
923
+ AuthorizeDevice :func (_ context.Context ) (* codersdk.ExternalAuthDevice ,error ) {
924
+ return & codersdk.ExternalAuthDevice {
925
+ DeviceCode :"device_code" ,
926
+ UserCode :"user_code" ,
927
+ },nil
928
+ },
929
+ },
930
+ })
931
+ client .HTTPClient .CheckRedirect = func (* http.Request , []* http.Request )error {
932
+ return http .ErrUseLastResponse
933
+ }
934
+
935
+ // Ensure that we redirect to the device login page when the user is not logged in.
936
+ oauthURL ,err := client .URL .Parse ("/api/v2/users/oauth2/github/callback" )
937
+ require .NoError (t ,err )
938
+
939
+ req ,err := http .NewRequestWithContext (context .Background (),"GET" ,oauthURL .String (),nil )
940
+
941
+ require .NoError (t ,err )
942
+ res ,err := client .HTTPClient .Do (req )
943
+ require .NoError (t ,err )
944
+ defer res .Body .Close ()
945
+
946
+ require .Equal (t ,http .StatusTemporaryRedirect ,res .StatusCode )
947
+ location ,err := res .Location ()
948
+ require .NoError (t ,err )
949
+ require .Equal (t ,"/login/device" ,location .Path )
950
+ query := location .Query ()
951
+ require .NotEmpty (t ,query .Get ("state" ))
952
+
953
+ // Ensure that we return a JSON response when the code is successfully exchanged.
954
+ oauthURL ,err = client .URL .Parse ("/api/v2/users/oauth2/github/callback?code=hey&state=somestate" )
955
+ require .NoError (t ,err )
956
+
957
+ req ,err = http .NewRequestWithContext (context .Background (),"GET" ,oauthURL .String (),nil )
958
+ req .AddCookie (& http.Cookie {
959
+ Name :"oauth_state" ,
960
+ Value :"somestate" ,
961
+ })
962
+ require .NoError (t ,err )
963
+ res ,err = client .HTTPClient .Do (req )
964
+ require .NoError (t ,err )
965
+ defer res .Body .Close ()
966
+
967
+ require .Equal (t ,http .StatusOK ,res .StatusCode )
968
+ var resp codersdk.OAuth2DeviceFlowCallbackResponse
969
+ require .NoError (t ,json .NewDecoder (res .Body ).Decode (& resp ))
970
+ require .Equal (t ,"/" ,resp .RedirectURL )
971
+ })
885
972
}
886
973
887
974
// nolint:bodyclose