Skip to content

Commit d8251a0

Browse files
youshyaddetz
authored andcommitted
feat: add demo5, with bad functions
1 parent 3fb9620 commit d8251a0

File tree

17 files changed

+1048
-0
lines changed

17 files changed

+1048
-0
lines changed

demo5/data/note_service.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package data
2+
3+
import (
4+
"github.com/addetz/secure-code-go/demo4/db"
5+
"github.com/google/uuid"
6+
)
7+
8+
type SecretNote struct {
9+
ID string `json:"id"`
10+
Username string `json:"username"`
11+
Text string `json:"text"`
12+
}
13+
14+
// SecretNoteService maintains the user notes.
15+
type SecretNoteService struct {
16+
dbService db.DatabaseService
17+
}
18+
19+
// NewSecretNoteService creates a SecretNoteService that is ready to use.
20+
func NewSecretNoteService(dbService db.DatabaseService) *SecretNoteService {
21+
return &SecretNoteService{
22+
dbService: dbService,
23+
}
24+
}
25+
26+
// Add adds a new SecretNote for the given user by using the SecretNoteService.
27+
func (ns *SecretNoteService) Add(user string, n SecretNote) error {
28+
id := uuid.New().String()
29+
return ns.dbService.AddNote(id, user, n.Text)
30+
}
31+
32+
// Get returns all the SecretNotes of a given user by using the SecretNoteService.
33+
func (ns *SecretNoteService) GetAll(user string) ([]SecretNote, error) {
34+
dbNotes, err := ns.dbService.GetUserNotes(user)
35+
if err != nil {
36+
return nil, err
37+
}
38+
var notes []SecretNote
39+
for _, n := range dbNotes {
40+
notes = append(notes, SecretNote{
41+
ID: n.ID,
42+
Username: n.Username,
43+
Text: n.Text,
44+
})
45+
}
46+
47+
return notes, nil
48+
}

demo5/data/note_service_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package data_test
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/addetz/secure-code-go/demo4/data"
8+
"github.com/addetz/secure-code-go/demo4/db"
9+
"github.com/addetz/secure-code-go/demo4/mocks"
10+
"github.com/google/uuid"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/mock"
13+
)
14+
15+
func TestAddNote(t *testing.T) {
16+
mockDB := new(mocks.DatabaseServiceMock)
17+
notes := data.NewSecretNoteService(mockDB)
18+
user := "user1"
19+
note := data.SecretNote{
20+
Text: "My Secret Note",
21+
}
22+
mockDB.On("AddNote", mock.AnythingOfType("string"), user, note.Text).Return(nil)
23+
err := notes.Add(user, note)
24+
assert.Nil(t, err)
25+
}
26+
27+
func TestGetAllNotes(t *testing.T) {
28+
t.Run("no notes found", func(t *testing.T) {
29+
user := "user1"
30+
mockDB := new(mocks.DatabaseServiceMock)
31+
noteService := data.NewSecretNoteService(mockDB)
32+
mockDB.On("GetUserNotes", user).Return(nil, errors.New("no notes found"))
33+
notes, err := noteService.GetAll(user)
34+
assert.Nil(t, notes)
35+
assert.NotNil(t, err)
36+
assert.Contains(t, err.Error(), "no notes found")
37+
})
38+
t.Run("notes found", func(t *testing.T) {
39+
user := "user1"
40+
dbNotes := []db.Note{
41+
{
42+
ID: uuid.New().String(),
43+
Username: user,
44+
Text: "My first note",
45+
},
46+
{
47+
ID: uuid.New().String(),
48+
Username: user,
49+
Text: "My second note",
50+
},
51+
}
52+
mockDB := new(mocks.DatabaseServiceMock)
53+
noteService := data.NewSecretNoteService(mockDB)
54+
mockDB.On("GetUserNotes", user).Return(dbNotes, nil)
55+
notes, err := noteService.GetAll(user)
56+
assert.Nil(t, err)
57+
assert.NotNil(t, notes)
58+
assert.Equal(t, len(dbNotes), len(notes))
59+
for i, n := range dbNotes {
60+
assert.Equal(t, n.ID, notes[i].ID)
61+
assert.Equal(t, n.Username, notes[i].Username)
62+
assert.Equal(t, n.Text, notes[i].Text)
63+
64+
}
65+
})
66+
}

