diff --git a/internal/repository/postgres/permission_repository.go b/internal/repository/postgres/permission_repository.go index 71afb42..e09d1dc 100644 --- a/internal/repository/postgres/permission_repository.go +++ b/internal/repository/postgres/permission_repository.go @@ -269,14 +269,81 @@ func (r *GrantedPermissionRepository) GrantPermissions(ctx context.Context, gran // GetGrantedPermissions retrieves all granted permissions for a token func (r *GrantedPermissionRepository) GetGrantedPermissions(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID) ([]*domain.GrantedPermission, error) { - // TODO: Implement actual granted permissions retrieval - return []*domain.GrantedPermission{}, nil + query := ` + SELECT id, token_type, token_id, permission_id, scope, created_at, created_by, revoked + FROM granted_permissions + WHERE token_type = $1 AND token_id = $2 AND revoked = false + ORDER BY created_at ASC + ` + + db := r.db.GetDB().(*sql.DB) + rows, err := db.QueryContext(ctx, query, string(tokenType), tokenID) + if err != nil { + return nil, fmt.Errorf("failed to query granted permissions: %w", err) + } + defer rows.Close() + + var permissions []*domain.GrantedPermission + for rows.Next() { + perm := &domain.GrantedPermission{} + var tokenTypeStr string + + err := rows.Scan( + &perm.ID, + &tokenTypeStr, + &perm.TokenID, + &perm.PermissionID, + &perm.Scope, + &perm.CreatedAt, + &perm.CreatedBy, + &perm.Revoked, + ) + + if err != nil { + return nil, fmt.Errorf("failed to scan granted permission: %w", err) + } + + perm.TokenType = domain.TokenType(tokenTypeStr) + permissions = append(permissions, perm) + } + + if err = rows.Err(); err != nil { + return nil, fmt.Errorf("error iterating granted permissions: %w", err) + } + + return permissions, nil } // GetGrantedPermissionScopes retrieves only the scopes for a token (more efficient) func (r *GrantedPermissionRepository) GetGrantedPermissionScopes(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID) ([]string, error) { - // TODO: Implement actual scope retrieval - return []string{}, nil + query := ` + SELECT scope + FROM granted_permissions + WHERE token_type = $1 AND token_id = $2 AND revoked = false + ORDER BY scope ASC + ` + + db := r.db.GetDB().(*sql.DB) + rows, err := db.QueryContext(ctx, query, string(tokenType), tokenID) + if err != nil { + return nil, fmt.Errorf("failed to query granted permission scopes: %w", err) + } + defer rows.Close() + + var scopes []string + for rows.Next() { + var scope string + if err := rows.Scan(&scope); err != nil { + return nil, fmt.Errorf("failed to scan permission scope: %w", err) + } + scopes = append(scopes, scope) + } + + if err = rows.Err(); err != nil { + return nil, fmt.Errorf("error iterating permission scopes: %w", err) + } + + return scopes, nil } // RevokePermission revokes a specific permission from a token diff --git a/internal/services/token_service.go b/internal/services/token_service.go index fc40d25..d877235 100644 --- a/internal/services/token_service.go +++ b/internal/services/token_service.go @@ -208,15 +208,122 @@ func (s *tokenService) GenerateUserToken(ctx context.Context, appID, userID stri func (s *tokenService) VerifyToken(ctx context.Context, req *domain.VerifyRequest) (*domain.VerifyResponse, error) { s.logger.Debug("Verifying token", zap.String("app_id", req.AppID), zap.String("type", string(req.Type))) - // TODO: Implement actual token verification logic - response := &domain.VerifyResponse{ - Valid: true, - UserID: req.UserID, - Permissions: []string{"basic"}, - TokenType: req.Type, + // Validate request + if req.Token == "" { + return &domain.VerifyResponse{ + Valid: false, + Error: "Token is required", + }, nil } - return response, nil + // Validate application exists + app, err := s.appRepo.GetByID(ctx, req.AppID) + if err != nil { + s.logger.Error("Failed to get application", zap.Error(err), zap.String("app_id", req.AppID)) + return &domain.VerifyResponse{ + Valid: false, + Error: "Invalid application", + }, nil + } + + switch req.Type { + case domain.TokenTypeStatic: + return s.verifyStaticToken(ctx, req, app) + case domain.TokenTypeUser: + return s.verifyUserToken(ctx, req, app) + default: + return &domain.VerifyResponse{ + Valid: false, + Error: "Invalid token type", + }, nil + } +} + +// verifyStaticToken verifies a static token +func (s *tokenService) verifyStaticToken(ctx context.Context, req *domain.VerifyRequest, app *domain.Application) (*domain.VerifyResponse, error) { + s.logger.Debug("Verifying static token", zap.String("app_id", req.AppID)) + + // Check token format + if !crypto.IsValidTokenFormat(req.Token) { + s.logger.Warn("Invalid token format", zap.String("app_id", req.AppID)) + return &domain.VerifyResponse{ + Valid: false, + Error: "Invalid token format", + }, nil + } + + // Try to find token by testing against all stored hashes for this app + tokens, err := s.tokenRepo.GetByAppID(ctx, req.AppID) + if err != nil { + s.logger.Error("Failed to get tokens for app", zap.Error(err), zap.String("app_id", req.AppID)) + return &domain.VerifyResponse{ + Valid: false, + Error: "Token verification failed", + }, nil + } + + var matchedToken *domain.StaticToken + for _, token := range tokens { + if s.tokenGen.VerifyToken(req.Token, token.KeyHash) { + matchedToken = token + break + } + } + + if matchedToken == nil { + s.logger.Warn("Token not found or invalid", zap.String("app_id", req.AppID)) + return &domain.VerifyResponse{ + Valid: false, + Error: "Invalid token", + }, nil + } + + // Get granted permissions for this token + permissions, err := s.grantRepo.GetGrantedPermissionScopes(ctx, domain.TokenTypeStatic, matchedToken.ID) + if err != nil { + s.logger.Error("Failed to get token permissions", zap.Error(err), zap.String("token_id", matchedToken.ID.String())) + return &domain.VerifyResponse{ + Valid: false, + Error: "Failed to retrieve permissions", + }, nil + } + + // Check specific permissions if requested + var permissionResults map[string]bool + if len(req.Permissions) > 0 { + permissionResults, err = s.grantRepo.HasAnyPermission(ctx, domain.TokenTypeStatic, matchedToken.ID, req.Permissions) + if err != nil { + s.logger.Error("Failed to check specific permissions", zap.Error(err)) + return &domain.VerifyResponse{ + Valid: false, + Error: "Failed to check permissions", + }, nil + } + } + + s.logger.Info("Static token verified successfully", + zap.String("token_id", matchedToken.ID.String()), + zap.String("app_id", req.AppID), + zap.Strings("permissions", permissions)) + + return &domain.VerifyResponse{ + Valid: true, + Permissions: permissions, + PermissionResults: permissionResults, + TokenType: domain.TokenTypeStatic, + }, nil +} + +// verifyUserToken verifies a user token (JWT-based) +func (s *tokenService) verifyUserToken(ctx context.Context, req *domain.VerifyRequest, app *domain.Application) (*domain.VerifyResponse, error) { + s.logger.Debug("Verifying user token", zap.String("app_id", req.AppID)) + + // TODO: Implement JWT token verification + // For now, return an error since user tokens are not fully implemented + return &domain.VerifyResponse{ + Valid: false, + Error: "User token verification not yet implemented", + }, nil } // RenewUserToken renews a user token diff --git a/server b/server index 94018c2..8847648 100755 Binary files a/server and b/server differ diff --git a/test/integration_test.go b/test/integration_test.go index ff58b05..d0788d7 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -420,12 +420,15 @@ func (suite *IntegrationTestSuite) TestStaticTokenWorkflow() { assert.True(t, verifyResp.Valid) assert.Equal(t, domain.TokenTypeStatic, verifyResp.TokenType) - // Note: The current service implementation returns ["basic"] as a placeholder - assert.Contains(t, verifyResp.Permissions, "basic") + // Verify that we get the actual permissions that were granted to the token + assert.Contains(t, verifyResp.Permissions, "repo.read") + assert.Contains(t, verifyResp.Permissions, "repo.write") if verifyResp.PermissionResults != nil { - // Check that we get some permission results + // Check that we get permission results for the requested permissions assert.NotEmpty(t, verifyResp.PermissionResults) + // The token should have the "repo.read" permission we requested + assert.True(t, verifyResp.PermissionResults["repo.read"]) } }) }