Skip to content

Commit

Permalink
feat: add demo5, with bad functions
Browse files Browse the repository at this point in the history
  • Loading branch information
youshy authored and addetz committed Aug 15, 2023
1 parent a7de51e commit 2787a4d
Show file tree
Hide file tree
Showing 17 changed files with 1,048 additions and 0 deletions.
48 changes: 48 additions & 0 deletions demo5/data/note_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package data

import (
"github.com/addetz/secure-code-go/demo4/db"
"github.com/google/uuid"
)

type SecretNote struct {
ID string `json:"id"`
Username string `json:"username"`
Text string `json:"text"`
}

// SecretNoteService maintains the user notes.
type SecretNoteService struct {
dbService db.DatabaseService
}

// NewSecretNoteService creates a SecretNoteService that is ready to use.
func NewSecretNoteService(dbService db.DatabaseService) *SecretNoteService {
return &SecretNoteService{
dbService: dbService,
}
}

// Add adds a new SecretNote for the given user by using the SecretNoteService.
func (ns *SecretNoteService) Add(user string, n SecretNote) error {
id := uuid.New().String()
return ns.dbService.AddNote(id, user, n.Text)
}

// Get returns all the SecretNotes of a given user by using the SecretNoteService.
func (ns *SecretNoteService) GetAll(user string) ([]SecretNote, error) {
dbNotes, err := ns.dbService.GetUserNotes(user)
if err != nil {
return nil, err
}
var notes []SecretNote
for _, n := range dbNotes {
notes = append(notes, SecretNote{
ID: n.ID,
Username: n.Username,
Text: n.Text,
})
}

return notes, nil
}
66 changes: 66 additions & 0 deletions demo5/data/note_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package data_test

import (
"errors"
"testing"

"github.com/addetz/secure-code-go/demo4/data"
"github.com/addetz/secure-code-go/demo4/db"
"github.com/addetz/secure-code-go/demo4/mocks"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestAddNote(t *testing.T) {
mockDB := new(mocks.DatabaseServiceMock)
notes := data.NewSecretNoteService(mockDB)
user := "user1"
note := data.SecretNote{
Text: "My Secret Note",
}
mockDB.On("AddNote", mock.AnythingOfType("string"), user, note.Text).Return(nil)
err := notes.Add(user, note)
assert.Nil(t, err)
}

func TestGetAllNotes(t *testing.T) {
t.Run("no notes found", func(t *testing.T) {
user := "user1"
mockDB := new(mocks.DatabaseServiceMock)
noteService := data.NewSecretNoteService(mockDB)
mockDB.On("GetUserNotes", user).Return(nil, errors.New("no notes found"))
notes, err := noteService.GetAll(user)
assert.Nil(t, notes)
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "no notes found")
})
t.Run("notes found", func(t *testing.T) {
user := "user1"
dbNotes := []db.Note{
{
ID: uuid.New().String(),
Username: user,
Text: "My first note",
},
{
ID: uuid.New().String(),
Username: user,
Text: "My second note",
},
}
mockDB := new(mocks.DatabaseServiceMock)
noteService := data.NewSecretNoteService(mockDB)
mockDB.On("GetUserNotes", user).Return(dbNotes, nil)
notes, err := noteService.GetAll(user)
assert.Nil(t, err)
assert.NotNil(t, notes)
assert.Equal(t, len(dbNotes), len(notes))
for i, n := range dbNotes {
assert.Equal(t, n.ID, notes[i].ID)
assert.Equal(t, n.Username, notes[i].Username)
assert.Equal(t, n.Text, notes[i].Text)

}
})
}
62 changes: 62 additions & 0 deletions demo5/data/user_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package data

import (
"github.com/addetz/secure-code-go/demo4/db"
"github.com/pkg/errors"

passwordvalidator "github.com/wagslane/go-password-validator"
"golang.org/x/crypto/bcrypt"
)

const minEntropyBits = 60

type User struct {
Username, Password string
}

// UserService holds
type UserService struct {
dbService db.DatabaseService
}

// NewUserService creates a ready to use user service.
func NewUserService(dbService db.DatabaseService) *UserService {
return &UserService{
dbService: dbService,
}
}

// Add validates a user password and creates a new user.
func (us *UserService) Add(name, password string) error {
if _, err := us.dbService.GetUser(name); err == nil {
return errors.New("user exists already, please log in instead")
}

err := passwordvalidator.Validate(password, minEntropyBits)
if err != nil {
return errors.Wrap(err, "validate new user password")
}

hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
return us.dbService.AddUser(name, string(hashedPassword))
}

// ValidatePassword checks the provided password of an existing user.
func (us *UserService) ValidatePassword(name, providedPwd string) error {
user, err := us.dbService.GetUser(name)
if err != nil {
return errors.Wrap(err, "user does not exist")
}
return bcrypt.CompareHashAndPassword([]byte(user.Pwd), []byte(providedPwd))
}

// ValidateUser checks the provided username belongs to an existing user.
func (us *UserService) ValidateUser(name string) error {
if _, err := us.dbService.GetUser(name); err != nil {
return errors.New("user not found")
}
return nil
}
84 changes: 84 additions & 0 deletions demo5/data/user_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package data_test

