lens @ fbcac585cf626917e2baf1d0065c7b632341ba01

  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}