mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
8fefd91e4a
**Breaking Change:** Existing oauth apps might now use PKCE. If an unknown IdP type was being used, and it does not support PKCE, it will break. To fix, set the PKCE methods on the external auth to `none` ``` export CODER_EXTERNAL_AUTH_1_PKCE_METHODS=none ```
210 lines
8.0 KiB
Go
210 lines
8.0 KiB
Go
package codersdk
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// EnhancedExternalAuthProvider is a constant that represents enhanced
|
|
// support for a type of external authentication. All of the Git providers
|
|
// are examples of enhanced, because they support intercepting "git clone".
|
|
type EnhancedExternalAuthProvider string
|
|
|
|
func (e EnhancedExternalAuthProvider) String() string {
|
|
return string(e)
|
|
}
|
|
|
|
// Git returns whether the provider is a Git provider.
|
|
func (e EnhancedExternalAuthProvider) Git() bool {
|
|
switch e {
|
|
case EnhancedExternalAuthProviderGitHub,
|
|
EnhancedExternalAuthProviderGitLab,
|
|
EnhancedExternalAuthProviderBitBucketCloud,
|
|
EnhancedExternalAuthProviderBitBucketServer,
|
|
EnhancedExternalAuthProviderAzureDevops,
|
|
EnhancedExternalAuthProviderAzureDevopsEntra,
|
|
EnhancedExternalAuthProviderGitea:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
const (
|
|
EnhancedExternalAuthProviderAzureDevops EnhancedExternalAuthProvider = "azure-devops"
|
|
// Authenticate to ADO using an app registration in Entra ID
|
|
EnhancedExternalAuthProviderAzureDevopsEntra EnhancedExternalAuthProvider = "azure-devops-entra"
|
|
EnhancedExternalAuthProviderGitHub EnhancedExternalAuthProvider = "github"
|
|
EnhancedExternalAuthProviderGitLab EnhancedExternalAuthProvider = "gitlab"
|
|
// EnhancedExternalAuthProviderBitBucketCloud is the Bitbucket Cloud provider.
|
|
// Not to be confused with the self-hosted 'EnhancedExternalAuthProviderBitBucketServer'
|
|
EnhancedExternalAuthProviderBitBucketCloud EnhancedExternalAuthProvider = "bitbucket-cloud"
|
|
EnhancedExternalAuthProviderBitBucketServer EnhancedExternalAuthProvider = "bitbucket-server"
|
|
EnhancedExternalAuthProviderSlack EnhancedExternalAuthProvider = "slack"
|
|
EnhancedExternalAuthProviderJFrog EnhancedExternalAuthProvider = "jfrog"
|
|
EnhancedExternalAuthProviderGitea EnhancedExternalAuthProvider = "gitea"
|
|
)
|
|
|
|
type ExternalAuth struct {
|
|
Authenticated bool `json:"authenticated"`
|
|
Device bool `json:"device"`
|
|
DisplayName string `json:"display_name"`
|
|
SupportsRevocation bool `json:"supports_revocation"`
|
|
|
|
// User is the user that authenticated with the provider.
|
|
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 []ExternalAuthAppInstallation `json:"installations"`
|
|
// AppInstallURL is the URL to install the app.
|
|
AppInstallURL string `json:"app_install_url"`
|
|
}
|
|
|
|
type ListUserExternalAuthResponse struct {
|
|
Providers []ExternalAuthLinkProvider `json:"providers"`
|
|
// Links are all the authenticated links for the user.
|
|
// If a link has a provider ID that does not exist, then that provider
|
|
// is no longer configured, rendering it unusable. It is still valuable
|
|
// to include these links so that the user can unlink them.
|
|
Links []ExternalAuthLink `json:"links"`
|
|
}
|
|
|
|
type DeleteExternalAuthByIDResponse struct {
|
|
// TokenRevoked set to true if token revocation was attempted and was successful
|
|
TokenRevoked bool `json:"token_revoked"`
|
|
TokenRevocationError string `json:"token_revocation_error,omitempty"`
|
|
}
|
|
|
|
// ExternalAuthLink is a link between a user and an external auth provider.
|
|
// It excludes information that requires a token to access, so can be statically
|
|
// built from the database and configs.
|
|
type ExternalAuthLink struct {
|
|
ProviderID string `json:"provider_id"`
|
|
CreatedAt time.Time `json:"created_at" format:"date-time"`
|
|
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
|
|
HasRefreshToken bool `json:"has_refresh_token"`
|
|
Expires time.Time `json:"expires" format:"date-time"`
|
|
Authenticated bool `json:"authenticated"`
|
|
ValidateError string `json:"validate_error"`
|
|
}
|
|
|
|
// ExternalAuthLinkProvider are the static details of a provider.
|
|
type ExternalAuthLinkProvider struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Device bool `json:"device"`
|
|
DisplayName string `json:"display_name"`
|
|
DisplayIcon string `json:"display_icon"`
|
|
AllowRefresh bool `json:"allow_refresh"`
|
|
AllowValidate bool `json:"allow_validate"`
|
|
SupportsRevocation bool `json:"supports_revocation"`
|
|
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
|
|
}
|
|
|
|
type ExternalAuthAppInstallation struct {
|
|
ID int `json:"id"`
|
|
Account ExternalAuthUser `json:"account"`
|
|
ConfigureURL string `json:"configure_url"`
|
|
}
|
|
|
|
type ExternalAuthUser struct {
|
|
ID int64 `json:"id"`
|
|
Login string `json:"login"`
|
|
AvatarURL string `json:"avatar_url"`
|
|
ProfileURL string `json:"profile_url"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// ExternalAuthDevice is the response from the device authorization endpoint.
|
|
// See: https://tools.ietf.org/html/rfc8628#section-3.2
|
|
type ExternalAuthDevice struct {
|
|
DeviceCode string `json:"device_code"`
|
|
UserCode string `json:"user_code"`
|
|
VerificationURI string `json:"verification_uri"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
Interval int `json:"interval"`
|
|
}
|
|
|
|
type ExternalAuthDeviceExchange struct {
|
|
DeviceCode string `json:"device_code"`
|
|
}
|
|
|
|
func (c *Client) ExternalAuthDeviceByID(ctx context.Context, provider string) (ExternalAuthDevice, error) {
|
|
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/external-auth/%s/device", provider), nil)
|
|
if err != nil {
|
|
return ExternalAuthDevice{}, err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusOK {
|
|
return ExternalAuthDevice{}, ReadBodyAsError(res)
|
|
}
|
|
var extAuth ExternalAuthDevice
|
|
return extAuth, json.NewDecoder(res.Body).Decode(&extAuth)
|
|
}
|
|
|
|
// 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/external-auth/%s/device", provider), req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusNoContent {
|
|
return ReadBodyAsError(res)
|
|
}
|
|
return 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/external-auth/%s", provider), nil)
|
|
if err != nil {
|
|
return ExternalAuth{}, err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusOK {
|
|
return ExternalAuth{}, ReadBodyAsError(res)
|
|
}
|
|
var extAuth ExternalAuth
|
|
return extAuth, json.NewDecoder(res.Body).Decode(&extAuth)
|
|
}
|
|
|
|
// UnlinkExternalAuthByID deletes the external auth for the given provider by ID
|
|
// for the user. This does not revoke the token from the IDP.
|
|
func (c *Client) UnlinkExternalAuthByID(ctx context.Context, provider string) (DeleteExternalAuthByIDResponse, error) {
|
|
noRevoke := DeleteExternalAuthByIDResponse{TokenRevoked: false}
|
|
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/external-auth/%s", provider), nil)
|
|
if err != nil {
|
|
return noRevoke, err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusOK {
|
|
return noRevoke, ReadBodyAsError(res)
|
|
}
|
|
var resp DeleteExternalAuthByIDResponse
|
|
err = json.NewDecoder(res.Body).Decode(&resp)
|
|
if err != nil {
|
|
return noRevoke, err
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// ListExternalAuths returns the available external auth providers and the user's
|
|
// authenticated links if they exist.
|
|
func (c *Client) ListExternalAuths(ctx context.Context) (ListUserExternalAuthResponse, error) {
|
|
res, err := c.Request(ctx, http.MethodGet, "/api/v2/external-auth", nil)
|
|
if err != nil {
|
|
return ListUserExternalAuthResponse{}, err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode != http.StatusOK {
|
|
return ListUserExternalAuthResponse{}, ReadBodyAsError(res)
|
|
}
|
|
var extAuth ListUserExternalAuthResponse
|
|
return extAuth, json.NewDecoder(res.Body).Decode(&extAuth)
|
|
}
|