/*
* Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
*
* This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
* more information.
*/
package db
import (
"database/sql"
"errors"
"github.com/matthewhartstonge/argon2"
"github.com/oklog/ulid/v2"
)
// CreateUser creates a user.
func CreateUser(email string, pwd string) error {
argon := argon2.DefaultConfig()
encoded, err := argon.HashEncoded([]byte(pwd))
if err != nil {
return err
}
ulid := ulid.Make().String()
_, err = DBConn.Exec(`INSERT INTO users(id, email, pwd) VALUES ($1, $2, $3)`,
ulid, email, string(encoded))
if err != nil {
return err
}
return nil
}
// CreateAdmin creates an admin user.
func CreateAdmin(email string, pwd string) error {
argon := argon2.DefaultConfig()
encoded, err := argon.HashEncoded([]byte(pwd))
if err != nil {
return err
}
ulid := ulid.Make().String()
_, err = DBConn.Exec(`INSERT INTO users(id, email, pwd, is_admin) VALUES ($1, $2, $3, TRUE)`,
ulid, email, string(encoded))
if err != nil {
return err
}
return nil
}
// User contains a users relevant information.
type User struct {
ID string
Email string
IsAdmin bool
}
// FetchUserWithCreds fetches a users information based on given credentials.
func FetchUserWithCreds(email string, pwd string) (*User, error) {
user := User{Email: email}
var encodedPwd string
err := DBConn.QueryRow(`SELECT id, pwd FROM users WHERE email = $1`,
email).Scan(&user.ID, &encodedPwd)
if err == sql.ErrNoRows {
return nil, errors.New("User not found")
}
if err != nil {
return nil, errors.New("User query failed")
}
ok, err := argon2.VerifyEncoded([]byte(pwd), []byte(encodedPwd))
if err != nil {
return nil, errors.New("User not found")
}
if !ok {
return nil, errors.New("User not found")
}
return &user, nil
}
// FetchUserWithID fetches a users information.
func FetchUserWithID(id string) (*User, error) {
user := User{ID: id}
err := DBConn.QueryRow(`SELECT email, is_admin FROM users WHERE id = $1`,
id).Scan(&user.Email, &user.IsAdmin)
if err == sql.ErrNoRows {
return nil, errors.New("User not found")
}
if err != nil {
return nil, errors.New("User query failed")
}
return &user, nil
}
// FetchAllUsers fetches all users.
func FetchAllUsers() ([]User, error) {
rows, err := DBConn.Query(`SELECT id, email, is_admin FROM users`)
if err != nil {
return nil, err
}
defer rows.Close()
var users []User
for rows.Next() {
var user User
err = rows.Scan(&user.ID, &user.Email, &user.IsAdmin)
if err != nil {
return nil, err
}
users = append(users, user)
}
err = rows.Err()
if err != nil {
return nil, err
}
return users, nil
}
// FetchUserEmail fetches a users email address.
func FetchUserEmail(id string) (string, error) {
var email string
err := DBConn.QueryRow(`SELECT email FROM users WHERE id = $1`,
id).Scan(&email)
if err != nil {
return "", err
}
return email, nil
}
// DeleteUser deletes a user.
func DeleteUser(id string) error {
err := DeleteDomainsForUser(id)
if err != nil {
return err
}
_, err = DBConn.Exec(`DELETE FROM users WHERE id = $1`, id)
if err != nil {
return err
}
return nil
}
// UpdateUserEmail updates a users email address.
func UpdateUserEmail(id string, email string) error {
_, err := DBConn.Exec(`UPDATE users SET email = $1 WHERE id = $2`,
email, id)
if err != nil {
return err
}
return nil
}
// UpdateUserPassword updates a users password.
func UpdateUserPassword(id string, pwd string) error {
argon := argon2.DefaultConfig()
encoded, err := argon.HashEncoded([]byte(pwd))
if err != nil {
return err
}
_, err = DBConn.Exec(`UPDATE users SET pwd = $1 WHERE id = $2`,
string(encoded), id)
if err != nil {
return err
}
return nil
}
// VerifyUserPassword verifies a users password.
func VerifyUserPassword(id string, pwd string) bool {
// FIXME: Currently doesn't return any errors, and just return false in error cases
// I mean... Shouldn't really error, but who knows
var encodedPwd string
err := DBConn.QueryRow(`SELECT pwd FROM users WHERE id = $1`,
id).Scan(&encodedPwd)
if err == sql.ErrNoRows {
return false
}
if err != nil {
return false
}
ok, err := argon2.VerifyEncoded([]byte(pwd), []byte(encodedPwd))
if err != nil {
return false
}
if !ok {
return false
}
return true
}