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
2 changes: 1 addition & 1 deletion docs/engines/tokenization.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ Example response (`200 OK`):

#### Delete Tokenization Key

- **Endpoint**: `DELETE /v1/tokenization/keys/:id`
- **Endpoint**: `DELETE /v1/tokenization/keys/:name`
- **Capability**: `delete`
- **Success**: `204 No Content`

Expand Down
48 changes: 20 additions & 28 deletions docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -811,31 +811,14 @@ paths:
$ref: "#/components/schemas/ErrorResponse"
"429":
$ref: "#/components/responses/TooManyRequests"
/v1/tokenization/keys/{name}/rotate:
post:
delete:
tags: [tokenization]
summary: Rotate tokenization key
summary: Delete tokenization key
security:
- bearerAuth: []
parameters:
- name: name
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TokenizationKeyRotateRequest"
responses:
"201":
description: New tokenization key version created
content:
application/json:
schema:
$ref: "#/components/schemas/TokenizationKeyResponse"
"204":
description: Deleted
"401":
$ref: "#/components/responses/Unauthorized"
"403":
Expand All @@ -850,22 +833,31 @@ paths:
$ref: "#/components/responses/ValidationError"
"429":
$ref: "#/components/responses/TooManyRequests"
/v1/tokenization/keys/{id}:
delete:
/v1/tokenization/keys/{name}/rotate:
post:
tags: [tokenization]
summary: Delete tokenization key
summary: Rotate tokenization key
security:
- bearerAuth: []
parameters:
- name: id
- name: name
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TokenizationKeyRotateRequest"
responses:
"204":
description: Deleted
"201":
description: New tokenization key version created
content:
application/json:
schema:
$ref: "#/components/schemas/TokenizationKeyResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
Expand Down
2 changes: 1 addition & 1 deletion internal/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ func (s *Server) registerTokenizationRoutes(
)

