package team import ( "context" "crypto/rand" "encoding/base32" "time" "Yimaru-Backend/internal/domain" ) func generateOpaqueRefreshToken() (string, error) { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { return "", err } return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(b), nil } // IssueRefreshTokenOnLogin revokes prior active team refresh tokens for the member and stores a new one. func (s *Service) IssueRefreshTokenOnLogin(ctx context.Context, memberID int64) (string, error) { if err := s.teamStore.RevokeAllActiveTeamRefreshTokensForMember(ctx, memberID); err != nil { return "", err } tok, err := generateOpaqueRefreshToken() if err != nil { return "", err } now := time.Now() exp := now.Add(time.Duration(s.refreshExpirySec) * time.Second) if err := s.teamStore.CreateTeamRefreshToken(ctx, memberID, tok, exp, now); err != nil { return "", err } return tok, nil } // RefreshSession validates a team refresh token, rotates it, and returns the member plus the new refresh token plaintext. func (s *Service) RefreshSession(ctx context.Context, refreshTokenPlain string) (domain.TeamMember, string, error) { rt, err := s.teamStore.GetTeamRefreshTokenByToken(ctx, refreshTokenPlain) if err != nil { return domain.TeamMember{}, "", err } if rt.Revoked { return domain.TeamMember{}, "", domain.ErrTeamRefreshTokenNotFound } if time.Now().After(rt.ExpiresAt) { return domain.TeamMember{}, "", domain.ErrTeamRefreshTokenExpired } if err := s.teamStore.RevokeTeamRefreshTokenByToken(ctx, refreshTokenPlain); err != nil { return domain.TeamMember{}, "", err } newTok, err := generateOpaqueRefreshToken() if err != nil { return domain.TeamMember{}, "", err } now := time.Now() exp := now.Add(time.Duration(s.refreshExpirySec) * time.Second) if err := s.teamStore.CreateTeamRefreshToken(ctx, rt.TeamMemberID, newTok, exp, now); err != nil { return domain.TeamMember{}, "", err } member, err := s.teamStore.GetTeamMemberByID(ctx, rt.TeamMemberID) if err != nil { return domain.TeamMember{}, "", err } if member.Status != domain.TeamMemberStatusActive { return domain.TeamMember{}, "", domain.ErrInvalidTeamMemberStatus } return member, newTok, nil }