2020-11-13 18:45:42 +00:00
package user
import (
2021-07-10 19:04:21 +00:00
"net/http"
2021-10-27 06:43:01 +00:00
"regexp"
"strings"
2021-01-09 20:49:48 +00:00
"sync"
2021-06-29 10:34:36 +00:00
"time"
2021-04-19 16:39:25 +00:00
2021-07-02 08:20:03 +00:00
"github.com/bouncepaw/mycorrhiza/cfg"
2021-04-19 16:39:25 +00:00
"golang.org/x/crypto/bcrypt"
2020-11-13 18:45:42 +00:00
)
2021-10-27 06:43:01 +00:00
var usernamePattern = regexp . MustCompile ( ` [^?!:#@><*|"'&% { }/]+ ` )
// User contains information about a given user required for identification.
2021-01-09 20:49:48 +00:00
type User struct {
// Name is a username. It must follow hypha naming rules.
2021-07-02 08:20:03 +00:00
Name string ` json:"name" `
Group string ` json:"group" `
Password string ` json:"hashed_password" `
RegisteredAt time . Time ` json:"registered_on" `
2021-11-10 07:21:11 +00:00
// Source is where the user from. Valid values: local, telegram.
2021-07-15 17:46:35 +00:00
Source string ` json:"source" `
2021-01-09 20:49:48 +00:00
sync . RWMutex
2021-04-19 16:39:25 +00:00
// A note about why HashedPassword is string and not []byte. The reason is
// simple: golang's json marshals []byte as slice of numbers, which is not
// acceptable.
2020-11-14 13:03:06 +00:00
}
2021-01-09 20:49:48 +00:00
// Route — Right (more is more right)
var minimalRights = map [ string ] int {
2021-01-23 19:00:58 +00:00
"edit" : 1 ,
"upload-binary" : 1 ,
"upload-text" : 1 ,
"rename-ask" : 2 ,
"rename-confirm" : 2 ,
"unattach-ask" : 2 ,
"unattach-confirm" : 2 ,
"update-header-links" : 3 ,
"delete-ask" : 3 ,
"delete-confirm" : 3 ,
"reindex" : 4 ,
2021-03-14 15:01:32 +00:00
"admin" : 4 ,
2021-02-18 14:50:37 +00:00
"admin/shutdown" : 4 ,
2020-11-14 14:46:04 +00:00
}
2021-06-29 15:10:48 +00:00
var groups = [ ] string {
"anon" ,
"editor" ,
"trusted" ,
"moderator" ,
"admin" ,
}
2021-07-14 21:00:35 +00:00
// Group — Right level
2021-01-09 20:49:48 +00:00
var groupRight = map [ string ] int {
"anon" : 0 ,
"editor" : 1 ,
"trusted" : 2 ,
"moderator" : 3 ,
"admin" : 4 ,
2020-11-14 13:03:06 +00:00
}
2021-10-01 17:12:16 +00:00
// ValidGroup checks whether provided user group name exists.
2021-06-29 15:10:48 +00:00
func ValidGroup ( group string ) bool {
for _ , grp := range groups {
if grp == group {
return true
}
}
return false
}
2021-10-01 17:12:16 +00:00
// ValidSource checks whether provided user source name exists.
2021-07-14 21:00:35 +00:00
func ValidSource ( source string ) bool {
return source == "local" || source == "telegram"
}
2021-10-01 17:12:16 +00:00
// EmptyUser constructs an anonymous user.
2021-01-24 07:30:14 +00:00
func EmptyUser ( ) * User {
2021-01-09 20:49:48 +00:00
return & User {
Name : "anon" ,
Group : "anon" ,
Password : "" ,
2021-07-15 17:46:35 +00:00
Source : "local" ,
2020-11-14 10:39:18 +00:00
}
}
2022-01-30 21:34:52 +00:00
// WikimindUser constructs the wikimind user, which is to be used for automated wiki edits and has admin privileges.
func WikimindUser ( ) * User {
return & User {
Name : "wikimind" ,
Group : "admin" ,
Password : "" ,
Source : "local" ,
}
}
2021-10-01 17:12:16 +00:00
// CanProceed checks whether user has rights to visit the provided path (and perform an action).
2021-01-09 20:49:48 +00:00
func ( user * User ) CanProceed ( route string ) bool {
2021-07-02 08:20:03 +00:00
if ! cfg . UseAuth {
2021-01-09 20:49:48 +00:00
return true
2020-11-14 10:39:18 +00:00
}
2020-11-14 13:03:06 +00:00
2021-01-09 20:49:48 +00:00
user . RLock ( )
defer user . RUnlock ( )
2020-11-14 10:39:18 +00:00
2021-10-29 08:59:30 +00:00
right := groupRight [ user . Group ]
minimalRight := minimalRights [ route ]
return right >= minimalRight
2020-11-14 10:39:18 +00:00
}
2021-01-09 20:49:48 +00:00
func ( user * User ) isCorrectPassword ( password string ) bool {
user . RLock ( )
defer user . RUnlock ( )
2020-11-13 18:45:42 +00:00
2021-07-02 08:20:03 +00:00
err := bcrypt . CompareHashAndPassword ( [ ] byte ( user . Password ) , [ ] byte ( password ) )
return err == nil
2020-11-14 10:39:18 +00:00
}
2021-07-10 19:04:21 +00:00
// ShowLockMaybe redirects to the lock page if the user is anon and the wiki has been configured to use the lock. It returns true if the user was redirected.
func ( user * User ) ShowLockMaybe ( w http . ResponseWriter , rq * http . Request ) bool {
if cfg . Locked && user . Group == "anon" {
http . Redirect ( w , rq , "/lock" , http . StatusSeeOther )
return true
}
return false
}
2021-10-27 06:43:01 +00:00
// IsValidUsername checks if the given username is valid.
func IsValidUsername ( username string ) bool {
return username != "anon" && username != "wikimind" &&
usernamePattern . MatchString ( strings . TrimSpace ( username ) ) &&
usernameIsWhiteListed ( username )
}
func usernameIsWhiteListed ( username string ) bool {
if ! cfg . UseWhiteList {
return true
}
for _ , allowedUsername := range cfg . WhiteList {
if allowedUsername == username {
return true
}
}
return false
}