Files
sonr/bridge/handlers/oauth2_provider.go
T
Prad NukalaandGitHub 13e6c3e84d Master (#1262)
* clear

* feat: Add everything

* fix: Commenht
2025-10-03 14:45:52 -04:00

924 lines
24 KiB
Go

package handlers
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"fmt"
"net/http"
"strings"
"sync"
"time"
"github.com/labstack/echo/v4"
"github.com/sonr-io/sonr/crypto/ucan"
)
// OAuth2Provider extends OIDCProvider with full OAuth2 capabilities
type OAuth2Provider struct {
*OIDCProvider
clientRegistry *ClientRegistry
scopeMapper *ScopeMapper
ucanDelegator *UCANDelegator
authCodeStore *AuthCodeStore
accessTokenStore *AccessTokenStore
refreshTokenStore *RefreshTokenStore
consentStore *ConsentStore
config *OAuth2Config
}
// AuthCodeStore manages authorization codes
type AuthCodeStore struct {
mu sync.RWMutex
codes map[string]*OAuth2AuthorizationCode
}
// AccessTokenStore manages access tokens
type AccessTokenStore struct {
mu sync.RWMutex
tokens map[string]*OAuth2AccessToken
}
// RefreshTokenStore manages refresh tokens
type RefreshTokenStore struct {
mu sync.RWMutex
tokens map[string]*OAuth2RefreshToken
}
// ConsentStore manages user consent records
type ConsentStore struct {
mu sync.RWMutex
consents map[string]*UserConsent // key: userDID:clientID
}
// UserConsent represents stored user consent
type UserConsent struct {
UserDID string `json:"user_did"`
ClientID string `json:"client_id"`
ApprovedScopes []string `json:"approved_scopes"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at"`
}
var oauth2Provider *OAuth2Provider
// InitializeOAuth2Provider initializes the OAuth2 provider
func InitializeOAuth2Provider() {
oauth2Provider = &OAuth2Provider{
OIDCProvider: oidcProvider,
clientRegistry: NewClientRegistry(),
scopeMapper: NewScopeMapper(),
ucanDelegator: NewUCANDelegator(nil),
authCodeStore: &AuthCodeStore{codes: make(map[string]*OAuth2AuthorizationCode)},
accessTokenStore: &AccessTokenStore{tokens: make(map[string]*OAuth2AccessToken)},
refreshTokenStore: &RefreshTokenStore{tokens: make(map[string]*OAuth2RefreshToken)},
consentStore: &ConsentStore{consents: make(map[string]*UserConsent)},
config: getDefaultOAuth2Config(),
}
// Start cleanup goroutine for expired tokens
go oauth2Provider.cleanupExpiredTokens()
}
// GetOAuth2Discovery returns OAuth2 discovery configuration
func GetOAuth2Discovery(c echo.Context) error {
if oauth2Provider == nil {
InitializeOAuth2Provider()
}
return c.JSON(http.StatusOK, oauth2Provider.config)
}
// HandleOAuth2Authorize handles OAuth2 authorization requests
func HandleOAuth2Authorize(c echo.Context) error {
if oauth2Provider == nil {
InitializeOAuth2Provider()
}
req := &OAuth2AuthorizationRequest{
ResponseType: c.QueryParam("response_type"),
ClientID: c.QueryParam("client_id"),
RedirectURI: c.QueryParam("redirect_uri"),
Scope: c.QueryParam("scope"),
State: c.QueryParam("state"),
CodeChallenge: c.QueryParam("code_challenge"),
CodeChallengeMethod: c.QueryParam("code_challenge_method"),
Nonce: c.QueryParam("nonce"),
Prompt: c.QueryParam("prompt"),
LoginHint: c.QueryParam("login_hint"),
}
// Validate client
client, err := oauth2Provider.clientRegistry.GetClient(req.ClientID)
if err != nil {
return oauth2Error(c, "invalid_client", "Unknown client", req.State)
}
// Validate redirect URI
if !client.ValidateRedirectURI(req.RedirectURI) {
return oauth2Error(c, "invalid_request", "Invalid redirect URI", req.State)
}
// Validate response type
if !isValidResponseType(req.ResponseType) {
return redirectError(
c,
req.RedirectURI,
"unsupported_response_type",
"Response type not supported",
req.State,
)
}
// Validate scopes
requestedScopes := parseScopes(req.Scope)
if !client.ValidateScopes(requestedScopes) {
return redirectError(
c,
req.RedirectURI,
"invalid_scope",
"Requested scope not allowed",
req.State,
)
}
// Validate PKCE for public clients
if client.ClientType == "public" && client.RequirePKCE {
if req.CodeChallenge == "" {
return redirectError(
c,
req.RedirectURI,
"invalid_request",
"PKCE required for public clients",
req.State,
)
}
if req.CodeChallengeMethod != PKCEMethodS256 {
return redirectError(
c,
req.RedirectURI,
"invalid_request",
"Only S256 PKCE method supported",
req.State,
)
}
}
// Check authentication
userDID := c.Get("user_did")
if userDID == nil {
// Store authorization request and redirect to authentication
sessionID := generateSessionID()
// TODO: Store auth request in session store
authURL := fmt.Sprintf(
"/auth/login?session_id=%s&return_to=%s",
sessionID,
c.Request().URL.String(),
)
return c.Redirect(http.StatusFound, authURL)
}
// Check consent
if client.RequiresConsent &&
!oauth2Provider.hasValidConsent(userDID.(string), req.ClientID, requestedScopes) {
// Render consent page
return renderOAuth2ConsentPage(c, req, client)
}
// Generate authorization code
code := generateSecureToken(32)
authCode := &OAuth2AuthorizationCode{
Code: code,
ClientID: req.ClientID,
UserDID: userDID.(string),
RedirectURI: req.RedirectURI,
Scopes: requestedScopes,
State: req.State,
Nonce: req.Nonce,
CodeChallenge: req.CodeChallenge,
CodeChallengeMethod: req.CodeChallengeMethod,
ExpiresAt: time.Now().Add(10 * time.Minute),
UCANContext: oauth2Provider.buildUCANContext(userDID.(string)),
}
// Store authorization code
oauth2Provider.authCodeStore.Store(authCode)
// Build redirect URL
redirectURL := buildAuthorizationRedirect(req.RedirectURI, code, req.State)
return c.Redirect(http.StatusFound, redirectURL)
}
// HandleOAuth2Token handles OAuth2 token requests
func HandleOAuth2Token(c echo.Context) error {
if oauth2Provider == nil {
InitializeOAuth2Provider()
}
var req OAuth2TokenRequest
if err := c.Bind(&req); err != nil {
return oauth2TokenError(c, "invalid_request", "Invalid token request")
}
// Authenticate client
client, err := oauth2Provider.authenticateClient(c, &req)
if err != nil {
return oauth2TokenError(c, "invalid_client", "Client authentication failed")
}
// Handle grant type
switch req.GrantType {
case "authorization_code":
return oauth2Provider.handleAuthorizationCodeGrant(c, client, &req)
case "refresh_token":
return oauth2Provider.handleRefreshTokenGrant(c, client, &req)
case "client_credentials":
return oauth2Provider.handleClientCredentialsGrant(c, client, &req)
default:
return oauth2TokenError(c, "unsupported_grant_type", "Grant type not supported")
}
}
// HandleOAuth2Introspection handles token introspection requests
func HandleOAuth2Introspection(c echo.Context) error {
if oauth2Provider == nil {
InitializeOAuth2Provider()
}
var req OAuth2IntrospectionRequest
if err := c.Bind(&req); err != nil {
return c.JSON(http.StatusBadRequest, &OAuth2IntrospectionResponse{Active: false})
}
// Authenticate client
client, err := oauth2Provider.authenticateClient(c, &OAuth2TokenRequest{
ClientID: req.ClientID,
ClientSecret: req.ClientSecret,
})
if err != nil {
return c.JSON(http.StatusUnauthorized, &OAuth2IntrospectionResponse{Active: false})
}
// Introspect token
response := oauth2Provider.introspectToken(req.Token, req.TokenTypeHint, client)
return c.JSON(http.StatusOK, response)
}
// HandleOAuth2Revocation handles token revocation requests
func HandleOAuth2Revocation(c echo.Context) error {
if oauth2Provider == nil {
InitializeOAuth2Provider()
}
var req OAuth2RevocationRequest
if err := c.Bind(&req); err != nil {
return c.NoContent(http.StatusBadRequest)
}
// Authenticate client
client, err := oauth2Provider.authenticateClient(c, &OAuth2TokenRequest{
ClientID: req.ClientID,
ClientSecret: req.ClientSecret,
})
if err != nil {
return c.NoContent(http.StatusUnauthorized)
}
// Revoke token
oauth2Provider.revokeToken(req.Token, req.TokenTypeHint, client)
return c.NoContent(http.StatusOK)
}
// Private methods
func (p *OAuth2Provider) handleAuthorizationCodeGrant(
c echo.Context,
client *OAuth2Client,
req *OAuth2TokenRequest,
) error {
// Retrieve authorization code
authCode := p.authCodeStore.Exchange(req.Code)
if authCode == nil {
return oauth2TokenError(c, "invalid_grant", "Invalid authorization code")
}
// Validate code hasn't expired
if time.Now().After(authCode.ExpiresAt) {
return oauth2TokenError(c, "invalid_grant", "Authorization code expired")
}
// Validate client
if authCode.ClientID != client.ClientID {
return oauth2TokenError(c, "invalid_grant", "Code was issued to different client")
}
// Validate redirect URI
if authCode.RedirectURI != req.RedirectURI {
return oauth2TokenError(c, "invalid_grant", "Redirect URI mismatch")
}
// Validate PKCE if present
if authCode.CodeChallenge != "" {
if !p.validatePKCE(req.CodeVerifier, authCode.CodeChallenge, authCode.CodeChallengeMethod) {
return oauth2TokenError(c, "invalid_grant", "Invalid PKCE verifier")
}
}
// Create UCAN delegation
ucanToken, err := p.ucanDelegator.CreateDelegation(
authCode.UserDID,
client.ClientID,
authCode.Scopes,
time.Now().Add(time.Hour),
)
if err != nil {
return oauth2TokenError(c, "server_error", "Failed to create delegation")
}
// Generate tokens
accessToken := p.generateAccessToken(authCode, ucanToken)
refreshToken := p.generateRefreshToken(authCode)
// Store tokens
p.accessTokenStore.Store(accessToken)
p.refreshTokenStore.Store(refreshToken)
// Generate ID token if openid scope present
var idToken string
if contains(authCode.Scopes, "openid") {
idToken, _ = generateIDToken(authCode.UserDID, client.ClientID, authCode.Nonce)
}
// Return token response
response := &OAuth2TokenResponse{
AccessToken: accessToken.Token,
TokenType: "Bearer",
ExpiresIn: 3600,
RefreshToken: refreshToken.Token,
Scope: strings.Join(authCode.Scopes, " "),
IDToken: idToken,
UCANToken: ucanToken.Raw,
}
return c.JSON(http.StatusOK, response)
}
func (p *OAuth2Provider) handleRefreshTokenGrant(
c echo.Context,
client *OAuth2Client,
req *OAuth2TokenRequest,
) error {
// Retrieve refresh token
oldRefreshToken := p.refreshTokenStore.Get(req.RefreshToken)
if oldRefreshToken == nil {
return oauth2TokenError(c, "invalid_grant", "Invalid refresh token")
}
// Validate client
if oldRefreshToken.ClientID != client.ClientID {
return oauth2TokenError(c, "invalid_grant", "Token was issued to different client")
}
// Validate expiration
if time.Now().After(oldRefreshToken.ExpiresAt) {
return oauth2TokenError(c, "invalid_grant", "Refresh token expired")
}
// Rotate refresh token
p.refreshTokenStore.Revoke(req.RefreshToken)
// Create new UCAN delegation
ucanToken, err := p.ucanDelegator.CreateDelegation(
oldRefreshToken.UserDID,
client.ClientID,
oldRefreshToken.Scopes,
time.Now().Add(time.Hour),
)
if err != nil {
return oauth2TokenError(c, "server_error", "Failed to create delegation")
}
// Generate new tokens
newAccessToken := &OAuth2AccessToken{
Token: generateSecureToken(32),
UserDID: oldRefreshToken.UserDID,
ClientID: client.ClientID,
Scopes: oldRefreshToken.Scopes,
ExpiresAt: time.Now().Add(time.Hour),
IssuedAt: time.Now(),
UCANToken: ucanToken,
}
newRefreshToken := &OAuth2RefreshToken{
Token: generateSecureToken(32),
AccessToken: newAccessToken.Token,
ClientID: client.ClientID,
UserDID: oldRefreshToken.UserDID,
Scopes: oldRefreshToken.Scopes,
ExpiresAt: time.Now().Add(30 * 24 * time.Hour),
IssuedAt: time.Now(),
RotationCount: oldRefreshToken.RotationCount + 1,
}
// Store new tokens
p.accessTokenStore.Store(newAccessToken)
p.refreshTokenStore.Store(newRefreshToken)
// Return response
response := &OAuth2TokenResponse{
AccessToken: newAccessToken.Token,
TokenType: "Bearer",
ExpiresIn: 3600,
RefreshToken: newRefreshToken.Token,
Scope: strings.Join(newAccessToken.Scopes, " "),
UCANToken: ucanToken.Raw,
}
return c.JSON(http.StatusOK, response)
}
func (p *OAuth2Provider) handleClientCredentialsGrant(
c echo.Context,
client *OAuth2Client,
req *OAuth2TokenRequest,
) error {
// Client credentials grant is only for confidential clients
if client.ClientType != "confidential" {
return oauth2TokenError(
c,
"unauthorized_client",
"Client type not authorized for this grant",
)
}
// Parse requested scopes
scopes := parseScopes(req.Scope)
if !client.ValidateScopes(scopes) {
return oauth2TokenError(c, "invalid_scope", "Requested scope not allowed")
}
// Create service-to-service UCAN token
ucanToken, err := p.ucanDelegator.CreateServiceDelegation(
client.ClientID,
scopes,
time.Now().Add(time.Hour),
)
if err != nil {
return oauth2TokenError(c, "server_error", "Failed to create delegation")
}
// Generate access token
accessToken := &OAuth2AccessToken{
Token: generateSecureToken(32),
UserDID: "", // No user for client credentials
ClientID: client.ClientID,
Scopes: scopes,
ExpiresAt: time.Now().Add(time.Hour),
IssuedAt: time.Now(),
UCANToken: ucanToken,
TokenType: "client_credentials",
}
// Store token
p.accessTokenStore.Store(accessToken)
// Return response
response := &OAuth2TokenResponse{
AccessToken: accessToken.Token,
TokenType: "Bearer",
ExpiresIn: 3600,
Scope: strings.Join(scopes, " "),
UCANToken: ucanToken.Raw,
}
return c.JSON(http.StatusOK, response)
}
func (p *OAuth2Provider) authenticateClient(
c echo.Context,
req *OAuth2TokenRequest,
) (*OAuth2Client, error) {
// Try Basic Auth first
if username, password, ok := c.Request().BasicAuth(); ok {
client, err := p.clientRegistry.GetClient(username)
if err != nil {
return nil, err
}
if client.ClientType == "confidential" &&
subtle.ConstantTimeCompare([]byte(client.ClientSecret), []byte(password)) == 1 {
return client, nil
}
return nil, fmt.Errorf("invalid client credentials")
}
// Try client_secret_post
if req.ClientID != "" && req.ClientSecret != "" {
client, err := p.clientRegistry.GetClient(req.ClientID)
if err != nil {
return nil, err
}
if client.ClientType == "confidential" &&
subtle.ConstantTimeCompare([]byte(client.ClientSecret), []byte(req.ClientSecret)) == 1 {
return client, nil
}
return nil, fmt.Errorf("invalid client credentials")
}
// Try client_assertion (JWT)
if req.ClientAssertion != "" &&
req.ClientAssertionType == "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" {
// TODO: Implement JWT client assertion validation
return nil, fmt.Errorf("JWT client assertion not yet implemented")
}
// Public client (no authentication)
if req.ClientID != "" {
client, err := p.clientRegistry.GetClient(req.ClientID)
if err != nil {
return nil, err
}
if client.ClientType == "public" {
return client, nil
}
}
return nil, fmt.Errorf("client authentication required")
}
func (p *OAuth2Provider) validatePKCE(verifier, challenge, method string) bool {
if method == "" {
method = PKCEMethodPlain
}
computed := computePKCEChallenge(verifier, method)
return subtle.ConstantTimeCompare([]byte(computed), []byte(challenge)) == 1
}
func (p *OAuth2Provider) hasValidConsent(userDID, clientID string, scopes []string) bool {
p.consentStore.mu.RLock()
defer p.consentStore.mu.RUnlock()
key := fmt.Sprintf("%s:%s", userDID, clientID)
consent, exists := p.consentStore.consents[key]
if !exists {
return false
}
// Check expiration
if time.Now().After(consent.ExpiresAt) {
return false
}
// Check all requested scopes are approved
for _, scope := range scopes {
if !contains(consent.ApprovedScopes, scope) {
return false
}
}
return true
}
func (p *OAuth2Provider) buildUCANContext(userDID string) *UCANAuthContext {
// TODO: Fetch actual vault and DID document data
return &UCANAuthContext{
VaultAddress: fmt.Sprintf("vault_%s", userDID),
EnclaveDataCID: fmt.Sprintf("cid_%s", userDID),
Capabilities: []string{"read", "write", "sign"},
}
}
func (p *OAuth2Provider) generateAccessToken(
authCode *OAuth2AuthorizationCode,
ucanToken *ucan.Token,
) *OAuth2AccessToken {
return &OAuth2AccessToken{
Token: generateSecureToken(32),
UserDID: authCode.UserDID,
ClientID: authCode.ClientID,
Scopes: authCode.Scopes,
ExpiresAt: time.Now().Add(time.Hour),
IssuedAt: time.Now(),
UCANToken: ucanToken,
SessionID: generateSessionID(),
TokenType: "authorization_code",
}
}
func (p *OAuth2Provider) generateRefreshToken(
authCode *OAuth2AuthorizationCode,
) *OAuth2RefreshToken {
return &OAuth2RefreshToken{
Token: generateSecureToken(32),
ClientID: authCode.ClientID,
UserDID: authCode.UserDID,
Scopes: authCode.Scopes,
ExpiresAt: time.Now().Add(30 * 24 * time.Hour),
IssuedAt: time.Now(),
RotationCount: 0,
}
}
func (p *OAuth2Provider) introspectToken(
token, tokenTypeHint string,
client *OAuth2Client,
) *OAuth2IntrospectionResponse {
// Try access token first
if accessToken := p.accessTokenStore.Get(token); accessToken != nil {
if accessToken.ClientID != client.ClientID {
return &OAuth2IntrospectionResponse{Active: false}
}
return &OAuth2IntrospectionResponse{
Active: time.Now().Before(accessToken.ExpiresAt),
Scope: strings.Join(accessToken.Scopes, " "),
ClientID: accessToken.ClientID,
Username: accessToken.UserDID,
TokenType: "Bearer",
ExpiresAt: accessToken.ExpiresAt.Unix(),
IssuedAt: accessToken.IssuedAt.Unix(),
Subject: accessToken.UserDID,
UCANToken: accessToken.UCANToken.Raw,
}
}
// Try refresh token
if refreshToken := p.refreshTokenStore.Get(token); refreshToken != nil {
if refreshToken.ClientID != client.ClientID {
return &OAuth2IntrospectionResponse{Active: false}
}
return &OAuth2IntrospectionResponse{
Active: time.Now().Before(refreshToken.ExpiresAt),
Scope: strings.Join(refreshToken.Scopes, " "),
ClientID: refreshToken.ClientID,
Username: refreshToken.UserDID,
TokenType: "refresh_token",
ExpiresAt: refreshToken.ExpiresAt.Unix(),
IssuedAt: refreshToken.IssuedAt.Unix(),
Subject: refreshToken.UserDID,
}
}
return &OAuth2IntrospectionResponse{Active: false}
}
func (p *OAuth2Provider) revokeToken(token, tokenTypeHint string, client *OAuth2Client) {
// Try to revoke as access token
if p.accessTokenStore.Revoke(token) {
return
}
// Try to revoke as refresh token
p.refreshTokenStore.Revoke(token)
}
func (p *OAuth2Provider) cleanupExpiredTokens() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for range ticker.C {
// Cleanup expired authorization codes
p.authCodeStore.CleanupExpired()
// Cleanup expired access tokens
p.accessTokenStore.CleanupExpired()
// Cleanup expired refresh tokens
p.refreshTokenStore.CleanupExpired()
}
}
// Store methods for token stores
func (s *AuthCodeStore) Store(code *OAuth2AuthorizationCode) {
s.mu.Lock()
defer s.mu.Unlock()
s.codes[code.Code] = code
}
func (s *AuthCodeStore) Exchange(code string) *OAuth2AuthorizationCode {
s.mu.Lock()
defer s.mu.Unlock()
authCode, exists := s.codes[code]
if !exists || authCode.Used {
return nil
}
authCode.Used = true
return authCode
}
func (s *AuthCodeStore) CleanupExpired() {
s.mu.Lock()
defer s.mu.Unlock()
now := time.Now()
for code, authCode := range s.codes {
if now.After(authCode.ExpiresAt) {
delete(s.codes, code)
}
}
}
func (s *AccessTokenStore) Store(token *OAuth2AccessToken) {
s.mu.Lock()
defer s.mu.Unlock()
s.tokens[token.Token] = token
}
func (s *AccessTokenStore) Get(token string) *OAuth2AccessToken {
s.mu.RLock()
defer s.mu.RUnlock()
return s.tokens[token]
}
func (s *AccessTokenStore) Revoke(token string) bool {
s.mu.Lock()
defer s.mu.Unlock()
if _, exists := s.tokens[token]; exists {
delete(s.tokens, token)
return true
}
return false
}
func (s *AccessTokenStore) CleanupExpired() {
s.mu.Lock()
defer s.mu.Unlock()
now := time.Now()
for token, accessToken := range s.tokens {
if now.After(accessToken.ExpiresAt) {
delete(s.tokens, token)
}
}
}
func (s *RefreshTokenStore) Store(token *OAuth2RefreshToken) {
s.mu.Lock()
defer s.mu.Unlock()
s.tokens[token.Token] = token
}
func (s *RefreshTokenStore) Get(token string) *OAuth2RefreshToken {
s.mu.RLock()
defer s.mu.RUnlock()
return s.tokens[token]
}
func (s *RefreshTokenStore) Revoke(token string) bool {
s.mu.Lock()
defer s.mu.Unlock()
if _, exists := s.tokens[token]; exists {
delete(s.tokens, token)
return true
}
return false
}
func (s *RefreshTokenStore) CleanupExpired() {
s.mu.Lock()
defer s.mu.Unlock()
now := time.Now()
for token, refreshToken := range s.tokens {
if now.After(refreshToken.ExpiresAt) {
delete(s.tokens, token)
}
}
}
// Helper functions
func generateSecureToken(bytes int) string {
b := make([]byte, bytes)
if _, err := rand.Read(b); err != nil {
return ""
}
return base64.RawURLEncoding.EncodeToString(b)
}
func parseScopes(scope string) []string {
if scope == "" {
return []string{}
}
return strings.Split(scope, " ")
}
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
func isValidResponseType(responseType string) bool {
validTypes := []string{
"code",
"token",
"id_token",
"code id_token",
"code token",
"id_token token",
"code id_token token",
}
return contains(validTypes, responseType)
}
func oauth2Error(c echo.Context, error, description, state string) error {
return c.JSON(http.StatusBadRequest, &OAuth2ErrorResponse{
Error: error,
ErrorDescription: description,
State: state,
})
}
func oauth2TokenError(c echo.Context, error, description string) error {
return c.JSON(http.StatusBadRequest, &OAuth2ErrorResponse{
Error: error,
ErrorDescription: description,
})
}
func redirectError(c echo.Context, redirectURI, error, description, state string) error {
url := fmt.Sprintf("%s?error=%s&error_description=%s&state=%s",
redirectURI, error, description, state)
return c.Redirect(http.StatusFound, url)
}
func buildAuthorizationRedirect(redirectURI, code, state string) string {
if strings.Contains(redirectURI, "?") {
return fmt.Sprintf("%s&code=%s&state=%s", redirectURI, code, state)
}
return fmt.Sprintf("%s?code=%s&state=%s", redirectURI, code, state)
}
func renderOAuth2ConsentPage(
c echo.Context,
req *OAuth2AuthorizationRequest,
client *OAuth2Client,
) error {
// TODO: Render actual consent page
return c.JSON(http.StatusOK, map[string]any{
"client": client,
"scopes": parseScopes(req.Scope),
"state": req.State,
"client_id": req.ClientID,
})
}
func getDefaultOAuth2Config() *OAuth2Config {
baseURL := "https://localhost:8080"
return &OAuth2Config{
Issuer: baseURL,
AuthorizationEndpoint: baseURL + "/oauth2/authorize",
TokenEndpoint: baseURL + "/oauth2/token",
UserInfoEndpoint: baseURL + "/oauth2/userinfo",
JWKSEndpoint: baseURL + "/oauth2/jwks",
RegistrationEndpoint: baseURL + "/oauth2/register",
IntrospectionEndpoint: baseURL + "/oauth2/introspect",
RevocationEndpoint: baseURL + "/oauth2/revoke",
ScopesSupported: []string{
"openid", "profile", "email", "offline_access",
"vault:read", "vault:write", "vault:sign", "vault:admin",
"service:manage", "did:read", "did:write",
},
ResponseTypesSupported: []string{
"code", "token", "id_token",
"code id_token", "code token",
"id_token token", "code id_token token",
},
ResponseModesSupported: []string{
"query", "fragment", "form_post",
},
GrantTypesSupported: []string{
"authorization_code", "implicit", "refresh_token",
"client_credentials", "urn:ietf:params:oauth:grant-type:device_code",
},
SubjectTypesSupported: []string{
"public", "pairwise",
},
IDTokenSigningAlgValuesSupported: []string{
"ES256", "RS256", "HS256",
},
TokenEndpointAuthMethodsSupported: []string{
"client_secret_basic", "client_secret_post",
"client_secret_jwt", "private_key_jwt", "none",
},
ClaimsSupported: []string{
"sub", "iss", "aud", "exp", "iat", "auth_time", "nonce",
"name", "given_name", "family_name", "middle_name", "nickname",
"preferred_username", "profile", "picture", "website", "email",
"email_verified", "did", "vault_id", "ucan_capabilities",
},
CodeChallengeMethodsSupported: []string{
PKCEMethodS256, PKCEMethodPlain,
},
ServiceDocumentation: baseURL + "/docs/oauth2",
UILocalesSupported: []string{"en-US"},
UCANSupported: true,
}
}