// Delete tokenization key
keys.DELETE("/:id",
keys.DELETE("/:name",
authHTTP.AuthorizationMiddleware(authDomain.DeleteCapability, auditLogUseCase, s.logger),
tokenizationKeyHandler.DeleteHandler,
)
Expand Down
15 changes: 7 additions & 8 deletions internal/tokenization/http/tokenization_key_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"net/http"

"github.com/gin-gonic/gin"
"github.com/google/uuid"

"github.com/allisson/secrets/internal/httputil"
"github.com/allisson/secrets/internal/tokenization/http/dto"
Expand Down Expand Up @@ -140,21 +139,21 @@ func (h *TokenizationKeyHandler) RotateHandler(c *gin.Context) {
c.JSON(http.StatusCreated, response)
}

// DeleteHandler soft-deletes a tokenization key by ID.
// DELETE /v1/tokenization/keys/:id - Requires DeleteCapability.
// DeleteHandler soft-deletes a tokenization key by name.
// DELETE /v1/tokenization/keys/:name - Requires DeleteCapability.
// Returns 204 No Content on success.
func (h *TokenizationKeyHandler) DeleteHandler(c *gin.Context) {
// Parse and validate UUID
keyID, err := uuid.Parse(c.Param("id"))
if err != nil {
// Get key name from URL parameter
name := c.Param("name")
if name == "" {
httputil.HandleBadRequestGin(c,
fmt.Errorf("invalid key ID format: must be a valid UUID"),
fmt.Errorf("key name is required"),
h.logger)
return
}

// Call use case
if err := h.keyUseCase.Delete(c.Request.Context(), keyID); err != nil {
if err := h.keyUseCase.Delete(c.Request.Context(), name); err != nil {
httputil.HandleErrorGin(c, err, h.logger)
return
}
Expand Down
41 changes: 12 additions & 29 deletions internal/tokenization/http/tokenization_key_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,51 +351,34 @@ func TestTokenizationKeyHandler_DeleteHandler(t *testing.T) {
t.Run("Success_DeleteKey", func(t *testing.T) {
handler, mockUseCase := setupTestKeyHandler(t)

keyID := uuid.Must(uuid.NewV7())
keyName := "test-key"

mockUseCase.EXPECT().
Delete(mock.Anything, keyID).
Delete(mock.Anything, keyName).
Return(nil).
Once()

c, w := createTestContext(http.MethodDelete, "/v1/tokenization/keys/"+keyID.String(), nil)
c.Params = gin.Params{{Key: "id", Value: keyID.String()}}
c, w := createTestContext(http.MethodDelete, "/v1/tokenization/keys/"+keyName, nil)
c.Params = gin.Params{{Key: "name", Value: keyName}}

handler.DeleteHandler(c)

assert.Equal(t, http.StatusNoContent, w.Code)
assert.Empty(t, w.Body.String())
})

t.Run("Error_InvalidUUID", func(t *testing.T) {
handler, _ := setupTestKeyHandler(t)

c, w := createTestContext(http.MethodDelete, "/v1/tokenization/keys/invalid-uuid", nil)
c.Params = gin.Params{{Key: "id", Value: "invalid-uuid"}}

handler.DeleteHandler(c)

assert.Equal(t, http.StatusBadRequest, w.Code)

var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "bad_request", response["error"])
assert.Contains(t, response["message"], "invalid key ID format")
})

t.Run("Error_KeyNotFound", func(t *testing.T) {
handler, mockUseCase := setupTestKeyHandler(t)

keyID := uuid.Must(uuid.NewV7())
keyName := "non-existent-key"

mockUseCase.EXPECT().
Delete(mock.Anything, keyID).
Delete(mock.Anything, keyName).
Return(tokenizationDomain.ErrTokenizationKeyNotFound).
Once()

c, w := createTestContext(http.MethodDelete, "/v1/tokenization/keys/"+keyID.String(), nil)
c.Params = gin.Params{{Key: "id", Value: keyID.String()}}
c, w := createTestContext(http.MethodDelete, "/v1/tokenization/keys/"+keyName, nil)
c.Params = gin.Params{{Key: "name", Value: keyName}}

handler.DeleteHandler(c)

Expand All @@ -405,16 +388,16 @@ func TestTokenizationKeyHandler_DeleteHandler(t *testing.T) {
t.Run("Error_UseCaseError", func(t *testing.T) {
handler, mockUseCase := setupTestKeyHandler(t)

keyID := uuid.Must(uuid.NewV7())
keyName := "test-key"
dbError := errors.New("database error")

mockUseCase.EXPECT().
Delete(mock.Anything, keyID).
Delete(mock.Anything, keyName).
Return(dbError).
Once()

c, w := createTestContext(http.MethodDelete, "/v1/tokenization/keys/"+keyID.String(), nil)
c.Params = gin.Params{{Key: "id", Value: keyID.String()}}
c, w := createTestContext(http.MethodDelete, "/v1/tokenization/keys/"+keyName, nil)
c.Params = gin.Params{{Key: "name", Value: keyName}}

handler.DeleteHandler(c)

Expand Down
11 changes: 3 additions & 8 deletions internal/tokenization/repository/mysql/mysql_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,12 @@ func (m *MySQLTokenizationKeyRepository) Create(
}

// Delete soft-deletes a tokenization key by setting its deleted_at timestamp.
func (m *MySQLTokenizationKeyRepository) Delete(ctx context.Context, keyID uuid.UUID) error {
func (m *MySQLTokenizationKeyRepository) Delete(ctx context.Context, name string) error {
querier := database.GetTx(ctx, m.db)

query := `UPDATE tokenization_keys SET deleted_at = NOW() WHERE id = ?`
query := `UPDATE tokenization_keys SET deleted_at = NOW() WHERE name = ?`

id, err := keyID.MarshalBinary()
if err != nil {
return apperrors.Wrap(err, "failed to marshal tokenization key id")
}

_, err = querier.ExecContext(ctx, query, id)
_, err := querier.ExecContext(ctx, query, name)
if err != nil {
return apperrors.Wrap(err, "failed to delete tokenization key")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ func TestMySQLTokenizationKeyRepository_Delete(t *testing.T) {
require.NoError(t, err)

// Delete the key
err = repo.Delete(ctx, key.ID)
err = repo.Delete(ctx, key.Name)
require.NoError(t, err)

// Verify soft delete - key should not be found
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ func (p *PostgreSQLTokenizationKeyRepository) Create(
}

// Delete soft-deletes a tokenization key by setting its deleted_at timestamp.
func (p *PostgreSQLTokenizationKeyRepository) Delete(ctx context.Context, keyID uuid.UUID) error {
func (p *PostgreSQLTokenizationKeyRepository) Delete(ctx context.Context, name string) error {
querier := database.GetTx(ctx, p.db)

query := `UPDATE tokenization_keys SET deleted_at = NOW() WHERE id = $1`
query := `UPDATE tokenization_keys SET deleted_at = NOW() WHERE name = $1`

_, err := querier.ExecContext(ctx, query, keyID)
_, err := querier.ExecContext(ctx, query, name)
if err != nil {
return apperrors.Wrap(err, "failed to delete tokenization key")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ func TestPostgreSQLTokenizationKeyRepository_Delete(t *testing.T) {
require.NoError(t, err)

// Delete the key
err = repo.Delete(ctx, key.ID)
err = repo.Delete(ctx, key.Name)
require.NoError(t, err)

// Verify soft delete - key should not be found
Expand Down
6 changes: 3 additions & 3 deletions internal/tokenization/usecase/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type DekRepository interface {
// TokenizationKeyRepository defines the interface for tokenization key persistence.
type TokenizationKeyRepository interface {
Create(ctx context.Context, key *tokenizationDomain.TokenizationKey) error
Delete(ctx context.Context, keyID uuid.UUID) error
Delete(ctx context.Context, name string) error
Get(ctx context.Context, keyID uuid.UUID) (*tokenizationDomain.TokenizationKey, error)
GetByName(ctx context.Context, name string) (*tokenizationDomain.TokenizationKey, error)
GetByNameAndVersion(
Expand Down Expand Up @@ -88,8 +88,8 @@ type TokenizationKeyUseCase interface {
alg cryptoDomain.Algorithm,
) (*tokenizationDomain.TokenizationKey, error)

// Delete soft deletes a tokenization key and all its versions by key ID.
Delete(ctx context.Context, keyID uuid.UUID) error
// Delete soft deletes a tokenization key and all its versions by name.
Delete(ctx context.Context, name string) error

// GetByName retrieves a single tokenization key by its name.
// Returns the latest version for the key. Filters out soft-deleted keys.
Expand Down
44 changes: 22 additions & 22 deletions internal/tokenization/usecase/mocks/mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading