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) }