users.go

 1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
 2//
 3// SPDX-License-Identifier: Apache-2.0
 4
 5package users
 6
 7import (
 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.
20func 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.
30func 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.
41func 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.
56func Delete(dbConn *sql.DB, username string) error { return db.DeleteUser(dbConn, username) }
57
58// Authorised 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.
60func Authorised(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// GetSession accepts a session cookie string and returns the username
75func GetSession(dbConn *sql.DB, session string) (string, time.Time, error) {
76	return db.GetSession(dbConn, session)
77}
78
79// InvalidateSession invalidates a session by setting the expiration date to the
80// current time.
81func InvalidateSession(dbConn *sql.DB, session string) error {
82	return db.InvalidateSession(dbConn, session, time.Now())
83}
84
85// CreateSession accepts a username and a token and creates a session in the
86// database.
87func CreateSession(dbConn *sql.DB, username, token string, expiry time.Time) error {
88	return db.CreateSession(dbConn, username, token, expiry)
89}
90
91// GetUsers returns a list of all users in the database as a slice of strings.
92func GetUsers(dbConn *sql.DB) ([]string, error) { return db.GetUsers(dbConn) }