@@ -748,12 +748,30 @@ type GithubOAuth2Config struct {
748
748
ListOrganizationMemberships func (ctx context.Context ,client * http.Client ) ([]* github.Membership ,error )
749
749
TeamMembership func (ctx context.Context ,client * http.Client ,org ,team ,username string ) (* github.Membership ,error )
750
750
751
+ DeviceFlowEnabled bool
752
+ ExchangeDeviceCode func (ctx context.Context ,deviceCode string ) (* oauth2.Token ,error )
753
+ AuthorizeDevice func (ctx context.Context ) (* codersdk.ExternalAuthDevice ,error )
754
+
751
755
AllowSignups bool
752
756
AllowEveryone bool
753
757
AllowOrganizations []string
754
758
AllowTeams []GithubOAuth2Team
755
759
}
756
760
761
+ func (c * GithubOAuth2Config )Exchange (ctx context.Context ,code string ,opts ... oauth2.AuthCodeOption ) (* oauth2.Token ,error ) {
762
+ if ! c .DeviceFlowEnabled {
763
+ return c .OAuth2Config .Exchange (ctx ,code ,opts ... )
764
+ }
765
+ return c .ExchangeDeviceCode (ctx ,code )
766
+ }
767
+
768
+ func (c * GithubOAuth2Config )AuthCodeURL (state string ,opts ... oauth2.AuthCodeOption )string {
769
+ if ! c .DeviceFlowEnabled {
770
+ return c .OAuth2Config .AuthCodeURL (state ,opts ... )
771
+ }
772
+ return "/login/device?state=" + state
773
+ }
774
+
757
775
// @Summary Get authentication methods
758
776
// @ID get-authentication-methods
759
777
// @Security CoderSessionToken
@@ -786,6 +804,53 @@ func (api *API) userAuthMethods(rw http.ResponseWriter, r *http.Request) {
786
804
})
787
805
}
788
806
807
+ // @Summary Get Github device auth.
808
+ // @ID get-github-device-auth
809
+ // @Security CoderSessionToken
810
+ // @Produce json
811
+ // @Tags Users
812
+ // @Success 200 {object} codersdk.ExternalAuthDevice
813
+ // @Router /users/oauth2/github/device [get]
814
+ func (api * API )userOAuth2GithubDevice (rw http.ResponseWriter ,r * http.Request ) {
815
+ var (
816
+ ctx = r .Context ()
817
+ auditor = api .Auditor .Load ()
818
+ aReq ,commitAudit = audit .InitRequest [database.APIKey ](rw ,& audit.RequestParams {
819
+ Audit :* auditor ,
820
+ Log :api .Logger ,
821
+ Request :r ,
822
+ Action :database .AuditActionLogin ,
823
+ })
824
+ )
825
+ aReq .Old = database.APIKey {}
826
+ defer commitAudit ()
827
+
828
+ if api .GithubOAuth2Config == nil {
829
+ httpapi .Write (ctx ,rw ,http .StatusBadRequest , codersdk.Response {
830
+ Message :"Github OAuth2 is not enabled." ,
831
+ })
832
+ return
833
+ }
834
+
835
+ if ! api .GithubOAuth2Config .DeviceFlowEnabled {
836
+ httpapi .Write (ctx ,rw ,http .StatusBadRequest , codersdk.Response {
837
+ Message :"Device flow is not enabled for Github OAuth2." ,
838
+ })
839
+ return
840
+ }
841
+
842
+ deviceAuth ,err := api .GithubOAuth2Config .AuthorizeDevice (ctx )
843
+ if err != nil {
844
+ httpapi .Write (ctx ,rw ,http .StatusInternalServerError , codersdk.Response {
845
+ Message :"Failed to authorize device." ,
846
+ Detail :err .Error (),
847
+ })
848
+ return
849
+ }
850
+
851
+ httpapi .Write (ctx ,rw ,http .StatusOK ,deviceAuth )
852
+ }
853
+
789
854
// @Summary OAuth 2.0 GitHub Callback
790
855
// @ID oauth-20-github-callback
791
856
// @Security CoderSessionToken
@@ -1016,7 +1081,14 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
1016
1081
}
1017
1082
1018
1083
redirect = uriFromURL (redirect )
1019
- http .Redirect (rw ,r ,redirect ,http .StatusTemporaryRedirect )
1084
+ if api .GithubOAuth2Config .DeviceFlowEnabled {
1085
+ // In the device flow, the redirect is handled client-side.
1086
+ httpapi .Write (ctx ,rw ,http .StatusOK , codersdk.OAuth2DeviceFlowCallbackResponse {
1087
+ RedirectURL :redirect ,
1088
+ })
1089
+ }else {
1090
+ http .Redirect (rw ,r ,redirect ,http .StatusTemporaryRedirect )
1091
+ }
1020
1092
}
1021
1093
1022
1094
type OIDCConfig struct {