chore: move `/gitauth` to `/externalauth` on the frontend (#9954)

* chore: move `/gitauth` to `/externalauth` on the frontend

This actually took a lot more jank than anticipated,
so I wanted to split this up before adding the ability
to embed new providers.

* Rename FE

* Fix em' up

* Fix linting error

* Fix e2e tests

* chore: update helm golden files
This commit is contained in:
Kyle Carberry 2023-09-30 14:30:01 -05:00 committed by GitHub
parent 16a2d4d733
commit 5596fb20b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1231 additions and 1211 deletions

View File

@ -39,6 +39,7 @@
"enterprisemeta",
"errgroup",
"eventsourcemock",
"externalauth",
"Failf",
"fatih",
"Formik",

View File

@ -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

View File

@ -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?")

View File

@ -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,

376
coderd/apidoc/docs.go generated
View File

@ -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": {

View File

@ -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": {

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)
}

View File

@ -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,

View File

@ -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,

View File

@ -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 {

View File

@ -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)

View File

@ -1,9 +0,0 @@
package gitauth_test
import (
"testing"
)
func TestOAuthJWTConfig(t *testing.T) {
t.Parallel()
}

View File

@ -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)
})
}
}

View File

@ -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)
})

View File

@ -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)
})
}
}

View File

@ -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",

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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},

View File

@ -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.

View File

@ -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)
}

View File

@ -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
}

48
docs/api/git.md generated
View File

@ -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

214
docs/api/schemas.md generated
View File

@ -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

8
docs/api/templates.md generated
View File

@ -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) |
<h3 id="get-git-auth-by-template-version-responseschema">Response Schema</h3>
<h3 id="get-external-auth-by-template-version-responseschema">Response Schema</h3>
Status Code **200**

View File

@ -225,10 +225,10 @@ func Tar(responses *Responses) ([]byte, error) {
}
responses.ProvisionPlan = append(responses.ProvisionPlan, &proto.Response{
Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
Error: resp.GetApply().GetError(),
Resources: resp.GetApply().GetResources(),
Parameters: resp.GetApply().GetParameters(),
GitAuthProviders: resp.GetApply().GetGitAuthProviders(),
Error: resp.GetApply().GetError(),
Resources: resp.GetApply().GetResources(),
Parameters: resp.GetApply().GetParameters(),
ExternalAuthProviders: resp.GetApply().GetExternalAuthProviders(),
}},
})
}

View File

@ -264,9 +264,9 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l
return nil, err
}
return &proto.PlanComplete{
Parameters: state.Parameters,
Resources: state.Resources,
GitAuthProviders: state.GitAuthProviders,
Parameters: state.Parameters,
Resources: state.Resources,
ExternalAuthProviders: state.ExternalAuthProviders,
}, nil
}
@ -404,10 +404,10 @@ func (e *executor) apply(
return nil, xerrors.Errorf("read statefile %q: %w", statefilePath, err)
}
return &proto.ApplyComplete{
Parameters: state.Parameters,
Resources: state.Resources,
GitAuthProviders: state.GitAuthProviders,
State: stateContent,
Parameters: state.Parameters,
Resources: state.Resources,
ExternalAuthProviders: state.ExternalAuthProviders,
State: stateContent,
}, nil
}

View File

@ -114,9 +114,9 @@ type resourceMetadataItem struct {
}
type State struct {
Resources []*proto.Resource
Parameters []*proto.RichParameter
GitAuthProviders []string
Resources []*proto.Resource
Parameters []*proto.RichParameter
ExternalAuthProviders []string
}
// ConvertState consumes Terraform state and a GraphViz representation
@ -680,9 +680,9 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error
}
return &State{
Resources: resources,
Parameters: parameters,
GitAuthProviders: gitAuthProviders,
Resources: resources,
Parameters: parameters,
ExternalAuthProviders: gitAuthProviders,
}, nil
}

View File