import (
"errors"
"testing"

"github.com/addetz/secure-code-go/demo4/data"
"github.com/addetz/secure-code-go/demo4/db"
"github.com/addetz/secure-code-go/demo4/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"golang.org/x/crypto/bcrypt"
)

func TestAdd(t *testing.T) {
t.Run("insufficient password", func(t *testing.T) {
name := "user1"
mockDB := new(mocks.DatabaseServiceMock)
us := data.NewUserService(mockDB)
mockDB.On("GetUser", name).Return(nil, errors.New("no user exists"))
err := us.Add(name, "test")
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "validate new user password: insecure password")
})
t.Run("successful add", func(t *testing.T) {
name := "user1"
password := "test-horse-pen-clam"
mockDB := new(mocks.DatabaseServiceMock)
us := data.NewUserService(mockDB)
mockDB.On("GetUser", name).Return(nil, errors.New("no user exists"))
mockDB.On("AddUser", name, mock.AnythingOfType("string")).Return(nil)
err := us.Add(name, password)
assert.Nil(t, err)
})
t.Run("duplicate user", func(t *testing.T) {
name := "user1"
password := "test-horse-pen-clam"
mockDB := new(mocks.DatabaseServiceMock)
us := data.NewUserService(mockDB)
mockDB.On("GetUser", name).Return(nil, nil)
err := us.Add(name, password)
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "user exists already")
})
}

func TestValidate(t *testing.T) {
t.Run("successful validate", func(t *testing.T) {
name := "user1"
password := "test-horse-pen-clam"
expected, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
mockDB := new(mocks.DatabaseServiceMock)
us := data.NewUserService(mockDB)
mockDB.On("GetUser", name).Return(&db.User{
Username: name,
Pwd: string(expected),
}, nil)
err := us.ValidatePassword(name, password)
assert.Nil(t, err)
})
t.Run("failed validate", func(t *testing.T) {
name := "user1"
password := "test-horse-pen-clam"
expected, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
mockDB := new(mocks.DatabaseServiceMock)
mockDB.On("GetUser", name).Return(&db.User{
Username: name,
Pwd: string(expected),
}, nil)
us := data.NewUserService(mockDB)
err := us.ValidatePassword(name, "garbage-password")
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "hashedPassword is not the hash of the given password")
})
t.Run("inexistent user", func(t *testing.T) {
name := "user1"
mockDB := new(mocks.DatabaseServiceMock)
us := data.NewUserService(mockDB)
mockDB.On("GetUser", name).Return(nil, errors.New("no user exists"))
err := us.ValidatePassword(name, "garbage-password")
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "user does not exist")
})
}
99 changes: 99 additions & 0 deletions demo5/db/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package db

import (
"database/sql"
"log"
)

type User struct {
Username, Pwd string
}

type Note struct {
ID, Username, Text string
}

type dbService struct {
db *sql.DB
}

type DatabaseService interface {
AddUser(username, pwd string) error
GetUser(username string) (*User, error)
AddNote(id, username, text string) error
GetUserNotes(username string) ([]Note, error)
}

// NewDatabaseService initialises a DatabaseService given its dependencies.
func NewDatabaseService(db *sql.DB) *dbService {
return &dbService{
db: db,
}
}

// AddUser creates a new user in the DB
func (ds *dbService) AddUser(username, pwd string) error {
stmt, err := ds.db.Prepare("INSERT INTO users (username, pwd) VALUES( $1, $2 )")
if err != nil {
log.Println("error1", err)
return err
}
defer stmt.Close()
if _, err := stmt.Exec(username, pwd); err != nil {
log.Println("error2", err)
return err
}
return nil
}

// GetUser returns a user from the database or an error if none exists.
func (ds *dbService) GetUser(username string) (*User, error) {
var user User
stmt, err := ds.db.Prepare("SELECT * FROM users WHERE username = $1 ")
if err != nil {
log.Println("error3", err)
return nil, err
}
defer stmt.Close()
if err := stmt.QueryRow(username).Scan(&user.Username, &user.Pwd); err != nil {
log.Println("error4", err)
return nil, err
}
return &user, nil
}

// AddNote creates a new note in the DB
func (ds *dbService) AddNote(id, username, text string) error {
stmt, err := ds.db.Prepare("INSERT INTO notes(id, username, noteText) VALUES($1, $2, $3)")
if err != nil {
return err
}
defer stmt.Close()
if _, err := stmt.Exec(id, username, text); err != nil {
return err
}
return nil
}

// GetUserNotes returns all the notes of a given user from the database or an error.
func (ds *dbService) GetUserNotes(username string) ([]Note, error) {
var notes []Note
stmt, err := ds.db.Prepare("SELECT * FROM notes WHERE username = $1")
if err != nil {
return nil, err
}
defer stmt.Close()
rows, err := stmt.Query(username)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
n := Note{}
if err := rows.Scan(&n.ID, &n.Username, &n.Text); err != nil {
return nil, err
}
notes = append(notes, n)
}
return notes, nil
}
Loading

0 comments on commit 2787a4d

Please sign in to comment.