1package service
2
3import (
4 "bytes"
5 "context"
6 "crypto/aes"
7 "crypto/cipher"
8 "crypto/rand"
9 "encoding/gob"
10 "errors"
11 "io"
12
13 "golang.org/x/crypto/bcrypt"
14
15 "git.sr.ht/~gabrielgio/img/pkg/database/repository"
16)
17
18type AuthController struct {
19 authRepository repository.AuthRepository
20 userRepository repository.UserRepository
21 key []byte
22}
23
24func NewAuthController(
25 authRepository repository.AuthRepository,
26 userRepository repository.UserRepository,
27 key []byte,
28) *AuthController {
29 return &AuthController{
30 authRepository: authRepository,
31 userRepository: userRepository,
32 key: key,
33 }
34}
35
36func (c *AuthController) Login(ctx context.Context, username, password []byte) ([]byte, error) {
37 id, err := c.authRepository.GetIDByUsername(ctx, string(username))
38 if err != nil {
39 return nil, err
40 }
41
42 hashedPassword, err := c.authRepository.GetPassword(ctx, id)
43 if err != nil {
44 return nil, err
45 }
46
47 if err := bcrypt.CompareHashAndPassword(hashedPassword, password); err != nil {
48 return nil, err
49 }
50
51 token := &Token{
52 UserID: id,
53 Username: string(username),
54 }
55 return WriteToken(token, c.key)
56}
57
58// InitialRegister register a initial user, it will validate if there is another
59// user stored already. If so an error `InvlidaInput` will be returned
60func (c *AuthController) InitialRegister(ctx context.Context, username, password []byte, path []byte) error {
61 exist, err := c.userRepository.Any(ctx)
62 if err != nil {
63 return err
64 }
65
66 if exist {
67 return InvlidInput
68 }
69
70 hash, err := bcrypt.GenerateFromPassword(password, bcrypt.MinCost)
71 if err != nil {
72 return err
73 }
74
75 _, err = c.userRepository.Create(ctx, &repository.CreateUser{
76 Username: string(username),
77 Password: hash,
78 IsAdmin: true,
79 Path: string(path),
80 })
81
82 return err
83}
84
85func (u *AuthController) List(ctx context.Context) ([]*repository.User, error) {
86 return u.userRepository.List(ctx)
87}
88
89func (u *AuthController) Get(ctx context.Context, id uint) (*repository.User, error) {
90 return u.userRepository.Get(ctx, id)
91}
92
93func (u *AuthController) Delete(ctx context.Context, id uint) error {
94 return u.userRepository.Delete(ctx, id)
95}
96
97func (u *AuthController) Upsert(
98 ctx context.Context,
99 id *uint,
100 username string,
101 name string,
102 password []byte,
103 isAdmin bool,
104 path string,
105) error {
106 if id != nil {
107 if err := u.userRepository.Update(ctx, *id, &repository.UpdateUser{
108 Username: username,
109 Name: name,
110 IsAdmin: isAdmin,
111 Path: path,
112 }); err != nil {
113 return err
114 }
115
116 if len(password) > 0 {
117 hash, err := bcrypt.GenerateFromPassword(password, bcrypt.MinCost)
118 if err != nil {
119 return err
120 }
121
122 return u.userRepository.UpdatePassword(ctx, *id, hash)
123 }
124 return nil
125 }
126
127 hash, err := bcrypt.GenerateFromPassword(password, bcrypt.MinCost)
128 if err != nil {
129 return err
130 }
131
132 _, err = u.userRepository.Create(ctx, &repository.CreateUser{
133 Username: username,
134 Name: name,
135 Password: hash,
136 IsAdmin: isAdmin,
137 Path: path,
138 })
139
140 return err
141}
142
143type (
144 AuthKey string
145 Token struct {
146 UserID uint
147 Username string
148 }
149)
150
151const TokenKey AuthKey = "token"
152
153func ReadToken(data []byte, key []byte) (*Token, error) {
154 block, err := aes.NewCipher(key)
155 if err != nil {
156 return nil, err
157 }
158
159 aesgcm, err := cipher.NewGCM(block)
160 if err != nil {
161 panic(err.Error())
162 }
163
164 nonceSize := aesgcm.NonceSize()
165 if len(data) < nonceSize {
166 return nil, errors.New("nonce size greater than data's size")
167 }
168
169 nonce, ciphertext := data[:nonceSize], data[nonceSize:]
170 plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
171 if err != nil {
172 return nil, err
173 }
174
175 r := bytes.NewReader(plaintext)
176 var token Token
177 dec := gob.NewDecoder(r)
178 if err = dec.Decode(&token); err != nil {
179 return nil, err
180 }
181 return &token, nil
182}
183
184func WriteToken(token *Token, key []byte) ([]byte, error) {
185 block, err := aes.NewCipher(key)
186 if err != nil {
187 return nil, err
188 }
189
190 aesgcm, err := cipher.NewGCM(block)
191 if err != nil {
192 return nil, err
193 }
194
195 var buffer bytes.Buffer
196 enc := gob.NewEncoder(&buffer)
197 if err := enc.Encode(token); err != nil {
198 return nil, err
199 }
200 nonce := make([]byte, aesgcm.NonceSize())
201 if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
202 return nil, err
203 }
204
205 ciphertext := aesgcm.Seal(nonce, nonce, buffer.Bytes(), nil)
206 return ciphertext, nil
207}