diff --git a/.vscode/settings.json b/.vscode/settings.json index b187ed265f..0664d7e81c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -39,6 +39,7 @@ "enterprisemeta", "errgroup", "eventsourcemock", + "externalauth", "Failf", "fatih", "Formik", diff --git a/Makefile b/Makefile index d382535030..64615132a5 100644 --- a/Makefile +++ b/Makefile @@ -542,7 +542,7 @@ site/src/api/typesGenerated.ts: scripts/apitypings/main.go $(shell find ./coders cd site pnpm run format:types ./src/api/typesGenerated.ts -site/e2e/provisionerGenerated.ts: provisionerd/proto/provisionerd.pb.go +site/e2e/provisionerGenerated.ts: provisionerd/proto/provisionerd.pb.go provisionersdk/proto/provisioner.pb.go cd site ../scripts/pnpm_install.sh pnpm run gen:provisioner diff --git a/cli/create_test.go b/cli/create_test.go index 6c516ae8d8..2cd4c9abaa 100644 --- a/cli/create_test.go +++ b/cli/create_test.go @@ -14,7 +14,7 @@ import ( "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/coderd/coderdtest" - "github.com/coder/coder/v2/coderd/gitauth" + "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/provisioner/echo" @@ -600,7 +600,7 @@ func TestCreateWithGitAuth(t *testing.T) { { Type: &proto.Response_Plan{ Plan: &proto.PlanComplete{ - GitAuthProviders: []string{"github"}, + ExternalAuthProviders: []string{"github"}, }, }, }, @@ -609,7 +609,7 @@ func TestCreateWithGitAuth(t *testing.T) { } client := coderdtest.New(t, &coderdtest.Options{ - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ OAuth2Config: &testutil.OAuth2Config{}, ID: "github", Regex: regexp.MustCompile(`github\.com`), @@ -628,7 +628,7 @@ func TestCreateWithGitAuth(t *testing.T) { clitest.Start(t, inv) pty.ExpectMatch("You must authenticate with GitHub to create a workspace") - resp := coderdtest.RequestGitAuthCallback(t, "github", client) + resp := coderdtest.RequestExternalAuthCallback(t, "github", client) _ = resp.Body.Close() require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode) pty.ExpectMatch("Confirm create?") diff --git a/cli/server.go b/cli/server.go index 634bb059dd..b0ec9f999b 100644 --- a/cli/server.go +++ b/cli/server.go @@ -72,7 +72,7 @@ import ( "github.com/coder/coder/v2/coderd/database/migrations" "github.com/coder/coder/v2/coderd/database/pubsub" "github.com/coder/coder/v2/coderd/devtunnel" - "github.com/coder/coder/v2/coderd/gitauth" + "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/coderd/gitsshkey" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/httpmw" @@ -574,16 +574,16 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. } vals.GitAuthProviders.Value = append(vals.GitAuthProviders.Value, gitAuthEnv...) - gitAuthConfigs, err := gitauth.ConvertConfig( + externalAuthConfigs, err := externalauth.ConvertConfig( vals.GitAuthProviders.Value, vals.AccessURL.Value(), ) if err != nil { - return xerrors.Errorf("convert git auth config: %w", err) + return xerrors.Errorf("convert external auth config: %w", err) } - for _, c := range gitAuthConfigs { + for _, c := range externalAuthConfigs { logger.Debug( - ctx, "loaded git auth config", + ctx, "loaded external auth config", slog.F("id", c.ID), ) } @@ -608,7 +608,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. Pubsub: pubsub.NewInMemory(), CacheDir: cacheDir, GoogleTokenValidator: googleTokenValidator, - ExternalAuthConfigs: gitAuthConfigs, + ExternalAuthConfigs: externalAuthConfigs, RealIPConfig: realIPConfig, SecureAuthCookie: vals.SecureAuthCookie.Value(), SSHKeygenAlgorithm: sshKeygenAlgorithm, diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 003baf1679..ab794794f6 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -602,6 +602,103 @@ const docTemplate = `{ } } }, + "/externalauth/{externalauth}": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Git" + ], + "summary": "Get external auth by ID", + "operationId": "get-external-auth-by-id", + "parameters": [ + { + "type": "string", + "format": "string", + "description": "Git Provider ID", + "name": "externalauth", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.ExternalAuth" + } + } + } + } + }, + "/externalauth/{externalauth}/device": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Git" + ], + "summary": "Get external auth device by ID.", + "operationId": "get-external-auth-device-by-id", + "parameters": [ + { + "type": "string", + "format": "string", + "description": "Git Provider ID", + "name": "externalauth", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.ExternalAuthDevice" + } + } + } + }, + "post": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "tags": [ + "Git" + ], + "summary": "Post external auth device by ID", + "operationId": "post-external-auth-device-by-id", + "parameters": [ + { + "type": "string", + "format": "string", + "description": "External Provider ID", + "name": "externalauth", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, "/files": { "post": { "security": [ @@ -677,103 +774,6 @@ const docTemplate = `{ } } }, - "/gitauth/{gitauth}": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "produces": [ - "application/json" - ], - "tags": [ - "Git" - ], - "summary": "Get git auth by ID", - "operationId": "get-git-auth-by-id", - "parameters": [ - { - "type": "string", - "format": "string", - "description": "Git Provider ID", - "name": "gitauth", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.GitAuth" - } - } - } - } - }, - "/gitauth/{gitauth}/device": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "produces": [ - "application/json" - ], - "tags": [ - "Git" - ], - "summary": "Get git auth device by ID.", - "operationId": "get-git-auth-device-by-id", - "parameters": [ - { - "type": "string", - "format": "string", - "description": "Git Provider ID", - "name": "gitauth", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.GitAuthDevice" - } - } - } - }, - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "tags": [ - "Git" - ], - "summary": "Post git auth device by ID", - "operationId": "post-git-auth-device-by-id", - "parameters": [ - { - "type": "string", - "format": "string", - "description": "Git Provider ID", - "name": "gitauth", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content" - } - } - } - }, "/groups/{group}": { "get": { "security": [ @@ -2768,7 +2768,7 @@ const docTemplate = `{ } } }, - "/templateversions/{templateversion}/gitauth": { + "/templateversions/{templateversion}/externalauth": { "get": { "security": [ { @@ -2781,8 +2781,8 @@ const docTemplate = `{ "tags": [ "Templates" ], - "summary": "Get git auth by template version", - "operationId": "get-git-auth-by-template-version", + "summary": "Get external auth by template version", + "operationId": "get-external-auth-by-template-version", "parameters": [ { "type": "string", @@ -8186,6 +8186,77 @@ const docTemplate = `{ "ExperimentDeploymentHealthPage" ] }, + "codersdk.ExternalAuth": { + "type": "object", + "properties": { + "app_install_url": { + "description": "AppInstallURL is the URL to install the app.", + "type": "string" + }, + "app_installable": { + "description": "AppInstallable is true if the request for app installs was successful.", + "type": "boolean" + }, + "authenticated": { + "type": "boolean" + }, + "device": { + "type": "boolean" + }, + "installations": { + "description": "AppInstallations are the installations that the user has access to.", + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.ExternalAuthAppInstallation" + } + }, + "type": { + "type": "string" + }, + "user": { + "description": "User is the user that authenticated with the provider.", + "allOf": [ + { + "$ref": "#/definitions/codersdk.ExternalAuthUser" + } + ] + } + } + }, + "codersdk.ExternalAuthAppInstallation": { + "type": "object", + "properties": { + "account": { + "$ref": "#/definitions/codersdk.ExternalAuthUser" + }, + "configure_url": { + "type": "string" + }, + "id": { + "type": "integer" + } + } + }, + "codersdk.ExternalAuthDevice": { + "type": "object", + "properties": { + "device_code": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "interval": { + "type": "integer" + }, + "user_code": { + "type": "string" + }, + "verification_uri": { + "type": "string" + } + } + }, "codersdk.ExternalAuthProvider": { "type": "string", "enum": [ @@ -8203,6 +8274,23 @@ const docTemplate = `{ "ExternalAuthProviderOpenIDConnect" ] }, + "codersdk.ExternalAuthUser": { + "type": "object", + "properties": { + "avatar_url": { + "type": "string" + }, + "login": { + "type": "string" + }, + "name": { + "type": "string" + }, + "profile_url": { + "type": "string" + } + } + }, "codersdk.Feature": { "type": "object", "properties": { @@ -8242,57 +8330,6 @@ const docTemplate = `{ } } }, - "codersdk.GitAuth": { - "type": "object", - "properties": { - "app_install_url": { - "description": "AppInstallURL is the URL to install the app.", - "type": "string" - }, - "app_installable": { - "description": "AppInstallable is true if the request for app installs was successful.", - "type": "boolean" - }, - "authenticated": { - "type": "boolean" - }, - "device": { - "type": "boolean" - }, - "installations": { - "description": "AppInstallations are the installations that the user has access to.", - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.GitAuthAppInstallation" - } - }, - "type": { - "type": "string" - }, - "user": { - "description": "User is the user that authenticated with the provider.", - "allOf": [ - { - "$ref": "#/definitions/codersdk.GitAuthUser" - } - ] - } - } - }, - "codersdk.GitAuthAppInstallation": { - "type": "object", - "properties": { - "account": { - "$ref": "#/definitions/codersdk.GitAuthUser" - }, - "configure_url": { - "type": "string" - }, - "id": { - "type": "integer" - } - } - }, "codersdk.GitAuthConfig": { "type": "object", "properties": { @@ -8340,43 +8377,6 @@ const docTemplate = `{ } } }, - "codersdk.GitAuthDevice": { - "type": "object", - "properties": { - "device_code": { - "type": "string" - }, - "expires_in": { - "type": "integer" - }, - "interval": { - "type": "integer" - }, - "user_code": { - "type": "string" - }, - "verification_uri": { - "type": "string" - } - } - }, - "codersdk.GitAuthUser": { - "type": "object", - "properties": { - "avatar_url": { - "type": "string" - }, - "login": { - "type": "string" - }, - "name": { - "type": "string" - }, - "profile_url": { - "type": "string" - } - } - }, "codersdk.GitSSHKey": { "type": "object", "properties": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index ec02780589..7a7ee0a69e 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -512,6 +512,93 @@ } } }, + "/externalauth/{externalauth}": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Git"], + "summary": "Get external auth by ID", + "operationId": "get-external-auth-by-id", + "parameters": [ + { + "type": "string", + "format": "string", + "description": "Git Provider ID", + "name": "externalauth", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.ExternalAuth" + } + } + } + } + }, + "/externalauth/{externalauth}/device": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Git"], + "summary": "Get external auth device by ID.", + "operationId": "get-external-auth-device-by-id", + "parameters": [ + { + "type": "string", + "format": "string", + "description": "Git Provider ID", + "name": "externalauth", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.ExternalAuthDevice" + } + } + } + }, + "post": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "tags": ["Git"], + "summary": "Post external auth device by ID", + "operationId": "post-external-auth-device-by-id", + "parameters": [ + { + "type": "string", + "format": "string", + "description": "External Provider ID", + "name": "externalauth", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, "/files": { "post": { "security": [ @@ -579,93 +666,6 @@ } } }, - "/gitauth/{gitauth}": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "produces": ["application/json"], - "tags": ["Git"], - "summary": "Get git auth by ID", - "operationId": "get-git-auth-by-id", - "parameters": [ - { - "type": "string", - "format": "string", - "description": "Git Provider ID", - "name": "gitauth", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.GitAuth" - } - } - } - } - }, - "/gitauth/{gitauth}/device": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "produces": ["application/json"], - "tags": ["Git"], - "summary": "Get git auth device by ID.", - "operationId": "get-git-auth-device-by-id", - "parameters": [ - { - "type": "string", - "format": "string", - "description": "Git Provider ID", - "name": "gitauth", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.GitAuthDevice" - } - } - } - }, - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "tags": ["Git"], - "summary": "Post git auth device by ID", - "operationId": "post-git-auth-device-by-id", - "parameters": [ - { - "type": "string", - "format": "string", - "description": "Git Provider ID", - "name": "gitauth", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content" - } - } - } - }, "/groups/{group}": { "get": { "security": [ @@ -2430,7 +2430,7 @@ } } }, - "/templateversions/{templateversion}/gitauth": { + "/templateversions/{templateversion}/externalauth": { "get": { "security": [ { @@ -2439,8 +2439,8 @@ ], "produces": ["application/json"], "tags": ["Templates"], - "summary": "Get git auth by template version", - "operationId": "get-git-auth-by-template-version", + "summary": "Get external auth by template version", + "operationId": "get-external-auth-by-template-version", "parameters": [ { "type": "string", @@ -7334,6 +7334,77 @@ "ExperimentDeploymentHealthPage" ] }, + "codersdk.ExternalAuth": { + "type": "object", + "properties": { + "app_install_url": { + "description": "AppInstallURL is the URL to install the app.", + "type": "string" + }, + "app_installable": { + "description": "AppInstallable is true if the request for app installs was successful.", + "type": "boolean" + }, + "authenticated": { + "type": "boolean" + }, + "device": { + "type": "boolean" + }, + "installations": { + "description": "AppInstallations are the installations that the user has access to.", + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.ExternalAuthAppInstallation" + } + }, + "type": { + "type": "string" + }, + "user": { + "description": "User is the user that authenticated with the provider.", + "allOf": [ + { + "$ref": "#/definitions/codersdk.ExternalAuthUser" + } + ] + } + } + }, + "codersdk.ExternalAuthAppInstallation": { + "type": "object", + "properties": { + "account": { + "$ref": "#/definitions/codersdk.ExternalAuthUser" + }, + "configure_url": { + "type": "string" + }, + "id": { + "type": "integer" + } + } + }, + "codersdk.ExternalAuthDevice": { + "type": "object", + "properties": { + "device_code": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "interval": { + "type": "integer" + }, + "user_code": { + "type": "string" + }, + "verification_uri": { + "type": "string" + } + } + }, "codersdk.ExternalAuthProvider": { "type": "string", "enum": [ @@ -7351,6 +7422,23 @@ "ExternalAuthProviderOpenIDConnect" ] }, + "codersdk.ExternalAuthUser": { + "type": "object", + "properties": { + "avatar_url": { + "type": "string" + }, + "login": { + "type": "string" + }, + "name": { + "type": "string" + }, + "profile_url": { + "type": "string" + } + } + }, "codersdk.Feature": { "type": "object", "properties": { @@ -7390,57 +7478,6 @@ } } }, - "codersdk.GitAuth": { - "type": "object", - "properties": { - "app_install_url": { - "description": "AppInstallURL is the URL to install the app.", - "type": "string" - }, - "app_installable": { - "description": "AppInstallable is true if the request for app installs was successful.", - "type": "boolean" - }, - "authenticated": { - "type": "boolean" - }, - "device": { - "type": "boolean" - }, - "installations": { - "description": "AppInstallations are the installations that the user has access to.", - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.GitAuthAppInstallation" - } - }, - "type": { - "type": "string" - }, - "user": { - "description": "User is the user that authenticated with the provider.", - "allOf": [ - { - "$ref": "#/definitions/codersdk.GitAuthUser" - } - ] - } - } - }, - "codersdk.GitAuthAppInstallation": { - "type": "object", - "properties": { - "account": { - "$ref": "#/definitions/codersdk.GitAuthUser" - }, - "configure_url": { - "type": "string" - }, - "id": { - "type": "integer" - } - } - }, "codersdk.GitAuthConfig": { "type": "object", "properties": { @@ -7488,43 +7525,6 @@ } } }, - "codersdk.GitAuthDevice": { - "type": "object", - "properties": { - "device_code": { - "type": "string" - }, - "expires_in": { - "type": "integer" - }, - "interval": { - "type": "integer" - }, - "user_code": { - "type": "string" - }, - "verification_uri": { - "type": "string" - } - } - }, - "codersdk.GitAuthUser": { - "type": "object", - "properties": { - "avatar_url": { - "type": "string" - }, - "login": { - "type": "string" - }, - "name": { - "type": "string" - }, - "profile_url": { - "type": "string" - } - } - }, "codersdk.GitSSHKey": { "type": "object", "properties": { diff --git a/coderd/coderd.go b/coderd/coderd.go index 6562044746..c02f9a8d3d 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -37,6 +37,7 @@ import ( // Used for swagger docs. _ "github.com/coder/coder/v2/coderd/apidoc" + "github.com/coder/coder/v2/coderd/externalauth" "cdr.dev/slog" "github.com/coder/coder/v2/buildinfo" @@ -47,7 +48,6 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/pubsub" - "github.com/coder/coder/v2/coderd/gitauth" "github.com/coder/coder/v2/coderd/gitsshkey" "github.com/coder/coder/v2/coderd/healthcheck" "github.com/coder/coder/v2/coderd/httpapi" @@ -115,7 +115,7 @@ type Options struct { SSHKeygenAlgorithm gitsshkey.Algorithm Telemetry telemetry.Reporter TracerProvider trace.TracerProvider - ExternalAuthConfigs []*gitauth.Config + ExternalAuthConfigs []*externalauth.Config RealIPConfig *httpmw.RealIPConfig TrialGenerator func(ctx context.Context, email string) error // TLSCertificates is used to mesh DERP servers securely. @@ -546,21 +546,24 @@ func New(options *Options) *API { }) // Register callback handlers for each OAuth2 provider. - r.Route("/gitauth", func(r chi.Router) { - for _, gitAuthConfig := range options.ExternalAuthConfigs { - // We don't need to register a callback handler for device auth. - if gitAuthConfig.DeviceAuth != nil { - continue + // We must support gitauth and externalauth for backwards compatibility. + for _, route := range []string{"gitauth", "externalauth"} { + r.Route("/"+route, func(r chi.Router) { + for _, externalAuthConfig := range options.ExternalAuthConfigs { + // We don't need to register a callback handler for device auth. + if externalAuthConfig.DeviceAuth != nil { + continue + } + r.Route(fmt.Sprintf("/%s/callback", externalAuthConfig.ID), func(r chi.Router) { + r.Use( + apiKeyMiddlewareRedirect, + httpmw.ExtractOAuth2(externalAuthConfig, options.HTTPClient, nil), + ) + r.Get("/", api.externalAuthCallback(externalAuthConfig)) + }) } - r.Route(fmt.Sprintf("/%s/callback", gitAuthConfig.ID), func(r chi.Router) { - r.Use( - apiKeyMiddlewareRedirect, - httpmw.ExtractOAuth2(gitAuthConfig, options.HTTPClient, nil), - ) - r.Get("/", api.gitAuthCallback(gitAuthConfig)) - }) - } - }) + }) + } r.Route("/api/v2", func(r chi.Router) { api.APIHandler = r @@ -613,14 +616,14 @@ func New(options *Options) *API { r.Get("/{fileID}", api.fileByID) r.Post("/", api.postFile) }) - r.Route("/gitauth/{gitauth}", func(r chi.Router) { + r.Route("/externalauth/{externalauth}", func(r chi.Router) { r.Use( apiKeyMiddleware, - httpmw.ExtractGitAuthParam(options.ExternalAuthConfigs), + httpmw.ExtractExternalAuthParam(options.ExternalAuthConfigs), ) - r.Get("/", api.gitAuthByID) - r.Post("/device", api.postGitAuthDeviceByID) - r.Get("/device", api.gitAuthDeviceByID) + r.Get("/", api.externalAuthByID) + r.Post("/device", api.postExternalAuthDeviceByID) + r.Get("/device", api.externalAuthDeviceByID) }) r.Route("/organizations", func(r chi.Router) { r.Use( @@ -686,7 +689,7 @@ func New(options *Options) *API { r.Get("/schema", templateVersionSchemaDeprecated) r.Get("/parameters", templateVersionParametersDeprecated) r.Get("/rich-parameters", api.templateVersionRichParameters) - r.Get("/gitauth", api.templateVersionGitAuth) + r.Get("/externalauth", api.templateVersionExternalAuth) r.Get("/variables", api.templateVersionVariables) r.Get("/resources", api.templateVersionResources) r.Get("/logs", api.templateVersionLogs) diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 9700311fe7..6cc0d5132e 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -59,7 +59,7 @@ import ( "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/coderd/database/pubsub" - "github.com/coder/coder/v2/coderd/gitauth" + "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/coderd/gitsshkey" "github.com/coder/coder/v2/coderd/healthcheck" "github.com/coder/coder/v2/coderd/httpapi" @@ -105,7 +105,7 @@ type Options struct { AutobuildStats chan<- autobuild.Stats Auditor audit.Auditor TLSCertificates []tls.Certificate - ExternalAuthConfigs []*gitauth.Config + ExternalAuthConfigs []*externalauth.Config TrialGenerator func(context.Context, string) error TemplateScheduleStore schedule.TemplateScheduleStore Coordinator tailnet.Coordinator @@ -899,14 +899,14 @@ func MustWorkspace(t *testing.T, client *codersdk.Client, workspaceID uuid.UUID) return ws } -// RequestGitAuthCallback makes a request with the proper OAuth2 state cookie -// to the git auth callback endpoint. -func RequestGitAuthCallback(t *testing.T, providerID string, client *codersdk.Client) *http.Response { +// RequestExternalAuthCallback makes a request with the proper OAuth2 state cookie +// to the external auth callback endpoint. +func RequestExternalAuthCallback(t *testing.T, providerID string, client *codersdk.Client) *http.Response { client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } state := "somestate" - oauthURL, err := client.URL.Parse(fmt.Sprintf("/gitauth/%s/callback?code=asd&state=%s", providerID, state)) + oauthURL, err := client.URL.Parse(fmt.Sprintf("/externalauth/%s/callback?code=asd&state=%s", providerID, state)) require.NoError(t, err) req, err := http.NewRequestWithContext(context.Background(), "GET", oauthURL.String(), nil) require.NoError(t, err) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index bee782a1aa..c0a5cfcecf 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2486,7 +2486,7 @@ func (q *querier) UpdateTemplateVersionDescriptionByJobID(ctx context.Context, a } func (q *querier) UpdateTemplateVersionExternalAuthProvidersByJobID(ctx context.Context, arg database.UpdateTemplateVersionExternalAuthProvidersByJobIDParams) error { - // An actor is allowed to update the template version git auth providers if they are authorized to update the template. + // An actor is allowed to update the template version external auth providers if they are authorized to update the template. tv, err := q.db.GetTemplateVersionByJobID(ctx, arg.JobID) if err != nil { return err diff --git a/coderd/gitauth.go b/coderd/externalauth.go similarity index 73% rename from coderd/gitauth.go rename to coderd/externalauth.go index 728f4d6613..f668d6b5a9 100644 --- a/coderd/gitauth.go +++ b/coderd/externalauth.go @@ -10,31 +10,31 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbtime" - "github.com/coder/coder/v2/coderd/gitauth" + "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/codersdk" ) -// @Summary Get git auth by ID -// @ID get-git-auth-by-id +// @Summary Get external auth by ID +// @ID get-external-auth-by-id // @Security CoderSessionToken // @Produce json // @Tags Git -// @Param gitauth path string true "Git Provider ID" format(string) -// @Success 200 {object} codersdk.GitAuth -// @Router /gitauth/{gitauth} [get] -func (api *API) gitAuthByID(w http.ResponseWriter, r *http.Request) { - config := httpmw.GitAuthParam(r) +// @Param externalauth path string true "Git Provider ID" format(string) +// @Success 200 {object} codersdk.ExternalAuth +// @Router /externalauth/{externalauth} [get] +func (api *API) externalAuthByID(w http.ResponseWriter, r *http.Request) { + config := httpmw.ExternalAuthParam(r) apiKey := httpmw.APIKey(r) ctx := r.Context() - res := codersdk.GitAuth{ + res := codersdk.ExternalAuth{ Authenticated: false, Device: config.DeviceAuth != nil, AppInstallURL: config.AppInstallURL, Type: config.Type.Pretty(), - AppInstallations: []codersdk.GitAuthAppInstallation{}, + AppInstallations: []codersdk.ExternalAuthAppInstallation{}, } link, err := api.Database.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{ @@ -44,7 +44,7 @@ func (api *API) gitAuthByID(w http.ResponseWriter, r *http.Request) { if err != nil { if !errors.Is(err, sql.ErrNoRows) { httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{ - Message: "Failed to get git auth link.", + Message: "Failed to get external auth link.", Detail: err.Error(), }) return @@ -71,24 +71,24 @@ func (api *API) gitAuthByID(w http.ResponseWriter, r *http.Request) { return } if res.AppInstallations == nil { - res.AppInstallations = []codersdk.GitAuthAppInstallation{} + res.AppInstallations = []codersdk.ExternalAuthAppInstallation{} } httpapi.Write(ctx, w, http.StatusOK, res) } -// @Summary Post git auth device by ID -// @ID post-git-auth-device-by-id +// @Summary Post external auth device by ID +// @ID post-external-auth-device-by-id // @Security CoderSessionToken // @Tags Git -// @Param gitauth path string true "Git Provider ID" format(string) +// @Param externalauth path string true "External Provider ID" format(string) // @Success 204 -// @Router /gitauth/{gitauth}/device [post] -func (api *API) postGitAuthDeviceByID(rw http.ResponseWriter, r *http.Request) { +// @Router /externalauth/{externalauth}/device [post] +func (api *API) postExternalAuthDeviceByID(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() apiKey := httpmw.APIKey(r) - config := httpmw.GitAuthParam(r) + config := httpmw.ExternalAuthParam(r) - var req codersdk.GitAuthDeviceExchange + var req codersdk.ExternalAuthDeviceExchange if !httpapi.Read(ctx, rw, r, &req) { return } @@ -116,7 +116,7 @@ func (api *API) postGitAuthDeviceByID(rw http.ResponseWriter, r *http.Request) { if err != nil { if !errors.Is(err, sql.ErrNoRows) { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Failed to get git auth link.", + Message: "Failed to get external auth link.", Detail: err.Error(), }) return @@ -133,7 +133,7 @@ func (api *API) postGitAuthDeviceByID(rw http.ResponseWriter, r *http.Request) { }) if err != nil { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Failed to insert git auth link.", + Message: "Failed to insert external auth link.", Detail: err.Error(), }) return @@ -149,7 +149,7 @@ func (api *API) postGitAuthDeviceByID(rw http.ResponseWriter, r *http.Request) { }) if err != nil { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Failed to update git auth link.", + Message: "Failed to update external auth link.", Detail: err.Error(), }) return @@ -158,16 +158,16 @@ func (api *API) postGitAuthDeviceByID(rw http.ResponseWriter, r *http.Request) { httpapi.Write(ctx, rw, http.StatusNoContent, nil) } -// @Summary Get git auth device by ID. -// @ID get-git-auth-device-by-id +// @Summary Get external auth device by ID. +// @ID get-external-auth-device-by-id // @Security CoderSessionToken // @Produce json // @Tags Git -// @Param gitauth path string true "Git Provider ID" format(string) -// @Success 200 {object} codersdk.GitAuthDevice -// @Router /gitauth/{gitauth}/device [get] -func (*API) gitAuthDeviceByID(rw http.ResponseWriter, r *http.Request) { - config := httpmw.GitAuthParam(r) +// @Param externalauth path string true "Git Provider ID" format(string) +// @Success 200 {object} codersdk.ExternalAuthDevice +// @Router /externalauth/{externalauth}/device [get] +func (*API) externalAuthDeviceByID(rw http.ResponseWriter, r *http.Request) { + config := httpmw.ExternalAuthParam(r) ctx := r.Context() if config.DeviceAuth == nil { @@ -189,7 +189,7 @@ func (*API) gitAuthDeviceByID(rw http.ResponseWriter, r *http.Request) { httpapi.Write(ctx, rw, http.StatusOK, deviceAuth) } -func (api *API) gitAuthCallback(gitAuthConfig *gitauth.Config) http.HandlerFunc { +func (api *API) externalAuthCallback(externalAuthConfig *externalauth.Config) http.HandlerFunc { return func(rw http.ResponseWriter, r *http.Request) { var ( ctx = r.Context() @@ -198,20 +198,20 @@ func (api *API) gitAuthCallback(gitAuthConfig *gitauth.Config) http.HandlerFunc ) _, err := api.Database.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{ - ProviderID: gitAuthConfig.ID, + ProviderID: externalAuthConfig.ID, UserID: apiKey.UserID, }) if err != nil { if !errors.Is(err, sql.ErrNoRows) { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Failed to get git auth link.", + Message: "Failed to get external auth link.", Detail: err.Error(), }) return } _, err = api.Database.InsertExternalAuthLink(ctx, database.InsertExternalAuthLinkParams{ - ProviderID: gitAuthConfig.ID, + ProviderID: externalAuthConfig.ID, UserID: apiKey.UserID, CreatedAt: dbtime.Now(), UpdatedAt: dbtime.Now(), @@ -221,14 +221,14 @@ func (api *API) gitAuthCallback(gitAuthConfig *gitauth.Config) http.HandlerFunc }) if err != nil { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Failed to insert git auth link.", + Message: "Failed to insert external auth link.", Detail: err.Error(), }) return } } else { _, err = api.Database.UpdateExternalAuthLink(ctx, database.UpdateExternalAuthLinkParams{ - ProviderID: gitAuthConfig.ID, + ProviderID: externalAuthConfig.ID, UserID: apiKey.UserID, UpdatedAt: dbtime.Now(), OAuthAccessToken: state.Token.AccessToken, @@ -237,7 +237,7 @@ func (api *API) gitAuthCallback(gitAuthConfig *gitauth.Config) http.HandlerFunc }) if err != nil { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Failed to update git auth link.", + Message: "Failed to update external auth link.", Detail: err.Error(), }) return @@ -247,7 +247,7 @@ func (api *API) gitAuthCallback(gitAuthConfig *gitauth.Config) http.HandlerFunc redirect := state.Redirect if redirect == "" { // This is a nicely rendered screen on the frontend - redirect = fmt.Sprintf("/gitauth/%s", gitAuthConfig.ID) + redirect = fmt.Sprintf("/externalauth/%s", externalAuthConfig.ID) } http.Redirect(rw, r, redirect, http.StatusTemporaryRedirect) } diff --git a/coderd/gitauth/config.go b/coderd/externalauth/config.go similarity index 83% rename from coderd/gitauth/config.go rename to coderd/externalauth/config.go index 820721ad1b..51ef866354 100644 --- a/coderd/gitauth/config.go +++ b/coderd/externalauth/config.go @@ -1,4 +1,4 @@ -package gitauth +package externalauth import ( "context" @@ -65,35 +65,35 @@ type Config struct { // RefreshToken automatically refreshes the token if expired and permitted. // It returns the token and a bool indicating if the token is valid. -func (c *Config) RefreshToken(ctx context.Context, db database.Store, gitAuthLink database.ExternalAuthLink) (database.ExternalAuthLink, bool, error) { +func (c *Config) RefreshToken(ctx context.Context, db database.Store, externalAuthLink database.ExternalAuthLink) (database.ExternalAuthLink, bool, error) { // If the token is expired and refresh is disabled, we prompt // the user to authenticate again. if c.NoRefresh && // If the time is set to 0, then it should never expire. // This is true for github, which has no expiry. - !gitAuthLink.OAuthExpiry.IsZero() && - gitAuthLink.OAuthExpiry.Before(dbtime.Now()) { - return gitAuthLink, false, nil + !externalAuthLink.OAuthExpiry.IsZero() && + externalAuthLink.OAuthExpiry.Before(dbtime.Now()) { + return externalAuthLink, false, nil } // This is additional defensive programming. Because TokenSource is an interface, // we cannot be sure that the implementation will treat an 'IsZero' time // as "not-expired". The default implementation does, but a custom implementation // might not. Removing the refreshToken will guarantee a refresh will fail. - refreshToken := gitAuthLink.OAuthRefreshToken + refreshToken := externalAuthLink.OAuthRefreshToken if c.NoRefresh { refreshToken = "" } token, err := c.TokenSource(ctx, &oauth2.Token{ - AccessToken: gitAuthLink.OAuthAccessToken, + AccessToken: externalAuthLink.OAuthAccessToken, RefreshToken: refreshToken, - Expiry: gitAuthLink.OAuthExpiry, + Expiry: externalAuthLink.OAuthExpiry, }).Token() if err != nil { // Even if the token fails to be obtained, we still return false because // we aren't trying to surface an error, we're just trying to obtain a valid token. - return gitAuthLink, false, nil + return externalAuthLink, false, nil } r := retry.New(50*time.Millisecond, 200*time.Millisecond) // See the comment below why the retry and cancel is required. @@ -102,7 +102,7 @@ func (c *Config) RefreshToken(ctx context.Context, db database.Store, gitAuthLin validate: valid, _, err := c.ValidateToken(ctx, token.AccessToken) if err != nil { - return gitAuthLink, false, xerrors.Errorf("validate git auth token: %w", err) + return externalAuthLink, false, xerrors.Errorf("validate external auth token: %w", err) } if !valid { // A customer using GitHub in Australia reported that validating immediately @@ -116,29 +116,29 @@ validate: goto validate } // The token is no longer valid! - return gitAuthLink, false, nil + return externalAuthLink, false, nil } - if token.AccessToken != gitAuthLink.OAuthAccessToken { + if token.AccessToken != externalAuthLink.OAuthAccessToken { // Update it - gitAuthLink, err = db.UpdateExternalAuthLink(ctx, database.UpdateExternalAuthLinkParams{ + externalAuthLink, err = db.UpdateExternalAuthLink(ctx, database.UpdateExternalAuthLinkParams{ ProviderID: c.ID, - UserID: gitAuthLink.UserID, + UserID: externalAuthLink.UserID, UpdatedAt: dbtime.Now(), OAuthAccessToken: token.AccessToken, OAuthRefreshToken: token.RefreshToken, OAuthExpiry: token.Expiry, }) if err != nil { - return gitAuthLink, false, xerrors.Errorf("update git auth link: %w", err) + return externalAuthLink, false, xerrors.Errorf("update external auth link: %w", err) } } - return gitAuthLink, true, nil + return externalAuthLink, true, nil } // ValidateToken ensures the Git token provided is valid! // The user is optionally returned if the provider supports it. -func (c *Config) ValidateToken(ctx context.Context, token string) (bool, *codersdk.GitAuthUser, error) { +func (c *Config) ValidateToken(ctx context.Context, token string) (bool, *codersdk.ExternalAuthUser, error) { if c.ValidateURL == "" { // Default that the token is valid if no validation URL is provided. return true, nil, nil @@ -167,12 +167,12 @@ func (c *Config) ValidateToken(ctx context.Context, token string) (bool, *coders return false, nil, xerrors.Errorf("status %d: body: %s", res.StatusCode, data) } - var user *codersdk.GitAuthUser + var user *codersdk.ExternalAuthUser if c.Type == codersdk.ExternalAuthProviderGitHub { var ghUser github.User err = json.NewDecoder(res.Body).Decode(&ghUser) if err == nil { - user = &codersdk.GitAuthUser{ + user = &codersdk.ExternalAuthUser{ Login: ghUser.GetLogin(), AvatarURL: ghUser.GetAvatarURL(), ProfileURL: ghUser.GetHTMLURL(), @@ -194,7 +194,7 @@ type AppInstallation struct { // AppInstallations returns a list of app installations for the given token. // If the provider does not support app installations, it returns nil. -func (c *Config) AppInstallations(ctx context.Context, token string) ([]codersdk.GitAuthAppInstallation, bool, error) { +func (c *Config) AppInstallations(ctx context.Context, token string) ([]codersdk.ExternalAuthAppInstallation, bool, error) { if c.AppInstallationsURL == "" { return nil, false, nil } @@ -213,7 +213,7 @@ func (c *Config) AppInstallations(ctx context.Context, token string) ([]codersdk if res.StatusCode != http.StatusOK { return nil, false, nil } - installs := []codersdk.GitAuthAppInstallation{} + installs := []codersdk.ExternalAuthAppInstallation{} if c.Type == codersdk.ExternalAuthProviderGitHub { var ghInstalls struct { Installations []*github.Installation `json:"installations"` @@ -227,10 +227,10 @@ func (c *Config) AppInstallations(ctx context.Context, token string) ([]codersdk if account == nil { continue } - installs = append(installs, codersdk.GitAuthAppInstallation{ + installs = append(installs, codersdk.ExternalAuthAppInstallation{ ID: int(installation.GetID()), ConfigureURL: installation.GetHTMLURL(), - Account: codersdk.GitAuthUser{ + Account: codersdk.ExternalAuthUser{ Login: account.GetLogin(), AvatarURL: account.GetAvatarURL(), ProfileURL: account.GetHTMLURL(), @@ -266,30 +266,30 @@ func ConvertConfig(entries []codersdk.GitAuthConfig, accessURL *url.URL) ([]*Con entry.ID = string(typ) } if valid := httpapi.NameValid(entry.ID); valid != nil { - return nil, xerrors.Errorf("git auth provider %q doesn't have a valid id: %w", entry.ID, valid) + return nil, xerrors.Errorf("external auth provider %q doesn't have a valid id: %w", entry.ID, valid) } _, exists := ids[entry.ID] if exists { if entry.ID == string(typ) { - return nil, xerrors.Errorf("multiple %s git auth providers provided. you must specify a unique id for each", typ) + return nil, xerrors.Errorf("multiple %s external auth providers provided. you must specify a unique id for each", typ) } return nil, xerrors.Errorf("multiple git providers exist with the id %q. specify a unique id for each", entry.ID) } ids[entry.ID] = struct{}{} if entry.ClientID == "" { - return nil, xerrors.Errorf("%q git auth provider: client_id must be provided", entry.ID) + return nil, xerrors.Errorf("%q external auth provider: client_id must be provided", entry.ID) } - authRedirect, err := accessURL.Parse(fmt.Sprintf("/gitauth/%s/callback", entry.ID)) + authRedirect, err := accessURL.Parse(fmt.Sprintf("/externalauth/%s/callback", entry.ID)) if err != nil { - return nil, xerrors.Errorf("parse gitauth callback url: %w", err) + return nil, xerrors.Errorf("parse externalauth callback url: %w", err) } regex := regex[typ] if entry.Regex != "" { regex, err = regexp.Compile(entry.Regex) if err != nil { - return nil, xerrors.Errorf("compile regex for git auth provider %q: %w", entry.ID, entry.Regex) + return nil, xerrors.Errorf("compile regex for external auth provider %q: %w", entry.ID, entry.Regex) } } @@ -339,7 +339,7 @@ func ConvertConfig(entries []codersdk.GitAuthConfig, accessURL *url.URL) ([]*Con entry.DeviceCodeURL = deviceAuthURL[typ] } if entry.DeviceCodeURL == "" { - return nil, xerrors.Errorf("git auth provider %q: device auth url must be provided", entry.ID) + return nil, xerrors.Errorf("external auth provider %q: device auth url must be provided", entry.ID) } cfg.DeviceAuth = &DeviceAuth{ ClientID: entry.ClientID, diff --git a/coderd/gitauth/config_test.go b/coderd/externalauth/config_test.go similarity index 91% rename from coderd/gitauth/config_test.go rename to coderd/externalauth/config_test.go index 3fecc561af..4bd9e0b162 100644 --- a/coderd/gitauth/config_test.go +++ b/coderd/externalauth/config_test.go @@ -1,4 +1,4 @@ -package gitauth_test +package externalauth_test import ( "context" @@ -19,14 +19,13 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbfake" - "github.com/coder/coder/v2/coderd/gitauth" + "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/testutil" ) func TestRefreshToken(t *testing.T) { t.Parallel() - const providerID = "test-idp" expired := time.Now().Add(time.Hour * -1) t.Run("NoRefreshExpired", func(t *testing.T) { @@ -44,7 +43,7 @@ func TestRefreshToken(t *testing.T) { return nil, xerrors.New("should not be called") }), }, - GitConfigOpt: func(cfg *gitauth.Config) { + GitConfigOpt: func(cfg *externalauth.Config) { cfg.NoRefresh = true }, }) @@ -75,7 +74,7 @@ func TestRefreshToken(t *testing.T) { return jwt.MapClaims{}, nil }), }, - GitConfigOpt: func(cfg *gitauth.Config) { + GitConfigOpt: func(cfg *externalauth.Config) { cfg.NoRefresh = true }, }) @@ -92,7 +91,7 @@ func TestRefreshToken(t *testing.T) { t.Run("FalseIfTokenSourceFails", func(t *testing.T) { t.Parallel() - config := &gitauth.Config{ + config := &externalauth.Config{ OAuth2Config: &testutil.OAuth2Config{ TokenSourceFunc: func() (*oauth2.Token, error) { return nil, xerrors.New("failure") @@ -118,7 +117,7 @@ func TestRefreshToken(t *testing.T) { return jwt.MapClaims{}, xerrors.New(staticError) }), }, - GitConfigOpt: func(cfg *gitauth.Config) { + GitConfigOpt: func(cfg *externalauth.Config) { }, }) @@ -143,7 +142,7 @@ func TestRefreshToken(t *testing.T) { return jwt.MapClaims{}, oidctest.StatusError(http.StatusUnauthorized, xerrors.New(staticError)) }), }, - GitConfigOpt: func(cfg *gitauth.Config) { + GitConfigOpt: func(cfg *externalauth.Config) { }, }) @@ -176,7 +175,7 @@ func TestRefreshToken(t *testing.T) { return jwt.MapClaims{}, oidctest.StatusError(http.StatusUnauthorized, xerrors.New(staticError)) }), }, - GitConfigOpt: func(cfg *gitauth.Config) { + GitConfigOpt: func(cfg *externalauth.Config) { cfg.Type = codersdk.ExternalAuthProviderGitHub }, }) @@ -206,7 +205,7 @@ func TestRefreshToken(t *testing.T) { return jwt.MapClaims{}, nil }), }, - GitConfigOpt: func(cfg *gitauth.Config) { + GitConfigOpt: func(cfg *externalauth.Config) { cfg.Type = codersdk.ExternalAuthProviderGitHub }, }) @@ -237,7 +236,7 @@ func TestRefreshToken(t *testing.T) { return jwt.MapClaims{}, nil }), }, - GitConfigOpt: func(cfg *gitauth.Config) { + GitConfigOpt: func(cfg *externalauth.Config) { cfg.Type = codersdk.ExternalAuthProviderGitHub }, DB: db, @@ -268,7 +267,7 @@ func TestConvertYAML(t *testing.T) { for _, tc := range []struct { Name string Input []codersdk.GitAuthConfig - Output []*gitauth.Config + Output []*externalauth.Config Error string }{{ Name: "InvalidType", @@ -298,7 +297,7 @@ func TestConvertYAML(t *testing.T) { }, { Type: string(codersdk.ExternalAuthProviderGitHub), }}, - Error: "multiple github git auth providers provided", + Error: "multiple github external auth providers provided", }, { Name: "InvalidRegex", Input: []codersdk.GitAuthConfig{{ @@ -307,7 +306,7 @@ func TestConvertYAML(t *testing.T) { ClientSecret: "example", Regex: `\K`, }}, - Error: "compile regex for git auth provider", + Error: "compile regex for external auth provider", }, { Name: "NoDeviceURL", Input: []codersdk.GitAuthConfig{{ @@ -321,7 +320,7 @@ func TestConvertYAML(t *testing.T) { tc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() - output, err := gitauth.ConvertConfig(tc.Input, &url.URL{}) + output, err := externalauth.ConvertConfig(tc.Input, &url.URL{}) if tc.Error != "" { require.Error(t, err) require.Contains(t, err.Error(), tc.Error) @@ -333,7 +332,7 @@ func TestConvertYAML(t *testing.T) { t.Run("CustomScopesAndEndpoint", func(t *testing.T) { t.Parallel() - config, err := gitauth.ConvertConfig([]codersdk.GitAuthConfig{{ + config, err := externalauth.ConvertConfig([]codersdk.GitAuthConfig{{ Type: string(codersdk.ExternalAuthProviderGitLab), ClientID: "id", ClientSecret: "secret", @@ -342,24 +341,24 @@ func TestConvertYAML(t *testing.T) { Scopes: []string{"read"}, }}, &url.URL{}) require.NoError(t, err) - require.Equal(t, "https://auth.com?client_id=id&redirect_uri=%2Fgitauth%2Fgitlab%2Fcallback&response_type=code&scope=read", config[0].AuthCodeURL("")) + require.Equal(t, "https://auth.com?client_id=id&redirect_uri=%2Fexternalauth%2Fgitlab%2Fcallback&response_type=code&scope=read", config[0].AuthCodeURL("")) }) } type testConfig struct { FakeIDPOpts []oidctest.FakeIDPOpt CoderOIDCConfigOpts []func(cfg *coderd.OIDCConfig) - GitConfigOpt func(cfg *gitauth.Config) + GitConfigOpt func(cfg *externalauth.Config) // If DB is passed in, the link will be inserted into the DB. DB database.Store } -// setupTest will configure a fake IDP and a gitauth.Config for testing. +// setupTest will configure a fake IDP and a externalauth.Config for testing. // The Fake's userinfo endpoint is used for validating tokens. // No http servers are started so use the fake IDP's HTTPClient to make requests. // The returned token is a fully valid token for the IDP. Feel free to manipulate it // to test different scenarios. -func setupOauth2Test(t *testing.T, settings testConfig) (*oidctest.FakeIDP, *gitauth.Config, database.ExternalAuthLink) { +func setupOauth2Test(t *testing.T, settings testConfig) (*oidctest.FakeIDP, *externalauth.Config, database.ExternalAuthLink) { t.Helper() const providerID = "test-idp" @@ -367,7 +366,7 @@ func setupOauth2Test(t *testing.T, settings testConfig) (*oidctest.FakeIDP, *git append([]oidctest.FakeIDPOpt{}, settings.FakeIDPOpts...)..., ) - config := &gitauth.Config{ + config := &externalauth.Config{ OAuth2Config: fake.OIDCConfig(t, nil, settings.CoderOIDCConfigOpts...), ID: providerID, ValidateURL: fake.WellknownConfig().UserInfoURL, diff --git a/coderd/gitauth/oauth.go b/coderd/externalauth/oauth.go similarity index 98% rename from coderd/gitauth/oauth.go rename to coderd/externalauth/oauth.go index 24b0416d20..0f679e8fe0 100644 --- a/coderd/gitauth/oauth.go +++ b/coderd/externalauth/oauth.go @@ -1,4 +1,4 @@ -package gitauth +package externalauth import ( "context" @@ -107,7 +107,7 @@ type DeviceAuth struct { // AuthorizeDevice begins the device authorization flow. // See: https://tools.ietf.org/html/rfc8628#section-3.1 -func (c *DeviceAuth) AuthorizeDevice(ctx context.Context) (*codersdk.GitAuthDevice, error) { +func (c *DeviceAuth) AuthorizeDevice(ctx context.Context) (*codersdk.ExternalAuthDevice, error) { if c.CodeURL == "" { return nil, xerrors.New("oauth2: device code URL not set") } @@ -126,7 +126,7 @@ func (c *DeviceAuth) AuthorizeDevice(ctx context.Context) (*codersdk.GitAuthDevi } defer resp.Body.Close() var r struct { - codersdk.GitAuthDevice + codersdk.ExternalAuthDevice ErrorDescription string `json:"error_description"` } err = json.NewDecoder(resp.Body).Decode(&r) @@ -136,7 +136,7 @@ func (c *DeviceAuth) AuthorizeDevice(ctx context.Context) (*codersdk.GitAuthDevi if r.ErrorDescription != "" { return nil, xerrors.New(r.ErrorDescription) } - return &r.GitAuthDevice, nil + return &r.ExternalAuthDevice, nil } type ExchangeDeviceCodeResponse struct { diff --git a/coderd/gitauth_test.go b/coderd/externalauth_test.go similarity index 84% rename from coderd/gitauth_test.go rename to coderd/externalauth_test.go index 985a823af4..b5090b6058 100644 --- a/coderd/gitauth_test.go +++ b/coderd/externalauth_test.go @@ -18,7 +18,7 @@ import ( "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database/dbtime" - "github.com/coder/coder/v2/coderd/gitauth" + "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" @@ -26,19 +26,19 @@ import ( "github.com/coder/coder/v2/testutil" ) -func TestGitAuthByID(t *testing.T) { +func TestExternalAuthByID(t *testing.T) { t.Parallel() t.Run("Unauthenticated", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{ - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ ID: "test", OAuth2Config: &testutil.OAuth2Config{}, Type: codersdk.ExternalAuthProviderGitHub, }}, }) coderdtest.CreateFirstUser(t, client) - auth, err := client.GitAuthByID(context.Background(), "test") + auth, err := client.ExternalAuthByID(context.Background(), "test") require.NoError(t, err) require.False(t, auth.Authenticated) }) @@ -47,7 +47,7 @@ func TestGitAuthByID(t *testing.T) { // still return that the provider is authenticated. t.Parallel() client := coderdtest.New(t, &coderdtest.Options{ - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ ID: "test", OAuth2Config: &testutil.OAuth2Config{}, // AzureDevops doesn't have a user endpoint! @@ -55,9 +55,9 @@ func TestGitAuthByID(t *testing.T) { }}, }) coderdtest.CreateFirstUser(t, client) - resp := coderdtest.RequestGitAuthCallback(t, "test", client) + resp := coderdtest.RequestExternalAuthCallback(t, "test", client) _ = resp.Body.Close() - auth, err := client.GitAuthByID(context.Background(), "test") + auth, err := client.ExternalAuthByID(context.Background(), "test") require.NoError(t, err) require.True(t, auth.Authenticated) }) @@ -71,7 +71,7 @@ func TestGitAuthByID(t *testing.T) { })) defer validateSrv.Close() client := coderdtest.New(t, &coderdtest.Options{ - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ ID: "test", ValidateURL: validateSrv.URL, OAuth2Config: &testutil.OAuth2Config{}, @@ -79,9 +79,9 @@ func TestGitAuthByID(t *testing.T) { }}, }) coderdtest.CreateFirstUser(t, client) - resp := coderdtest.RequestGitAuthCallback(t, "test", client) + resp := coderdtest.RequestExternalAuthCallback(t, "test", client) _ = resp.Body.Close() - auth, err := client.GitAuthByID(context.Background(), "test") + auth, err := client.ExternalAuthByID(context.Background(), "test") require.NoError(t, err) require.True(t, auth.Authenticated) require.NotNil(t, auth.User) @@ -111,7 +111,7 @@ func TestGitAuthByID(t *testing.T) { })) defer srv.Close() client := coderdtest.New(t, &coderdtest.Options{ - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ ID: "test", ValidateURL: srv.URL + "/user", AppInstallationsURL: srv.URL + "/installs", @@ -120,9 +120,9 @@ func TestGitAuthByID(t *testing.T) { }}, }) coderdtest.CreateFirstUser(t, client) - resp := coderdtest.RequestGitAuthCallback(t, "test", client) + resp := coderdtest.RequestExternalAuthCallback(t, "test", client) _ = resp.Body.Close() - auth, err := client.GitAuthByID(context.Background(), "test") + auth, err := client.ExternalAuthByID(context.Background(), "test") require.NoError(t, err) require.True(t, auth.Authenticated) require.NotNil(t, auth.User) @@ -137,12 +137,12 @@ func TestGitAuthDevice(t *testing.T) { t.Run("NotSupported", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{ - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ ID: "test", }}, }) coderdtest.CreateFirstUser(t, client) - _, err := client.GitAuthDeviceByID(context.Background(), "test") + _, err := client.ExternalAuthDeviceByID(context.Background(), "test") var sdkErr *codersdk.Error require.ErrorAs(t, err, &sdkErr) require.Equal(t, http.StatusBadRequest, sdkErr.StatusCode()) @@ -150,15 +150,15 @@ func TestGitAuthDevice(t *testing.T) { t.Run("FetchCode", func(t *testing.T) { t.Parallel() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - httpapi.Write(r.Context(), w, http.StatusOK, codersdk.GitAuthDevice{ + httpapi.Write(r.Context(), w, http.StatusOK, codersdk.ExternalAuthDevice{ UserCode: "hey", }) })) defer srv.Close() client := coderdtest.New(t, &coderdtest.Options{ - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ ID: "test", - DeviceAuth: &gitauth.DeviceAuth{ + DeviceAuth: &externalauth.DeviceAuth{ ClientID: "test", CodeURL: srv.URL, Scopes: []string{"repo"}, @@ -166,13 +166,13 @@ func TestGitAuthDevice(t *testing.T) { }}, }) coderdtest.CreateFirstUser(t, client) - device, err := client.GitAuthDeviceByID(context.Background(), "test") + device, err := client.ExternalAuthDeviceByID(context.Background(), "test") require.NoError(t, err) require.Equal(t, "hey", device.UserCode) }) t.Run("ExchangeCode", func(t *testing.T) { t.Parallel() - resp := gitauth.ExchangeDeviceCodeResponse{ + resp := externalauth.ExchangeDeviceCodeResponse{ Error: "authorization_pending", } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -180,9 +180,9 @@ func TestGitAuthDevice(t *testing.T) { })) defer srv.Close() client := coderdtest.New(t, &coderdtest.Options{ - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ ID: "test", - DeviceAuth: &gitauth.DeviceAuth{ + DeviceAuth: &externalauth.DeviceAuth{ ClientID: "test", TokenURL: srv.URL, Scopes: []string{"repo"}, @@ -190,7 +190,7 @@ func TestGitAuthDevice(t *testing.T) { }}, }) coderdtest.CreateFirstUser(t, client) - err := client.GitAuthDeviceExchange(context.Background(), "test", codersdk.GitAuthDeviceExchange{ + err := client.ExternalAuthDeviceExchange(context.Background(), "test", codersdk.ExternalAuthDeviceExchange{ DeviceCode: "hey", }) var sdkErr *codersdk.Error @@ -198,16 +198,16 @@ func TestGitAuthDevice(t *testing.T) { require.Equal(t, http.StatusBadRequest, sdkErr.StatusCode()) require.Equal(t, "authorization_pending", sdkErr.Detail) - resp = gitauth.ExchangeDeviceCodeResponse{ + resp = externalauth.ExchangeDeviceCodeResponse{ AccessToken: "hey", } - err = client.GitAuthDeviceExchange(context.Background(), "test", codersdk.GitAuthDeviceExchange{ + err = client.ExternalAuthDeviceExchange(context.Background(), "test", codersdk.ExternalAuthDeviceExchange{ DeviceCode: "hey", }) require.NoError(t, err) - auth, err := client.GitAuthByID(context.Background(), "test") + auth, err := client.ExternalAuthByID(context.Background(), "test") require.NoError(t, err) require.True(t, auth.Authenticated) }) @@ -220,7 +220,7 @@ func TestGitAuthCallback(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{ IncludeProvisionerDaemon: true, - ExternalAuthConfigs: []*gitauth.Config{}, + ExternalAuthConfigs: []*externalauth.Config{}, }) user := coderdtest.CreateFirstUser(t, client) authToken := uuid.NewString() @@ -245,7 +245,7 @@ func TestGitAuthCallback(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{ IncludeProvisionerDaemon: true, - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ OAuth2Config: &testutil.OAuth2Config{}, ID: "github", Regex: regexp.MustCompile(`github\.com`), @@ -268,27 +268,27 @@ func TestGitAuthCallback(t *testing.T) { agentClient.SetSessionToken(authToken) token, err := agentClient.GitAuth(context.Background(), "github.com/asd/asd", false) require.NoError(t, err) - require.True(t, strings.HasSuffix(token.URL, fmt.Sprintf("/gitauth/%s", "github"))) + require.True(t, strings.HasSuffix(token.URL, fmt.Sprintf("/externalauth/%s", "github"))) }) t.Run("UnauthorizedCallback", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{ IncludeProvisionerDaemon: true, - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ OAuth2Config: &testutil.OAuth2Config{}, ID: "github", Regex: regexp.MustCompile(`github\.com`), Type: codersdk.ExternalAuthProviderGitHub, }}, }) - resp := coderdtest.RequestGitAuthCallback(t, "github", client) + resp := coderdtest.RequestExternalAuthCallback(t, "github", client) require.Equal(t, http.StatusSeeOther, resp.StatusCode) }) t.Run("AuthorizedCallback", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{ IncludeProvisionerDaemon: true, - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ OAuth2Config: &testutil.OAuth2Config{}, ID: "github", Regex: regexp.MustCompile(`github\.com`), @@ -296,14 +296,14 @@ func TestGitAuthCallback(t *testing.T) { }}, }) _ = coderdtest.CreateFirstUser(t, client) - resp := coderdtest.RequestGitAuthCallback(t, "github", client) + resp := coderdtest.RequestExternalAuthCallback(t, "github", client) require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode) location, err := resp.Location() require.NoError(t, err) - require.Equal(t, "/gitauth/github", location.Path) + require.Equal(t, "/externalauth/github", location.Path) // Callback again to simulate updating the token. - resp = coderdtest.RequestGitAuthCallback(t, "github", client) + resp = coderdtest.RequestExternalAuthCallback(t, "github", client) require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode) }) t.Run("ValidateURL", func(t *testing.T) { @@ -314,7 +314,7 @@ func TestGitAuthCallback(t *testing.T) { defer srv.Close() client := coderdtest.New(t, &coderdtest.Options{ IncludeProvisionerDaemon: true, - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ ValidateURL: srv.URL, OAuth2Config: &testutil.OAuth2Config{}, ID: "github", @@ -337,7 +337,7 @@ func TestGitAuthCallback(t *testing.T) { agentClient := agentsdk.New(client.URL) agentClient.SetSessionToken(authToken) - resp := coderdtest.RequestGitAuthCallback(t, "github", client) + resp := coderdtest.RequestExternalAuthCallback(t, "github", client) require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode) // If the validation URL says unauthorized, the callback @@ -359,14 +359,14 @@ func TestGitAuthCallback(t *testing.T) { var apiError *codersdk.Error require.ErrorAs(t, err, &apiError) require.Equal(t, http.StatusInternalServerError, apiError.StatusCode()) - require.Equal(t, "validate git auth token: status 403: body: Something went wrong!", apiError.Detail) + require.Equal(t, "validate external auth token: status 403: body: Something went wrong!", apiError.Detail) }) t.Run("ExpiredNoRefresh", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{ IncludeProvisionerDaemon: true, - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ OAuth2Config: &testutil.OAuth2Config{ Token: &oauth2.Token{ AccessToken: "token", @@ -402,7 +402,7 @@ func TestGitAuthCallback(t *testing.T) { // In the configuration, we set our OAuth provider // to return an expired token. Coder consumes this // and stores it. - resp := coderdtest.RequestGitAuthCallback(t, "github", client) + resp := coderdtest.RequestExternalAuthCallback(t, "github", client) require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode) // Because the token is expired and `NoRefresh` is specified, @@ -416,7 +416,7 @@ func TestGitAuthCallback(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{ IncludeProvisionerDaemon: true, - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ OAuth2Config: &testutil.OAuth2Config{}, ID: "github", Regex: regexp.MustCompile(`github\.com`), @@ -452,7 +452,7 @@ func TestGitAuthCallback(t *testing.T) { time.Sleep(250 * time.Millisecond) - resp := coderdtest.RequestGitAuthCallback(t, "github", client) + resp := coderdtest.RequestExternalAuthCallback(t, "github", client) require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode) token = <-tokenChan require.Equal(t, "access_token", token.Username) diff --git a/coderd/gitauth/oauth_test.go b/coderd/gitauth/oauth_test.go deleted file mode 100644 index b85baeb935..0000000000 --- a/coderd/gitauth/oauth_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package gitauth_test - -import ( - "testing" -) - -func TestOAuthJWTConfig(t *testing.T) { - t.Parallel() -} diff --git a/coderd/httpmw/externalauthparam.go b/coderd/httpmw/externalauthparam.go new file mode 100644 index 0000000000..91c3e22f22 --- /dev/null +++ b/coderd/httpmw/externalauthparam.go @@ -0,0 +1,40 @@ +package httpmw + +import ( + "context" + "net/http" + + "github.com/go-chi/chi/v5" + + "github.com/coder/coder/v2/coderd/externalauth" + "github.com/coder/coder/v2/coderd/httpapi" +) + +type externalAuthParamContextKey struct{} + +func ExternalAuthParam(r *http.Request) *externalauth.Config { + config, ok := r.Context().Value(externalAuthParamContextKey{}).(*externalauth.Config) + if !ok { + panic("developer error: external auth param middleware not provided") + } + return config +} + +func ExtractExternalAuthParam(configs []*externalauth.Config) func(next http.Handler) http.Handler { + configByID := make(map[string]*externalauth.Config) + for _, c := range configs { + configByID[c.ID] = c + } + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + config, ok := configByID[chi.URLParam(r, "externalauth")] + if !ok { + httpapi.ResourceNotFound(w) + return + } + + r = r.WithContext(context.WithValue(r.Context(), externalAuthParamContextKey{}, config)) + next.ServeHTTP(w, r) + }) + } +} diff --git a/coderd/httpmw/gitauthparam_test.go b/coderd/httpmw/externalauthparam_test.go similarity index 72% rename from coderd/httpmw/gitauthparam_test.go rename to coderd/httpmw/externalauthparam_test.go index 665e438a23..4081e9f426 100644 --- a/coderd/httpmw/gitauthparam_test.go +++ b/coderd/httpmw/externalauthparam_test.go @@ -9,25 +9,25 @@ import ( "github.com/go-chi/chi/v5" "github.com/stretchr/testify/require" - "github.com/coder/coder/v2/coderd/gitauth" + "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/coderd/httpmw" ) //nolint:bodyclose -func TestGitAuthParam(t *testing.T) { +func TestExternalAuthParam(t *testing.T) { t.Parallel() t.Run("Found", func(t *testing.T) { t.Parallel() routeCtx := chi.NewRouteContext() - routeCtx.URLParams.Add("gitauth", "my-id") + routeCtx.URLParams.Add("externalauth", "my-id") r := httptest.NewRequest(http.MethodGet, "/", nil) r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, routeCtx)) res := httptest.NewRecorder() - httpmw.ExtractGitAuthParam([]*gitauth.Config{{ + httpmw.ExtractExternalAuthParam([]*externalauth.Config{{ ID: "my-id", }})(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "my-id", httpmw.GitAuthParam(r).ID) + require.Equal(t, "my-id", httpmw.ExternalAuthParam(r).ID) w.WriteHeader(http.StatusOK) })).ServeHTTP(res, r) @@ -37,12 +37,12 @@ func TestGitAuthParam(t *testing.T) { t.Run("NotFound", func(t *testing.T) { t.Parallel() routeCtx := chi.NewRouteContext() - routeCtx.URLParams.Add("gitauth", "my-id") + routeCtx.URLParams.Add("externalauth", "my-id") r := httptest.NewRequest(http.MethodGet, "/", nil) r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, routeCtx)) res := httptest.NewRecorder() - httpmw.ExtractGitAuthParam([]*gitauth.Config{})(nil).ServeHTTP(res, r) + httpmw.ExtractExternalAuthParam([]*externalauth.Config{})(nil).ServeHTTP(res, r) require.Equal(t, http.StatusNotFound, res.Result().StatusCode) }) diff --git a/coderd/httpmw/gitauthparam.go b/coderd/httpmw/gitauthparam.go deleted file mode 100644 index 240732275b..0000000000 --- a/coderd/httpmw/gitauthparam.go +++ /dev/null @@ -1,40 +0,0 @@ -package httpmw - -import ( - "context" - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/coder/coder/v2/coderd/gitauth" - "github.com/coder/coder/v2/coderd/httpapi" -) - -type gitAuthParamContextKey struct{} - -func GitAuthParam(r *http.Request) *gitauth.Config { - config, ok := r.Context().Value(gitAuthParamContextKey{}).(*gitauth.Config) - if !ok { - panic("developer error: gitauth param middleware not provided") - } - return config -} - -func ExtractGitAuthParam(configs []*gitauth.Config) func(next http.Handler) http.Handler { - configByID := make(map[string]*gitauth.Config) - for _, c := range configs { - configByID[c.ID] = c - } - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - config, ok := configByID[chi.URLParam(r, "gitauth")] - if !ok { - httpapi.ResourceNotFound(w) - return - } - - r = r.WithContext(context.WithValue(r.Context(), gitAuthParamContextKey{}, config)) - next.ServeHTTP(w, r) - }) - } -} diff --git a/coderd/httpmw/patternmatcher/routepatterns_test.go b/coderd/httpmw/patternmatcher/routepatterns_test.go index dc7f779136..880fa88e71 100644 --- a/coderd/httpmw/patternmatcher/routepatterns_test.go +++ b/coderd/httpmw/patternmatcher/routepatterns_test.go @@ -65,9 +65,9 @@ func Test_RoutePatterns(t *testing.T) { "/api/**", "/@*/*/apps/**", "/%40*/*/apps/**", - "/gitauth/*/callback", + "/externalauth/*/callback", }, - output: "^(/api/?|/api/.+/?|/@[^/]+/[^/]+/apps/.+/?|/%40[^/]+/[^/]+/apps/.+/?|/gitauth/[^/]+/callback/?)$", + output: "^(/api/?|/api/.+/?|/@[^/]+/[^/]+/apps/.+/?|/%40[^/]+/[^/]+/apps/.+/?|/externalauth/[^/]+/callback/?)$", }, { name: "Slash", diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 2c063660db..bbda529af6 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -31,7 +31,7 @@ import ( "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/database/pubsub" - "github.com/coder/coder/v2/coderd/gitauth" + "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/coderd/schedule" "github.com/coder/coder/v2/coderd/telemetry" @@ -49,7 +49,7 @@ const DefaultAcquireJobLongPollDur = time.Second * 5 type Options struct { OIDCConfig httpmw.OAuth2Config - ExternalAuthConfigs []*gitauth.Config + ExternalAuthConfigs []*externalauth.Config // TimeNowFn is only used in tests TimeNowFn func() time.Time @@ -62,7 +62,7 @@ type server struct { ID uuid.UUID Logger slog.Logger Provisioners []database.ProvisionerType - ExternalAuthConfigs []*gitauth.Config + ExternalAuthConfigs []*externalauth.Config Tags Tags Database database.Store Pubsub pubsub.Pubsub @@ -416,7 +416,7 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo if err != nil { return nil, failJob(fmt.Sprintf("acquire external auth link: %s", err)) } - var config *gitauth.Config + var config *externalauth.Config for _, c := range s.ExternalAuthConfigs { if c.ID != p { continue diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 7b380b6426..4b9ccd4e9a 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -31,7 +31,7 @@ import ( "github.com/coder/coder/v2/coderd/database/dbgen" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/database/pubsub" - "github.com/coder/coder/v2/coderd/gitauth" + "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/coderd/provisionerdserver" "github.com/coder/coder/v2/coderd/schedule" "github.com/coder/coder/v2/coderd/schedule/cron" @@ -143,7 +143,7 @@ func TestAcquireJob(t *testing.T) { gitAuthProvider := "github" srv, db, ps := setup(t, false, &overrides{ deploymentValues: dv, - externalAuthConfigs: []*gitauth.Config{{ + externalAuthConfigs: []*externalauth.Config{{ ID: gitAuthProvider, OAuth2Config: &testutil.OAuth2Config{}, }}, @@ -941,7 +941,7 @@ func TestCompleteJob(t *testing.T) { srvID := uuid.New() srv, db, _ := setup(t, false, &overrides{ id: &srvID, - externalAuthConfigs: []*gitauth.Config{{ + externalAuthConfigs: []*externalauth.Config{{ ID: "github", }}, }) @@ -1675,7 +1675,7 @@ func TestInsertWorkspaceResource(t *testing.T) { type overrides struct { deploymentValues *codersdk.DeploymentValues - externalAuthConfigs []*gitauth.Config + externalAuthConfigs []*externalauth.Config id *uuid.UUID templateScheduleStore *atomic.Pointer[schedule.TemplateScheduleStore] userQuietHoursScheduleStore *atomic.Pointer[schedule.UserQuietHoursScheduleStore] @@ -1691,7 +1691,7 @@ func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisi db := dbfake.New() ps := pubsub.NewInMemory() deploymentValues := &codersdk.DeploymentValues{} - var externalAuthConfigs []*gitauth.Config + var externalAuthConfigs []*externalauth.Config srvID := uuid.New() tss := testTemplateScheduleStore() uqhss := testUserQuietHoursScheduleStore() diff --git a/coderd/templateversions.go b/coderd/templateversions.go index a797d16979..cbc9bb0605 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -22,7 +22,7 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/database/provisionerjobs" - "github.com/coder/coder/v2/coderd/gitauth" + "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/coderd/parameter" @@ -273,15 +273,15 @@ func (api *API) templateVersionRichParameters(rw http.ResponseWriter, r *http.Re httpapi.Write(ctx, rw, http.StatusOK, templateVersionParameters) } -// @Summary Get git auth by template version -// @ID get-git-auth-by-template-version +// @Summary Get external auth by template version +// @ID get-external-auth-by-template-version // @Security CoderSessionToken // @Produce json // @Tags Templates // @Param templateversion path string true "Template version ID" format(uuid) // @Success 200 {array} codersdk.TemplateVersionExternalAuth -// @Router /templateversions/{templateversion}/gitauth [get] -func (api *API) templateVersionGitAuth(rw http.ResponseWriter, r *http.Request) { +// @Router /templateversions/{templateversion}/externalauth [get] +func (api *API) templateVersionExternalAuth(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() var ( apiKey = httpmw.APIKey(r) @@ -291,7 +291,7 @@ func (api *API) templateVersionGitAuth(rw http.ResponseWriter, r *http.Request) rawProviders := templateVersion.ExternalAuthProviders providers := make([]codersdk.TemplateVersionExternalAuth, 0) for _, rawProvider := range rawProviders { - var config *gitauth.Config + var config *externalauth.Config for _, provider := range api.ExternalAuthConfigs { if provider.ID == rawProvider { config = provider @@ -307,7 +307,7 @@ func (api *API) templateVersionGitAuth(rw http.ResponseWriter, r *http.Request) } // This is the URL that will redirect the user with a state token. - redirectURL, err := api.AccessURL.Parse(fmt.Sprintf("/gitauth/%s", config.ID)) + redirectURL, err := api.AccessURL.Parse(fmt.Sprintf("/externalauth/%s", config.ID)) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Failed to parse access URL.", @@ -333,7 +333,7 @@ func (api *API) templateVersionGitAuth(rw http.ResponseWriter, r *http.Request) } if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching git auth link.", + Message: "Internal error fetching external auth link.", Detail: err.Error(), }) return @@ -342,7 +342,7 @@ func (api *API) templateVersionGitAuth(rw http.ResponseWriter, r *http.Request) _, updated, err := config.RefreshToken(ctx, api.Database, authLink) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Failed to refresh git auth token.", + Message: "Failed to refresh external auth token.", Detail: err.Error(), }) return diff --git a/coderd/templateversions_test.go b/coderd/templateversions_test.go index 1218356703..7f9c7f54dc 100644 --- a/coderd/templateversions_test.go +++ b/coderd/templateversions_test.go @@ -16,7 +16,7 @@ import ( "github.com/coder/coder/v2/coderd/audit" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/coderd/gitauth" + "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/coderd/provisionerdserver" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/codersdk" @@ -319,7 +319,7 @@ func TestPatchCancelTemplateVersion(t *testing.T) { }) } -func TestTemplateVersionsGitAuth(t *testing.T) { +func TestTemplateVersionsExternalAuth(t *testing.T) { t.Parallel() t.Run("Empty", func(t *testing.T) { t.Parallel() @@ -338,7 +338,7 @@ func TestTemplateVersionsGitAuth(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{ IncludeProvisionerDaemon: true, - ExternalAuthConfigs: []*gitauth.Config{{ + ExternalAuthConfigs: []*externalauth.Config{{ OAuth2Config: &testutil.OAuth2Config{}, ID: "github", Regex: regexp.MustCompile(`github\.com`), @@ -351,7 +351,7 @@ func TestTemplateVersionsGitAuth(t *testing.T) { ProvisionPlan: []*proto.Response{{ Type: &proto.Response_Plan{ Plan: &proto.PlanComplete{ - GitAuthProviders: []string{"github"}, + ExternalAuthProviders: []string{"github"}, }, }, }}, @@ -368,7 +368,7 @@ func TestTemplateVersionsGitAuth(t *testing.T) { require.False(t, providers[0].Authenticated) // Perform the Git auth callback to authenticate the user... - resp := coderdtest.RequestGitAuthCallback(t, "github", client) + resp := coderdtest.RequestExternalAuthCallback(t, "github", client) _ = resp.Body.Close() require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode) diff --git a/coderd/tracing/httpmw.go b/coderd/tracing/httpmw.go index 653a743862..994c16f41a 100644 --- a/coderd/tracing/httpmw.go +++ b/coderd/tracing/httpmw.go @@ -26,7 +26,7 @@ func Middleware(tracerProvider trace.TracerProvider) func(http.Handler) http.Han "/api/**", "/@*/*/apps/**", "/%40*/*/apps/**", - "/gitauth/*/callback", + "/externalauth/*/callback", }.MustCompile() var tracer trace.Tracer diff --git a/coderd/tracing/httpmw_test.go b/coderd/tracing/httpmw_test.go index e866acd513..ca759513ec 100644 --- a/coderd/tracing/httpmw_test.go +++ b/coderd/tracing/httpmw_test.go @@ -59,7 +59,7 @@ func Test_Middleware(t *testing.T) { {"/%40hi/hi/apps/hi", true}, {"/%40hi/hi/apps/hi/hi", true}, {"/%40hi/hi/apps/hi/hi", true}, - {"/gitauth/hi/callback", true}, + {"/externalauth/hi/callback", true}, // Other routes that should not be collected. {"/index.html", false}, diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index cef3910581..b7b258b4f8 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -34,7 +34,7 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbtime" - "github.com/coder/coder/v2/coderd/gitauth" + "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/coderd/rbac" @@ -2178,25 +2178,25 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request) // new token to be issued! listen := r.URL.Query().Has("listen") - var gitAuthConfig *gitauth.Config + var externalAuthConfig *externalauth.Config for _, gitAuth := range api.ExternalAuthConfigs { matches := gitAuth.Regex.MatchString(gitURL) if !matches { continue } - gitAuthConfig = gitAuth + externalAuthConfig = gitAuth } - if gitAuthConfig == nil { - detail := "No git providers are configured." + if externalAuthConfig == nil { + detail := "No external auth providers are configured." if len(api.ExternalAuthConfigs) > 0 { regexURLs := make([]string, 0, len(api.ExternalAuthConfigs)) - for _, gitAuth := range api.ExternalAuthConfigs { - regexURLs = append(regexURLs, fmt.Sprintf("%s=%q", gitAuth.ID, gitAuth.Regex.String())) + for _, extAuth := range api.ExternalAuthConfigs { + regexURLs = append(regexURLs, fmt.Sprintf("%s=%q", extAuth.ID, extAuth.Regex.String())) } - detail = fmt.Sprintf("The configured git provider have regex filters that do not match the git url. Provider url regexs: %s", strings.Join(regexURLs, ",")) + detail = fmt.Sprintf("The configured external auth provider have regex filters that do not match the git url. Provider url regexs: %s", strings.Join(regexURLs, ",")) } httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ - Message: fmt.Sprintf("No matching git provider found in Coder for the url %q.", gitURL), + Message: fmt.Sprintf("No matching external auth provider found in Coder for the url %q.", gitURL), Detail: detail, }) return @@ -2239,8 +2239,8 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request) return case <-ticker.C: } - gitAuthLink, err := api.Database.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{ - ProviderID: gitAuthConfig.ID, + externalAuthLink, err := api.Database.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{ + ProviderID: externalAuthConfig.ID, UserID: workspace.OwnerID, }) if err != nil { @@ -2248,7 +2248,7 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request) continue } httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Failed to get git auth link.", + Message: "Failed to get external auth link.", Detail: err.Error(), }) return @@ -2258,27 +2258,27 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request) // to expire. // See // https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app. - if gitAuthLink.OAuthExpiry.Before(dbtime.Now()) && !gitAuthLink.OAuthExpiry.IsZero() { + if externalAuthLink.OAuthExpiry.Before(dbtime.Now()) && !externalAuthLink.OAuthExpiry.IsZero() { continue } - valid, _, err := gitAuthConfig.ValidateToken(ctx, gitAuthLink.OAuthAccessToken) + valid, _, err := externalAuthConfig.ValidateToken(ctx, externalAuthLink.OAuthAccessToken) if err != nil { - api.Logger.Warn(ctx, "failed to validate git auth token", + api.Logger.Warn(ctx, "failed to validate external auth token", slog.F("workspace_owner_id", workspace.OwnerID.String()), - slog.F("validate_url", gitAuthConfig.ValidateURL), + slog.F("validate_url", externalAuthConfig.ValidateURL), slog.Error(err), ) } if !valid { continue } - httpapi.Write(ctx, rw, http.StatusOK, formatGitAuthAccessToken(gitAuthConfig.Type, gitAuthLink.OAuthAccessToken)) + httpapi.Write(ctx, rw, http.StatusOK, formatGitAuthAccessToken(externalAuthConfig.Type, externalAuthLink.OAuthAccessToken)) return } } // This is the URL that will redirect the user with a state token. - redirectURL, err := api.AccessURL.Parse(fmt.Sprintf("/gitauth/%s", gitAuthConfig.ID)) + redirectURL, err := api.AccessURL.Parse(fmt.Sprintf("/externalauth/%s", externalAuthConfig.ID)) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Failed to parse access URL.", @@ -2287,14 +2287,14 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request) return } - gitAuthLink, err := api.Database.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{ - ProviderID: gitAuthConfig.ID, + externalAuthLink, err := api.Database.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{ + ProviderID: externalAuthConfig.ID, UserID: workspace.OwnerID, }) if err != nil { if !errors.Is(err, sql.ErrNoRows) { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Failed to get git auth link.", + Message: "Failed to get external auth link.", Detail: err.Error(), }) return @@ -2306,10 +2306,10 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request) return } - gitAuthLink, updated, err := gitAuthConfig.RefreshToken(ctx, api.Database, gitAuthLink) + externalAuthLink, updated, err := externalAuthConfig.RefreshToken(ctx, api.Database, externalAuthLink) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Failed to refresh git auth token.", + Message: "Failed to refresh external auth token.", Detail: err.Error(), }) return @@ -2320,7 +2320,7 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request) }) return } - httpapi.Write(ctx, rw, http.StatusOK, formatGitAuthAccessToken(gitAuthConfig.Type, gitAuthLink.OAuthAccessToken)) + httpapi.Write(ctx, rw, http.StatusOK, formatGitAuthAccessToken(externalAuthConfig.Type, externalAuthLink.OAuthAccessToken)) } // Provider types have different username/password formats. diff --git a/codersdk/gitauth.go b/codersdk/externalauth.go similarity index 51% rename from codersdk/gitauth.go rename to codersdk/externalauth.go index c825977120..5350dc3a6b 100644 --- a/codersdk/gitauth.go +++ b/codersdk/externalauth.go @@ -7,37 +7,37 @@ import ( "net/http" ) -type GitAuth struct { +type ExternalAuth struct { Authenticated bool `json:"authenticated"` Device bool `json:"device"` Type string `json:"type"` // User is the user that authenticated with the provider. - User *GitAuthUser `json:"user"` + User *ExternalAuthUser `json:"user"` // AppInstallable is true if the request for app installs was successful. AppInstallable bool `json:"app_installable"` // AppInstallations are the installations that the user has access to. - AppInstallations []GitAuthAppInstallation `json:"installations"` + AppInstallations []ExternalAuthAppInstallation `json:"installations"` // AppInstallURL is the URL to install the app. AppInstallURL string `json:"app_install_url"` } -type GitAuthAppInstallation struct { - ID int `json:"id"` - Account GitAuthUser `json:"account"` - ConfigureURL string `json:"configure_url"` +type ExternalAuthAppInstallation struct { + ID int `json:"id"` + Account ExternalAuthUser `json:"account"` + ConfigureURL string `json:"configure_url"` } -type GitAuthUser struct { +type ExternalAuthUser struct { Login string `json:"login"` AvatarURL string `json:"avatar_url"` ProfileURL string `json:"profile_url"` Name string `json:"name"` } -// GitAuthDevice is the response from the device authorization endpoint. +// ExternalAuthDevice is the response from the device authorization endpoint. // See: https://tools.ietf.org/html/rfc8628#section-3.2 -type GitAuthDevice struct { +type ExternalAuthDevice struct { DeviceCode string `json:"device_code"` UserCode string `json:"user_code"` VerificationURI string `json:"verification_uri"` @@ -45,26 +45,26 @@ type GitAuthDevice struct { Interval int `json:"interval"` } -type GitAuthDeviceExchange struct { +type ExternalAuthDeviceExchange struct { DeviceCode string `json:"device_code"` } -func (c *Client) GitAuthDeviceByID(ctx context.Context, provider string) (GitAuthDevice, error) { - res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/gitauth/%s/device", provider), nil) +func (c *Client) ExternalAuthDeviceByID(ctx context.Context, provider string) (ExternalAuthDevice, error) { + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/externalauth/%s/device", provider), nil) if err != nil { - return GitAuthDevice{}, err + return ExternalAuthDevice{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return GitAuthDevice{}, ReadBodyAsError(res) + return ExternalAuthDevice{}, ReadBodyAsError(res) } - var gitauth GitAuthDevice - return gitauth, json.NewDecoder(res.Body).Decode(&gitauth) + var extAuth ExternalAuthDevice + return extAuth, json.NewDecoder(res.Body).Decode(&extAuth) } -// ExchangeGitAuth exchanges a device code for a git auth token. -func (c *Client) GitAuthDeviceExchange(ctx context.Context, provider string, req GitAuthDeviceExchange) error { - res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/gitauth/%s/device", provider), req) +// ExchangeGitAuth exchanges a device code for an external auth token. +func (c *Client) ExternalAuthDeviceExchange(ctx context.Context, provider string, req ExternalAuthDeviceExchange) error { + res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/externalauth/%s/device", provider), req) if err != nil { return err } @@ -75,16 +75,16 @@ func (c *Client) GitAuthDeviceExchange(ctx context.Context, provider string, req return nil } -// GitAuthByID returns the git auth for the given provider by ID. -func (c *Client) GitAuthByID(ctx context.Context, provider string) (GitAuth, error) { - res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/gitauth/%s", provider), nil) +// ExternalAuthByID returns the external auth for the given provider by ID. +func (c *Client) ExternalAuthByID(ctx context.Context, provider string) (ExternalAuth, error) { + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/externalauth/%s", provider), nil) if err != nil { - return GitAuth{}, err + return ExternalAuth{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return GitAuth{}, ReadBodyAsError(res) + return ExternalAuth{}, ReadBodyAsError(res) } - var gitauth GitAuth - return gitauth, json.NewDecoder(res.Body).Decode(&gitauth) + var extAuth ExternalAuth + return extAuth, json.NewDecoder(res.Body).Decode(&extAuth) } diff --git a/codersdk/templateversions.go b/codersdk/templateversions.go index 3e3a1363bc..fb1f0b21fe 100644 --- a/codersdk/templateversions.go +++ b/codersdk/templateversions.go @@ -134,7 +134,7 @@ func (c *Client) TemplateVersionRichParameters(ctx context.Context, version uuid // TemplateVersionExternalAuth returns authentication providers for the requested template version. func (c *Client) TemplateVersionExternalAuth(ctx context.Context, version uuid.UUID) ([]TemplateVersionExternalAuth, error) { - res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s/gitauth", version), nil) + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s/externalauth", version), nil) if err != nil { return nil, err } diff --git a/docs/api/git.md b/docs/api/git.md index 0f55fdfba9..bcc8889006 100644 --- a/docs/api/git.md +++ b/docs/api/git.md @@ -1,23 +1,23 @@ # Git -## Get git auth by ID +## Get external auth by ID ### Code samples ```shell # Example request using curl -curl -X GET http://coder-server:8080/api/v2/gitauth/{gitauth} \ +curl -X GET http://coder-server:8080/api/v2/externalauth/{externalauth} \ -H 'Accept: application/json' \ -H 'Coder-Session-Token: API_KEY' ``` -`GET /gitauth/{gitauth}` +`GET /externalauth/{externalauth}` ### Parameters -| Name | In | Type | Required | Description | -| --------- | ---- | -------------- | -------- | --------------- | -| `gitauth` | path | string(string) | true | Git Provider ID | +| Name | In | Type | Required | Description | +| -------------- | ---- | -------------- | -------- | --------------- | +| `externalauth` | path | string(string) | true | Git Provider ID | ### Example responses @@ -53,30 +53,30 @@ curl -X GET http://coder-server:8080/api/v2/gitauth/{gitauth} \ ### Responses -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------- | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.GitAuth](schemas.md#codersdkgitauth) | +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | -------------------------------------------------------- | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.ExternalAuth](schemas.md#codersdkexternalauth) | To perform this operation, you must be authenticated. [Learn more](authentication.md). -## Get git auth device by ID. +## Get external auth device by ID. ### Code samples ```shell # Example request using curl -curl -X GET http://coder-server:8080/api/v2/gitauth/{gitauth}/device \ +curl -X GET http://coder-server:8080/api/v2/externalauth/{externalauth}/device \ -H 'Accept: application/json' \ -H 'Coder-Session-Token: API_KEY' ``` -`GET /gitauth/{gitauth}/device` +`GET /externalauth/{externalauth}/device` ### Parameters -| Name | In | Type | Required | Description | -| --------- | ---- | -------------- | -------- | --------------- | -| `gitauth` | path | string(string) | true | Git Provider ID | +| Name | In | Type | Required | Description | +| -------------- | ---- | -------------- | -------- | --------------- | +| `externalauth` | path | string(string) | true | Git Provider ID | ### Example responses @@ -94,29 +94,29 @@ curl -X GET http://coder-server:8080/api/v2/gitauth/{gitauth}/device \ ### Responses -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------- | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.GitAuthDevice](schemas.md#codersdkgitauthdevice) | +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | -------------------------------------------------------------------- | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.ExternalAuthDevice](schemas.md#codersdkexternalauthdevice) | To perform this operation, you must be authenticated. [Learn more](authentication.md). -## Post git auth device by ID +## Post external auth device by ID ### Code samples ```shell # Example request using curl -curl -X POST http://coder-server:8080/api/v2/gitauth/{gitauth}/device \ +curl -X POST http://coder-server:8080/api/v2/externalauth/{externalauth}/device \ -H 'Coder-Session-Token: API_KEY' ``` -`POST /gitauth/{gitauth}/device` +`POST /externalauth/{externalauth}/device` ### Parameters -| Name | In | Type | Required | Description | -| --------- | ---- | -------------- | -------- | --------------- | -| `gitauth` | path | string(string) | true | Git Provider ID | +| Name | In | Type | Required | Description | +| -------------- | ---- | -------------- | -------- | -------------------- | +| `externalauth` | path | string(string) | true | External Provider ID | ### Responses diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 4e4a1dede6..00b61e6202 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -2752,6 +2752,93 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `template_autostop_requirement` | | `deployment_health_page` | +## codersdk.ExternalAuth + +```json +{ + "app_install_url": "string", + "app_installable": true, + "authenticated": true, + "device": true, + "installations": [ + { + "account": { + "avatar_url": "string", + "login": "string", + "name": "string", + "profile_url": "string" + }, + "configure_url": "string", + "id": 0 + } + ], + "type": "string", + "user": { + "avatar_url": "string", + "login": "string", + "name": "string", + "profile_url": "string" + } +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ----------------- | ------------------------------------------------------------------------------------- | -------- | ------------ | ----------------------------------------------------------------------- | +| `app_install_url` | string | false | | App install URL is the URL to install the app. | +| `app_installable` | boolean | false | | App installable is true if the request for app installs was successful. | +| `authenticated` | boolean | false | | | +| `device` | boolean | false | | | +| `installations` | array of [codersdk.ExternalAuthAppInstallation](#codersdkexternalauthappinstallation) | false | | Installations are the installations that the user has access to. | +| `type` | string | false | | | +| `user` | [codersdk.ExternalAuthUser](#codersdkexternalauthuser) | false | | User is the user that authenticated with the provider. | + +## codersdk.ExternalAuthAppInstallation + +```json +{ + "account": { + "avatar_url": "string", + "login": "string", + "name": "string", + "profile_url": "string" + }, + "configure_url": "string", + "id": 0 +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| --------------- | ------------------------------------------------------ | -------- | ------------ | ----------- | +| `account` | [codersdk.ExternalAuthUser](#codersdkexternalauthuser) | false | | | +| `configure_url` | string | false | | | +| `id` | integer | false | | | + +## codersdk.ExternalAuthDevice + +```json +{ + "device_code": "string", + "expires_in": 0, + "interval": 0, + "user_code": "string", + "verification_uri": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------- | -------- | ------------ | ----------- | +| `device_code` | string | false | | | +| `expires_in` | integer | false | | | +| `interval` | integer | false | | | +| `user_code` | string | false | | | +| `verification_uri` | string | false | | | + ## codersdk.ExternalAuthProvider ```json @@ -2770,6 +2857,26 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `bitbucket` | | `openid-connect` | +## codersdk.ExternalAuthUser + +```json +{ + "avatar_url": "string", + "login": "string", + "name": "string", + "profile_url": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------- | ------ | -------- | ------------ | ----------- | +| `avatar_url` | string | false | | | +| `login` | string | false | | | +| `name` | string | false | | | +| `profile_url` | string | false | | | + ## codersdk.Feature ```json @@ -2838,71 +2945,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `count` | integer | false | | | | `users` | array of [codersdk.User](#codersdkuser) | false | | | -## codersdk.GitAuth - -```json -{ - "app_install_url": "string", - "app_installable": true, - "authenticated": true, - "device": true, - "installations": [ - { - "account": { - "avatar_url": "string", - "login": "string", - "name": "string", - "profile_url": "string" - }, - "configure_url": "string", - "id": 0 - } - ], - "type": "string", - "user": { - "avatar_url": "string", - "login": "string", - "name": "string", - "profile_url": "string" - } -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ----------------- | --------------------------------------------------------------------------- | -------- | ------------ | ----------------------------------------------------------------------- | -| `app_install_url` | string | false | | App install URL is the URL to install the app. | -| `app_installable` | boolean | false | | App installable is true if the request for app installs was successful. | -| `authenticated` | boolean | false | | | -| `device` | boolean | false | | | -| `installations` | array of [codersdk.GitAuthAppInstallation](#codersdkgitauthappinstallation) | false | | Installations are the installations that the user has access to. | -| `type` | string | false | | | -| `user` | [codersdk.GitAuthUser](#codersdkgitauthuser) | false | | User is the user that authenticated with the provider. | - -## codersdk.GitAuthAppInstallation - -```json -{ - "account": { - "avatar_url": "string", - "login": "string", - "name": "string", - "profile_url": "string" - }, - "configure_url": "string", - "id": 0 -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| --------------- | -------------------------------------------- | -------- | ------------ | ----------- | -| `account` | [codersdk.GitAuthUser](#codersdkgitauthuser) | false | | | -| `configure_url` | string | false | | | -| `id` | integer | false | | | - ## codersdk.GitAuthConfig ```json @@ -2941,48 +2983,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `type` | string | false | | | | `validate_url` | string | false | | | -## codersdk.GitAuthDevice - -```json -{ - "device_code": "string", - "expires_in": 0, - "interval": 0, - "user_code": "string", - "verification_uri": "string" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------------ | ------- | -------- | ------------ | ----------- | -| `device_code` | string | false | | | -| `expires_in` | integer | false | | | -| `interval` | integer | false | | | -| `user_code` | string | false | | | -| `verification_uri` | string | false | | | - -## codersdk.GitAuthUser - -```json -{ - "avatar_url": "string", - "login": "string", - "name": "string", - "profile_url": "string" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------- | ------ | -------- | ------------ | ----------- | -| `avatar_url` | string | false | | | -| `login` | string | false | | | -| `name` | string | false | | | -| `profile_url` | string | false | | | - ## codersdk.GitSSHKey ```json diff --git a/docs/api/templates.md b/docs/api/templates.md index f4084144e0..fe9d76633e 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -1800,18 +1800,18 @@ Status Code **200** To perform this operation, you must be authenticated. [Learn more](authentication.md). -## Get git auth by template version +## Get external auth by template version ### Code samples ```shell # Example request using curl -curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/gitauth \ +curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/externalauth \ -H 'Accept: application/json' \ -H 'Coder-Session-Token: API_KEY' ``` -`GET /templateversions/{templateversion}/gitauth` +`GET /templateversions/{templateversion}/externalauth` ### Parameters @@ -1840,7 +1840,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/g | ------ | ------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------- | | 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [codersdk.TemplateVersionExternalAuth](schemas.md#codersdktemplateversionexternalauth) | -