lens @ d2a9e162352646e7f1550823c3d88496590f8760

  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: string(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 Token struct {
144	UserID   uint
145	Username string
146}
147
148func ReadToken(data []byte, key []byte) (*Token, error) {
149	block, err := aes.NewCipher(key)
150	if err != nil {
151		return nil, err
152	}
153
154	aesgcm, err := cipher.NewGCM(block)
155	if err != nil {
156		panic(err.Error())
157	}
158
159	nonceSize := aesgcm.NonceSize()
160	if len(data) < nonceSize {
161		return nil, errors.New("nonce size greater than data's size")
162	}
163
164	nonce, ciphertext := data[:nonceSize], data[nonceSize:]
165	plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
166	if err != nil {
167		return nil, err
168	}
169
170	r := bytes.NewReader(plaintext)
171	var token Token
172	dec := gob.NewDecoder(r)
173	if err = dec.Decode(&token); err != nil {
174		return nil, err
175	}
176	return &token, nil
177}
178
179func WriteToken(token *Token, key []byte) ([]byte, error) {
180	block, err := aes.NewCipher(key)
181	if err != nil {
182		return nil, err
183	}
184
185	aesgcm, err := cipher.NewGCM(block)
186	if err != nil {
187		return nil, err
188	}
189
190	var buffer bytes.Buffer
191	enc := gob.NewEncoder(&buffer)
192	if err := enc.Encode(token); err != nil {
193		return nil, err
194	}
195	nonce := make([]byte, aesgcm.NonceSize())
196	if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
197		return nil, err
198	}
199
200	ciphertext := aesgcm.Seal(nonce, nonce, buffer.Bytes(), nil)
201	return ciphertext, nil
202}