Skip to content

Commit 5793087

Browse files
authored
additional verification of org_id and user_id in token service (#2562)
1 parent 4ed47b8 commit 5793087

File tree

2 files changed

+56
-12
lines changed

2 files changed

+56
-12
lines changed

taco/internal/token_service/handler.go

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ type VerifyTokenRequest struct {
4848
OrgID string `json:"org_id"`
4949
}
5050

51+
// DeleteTokenRequest represents the request to delete a token
52+
type DeleteTokenRequest struct {
53+
OrgID string `json:"org_id" validate:"required"`
54+
}
55+
5156
// CreateToken creates a new token
5257
func (h *Handler) CreateToken(c echo.Context) error {
5358
var req CreateTokenRequest
@@ -85,10 +90,14 @@ func (h *Handler) CreateToken(c echo.Context) error {
8590
return c.JSON(http.StatusCreated, toTokenResponse(token))
8691
}
8792

88-
// ListTokens lists all tokens for a user and org
93+
// ListTokens lists all tokens for an org (org_id is required)
8994
func (h *Handler) ListTokens(c echo.Context) error {
90-
userID := c.QueryParam("user_id")
9195
orgID := c.QueryParam("org_id")
96+
if orgID == "" {
97+
return c.JSON(http.StatusBadRequest, map[string]string{"error": "org_id is required"})
98+
}
99+
100+
userID := c.QueryParam("user_id") // Optional filter within org
92101

93102
tokens, err := h.repo.ListTokens(c.Request().Context(), userID, orgID)
94103
if err != nil {
@@ -110,16 +119,26 @@ func (h *Handler) ListTokens(c echo.Context) error {
110119
return c.JSON(http.StatusOK, responses)
111120
}
112121

113-
// DeleteToken deletes a token by ID
122+
// DeleteToken deletes a token by ID with org ownership validation
114123
func (h *Handler) DeleteToken(c echo.Context) error {
115124
tokenID := c.Param("id")
116125
if tokenID == "" {
117126
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Token ID is required"})
118127
}
119128

120-
if err := h.repo.DeleteToken(c.Request().Context(), tokenID); err != nil {
129+
// Parse org_id from request body for ownership validation
130+
var req DeleteTokenRequest
131+
if err := c.Bind(&req); err != nil || req.OrgID == "" {
132+
return c.JSON(http.StatusBadRequest, map[string]string{"error": "org_id is required"})
133+
}
134+
135+
// Delete with org validation - returns "not found" if token doesn't belong to org
136+
if err := h.repo.DeleteTokenForOrg(c.Request().Context(), tokenID, req.OrgID); err != nil {
137+
if err.Error() == "token not found" {
138+
return c.JSON(http.StatusNotFound, map[string]string{"error": "Token not found"})
139+
}
121140
logger := logging.FromContext(c)
122-
logger.Error("Failed to delete token", "token_id", tokenID, "error", err)
141+
logger.Error("Failed to delete token", "token_id", tokenID, "org_id", req.OrgID, "error", err)
123142
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
124143
}
125144

@@ -153,18 +172,19 @@ func (h *Handler) VerifyToken(c echo.Context) error {
153172
})
154173
}
155174

156-
// GetToken retrieves a token by ID
175+
// GetToken retrieves a token by ID with org ownership validation
157176
func (h *Handler) GetToken(c echo.Context) error {
158177
tokenID := c.Param("id")
159-
if tokenID == "" {
160-
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Token ID is required"})
178+
orgID := c.QueryParam("org_id")
179+
180+
if tokenID == "" || orgID == "" {
181+
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Token ID and org_id are required"})
161182
}
162183

163-
token, err := h.repo.GetToken(c.Request().Context(), tokenID)
184+
// Get with org validation - returns "not found" if token doesn't belong to org
185+
token, err := h.repo.GetTokenForOrg(c.Request().Context(), tokenID, orgID)
164186
if err != nil {
165-
logger := logging.FromContext(c)
166-
logger.Error("Failed to get token", "token_id", tokenID, "error", err)
167-
return c.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
187+
return c.JSON(http.StatusNotFound, map[string]string{"error": "Token not found"})
168188
}
169189

170190
return c.JSON(http.StatusOK, toTokenResponseHidden(token)) // Hide token hash

taco/internal/token_service/repository.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,30 @@ func (r *TokenRepository) GetToken(ctx context.Context, tokenID string) (*types.
150150
return &token, nil
151151
}
152152

153+
// GetTokenForOrg retrieves a token by ID only if it belongs to the specified org
154+
func (r *TokenRepository) GetTokenForOrg(ctx context.Context, tokenID, orgID string) (*types.Token, error) {
155+
var token types.Token
156+
if err := r.db.WithContext(ctx).Where("id = ? AND org_id = ?", tokenID, orgID).First(&token).Error; err != nil {
157+
if errors.Is(err, gorm.ErrRecordNotFound) {
158+
return nil, errors.New(errTokenNotFound)
159+
}
160+
return nil, fmt.Errorf("failed to get token: %w", err)
161+
}
162+
return &token, nil
163+
}
164+
165+
// DeleteTokenForOrg deletes a token by ID only if it belongs to the specified org
166+
func (r *TokenRepository) DeleteTokenForOrg(ctx context.Context, tokenID, orgID string) error {
167+
result := r.db.WithContext(ctx).Where("id = ? AND org_id = ?", tokenID, orgID).Delete(&types.Token{})
168+
if result.Error != nil {
169+
return fmt.Errorf("failed to delete token: %w", result.Error)
170+
}
171+
if result.RowsAffected == 0 {
172+
return errors.New(errTokenNotFound)
173+
}
174+
return nil
175+
}
176+
153177
// generateSecureToken generates a cryptographically secure random token
154178
func generateSecureToken() (string, error) {
155179
b := make([]byte, 32)

0 commit comments

Comments
 (0)