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 }