1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-09-10 06:36:23 -07:00
gochan/pkg/gcsql/staff.go

261 lines
7.7 KiB
Go
Raw Normal View History

2022-01-30 18:15:15 -08:00
package gcsql
import (
"context"
"database/sql"
2022-09-03 15:25:50 -07:00
"errors"
"fmt"
"net/http"
2022-01-30 18:15:15 -08:00
"time"
"github.com/Eggbertx/durationutil"
"github.com/gochan-org/gochan/pkg/config"
2022-01-30 18:15:15 -08:00
"github.com/gochan-org/gochan/pkg/gcutil"
)
2022-10-31 12:27:41 -07:00
var (
ErrUnrecognizedUsername = errors.New("invalid username")
)
// createDefaultAdminIfNoStaff creates a new default admin account if no accounts exist
func createDefaultAdminIfNoStaff() error {
2024-03-28 23:04:22 -07:00
const query = `SELECT COUNT(id) FROM DBPREFIXstaff`
var count int
2024-05-30 13:16:13 -07:00
err := QueryRowTimeoutSQL(nil, query, nil, []any{&count})
2024-03-28 23:04:22 -07:00
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
if count > 0 {
return nil
}
2024-03-28 23:04:22 -07:00
_, err = NewStaff("admin", "password", 3)
return err
2022-01-30 18:15:15 -08:00
}
func NewStaff(username string, password string, rank int) (*Staff, error) {
const sqlINSERT = `INSERT INTO DBPREFIXstaff
(username, password_checksum, global_rank)
VALUES(?,?,?)`
passwordChecksum := gcutil.BcryptSum(password)
2024-05-30 13:16:13 -07:00
_, err := ExecTimeoutSQL(nil, sqlINSERT, username, passwordChecksum, rank)
2024-03-28 23:04:22 -07:00
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, err
}
return &Staff{
Username: username,
PasswordChecksum: passwordChecksum,
Rank: rank,
AddedOn: time.Now(),
IsActive: true,
}, nil
}
// SetActive changes the active status of the staff member. If `active` is false, the login sessions are cleared
func (s *Staff) SetActive(active bool) error {
const updateActive = `UPDATE DBPREFIXstaff SET is_active = FALSE WHERE username = ?`
2024-05-30 13:16:13 -07:00
_, err := ExecTimeoutSQL(nil, updateActive, s.Username)
if err != nil {
return err
}
if active {
return nil
}
return s.ClearSessions()
}
// ClearSessions clears all login sessions for the user, requiring them to login again
func (s *Staff) ClearSessions() error {
const query = `SELECT id FROM DBPREFIXstaff WHERE username = ?`
const deleteSessions = `DELETE FROM DBPREFIXsessions WHERE staff_id = ?`
var err error
ctx, cancel := context.WithTimeout(context.Background(), gcdb.defaultTimeout)
defer cancel()
if s.ID == 0 {
// ID field not set, get it from the DB
2024-12-14 22:17:45 -08:00
err = QueryRowContextSQL(ctx, nil, query, []any{s.Username}, []any{&s.ID})
if errors.Is(err, sql.ErrNoRows) {
return ErrUnrecognizedUsername
}
if err != nil {
return err
}
}
_, err = ExecContextSQL(ctx, nil, deleteSessions, s.ID)
return err
2022-01-30 18:15:15 -08:00
}
func (s *Staff) RankTitle() string {
if s.Rank == 3 {
return "Administrator"
} else if s.Rank == 2 {
return "Moderator"
} else if s.Rank == 1 {
return "Janitor"
}
return ""
}
2024-12-14 22:17:45 -08:00
// UpdateStaff sets the rank and password of the staff account with the given username
func UpdateStaff(username string, rank int, password string) error {
// first check if it's a recognized username
id, err := GetStaffID(username)
if err != nil {
return err
}
const sqlUpdate = `UPDATE DBPREFIXstaff SET global_rank = ?, password_checksum = ? WHERE id = ?`
checksum := gcutil.BcryptSum(password)
_, err = ExecTimeoutSQL(nil, sqlUpdate, rank, checksum, id)
return err
}
// UpdateStaff sets the password of the staff account with the given username
2023-06-07 14:18:02 -07:00
func UpdatePassword(username string, newPassword string) error {
2024-12-14 22:17:45 -08:00
const sqlUPDATE = `UPDATE DBPREFIXstaff SET password_checksum = ? WHERE id = ?`
id, err := GetStaffID(username)
if err != nil {
return err
}
2023-06-07 14:18:02 -07:00
checksum := gcutil.BcryptSum(newPassword)
2024-12-14 22:17:45 -08:00
_, err = ExecTimeoutSQL(nil, sqlUPDATE, checksum, id)
2023-06-07 14:18:02 -07:00
return err
}
// EndStaffSession deletes any session rows associated with the requests session cookie and then
// makes the cookie expire, essentially deleting it
func EndStaffSession(writer http.ResponseWriter, request *http.Request) error {
session, err := request.Cookie("sessiondata")
if err != nil {
// No staff session cookie, presumably not logged in so nothing to do
return nil
}
// make it so that the next time the page is loaded, the browser will delete it
sessionVal := session.Value
session.MaxAge = -1
http.SetCookie(writer, session)
ctx, cancel := context.WithTimeout(context.Background(), gcdb.defaultTimeout)
defer cancel()
staffID := 0
if err = QueryRowContextSQL(ctx, nil, `SELECT staff_id FROM DBPREFIXsessions WHERE data = ?`,
[]any{session.Value}, []any{&staffID}); err != nil && err != sql.ErrNoRows {
// something went wrong with the query and it's not caused by no rows being returned
2022-09-03 15:25:50 -07:00
return errors.New("failed getting staff ID: " + err.Error())
}
_, err = ExecContextSQL(ctx, nil, `DELETE FROM DBPREFIXsessions WHERE data = ?`, sessionVal)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
2022-09-03 15:25:50 -07:00
return fmt.Errorf("failed clearing session for staff with id %d", staffID)
}
return nil
}
func DeactivateStaff(username string) error {
s := Staff{Username: username}
return s.SetActive(false)
2022-01-30 18:15:15 -08:00
}
func GetStaffUsernameFromID(id int) (string, error) {
2022-10-31 12:27:41 -07:00
const query = `SELECT username FROM DBPREFIXstaff WHERE id = ?`
var username string
2024-05-30 13:16:13 -07:00
err := QueryRowTimeoutSQL(nil, query, []any{id}, []any{&username})
2022-10-31 12:27:41 -07:00
return username, err
}
2024-12-14 22:17:45 -08:00
// GetStaffID gets the ID of the given staff, given the username, and returns ErrUnrecognizedUsername if none match
2023-10-24 15:22:41 -07:00
func GetStaffID(username string) (int, error) {
2024-12-14 22:17:45 -08:00
const query = `SELECT id FROM DBPREFIXstaff WHERE username = ?`
2023-10-24 15:22:41 -07:00
var id int
2024-05-30 13:16:13 -07:00
err := QueryRowTimeoutSQL(nil, query, []any{username}, []any{&id})
2024-12-14 22:17:45 -08:00
if errors.Is(err, sql.ErrNoRows) {
err = ErrUnrecognizedUsername
}
2023-10-24 15:22:41 -07:00
return id, err
}
// GetStaffBySession gets the staff that is logged in in the given session
func GetStaffBySession(session string) (*Staff, error) {
2022-10-31 12:27:41 -07:00
const query = `SELECT
2024-05-30 13:16:13 -07:00
staff.id, staff.username, staff.password_checksum, staff.global_rank, staff.added_on, staff.last_login
2022-01-30 18:15:15 -08:00
FROM DBPREFIXstaff as staff
2024-05-30 13:16:13 -07:00
JOIN DBPREFIXsessions as sessions ON sessions.staff_id = staff.id
WHERE sessions.data = ?`
var staff Staff
2024-05-30 13:16:13 -07:00
err := QueryRowTimeoutSQL(nil, query, []any{session}, []any{
&staff.ID, &staff.Username, &staff.PasswordChecksum, &staff.Rank, &staff.AddedOn, &staff.LastLogin})
return &staff, err
2022-10-31 12:27:41 -07:00
}
// GetStaffFromRequest returns the staff making the request. If the request does not have
// a staff cookie, it will return a staff object with rank 0.
func GetStaffFromRequest(request *http.Request) (*Staff, error) {
sessionCookie, err := request.Cookie("sessiondata")
if err != nil {
return &Staff{Rank: 0}, nil
}
staff, err := GetStaffBySession(sessionCookie.Value)
if errors.Is(err, sql.ErrNoRows) {
return &Staff{Rank: 0}, nil
} else if err != nil {
return nil, err
}
return staff, nil
}
2022-10-31 12:27:41 -07:00
func GetStaffByUsername(username string, onlyActive bool) (*Staff, error) {
query := `SELECT
id, username, password_checksum, global_rank, added_on, last_login, is_active
FROM DBPREFIXstaff WHERE username = ?`
if onlyActive {
query += ` AND is_active = TRUE`
}
staff := new(Staff)
2024-05-30 13:16:13 -07:00
err := QueryRowTimeoutSQL(nil, query, []any{username}, []any{
2022-11-09 10:56:10 -08:00
&staff.ID, &staff.Username, &staff.PasswordChecksum, &staff.Rank, &staff.AddedOn,
2022-10-31 12:27:41 -07:00
&staff.LastLogin, &staff.IsActive,
})
2022-10-31 12:27:41 -07:00
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrUnrecognizedUsername
}
2022-01-30 18:15:15 -08:00
return staff, err
}
2022-10-31 12:27:41 -07:00
// CreateLoginSession inserts a session for a given key and username into the database
2022-11-09 10:56:10 -08:00
func (staff *Staff) CreateLoginSession(key string) error {
2022-10-31 12:27:41 -07:00
const insertSQL = `INSERT INTO DBPREFIXsessions (staff_id,data,expires) VALUES(?,?,?)`
const updateSQL = `UPDATE DBPREFIXstaff SET last_login = CURRENT_TIMESTAMP WHERE id = ?`
ctx, cancel := context.WithTimeout(context.Background(), gcdb.defaultTimeout)
defer cancel()
tx, err := BeginContextTx(ctx)
2022-10-31 12:27:41 -07:00
if err != nil {
return err
}
defer tx.Rollback()
dur, err := durationutil.ParseLongerDuration(config.GetSiteConfig().StaffSessionDuration)
if err != nil {
return err
}
_, err = ExecContextSQL(ctx, tx, insertSQL, staff.ID, key, time.Now().Add(dur))
if err != nil {
return err
}
_, err = ExecContextSQL(ctx, tx, updateSQL, staff.ID)
if err != nil {
return err
}
return tx.Commit()
2022-10-31 12:27:41 -07:00
}