1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2025-03-10 13:38:20 +00:00

129 lines
3.7 KiB
Go
Raw Normal View History

package user
import (
"errors"
"fmt"
"log/slog"
"net/http"
"time"
"github.com/bouncepaw/mycorrhiza/internal/cfg"
"github.com/bouncepaw/mycorrhiza/util"
"golang.org/x/crypto/bcrypt"
)
// CanProceed returns `true` if the user in `rq` has enough rights to access `route`.
func CanProceed(rq *http.Request, route string) bool {
return FromRequest(rq).CanProceed(route)
}
// FromRequest returns user from `rq`. If there is no user, an anon user is returned instead.
func FromRequest(rq *http.Request) *User {
2025-03-07 10:26:46 +00:00
if cfg.OverrideLogin != "" {
return ByName(cfg.OverrideLogin)
}
2024-06-16 14:14:54 +01:00
username, ok := rq.Header["X-Webauth-User"]
if !ok || len(username) < 1 {
2021-01-24 12:30:14 +05:00
return EmptyUser()
}
2024-06-16 14:14:54 +01:00
return ByName(username[0])
}
// LogoutFromRequest logs the user in `rq` out and rewrites the cookie in `w`.
func LogoutFromRequest(w http.ResponseWriter, rq *http.Request) {
cookieFromUser, err := rq.Cookie("mycorrhiza_token")
if err == nil {
http.SetCookie(w, cookie("token", "", time.Unix(0, 0)))
terminateSession(cookieFromUser.Value)
}
}
// Register registers the given user. If it fails, a non-nil error is returned.
2021-07-14 21:00:35 +00:00
func Register(username, password, group, source string, force bool) error {
if !IsValidUsername(username) {
return fmt.Errorf("illegal username %s", username)
}
username = util.CanonicalName(username)
switch {
case !IsValidUsername(username):
2021-08-12 17:12:53 +05:00
return fmt.Errorf("illegal username %s", username)
2021-07-02 19:02:42 +07:00
case !ValidGroup(group):
2021-08-12 17:12:53 +05:00
return fmt.Errorf("invalid group %s", group)
2021-07-14 21:00:35 +00:00
case !ValidSource(source):
2021-08-12 17:12:53 +05:00
return fmt.Errorf("invalid source %s", source)
2021-07-02 19:02:42 +07:00
case HasUsername(username):
2021-08-12 17:12:53 +05:00
return fmt.Errorf("username %s is already taken", username)
case !force && cfg.RegistrationLimit > 0 && Count() >= cfg.RegistrationLimit:
return fmt.Errorf("reached the limit of registered users (%d)", cfg.RegistrationLimit)
case password == "" && source != "telegram":
return fmt.Errorf("password must not be empty")
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
u := User{
Name: username,
Group: group,
Source: source,
Password: string(hash),
RegisteredAt: time.Now(),
}
users.Store(username, &u)
return SaveUserDatabase()
}
var (
ErrUnknownUsername = errors.New("unknown username")
ErrWrongPassword = errors.New("wrong password")
)
// LoginDataHTTP logs such user in and returns string representation of an error if there is any.
2021-07-14 19:51:55 +00:00
//
// The HTTP parameters are used for setting header status (bad request, if it is bad) and saving a cookie.
func LoginDataHTTP(w http.ResponseWriter, username, password string) error {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
if !HasUsername(username) {
w.WriteHeader(http.StatusBadRequest)
slog.Info("Unknown username entered", "username", username)
return ErrUnknownUsername
}
if !CredentialsOK(username, password) {
w.WriteHeader(http.StatusBadRequest)
slog.Info("Wrong password entered", "username", username)
return ErrWrongPassword
}
token, err := AddSession(username)
if err != nil {
slog.Error("Failed to add session", "username", username, "err", err)
w.WriteHeader(http.StatusBadRequest)
return err
}
http.SetCookie(w, cookie("token", token, time.Now().Add(365*24*time.Hour)))
return nil
}
// AddSession saves a session for `username` and returns a token to use.
func AddSession(username string) (string, error) {
token, err := util.RandomString(16)
if err == nil {
commenceSession(username, token)
slog.Info("Added session", "username", username)
}
return token, err
}
// A handy cookie constructor
func cookie(nameSuffix, val string, t time.Time) *http.Cookie {
return &http.Cookie{
Name: "mycorrhiza_" + nameSuffix,
Value: val,
Expires: t,
Path: "/",
}
}