/ users / users.go
users.go
  1  // SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2  //
  3  // SPDX-License-Identifier: Apache-2.0
  4  
  5  package users
  6  
  7  import (
  8  	"crypto/rand"
  9  	"database/sql"
 10  	"encoding/base64"
 11  	"time"
 12  
 13  	"git.sr.ht/~amolith/willow/db"
 14  	"golang.org/x/crypto/argon2"
 15  )
 16  
 17  // argonHash accepts two strings for the user's password and a random salt,
 18  // hashes the password using the salt, and returns the hash as a base64-encoded
 19  // string.
 20  func argonHash(password, salt string) (string, error) {
 21  	decodedSalt, err := base64.StdEncoding.DecodeString(salt)
 22  	if err != nil {
 23  		return "", err
 24  	}
 25  	return base64.StdEncoding.EncodeToString(argon2.IDKey([]byte(password), decodedSalt, 2, 64*1024, 4, 64)), nil
 26  }
 27  
 28  // generateSalt generates a random salt and returns it as a base64-encoded
 29  // string.
 30  func generateSalt() (string, error) {
 31  	salt := make([]byte, 16)
 32  	_, err := rand.Read(salt)
 33  	if err != nil {
 34  		return "", err
 35  	}
 36  	return base64.StdEncoding.EncodeToString(salt), nil
 37  }
 38  
 39  // Register accepts a username and password, hashes the password and stores the
 40  // hash and salt in the database.
 41  func Register(dbConn *sql.DB, username, password string) error {
 42  	salt, err := generateSalt()
 43  	if err != nil {
 44  		return err
 45  	}
 46  
 47  	hash, err := argonHash(password, salt)
 48  	if err != nil {
 49  		return err
 50  	}
 51  
 52  	return db.CreateUser(dbConn, username, hash, salt)
 53  }
 54  
 55  // Delete removes a user from the database.
 56  func Delete(dbConn *sql.DB, username string) error { return db.DeleteUser(dbConn, username) }
 57  
 58  // UserAuthorised accepts a username string, a token string, and returns true if the
 59  // user is authorised, false if not, and an error if one is encountered.
 60  func UserAuthorised(dbConn *sql.DB, username, token string) (bool, error) {
 61  	dbHash, dbSalt, err := db.GetUser(dbConn, username)
 62  	if err != nil {
 63  		return false, err
 64  	}
 65  
 66  	providedHash, err := argonHash(token, dbSalt)
 67  	if err != nil {
 68  		return false, err
 69  	}
 70  
 71  	return dbHash == providedHash, nil
 72  }
 73  
 74  // SessionAuthorised accepts a session string and returns true if the session is
 75  // valid and false if not.
 76  func SessionAuthorised(dbConn *sql.DB, session string) (bool, error) {
 77  	dbResult, expiry, err := db.GetSession(dbConn, session)
 78  	if dbResult == "" || expiry.Before(time.Now()) || err != nil {
 79  		return false, err
 80  	}
 81  
 82  	return true, nil
 83  }
 84  
 85  // InvalidateSession invalidates a session by setting the expiration date to now.
 86  func InvalidateSession(dbConn *sql.DB, session string) error {
 87  	return db.InvalidateSession(dbConn, session, time.Now())
 88  }
 89  
 90  // CreateSession accepts a username, generates a token, stores it in the
 91  // database, and returns it
 92  func CreateSession(dbConn *sql.DB, username string) (string, time.Time, error) {
 93  	token, err := generateSalt()
 94  	if err != nil {
 95  		return "", time.Time{}, err
 96  	}
 97  
 98  	expiry := time.Now().Add(7 * 24 * time.Hour)
 99  
100  	err = db.CreateSession(dbConn, username, token, expiry)
101  	if err != nil {
102  		return "", time.Time{}, err
103  	}
104  
105  	return token, expiry, nil
106  }
107  
108  // GetUsers returns a list of all users in the database as a slice of strings.
109  func GetUsers(dbConn *sql.DB) ([]string, error) { return db.GetUsers(dbConn) }