@@ -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
0 commit comments