From c8e1328164e9ffbd681c3c0e449f1e6b9856b896 Mon Sep 17 00:00:00 2001 From: Gabriel Arakaki Giovanini Date: Sun, 26 Feb 2023 19:54:48 +0100 Subject: feat: Inicial commit It contains rough template for the server and runners. It contains rough template for the server and runners. --- pkg/components/auth/controller.go | 57 ++++++++++ pkg/components/auth/controller_test.go | 190 +++++++++++++++++++++++++++++++++ pkg/components/auth/model.go | 32 ++++++ 3 files changed, 279 insertions(+) create mode 100644 pkg/components/auth/controller.go create mode 100644 pkg/components/auth/controller_test.go create mode 100644 pkg/components/auth/model.go (limited to 'pkg/components/auth') diff --git a/pkg/components/auth/controller.go b/pkg/components/auth/controller.go new file mode 100644 index 0000000..4da6071 --- /dev/null +++ b/pkg/components/auth/controller.go @@ -0,0 +1,57 @@ +package auth + +import ( + "context" + + "golang.org/x/crypto/bcrypt" + + "git.sr.ht/~gabrielgio/img/pkg/ext" +) + +type Controller struct { + repository Repository + key []byte +} + +func NewController(repository Repository, key []byte) *Controller { + return &Controller{ + repository: repository, + key: key, + } +} + +func (c *Controller) Login(ctx context.Context, username, password []byte) ([]byte, error) { + id, err := c.repository.GetIDByUsername(ctx, string(username)) + if err != nil { + return nil, err + } + + hashedPassword, err := c.repository.GetPassword(ctx, id) + if err != nil { + return nil, err + } + + if err := bcrypt.CompareHashAndPassword(hashedPassword, password); err != nil { + return nil, err + } + + token := &ext.Token{ + UserID: id, + Username: string(username), + } + return ext.WriteToken(token, c.key) +} + +func (c *Controller) Register(ctx context.Context, username, password []byte) error { + hash, err := bcrypt.GenerateFromPassword(password, bcrypt.MinCost) + if err != nil { + return err + } + + _, err = c.repository.Create(ctx, &CreateUser{ + Username: string(username), + Password: hash, + }) + + return err +} diff --git a/pkg/components/auth/controller_test.go b/pkg/components/auth/controller_test.go new file mode 100644 index 0000000..33aa901 --- /dev/null +++ b/pkg/components/auth/controller_test.go @@ -0,0 +1,190 @@ +//go:build unit + +package auth + +import ( + "context" + "errors" + "testing" + + "github.com/samber/lo" + + "git.sr.ht/~gabrielgio/img/pkg/ext" + "git.sr.ht/~gabrielgio/img/pkg/testkit" +) + +type ( + scene struct { + ctx context.Context + mockRepository *MockUserRepository + controller Controller + } + + mockUser struct { + id uint + username string + password []byte + } + + MockUserRepository struct { + index uint + users []*mockUser + err error + } +) + +var ( + _ Repository = &MockUserRepository{} + key = []byte("6368616e676520746869732070617373") +) + +func setUp() *scene { + mockUserRepository := &MockUserRepository{} + return &scene{ + ctx: context.Background(), + mockRepository: mockUserRepository, + controller: *NewController(mockUserRepository, key), + } +} + +func TestRegisterAndLogin(t *testing.T) { + testCases := []struct { + name string + username string + password []byte + }{ + { + name: "Normal register", + username: "username", + password: []byte("password"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + scene := setUp() + + err := scene.controller.Register(scene.ctx, []byte(tc.username), tc.password) + testkit.TestFatalError(t, "Register", err) + + userID := scene.mockRepository.GetLastId() + + user, err := scene.mockRepository.Get(scene.ctx, userID) + testkit.TestFatalError(t, "Get", err) + testkit.TestValue(t, "Register", tc.username, user.Username) + + auth, err := scene.controller.Login(scene.ctx, []byte(tc.username), tc.password) + testkit.TestFatalError(t, "Login", err) + + token, err := ext.ReadToken(auth, key) + testkit.TestFatalError(t, "Login", err) + + testkit.TestValue(t, "Login", tc.username, token.Username) + testkit.TestValue(t, "Login", userID, token.UserID) + }) + } +} + +func toUser(m *mockUser, _ int) *User { + return &User{ + ID: m.id, + Username: m.username, + } +} + +func (m *MockUserRepository) GetLastId() uint { + return m.index +} + +func (m *MockUserRepository) List(ctx context.Context) ([]*User, error) { + if m.err != nil { + return nil, m.err + } + + return lo.Map(m.users, toUser), nil +} + +func (m *MockUserRepository) Get(ctx context.Context, id uint) (*User, error) { + if m.err != nil { + return nil, m.err + } + + for _, m := range m.users { + if m.id == id { + return toUser(m, 0), nil + } + } + return nil, errors.New("Item not found") +} + +func (m *MockUserRepository) GetIDByUsername(ctx context.Context, username string) (uint, error) { + if m.err != nil { + return 0, m.err + } + + for _, m := range m.users { + if m.username == username { + return m.id, nil + } + } + return 0, errors.New("Item not found") +} + +func (m *MockUserRepository) GetPassword(ctx context.Context, id uint) ([]byte, error) { + if m.err != nil { + return nil, m.err + } + + for _, m := range m.users { + if m.id == id { + return m.password, nil + } + } + return nil, errors.New("Item not found") +} + +func (m *MockUserRepository) Create(ctx context.Context, createUser *CreateUser) (uint, error) { + if m.err != nil { + return 0, m.err + } + + m.index++ + + m.users = append(m.users, &mockUser{ + id: m.index, + username: createUser.Username, + password: createUser.Password, + }) + + return m.index, nil +} + +func (m *MockUserRepository) Update(ctx context.Context, id uint, update *UpdateUser) error { + if m.err != nil { + return m.err + } + + for _, m := range m.users { + if m.id == id { + m.username = update.Username + } + } + return nil +} + +func remove[T any](slice []T, s int) []T { + return append(slice[:s], slice[s+1:]...) +} + +func (r *MockUserRepository) Delete(ctx context.Context, id uint) error { + if r.err != nil { + return r.err + } + + for i, m := range r.users { + if m.id == id { + r.users = remove(r.users, i) + } + } + return nil +} diff --git a/pkg/components/auth/model.go b/pkg/components/auth/model.go new file mode 100644 index 0000000..e46ef49 --- /dev/null +++ b/pkg/components/auth/model.go @@ -0,0 +1,32 @@ +package auth + +import "context" + +type ( + // TODO: move to user later + User struct { + ID uint + Username string + Name string + } + + // TODO: move to user later + UpdateUser struct { + Username string + Name string + } + + // TODO: move to user later + CreateUser struct { + Username string + Name string + Password []byte + } + + Repository interface { + GetIDByUsername(ctx context.Context, username string) (uint, error) + GetPassword(ctx context.Context, id uint) ([]byte, error) + // TODO: move to user later + Create(ctx context.Context, createUser *CreateUser) (uint, error) + } +) -- cgit v1.2.3