diff --git a/backend/internal/controller/oidc_controller.go b/backend/internal/controller/oidc_controller.go index 0033e918..6285975f 100644 --- a/backend/internal/controller/oidc_controller.go +++ b/backend/internal/controller/oidc_controller.go @@ -1,6 +1,8 @@ package controller import ( + "errors" + "io" "net/http" "strconv" "strings" @@ -234,14 +236,22 @@ func (oc *OidcController) updateClientHandler(c *gin.Context) { // createClientSecretHandler godoc // @Summary Create client secret -// @Description Generate a new secret for an OIDC client +// @Description Set or generate a new secret for an OIDC client // @Tags OIDC +// @Accept json // @Produce json // @Param id path string true "Client ID" +// @Param payload body dto.OidcClientSecretDto false "Client secret" // @Success 200 {object} object "{ \"secret\": \"string\" }" // @Router /api/oidc/clients/{id}/secret [post] func (oc *OidcController) createClientSecretHandler(c *gin.Context) { - secret, err := oc.oidcService.CreateClientSecret(c.Request.Context(), c.Param("id")) + var input dto.OidcClientSecretDto + if err := c.ShouldBindJSON(&input); err != nil && !errors.Is(err, io.EOF) { + _ = c.Error(err) + return + } + + secret, err := oc.oidcService.CreateClientSecret(c.Request.Context(), c.Param("id"), input) if err != nil { _ = c.Error(err) return diff --git a/backend/internal/dto/oidc_dto.go b/backend/internal/dto/oidc_dto.go index d87716b0..507364c9 100644 --- a/backend/internal/dto/oidc_dto.go +++ b/backend/internal/dto/oidc_dto.go @@ -59,6 +59,10 @@ type OidcClientCreateDto struct { ID string `json:"id" binding:"omitempty,client_id,min=2,max=128"` } +type OidcClientSecretDto struct { + Secret string `json:"secret" binding:"omitempty,min=16,printascii"` +} + type OidcClientCredentialsDto struct { FederatedIdentities []OidcClientFederatedIdentityDto `json:"federatedIdentities,omitempty"` } diff --git a/backend/internal/service/oidc_service.go b/backend/internal/service/oidc_service.go index 805e6076..c9cdef80 100644 --- a/backend/internal/service/oidc_service.go +++ b/backend/internal/service/oidc_service.go @@ -266,7 +266,7 @@ func (s *OidcService) DeleteClient(ctx context.Context, clientID string) error { return nil } -func (s *OidcService) CreateClientSecret(ctx context.Context, clientID string) (string, error) { +func (s *OidcService) CreateClientSecret(ctx context.Context, clientID string, input dto.OidcClientSecretDto) (string, error) { tx := s.db.Begin() defer func() { tx.Rollback() @@ -281,9 +281,12 @@ func (s *OidcService) CreateClientSecret(ctx context.Context, clientID string) ( return "", err } - clientSecret, err := utils.GenerateRandomAlphanumericString(32) - if err != nil { - return "", err + clientSecret := input.Secret + if clientSecret == "" { + clientSecret, err = utils.GenerateRandomAlphanumericString(32) + if err != nil { + return "", err + } } hashedSecret, err := bcrypt.GenerateFromPassword([]byte(clientSecret), bcrypt.DefaultCost) diff --git a/backend/internal/service/oidc_service_test.go b/backend/internal/service/oidc_service_test.go index 0f233c8a..5806970e 100644 --- a/backend/internal/service/oidc_service_test.go +++ b/backend/internal/service/oidc_service_test.go @@ -7,6 +7,8 @@ import ( "strings" "testing" + "golang.org/x/crypto/bcrypt" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -499,6 +501,29 @@ func TestOidcService_CreateClient_withoutDescription(t *testing.T) { assert.Empty(t, fetched.Description) } +func TestOidcService_CreateClientSecret_withCustomSecret(t *testing.T) { + db := testutils.NewDatabaseForTest(t) + + s, err := NewOidcService(db, nil, nil, nil, nil, nil) + require.NoError(t, err) + + client := model.OidcClient{Name: "Test Client"} + err = db.Create(&client).Error + require.NoError(t, err) + + customSecret := "custom-client-secret-with-a-minimum-length" + input := dto.OidcClientSecretDto{Secret: customSecret} + + secret, err := s.CreateClientSecret(t.Context(), client.ID, input) + require.NoError(t, err) + assert.Equal(t, customSecret, secret) + + var fetched model.OidcClient + err = db.First(&fetched, "id = ?", client.ID).Error + require.NoError(t, err) + require.NoError(t, bcrypt.CompareHashAndPassword([]byte(fetched.Secret), []byte(customSecret))) +} + func TestOidcService_UpdateClient_description(t *testing.T) { db := testutils.NewDatabaseForTest(t)