Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ func NewAPIWithVersion(globalConfig *conf.GlobalConfiguration, db *storage.Conne
r.Use(api.oauthServer.LoadOAuthServerClient)
r.Get("/", api.oauthServer.OAuthServerClientGet)
r.Delete("/", api.oauthServer.OAuthServerClientDelete)
r.Post("/regenerate_secret", api.oauthServer.OAuthServerClientRegenerateSecret)
})
})
})
Expand Down
4 changes: 2 additions & 2 deletions internal/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestOAuthServerDisabledByDefault(t *testing.T) {

// OAuth server should be disabled by default
require.False(t, api.config.OAuthServer.Enabled)

// OAuth server instance should not be initialized when disabled
require.Nil(t, api.oauthServer)
}
Expand All @@ -78,7 +78,7 @@ func TestOAuthServerCanBeEnabled(t *testing.T) {

// OAuth server should be enabled
require.True(t, api.config.OAuthServer.Enabled)

// OAuth server instance should be initialized when enabled
require.NotNil(t, api.oauthServer)
}
21 changes: 21 additions & 0 deletions internal/api/oauthserver/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,27 @@ func (s *Server) OAuthServerClientDelete(w http.ResponseWriter, r *http.Request)
return nil
}

// OAuthServerClientRegenerateSecret handles POST /admin/oauth/clients/{client_id}/regenerate_secret
func (s *Server) OAuthServerClientRegenerateSecret(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
client := shared.GetOAuthServerClient(ctx)

// Only confidential clients can have their secrets regenerated
if !client.IsConfidential() {
return apierrors.NewBadRequestError(apierrors.ErrorCodeValidationFailed, "Cannot regenerate secret for public client")
}

updatedClient, plaintextSecret, err := s.regenerateOAuthServerClientSecret(ctx, client.ID)
if err != nil {
return apierrors.NewInternalServerError("Error regenerating OAuth client secret").WithInternalError(err)
}

response := oauthServerClientToResponse(updatedClient)
response.ClientSecret = plaintextSecret

return shared.SendJSON(w, http.StatusOK, response)
}

// OAuthServerClientList handles GET /admin/oauth/clients
func (s *Server) OAuthServerClientList(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
Expand Down
32 changes: 31 additions & 1 deletion internal/api/oauthserver/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ func validateRedirectURI(uri string) error {
return nil
}


// generateClientSecret generates a secure random client secret
func generateClientSecret() string {
b := make([]byte, 32)
Expand Down Expand Up @@ -248,3 +247,34 @@ func (s *Server) deleteOAuthServerClient(ctx context.Context, clientID uuid.UUID

return nil
}

// regenerateOAuthServerClientSecret regenerates a client secret for confidential clients
func (s *Server) regenerateOAuthServerClientSecret(ctx context.Context, clientID uuid.UUID) (*models.OAuthServerClient, string, error) {
db := s.db.WithContext(ctx)

client, err := models.FindOAuthServerClientByID(db, clientID)
if err != nil {
return nil, "", err
}

// Only confidential clients can have their secrets regenerated
if !client.IsConfidential() {
return nil, "", errors.New("cannot regenerate secret for public client")
}

// Generate new client secret
plaintextSecret := generateClientSecret()
hash, err := hashClientSecret(plaintextSecret)
if err != nil {
return nil, "", errors.Wrap(err, "failed to hash client secret")
}

// Update client with new secret hash
client.ClientSecretHash = hash

if err := models.UpdateOAuthServerClient(db, client); err != nil {
return nil, "", errors.Wrap(err, "failed to update OAuth client with new secret")
}

return client, plaintextSecret, nil
}