demo5/data/user_service.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package data
2+
3+
import (
4+
"github.com/addetz/secure-code-go/demo4/db"
5+
"github.com/pkg/errors"
6+
7+
passwordvalidator "github.com/wagslane/go-password-validator"
8+
"golang.org/x/crypto/bcrypt"
9+
)
10+
11+
const minEntropyBits = 60
12+
13+
type User struct {
14+
Username, Password string
15+
}
16+
17+
// UserService holds
18+
type UserService struct {
19+
dbService db.DatabaseService
20+
}
21+
22+
// NewUserService creates a ready to use user service.
23+
func NewUserService(dbService db.DatabaseService) *UserService {
24+
return &UserService{
25+
dbService: dbService,
26+
}
27+
}
28+
29+
// Add validates a user password and creates a new user.
30+
func (us *UserService) Add(name, password string) error {
31+
if _, err := us.dbService.GetUser(name); err == nil {
32+
return errors.New("user exists already, please log in instead")
33+
}
34+
35+
err := passwordvalidator.Validate(password, minEntropyBits)
36+
if err != nil {
37+
return errors.Wrap(err, "validate new user password")
38+
}
39+
40+
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
41+
if err != nil {
42+
return err
43+
}
44+
return us.dbService.AddUser(name, string(hashedPassword))
45+
}
46+
47+
// ValidatePassword checks the provided password of an existing user.
48+
func (us *UserService) ValidatePassword(name, providedPwd string) error {
49+
user, err := us.dbService.GetUser(name)
50+
if err != nil {
51+
return errors.Wrap(err, "user does not exist")
52+
}
53+
return bcrypt.CompareHashAndPassword([]byte(user.Pwd), []byte(providedPwd))
54+
}
55+
56+
// ValidateUser checks the provided username belongs to an existing user.
57+
func (us *UserService) ValidateUser(name string) error {
58+
if _, err := us.dbService.GetUser(name); err != nil {
59+
return errors.New("user not found")
60+
}
61+
return nil
62+
}

demo5/data/user_service_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package data_test
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/addetz/secure-code-go/demo4/data"
8+
"github.com/addetz/secure-code-go/demo4/db"
9+
"github.com/addetz/secure-code-go/demo4/mocks"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/mock"
12+
"golang.org/x/crypto/bcrypt"
13+
)
14+
15+
func TestAdd(t *testing.T) {
16+
t.Run("insufficient password", func(t *testing.T) {
17+
name := "user1"
18+
mockDB := new(mocks.DatabaseServiceMock)
19+
us := data.NewUserService(mockDB)
20+
mockDB.On("GetUser", name).Return(nil, errors.New("no user exists"))
21+
err := us.Add(name, "test")
22+
assert.NotNil(t, err)
23+
assert.Contains(t, err.Error(), "validate new user password: insecure password")
24+
})
25+
t.Run("successful add", func(t *testing.T) {
26+
name := "user1"
27+
password := "test-horse-pen-clam"
28+
mockDB := new(mocks.DatabaseServiceMock)
29+
us := data.NewUserService(mockDB)
30+
mockDB.On("GetUser", name).Return(nil, errors.New("no user exists"))
31+
mockDB.On("AddUser", name, mock.AnythingOfType("string")).Return(nil)
32+
err := us.Add(name, password)
33+
assert.Nil(t, err)
34+
})
35+
t.Run("duplicate user", func(t *testing.T) {
36+
name := "user1"
37+
password := "test-horse-pen-clam"
38+
mockDB := new(mocks.DatabaseServiceMock)
39+
us := data.NewUserService(mockDB)
40+
mockDB.On("GetUser", name).Return(nil, nil)
41+
err := us.Add(name, password)
42+
assert.NotNil(t, err)
43+
assert.Contains(t, err.Error(), "user exists already")
44+
})
45+
}
46+
47+
func TestValidate(t *testing.T) {
48+
t.Run("successful validate", func(t *testing.T) {
49+
name := "user1"
50+
password := "test-horse-pen-clam"
51+
expected, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
52+
mockDB := new(mocks.DatabaseServiceMock)
53+
us := data.NewUserService(mockDB)
54+
mockDB.On("GetUser", name).Return(&db.User{
55+
Username: name,
56+
Pwd: string(expected),
57+
}, nil)
58+
err := us.ValidatePassword(name, password)
59+
assert.Nil(t, err)
60+
})
61+
t.Run("failed validate", func(t *testing.T) {
62+
name := "user1"
63+
password := "test-horse-pen-clam"
64+
expected, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
65+
mockDB := new(mocks.DatabaseServiceMock)
66+
mockDB.On("GetUser", name).Return(&db.User{
67+
Username: name,
68+
Pwd: string(expected),
69+
}, nil)
70+
us := data.NewUserService(mockDB)
71+
err := us.ValidatePassword(name, "garbage-password")
72+
assert.NotNil(t, err)
73+
assert.Contains(t, err.Error(), "hashedPassword is not the hash of the given password")
74+
})
75+
t.Run("inexistent user", func(t *testing.T) {
76+
name := "user1"
77+
mockDB := new(mocks.DatabaseServiceMock)
78+
us := data.NewUserService(mockDB)
79+
mockDB.On("GetUser", name).Return(nil, errors.New("no user exists"))
80+
err := us.ValidatePassword(name, "garbage-password")
81+
assert.NotNil(t, err)
82+
assert.Contains(t, err.Error(), "user does not exist")
83+
})
84+
}

