/* * Copyright (C) 2024 Jonni Liljamo * * 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 }