summaryrefslogtreecommitdiff
path: root/internal/service/auth.go
blob: c52d2f0d9d2e7381f0860c5640e0a24fb8c34dd0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package service

import (
	"context"
	"fmt"

	"codeberg.org/snonux/player/internal/auth"
	"codeberg.org/snonux/player/internal/clock"
	"codeberg.org/snonux/player/internal/model"
	"codeberg.org/snonux/player/internal/repository"
)

// authService is the concrete implementation of AuthService.
type authService struct {
	store  repository.AuthServiceStore
	clock  clock.Clock
	hasher auth.Hasher
	sm     *auth.SessionManager
}

// NewAuthService creates a concrete AuthService.
func NewAuthService(store repository.AuthServiceStore, clk clock.Clock, hasher auth.Hasher, sm *auth.SessionManager) AuthService {
	return &authService{
		store:  store,
		clock:  clk,
		hasher: hasher,
		sm:     sm,
	}
}

// Bootstrap creates the first admin user when no users exist.
func (s *authService) Bootstrap(ctx context.Context, username, password string) (*AuthResult, error) {
	count, err := s.store.CountUsers(ctx)
	if err != nil {
		return nil, fmt.Errorf("count users: %w", err)
	}
	if count > 0 {
		return nil, ErrAlreadyBootstrapped
	}

	hash, err := s.hasher.Hash(password)
	if err != nil {
		return nil, fmt.Errorf("hash password: %w", err)
	}

	user := &model.User{
		Username:     username,
		PasswordHash: hash,
		IsAdmin:      true,
		CreatedAt:    s.clock.Now(),
	}

	id, err := s.store.CreateUser(ctx, user)
	if err != nil {
		return nil, fmt.Errorf("create user: %w", err)
	}
	user.ID = id

	sessID, err := s.sm.CreateSession(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("create session: %w", err)
	}

	return &AuthResult{User: user, SessionID: sessID}, nil
}

// Login authenticates a user and creates a session.
func (s *authService) Login(ctx context.Context, username, password string) (*AuthResult, error) {
	user, err := s.store.GetUserByUsername(ctx, username)
	if err != nil {
		return nil, fmt.Errorf("get user: %w", err)
	}
	if user == nil {
		return nil, ErrInvalidCredentials
	}
	if err := s.hasher.Compare(user.PasswordHash, password); err != nil {
		return nil, ErrInvalidCredentials
	}

	sessID, err := s.sm.CreateSession(ctx, user.ID)
	if err != nil {
		return nil, fmt.Errorf("create session: %w", err)
	}

	return &AuthResult{User: user, SessionID: sessID}, nil
}