Yimaru-BackEnd/internal/services/team/tokens.go
2026-04-17 10:16:25 -07:00

70 lines
2.2 KiB
Go

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
}