package services import ( "context" "fmt" "time" "github.com/google/uuid" "go.uber.org/zap" "github.com/kms/api-key-service/internal/crypto" "github.com/kms/api-key-service/internal/domain" "github.com/kms/api-key-service/internal/repository" ) // tokenService implements the TokenService interface type tokenService struct { tokenRepo repository.StaticTokenRepository appRepo repository.ApplicationRepository permRepo repository.PermissionRepository grantRepo repository.GrantedPermissionRepository tokenGen *crypto.TokenGenerator logger *zap.Logger } // NewTokenService creates a new token service func NewTokenService( tokenRepo repository.StaticTokenRepository, appRepo repository.ApplicationRepository, permRepo repository.PermissionRepository, grantRepo repository.GrantedPermissionRepository, hmacKey string, logger *zap.Logger, ) TokenService { return &tokenService{ tokenRepo: tokenRepo, appRepo: appRepo, permRepo: permRepo, grantRepo: grantRepo, tokenGen: crypto.NewTokenGenerator(hmacKey), logger: logger, } } // CreateStaticToken creates a new static token func (s *tokenService) CreateStaticToken(ctx context.Context, req *domain.CreateStaticTokenRequest, userID string) (*domain.CreateStaticTokenResponse, error) { s.logger.Info("Creating static token", zap.String("app_id", req.AppID), zap.String("user_id", userID)) // 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 nil, fmt.Errorf("application not found: %w", err) } // Validate permissions exist validPermissions, err := s.permRepo.ValidatePermissionScopes(ctx, req.Permissions) if err != nil { s.logger.Error("Failed to validate permissions", zap.Error(err)) return nil, fmt.Errorf("failed to validate permissions: %w", err) } if len(validPermissions) != len(req.Permissions) { s.logger.Warn("Some permissions are invalid", zap.Strings("requested", req.Permissions), zap.Strings("valid", validPermissions)) return nil, fmt.Errorf("some requested permissions are invalid") } // Generate secure token tokenInfo, err := s.tokenGen.GenerateTokenWithInfo() if err != nil { s.logger.Error("Failed to generate secure token", zap.Error(err)) return nil, fmt.Errorf("failed to generate token: %w", err) } tokenID := uuid.New() now := time.Now() // Create the token entity token := &domain.StaticToken{ ID: tokenID, AppID: req.AppID, Owner: req.Owner, KeyHash: tokenInfo.Hash, Type: "hmac", CreatedAt: now, UpdatedAt: now, } // Save the token to the database err = s.tokenRepo.Create(ctx, token) if err != nil { s.logger.Error("Failed to create token in database", zap.Error(err), zap.String("token_id", tokenID.String())) return nil, fmt.Errorf("failed to create token: %w", err) } // Grant permissions to the token var grants []*domain.GrantedPermission for _, permScope := range validPermissions { // Get permission by scope to get the ID perm, err := s.permRepo.GetAvailablePermissionByScope(ctx, permScope) if err != nil { s.logger.Error("Failed to get permission by scope", zap.Error(err), zap.String("scope", permScope)) continue } grant := &domain.GrantedPermission{ ID: uuid.New(), TokenType: domain.TokenTypeStatic, TokenID: tokenID, PermissionID: perm.ID, Scope: permScope, CreatedBy: userID, } grants = append(grants, grant) } if len(grants) > 0 { err = s.grantRepo.GrantPermissions(ctx, grants) if err != nil { s.logger.Error("Failed to grant permissions", zap.Error(err)) // Clean up the token if permission granting fails s.tokenRepo.Delete(ctx, tokenID) return nil, fmt.Errorf("failed to grant permissions: %w", err) } } response := &domain.CreateStaticTokenResponse{ ID: tokenID, Token: tokenInfo.Token, // Return the actual token only once Permissions: validPermissions, CreatedAt: now, } s.logger.Info("Static token created successfully", zap.String("token_id", tokenID.String()), zap.String("app_id", app.AppID), zap.Strings("permissions", validPermissions)) return response, nil } // ListByApp lists all tokens for an application func (s *tokenService) ListByApp(ctx context.Context, appID string, limit, offset int) ([]*domain.StaticToken, error) { s.logger.Debug("Listing tokens for application", zap.String("app_id", appID)) // TODO: Implement actual token listing return []*domain.StaticToken{}, nil } // Delete deletes a token func (s *tokenService) Delete(ctx context.Context, tokenID uuid.UUID, userID string) error { s.logger.Info("Deleting token", zap.String("token_id", tokenID.String()), zap.String("user_id", userID)) // Check if token exists exists, err := s.tokenRepo.Exists(ctx, tokenID) if err != nil { s.logger.Error("Failed to check token existence", zap.Error(err), zap.String("token_id", tokenID.String())) return err } if !exists { s.logger.Error("Token not found", zap.String("token_id", tokenID.String())) return fmt.Errorf("token with ID '%s' not found", tokenID.String()) } // Delete the token err = s.tokenRepo.Delete(ctx, tokenID) if err != nil { s.logger.Error("Failed to delete token", zap.Error(err), zap.String("token_id", tokenID.String())) return err } // TODO: Revoke associated permissions return nil } // GenerateUserToken generates a user token func (s *tokenService) GenerateUserToken(ctx context.Context, appID, userID string, permissions []string) (string, error) { s.logger.Info("Generating user token", zap.String("app_id", appID), zap.String("user_id", userID)) // TODO: Validate application // TODO: Validate permissions // TODO: Generate JWT token return "user-token-placeholder-" + userID, nil } // VerifyToken verifies a token and returns verification response 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, } return response, nil } // RenewUserToken renews a user token func (s *tokenService) RenewUserToken(ctx context.Context, req *domain.RenewRequest) (*domain.RenewResponse, error) { s.logger.Info("Renewing user token", zap.String("app_id", req.AppID), zap.String("user_id", req.UserID)) // TODO: Validate current token // TODO: Generate new token with extended expiry but same max valid date response := &domain.RenewResponse{ Token: "renewed-token-placeholder", ExpiresAt: time.Now().Add(7 * 24 * time.Hour), MaxValidAt: time.Now().Add(30 * 24 * time.Hour), } return response, nil }