Prepare for rewrite on several functions

User struct now exports everything, encapsulation is not necessary
over here. Instead of introducing a new variable uInfo we'll use
user.User and pass that. Handlers & related functions will be
rewritten to work with this change. This will make it easier to work
on later as the program grows. I'm also rethinking error handling.
This commit is contained in:
Andinus 2020-03-28 19:25:53 +05:30
parent b0b83af1c4
commit 7b95d6b80d
Signed by: andinus
GPG Key ID: B67D55D482A799FD
11 changed files with 13 additions and 407 deletions

View File

@ -1,46 +0,0 @@
package auth
import (
"log"
"tildegit.org/andinus/perseus/storage/sqlite3"
"tildegit.org/andinus/perseus/user"
)
// Login takes in login details and returns an error. If error doesn't
// equal nil then consider login failed.
func Login(db *sqlite3.DB, uInfo map[string]string) error {
// Acquire read lock on the database.
db.Mu.RLock()
defer db.Mu.RUnlock()
u := user.User{}
u.SetUsername(uInfo["username"])
// Get password for this user from the database.
stmt, err := db.Conn.Prepare("SELECT password FROM users WHERE username = ?")
if err != nil {
log.Printf("auth/login.go: %s\n",
"failed to prepare statement")
return err
}
defer stmt.Close()
var pass string
err = stmt.QueryRow(u.Username()).Scan(&pass)
if err != nil {
log.Printf("auth/login.go: %s\n",
"query failed")
return err
}
u.SetPassword(pass)
// Check user's password.
err = checkPass(uInfo["password"], u.Password())
if err != nil {
log.Printf("auth/login.go: %s%s\n",
"user login failed, username: ", u.Username())
}
return err
}

View File

@ -1,42 +0,0 @@
package auth
import (
"errors"
"log"
"regexp"
"strings"
"tildegit.org/andinus/perseus/storage/sqlite3"
"tildegit.org/andinus/perseus/user"
)
// Register takes in registration details and returns an error. If
// error doesn't equal nil then the registration was unsuccessful.
// uInfo should have username & password.
func Register(db *sqlite3.DB, uInfo map[string]string) error {
u := user.User{}
u.SetID(genID(64))
u.SetUsername(strings.ToLower(uInfo["username"]))
// Validate username
re := regexp.MustCompile("^[a-z0-9]*$")
if !re.MatchString(u.Username()) {
return errors.New("auth/register.go: invalid username")
}
// Validate password
if len(uInfo["password"]) < 8 {
return errors.New("auth/register.go: password too short")
}
pass, err := hashPass(uInfo["password"])
if err != nil {
log.Printf("auth/register.go: %s\n",
"hashPass func failed")
return err
}
u.SetPassword(pass)
err = u.AddUser(db)
return err
}

View File

@ -1,58 +0,0 @@
package token
import (
"log"
"time"
"tildegit.org/andinus/perseus/storage/sqlite3"
"tildegit.org/andinus/perseus/user"
)
// AddToken will generate a random token, add it to database and
// return the token.
func AddToken(db *sqlite3.DB, uInfo map[string]string) (token string, err error) {
// Acquire write lock on the database.
db.Mu.Lock()
defer db.Mu.Unlock()
token = genToken(64)
u := user.User{}
u.SetUsername(uInfo["username"])
// Set user id from username.
err = u.GetID(db)
if err != nil {
log.Printf("auth/token.go: %s\n",
"failed to get id from username")
return
}
// Start the transaction
tx, err := db.Conn.Begin()
if err != nil {
log.Printf("auth/token.go: %s\n",
"failed to begin transaction")
return
}
stmt, err := db.Conn.Prepare(`
INSERT INTO access(id, token, genTime) values(?, ?, ?)`)
if err != nil {
log.Printf("auth/token.go: %s\n",
"failed to prepare statement")
return
}
defer stmt.Close()
_, err = stmt.Exec(u.ID(), u.Username(), time.Now().UTC())
if err != nil {
log.Printf("auth/token.go: %s\n",
"failed to execute statement")
return
}
tx.Commit()
return
}

