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