@@ -752,6 +752,22 @@ type GithubOAuth2Config struct {
752752AllowEveryone bool
753753AllowOrganizations []string
754754AllowTeams []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
755771}
756772
757773// @Summary Get authentication methods
@@ -786,6 +802,53 @@ func (api *API) userAuthMethods(rw http.ResponseWriter, r *http.Request) {
786802})
787803}
788804
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+
789852// @Summary OAuth 2.0 GitHub Callback
790853// @ID oauth-20-github-callback
791854// @Security CoderSessionToken
@@ -1016,7 +1079,14 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
10161079}
10171080
10181081redirect = 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+ }
10201090}
10211091
10221092type OIDCConfig struct {