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
85type Token struct {
86 UserID uint
87 Username string
88}
89
90func ReadToken(data []byte, key []byte) (*Token, error) {
91 block, err := aes.NewCipher(key)
92 if err != nil {
93 return nil, err
94 }
95
96 aesgcm, err := cipher.NewGCM(block)
97 if err != nil {
98 panic(err.Error())
99 }
100
101 nonceSize := aesgcm.NonceSize()
102 if len(data) < nonceSize {
103 return nil, errors.New("nonce size greater than data's size")
104 }
105
106 nonce, ciphertext := data[:nonceSize], data[nonceSize:]
107 plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
108 if err != nil {
109 return nil, err
110 }
111
112 r := bytes.NewReader(plaintext)
113 var token Token
114 dec := gob.NewDecoder(r)
115 if err = dec.Decode(&token); err != nil {
116 return nil, err
117 }
118 return &token, nil
119}
120
121func WriteToken(token *Token, key []byte) ([]byte, error) {
122 block, err := aes.NewCipher(key)
123 if err != nil {
124 return nil, err
125 }
126
127 aesgcm, err := cipher.NewGCM(block)
128 if err != nil {
129 return nil, err
130 }
131
132 var buffer bytes.Buffer
133 enc := gob.NewEncoder(&buffer)
134 if err := enc.Encode(token); err != nil {
135 return nil, err
136 }
137 nonce := make([]byte, aesgcm.NonceSize())
138 if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
139 return nil, err
140 }
141
142 ciphertext := aesgcm.Seal(nonce, nonce, buffer.Bytes(), nil)
143 return ciphertext, nil
144}