/
user.go
143 lines (118 loc) · 3.82 KB
/
user.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package api
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"log"
"os"
"path/filepath"
"strconv"
"github.com/math2001/money/db"
"golang.org/x/crypto/scrypt"
)
var ErrEmailAlreadyUsed = errors.New("email already used")
var ErrWrongIdentifiers = errors.New("wrong identifiers")
type user struct {
Email string
Password []byte
ID int
}
// SignUp creates a new user
//
// FIXME: this function can change the state of the application but still
// return an error. It needs to clean up after itself if that happens, because
// otherwise, we are left with a corrupted state
//
// FIXME: this is extrememly inefficient. It reads all the user data into
// memory just to compare emails and possibly add one entry
func (api *API) SignUp(email, password string) (*db.User, error) {
log.Printf("Sign up %q", email)
// check taken entry in users file
f, err := os.Open(api.userslist)
if err != nil {
// TODO: could send an email to email (apologise)
return nil, fmt.Errorf("signing up, opening users list %q: %s", api.userslist, err)
}
decoder := json.NewDecoder(f)
var users []user
if err := decoder.Decode(&users); err != nil {
return nil, fmt.Errorf("signing up, parsing users list: %q: %s", api.userslist, err)
}
// check if the email has already been used
for _, user := range users {
if user.Email == email {
log.Printf("Return email already used")
return nil, ErrEmailAlreadyUsed
}
}
// FIXME: the key size (32) should be a constant
hashedpassword, err := scrypt.Key([]byte(password), api.sm.Get(saltpassword), 32768, 8, 1, 32)
if err != nil {
return nil, fmt.Errorf("signing up, hashing password: %s", err)
}
// add entry in users file
userid := len(users) + 1
users = append(users, user{
Email: email,
Password: hashedpassword,
ID: userid,
})
f.Close()
f, err = os.Create(api.userslist)
if err != nil {
return nil, fmt.Errorf("signing up, recreating users list: %s", err)
}
encoder := json.NewEncoder(f)
if err := encoder.Encode(users); err != nil {
// FIXME: the users file is now corrupted. Try to rewrite the old
// version
return nil, fmt.Errorf("signing up, saving user to database: %s", err)
}
u := db.NewUser(userid, email, filepath.Join(api.Usersdir, strconv.Itoa(userid)))
if err := u.SignUp([]byte(password)); err != nil {
return nil, fmt.Errorf("signing up db.User: %s", err)
}
return u, nil
}
// Login adds the user to loggedusers
func (api *API) Login(email, password string) (*db.User, error) {
// FIXME: that's a lot of duplicate logic from sign up...
f, err := os.Open(api.userslist)
if err != nil {
// TODO: could send an email to email (apologise)
return nil, fmt.Errorf("signing up, opening users list %q: %s", api.userslist, err)
}
defer f.Close()
decoder := json.NewDecoder(f)
// FIXME: this is extrememly inefficient. It reads all the user data into
// memory just to compare emails and password pairs...
var users []user
if err := decoder.Decode(&users); err != nil {
return nil, fmt.Errorf("signing up, parsing users list: %q: %s", api.userslist, err)
}
hashedpassword := scryptKey([]byte(password), api.sm.Get(saltpassword))
var match user
// check if the email has already been used
for _, user := range users {
if user.Email == email && bytes.Equal(user.Password, hashedpassword) {
match = user
break
}
}
// ie. no match
if match.ID == 0 {
return nil, ErrWrongIdentifiers
}
u := db.NewUser(match.ID, match.Email, filepath.Join(api.Usersdir, strconv.Itoa(match.ID)))
if err := u.Login([]byte(password)); err != nil {
return nil, fmt.Errorf("logging in: %s", err)
}
log.Printf("user is now logged in: %v", u)
return u, nil
}
// Logout has nothing to do to log out someone from the api's point of view
// so we just at least check that the current user is valid
func (api *API) Logout(user *db.User) error {
return nil
}