View File

@ -1,14 +0,0 @@
package token
import (
"crypto/rand"
"encoding/base64"
)
// genToken generates a random token string of length n. Don't forget to
// seed the random number generator otherwise it won't be random.
func genToken(n int) string {
b := make([]byte, n/2)
rand.Read(b)
return base64.StdEncoding.EncodeToString(b)
}

View File

@ -1,51 +0,0 @@
package token
import (
"errors"
"log"
"tildegit.org/andinus/perseus/storage/sqlite3"
"tildegit.org/andinus/perseus/user"
)
// ValToken will validate the token and returns an error. If error
// doesn't equal nil then consider token invalid.
func ValToken(db *sqlite3.DB, uInfo map[string]string) error {
// Acquire read lock on the database.
db.Mu.RLock()
defer db.Mu.RUnlock()
u := user.User{}
u.SetUsername(uInfo["username"])
// Set user id from username.
err := u.GetID(db)
if err != nil {
log.Printf("auth/token.go: %s\n",
"failed to get id from username")
return err
}
// Check if user's token is valid.
stmt, err := db.Conn.Prepare("SELECT token FROM access WHERE id = ?")
if err != nil {
log.Printf("auth/token.go: %s\n",
"failed to prepare statement")
return err
}
defer stmt.Close()
var token string
err = stmt.QueryRow(u.ID()).Scan(&token)
if err != nil {
log.Printf("auth/token.go: %s\n",
"query failed")
return err
}
if token != uInfo["token"] {
err = errors.New("token mismatch")
}
return err
}

View File

@ -1,83 +0,0 @@
package web
import (
"fmt"
"html/template"
"log"
"net/http"
"time"
"tildegit.org/andinus/perseus/auth"
"tildegit.org/andinus/perseus/auth/token"
"tildegit.org/andinus/perseus/core"
"tildegit.org/andinus/perseus/storage/sqlite3"
)
// HandleLogin handles /login pages.
func HandleLogin(w http.ResponseWriter, r *http.Request, db *sqlite3.DB) {
p := Page{Version: core.Version()}
error := []string{}
success := []string{}
switch r.Method {
case http.MethodGet:
t, _ := template.ParseFiles("web/login.html")
t.Execute(w, p)
case http.MethodPost:
if err := r.ParseForm(); err != nil {
log.Printf("web/login.go: 400 Bad Request :: %s", err.Error())
http.Error(w, "400 Bad Request", http.StatusBadRequest)
return
}
// Get form values
uInfo := make(map[string]string)
uInfo["username"] = r.FormValue("username")
uInfo["password"] = r.FormValue("password")
// Perform authentication
err := auth.Login(db, uInfo)
if err != nil {
log.Printf("web/login.go: %s :: %s :: %s",
"login failed",
uInfo["username"],
err.Error())
error = append(error,
fmt.Sprintf("Login failed"))
p.Error = error
} else {
success = append(success,
fmt.Sprintf("Login successful"))
p.Success = success
// Set token if login was successful.
token, err := token.AddToken(db, uInfo)
if err != nil {
log.Printf("web/login.go: %s :: %s :: %s",
"token generation failed",
uInfo["username"],
err.Error())
error = append(error,
fmt.Sprintf("Token generation failed"))
}
// If token was generated then ask browser to
// set it as cookie.
expiration := time.Now().Add(1 * 24 * time.Hour)
cookie := http.Cookie{Name: "token", Value: token, Expires: expiration}
http.SetCookie(w, &cookie)
}
t, _ := template.ParseFiles("web/login.html")
t.Execute(w, p)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
log.Printf("web/login.go: %v not allowed on %v", r.Method, r.URL)
}
}

View File

