Files
coder/codersdk/oauth2.go
T
Ehab Younes 6683d807ac refactor: add RFC-compliant enum types and use SDK as source of truth (#21468)
Add comprehensive OAuth2 enum types to codersdk following RFC specifications:
- OAuth2ProviderGrantType (RFC 6749)
- OAuth2ProviderResponseType (RFC 6749)
- OAuth2TokenEndpointAuthMethod (RFC 7591)
- OAuth2PKCECodeChallengeMethod (RFC 7636)
- OAuth2TokenType (RFC 6749, RFC 9449)
- OAuth2RevocationTokenTypeHint (RFC 7009)
- OAuth2ErrorCode (RFC 6749, RFC 7009, RFC 8707)

Add OAuth2TokenRequest, OAuth2TokenResponse, OAuth2TokenRevocationRequest,
and OAuth2Error structs to the SDK. Update OAuth2ClientRegistrationRequest,
OAuth2ClientRegistrationResponse, OAuth2ClientConfiguration, and
OAuth2AuthorizationServerMetadata to use typed enums instead of raw strings.

This makes codersdk the single source of truth for OAuth2 types, eliminating
duplication between SDK and server-side structs.

Closes #21476
2026-01-15 12:41:28 +03:00

653 lines
26 KiB
Go

package codersdk
import (
"context"
"crypto/sha256"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"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"`
// Endpoints are included in the app response for easier discovery. The OAuth2
// spec does not have a defined place to find these (for comparison, OIDC has
// a '/.well-known/openid-configuration' endpoint).
Endpoints OAuth2AppEndpoints `json:"endpoints"`
}
type OAuth2AppEndpoints struct {
Authorization string `json:"authorization"`
Token string `json:"token"`
TokenRevoke string `json:"token_revoke"`
// DeviceAuth is optional.
DeviceAuth string `json:"device_authorization"`
}
type OAuth2ProviderAppFilter struct {
UserID uuid.UUID `json:"user_id,omitempty" format:"uuid"`
}
// OAuth2ProviderApps returns the applications configured to authenticate using
// Coder as an OAuth2 provider.
func (c *Client) OAuth2ProviderApps(ctx context.Context, filter OAuth2ProviderAppFilter) ([]OAuth2ProviderApp, error) {
res, err := c.Request(ctx, http.MethodGet, "/api/v2/oauth2-provider/apps", nil,
func(r *http.Request) {
if filter.UserID != uuid.Nil {
q := r.URL.Query()
q.Set("user_id", filter.UserID.String())
r.URL.RawQuery = q.Encode()
}
})
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.StatusCreated {
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
}
type OAuth2ProviderGrantType string
// OAuth2ProviderGrantType values (RFC 6749).
const (
OAuth2ProviderGrantTypeAuthorizationCode OAuth2ProviderGrantType = "authorization_code"
OAuth2ProviderGrantTypeRefreshToken OAuth2ProviderGrantType = "refresh_token"
OAuth2ProviderGrantTypePassword OAuth2ProviderGrantType = "password"
OAuth2ProviderGrantTypeClientCredentials OAuth2ProviderGrantType = "client_credentials"
OAuth2ProviderGrantTypeImplicit OAuth2ProviderGrantType = "implicit"
)
func (e OAuth2ProviderGrantType) Valid() bool {
switch e {
case OAuth2ProviderGrantTypeAuthorizationCode,
OAuth2ProviderGrantTypeRefreshToken,
OAuth2ProviderGrantTypePassword,
OAuth2ProviderGrantTypeClientCredentials,
OAuth2ProviderGrantTypeImplicit:
return true
}
return false
}
type OAuth2ProviderResponseType string
// OAuth2ProviderResponseType values (RFC 6749).
const (
OAuth2ProviderResponseTypeCode OAuth2ProviderResponseType = "code"
OAuth2ProviderResponseTypeToken OAuth2ProviderResponseType = "token"
)
func (e OAuth2ProviderResponseType) Valid() bool {
switch e {
case OAuth2ProviderResponseTypeCode, OAuth2ProviderResponseTypeToken:
return true
}
return false
}
type OAuth2TokenEndpointAuthMethod string
const (
OAuth2TokenEndpointAuthMethodClientSecretBasic OAuth2TokenEndpointAuthMethod = "client_secret_basic"
OAuth2TokenEndpointAuthMethodClientSecretPost OAuth2TokenEndpointAuthMethod = "client_secret_post"
OAuth2TokenEndpointAuthMethodNone OAuth2TokenEndpointAuthMethod = "none"
)
func (m OAuth2TokenEndpointAuthMethod) Valid() bool {
switch m {
case OAuth2TokenEndpointAuthMethodClientSecretBasic,
OAuth2TokenEndpointAuthMethodClientSecretPost,
OAuth2TokenEndpointAuthMethodNone:
return true
}
return false
}
type OAuth2PKCECodeChallengeMethod string
// OAuth2PKCECodeChallengeMethod values (RFC 7636).
const (
OAuth2PKCECodeChallengeMethodS256 OAuth2PKCECodeChallengeMethod = "S256"
OAuth2PKCECodeChallengeMethodPlain OAuth2PKCECodeChallengeMethod = "plain"
)
func (m OAuth2PKCECodeChallengeMethod) Valid() bool {
switch m {
case OAuth2PKCECodeChallengeMethodS256, OAuth2PKCECodeChallengeMethodPlain:
return true
}
return false
}
type OAuth2TokenType string
// OAuth2TokenType values (RFC 6749, RFC 9449).
const (
OAuth2TokenTypeBearer OAuth2TokenType = "Bearer"
OAuth2TokenTypeDPoP OAuth2TokenType = "DPoP"
)
func (t OAuth2TokenType) Valid() bool {
switch t {
case OAuth2TokenTypeBearer, OAuth2TokenTypeDPoP:
return true
}
return false
}
type OAuth2RevocationTokenTypeHint string
const (
OAuth2RevocationTokenTypeHintAccessToken OAuth2RevocationTokenTypeHint = "access_token"
OAuth2RevocationTokenTypeHintRefreshToken OAuth2RevocationTokenTypeHint = "refresh_token"
)
func (h OAuth2RevocationTokenTypeHint) Valid() bool {
switch h {
case OAuth2RevocationTokenTypeHintAccessToken, OAuth2RevocationTokenTypeHintRefreshToken:
return true
}
return false
}
type OAuth2ErrorCode string
// OAuth2 error codes per RFC 6749, RFC 7009, RFC 8707.
// This is not comprehensive; it includes only codes relevant to this implementation.
const (
// RFC 6749 - Token endpoint errors.
OAuth2ErrorCodeInvalidRequest OAuth2ErrorCode = "invalid_request"
OAuth2ErrorCodeInvalidClient OAuth2ErrorCode = "invalid_client"
OAuth2ErrorCodeInvalidGrant OAuth2ErrorCode = "invalid_grant"
OAuth2ErrorCodeUnauthorizedClient OAuth2ErrorCode = "unauthorized_client"
OAuth2ErrorCodeUnsupportedGrantType OAuth2ErrorCode = "unsupported_grant_type"
OAuth2ErrorCodeInvalidScope OAuth2ErrorCode = "invalid_scope"
// RFC 6749 - Authorization endpoint errors.
OAuth2ErrorCodeAccessDenied OAuth2ErrorCode = "access_denied"
OAuth2ErrorCodeUnsupportedResponseType OAuth2ErrorCode = "unsupported_response_type"
OAuth2ErrorCodeServerError OAuth2ErrorCode = "server_error"
OAuth2ErrorCodeTemporarilyUnavailable OAuth2ErrorCode = "temporarily_unavailable"
// RFC 7009 - Token revocation errors.
OAuth2ErrorCodeUnsupportedTokenType OAuth2ErrorCode = "unsupported_token_type"
// RFC 8707 - Resource indicator errors.
OAuth2ErrorCodeInvalidTarget OAuth2ErrorCode = "invalid_target"
)
func (c OAuth2ErrorCode) Valid() bool {
switch c {
case OAuth2ErrorCodeInvalidRequest,
OAuth2ErrorCodeInvalidClient,
OAuth2ErrorCodeInvalidGrant,
OAuth2ErrorCodeUnauthorizedClient,
OAuth2ErrorCodeUnsupportedGrantType,
OAuth2ErrorCodeInvalidScope,
OAuth2ErrorCodeAccessDenied,
OAuth2ErrorCodeUnsupportedResponseType,
OAuth2ErrorCodeServerError,
OAuth2ErrorCodeTemporarilyUnavailable,
OAuth2ErrorCodeUnsupportedTokenType,
OAuth2ErrorCodeInvalidTarget:
return true
}
return false
}
// OAuth2Error represents an OAuth2-compliant error response per RFC 6749.
type OAuth2Error struct {
Error OAuth2ErrorCode `json:"error"`
ErrorDescription string `json:"error_description,omitempty"`
ErrorURI string `json:"error_uri,omitempty"`
}
// OAuth2TokenRequest represents a token request per RFC 6749. The actual wire
// format is application/x-www-form-urlencoded; this struct is for SDK docs.
type OAuth2TokenRequest struct {
GrantType OAuth2ProviderGrantType `json:"grant_type"`
Code string `json:"code,omitempty"`
RedirectURI string `json:"redirect_uri,omitempty"`
ClientID string `json:"client_id,omitempty"`
ClientSecret string `json:"client_secret,omitempty"`
CodeVerifier string `json:"code_verifier,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
Resource string `json:"resource,omitempty"`
Scope string `json:"scope,omitempty"`
}
// OAuth2TokenResponse represents a successful token response per RFC 6749.
type OAuth2TokenResponse struct {
AccessToken string `json:"access_token"`
TokenType OAuth2TokenType `json:"token_type"`
ExpiresIn int64 `json:"expires_in,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
Scope string `json:"scope,omitempty"`
// Expiry is not part of RFC 6749 but is included for compatibility with
// golang.org/x/oauth2.Token and clients that expect a timestamp.
Expiry *time.Time `json:"expiry,omitempty" format:"date-time"`
}
// OAuth2TokenRevocationRequest represents a token revocation request per RFC 7009.
type OAuth2TokenRevocationRequest struct {
Token string `json:"token"`
TokenTypeHint OAuth2RevocationTokenTypeHint `json:"token_type_hint,omitempty"`
ClientID string `json:"client_id,omitempty"`
ClientSecret string `json:"client_secret,omitempty"`
}
// RevokeOAuth2Token revokes a specific OAuth2 token using RFC 7009 token revocation.
func (c *Client) RevokeOAuth2Token(ctx context.Context, clientID uuid.UUID, token string) error {
form := url.Values{}
form.Set("token", token)
// Client authentication is handled via the client_id in the app middleware
form.Set("client_id", clientID.String())
res, err := c.Request(ctx, http.MethodPost, "/oauth2/revoke", strings.NewReader(form.Encode()), func(r *http.Request) {
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
})
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return ReadBodyAsError(res)
}
return nil
}
// RevokeOAuth2ProviderApp completely revokes an app's access for the
// authenticated user.
func (c *Client) RevokeOAuth2ProviderApp(ctx context.Context, appID uuid.UUID) error {
res, err := c.Request(ctx, http.MethodDelete, "/oauth2/tokens", nil, func(r *http.Request) {
q := r.URL.Query()
q.Set("client_id", appID.String())
r.URL.RawQuery = q.Encode()
})
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusNoContent {
return ReadBodyAsError(res)
}
return nil
}
type OAuth2DeviceFlowCallbackResponse struct {
RedirectURL string `json:"redirect_url"`
}
// OAuth2AuthorizationServerMetadata represents RFC 8414 OAuth 2.0 Authorization Server Metadata.
type OAuth2AuthorizationServerMetadata struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
ResponseTypesSupported []OAuth2ProviderResponseType `json:"response_types_supported"`
GrantTypesSupported []OAuth2ProviderGrantType `json:"grant_types_supported,omitempty"`
CodeChallengeMethodsSupported []OAuth2PKCECodeChallengeMethod `json:"code_challenge_methods_supported,omitempty"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
TokenEndpointAuthMethodsSupported []OAuth2TokenEndpointAuthMethod `json:"token_endpoint_auth_methods_supported,omitempty"`
}
// OAuth2ProtectedResourceMetadata represents RFC 9728 OAuth 2.0 Protected Resource Metadata
type OAuth2ProtectedResourceMetadata struct {
Resource string `json:"resource"`
AuthorizationServers []string `json:"authorization_servers"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
BearerMethodsSupported []string `json:"bearer_methods_supported,omitempty"`
}
// OAuth2ClientRegistrationRequest represents RFC 7591 Dynamic Client Registration Request.
type OAuth2ClientRegistrationRequest struct {
RedirectURIs []string `json:"redirect_uris,omitempty"`
ClientName string `json:"client_name,omitempty"`
ClientURI string `json:"client_uri,omitempty"`
LogoURI string `json:"logo_uri,omitempty"`
TOSURI string `json:"tos_uri,omitempty"`
PolicyURI string `json:"policy_uri,omitempty"`
JWKSURI string `json:"jwks_uri,omitempty"`
JWKS json.RawMessage `json:"jwks,omitempty" swaggertype:"object"`
SoftwareID string `json:"software_id,omitempty"`
SoftwareVersion string `json:"software_version,omitempty"`
SoftwareStatement string `json:"software_statement,omitempty"`
GrantTypes []OAuth2ProviderGrantType `json:"grant_types,omitempty"`
ResponseTypes []OAuth2ProviderResponseType `json:"response_types,omitempty"`
TokenEndpointAuthMethod OAuth2TokenEndpointAuthMethod `json:"token_endpoint_auth_method,omitempty"`
Scope string `json:"scope,omitempty"`
Contacts []string `json:"contacts,omitempty"`
}
func (req OAuth2ClientRegistrationRequest) ApplyDefaults() OAuth2ClientRegistrationRequest {
// Apply grant type defaults.
if len(req.GrantTypes) == 0 {
req.GrantTypes = []OAuth2ProviderGrantType{
OAuth2ProviderGrantTypeAuthorizationCode,
OAuth2ProviderGrantTypeRefreshToken,
}
}
// Apply response type defaults.
if len(req.ResponseTypes) == 0 {
req.ResponseTypes = []OAuth2ProviderResponseType{
OAuth2ProviderResponseTypeCode,
}
}
// Apply token endpoint auth method default (RFC 7591 section 2).
if req.TokenEndpointAuthMethod == "" {
// Default according to RFC 7591: "client_secret_basic" for confidential clients.
// For public clients, should be explicitly set to "none".
req.TokenEndpointAuthMethod = OAuth2TokenEndpointAuthMethodClientSecretBasic
}
// Apply client name default if not provided.
if req.ClientName == "" {
req.ClientName = "Dynamically Registered Client"
}
return req
}
// DetermineClientType determines if client is public or confidential
func (*OAuth2ClientRegistrationRequest) DetermineClientType() string {
// For now, default to confidential
// In the future, we might detect based on:
// - token_endpoint_auth_method == "none" -> public
// - application_type == "native" -> might be public
// - Other heuristics
return "confidential"
}
// GenerateClientName generates a client name if not provided
func (req *OAuth2ClientRegistrationRequest) GenerateClientName() string {
if req.ClientName != "" {
// Ensure client name fits database constraint (varchar(64))
if len(req.ClientName) > 64 {
// Preserve uniqueness by including a hash of the original name
hash := fmt.Sprintf("%x", sha256.Sum256([]byte(req.ClientName)))[:8]
maxPrefix := 64 - 1 - len(hash) // 1 for separator
return req.ClientName[:maxPrefix] + "-" + hash
}
return req.ClientName
}
// Try to derive from client_uri
if req.ClientURI != "" {
if uri, err := url.Parse(req.ClientURI); err == nil && uri.Host != "" {
name := fmt.Sprintf("Client (%s)", uri.Host)
if len(name) > 64 {
return name[:64]
}
return name
}
}
// Try to derive from first redirect URI
if len(req.RedirectURIs) > 0 {
if uri, err := url.Parse(req.RedirectURIs[0]); err == nil && uri.Host != "" {
name := fmt.Sprintf("Client (%s)", uri.Host)
if len(name) > 64 {
return name[:64]
}
return name
}
}
return "Dynamically Registered Client"
}
// OAuth2ClientRegistrationResponse represents RFC 7591 Dynamic Client Registration Response.
type OAuth2ClientRegistrationResponse struct {
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret,omitempty"`
ClientIDIssuedAt int64 `json:"client_id_issued_at,omitempty"`
ClientSecretExpiresAt int64 `json:"client_secret_expires_at,omitempty"`
RedirectURIs []string `json:"redirect_uris,omitempty"`
ClientName string `json:"client_name,omitempty"`
ClientURI string `json:"client_uri,omitempty"`
LogoURI string `json:"logo_uri,omitempty"`
TOSURI string `json:"tos_uri,omitempty"`
PolicyURI string `json:"policy_uri,omitempty"`
JWKSURI string `json:"jwks_uri,omitempty"`
JWKS json.RawMessage `json:"jwks,omitempty" swaggertype:"object"`
SoftwareID string `json:"software_id,omitempty"`
SoftwareVersion string `json:"software_version,omitempty"`
GrantTypes []OAuth2ProviderGrantType `json:"grant_types"`
ResponseTypes []OAuth2ProviderResponseType `json:"response_types"`
TokenEndpointAuthMethod OAuth2TokenEndpointAuthMethod `json:"token_endpoint_auth_method"`
Scope string `json:"scope,omitempty"`
Contacts []string `json:"contacts,omitempty"`
RegistrationAccessToken string `json:"registration_access_token"`
RegistrationClientURI string `json:"registration_client_uri"`
}
// PostOAuth2ClientRegistration dynamically registers a new OAuth2 client (RFC 7591)
func (c *Client) PostOAuth2ClientRegistration(ctx context.Context, req OAuth2ClientRegistrationRequest) (OAuth2ClientRegistrationResponse, error) {
res, err := c.Request(ctx, http.MethodPost, "/oauth2/register", req)
if err != nil {
return OAuth2ClientRegistrationResponse{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return OAuth2ClientRegistrationResponse{}, ReadBodyAsError(res)
}
var resp OAuth2ClientRegistrationResponse
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
// GetOAuth2ClientConfiguration retrieves client configuration (RFC 7592)
func (c *Client) GetOAuth2ClientConfiguration(ctx context.Context, clientID string, registrationAccessToken string) (OAuth2ClientConfiguration, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/oauth2/clients/%s", clientID), nil,
func(r *http.Request) {
r.Header.Set("Authorization", "Bearer "+registrationAccessToken)
})
if err != nil {
return OAuth2ClientConfiguration{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return OAuth2ClientConfiguration{}, ReadBodyAsError(res)
}
var resp OAuth2ClientConfiguration
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
// PutOAuth2ClientConfiguration updates client configuration (RFC 7592)
func (c *Client) PutOAuth2ClientConfiguration(ctx context.Context, clientID string, registrationAccessToken string, req OAuth2ClientRegistrationRequest) (OAuth2ClientConfiguration, error) {
res, err := c.Request(ctx, http.MethodPut, fmt.Sprintf("/oauth2/clients/%s", clientID), req,
func(r *http.Request) {
r.Header.Set("Authorization", "Bearer "+registrationAccessToken)
})
if err != nil {
return OAuth2ClientConfiguration{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return OAuth2ClientConfiguration{}, ReadBodyAsError(res)
}
var resp OAuth2ClientConfiguration
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
// DeleteOAuth2ClientConfiguration deletes client registration (RFC 7592)
func (c *Client) DeleteOAuth2ClientConfiguration(ctx context.Context, clientID string, registrationAccessToken string) error {
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/oauth2/clients/%s", clientID), nil,
func(r *http.Request) {
r.Header.Set("Authorization", "Bearer "+registrationAccessToken)
})
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusNoContent {
return ReadBodyAsError(res)
}
return nil
}
// OAuth2ClientConfiguration represents RFC 7592 Client Read Response.
type OAuth2ClientConfiguration struct {
ClientID string `json:"client_id"`
ClientIDIssuedAt int64 `json:"client_id_issued_at"`
ClientSecretExpiresAt int64 `json:"client_secret_expires_at,omitempty"`
RedirectURIs []string `json:"redirect_uris,omitempty"`
ClientName string `json:"client_name,omitempty"`
ClientURI string `json:"client_uri,omitempty"`
LogoURI string `json:"logo_uri,omitempty"`
TOSURI string `json:"tos_uri,omitempty"`
PolicyURI string `json:"policy_uri,omitempty"`
JWKSURI string `json:"jwks_uri,omitempty"`
JWKS json.RawMessage `json:"jwks,omitempty" swaggertype:"object"`
SoftwareID string `json:"software_id,omitempty"`
SoftwareVersion string `json:"software_version,omitempty"`
GrantTypes []OAuth2ProviderGrantType `json:"grant_types"`
ResponseTypes []OAuth2ProviderResponseType `json:"response_types"`
TokenEndpointAuthMethod OAuth2TokenEndpointAuthMethod `json:"token_endpoint_auth_method"`
Scope string `json:"scope,omitempty"`
Contacts []string `json:"contacts,omitempty"`
RegistrationAccessToken string `json:"registration_access_token,omitempty"`
RegistrationClientURI string `json:"registration_client_uri"`
}