91 lines
2.2 KiB
Go
91 lines
2.2 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"gitea.michaelthomson.dev/mthomson/habits/internal/auth"
|
|
userrepository "gitea.michaelthomson.dev/mthomson/habits/internal/user/repository"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
var (
|
|
ErrNotFound error = errors.New("user cannot be found")
|
|
ErrUnauthorized error = errors.New("user password incorrect")
|
|
)
|
|
|
|
type UserRepository interface {
|
|
Create(ctx context.Context, user userrepository.UserRow) (userrepository.UserRow, error)
|
|
GetByEmail(ctx context.Context, email string) (userrepository.UserRow, error)
|
|
}
|
|
|
|
type AuthService struct {
|
|
logger *slog.Logger
|
|
jwtKey []byte
|
|
userRepository UserRepository
|
|
argon2IdHash *auth.Argon2IdHash
|
|
}
|
|
|
|
func NewAuthService(logger *slog.Logger, jwtKey []byte, userRepository UserRepository, argon2IdHash *auth.Argon2IdHash) *AuthService {
|
|
return &AuthService{
|
|
logger: logger,
|
|
jwtKey: jwtKey,
|
|
userRepository: userRepository,
|
|
argon2IdHash: argon2IdHash,
|
|
}
|
|
}
|
|
|
|
func (a AuthService) Login(ctx context.Context, email string, password string) (string, error) {
|
|
// get user if exists
|
|
userRow, err := a.userRepository.GetByEmail(ctx, email)
|
|
if err != nil {
|
|
if err == userrepository.ErrNotFound {
|
|
return "", ErrNotFound
|
|
}
|
|
|
|
a.logger.ErrorContext(ctx, err.Error())
|
|
return "", err
|
|
}
|
|
|
|
// compare hashed passswords
|
|
err = a.argon2IdHash.Compare(userRow.HashedPassword, userRow.Salt, []byte(password))
|
|
|
|
if err == auth.ErrNoMatch {
|
|
return "", ErrUnauthorized
|
|
}
|
|
|
|
if err != nil {
|
|
a.logger.ErrorContext(ctx, err.Error())
|
|
return "", err
|
|
}
|
|
|
|
// create token and return it
|
|
token, err := a.CreateToken(ctx, email)
|
|
if err != nil {
|
|
a.logger.ErrorContext(ctx, err.Error())
|
|
return "", err
|
|
}
|
|
|
|
return token, nil
|
|
}
|
|
|
|
func (a AuthService) CreateToken(ctx context.Context, email string) (string, error) {
|
|
// Create a new JWT token with claims
|
|
claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|
"sub": email,
|
|
"iss": "todo-app",
|
|
"aud": "user",
|
|
"exp": time.Now().Add(time.Hour).Unix(),
|
|
"iat": time.Now().Unix(),
|
|
})
|
|
|
|
tokenString, err := claims.SignedString(a.jwtKey)
|
|
if err != nil {
|
|
a.logger.ErrorContext(ctx, err.Error())
|
|
}
|
|
|
|
return tokenString, nil
|
|
}
|