2025-11-10 00:19:30 +00:00
|
|
|
package auth
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
"errors"
|
|
|
|
|
|
2025-11-26 01:09:42 +00:00
|
|
|
"github.com/get-drexa/drexa/internal/user"
|
|
|
|
|
"github.com/google/uuid"
|
2025-11-10 00:19:30 +00:00
|
|
|
"github.com/uptrace/bun"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type LoginResult struct {
|
2025-11-26 01:09:42 +00:00
|
|
|
User *user.User
|
2025-11-10 00:19:30 +00:00
|
|
|
AccessToken string
|
|
|
|
|
RefreshToken string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ErrInvalidCredentials = errors.New("invalid credentials")
|
|
|
|
|
var ErrUserExists = errors.New("user already exists")
|
|
|
|
|
|
|
|
|
|
type Service struct {
|
|
|
|
|
db *bun.DB
|
2025-11-26 01:09:42 +00:00
|
|
|
userService *user.Service
|
2025-11-10 00:19:30 +00:00
|
|
|
tokenConfig TokenConfig
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type registerOptions struct {
|
|
|
|
|
displayName string
|
|
|
|
|
email string
|
|
|
|
|
password string
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 01:09:42 +00:00
|
|
|
func NewService(db *bun.DB, userService *user.Service, tokenConfig TokenConfig) *Service {
|
2025-11-10 00:19:30 +00:00
|
|
|
return &Service{
|
|
|
|
|
db: db,
|
2025-11-26 01:09:42 +00:00
|
|
|
userService: userService,
|
2025-11-10 00:19:30 +00:00
|
|
|
tokenConfig: tokenConfig,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Service) LoginWithEmailAndPassword(ctx context.Context, email, password string) (*LoginResult, error) {
|
2025-11-26 01:09:42 +00:00
|
|
|
var u user.User
|
2025-11-10 00:19:30 +00:00
|
|
|
|
2025-11-26 01:09:42 +00:00
|
|
|
err := s.db.NewSelect().Model(&u).Where("email = ?", email).Scan(ctx)
|
2025-11-10 00:19:30 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 01:09:42 +00:00
|
|
|
ok, err := VerifyPassword(password, u.Password)
|
2025-11-10 00:19:30 +00:00
|
|
|
if err != nil || !ok {
|
|
|
|
|
return nil, ErrInvalidCredentials
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 01:09:42 +00:00
|
|
|
at, err := GenerateAccessToken(&u, &s.tokenConfig)
|
2025-11-10 00:19:30 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 01:09:42 +00:00
|
|
|
rt, err := GenerateRefreshToken(&u, &s.tokenConfig)
|
2025-11-10 00:19:30 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = s.db.NewInsert().Model(rt).Exec(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &LoginResult{
|
2025-11-26 01:09:42 +00:00
|
|
|
User: &u,
|
2025-11-10 00:19:30 +00:00
|
|
|
AccessToken: at,
|
|
|
|
|
RefreshToken: hex.EncodeToString(rt.Token),
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Service) Register(ctx context.Context, opts registerOptions) (*LoginResult, error) {
|
2025-11-26 01:09:42 +00:00
|
|
|
u := user.User{
|
2025-11-10 00:19:30 +00:00
|
|
|
Email: opts.email,
|
|
|
|
|
DisplayName: opts.displayName,
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 01:09:42 +00:00
|
|
|
exists, err := s.db.NewSelect().Model(&u).Where("email = ?", opts.email).Exists(ctx)
|
2025-11-10 00:19:30 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if exists {
|
|
|
|
|
return nil, ErrUserExists
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 01:09:42 +00:00
|
|
|
u.Password, err = HashPassword(opts.password)
|
2025-11-10 00:19:30 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 01:09:42 +00:00
|
|
|
_, err = s.db.NewInsert().Model(&u).Exec(ctx)
|
2025-11-10 00:19:30 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 01:09:42 +00:00
|
|
|
at, err := GenerateAccessToken(&u, &s.tokenConfig)
|
2025-11-10 00:19:30 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 01:09:42 +00:00
|
|
|
rt, err := GenerateRefreshToken(&u, &s.tokenConfig)
|
2025-11-10 00:19:30 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &LoginResult{
|
2025-11-26 01:09:42 +00:00
|
|
|
User: &u,
|
2025-11-10 00:19:30 +00:00
|
|
|
AccessToken: at,
|
|
|
|
|
RefreshToken: hex.EncodeToString(rt.Token),
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
2025-11-26 01:09:42 +00:00
|
|
|
|
|
|
|
|
func (s *Service) AuthenticateWithAccessToken(ctx context.Context, token string) (*user.User, error) {
|
|
|
|
|
claims, err := ParseAccessToken(token, &s.tokenConfig)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
id, err := uuid.Parse(claims.Subject)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, newInvalidAccessTokenError(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return s.userService.UserByID(ctx, id)
|
|
|
|
|
}
|