-
-
Notifications
You must be signed in to change notification settings - Fork 122
/
service.go
164 lines (130 loc) · 4.52 KB
/
service.go
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// Copyright (c) 2021 - 2024, Ludvig Lundgren and the autobrr contributors.
// SPDX-License-Identifier: GPL-2.0-or-later
package auth
import (
"context"
"github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/internal/logger"
"github.com/autobrr/autobrr/internal/user"
"github.com/autobrr/autobrr/pkg/argon2id"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
type Service interface {
GetUserCount(ctx context.Context) (int, error)
Login(ctx context.Context, username, password string) (*domain.User, error)
CreateUser(ctx context.Context, req domain.CreateUserRequest) error
UpdateUser(ctx context.Context, req domain.UpdateUserRequest) error
CreateHash(password string) (hash string, err error)
ComparePasswordAndHash(password string, hash string) (match bool, err error)
}
type service struct {
log zerolog.Logger
userSvc user.Service
}
func NewService(log logger.Logger, userSvc user.Service) Service {
return &service{
log: log.With().Str("module", "auth").Logger(),
userSvc: userSvc,
}
}
func (s *service) GetUserCount(ctx context.Context) (int, error) {
return s.userSvc.GetUserCount(ctx)
}
func (s *service) Login(ctx context.Context, username, password string) (*domain.User, error) {
if username == "" || password == "" {
return nil, errors.New("empty credentials supplied")
}
// find user
u, err := s.userSvc.FindByUsername(ctx, username)
if err != nil {
s.log.Error().Err(err).Msgf("could not find user by username: %v", username)
return nil, errors.Wrapf(err, "invalid login: %s", username)
}
if u == nil {
return nil, errors.Errorf("invalid login: %s", username)
}
// compare password from request and the saved password
match, err := s.ComparePasswordAndHash(password, u.Password)
if err != nil {
return nil, errors.New("error checking credentials")
}
if !match {
s.log.Error().Msg("bad credentials")
return nil, errors.Errorf("invalid login: %s", username)
}
return u, nil
}
func (s *service) CreateUser(ctx context.Context, req domain.CreateUserRequest) error {
if req.Username == "" {
return errors.New("validation error: empty username supplied")
} else if req.Password == "" {
return errors.New("validation error: empty password supplied")
}
userCount, err := s.userSvc.GetUserCount(ctx)
if err != nil {
return err
}
if userCount > 0 {
return errors.New("only 1 user account is supported at the moment")
}
hashed, err := s.CreateHash(req.Password)
if err != nil {
return errors.New("failed to hash password")
}
req.Password = hashed
if err := s.userSvc.CreateUser(ctx, req); err != nil {
s.log.Error().Err(err).Msgf("could not create user: %s", req.Username)
return errors.New("failed to create new user")
}
return nil
}
func (s *service) UpdateUser(ctx context.Context, req domain.UpdateUserRequest) error {
if req.PasswordCurrent == "" {
return errors.New("validation error: empty current password supplied")
}
if req.PasswordNew != "" && req.PasswordCurrent != "" {
if req.PasswordNew == req.PasswordCurrent {
return errors.New("validation error: new password must be different")
}
}
// find user
u, err := s.userSvc.FindByUsername(ctx, req.UsernameCurrent)
if err != nil {
s.log.Trace().Err(err).Msgf("invalid login %v", req.UsernameCurrent)
return errors.Wrapf(err, "invalid login: %s", req.UsernameCurrent)
}
if u == nil {
return errors.Errorf("invalid login: %s", req.UsernameCurrent)
}
// compare password from request and the saved password
match, err := s.ComparePasswordAndHash(req.PasswordCurrent, u.Password)
if err != nil {
return errors.New("error checking credentials")
}
if !match {
s.log.Debug().Msgf("bad credentials: %q | %q", req.UsernameCurrent, req.PasswordCurrent)
return errors.Errorf("invalid login: %s", req.UsernameCurrent)
}
if req.PasswordNew != "" {
hashed, err := s.CreateHash(req.PasswordNew)
if err != nil {
return errors.New("failed to hash password")
}
req.PasswordNewHash = hashed
}
if err := s.userSvc.Update(ctx, req); err != nil {
s.log.Error().Err(err).Msgf("could not change password for user: %s", req.UsernameCurrent)
return errors.New("failed to change password")
}
return nil
}
func (s *service) ComparePasswordAndHash(password string, hash string) (match bool, err error) {
return argon2id.ComparePasswordAndHash(password, hash)
}
func (s *service) CreateHash(password string) (hash string, err error) {
if password == "" {
return "", errors.New("must supply non empty password to CreateHash")
}
return argon2id.CreateHash(password, argon2id.DefaultParams)
}