lens @ 13fd911873a7df311544b1ec916c98ee05c91bbc

  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}