2021-01-10 01:49:48 +05:00
|
|
|
|
package user
|
|
|
|
|
|
|
|
|
|
import (
|
2022-05-17 16:31:12 +03:00
|
|
|
|
"errors"
|
2021-07-16 00:46:35 +07:00
|
|
|
|
"fmt"
|
2024-09-07 23:55:39 +03:00
|
|
|
|
"log/slog"
|
2021-07-16 00:46:35 +07:00
|
|
|
|
"net/http"
|
|
|
|
|
"time"
|
2021-01-10 01:49:48 +05:00
|
|
|
|
|
2024-09-07 23:55:39 +03:00
|
|
|
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
2021-01-10 01:49:48 +05:00
|
|
|
|
"github.com/bouncepaw/mycorrhiza/util"
|
2024-09-07 23:55:39 +03:00
|
|
|
|
|
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
2021-01-10 01:49:48 +05:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 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()
|
2021-01-10 01:49:48 +05:00
|
|
|
|
}
|
2024-06-16 14:14:54 +01:00
|
|
|
|
return ByName(username[0])
|
2021-01-10 01:49:48 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-19 21:39:25 +05:00
|
|
|
|
// 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 {
|
2022-05-17 16:31:12 +03:00
|
|
|
|
if !IsValidUsername(username) {
|
|
|
|
|
return fmt.Errorf("illegal username ‘%s’", username)
|
|
|
|
|
}
|
2021-04-19 21:39:25 +05:00
|
|
|
|
username = util.CanonicalName(username)
|
2021-07-02 17:25:13 +07:00
|
|
|
|
|
2021-04-19 21:39:25 +05:00
|
|
|
|
switch {
|
2021-10-27 13:43:01 +07:00
|
|
|
|
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)
|
2021-07-02 17:25:13 +07:00
|
|
|
|
case !force && cfg.RegistrationLimit > 0 && Count() >= cfg.RegistrationLimit:
|
|
|
|
|
return fmt.Errorf("reached the limit of registered users (%d)", cfg.RegistrationLimit)
|
2023-02-25 23:38:38 +03:00
|
|
|
|
case password == "" && source != "telegram":
|
2022-08-20 15:33:05 +03:00
|
|
|
|
return fmt.Errorf("password must not be empty")
|
2021-04-19 21:39:25 +05:00
|
|
|
|
}
|
2021-07-02 17:25:13 +07:00
|
|
|
|
|
2021-04-19 21:39:25 +05:00
|
|
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2021-07-02 17:25:13 +07:00
|
|
|
|
|
2021-04-19 21:39:25 +05:00
|
|
|
|
u := User{
|
2021-07-02 15:20:03 +07:00
|
|
|
|
Name: username,
|
2021-07-02 17:25:13 +07:00
|
|
|
|
Group: group,
|
2021-07-16 00:46:35 +07:00
|
|
|
|
Source: source,
|
2021-07-02 15:20:03 +07:00
|
|
|
|
Password: string(hash),
|
|
|
|
|
RegisteredAt: time.Now(),
|
2021-04-19 21:39:25 +05:00
|
|
|
|
}
|
|
|
|
|
users.Store(username, &u)
|
2021-07-02 17:25:13 +07:00
|
|
|
|
return SaveUserDatabase()
|
2021-04-19 21:39:25 +05:00
|
|
|
|
}
|
|
|
|
|
|
2024-09-07 21:22:41 +03:00
|
|
|
|
var (
|
|
|
|
|
ErrUnknownUsername = errors.New("unknown username")
|
|
|
|
|
ErrWrongPassword = errors.New("wrong password")
|
|
|
|
|
)
|
|
|
|
|
|
2021-01-10 01:49:48 +05:00
|
|
|
|
// 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.
|
2022-05-17 16:31:12 +03:00
|
|
|
|
func LoginDataHTTP(w http.ResponseWriter, username, password string) error {
|
2021-01-10 01:49:48 +05:00
|
|
|
|
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
|
|
|
|
if !HasUsername(username) {
|
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
2024-09-07 23:55:39 +03:00
|
|
|
|
slog.Info("Unknown username entered", "username", username)
|
2024-09-07 21:22:41 +03:00
|
|
|
|
return ErrUnknownUsername
|
2021-01-10 01:49:48 +05:00
|
|
|
|
}
|
|
|
|
|
if !CredentialsOK(username, password) {
|
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
2024-09-07 23:55:39 +03:00
|
|
|
|
slog.Info("Wrong password entered", "username", username)
|
2024-09-07 21:22:41 +03:00
|
|
|
|
return ErrWrongPassword
|
2021-01-10 01:49:48 +05:00
|
|
|
|
}
|
|
|
|
|
token, err := AddSession(username)
|
|
|
|
|
if err != nil {
|
2024-09-07 23:55:39 +03:00
|
|
|
|
slog.Error("Failed to add session", "username", username, "err", err)
|
2021-01-10 01:49:48 +05:00
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
2022-05-17 16:31:12 +03:00
|
|
|
|
return err
|
2021-01-10 01:49:48 +05:00
|
|
|
|
}
|
|
|
|
|
http.SetCookie(w, cookie("token", token, time.Now().Add(365*24*time.Hour)))
|
2022-05-17 16:31:12 +03:00
|
|
|
|
return nil
|
2021-01-10 01:49:48 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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)
|
2024-09-07 23:55:39 +03:00
|
|
|
|
slog.Info("Added session", "username", username)
|
2021-01-10 01:49:48 +05:00
|
|
|
|
}
|
|
|
|
|
return token, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A handy cookie constructor
|
2021-10-02 01:12:16 +08:00
|
|
|
|
func cookie(nameSuffix, val string, t time.Time) *http.Cookie {
|
2021-01-10 01:49:48 +05:00
|
|
|
|
return &http.Cookie{
|
2021-10-02 01:12:16 +08:00
|
|
|
|
Name: "mycorrhiza_" + nameSuffix,
|
2021-01-10 01:49:48 +05:00
|
|
|
|
Value: val,
|
|
|
|
|
Expires: t,
|
|
|
|
|
Path: "/",
|
|
|
|
|
}
|
|
|
|
|
}
|