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

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

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

* Rename FE

* Fix em' up

* Fix linting error

* Fix e2e tests

* chore: update helm golden files
This commit is contained in:
Kyle Carberry
2023-09-30 14:30:01 -05:00
committed by GitHub
parent 16a2d4d733
commit 5596fb20b5
59 changed files with 1231 additions and 1211 deletions
+188 -188
View File
@@ -602,6 +602,103 @@ const docTemplate = `{
}
}
},
"/externalauth/{externalauth}": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Git"
],
"summary": "Get external auth by ID",
"operationId": "get-external-auth-by-id",
"parameters": [
{
"type": "string",
"format": "string",
"description": "Git Provider ID",
"name": "externalauth",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.ExternalAuth"
}
}
}
}
},
"/externalauth/{externalauth}/device": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Git"
],
"summary": "Get external auth device by ID.",
"operationId": "get-external-auth-device-by-id",
"parameters": [
{
"type": "string",
"format": "string",
"description": "Git Provider ID",
"name": "externalauth",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.ExternalAuthDevice"
}
}
}
},
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Git"
],
"summary": "Post external auth device by ID",
"operationId": "post-external-auth-device-by-id",
"parameters": [
{
"type": "string",
"format": "string",
"description": "External Provider ID",
"name": "externalauth",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/files": {
"post": {
"security": [
@@ -677,103 +774,6 @@ const docTemplate = `{
}
}
},
"/gitauth/{gitauth}": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Git"
],
"summary": "Get git auth by ID",
"operationId": "get-git-auth-by-id",
"parameters": [
{
"type": "string",
"format": "string",
"description": "Git Provider ID",
"name": "gitauth",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.GitAuth"
}
}
}
}
},
"/gitauth/{gitauth}/device": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Git"
],
"summary": "Get git auth device by ID.",
"operationId": "get-git-auth-device-by-id",
"parameters": [
{
"type": "string",
"format": "string",
"description": "Git Provider ID",
"name": "gitauth",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.GitAuthDevice"
}
}
}
},
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Git"
],
"summary": "Post git auth device by ID",
"operationId": "post-git-auth-device-by-id",
"parameters": [
{
"type": "string",
"format": "string",
"description": "Git Provider ID",
"name": "gitauth",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/groups/{group}": {
"get": {
"security": [
@@ -2768,7 +2768,7 @@ const docTemplate = `{
}
}
},
"/templateversions/{templateversion}/gitauth": {
"/templateversions/{templateversion}/externalauth": {
"get": {
"security": [
{
@@ -2781,8 +2781,8 @@ const docTemplate = `{
"tags": [
"Templates"
],
"summary": "Get git auth by template version",
"operationId": "get-git-auth-by-template-version",
"summary": "Get external auth by template version",
"operationId": "get-external-auth-by-template-version",
"parameters": [
{
"type": "string",
@@ -8186,6 +8186,77 @@ const docTemplate = `{
"ExperimentDeploymentHealthPage"
]
},
"codersdk.ExternalAuth": {
"type": "object",
"properties": {
"app_install_url": {
"description": "AppInstallURL is the URL to install the app.",
"type": "string"
},
"app_installable": {
"description": "AppInstallable is true if the request for app installs was successful.",
"type": "boolean"
},
"authenticated": {
"type": "boolean"
},
"device": {
"type": "boolean"
},
"installations": {
"description": "AppInstallations are the installations that the user has access to.",
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.ExternalAuthAppInstallation"
}
},
"type": {
"type": "string"
},
"user": {
"description": "User is the user that authenticated with the provider.",
"allOf": [
{
"$ref": "#/definitions/codersdk.ExternalAuthUser"
}
]
}
}
},
"codersdk.ExternalAuthAppInstallation": {
"type": "object",
"properties": {
"account": {
"$ref": "#/definitions/codersdk.ExternalAuthUser"
},
"configure_url": {
"type": "string"
},
"id": {
"type": "integer"
}
}
},
"codersdk.ExternalAuthDevice": {
"type": "object",
"properties": {
"device_code": {
"type": "string"
},
"expires_in": {
"type": "integer"
},
"interval": {
"type": "integer"
},
"user_code": {
"type": "string"
},
"verification_uri": {
"type": "string"
}
}
},
"codersdk.ExternalAuthProvider": {
"type": "string",
"enum": [
@@ -8203,6 +8274,23 @@ const docTemplate = `{
"ExternalAuthProviderOpenIDConnect"
]
},
"codersdk.ExternalAuthUser": {
"type": "object",
"properties": {
"avatar_url": {
"type": "string"
},
"login": {
"type": "string"
},
"name": {
"type": "string"
},
"profile_url": {
"type": "string"
}
}
},
"codersdk.Feature": {
"type": "object",
"properties": {
@@ -8242,57 +8330,6 @@ const docTemplate = `{
}
}
},
"codersdk.GitAuth": {
"type": "object",
"properties": {
"app_install_url": {
"description": "AppInstallURL is the URL to install the app.",
"type": "string"
},
"app_installable": {
"description": "AppInstallable is true if the request for app installs was successful.",
"type": "boolean"
},
"authenticated": {
"type": "boolean"
},
"device": {
"type": "boolean"
},
"installations": {
"description": "AppInstallations are the installations that the user has access to.",
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.GitAuthAppInstallation"
}
},
"type": {
"type": "string"
},
"user": {
"description": "User is the user that authenticated with the provider.",
"allOf": [
{
"$ref": "#/definitions/codersdk.GitAuthUser"
}
]
}
}
},
"codersdk.GitAuthAppInstallation": {
"type": "object",
"properties": {
"account": {
"$ref": "#/definitions/codersdk.GitAuthUser"
},
"configure_url": {
"type": "string"
},
"id": {
"type": "integer"
}
}
},
"codersdk.GitAuthConfig": {
"type": "object",
"properties": {
@@ -8340,43 +8377,6 @@ const docTemplate = `{
}
}
},
"codersdk.GitAuthDevice": {
"type": "object",
"properties": {
"device_code": {
"type": "string"
},
"expires_in": {
"type": "integer"
},
"interval": {
"type": "integer"
},
"user_code": {
"type": "string"
},
"verification_uri": {
"type": "string"
}
}
},
"codersdk.GitAuthUser": {
"type": "object",
"properties": {
"avatar_url": {
"type": "string"
},
"login": {
"type": "string"
},
"name": {
"type": "string"
},
"profile_url": {
"type": "string"
}
}
},
"codersdk.GitSSHKey": {
"type": "object",
"properties": {
+178 -178
View File
@@ -512,6 +512,93 @@
}
}
},
"/externalauth/{externalauth}": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Git"],
"summary": "Get external auth by ID",
"operationId": "get-external-auth-by-id",
"parameters": [
{
"type": "string",
"format": "string",
"description": "Git Provider ID",
"name": "externalauth",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.ExternalAuth"
}
}
}
}
},
"/externalauth/{externalauth}/device": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Git"],
"summary": "Get external auth device by ID.",
"operationId": "get-external-auth-device-by-id",
"parameters": [
{
"type": "string",
"format": "string",
"description": "Git Provider ID",
"name": "externalauth",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.ExternalAuthDevice"
}
}
}
},
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Git"],
"summary": "Post external auth device by ID",
"operationId": "post-external-auth-device-by-id",
"parameters": [
{
"type": "string",
"format": "string",
"description": "External Provider ID",
"name": "externalauth",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/files": {
"post": {
"security": [
@@ -579,93 +666,6 @@
}
}
},
"/gitauth/{gitauth}": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Git"],
"summary": "Get git auth by ID",
"operationId": "get-git-auth-by-id",
"parameters": [
{
"type": "string",
"format": "string",
"description": "Git Provider ID",
"name": "gitauth",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.GitAuth"
}
}
}
}
},
"/gitauth/{gitauth}/device": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Git"],
"summary": "Get git auth device by ID.",
"operationId": "get-git-auth-device-by-id",
"parameters": [
{
"type": "string",
"format": "string",
"description": "Git Provider ID",
"name": "gitauth",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.GitAuthDevice"
}
}
}
},
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Git"],
"summary": "Post git auth device by ID",
"operationId": "post-git-auth-device-by-id",
"parameters": [
{
"type": "string",
"format": "string",
"description": "Git Provider ID",
"name": "gitauth",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/groups/{group}": {
"get": {
"security": [
@@ -2430,7 +2430,7 @@
}
}
},
"/templateversions/{templateversion}/gitauth": {
"/templateversions/{templateversion}/externalauth": {
"get": {
"security": [
{
@@ -2439,8 +2439,8 @@
],
"produces": ["application/json"],
"tags": ["Templates"],
"summary": "Get git auth by template version",
"operationId": "get-git-auth-by-template-version",
"summary": "Get external auth by template version",
"operationId": "get-external-auth-by-template-version",
"parameters": [
{
"type": "string",
@@ -7334,6 +7334,77 @@
"ExperimentDeploymentHealthPage"
]
},
"codersdk.ExternalAuth": {
"type": "object",
"properties": {
"app_install_url": {
"description": "AppInstallURL is the URL to install the app.",
"type": "string"
},
"app_installable": {
"description": "AppInstallable is true if the request for app installs was successful.",
"type": "boolean"
},
"authenticated": {
"type": "boolean"
},
"device": {
"type": "boolean"
},
"installations": {
"description": "AppInstallations are the installations that the user has access to.",
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.ExternalAuthAppInstallation"
}
},
"type": {
"type": "string"
},
"user": {
"description": "User is the user that authenticated with the provider.",
"allOf": [
{
"$ref": "#/definitions/codersdk.ExternalAuthUser"
}
]
}
}
},
"codersdk.ExternalAuthAppInstallation": {
"type": "object",
"properties": {
"account": {
"$ref": "#/definitions/codersdk.ExternalAuthUser"
},
"configure_url": {
"type": "string"
},
"id": {
"type": "integer"
}
}
},
"codersdk.ExternalAuthDevice": {
"type": "object",
"properties": {
"device_code": {
"type": "string"
},
"expires_in": {
"type": "integer"
},
"interval": {
"type": "integer"
},
"user_code": {
"type": "string"
},
"verification_uri": {
"type": "string"
}
}
},
"codersdk.ExternalAuthProvider": {
"type": "string",
"enum": [
@@ -7351,6 +7422,23 @@
"ExternalAuthProviderOpenIDConnect"
]
},
"codersdk.ExternalAuthUser": {
"type": "object",
"properties": {
"avatar_url": {
"type": "string"
},
"login": {
"type": "string"
},
"name": {
"type": "string"
},
"profile_url": {
"type": "string"
}
}
},
"codersdk.Feature": {
"type": "object",
"properties": {
@@ -7390,57 +7478,6 @@
}
}
},
"codersdk.GitAuth": {
"type": "object",
"properties": {
"app_install_url": {
"description": "AppInstallURL is the URL to install the app.",
"type": "string"
},
"app_installable": {
"description": "AppInstallable is true if the request for app installs was successful.",
"type": "boolean"
},
"authenticated": {
"type": "boolean"
},
"device": {
"type": "boolean"
},
"installations": {
"description": "AppInstallations are the installations that the user has access to.",
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.GitAuthAppInstallation"
}
},
"type": {
"type": "string"
},
"user": {
"description": "User is the user that authenticated with the provider.",
"allOf": [
{
"$ref": "#/definitions/codersdk.GitAuthUser"
}
]
}
}
},
"codersdk.GitAuthAppInstallation": {
"type": "object",
"properties": {
"account": {
"$ref": "#/definitions/codersdk.GitAuthUser"
},
"configure_url": {
"type": "string"
},
"id": {
"type": "integer"
}
}
},
"codersdk.GitAuthConfig": {
"type": "object",
"properties": {
@@ -7488,43 +7525,6 @@
}
}
},
"codersdk.GitAuthDevice": {
"type": "object",
"properties": {
"device_code": {
"type": "string"
},
"expires_in": {
"type": "integer"
},
"interval": {
"type": "integer"
},
"user_code": {
"type": "string"
},
"verification_uri": {
"type": "string"
}
}
},
"codersdk.GitAuthUser": {
"type": "object",
"properties": {
"avatar_url": {
"type": "string"
},
"login": {
"type": "string"
},
"name": {
"type": "string"
},
"profile_url": {
"type": "string"
}
}
},
"codersdk.GitSSHKey": {
"type": "object",
"properties": {
+25 -22
View File
@@ -37,6 +37,7 @@ import (
// Used for swagger docs.
_ "github.com/coder/coder/v2/coderd/apidoc"
"github.com/coder/coder/v2/coderd/externalauth"
"cdr.dev/slog"
"github.com/coder/coder/v2/buildinfo"
@@ -47,7 +48,6 @@ import (
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/database/pubsub"
"github.com/coder/coder/v2/coderd/gitauth"
"github.com/coder/coder/v2/coderd/gitsshkey"
"github.com/coder/coder/v2/coderd/healthcheck"
"github.com/coder/coder/v2/coderd/httpapi"
@@ -115,7 +115,7 @@ type Options struct {
SSHKeygenAlgorithm gitsshkey.Algorithm
Telemetry telemetry.Reporter
TracerProvider trace.TracerProvider
ExternalAuthConfigs []*gitauth.Config
ExternalAuthConfigs []*externalauth.Config
RealIPConfig *httpmw.RealIPConfig
TrialGenerator func(ctx context.Context, email string) error
// TLSCertificates is used to mesh DERP servers securely.
@@ -546,21 +546,24 @@ func New(options *Options) *API {
})
// Register callback handlers for each OAuth2 provider.
r.Route("/gitauth", func(r chi.Router) {
for _, gitAuthConfig := range options.ExternalAuthConfigs {
// We don't need to register a callback handler for device auth.
if gitAuthConfig.DeviceAuth != nil {
continue
// We must support gitauth and externalauth for backwards compatibility.
for _, route := range []string{"gitauth", "externalauth"} {
r.Route("/"+route, func(r chi.Router) {
for _, externalAuthConfig := range options.ExternalAuthConfigs {
// We don't need to register a callback handler for device auth.
if externalAuthConfig.DeviceAuth != nil {
continue
}
r.Route(fmt.Sprintf("/%s/callback", externalAuthConfig.ID), func(r chi.Router) {
r.Use(
apiKeyMiddlewareRedirect,
httpmw.ExtractOAuth2(externalAuthConfig, options.HTTPClient, nil),
)
r.Get("/", api.externalAuthCallback(externalAuthConfig))
})
}
r.Route(fmt.Sprintf("/%s/callback", gitAuthConfig.ID), func(r chi.Router) {
r.Use(
apiKeyMiddlewareRedirect,
httpmw.ExtractOAuth2(gitAuthConfig, options.HTTPClient, nil),
)
r.Get("/", api.gitAuthCallback(gitAuthConfig))
})
}
})
})
}
r.Route("/api/v2", func(r chi.Router) {
api.APIHandler = r
@@ -613,14 +616,14 @@ func New(options *Options) *API {
r.Get("/{fileID}", api.fileByID)
r.Post("/", api.postFile)
})
r.Route("/gitauth/{gitauth}", func(r chi.Router) {
r.Route("/externalauth/{externalauth}", func(r chi.Router) {
r.Use(
apiKeyMiddleware,
httpmw.ExtractGitAuthParam(options.ExternalAuthConfigs),
httpmw.ExtractExternalAuthParam(options.ExternalAuthConfigs),
)
r.Get("/", api.gitAuthByID)
r.Post("/device", api.postGitAuthDeviceByID)
r.Get("/device", api.gitAuthDeviceByID)
r.Get("/", api.externalAuthByID)
r.Post("/device", api.postExternalAuthDeviceByID)
r.Get("/device", api.externalAuthDeviceByID)
})
r.Route("/organizations", func(r chi.Router) {
r.Use(
@@ -686,7 +689,7 @@ func New(options *Options) *API {
r.Get("/schema", templateVersionSchemaDeprecated)
r.Get("/parameters", templateVersionParametersDeprecated)
r.Get("/rich-parameters", api.templateVersionRichParameters)
r.Get("/gitauth", api.templateVersionGitAuth)
r.Get("/externalauth", api.templateVersionExternalAuth)
r.Get("/variables", api.templateVersionVariables)
r.Get("/resources", api.templateVersionResources)
r.Get("/logs", api.templateVersionLogs)
+6 -6
View File
@@ -59,7 +59,7 @@ import (
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/coderd/database/pubsub"
"github.com/coder/coder/v2/coderd/gitauth"
"github.com/coder/coder/v2/coderd/externalauth"
"github.com/coder/coder/v2/coderd/gitsshkey"
"github.com/coder/coder/v2/coderd/healthcheck"
"github.com/coder/coder/v2/coderd/httpapi"
@@ -105,7 +105,7 @@ type Options struct {
AutobuildStats chan<- autobuild.Stats
Auditor audit.Auditor
TLSCertificates []tls.Certificate
ExternalAuthConfigs []*gitauth.Config
ExternalAuthConfigs []*externalauth.Config
TrialGenerator func(context.Context, string) error
TemplateScheduleStore schedule.TemplateScheduleStore
Coordinator tailnet.Coordinator
@@ -899,14 +899,14 @@ func MustWorkspace(t *testing.T, client *codersdk.Client, workspaceID uuid.UUID)
return ws
}
// RequestGitAuthCallback makes a request with the proper OAuth2 state cookie
// to the git auth callback endpoint.
func RequestGitAuthCallback(t *testing.T, providerID string, client *codersdk.Client) *http.Response {
// RequestExternalAuthCallback makes a request with the proper OAuth2 state cookie
// to the external auth callback endpoint.
func RequestExternalAuthCallback(t *testing.T, providerID string, client *codersdk.Client) *http.Response {
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
state := "somestate"
oauthURL, err := client.URL.Parse(fmt.Sprintf("/gitauth/%s/callback?code=asd&state=%s", providerID, state))
oauthURL, err := client.URL.Parse(fmt.Sprintf("/externalauth/%s/callback?code=asd&state=%s", providerID, state))
require.NoError(t, err)
req, err := http.NewRequestWithContext(context.Background(), "GET", oauthURL.String(), nil)
require.NoError(t, err)
+1 -1
View File
@@ -2486,7 +2486,7 @@ func (q *querier) UpdateTemplateVersionDescriptionByJobID(ctx context.Context, a
}
func (q *querier) UpdateTemplateVersionExternalAuthProvidersByJobID(ctx context.Context, arg database.UpdateTemplateVersionExternalAuthProvidersByJobIDParams) error {
// An actor is allowed to update the template version git auth providers if they are authorized to update the template.
// An actor is allowed to update the template version external auth providers if they are authorized to update the template.
tv, err := q.db.GetTemplateVersionByJobID(ctx, arg.JobID)
if err != nil {
return err
+37 -37
View File
@@ -10,31 +10,31 @@ import (
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/gitauth"
"github.com/coder/coder/v2/coderd/externalauth"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/httpmw"
"github.com/coder/coder/v2/codersdk"
)
// @Summary Get git auth by ID
// @ID get-git-auth-by-id
// @Summary Get external auth by ID
// @ID get-external-auth-by-id
// @Security CoderSessionToken
// @Produce json
// @Tags Git
// @Param gitauth path string true "Git Provider ID" format(string)
// @Success 200 {object} codersdk.GitAuth
// @Router /gitauth/{gitauth} [get]
func (api *API) gitAuthByID(w http.ResponseWriter, r *http.Request) {
config := httpmw.GitAuthParam(r)
// @Param externalauth path string true "Git Provider ID" format(string)
// @Success 200 {object} codersdk.ExternalAuth
// @Router /externalauth/{externalauth} [get]
func (api *API) externalAuthByID(w http.ResponseWriter, r *http.Request) {
config := httpmw.ExternalAuthParam(r)
apiKey := httpmw.APIKey(r)
ctx := r.Context()
res := codersdk.GitAuth{
res := codersdk.ExternalAuth{
Authenticated: false,
Device: config.DeviceAuth != nil,
AppInstallURL: config.AppInstallURL,
Type: config.Type.Pretty(),
AppInstallations: []codersdk.GitAuthAppInstallation{},
AppInstallations: []codersdk.ExternalAuthAppInstallation{},
}
link, err := api.Database.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{
@@ -44,7 +44,7 @@ func (api *API) gitAuthByID(w http.ResponseWriter, r *http.Request) {
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to get git auth link.",
Message: "Failed to get external auth link.",
Detail: err.Error(),
})
return
@@ -71,24 +71,24 @@ func (api *API) gitAuthByID(w http.ResponseWriter, r *http.Request) {
return
}
if res.AppInstallations == nil {
res.AppInstallations = []codersdk.GitAuthAppInstallation{}
res.AppInstallations = []codersdk.ExternalAuthAppInstallation{}
}
httpapi.Write(ctx, w, http.StatusOK, res)
}
// @Summary Post git auth device by ID
// @ID post-git-auth-device-by-id
// @Summary Post external auth device by ID
// @ID post-external-auth-device-by-id
// @Security CoderSessionToken
// @Tags Git
// @Param gitauth path string true "Git Provider ID" format(string)
// @Param externalauth path string true "External Provider ID" format(string)
// @Success 204
// @Router /gitauth/{gitauth}/device [post]
func (api *API) postGitAuthDeviceByID(rw http.ResponseWriter, r *http.Request) {
// @Router /externalauth/{externalauth}/device [post]
func (api *API) postExternalAuthDeviceByID(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)
config := httpmw.GitAuthParam(r)
config := httpmw.ExternalAuthParam(r)
var req codersdk.GitAuthDeviceExchange
var req codersdk.ExternalAuthDeviceExchange
if !httpapi.Read(ctx, rw, r, &req) {
return
}
@@ -116,7 +116,7 @@ func (api *API) postGitAuthDeviceByID(rw http.ResponseWriter, r *http.Request) {
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Failed to get git auth link.",
Message: "Failed to get external auth link.",
Detail: err.Error(),
})
return
@@ -133,7 +133,7 @@ func (api *API) postGitAuthDeviceByID(rw http.ResponseWriter, r *http.Request) {
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Failed to insert git auth link.",
Message: "Failed to insert external auth link.",
Detail: err.Error(),
})
return
@@ -149,7 +149,7 @@ func (api *API) postGitAuthDeviceByID(rw http.ResponseWriter, r *http.Request) {
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Failed to update git auth link.",
Message: "Failed to update external auth link.",
Detail: err.Error(),
})
return
@@ -158,16 +158,16 @@ func (api *API) postGitAuthDeviceByID(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusNoContent, nil)
}
// @Summary Get git auth device by ID.
// @ID get-git-auth-device-by-id
// @Summary Get external auth device by ID.
// @ID get-external-auth-device-by-id
// @Security CoderSessionToken
// @Produce json
// @Tags Git
// @Param gitauth path string true "Git Provider ID" format(string)
// @Success 200 {object} codersdk.GitAuthDevice
// @Router /gitauth/{gitauth}/device [get]
func (*API) gitAuthDeviceByID(rw http.ResponseWriter, r *http.Request) {
config := httpmw.GitAuthParam(r)
// @Param externalauth path string true "Git Provider ID" format(string)
// @Success 200 {object} codersdk.ExternalAuthDevice
// @Router /externalauth/{externalauth}/device [get]
func (*API) externalAuthDeviceByID(rw http.ResponseWriter, r *http.Request) {
config := httpmw.ExternalAuthParam(r)
ctx := r.Context()
if config.DeviceAuth == nil {
@@ -189,7 +189,7 @@ func (*API) gitAuthDeviceByID(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusOK, deviceAuth)
}
func (api *API) gitAuthCallback(gitAuthConfig *gitauth.Config) http.HandlerFunc {
func (api *API) externalAuthCallback(externalAuthConfig *externalauth.Config) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@@ -198,20 +198,20 @@ func (api *API) gitAuthCallback(gitAuthConfig *gitauth.Config) http.HandlerFunc
)
_, err := api.Database.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{
ProviderID: gitAuthConfig.ID,
ProviderID: externalAuthConfig.ID,
UserID: apiKey.UserID,
})
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Failed to get git auth link.",
Message: "Failed to get external auth link.",
Detail: err.Error(),
})
return
}
_, err = api.Database.InsertExternalAuthLink(ctx, database.InsertExternalAuthLinkParams{
ProviderID: gitAuthConfig.ID,
ProviderID: externalAuthConfig.ID,
UserID: apiKey.UserID,
CreatedAt: dbtime.Now(),
UpdatedAt: dbtime.Now(),
@@ -221,14 +221,14 @@ func (api *API) gitAuthCallback(gitAuthConfig *gitauth.Config) http.HandlerFunc
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Failed to insert git auth link.",
Message: "Failed to insert external auth link.",
Detail: err.Error(),
})
return
}
} else {
_, err = api.Database.UpdateExternalAuthLink(ctx, database.UpdateExternalAuthLinkParams{
ProviderID: gitAuthConfig.ID,
ProviderID: externalAuthConfig.ID,
UserID: apiKey.UserID,
UpdatedAt: dbtime.Now(),
OAuthAccessToken: state.Token.AccessToken,
@@ -237,7 +237,7 @@ func (api *API) gitAuthCallback(gitAuthConfig *gitauth.Config) http.HandlerFunc
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Failed to update git auth link.",
Message: "Failed to update external auth link.",
Detail: err.Error(),
})
return
@@ -247,7 +247,7 @@ func (api *API) gitAuthCallback(gitAuthConfig *gitauth.Config) http.HandlerFunc
redirect := state.Redirect
if redirect == "" {
// This is a nicely rendered screen on the frontend
redirect = fmt.Sprintf("/gitauth/%s", gitAuthConfig.ID)
redirect = fmt.Sprintf("/externalauth/%s", externalAuthConfig.ID)
}
http.Redirect(rw, r, redirect, http.StatusTemporaryRedirect)
}
@@ -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)
-9
View File
@@ -1,9 +0,0 @@
package gitauth_test
import (
"testing"
)
func TestOAuthJWTConfig(t *testing.T) {
t.Parallel()
}
+40
View File
@@ -0,0 +1,40 @@
package httpmw
import (
"context"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/coder/coder/v2/coderd/externalauth"
"github.com/coder/coder/v2/coderd/httpapi"
)
type externalAuthParamContextKey struct{}
func ExternalAuthParam(r *http.Request) *externalauth.Config {
config, ok := r.Context().Value(externalAuthParamContextKey{}).(*externalauth.Config)
if !ok {
panic("developer error: external auth param middleware not provided")
}
return config
}
func ExtractExternalAuthParam(configs []*externalauth.Config) func(next http.Handler) http.Handler {
configByID := make(map[string]*externalauth.Config)
for _, c := range configs {
configByID[c.ID] = c
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
config, ok := configByID[chi.URLParam(r, "externalauth")]
if !ok {
httpapi.ResourceNotFound(w)
return
}
r = r.WithContext(context.WithValue(r.Context(), externalAuthParamContextKey{}, config))
next.ServeHTTP(w, r)
})
}
}
@@ -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)
})
-40
View File
@@ -1,40 +0,0 @@
package httpmw
import (
"context"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/coder/coder/v2/coderd/gitauth"
"github.com/coder/coder/v2/coderd/httpapi"
)
type gitAuthParamContextKey struct{}
func GitAuthParam(r *http.Request) *gitauth.Config {
config, ok := r.Context().Value(gitAuthParamContextKey{}).(*gitauth.Config)
if !ok {
panic("developer error: gitauth param middleware not provided")
}
return config
}
func ExtractGitAuthParam(configs []*gitauth.Config) func(next http.Handler) http.Handler {
configByID := make(map[string]*gitauth.Config)
for _, c := range configs {
configByID[c.ID] = c
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
config, ok := configByID[chi.URLParam(r, "gitauth")]
if !ok {
httpapi.ResourceNotFound(w)
return
}
r = r.WithContext(context.WithValue(r.Context(), gitAuthParamContextKey{}, config))
next.ServeHTTP(w, r)
})
}
}
@@ -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()
+9 -9
View File
@@ -22,7 +22,7 @@ import (
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/database/provisionerjobs"
"github.com/coder/coder/v2/coderd/gitauth"
"github.com/coder/coder/v2/coderd/externalauth"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/httpmw"
"github.com/coder/coder/v2/coderd/parameter"
@@ -273,15 +273,15 @@ func (api *API) templateVersionRichParameters(rw http.ResponseWriter, r *http.Re
httpapi.Write(ctx, rw, http.StatusOK, templateVersionParameters)
}
// @Summary Get git auth by template version
// @ID get-git-auth-by-template-version
// @Summary Get external auth by template version
// @ID get-external-auth-by-template-version
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param templateversion path string true "Template version ID" format(uuid)
// @Success 200 {array} codersdk.TemplateVersionExternalAuth
// @Router /templateversions/{templateversion}/gitauth [get]
func (api *API) templateVersionGitAuth(rw http.ResponseWriter, r *http.Request) {
// @Router /templateversions/{templateversion}/externalauth [get]
func (api *API) templateVersionExternalAuth(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var (
apiKey = httpmw.APIKey(r)
@@ -291,7 +291,7 @@ func (api *API) templateVersionGitAuth(rw http.ResponseWriter, r *http.Request)
rawProviders := templateVersion.ExternalAuthProviders
providers := make([]codersdk.TemplateVersionExternalAuth, 0)
for _, rawProvider := range rawProviders {
var config *gitauth.Config
var config *externalauth.Config
for _, provider := range api.ExternalAuthConfigs {
if provider.ID == rawProvider {
config = provider
@@ -307,7 +307,7 @@ func (api *API) templateVersionGitAuth(rw http.ResponseWriter, r *http.Request)
}
// This is the URL that will redirect the user with a state token.
redirectURL, err := api.AccessURL.Parse(fmt.Sprintf("/gitauth/%s", config.ID))
redirectURL, err := api.AccessURL.Parse(fmt.Sprintf("/externalauth/%s", config.ID))
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to parse access URL.",
@@ -333,7 +333,7 @@ func (api *API) templateVersionGitAuth(rw http.ResponseWriter, r *http.Request)
}
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching git auth link.",
Message: "Internal error fetching external auth link.",
Detail: err.Error(),
})
return
@@ -342,7 +342,7 @@ func (api *API) templateVersionGitAuth(rw http.ResponseWriter, r *http.Request)
_, updated, err := config.RefreshToken(ctx, api.Database, authLink)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to refresh git auth token.",
Message: "Failed to refresh external auth token.",
Detail: err.Error(),
})
return
+5 -5
View File
@@ -16,7 +16,7 @@ import (
"github.com/coder/coder/v2/coderd/audit"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/gitauth"
"github.com/coder/coder/v2/coderd/externalauth"
"github.com/coder/coder/v2/coderd/provisionerdserver"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/codersdk"
@@ -319,7 +319,7 @@ func TestPatchCancelTemplateVersion(t *testing.T) {
})
}
func TestTemplateVersionsGitAuth(t *testing.T) {
func TestTemplateVersionsExternalAuth(t *testing.T) {
t.Parallel()
t.Run("Empty", func(t *testing.T) {
t.Parallel()
@@ -338,7 +338,7 @@ func TestTemplateVersionsGitAuth(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
ExternalAuthConfigs: []*gitauth.Config{{
ExternalAuthConfigs: []*externalauth.Config{{
OAuth2Config: &testutil.OAuth2Config{},
ID: "github",
Regex: regexp.MustCompile(`github\.com`),
@@ -351,7 +351,7 @@ func TestTemplateVersionsGitAuth(t *testing.T) {
ProvisionPlan: []*proto.Response{{
Type: &proto.Response_Plan{
Plan: &proto.PlanComplete{
GitAuthProviders: []string{"github"},
ExternalAuthProviders: []string{"github"},
},
},
}},
@@ -368,7 +368,7 @@ func TestTemplateVersionsGitAuth(t *testing.T) {
require.False(t, providers[0].Authenticated)
// Perform the Git auth callback to authenticate the user...
resp := coderdtest.RequestGitAuthCallback(t, "github", client)
resp := coderdtest.RequestExternalAuthCallback(t, "github", client)
_ = resp.Body.Close()
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
+1 -1
View File
@@ -26,7 +26,7 @@ func Middleware(tracerProvider trace.TracerProvider) func(http.Handler) http.Han
"/api/**",
"/@*/*/apps/**",
"/%40*/*/apps/**",
"/gitauth/*/callback",
"/externalauth/*/callback",
}.MustCompile()
var tracer trace.Tracer
+1 -1
View File
@@ -59,7 +59,7 @@ func Test_Middleware(t *testing.T) {
{"/%40hi/hi/apps/hi", true},
{"/%40hi/hi/apps/hi/hi", true},
{"/%40hi/hi/apps/hi/hi", true},
{"/gitauth/hi/callback", true},
{"/externalauth/hi/callback", true},
// Other routes that should not be collected.
{"/index.html", false},
+24 -24
View File
@@ -34,7 +34,7 @@ import (
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/gitauth"
"github.com/coder/coder/v2/coderd/externalauth"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/httpmw"
"github.com/coder/coder/v2/coderd/rbac"
@@ -2178,25 +2178,25 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request)
// new token to be issued!
listen := r.URL.Query().Has("listen")
var gitAuthConfig *gitauth.Config
var externalAuthConfig *externalauth.Config
for _, gitAuth := range api.ExternalAuthConfigs {
matches := gitAuth.Regex.MatchString(gitURL)
if !matches {
continue
}
gitAuthConfig = gitAuth
externalAuthConfig = gitAuth
}
if gitAuthConfig == nil {
detail := "No git providers are configured."
if externalAuthConfig == nil {
detail := "No external auth providers are configured."
if len(api.ExternalAuthConfigs) > 0 {
regexURLs := make([]string, 0, len(api.ExternalAuthConfigs))
for _, gitAuth := range api.ExternalAuthConfigs {
regexURLs = append(regexURLs, fmt.Sprintf("%s=%q", gitAuth.ID, gitAuth.Regex.String()))
for _, extAuth := range api.ExternalAuthConfigs {
regexURLs = append(regexURLs, fmt.Sprintf("%s=%q", extAuth.ID, extAuth.Regex.String()))
}
detail = fmt.Sprintf("The configured git provider have regex filters that do not match the git url. Provider url regexs: %s", strings.Join(regexURLs, ","))
detail = fmt.Sprintf("The configured external auth provider have regex filters that do not match the git url. Provider url regexs: %s", strings.Join(regexURLs, ","))
}
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
Message: fmt.Sprintf("No matching git provider found in Coder for the url %q.", gitURL),
Message: fmt.Sprintf("No matching external auth provider found in Coder for the url %q.", gitURL),
Detail: detail,
})
return
@@ -2239,8 +2239,8 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request)
return
case <-ticker.C:
}
gitAuthLink, err := api.Database.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{
ProviderID: gitAuthConfig.ID,
externalAuthLink, err := api.Database.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{
ProviderID: externalAuthConfig.ID,
UserID: workspace.OwnerID,
})
if err != nil {
@@ -2248,7 +2248,7 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request)
continue
}
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to get git auth link.",
Message: "Failed to get external auth link.",
Detail: err.Error(),
})
return
@@ -2258,27 +2258,27 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request)
// to expire.
// See
// https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app.
if gitAuthLink.OAuthExpiry.Before(dbtime.Now()) && !gitAuthLink.OAuthExpiry.IsZero() {
if externalAuthLink.OAuthExpiry.Before(dbtime.Now()) && !externalAuthLink.OAuthExpiry.IsZero() {
continue
}
valid, _, err := gitAuthConfig.ValidateToken(ctx, gitAuthLink.OAuthAccessToken)
valid, _, err := externalAuthConfig.ValidateToken(ctx, externalAuthLink.OAuthAccessToken)
if err != nil {
api.Logger.Warn(ctx, "failed to validate git auth token",
api.Logger.Warn(ctx, "failed to validate external auth token",
slog.F("workspace_owner_id", workspace.OwnerID.String()),
slog.F("validate_url", gitAuthConfig.ValidateURL),
slog.F("validate_url", externalAuthConfig.ValidateURL),
slog.Error(err),
)
}
if !valid {
continue
}
httpapi.Write(ctx, rw, http.StatusOK, formatGitAuthAccessToken(gitAuthConfig.Type, gitAuthLink.OAuthAccessToken))
httpapi.Write(ctx, rw, http.StatusOK, formatGitAuthAccessToken(externalAuthConfig.Type, externalAuthLink.OAuthAccessToken))
return
}
}
// This is the URL that will redirect the user with a state token.
redirectURL, err := api.AccessURL.Parse(fmt.Sprintf("/gitauth/%s", gitAuthConfig.ID))
redirectURL, err := api.AccessURL.Parse(fmt.Sprintf("/externalauth/%s", externalAuthConfig.ID))
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to parse access URL.",
@@ -2287,14 +2287,14 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request)
return
}
gitAuthLink, err := api.Database.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{
ProviderID: gitAuthConfig.ID,
externalAuthLink, err := api.Database.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{
ProviderID: externalAuthConfig.ID,
UserID: workspace.OwnerID,
})
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to get git auth link.",
Message: "Failed to get external auth link.",
Detail: err.Error(),
})
return
@@ -2306,10 +2306,10 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request)
return
}
gitAuthLink, updated, err := gitAuthConfig.RefreshToken(ctx, api.Database, gitAuthLink)
externalAuthLink, updated, err := externalAuthConfig.RefreshToken(ctx, api.Database, externalAuthLink)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to refresh git auth token.",
Message: "Failed to refresh external auth token.",
Detail: err.Error(),
})
return
@@ -2320,7 +2320,7 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request)
})
return
}
httpapi.Write(ctx, rw, http.StatusOK, formatGitAuthAccessToken(gitAuthConfig.Type, gitAuthLink.OAuthAccessToken))
httpapi.Write(ctx, rw, http.StatusOK, formatGitAuthAccessToken(externalAuthConfig.Type, externalAuthLink.OAuthAccessToken))
}
// Provider types have different username/password formats.