1
0

util.go 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. package user
  2. import (
  3. "golang.org/x/crypto/bcrypt"
  4. "heckel.io/ntfy/v2/util"
  5. "regexp"
  6. "strings"
  7. )
  8. var (
  9. allowedUsernameRegex = regexp.MustCompile(`^[-_.+@a-zA-Z0-9]+$`) // Does not include Everyone (*)
  10. allowedTopicRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`) // No '*'
  11. allowedTopicPatternRegex = regexp.MustCompile(`^[-_*A-Za-z0-9]{1,64}$`) // Adds '*' for wildcards!
  12. allowedTierRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`)
  13. allowedTokenRegex = regexp.MustCompile(`^tk_[-_A-Za-z0-9]{29}$`) // Must be tokenLength-len(tokenPrefix)
  14. )
  15. // AllowedRole returns true if the given role can be used for new users
  16. func AllowedRole(role Role) bool {
  17. return role == RoleUser || role == RoleAdmin
  18. }
  19. // AllowedUsername returns true if the given username is valid
  20. func AllowedUsername(username string) bool {
  21. return allowedUsernameRegex.MatchString(username)
  22. }
  23. // AllowedTopic returns true if the given topic name is valid
  24. func AllowedTopic(topic string) bool {
  25. return allowedTopicRegex.MatchString(topic)
  26. }
  27. // AllowedTopicPattern returns true if the given topic pattern is valid; this includes the wildcard character (*)
  28. func AllowedTopicPattern(topic string) bool {
  29. return allowedTopicPatternRegex.MatchString(topic)
  30. }
  31. // AllowedTier returns true if the given tier name is valid
  32. func AllowedTier(tier string) bool {
  33. return allowedTierRegex.MatchString(tier)
  34. }
  35. // ValidPasswordHash checks if the given password hash is a valid bcrypt hash
  36. func ValidPasswordHash(hash string) error {
  37. if !strings.HasPrefix(hash, "$2a$") && !strings.HasPrefix(hash, "$2b$") && !strings.HasPrefix(hash, "$2y$") {
  38. return ErrPasswordHashInvalid
  39. }
  40. cost, err := bcrypt.Cost([]byte(hash))
  41. if err != nil {
  42. return err
  43. } else if cost < DefaultUserPasswordBcryptCost {
  44. return ErrPasswordHashWeak
  45. }
  46. return nil
  47. }
  48. // ValidToken returns true if the given token matches the naming convention
  49. func ValidToken(token string) bool {
  50. return allowedTokenRegex.MatchString(token)
  51. }
  52. // GenerateToken generates a new token with a prefix and a fixed length
  53. // Lowercase only to support "<topic>+<token>@<domain>" email addresses
  54. func GenerateToken() string {
  55. return util.RandomLowerStringPrefix(tokenPrefix, tokenLength)
  56. }
  57. // HashPassword hashes the given password using bcrypt with the configured cost
  58. func HashPassword(password string) (string, error) {
  59. return hashPassword(password, DefaultUserPasswordBcryptCost)
  60. }
  61. func hashPassword(password string, cost int) (string, error) {
  62. hash, err := bcrypt.GenerateFromPassword([]byte(password), cost)
  63. if err != nil {
  64. return "", err
  65. }
  66. return string(hash), nil
  67. }