Initial
This commit is contained in:
commit
97dc0a3570
78 files changed
+7054
No files matched your search
@@ -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)
|
||||
}
|
||||
Reference in new issue
Block a user