demo5/db/db.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package db
2+
3+
import (
4+
"database/sql"
5+
"log"
6+
)
7+
8+
type User struct {
9+
Username, Pwd string
10+
}
11+
12+
type Note struct {
13+
ID, Username, Text string
14+
}
15+
16+
type dbService struct {
17+
db *sql.DB
18+
}
19+
20+
type DatabaseService interface {
21+
AddUser(username, pwd string) error
22+
GetUser(username string) (*User, error)
23+
AddNote(id, username, text string) error
24+
GetUserNotes(username string) ([]Note, error)
25+
}
26+
27+
// NewDatabaseService initialises a DatabaseService given its dependencies.
28+
func NewDatabaseService(db *sql.DB) *dbService {
29+
return &dbService{
30+
db: db,
31+
}
32+
}
33+
34+
// AddUser creates a new user in the DB
35+
func (ds *dbService) AddUser(username, pwd string) error {
36+
stmt, err := ds.db.Prepare("INSERT INTO users (username, pwd) VALUES( $1, $2 )")
37+
if err != nil {
38+
log.Println("error1", err)
39+
return err
40+
}
41+
defer stmt.Close()
42+
if _, err := stmt.Exec(username, pwd); err != nil {
43+
log.Println("error2", err)
44+
return err
45+
}
46+
return nil
47+
}
48+
49+
// GetUser returns a user from the database or an error if none exists.
50+
func (ds *dbService) GetUser(username string) (*User, error) {
51+
var user User
52+
stmt, err := ds.db.Prepare("SELECT * FROM users WHERE username = $1 ")
53+
if err != nil {
54+
log.Println("error3", err)
55+
return nil, err
56+
}
57+
defer stmt.Close()
58+
if err := stmt.QueryRow(username).Scan(&user.Username, &user.Pwd); err != nil {
59+
log.Println("error4", err)
60+
return nil, err
61+
}
62+
return &user, nil
63+
}
64+
65+
// AddNote creates a new note in the DB
66+
func (ds *dbService) AddNote(id, username, text string) error {
67+
stmt, err := ds.db.Prepare("INSERT INTO notes(id, username, noteText) VALUES($1, $2, $3)")
68+
if err != nil {
69+
return err
70+
}
71+
defer stmt.Close()
72+
if _, err := stmt.Exec(id, username, text); err != nil {
73+
return err
74+
}
75+
return nil
76+
}
77+
78+
// GetUserNotes returns all the notes of a given user from the database or an error.
79+
func (ds *dbService) GetUserNotes(username string) ([]Note, error) {
80+
var notes []Note
81+
stmt, err := ds.db.Prepare("SELECT * FROM notes WHERE username = $1")
82+
if err != nil {
83+
return nil, err
84+
}
85+
defer stmt.Close()
86+
rows, err := stmt.Query(username)
87+
if err != nil {
88+
return nil, err
89+
}
90+
defer rows.Close()
91+
for rows.Next() {
92+
n := Note{}
93+
if err := rows.Scan(&n.ID, &n.Username, &n.Text); err != nil {
94+
return nil, err
95+
}
96+
notes = append(notes, n)
97+
}
98+
return notes, nil
99+
}

0 commit comments

Comments
 (0)