This repository was archived by the owner on Nov 13, 2020. It is now read-only.
Repository navigation
Expand file tree
/
Copy pathuser.go
More file actions
201 lines (179 loc) · 6.18 KB
/
Copy pathuser.go
File metadata and controls
201 lines (179 loc) · 6.18 KB
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
package authandler
import (
"database/sql"
"encoding/base64"
"errors"
"fmt"
"log"
"net/url"
"time"
)
// Represent roles used for users.
const (
Member = iota
Manager
Supervisor
Admin
createUsersTableSQL = "CREATE TABLE IF NOT EXISTS %v (username varchar, fname varchar DEFAULT '', lname varchar DEFAULT '', email varchar NOT NULL UNIQUE, role int NOT NULL DEFAULT 0, validated boolean DEFAULT false, pass_hash char(80) DEFAULT '', last_access timestamp DEFAULT 'epoch', PRIMARY KEY (username));"
addUserToDatabaseSQL = "INSERT INTO %v (username, fname, lname, email, validated, pass_hash) VALUES ('%v','%v','%v','%v',false,'%v');"
getUserInfoSQL = "SELECT username, fname, lname, email, role, validated FROM %v WHERE %v = '%v';"
getUserPasswordHashSQL = "SELECT pass_hash FROM %v WHERE username = '%v';"
validateUserSQL = "UPDATE %v SET validated = true WHERE username = '%v';"
updateUserPasswordSQL = "UPDATE %v SET (pass_hash, validated) = ('%v', true) WHERE username = '%v';"
updateUserLastAccessSQL = "UPDATE %v SET last_access = '%v' WHERE username = '%v';"
getUserLastAccessSQL = "SELECT last_access FROM %v WHERE username = '%v';"
deleteTestTableSQL = "DROP TABLE %v;"
dateLayout = "2006-01-02 15:04:05"
)
// Role is represents the role of a user.
// Roles elevate and have a linear hierarchy.
type Role int
// HasRole returns whether the role has the given permssion level.
func (r Role) HasRole(permission Role) bool {
return r >= permission
}
// User represents a user to be logged in or signed up represented in the created database.
// For ease, you would want the representation of the user in your app to embed User.
type User struct {
FirstName, LastName, Email, Greet, Username string
Role Role
validated bool
passHash []byte
}
// GetEmail implements the email.Recipient interface.
func (u User) GetEmail() string {
return u.Email
}
// Greeting implements the email.Recipient interface.
func (u User) Greeting() string {
return u.FirstName
}
// HasPermission determines whether the user has the given permission level
func (u User) HasPermission(role Role) bool {
return u.Role.HasRole(role)
}
// IsValidated returns whether the user has validated their login
func (u User) IsValidated() bool {
return u.validated
}
func (u User) isValid() bool {
if u.FirstName == "" || u.LastName == "" || u.Email == "" || u.Username == "" {
return false
}
return true
}
func getUserFromDB(db *sql.DB, tableName, col, search string) *User {
tx, err := db.Begin()
if err != nil {
log.Println("Cannot connect to database")
return nil
}
user := User{}
err = tx.QueryRow(fmt.Sprintf(getUserInfoSQL, tableName, col, search)).Scan(&user.Username, &user.FirstName, &user.LastName, &user.Email, &user.Role, &user.validated)
if err != nil {
fmt.Println(err)
tx.Rollback()
return nil
}
tx.Commit()
user.Email, _ = url.QueryUnescape(user.Email)
user.FirstName, _ = url.QueryUnescape(user.FirstName)
user.LastName, _ = url.QueryUnescape(user.LastName)
user.Username, _ = url.QueryUnescape(user.Username)
user.passHash, _ = getUserPasswordHash(db, tableName, user.Username)
return &user
}
func usernameOrEmailExists(db *sql.DB, tableName string, user *User) (bool, bool) {
usernameSearch := getUserFromDB(db, tableName, "username", user.Username)
emailSearch := getUserFromDB(db, tableName, "email", user.GetEmail())
return usernameSearch != nil, emailSearch != nil
}
func addUserToDatabase(db *sql.DB, tableName string, user *User) error {
tx, err := db.Begin()
if err != nil {
log.Println("Cannot connect to database")
return err
}
_, err = tx.Exec(fmt.Sprintf(addUserToDatabaseSQL, tableName, user.Username, user.FirstName, user.LastName, user.Email, base64.RawURLEncoding.EncodeToString(user.passHash)))
if err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}
func validateUser(db *sql.DB, tableName string, user *User) error {
tx, err := db.Begin()
if err != nil {
log.Println("User cannot be verified")
return fmt.Errorf("User %v cannot be verified", user.Username)
}
_, err = tx.Exec(fmt.Sprintf(validateUserSQL, tableName, user.Username))
if err != nil {
tx.Rollback()
return fmt.Errorf("User %v cannot be verified", user.Username)
}
return tx.Commit()
}
func getUserPasswordHash(db *sql.DB, tableName, username string) ([]byte, error) {
tx, err := db.Begin()
if err != nil {
log.Println(err)
return nil, errors.New("Failed to get password from database")
}
var pwHash string
err = tx.QueryRow(fmt.Sprintf(getUserPasswordHashSQL, tableName, username)).Scan(&pwHash)
if err != nil {
tx.Rollback()
log.Printf("User %v not found in the database\n", username)
return nil, fmt.Errorf("User %v not found in database", username)
}
pwDecoded, err := base64.RawURLEncoding.DecodeString(pwHash)
if err != nil {
tx.Rollback()
log.Println(err)
log.Println("Error decoding password from database. Database might be corrupted!")
return nil, errors.New("Failed to get password from database")
}
return pwDecoded, tx.Commit()
}
func updateUserPassword(db *sql.DB, tableName, username, passHash string) error {
tx, err := db.Begin()
if err != nil {
return errors.New("Failed to connect to database")
}
_, err = tx.Exec(fmt.Sprintf(updateUserPasswordSQL, tableName, passHash, username))
if err != nil {
tx.Rollback()
return errors.New("Failed to update user's last access time")
}
tx.Commit()
return nil
}
func getUserLastAccess(db *sql.DB, tableName, username string) *time.Time {
tx, err := db.Begin()
if err != nil {
return nil
}
var t time.Time
err = tx.QueryRow(fmt.Sprintf(getUserLastAccessSQL, tableName, username)).Scan(&t)
if err != nil {
tx.Rollback()
return nil
}
tx.Commit()
return &t
}
func updateUserLastAccess(db *sql.DB, tableName, username string) error {
tx, err := db.Begin()
if err != nil {
return errors.New("Failed to connect to database")
}
_, err = tx.Exec(fmt.Sprintf(updateUserLastAccessSQL, tableName, time.Now().Format(dateLayout), username))
if err != nil {
log.Println(err)
tx.Rollback()
return errors.New("Failed to update user's access time")
}
tx.Commit()
return nil
}