This commit is contained in:
blankhex committed 2026-10-21 19:41:26 +03:00
commit 97dc0a3570
78 files changed
+7054

No files matched your search

+193
View File
@@ -0,0 +1,193 @@
package auth
import (
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"log/slog"
"time"
"goAuth/internal/invite"
"goAuth/internal/user"
"github.com/mileusna/useragent"
)
const (
TransactionTimeout = 15 * time.Second
)
type Service struct {
r *Repository
u *user.Service
i *invite.Service
shortDuration time.Duration
longDuration time.Duration
tokenSize int
}
func NewService(r *Repository, u *user.Service, i *invite.Service, shortDuration, longDuration time.Duration, tokenSize int) (*Service, error) {
if tokenSize <= 0 {
tokenSize = 32
}
return &Service{r: r, u: u, i: i, shortDuration: shortDuration, longDuration: longDuration, tokenSize: tokenSize}, nil
}
func detachWithTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
return context.WithTimeout(context.WithoutCancel(ctx), timeout)
}
func (s *Service) ClassifiedUserAgent(userAgent string) string {
ua := useragent.Parse(userAgent)
var class string
if ua.Mobile {
class = "Mobile"
}
if ua.Tablet {
class = "Tablet"
}
if ua.Desktop {
class = "Desktop"
}
if ua.Bot {
class = "Bot"
}
device := class
if ua.Name != "" {
device += fmt.Sprintf(", %s %s", ua.Name, ua.Version)
}
if ua.OS != "" {
device += fmt.Sprintf(", %s %s", ua.OS, ua.OSVersion)
}
if ua.Device != "" {
device += fmt.Sprintf(", %s", ua.Device)
}
return device
}
func (s *Service) generateToken() (string, error) {
b := make([]byte, s.tokenSize)
_, err := rand.Read(b)
if err != nil {
slog.Error("generate session token", slog.String("op", "service.generateToken"), slog.Any("error", err))
return "", ErrInternal
}
return base64.RawURLEncoding.EncodeToString(b), nil
}
func (s *Service) RegisterUser(ctx context.Context, login, password, invite string, address, device string) (*user.User, *Session, error) {
err := ctx.Err()
if err != nil {
return nil, nil, ErrCanceled
}
newCtx, cancelFunc := detachWithTimeout(ctx, TransactionTimeout)
defer cancelFunc()
user, err := s.u.CreateUser(newCtx, login, password, false)
if err != nil {
return nil, nil, err
}
_, err = s.i.UseInvite(newCtx, invite)
if err != nil {
s.u.DeleteUser(newCtx, user)
return nil, nil, err
}
_, err = s.u.ActivateUser(newCtx, user)
if err != nil {
s.u.DeleteUser(newCtx, user)
return nil, nil, err
}
return s.LoginUser(newCtx, login, password, address, device, false)
}
func (s *Service) LoginUser(ctx context.Context, login, password, address, device string, isLong bool) (*user.User, *Session, error) {
token, err := s.generateToken()
if err != nil {
return nil, nil, err
}
user, err := s.u.UserByLogin(ctx, login)
if err != nil {
return nil, nil, err
}
err = s.u.ValidatePassword(ctx, user, password)
if err != nil {
return nil, nil, err
}
var duration time.Duration
if isLong {
duration = s.longDuration
} else {
duration = s.shortDuration
}
session, err := s.r.CreateSession(ctx, token, user.Id, address, device, time.Now().UTC().Add(duration), isLong)
if err != nil {
return nil, nil, err
}
return user, session, nil
}
func (s *Service) BumpSession(ctx context.Context, session *Session) (*Session, error) {
var duration time.Duration
if session.IsLong {
duration = s.longDuration
} else {
duration = s.shortDuration
}
halfExpire := time.Now().UTC().Add(duration / 2)
if session.ExpiresAt.After(halfExpire) {
return session, nil
}
value := *session
cloned := &value
cloned.ExpiresAt = time.Now().UTC().Add(duration)
err := s.r.UpdateSession(ctx, cloned)
if err != nil {
return nil, err
}
return cloned, nil
}
func (s *Service) SessionByToken(ctx context.Context, token string) (*Session, error) {
return s.r.SessionByToken(ctx, token)
}
func (s *Service) CountSessions(ctx context.Context, user *user.User) (int64, error) {
return s.r.CountSessions(ctx, user.Id)
}
func (s *Service) Sessions(ctx context.Context, user *user.User, limit, offset int64) ([]*Session, error) {
return s.r.Sessions(ctx, user.Id, limit, offset)
}
func (s *Service) DeleteSession(ctx context.Context, session *Session) error {
return s.r.DeleteSession(ctx, session.Token)
}
func (s *Service) DeleteSessions(ctx context.Context, user *user.User) error {
return s.r.DeleteSessions(ctx, user.Id)
}
func (s *Service) CleanupSessions(ctx context.Context) error {
return s.r.CleanupSessions(ctx)
}