@ -1,8 +1,11 @@
package web
import "html/template"
import (
"html/template"
)
// Page holds page information
// Page holds page information that is sent to all webpages rendered
// by perseus.
type Page struct {
SafeList []template.HTML
List []string

View File

@ -1,74 +0,0 @@
package web
import (
"fmt"
"html/template"
"log"
"net/http"
"strings"
"tildegit.org/andinus/perseus/auth"
"tildegit.org/andinus/perseus/core"
"tildegit.org/andinus/perseus/storage/sqlite3"
)
// HandleRegister handles /register pages.
func HandleRegister(w http.ResponseWriter, r *http.Request, db *sqlite3.DB) {
p := Page{Version: core.Version()}
p.Notice = []string{
"Only [a-z] & [0-9] allowed for username",
"Password length must be greater than 8 characters",
}
switch r.Method {
case http.MethodGet:
t, _ := template.ParseFiles("web/register.html")
t.Execute(w, p)
case http.MethodPost:
if err := r.ParseForm(); err != nil {
log.Printf("web/register.go: 400 Bad Request :: %s", err.Error())
http.Error(w, "400 Bad Request", http.StatusBadRequest)
return
}
// Get form values
uInfo := make(map[string]string)
uInfo["username"] = r.FormValue("username")
uInfo["password"] = r.FormValue("password")
// Perform registration
err := auth.Register(db, uInfo)
if err != nil {
log.Printf("web/register.go: %s :: %s :: %s",
"registration failed",
uInfo["username"],
err.Error())
error := []string{}
error = append(error,
fmt.Sprintf("Registration failed"))
// Check if the error was because of username
// not being unique.
if strings.HasPrefix(err.Error(), "UNIQUE constraint failed") {
error = append(error,
fmt.Sprintf("Username not unique"))
}
p.Error = error
} else {
success := []string{}
success = append(success,
fmt.Sprintf("Registration successful"))
p.Success = success
}
t, _ := template.ParseFiles("web/register.html")
t.Execute(w, p)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
log.Printf("web/register.go: %v not allowed on %v", r.Method, r.URL)
}
}

View File

@ -8,7 +8,7 @@ import (
)
// AddUser adds the user to record.
func (u *User) AddUser(db *sqlite3.DB) error {
func (u User) AddUser(db *sqlite3.DB) error {
// Acquire write lock on the database.
db.Mu.Lock()
defer db.Mu.Unlock()
@ -30,7 +30,7 @@ INSERT INTO users(id, username, password, regTime) values(?, ?, ?, ?)`)
}
defer usrStmt.Close()
_, err = usrStmt.Exec(u.id, u.username, u.password, time.Now().UTC())
_, err = usrStmt.Exec(u.ID, u.Username, u.Password, time.Now().UTC())
if err != nil {
log.Printf("user/adduser.go: %s\n",
"failed to execute statement")

View File

@ -18,12 +18,12 @@ func (u *User) GetID(db *sqlite3.DB) error {
defer stmt.Close()
var id string
err = stmt.QueryRow(u.username).Scan(&id)
err = stmt.QueryRow(u.Username).Scan(&id)
if err != nil {
log.Printf("user/getid.go: %s\n",
"query failed")
}
u.id = id
u.ID = id
return err
}

View File

@ -2,37 +2,8 @@ package user
// User holds information about the user.
type User struct {
id string
username string
password string
}
// SetUsername will set the username.
func (u *User) SetUsername(username string) {
u.username = username
}
// Username returns the username.
func (u *User) Username() string {
return u.username
}
// SetPassword will set the password.
func (u *User) SetPassword(password string) {
u.password = password
}
// Password returns the password.
func (u *User) Password() string {
return u.password
}
// SetID will set the id.
func (u *User) SetID(id string) {
u.id = id
}
// ID returns the id.
func (u *User) ID() string {
return u.id
ID string
Username string
Password string
Hash string
}