mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add OAuth2 applications (#11197)
* Add database tables for OAuth2 applications These are applications that will be able to use OAuth2 to get an API key from Coder. * Add endpoints for managing OAuth2 applications These let you add, update, and remove OAuth2 applications. * Add frontend for managing OAuth2 applications
This commit is contained in:
@@ -50,6 +50,7 @@ const (
|
||||
FeatureExternalTokenEncryption FeatureName = "external_token_encryption"
|
||||
FeatureWorkspaceBatchActions FeatureName = "workspace_batch_actions"
|
||||
FeatureAccessControl FeatureName = "access_control"
|
||||
FeatureOAuth2Provider FeatureName = "oauth2_provider"
|
||||
)
|
||||
|
||||
// FeatureNames must be kept in-sync with the Feature enum above.
|
||||
@@ -69,6 +70,7 @@ var FeatureNames = []FeatureName{
|
||||
FeatureExternalTokenEncryption,
|
||||
FeatureWorkspaceBatchActions,
|
||||
FeatureAccessControl,
|
||||
FeatureOAuth2Provider,
|
||||
}
|
||||
|
||||
// Humanize returns the feature name in a human-readable format.
|
||||
@@ -78,6 +80,8 @@ func (n FeatureName) Humanize() string {
|
||||
return "Template RBAC"
|
||||
case FeatureSCIM:
|
||||
return "SCIM"
|
||||
case FeatureOAuth2Provider:
|
||||
return "OAuth Provider"
|
||||
default:
|
||||
return strings.Title(strings.ReplaceAll(string(n), "_", " "))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
package codersdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type OAuth2ProviderApp struct {
|
||||
ID uuid.UUID `json:"id" format:"uuid"`
|
||||
Name string `json:"name"`
|
||||
CallbackURL string `json:"callback_url"`
|
||||
Icon string `json:"icon"`
|
||||
}
|
||||
|
||||
// OAuth2ProviderApps returns the applications configured to authenticate using
|
||||
// Coder as an OAuth2 provider.
|
||||
func (c *Client) OAuth2ProviderApps(ctx context.Context) ([]OAuth2ProviderApp, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, "/api/v2/oauth2-provider/apps", nil)
|
||||
if err != nil {
|
||||
return []OAuth2ProviderApp{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return []OAuth2ProviderApp{}, ReadBodyAsError(res)
|
||||
}
|
||||
var apps []OAuth2ProviderApp
|
||||
return apps, json.NewDecoder(res.Body).Decode(&apps)
|
||||
}
|
||||
|
||||
// OAuth2ProviderApp returns an application configured to authenticate using
|
||||
// Coder as an OAuth2 provider.
|
||||
func (c *Client) OAuth2ProviderApp(ctx context.Context, id uuid.UUID) (OAuth2ProviderApp, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/oauth2-provider/apps/%s", id), nil)
|
||||
if err != nil {
|
||||
return OAuth2ProviderApp{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return OAuth2ProviderApp{}, ReadBodyAsError(res)
|
||||
}
|
||||
var apps OAuth2ProviderApp
|
||||
return apps, json.NewDecoder(res.Body).Decode(&apps)
|
||||
}
|
||||
|
||||
type PostOAuth2ProviderAppRequest struct {
|
||||
Name string `json:"name" validate:"required,oauth2_app_name"`
|
||||
CallbackURL string `json:"callback_url" validate:"required,http_url"`
|
||||
Icon string `json:"icon" validate:"omitempty"`
|
||||
}
|
||||
|
||||
// PostOAuth2ProviderApp adds an application that can authenticate using Coder
|
||||
// as an OAuth2 provider.
|
||||
func (c *Client) PostOAuth2ProviderApp(ctx context.Context, app PostOAuth2ProviderAppRequest) (OAuth2ProviderApp, error) {
|
||||
res, err := c.Request(ctx, http.MethodPost, "/api/v2/oauth2-provider/apps", app)
|
||||
if err != nil {
|
||||
return OAuth2ProviderApp{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
return OAuth2ProviderApp{}, ReadBodyAsError(res)
|
||||
}
|
||||
var resp OAuth2ProviderApp
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
type PutOAuth2ProviderAppRequest struct {
|
||||
Name string `json:"name" validate:"required,oauth2_app_name"`
|
||||
CallbackURL string `json:"callback_url" validate:"required,http_url"`
|
||||
Icon string `json:"icon" validate:"omitempty"`
|
||||
}
|
||||
|
||||
// PutOAuth2ProviderApp updates an application that can authenticate using Coder
|
||||
// as an OAuth2 provider.
|
||||
func (c *Client) PutOAuth2ProviderApp(ctx context.Context, id uuid.UUID, app PutOAuth2ProviderAppRequest) (OAuth2ProviderApp, error) {
|
||||
res, err := c.Request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/oauth2-provider/apps/%s", id), app)
|
||||
if err != nil {
|
||||
return OAuth2ProviderApp{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return OAuth2ProviderApp{}, ReadBodyAsError(res)
|
||||
}
|
||||
var resp OAuth2ProviderApp
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
// DeleteOAuth2ProviderApp deletes an application, also invalidating any tokens
|
||||
// that were generated from it.
|
||||
func (c *Client) DeleteOAuth2ProviderApp(ctx context.Context, id uuid.UUID) error {
|
||||
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/oauth2-provider/apps/%s", id), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
return ReadBodyAsError(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type OAuth2ProviderAppSecretFull struct {
|
||||
ID uuid.UUID `json:"id" format:"uuid"`
|
||||
ClientSecretFull string `json:"client_secret_full"`
|
||||
}
|
||||
|
||||
type OAuth2ProviderAppSecret struct {
|
||||
ID uuid.UUID `json:"id" format:"uuid"`
|
||||
LastUsedAt NullTime `json:"last_used_at"`
|
||||
ClientSecretTruncated string `json:"client_secret_truncated"`
|
||||
}
|
||||
|
||||
// OAuth2ProviderAppSecrets returns the truncated secrets for an OAuth2
|
||||
// application.
|
||||
func (c *Client) OAuth2ProviderAppSecrets(ctx context.Context, appID uuid.UUID) ([]OAuth2ProviderAppSecret, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/oauth2-provider/apps/%s/secrets", appID), nil)
|
||||
if err != nil {
|
||||
return []OAuth2ProviderAppSecret{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return []OAuth2ProviderAppSecret{}, ReadBodyAsError(res)
|
||||
}
|
||||
var resp []OAuth2ProviderAppSecret
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
// PostOAuth2ProviderAppSecret creates a new secret for an OAuth2 application.
|
||||
// This is the only time the full secret will be revealed.
|
||||
func (c *Client) PostOAuth2ProviderAppSecret(ctx context.Context, appID uuid.UUID) (OAuth2ProviderAppSecretFull, error) {
|
||||
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/oauth2-provider/apps/%s/secrets", appID), nil)
|
||||
if err != nil {
|
||||
return OAuth2ProviderAppSecretFull{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return OAuth2ProviderAppSecretFull{}, ReadBodyAsError(res)
|
||||
}
|
||||
var resp OAuth2ProviderAppSecretFull
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
// DeleteOAuth2ProviderAppSecret deletes a secret from an OAuth2 application,
|
||||
// also invalidating any tokens that generated from it.
|
||||
func (c *Client) DeleteOAuth2ProviderAppSecret(ctx context.Context, appID uuid.UUID, secretID uuid.UUID) error {
|
||||
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/oauth2-provider/apps/%s/secrets/%s", appID, secretID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
return ReadBodyAsError(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user