@ -546,7 +546,7 @@ func TestConvertResources(t *testing.T) {
state, err := terraform.ConvertState(modules, string(tfPlanGraph))
require.NoError(t, err)
sortResources(state.Resources)
sort.Strings(state.GitAuthProviders)
sort.Strings(state.ExternalAuthProviders)
expectedNoMetadata := make([]*proto.Resource, 0)
for _, resource := range expected.resources {
@ -584,7 +584,7 @@ func TestConvertResources(t *testing.T) {
require.Equal(t, string(parametersWant), string(parametersGot))
require.Equal(t, expectedNoMetadataMap, resourcesMap)
require.ElementsMatch(t, expected.gitAuthProviders, state.GitAuthProviders)
require.ElementsMatch(t, expected.gitAuthProviders, state.ExternalAuthProviders)
})
t.Run("Provision", func(t *testing.T) {
@ -600,7 +600,7 @@ func TestConvertResources(t *testing.T) {
state, err := terraform.ConvertState([]*tfjson.StateModule{tfState.Values.RootModule}, string(tfStateGraph))
require.NoError(t, err)
sortResources(state.Resources)
sort.Strings(state.GitAuthProviders)
sort.Strings(state.ExternalAuthProviders)
for _, resource := range state.Resources {
for _, agent := range resource.Agents {
agent.Id = ""
@ -627,7 +627,7 @@ func TestConvertResources(t *testing.T) {
require.NoError(t, err)
require.Equal(t, expectedMap, resourcesMap)
require.ElementsMatch(t, expected.gitAuthProviders, state.GitAuthProviders)
require.ElementsMatch(t, expected.gitAuthProviders, state.ExternalAuthProviders)
})
})
}

View File

@ -720,7 +720,7 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(
return &templateImportProvision{
Resources: c.Resources,
Parameters: c.Parameters,
ExternalAuthProviders: c.GitAuthProviders,
ExternalAuthProviders: c.ExternalAuthProviders,
}, nil
default:
return nil, xerrors.Errorf("invalid message type %q received from provisioner",

View File

@ -1867,10 +1867,10 @@ type PlanComplete struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
Resources []*Resource `protobuf:"bytes,2,rep,name=resources,proto3" json:"resources,omitempty"`
Parameters []*RichParameter `protobuf:"bytes,3,rep,name=parameters,proto3" json:"parameters,omitempty"`
GitAuthProviders []string `protobuf:"bytes,4,rep,name=git_auth_providers,json=gitAuthProviders,proto3" json:"git_auth_providers,omitempty"`
Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
Resources []*Resource `protobuf:"bytes,2,rep,name=resources,proto3" json:"resources,omitempty"`
Parameters []*RichParameter `protobuf:"bytes,3,rep,name=parameters,proto3" json:"parameters,omitempty"`
ExternalAuthProviders []string `protobuf:"bytes,4,rep,name=external_auth_providers,json=externalAuthProviders,proto3" json:"external_auth_providers,omitempty"`
}
func (x *PlanComplete) Reset() {
@ -1926,9 +1926,9 @@ func (x *PlanComplete) GetParameters() []*RichParameter {
return nil
}
func (x *PlanComplete) GetGitAuthProviders() []string {
func (x *PlanComplete) GetExternalAuthProviders() []string {
if x != nil {
return x.GitAuthProviders
return x.ExternalAuthProviders
}
return nil
}
@ -1988,11 +1988,11 @@ type ApplyComplete struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
State []byte `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
Resources []*Resource `protobuf:"bytes,3,rep,name=resources,proto3" json:"resources,omitempty"`
Parameters []*RichParameter `protobuf:"bytes,4,rep,name=parameters,proto3" json:"parameters,omitempty"`
GitAuthProviders []string `protobuf:"bytes,5,rep,name=git_auth_providers,json=gitAuthProviders,proto3" json:"git_auth_providers,omitempty"`
State []byte `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
Resources []*Resource `protobuf:"bytes,3,rep,name=resources,proto3" json:"resources,omitempty"`
Parameters []*RichParameter `protobuf:"bytes,4,rep,name=parameters,proto3" json:"parameters,omitempty"`
ExternalAuthProviders []string `protobuf:"bytes,5,rep,name=external_auth_providers,json=externalAuthProviders,proto3" json:"external_auth_providers,omitempty"`
}
func (x *ApplyComplete) Reset() {
@ -2055,9 +2055,9 @@ func (x *ApplyComplete) GetParameters() []*RichParameter {
return nil
}
func (x *ApplyComplete) GetGitAuthProviders() []string {
func (x *ApplyComplete) GetExternalAuthProviders() []string {
if x != nil {
return x.GitAuthProviders
return x.ExternalAuthProviders
}
return nil
}
@ -2778,7 +2778,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{
0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45,
0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69,
0x64, 0x65, 0x72, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74,
0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0xc3, 0x01, 0x0a, 0x0c, 0x50,
0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0xcd, 0x01, 0x0a, 0x0c, 0x50,
0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65,
0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02,
@ -2788,78 +2788,80 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{
0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72,
0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
0x72, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10,
0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73,
0x22, 0x41, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0x22, 0xda, 0x01, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d,
0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65,
0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65,
0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72,
0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
0x72, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10,
0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73,
0x22, 0x0f, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x22, 0x8c, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a,
0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e,
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x0a, 0x05,
0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72,
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12,
0x2e, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e,
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12,
0x31, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19,
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70,
0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70,
0x6c, 0x79, 0x12, 0x34, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00,
0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
0x22, 0xd1, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a,
0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03,
0x6c, 0x6f, 0x67, 0x12, 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00,
0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18,
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65,
0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c,
0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c,
0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x42, 0x06, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c,
0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44,
0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02,
0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52,
0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72,
0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45,
0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43,
0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43,
0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54,
0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41,
0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b,
0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x32, 0x49, 0x0a, 0x0b, 0x50,
0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x07, 0x53, 0x65,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x70, 0x72,
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72,
0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73,
0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x72, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61,
0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20,
0x03, 0x28, 0x09, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74,
0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0x41, 0x0a, 0x0c, 0x41, 0x70,
0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xe4, 0x01,
0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12,
0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05,
0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15,
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72,
0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x36, 0x0a, 0x17,
0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72,
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15, 0x65,
0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69,
0x64, 0x65, 0x72, 0x73, 0x22, 0x0f, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x8c, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x2d, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x12, 0x31, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61,
0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61,
0x72, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70,
0x6c, 0x61, 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52,
0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x34, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c,
0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x22, 0xd1, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10,
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67,
0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65,
0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70,
0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70,
0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05,
0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72,
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43,
0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79,
0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c,
0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12,
0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e,
0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09,
0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70,
0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05,
0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45,
0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55,
0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70,
0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a,
0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50,
0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x32,
0x49, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x3a,
0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x76,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63,
0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@ -245,7 +245,7 @@ message PlanComplete {
string error = 1;
repeated Resource resources = 2;
repeated RichParameter parameters = 3;
repeated string git_auth_providers = 4;
repeated string external_auth_providers = 4;
}
// ApplyRequest asks the provisioner to apply the changes. Apply MUST be preceded by a successful plan request/response
@ -260,7 +260,7 @@ message ApplyComplete {
string error = 2;
repeated Resource resources = 3;
repeated RichParameter parameters = 4;
repeated string git_auth_providers = 5;
repeated string external_auth_providers = 5;
}
// CancelRequest requests that the previous request be canceled gracefully.

View File

@ -426,7 +426,7 @@ const createTemplateVersionTar = async (
error: response.apply?.error ?? "",
resources: response.apply?.resources ?? [],
parameters: response.apply?.parameters ?? [],
gitAuthProviders: response.apply?.gitAuthProviders ?? [],
externalAuthProviders: response.apply?.externalAuthProviders ?? [],
},
};
});
@ -508,7 +508,7 @@ const createTemplateVersionTar = async (
state: new Uint8Array(),
resources: [],
parameters: [],
gitAuthProviders: [],
externalAuthProviders: [],
...response.apply,
} as ApplyComplete;
response.apply.resources = response.apply.resources?.map(fillResource);
@ -523,7 +523,7 @@ const createTemplateVersionTar = async (
error: "",
resources: [],
parameters: [],
gitAuthProviders: [],
externalAuthProviders: [],
...response.plan,
} as PlanComplete;
response.plan.resources = response.plan.resources?.map(fillResource);

View File

@ -249,7 +249,7 @@ export interface PlanComplete {
error: string;
resources: Resource[];
parameters: RichParameter[];
gitAuthProviders: string[];
externalAuthProviders: string[];
}
/**
@ -266,7 +266,7 @@ export interface ApplyComplete {
error: string;
resources: Resource[];
parameters: RichParameter[];
gitAuthProviders: string[];
externalAuthProviders: string[];
}
/** CancelRequest requests that the previous request be canceled gracefully. */
@ -859,7 +859,7 @@ export const PlanComplete = {
for (const v of message.parameters) {
RichParameter.encode(v!, writer.uint32(26).fork()).ldelim();
}
for (const v of message.gitAuthProviders) {
for (const v of message.externalAuthProviders) {
writer.uint32(34).string(v!);
}
return writer;
@ -895,7 +895,7 @@ export const ApplyComplete = {
for (const v of message.parameters) {
RichParameter.encode(v!, writer.uint32(34).fork()).ldelim();
}
for (const v of message.gitAuthProviders) {
for (const v of message.externalAuthProviders) {
writer.uint32(42).string(v!);
}
return writer;

View File

@ -1,15 +1,15 @@
import { test } from "@playwright/test";
import { gitAuth } from "../constants";
import { Endpoints } from "@octokit/types";
import { GitAuthDevice } from "api/typesGenerated";
import { ExternalAuthDevice } from "api/typesGenerated";
import { Awaiter, createServer } from "../helpers";
import { beforeCoderTest } from "../hooks";
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
// Ensures that a Git auth provider with the device flow functions and completes!
test("git auth device", async ({ page }) => {
const device: GitAuthDevice = {
test("external auth device", async ({ page }) => {
const device: ExternalAuthDevice = {
device_code: "1234",
user_code: "1234-5678",
expires_in: 900,
@ -46,7 +46,7 @@ test("git auth device", async ({ page }) => {
sentPending.done();
});
await page.goto(`/gitauth/${gitAuth.deviceProvider}`, {
await page.goto(`/externalauth/${gitAuth.deviceProvider}`, {
waitUntil: "domcontentloaded",
});
await page.getByText(device.user_code).isVisible();
@ -57,7 +57,7 @@ test("git auth device", async ({ page }) => {
await page.waitForSelector("text=1 organization authorized");
});
test("git auth web", async ({ baseURL, page }) => {
test("external auth web", async ({ baseURL, page }) => {
const srv = await createServer(gitAuth.webPort);
// The GitHub validate endpoint returns the currently authenticated user!
srv.use(gitAuth.validatePath, (req, res) => {
@ -70,11 +70,11 @@ test("git auth web", async ({ baseURL, page }) => {
});
srv.use(gitAuth.authPath, (req, res) => {
res.redirect(
`${baseURL}/gitauth/${gitAuth.webProvider}/callback?code=1234&state=` +
`${baseURL}/externalauth/${gitAuth.webProvider}/callback?code=1234&state=` +
req.query.state,
);
});
await page.goto(`/gitauth/${gitAuth.webProvider}`, {
await page.goto(`/externalauth/${gitAuth.webProvider}`, {
waitUntil: "domcontentloaded",
});
// This endpoint doesn't have the installations URL set intentionally!

View File

@ -122,7 +122,9 @@ const NetworkSettingsPage = lazy(
"./pages/DeploySettingsPage/NetworkSettingsPage/NetworkSettingsPage"
),
);
const GitAuthPage = lazy(() => import("./pages/GitAuthPage/GitAuthPage"));
const ExternalAuthPage = lazy(
() => import("./pages/ExternalAuthPage/ExternalAuthPage"),
);
const TemplateVersionPage = lazy(
() => import("./pages/TemplateVersionPage/TemplateVersionPage"),
);
@ -207,7 +209,10 @@ export const AppRouter: FC = () => {
<Route path="health" element={<HealthPage />} />
<Route path="gitauth/:provider" element={<GitAuthPage />} />
<Route
path="externalauth/:provider"
element={<ExternalAuthPage />}
/>
<Route path="workspaces" element={<WorkspacesPage />} />

View File

@ -331,11 +331,11 @@ export const createTemplateVersion = async (
return response.data;
};
export const getTemplateVersionGitAuth = async (
export const getTemplateVersionExternalAuth = async (
versionId: string,
): Promise<TypesGen.TemplateVersionExternalAuth[]> => {
const response = await axios.get(
`/api/v2/templateversions/${versionId}/gitauth`,
`/api/v2/templateversions/${versionId}/externalauth`,
);
return response.data;
};
@ -855,25 +855,25 @@ export const getExperiments = async (): Promise<TypesGen.Experiment[]> => {
}
};
export const getGitAuthProvider = async (
export const getExternalAuthProvider = async (
provider: string,
): Promise<TypesGen.GitAuth> => {
const resp = await axios.get(`/api/v2/gitauth/${provider}`);
): Promise<TypesGen.ExternalAuth> => {
const resp = await axios.get(`/api/v2/externalauth/${provider}`);
return resp.data;
};
export const getGitAuthDevice = async (
export const getExternalAuthDevice = async (
provider: string,
): Promise<TypesGen.GitAuthDevice> => {
const resp = await axios.get(`/api/v2/gitauth/${provider}/device`);
): Promise<TypesGen.ExternalAuthDevice> => {
const resp = await axios.get(`/api/v2/externalauth/${provider}/device`);
return resp.data;
};
export const exchangeGitAuthDevice = async (
export const exchangeExternalAuthDevice = async (
provider: string,
req: TypesGen.GitAuthDeviceExchange,
req: TypesGen.ExternalAuthDeviceExchange,
): Promise<void> => {
const resp = await axios.post(`/api/v2/gitauth/${provider}/device`, req);
const resp = await axios.post(`/api/v2/externalauth/${provider}/device`, req);
return resp.data;
};

View File

@ -121,15 +121,15 @@ export const updateActiveTemplateVersion = (
};
};
export const templateVersionGitAuthKey = (versionId: string) => [
export const templateVersionExternalAuthKey = (versionId: string) => [
"templateVersion",
versionId,
"gitAuth",
"externalAuth",
];
export const templateVersionGitAuth = (versionId: string) => {
export const templateVersionExternalAuth = (versionId: string) => {
return {
queryKey: templateVersionGitAuthKey(versionId),
queryFn: () => API.getTemplateVersionGitAuth(versionId),
queryKey: templateVersionExternalAuthKey(versionId),
queryFn: () => API.getTemplateVersionExternalAuth(versionId),
};
};

View File

@ -422,6 +422,46 @@ export interface Entitlements {
// From codersdk/deployment.go
export type Experiments = Experiment[];
// From codersdk/externalauth.go
export interface ExternalAuth {
readonly authenticated: boolean;
readonly device: boolean;
readonly type: string;
readonly user?: ExternalAuthUser;
readonly app_installable: boolean;
readonly installations: ExternalAuthAppInstallation[];
readonly app_install_url: string;
}
// From codersdk/externalauth.go
export interface ExternalAuthAppInstallation {
readonly id: number;
readonly account: ExternalAuthUser;
readonly configure_url: string;
}
// From codersdk/externalauth.go
export interface ExternalAuthDevice {
readonly device_code: string;
readonly user_code: string;
readonly verification_uri: string;
readonly expires_in: number;
readonly interval: number;
}
// From codersdk/externalauth.go
export interface ExternalAuthDeviceExchange {
readonly device_code: string;
}
// From codersdk/externalauth.go
export interface ExternalAuthUser {
readonly login: string;
readonly avatar_url: string;
readonly profile_url: string;
readonly name: string;
}
// From codersdk/deployment.go
export interface Feature {
readonly entitlement: Entitlement;
@ -441,24 +481,6 @@ export interface GetUsersResponse {
readonly count: number;
}
// From codersdk/gitauth.go
export interface GitAuth {
readonly authenticated: boolean;
readonly device: boolean;
readonly type: string;
readonly user?: GitAuthUser;
readonly app_installable: boolean;
readonly installations: GitAuthAppInstallation[];
readonly app_install_url: string;
}
// From codersdk/gitauth.go
export interface GitAuthAppInstallation {
readonly id: number;
readonly account: GitAuthUser;
readonly configure_url: string;
}
// From codersdk/deployment.go
export interface GitAuthConfig {
readonly id: string;
@ -476,28 +498,6 @@ export interface GitAuthConfig {
readonly device_code_url: string;
}
// From codersdk/gitauth.go
export interface GitAuthDevice {
readonly device_code: string;
readonly user_code: string;
readonly verification_uri: string;
readonly expires_in: number;
readonly interval: number;
}
// From codersdk/gitauth.go
export interface GitAuthDeviceExchange {
readonly device_code: string;
}
// From codersdk/gitauth.go
export interface GitAuthUser {
readonly login: string;
readonly avatar_url: string;
readonly profile_url: string;
readonly name: string;
}
// From codersdk/gitsshkey.go
export interface GitSSHKey {
readonly user_id: string;

View File

@ -156,7 +156,7 @@ describe("CreateWorkspacePage", () => {
expect(validationError).toBeInTheDocument();
});
it("gitauth authenticates and succeeds", async () => {
it("external auth authenticates and succeeds", async () => {
jest
.spyOn(API, "getWorkspaceQuota")
.mockResolvedValueOnce(MockWorkspaceQuota);
@ -165,7 +165,7 @@ describe("CreateWorkspacePage", () => {
.mockResolvedValueOnce({ users: [MockUser], count: 1 });
jest.spyOn(API, "createWorkspace").mockResolvedValueOnce(MockWorkspace);
jest
.spyOn(API, "getTemplateVersionGitAuth")
.spyOn(API, "getTemplateVersionExternalAuth")
.mockResolvedValue([MockTemplateVersionExternalAuthGithub]);
renderCreateWorkspacePage();
@ -181,7 +181,7 @@ describe("CreateWorkspacePage", () => {
await userEvent.click(githubButton);
jest
.spyOn(API, "getTemplateVersionGitAuth")
.spyOn(API, "getTemplateVersionExternalAuth")
.mockResolvedValue([MockTemplateVersionExternalAuthGithubAuthenticated]);
await screen.findByText("Authenticated with GitHub");
@ -200,9 +200,9 @@ describe("CreateWorkspacePage", () => {
);
});
it("gitauth: errors if unauthenticated and submits", async () => {
it("external auth: errors if unauthenticated and submits", async () => {
jest
.spyOn(API, "getTemplateVersionGitAuth")
.spyOn(API, "getTemplateVersionExternalAuth")
.mockResolvedValueOnce([MockTemplateVersionExternalAuthGithub]);
renderCreateWorkspacePage();

View File

@ -24,9 +24,9 @@ import {
NumberDictionary,
} from "unique-names-generator";
import { useQuery } from "@tanstack/react-query";
import { templateVersionGitAuth } from "api/queries/templates";
import { templateVersionExternalAuth } from "api/queries/templates";
export type GitAuthPollingState = "idle" | "polling" | "abandoned";
export type ExternalAuthPollingState = "idle" | "polling" | "abandoned";
const CreateWorkspacePage: FC = () => {
const organizationId = useOrganizationId();
@ -58,43 +58,44 @@ const CreateWorkspacePage: FC = () => {
? "Creating workspace..."
: "Create workspace";
const [gitAuthPollingState, setGitAuthPollingState] =
useState<GitAuthPollingState>("idle");
const [externalAuthPollingState, setExternalAuthPollingState] =
useState<ExternalAuthPollingState>("idle");
const startPollingGitAuth = useCallback(() => {
setGitAuthPollingState("polling");
const startPollingExternalAuth = useCallback(() => {
setExternalAuthPollingState("polling");
}, []);
const { data: gitAuth, error } = useQuery(
const { data: externalAuth, error } = useQuery(
versionId
? {
...templateVersionGitAuth(versionId),
refetchInterval: gitAuthPollingState === "polling" ? 1000 : false,
...templateVersionExternalAuth(versionId),
refetchInterval:
externalAuthPollingState === "polling" ? 1000 : false,
}
: { enabled: false },
);
const allSignedIn = gitAuth?.every((it) => it.authenticated);
const allSignedIn = externalAuth?.every((it) => it.authenticated);
useEffect(() => {
if (allSignedIn) {
setGitAuthPollingState("idle");
setExternalAuthPollingState("idle");
return;
}
if (gitAuthPollingState !== "polling") {
if (externalAuthPollingState !== "polling") {
return;
}
// Poll for a maximum of one minute
const quitPolling = setTimeout(
() => setGitAuthPollingState("abandoned"),
() => setExternalAuthPollingState("abandoned"),
60_000,
);
return () => {
clearTimeout(quitPolling);
};
}, [gitAuthPollingState, allSignedIn]);
}, [externalAuthPollingState, allSignedIn]);
return (
<>
@ -116,9 +117,9 @@ const CreateWorkspacePage: FC = () => {
error={error}
template={template!}
versionId={versionId}
gitAuth={gitAuth ?? []}
gitAuthPollingState={gitAuthPollingState}
startPollingGitAuth={startPollingGitAuth}
externalAuth={externalAuth ?? []}
externalAuthPollingState={externalAuthPollingState}
startPollingExternalAuth={startPollingExternalAuth}
permissions={permissions as CreateWSPermissions}
parameters={parameters!}
creatingWorkspace={createWorkspaceState.matches("creatingWorkspace")}

View File

@ -18,7 +18,7 @@ const meta: Meta<typeof CreateWorkspacePageView> = {
defaultBuildParameters: [],
template: MockTemplate,
parameters: [],
gitAuth: [],
externalAuth: [],
permissions: {
createWorkspaceForUser: true,
},
@ -86,9 +86,9 @@ export const Parameters: Story = {
},
};
export const GitAuth: Story = {
export const ExternalAuth: Story = {
args: {
gitAuth: [
externalAuth: [
{
id: "github",
type: "github",

View File

@ -27,10 +27,10 @@ import {
MutableTemplateParametersSection,
} from "components/TemplateParameters/TemplateParameters";
import { CreateWSPermissions } from "xServices/createWorkspace/createWorkspaceXService";
import { GitAuth } from "./GitAuth";
import { ExternalAuth } from "./ExternalAuth";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Stack } from "components/Stack/Stack";
import { type GitAuthPollingState } from "./CreateWorkspacePage";
import { type ExternalAuthPollingState } from "./CreateWorkspacePage";
export interface CreateWorkspacePageViewProps {
error: unknown;
@ -38,9 +38,9 @@ export interface CreateWorkspacePageViewProps {
defaultOwner: TypesGen.User;
template: TypesGen.Template;
versionId?: string;
gitAuth: TypesGen.TemplateVersionExternalAuth[];
gitAuthPollingState: GitAuthPollingState;
startPollingGitAuth: () => void;
externalAuth: TypesGen.TemplateVersionExternalAuth[];
externalAuthPollingState: ExternalAuthPollingState;
startPollingExternalAuth: () => void;
parameters: TypesGen.TemplateVersionParameter[];
defaultBuildParameters: TypesGen.WorkspaceBuildParameter[];
permissions: CreateWSPermissions;
@ -58,9 +58,9 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
defaultOwner,
template,
versionId,
gitAuth,
gitAuthPollingState,
startPollingGitAuth,
externalAuth,
externalAuthPollingState,
startPollingExternalAuth,
parameters,
defaultBuildParameters,
permissions,
@ -70,7 +70,8 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
}) => {
const styles = useStyles();
const [owner, setOwner] = useState(defaultOwner);
const { verifyGitAuth, gitAuthErrors } = useGitAuthVerification(gitAuth);
const { verifyExternalAuth, externalAuthErrors } =
useExternalAuthVerification(externalAuth);
const form: FormikContextType<TypesGen.CreateWorkspaceRequest> =
useFormik<TypesGen.CreateWorkspaceRequest>({
initialValues: {
@ -87,7 +88,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
}),
enableReinitialize: true,
onSubmit: (request) => {
if (!verifyGitAuth()) {
if (!verifyExternalAuth()) {
form.setSubmitting(false);
return;
}
@ -160,21 +161,21 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
</FormSection>
)}
{gitAuth && gitAuth.length > 0 && (
{externalAuth && externalAuth.length > 0 && (
<FormSection
title="Git Authentication"
description="This template requires authentication to automatically perform Git operations on create."
>
<FormFields>
{gitAuth.map((auth) => (
<GitAuth
{externalAuth.map((auth) => (
<ExternalAuth
key={auth.id}
authenticateURL={auth.authenticate_url}
authenticated={auth.authenticated}
gitAuthPollingState={gitAuthPollingState}
startPollingGitAuth={startPollingGitAuth}
externalAuthPollingState={externalAuthPollingState}
startPollingExternalAuth={startPollingExternalAuth}
type={auth.type}
error={gitAuthErrors[auth.id]}
error={externalAuthErrors[auth.id]}
/>
))}
</FormFields>
@ -231,23 +232,24 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
);
};
type GitAuthErrors = Record<string, string>;
type ExternalAuthErrors = Record<string, string>;
const useGitAuthVerification = (
gitAuth: TypesGen.TemplateVersionExternalAuth[],
const useExternalAuthVerification = (
externalAuth: TypesGen.TemplateVersionExternalAuth[],
) => {
const [gitAuthErrors, setGitAuthErrors] = useState<GitAuthErrors>({});
const [externalAuthErrors, setExternalAuthErrors] =
useState<ExternalAuthErrors>({});
// Clear errors when gitAuth is refreshed
// Clear errors when externalAuth is refreshed
useEffect(() => {
setGitAuthErrors({});
}, [gitAuth]);
setExternalAuthErrors({});
}, [externalAuth]);
const verifyGitAuth = () => {
const errors: GitAuthErrors = {};
const verifyExternalAuth = () => {
const errors: ExternalAuthErrors = {};
for (let i = 0; i < gitAuth.length; i++) {
const auth = gitAuth.at(i);
for (let i = 0; i < externalAuth.length; i++) {
const auth = externalAuth.at(i);
if (!auth) {
continue;
}
@ -256,14 +258,14 @@ const useGitAuthVerification = (
}
}
setGitAuthErrors(errors);
setExternalAuthErrors(errors);
const isValid = Object.keys(errors).length === 0;
return isValid;
};
return {
gitAuthErrors,
verifyGitAuth,
externalAuthErrors,
verifyExternalAuth,
};
};

View File

@ -1,13 +1,13 @@
import { GitAuth } from "./GitAuth";
import { ExternalAuth } from "./ExternalAuth";
import type { Meta, StoryObj } from "@storybook/react";
const meta: Meta<typeof GitAuth> = {
title: "components/GitAuth",
component: GitAuth,
const meta: Meta<typeof ExternalAuth> = {
title: "components/ExternalAuth",
component: ExternalAuth,
};
export default meta;
type Story = StoryObj<typeof GitAuth>;
type Story = StoryObj<typeof ExternalAuth>;
export const GithubNotAuthenticated: Story = {
args: {

View File

@ -9,27 +9,27 @@ import { BitbucketIcon } from "components/Icons/BitbucketIcon";
import { GitlabIcon } from "components/Icons/GitlabIcon";
import { FC } from "react";
import { makeStyles } from "@mui/styles";
import { type GitAuthPollingState } from "./CreateWorkspacePage";
import { type ExternalAuthPollingState } from "./CreateWorkspacePage";
import { Stack } from "components/Stack/Stack";
import ReplayIcon from "@mui/icons-material/Replay";
import { LoadingButton } from "components/LoadingButton/LoadingButton";
export interface GitAuthProps {
export interface ExternalAuthProps {
type: TypesGen.ExternalAuthProvider;
authenticated: boolean;
authenticateURL: string;
gitAuthPollingState: GitAuthPollingState;
startPollingGitAuth: () => void;
externalAuthPollingState: ExternalAuthPollingState;
startPollingExternalAuth: () => void;
error?: string;
}
export const GitAuth: FC<GitAuthProps> = (props) => {
export const ExternalAuth: FC<ExternalAuthProps> = (props) => {
const {
type,
authenticated,
authenticateURL,
gitAuthPollingState,
startPollingGitAuth,
externalAuthPollingState,
startPollingExternalAuth,
error,
} = props;
@ -66,7 +66,7 @@ export const GitAuth: FC<GitAuthProps> = (props) => {
>
<Stack alignItems="center" spacing={1}>
<LoadingButton
loading={gitAuthPollingState === "polling"}
loading={externalAuthPollingState === "polling"}
href={authenticateURL}
variant="contained"
size="large"
@ -82,7 +82,7 @@ export const GitAuth: FC<GitAuthProps> = (props) => {
return;
}
window.open(authenticateURL, "_blank", "width=900,height=600");
startPollingGitAuth();
startPollingExternalAuth();
}}
>
{authenticated
@ -90,8 +90,8 @@ export const GitAuth: FC<GitAuthProps> = (props) => {
: `Login with ${prettyName}`}
</LoadingButton>
{gitAuthPollingState === "abandoned" && (
<Button variant="text" onClick={startPollingGitAuth}>
{externalAuthPollingState === "abandoned" && (
<Button variant="text" onClick={startPollingExternalAuth}>
<ReplayIcon /> Check again
</Button>
)}

View File

@ -0,0 +1,96 @@
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
exchangeExternalAuthDevice,
getExternalAuthDevice,
getExternalAuthProvider,
} from "api/api";
import { usePermissions } from "hooks";
import { type FC } from "react";
import { useParams } from "react-router-dom";
import ExternalAuthPageView from "./ExternalAuthPageView";
import { ApiErrorResponse } from "api/errors";
import { isAxiosError } from "axios";
const ExternalAuthPage: FC = () => {
const { provider } = useParams();
if (!provider) {
throw new Error("provider must exist");
}
const permissions = usePermissions();
const queryClient = useQueryClient();
const getExternalAuthProviderQuery = useQuery({
queryKey: ["externalauth", provider],
queryFn: () => getExternalAuthProvider(provider),
refetchOnWindowFocus: true,
});
const getExternalAuthDeviceQuery = useQuery({
enabled:
Boolean(!getExternalAuthProviderQuery.data?.authenticated) &&
Boolean(getExternalAuthProviderQuery.data?.device),
queryFn: () => getExternalAuthDevice(provider),
queryKey: ["externalauth", provider, "device"],
refetchOnMount: false,
});
const exchangeExternalAuthDeviceQuery = useQuery({
queryFn: () =>
exchangeExternalAuthDevice(provider, {
device_code: getExternalAuthDeviceQuery.data?.device_code || "",
}),
queryKey: [
"externalauth",
provider,
getExternalAuthDeviceQuery.data?.device_code,
],
enabled: Boolean(getExternalAuthDeviceQuery.data),
onSuccess: () => {
// Force a refresh of the Git auth status.
queryClient.invalidateQueries(["externalauth", provider]).catch((ex) => {
console.error("invalidate queries", ex);
});
},
retry: true,
retryDelay: (getExternalAuthDeviceQuery.data?.interval || 5) * 1000,
refetchOnWindowFocus: (query) =>
query.state.status === "success" ? false : "always",
});
if (
getExternalAuthProviderQuery.isLoading ||
!getExternalAuthProviderQuery.data
) {
return null;
}
let deviceExchangeError: ApiErrorResponse | undefined;
if (isAxiosError(exchangeExternalAuthDeviceQuery.failureReason)) {
deviceExchangeError =
exchangeExternalAuthDeviceQuery.failureReason.response?.data;
}
if (
!getExternalAuthProviderQuery.data.authenticated &&
!getExternalAuthProviderQuery.data.device
) {
window.location.href = `/externalauth/${provider}/callback`;
return null;
}
return (
<ExternalAuthPageView
externalAuth={getExternalAuthProviderQuery.data}
onReauthenticate={() => {
queryClient.setQueryData(["externalauth", provider], {
...getExternalAuthProviderQuery.data,
authenticated: false,
});
}}
viewExternalAuthConfig={permissions.viewExternalAuthConfig}
deviceExchangeError={deviceExchangeError}
externalAuthDevice={getExternalAuthDeviceQuery.data}
/>
);
};
export default ExternalAuthPage;

View File

@ -1,18 +1,20 @@
import { Meta, StoryFn } from "@storybook/react";
import GitAuthPageView, { GitAuthPageViewProps } from "./GitAuthPageView";
import ExternalAuthPageView, {
ExternalAuthPageViewProps,
} from "./ExternalAuthPageView";
export default {
title: "pages/GitAuthPageView",
component: GitAuthPageView,
} as Meta<typeof GitAuthPageView>;
title: "pages/ExternalAuthPageView",
component: ExternalAuthPageView,
} as Meta<typeof ExternalAuthPageView>;
const Template: StoryFn<GitAuthPageViewProps> = (args) => (
<GitAuthPageView {...args} />
const Template: StoryFn<ExternalAuthPageViewProps> = (args) => (
<ExternalAuthPageView {...args} />
);
export const WebAuthenticated = Template.bind({});
WebAuthenticated.args = {
gitAuth: {
externalAuth: {
type: "BitBucket",
authenticated: true,
device: false,
@ -30,7 +32,7 @@ WebAuthenticated.args = {
export const DeviceUnauthenticated = Template.bind({});
DeviceUnauthenticated.args = {
gitAuth: {
externalAuth: {
type: "GitHub",
authenticated: false,
device: true,
@ -38,7 +40,7 @@ DeviceUnauthenticated.args = {
app_install_url: "",
app_installable: false,
},
gitAuthDevice: {
externalAuthDevice: {
device_code: "1234-5678",
expires_in: 900,
interval: 5,
@ -49,7 +51,7 @@ DeviceUnauthenticated.args = {
export const DeviceUnauthenticatedError = Template.bind({});
DeviceUnauthenticatedError.args = {
gitAuth: {
externalAuth: {
type: "GitHub",
authenticated: false,
device: true,
@ -57,7 +59,7 @@ DeviceUnauthenticatedError.args = {
app_install_url: "",
app_installable: false,
},
gitAuthDevice: {
externalAuthDevice: {
device_code: "1234-5678",
expires_in: 900,
interval: 5,
@ -72,8 +74,8 @@ DeviceUnauthenticatedError.args = {
export const DeviceAuthenticatedNotInstalled = Template.bind({});
DeviceAuthenticatedNotInstalled.args = {
viewGitAuthConfig: true,
gitAuth: {
viewExternalAuthConfig: true,
externalAuth: {
type: "GitHub",
authenticated: true,
device: true,
@ -91,7 +93,7 @@ DeviceAuthenticatedNotInstalled.args = {
export const DeviceAuthenticatedInstalled = Template.bind({});
DeviceAuthenticatedInstalled.args = {
gitAuth: {
externalAuth: {
type: "GitHub",
authenticated: true,
device: true,

View File

@ -5,7 +5,7 @@ import Link from "@mui/material/Link";
import Tooltip from "@mui/material/Tooltip";
import { makeStyles } from "@mui/styles";
import { ApiErrorResponse } from "api/errors";
import { GitAuth, GitAuthDevice } from "api/typesGenerated";
import { ExternalAuth, ExternalAuthDevice } from "api/typesGenerated";
import { Alert } from "components/Alert/Alert";
import { Avatar } from "components/Avatar/Avatar";
import { CopyButton } from "components/CopyButton/CopyButton";
@ -13,47 +13,53 @@ import { SignInLayout } from "components/SignInLayout/SignInLayout";
import { Welcome } from "components/Welcome/Welcome";
import { type FC } from "react";
export interface GitAuthPageViewProps {
gitAuth: GitAuth;
viewGitAuthConfig: boolean;
export interface ExternalAuthPageViewProps {
externalAuth: ExternalAuth;
viewExternalAuthConfig: boolean;
gitAuthDevice?: GitAuthDevice;
externalAuthDevice?: ExternalAuthDevice;
deviceExchangeError?: ApiErrorResponse;
onReauthenticate: () => void;
}
const GitAuthPageView: FC<GitAuthPageViewProps> = ({
const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
deviceExchangeError,
gitAuth,
gitAuthDevice,
externalAuth,
externalAuthDevice,
onReauthenticate,
viewGitAuthConfig,
viewExternalAuthConfig,
}) => {
const styles = useStyles();
if (!gitAuth.authenticated) {
if (!externalAuth.authenticated) {
return (
<SignInLayout>
<Welcome message={`Authenticate with ${gitAuth.type}`} />
<Welcome message={`Authenticate with ${externalAuth.type}`} />
{gitAuth.device && (
{externalAuth.device && (
<GitDeviceAuth
deviceExchangeError={deviceExchangeError}
gitAuthDevice={gitAuthDevice}
externalAuthDevice={externalAuthDevice}
/>
)}
</SignInLayout>
);
}
const hasInstallations = gitAuth.installations.length > 0;
const hasInstallations = externalAuth.installations.length > 0;
// We only want to wrap this with a link if an install URL is available!
let installTheApp: JSX.Element = <>{`install the ${gitAuth.type} App`}</>;
if (gitAuth.app_install_url) {
let installTheApp: JSX.Element = (
<>{`install the ${externalAuth.type} App`}</>
);
if (externalAuth.app_install_url) {
installTheApp = (
<Link href={gitAuth.app_install_url} target="_blank" rel="noreferrer">
<Link
href={externalAuth.app_install_url}
target="_blank"
rel="noreferrer"
>
{installTheApp}
</Link>
);
@ -61,16 +67,17 @@ const GitAuthPageView: FC<GitAuthPageViewProps> = ({
return (
<SignInLayout>
<Welcome message={`You've authenticated with ${gitAuth.type}!`} />
<Welcome message={`You've authenticated with ${externalAuth.type}!`} />
<p className={styles.text}>
Hey @{gitAuth.user?.login}! 👋{" "}
{(!gitAuth.app_installable || gitAuth.installations.length > 0) &&
Hey @{externalAuth.user?.login}! 👋{" "}
{(!externalAuth.app_installable ||
externalAuth.installations.length > 0) &&
"You are now authenticated with Git. Feel free to close this window!"}
</p>
{gitAuth.installations.length > 0 && (
{externalAuth.installations.length > 0 && (
<div className={styles.authorizedInstalls}>
{gitAuth.installations.map((install) => {
{externalAuth.installations.map((install) => {
if (!install.account) {
return;
}
@ -93,32 +100,33 @@ const GitAuthPageView: FC<GitAuthPageViewProps> = ({
);
})}
&nbsp;
{gitAuth.installations.length} organization
{gitAuth.installations.length !== 1 && "s are"} authorized
{externalAuth.installations.length} organization
{externalAuth.installations.length !== 1 && "s are"} authorized
</div>
)}
<div className={styles.links}>
{!hasInstallations && gitAuth.app_installable && (
{!hasInstallations && externalAuth.app_installable && (
<Alert severity="warning" className={styles.installAlert}>
You must {installTheApp} to clone private repositories. Accounts
will appear here once authorized.
</Alert>
)}
{viewGitAuthConfig &&
gitAuth.app_install_url &&
gitAuth.app_installable && (
{viewExternalAuthConfig &&
externalAuth.app_install_url &&
externalAuth.app_installable && (
<Link
href={gitAuth.app_install_url}
href={externalAuth.app_install_url}
target="_blank"
rel="noreferrer"
className={styles.link}
>
<OpenInNewIcon fontSize="small" />
{gitAuth.installations.length > 0
{externalAuth.installations.length > 0
? "Configure"
: "Install"} the {gitAuth.type} App
: "Install"}{" "}
the {externalAuth.type} App
</Link>
)}
<Link
@ -136,9 +144,9 @@ const GitAuthPageView: FC<GitAuthPageViewProps> = ({
};
const GitDeviceAuth: FC<{
gitAuthDevice?: GitAuthDevice;
externalAuthDevice?: ExternalAuthDevice;
deviceExchangeError?: ApiErrorResponse;
}> = ({ gitAuthDevice, deviceExchangeError }) => {
}> = ({ externalAuthDevice, deviceExchangeError }) => {
const styles = useStyles();
let status = (
@ -175,7 +183,7 @@ const GitDeviceAuth: FC<{
}
}
if (!gitAuthDevice) {
if (!externalAuthDevice) {
return <CircularProgress />;
}
@ -184,8 +192,8 @@ const GitDeviceAuth: FC<{
<p className={styles.text}>
Copy your one-time code:&nbsp;
<div className={styles.copyCode}>
<span className={styles.code}>{gitAuthDevice.user_code}</span>&nbsp;{" "}
<CopyButton text={gitAuthDevice.user_code} />
<span className={styles.code}>{externalAuthDevice.user_code}</span>
&nbsp; <CopyButton text={externalAuthDevice.user_code} />
</div>
<br />
Then open the link below and paste it:
@ -193,7 +201,7 @@ const GitDeviceAuth: FC<{
<div className={styles.links}>
<Link
className={styles.link}
href={gitAuthDevice.verification_uri}
href={externalAuthDevice.verification_uri}
target="_blank"
rel="noreferrer"
>
@ -207,7 +215,7 @@ const GitDeviceAuth: FC<{
);
};
export default GitAuthPageView;
export default ExternalAuthPageView;
const useStyles = makeStyles((theme) => ({
text: {

View File

@ -1,89 +0,0 @@
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
exchangeGitAuthDevice,
getGitAuthDevice,
getGitAuthProvider,
} from "api/api";
import { usePermissions } from "hooks";
import { type FC } from "react";
import { useParams } from "react-router-dom";
import GitAuthPageView from "./GitAuthPageView";
import { ApiErrorResponse } from "api/errors";
import { isAxiosError } from "axios";
const GitAuthPage: FC = () => {
const { provider } = useParams();
if (!provider) {
throw new Error("provider must exist");
}
const permissions = usePermissions();
const queryClient = useQueryClient();
const getGitAuthProviderQuery = useQuery({
queryKey: ["gitauth", provider],
queryFn: () => getGitAuthProvider(provider),
refetchOnWindowFocus: true,
});
const getGitAuthDeviceQuery = useQuery({
enabled:
Boolean(!getGitAuthProviderQuery.data?.authenticated) &&
Boolean(getGitAuthProviderQuery.data?.device),
queryFn: () => getGitAuthDevice(provider),
queryKey: ["gitauth", provider, "device"],
refetchOnMount: false,
});
const exchangeGitAuthDeviceQuery = useQuery({
queryFn: () =>
exchangeGitAuthDevice(provider, {
device_code: getGitAuthDeviceQuery.data?.device_code || "",
}),
queryKey: ["gitauth", provider, getGitAuthDeviceQuery.data?.device_code],
enabled: Boolean(getGitAuthDeviceQuery.data),
onSuccess: () => {
// Force a refresh of the Git auth status.
queryClient.invalidateQueries(["gitauth", provider]).catch((ex) => {
console.error("invalidate queries", ex);
});
},
retry: true,
retryDelay: (getGitAuthDeviceQuery.data?.interval || 5) * 1000,
refetchOnWindowFocus: (query) =>
query.state.status === "success" ? false : "always",
});
if (getGitAuthProviderQuery.isLoading || !getGitAuthProviderQuery.data) {
return null;
}
let deviceExchangeError: ApiErrorResponse | undefined;
if (isAxiosError(exchangeGitAuthDeviceQuery.failureReason)) {
deviceExchangeError =
exchangeGitAuthDeviceQuery.failureReason.response?.data;
}
if (
!getGitAuthProviderQuery.data.authenticated &&
!getGitAuthProviderQuery.data.device
) {
window.location.href = `/gitauth/${provider}/callback`;
return null;
}
return (
<GitAuthPageView
gitAuth={getGitAuthProviderQuery.data}
onReauthenticate={() => {
queryClient.setQueryData(["gitauth", provider], {
...getGitAuthProviderQuery.data,
authenticated: false,
});
}}
viewGitAuthConfig={permissions.viewGitAuthConfig}
deviceExchangeError={deviceExchangeError}
gitAuthDevice={getGitAuthDeviceQuery.data}
/>
);
};
export default GitAuthPage;

View File

@ -2140,7 +2140,7 @@ export const MockPermissions: Permissions = {
viewDeploymentValues: true,
viewUpdateCheck: true,
viewDeploymentStats: true,
viewGitAuthConfig: true,
viewExternalAuthConfig: true,
editWorkspaceProxies: true,
};
@ -2188,7 +2188,7 @@ export const MockTemplateVersionExternalAuthGithub: TypesGen.TemplateVersionExte
{
id: "github",
type: "github",
authenticate_url: "https://example.com/gitauth/github",
authenticate_url: "https://example.com/externalauth/github",
authenticated: false,
};
@ -2196,7 +2196,7 @@ export const MockTemplateVersionExternalAuthGithubAuthenticated: TypesGen.Templa
{
id: "github",
type: "github",
authenticate_url: "https://example.com/gitauth/github",
authenticate_url: "https://example.com/externalauth/github",
authenticated: true,
};

View File

@ -112,7 +112,7 @@ export const handlers = [
},
),
rest.get(
"/api/v2/templateversions/:templateVersionId/gitauth",
"/api/v2/templateversions/:templateVersionId/externalauth",
async (req, res, ctx) => {
return res(ctx.status(200), ctx.json([]));
},

View File

@ -1 +0,0 @@
export const REFRESH_GITAUTH_BROADCAST_CHANNEL = "gitauth_refresh";

View File

@ -18,7 +18,7 @@ export const checks = {
viewDeploymentValues: "viewDeploymentValues",
createGroup: "createGroup",
viewUpdateCheck: "viewUpdateCheck",
viewGitAuthConfig: "viewGitAuthConfig",
viewExternalAuthConfig: "viewExternalAuthConfig",
viewDeploymentStats: "viewDeploymentStats",
editWorkspaceProxies: "editWorkspaceProxies",
} as const;
@ -84,7 +84,7 @@ export const permissionsToCheck = {
},
action: "read",
},
[checks.viewGitAuthConfig]: {
[checks.viewExternalAuthConfig]: {
object: {
resource_type: "deployment_config",
},