mirror of
https://github.com/riwiwa/muzi.git
synced 2026-02-28 11:56:57 -08:00
improve readability of the web package
This commit is contained in:
183
web/auth.go
Normal file
183
web/auth.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package web
|
||||
|
||||
// Functions used to authenticate web UI users.
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"muzi/db"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// Generates a hex string 32 characters in length.
|
||||
func generateID() (string, error) {
|
||||
b := make([]byte, 16)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// Returns a salted hash of a password if valid (8-64 chars).
|
||||
func hashPassword(pass []byte) (string, error) {
|
||||
if len([]rune(string(pass))) < 8 || len(pass) > 64 {
|
||||
return "", errors.New("Error: Password must be greater than 8 chars.")
|
||||
}
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword(pass, bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Couldn't hash password: %v\n", err)
|
||||
return "", err
|
||||
}
|
||||
return string(hashedPassword), nil
|
||||
}
|
||||
|
||||
// Compares a plaintext password and a hashed password. Returns T/F depending
|
||||
// on comparison result.
|
||||
func verifyPassword(hashedPassword string, enteredPassword []byte) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), enteredPassword)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error while comparing passwords: %v\n", err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Handles the submission of new account credentials. Stores credentials in
|
||||
// the users table. Sets a browser cookie for successful new users.
|
||||
func createAccount(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "POST" {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
username := r.FormValue("uname")
|
||||
if len([]rune(string(username))) == 0 {
|
||||
http.Redirect(w, r, "/createaccount?error=userlength", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
var usertaken bool
|
||||
err = db.Pool.QueryRow(r.Context(),
|
||||
"SELECT EXISTS(SELECT 1 FROM users WHERE username = $1)", username).
|
||||
Scan(&usertaken)
|
||||
if usertaken == true {
|
||||
http.Redirect(w, r, "/createaccount?error=usertaken", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
hashedPassword, err := hashPassword([]byte(r.FormValue("pass")))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error hashing password: %v\n", err)
|
||||
http.Redirect(w, r, "/createaccount?error=passlength", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = db.Pool.Exec(
|
||||
r.Context(),
|
||||
`INSERT INTO users (username, password) VALUES ($1, $2);`,
|
||||
username,
|
||||
hashedPassword,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Cannot add new user to users table: %v\n", err)
|
||||
http.Redirect(w, r, "/createaccount", http.StatusSeeOther)
|
||||
} else {
|
||||
sessionID := createSession(username)
|
||||
if sessionID == "" {
|
||||
http.Redirect(w, r, "/login?error=session", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "session",
|
||||
Value: sessionID,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
MaxAge: 86400 * 30,
|
||||
})
|
||||
http.Redirect(w, r, "/profile/"+username, http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Renders the create account page
|
||||
func createAccountPageHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
type data struct {
|
||||
Error string
|
||||
}
|
||||
d := data{Error: "len"}
|
||||
err := templates.ExecuteTemplate(w, "create_account.gohtml", d)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handles submission of login credentials by checking if the username
|
||||
// is in the database and the stored password for that username matches the
|
||||
// given password. Sets browser cookie on successful login.
|
||||
func loginSubmit(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "POST" {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
username := r.FormValue("uname")
|
||||
if username == "" {
|
||||
http.Redirect(w, r, "/login?error=invalid-creds", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
password := r.FormValue("pass")
|
||||
var storedPassword string
|
||||
err = db.Pool.QueryRow(r.Context(), "SELECT password FROM users WHERE username = $1;", username).
|
||||
Scan(&storedPassword)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Cannot get password for entered username: %v\n", err)
|
||||
}
|
||||
|
||||
if verifyPassword(storedPassword, []byte(password)) {
|
||||
sessionID := createSession(username)
|
||||
if sessionID == "" {
|
||||
http.Redirect(w, r, "/login?error=session", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "session",
|
||||
Value: sessionID,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
MaxAge: 86400 * 30,
|
||||
})
|
||||
http.Redirect(w, r, "/profile/"+username, http.StatusSeeOther)
|
||||
} else {
|
||||
http.Redirect(w, r, "/login?error=invalid-creds", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Renders the login page
|
||||
func loginPageHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
type data struct {
|
||||
Error string
|
||||
}
|
||||
d := data{Error: r.URL.Query().Get("error")}
|
||||
err := templates.ExecuteTemplate(w, "login.gohtml", d)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user