mirror of https://github.com/coder/coder.git
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:
parent
16a2d4d733
commit
5596fb20b5
|
@ -39,6 +39,7 @@
|
|||
"enterprisemeta",
|
||||
"errgroup",
|
||||
"eventsourcemock",
|
||||
"externalauth",
|
||||
"Failf",
|
||||
"fatih",
|
||||
"Formik",
|
||||
|
|
2
Makefile
2
Makefile
|
@ -542,7 +542,7 @@ site/src/api/typesGenerated.ts: scripts/apitypings/main.go $(shell find ./coders
|
|||
cd site
|
||||
pnpm run format:types ./src/api/typesGenerated.ts
|
||||
|
||||
site/e2e/provisionerGenerated.ts: provisionerd/proto/provisionerd.pb.go
|
||||
site/e2e/provisionerGenerated.ts: provisionerd/proto/provisionerd.pb.go provisionersdk/proto/provisioner.pb.go
|
||||
cd site
|
||||
../scripts/pnpm_install.sh
|
||||
pnpm run gen:provisioner
|
||||
|
|
|
@ -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?")
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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,
|
|
@ -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,
|
|
@ -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 {
|
|
@ -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)
|
|
@ -1,9 +0,0 @@
|
|||
package gitauth_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOAuthJWTConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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**
|
||||
|
||||
|
|
|
@ -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(),
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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!
|
|
@ -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 />} />
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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")}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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: {
|
|
@ -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>
|
||||
)}
|
|
@ -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;
|
|
@ -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,
|
|
@ -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> = ({
|
|||
);
|
||||
})}
|
||||
|
||||
{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:
|
||||
<div className={styles.copyCode}>
|
||||
<span className={styles.code}>{gitAuthDevice.user_code}</span> {" "}
|
||||
<CopyButton text={gitAuthDevice.user_code} />
|
||||
<span className={styles.code}>{externalAuthDevice.user_code}</span>
|
||||
<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: {
|
|
@ -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;
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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([]));
|
||||
},
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export const REFRESH_GITAUTH_BROADCAST_CHANNEL = "gitauth_refresh";
|
|
@ -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",
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue