mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-30 02:12:24 -05:00
Start messing around with federation
This commit is contained in:
parent
b0819c1a63
commit
1c3ad47f52
10 changed files with 245 additions and 83 deletions
|
|
@ -21,12 +21,16 @@ package db
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-fed/activity/pub"
|
"github.com/go-fed/activity/pub"
|
||||||
"github.com/go-fed/activity/streams/vocab"
|
"github.com/go-fed/activity/streams/vocab"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FederatingDB uses the underlying DB interface to implement the go-fed pub.Database interface.
|
// FederatingDB uses the underlying DB interface to implement the go-fed pub.Database interface.
|
||||||
|
|
@ -35,13 +39,15 @@ type federatingDB struct {
|
||||||
locks *sync.Map
|
locks *sync.Map
|
||||||
db DB
|
db DB
|
||||||
config *config.Config
|
config *config.Config
|
||||||
|
log *logrus.Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFederatingDB(db DB, config *config.Config) pub.Database {
|
func newFederatingDB(db DB, config *config.Config, log *logrus.Entry) pub.Database {
|
||||||
return &federatingDB{
|
return &federatingDB{
|
||||||
locks: new(sync.Map),
|
locks: new(sync.Map),
|
||||||
db: db,
|
db: db,
|
||||||
config: config,
|
config: config,
|
||||||
|
log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,11 +124,75 @@ func (f *federatingDB) SetInbox(c context.Context, inbox vocab.ActivityStreamsOr
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Owns returns true if the database has an entry for the IRI and it
|
// Owns returns true if the IRI belongs to this instance, and if
|
||||||
// exists in the database.
|
// the database has an entry for the IRI.
|
||||||
//
|
|
||||||
// The library makes this call only after acquiring a lock first.
|
// The library makes this call only after acquiring a lock first.
|
||||||
func (f *federatingDB) Owns(c context.Context, id *url.URL) (owns bool, err error) {
|
func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) {
|
||||||
|
l := f.log.WithFields(logrus.Fields{
|
||||||
|
"func": "Owns",
|
||||||
|
"activityID": id.String(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// if the id host isn't this instance host, we don't own this IRI
|
||||||
|
if id.Host != f.config.Host {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// apparently we own it, so what *is* it?
|
||||||
|
|
||||||
|
// check if it's a status, eg /users/example_username/statuses/SOME_UUID_OF_A_STATUS
|
||||||
|
if util.IsStatusesPath(id) {
|
||||||
|
username, uid, err := util.ParseStatusesPath(id)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err)
|
||||||
|
}
|
||||||
|
acct := >smodel.Account{}
|
||||||
|
if err := f.db.GetWhere("username", username, acct); err != nil {
|
||||||
|
if _, ok := err.(ErrNoEntries); ok {
|
||||||
|
// there are no entries for this username
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("database error fetching account with username %s: %s", username, err)
|
||||||
|
}
|
||||||
|
if acct.Domain != "" {
|
||||||
|
// this is a remote account so we don't own it after all
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
status := >smodel.Status{}
|
||||||
|
if err := f.db.GetByID(uid, status); err != nil {
|
||||||
|
if _, ok := err.(ErrNoEntries); ok {
|
||||||
|
// there are no entries for this status
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("database error fetching status with id %s: %s", uid, err)
|
||||||
|
}
|
||||||
|
// the user exists, the status exists, we own both, we're good
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if it's a user, eg /users/example_username
|
||||||
|
if util.IsUserPath(id) {
|
||||||
|
username, err := util.ParseUserPath(id)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err)
|
||||||
|
}
|
||||||
|
acct := >smodel.Account{}
|
||||||
|
if err := f.db.GetWhere("username", username, acct); err != nil {
|
||||||
|
if _, ok := err.(ErrNoEntries); ok {
|
||||||
|
// there are no entries for this username
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("database error fetching account with username %s: %s", username, err)
|
||||||
|
}
|
||||||
|
if acct.Domain != "" {
|
||||||
|
// this is a remote account so we don't own it after all
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
// the user exists, we own it, we're good
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Info("could not match activityID")
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ func newPostgresService(ctx context.Context, c *config.Config, log *logrus.Entry
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
|
|
||||||
federatingDB := newFederatingDB(ps, c)
|
federatingDB := newFederatingDB(ps, c, log)
|
||||||
ps.federationDB = federatingDB
|
ps.federationDB = federatingDB
|
||||||
|
|
||||||
// we can confidently return this useable postgres service now
|
// we can confidently return this useable postgres service now
|
||||||
|
|
|
||||||
|
|
@ -18,19 +18,70 @@
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import "regexp"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minimumPasswordEntropy = 60 // dictates password strength. See https://github.com/wagslane/go-password-validator
|
||||||
|
minimumReasonLength = 40
|
||||||
|
maximumReasonLength = 500
|
||||||
|
maximumEmailLength = 256
|
||||||
|
maximumUsernameLength = 64
|
||||||
|
maximumPasswordLength = 64
|
||||||
|
maximumEmojiShortcodeLength = 30
|
||||||
|
maximumHashtagLength = 30
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// mention regex can be played around with here: https://regex101.com/r/qwM9D3/1
|
// mention regex can be played around with here: https://regex101.com/r/qwM9D3/1
|
||||||
mentionRegexString = `(?: |^|\W)(@[a-zA-Z0-9_]+(?:@[a-zA-Z0-9_\-\.]+)?)(?: |\n)`
|
mentionFinderRegexString = `(?: |^|\W)(@[a-zA-Z0-9_]+(?:@[a-zA-Z0-9_\-\.]+)?)(?: |\n)`
|
||||||
mentionRegex = regexp.MustCompile(mentionRegexString)
|
mentionFinderRegex = regexp.MustCompile(mentionFinderRegexString)
|
||||||
|
|
||||||
// hashtag regex can be played with here: https://regex101.com/r/Vhy8pg/1
|
// hashtag regex can be played with here: https://regex101.com/r/Vhy8pg/1
|
||||||
hashtagRegexString = `(?: |^|\W)?#([a-zA-Z0-9]{1,30})(?:\b|\r)`
|
hashtagFinderRegexString = fmt.Sprintf(`(?: |^|\W)?#([a-zA-Z0-9]{1,%d})(?:\b|\r)`, maximumHashtagLength)
|
||||||
hashtagRegex = regexp.MustCompile(hashtagRegexString)
|
hashtagFinderRegex = regexp.MustCompile(hashtagFinderRegexString)
|
||||||
// emoji regex can be played with here: https://regex101.com/r/478XGM/1
|
|
||||||
emojiRegexString = `(?: |^|\W)?:([a-zA-Z0-9_]{2,30}):(?:\b|\r)?`
|
|
||||||
emojiRegex = regexp.MustCompile(emojiRegexString)
|
|
||||||
// emoji shortcode regex can be played with here: https://regex101.com/r/zMDRaG/1
|
// emoji shortcode regex can be played with here: https://regex101.com/r/zMDRaG/1
|
||||||
emojiShortcodeString = `^[a-z0-9_]{2,30}$`
|
emojiShortcodeRegexString = fmt.Sprintf(`[a-z0-9_]{2,%d}`, maximumEmojiShortcodeLength)
|
||||||
emojiShortcodeRegex = regexp.MustCompile(emojiShortcodeString)
|
emojiShortcodeValidationRegex = regexp.MustCompile(fmt.Sprintf("^%s$", emojiShortcodeRegexString))
|
||||||
|
|
||||||
|
// emoji regex can be played with here: https://regex101.com/r/478XGM/1
|
||||||
|
emojiFinderRegexString = fmt.Sprintf(`(?: |^|\W)?:(%s):(?:\b|\r)?`, emojiShortcodeRegexString)
|
||||||
|
emojiFinderRegex = regexp.MustCompile(emojiFinderRegexString)
|
||||||
|
|
||||||
|
// usernameRegexString defines an acceptable username on this instance
|
||||||
|
usernameRegexString = fmt.Sprintf(`[a-z0-9_]{2,%d}`, maximumUsernameLength)
|
||||||
|
// usernameValidationRegex can be used to validate usernames of new signups
|
||||||
|
usernameValidationRegex = regexp.MustCompile(fmt.Sprintf(`^%s$`, usernameRegexString))
|
||||||
|
|
||||||
|
userPathRegexString = fmt.Sprintf(`^?/%s/(%s)$`, UsersPath, usernameRegexString)
|
||||||
|
// userPathRegex parses a path that validates and captures the username part from eg /users/example_username
|
||||||
|
userPathRegex = regexp.MustCompile(userPathRegexString)
|
||||||
|
|
||||||
|
actorPathRegexString = fmt.Sprintf(`^?/%s/(%s)$`, ActorsPath, usernameRegexString)
|
||||||
|
// actorPathRegex parses a path that validates and captures the username part from eg /actors/example_username
|
||||||
|
actorPathRegex = regexp.MustCompile(actorPathRegexString)
|
||||||
|
|
||||||
|
followersPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, FollowersPath)
|
||||||
|
// followersPathRegex parses a path that validates and captures the username part from eg /users/example_username/followers
|
||||||
|
followersPathRegex = regexp.MustCompile(followersPathRegexString)
|
||||||
|
|
||||||
|
followingPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, FollowingPath)
|
||||||
|
// followingPathRegex parses a path that validates and captures the username part from eg /users/example_username/following
|
||||||
|
followingPathRegex = regexp.MustCompile(followingPathRegexString)
|
||||||
|
|
||||||
|
likedPathRegexString = fmt.Sprintf(`^/?%s/%s/%s$`, UsersPath, usernameRegexString, LikedPath)
|
||||||
|
// followingPathRegex parses a path that validates and captures the username part from eg /users/example_username/liked
|
||||||
|
likedPathRegex = regexp.MustCompile(likedPathRegexString)
|
||||||
|
|
||||||
|
// see https://ihateregex.io/expr/uuid/
|
||||||
|
uuidRegexString = `[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}`
|
||||||
|
|
||||||
|
statusesPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, StatusesPath, uuidRegexString)
|
||||||
|
// statusesPathRegex parses a path that validates and captures the username part and the uuid part
|
||||||
|
// from eg /users/example_username/statuses/123e4567-e89b-12d3-a456-426655440000.
|
||||||
|
// The regex can be played with here: https://regex101.com/r/G9zuxQ/1
|
||||||
|
statusesPathRegex = regexp.MustCompile(statusesPathRegexString)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import (
|
||||||
// The case of the returned mentions will be lowered, for consistency.
|
// The case of the returned mentions will be lowered, for consistency.
|
||||||
func DeriveMentions(status string) []string {
|
func DeriveMentions(status string) []string {
|
||||||
mentionedAccounts := []string{}
|
mentionedAccounts := []string{}
|
||||||
for _, m := range mentionRegex.FindAllStringSubmatch(status, -1) {
|
for _, m := range mentionFinderRegex.FindAllStringSubmatch(status, -1) {
|
||||||
mentionedAccounts = append(mentionedAccounts, m[1])
|
mentionedAccounts = append(mentionedAccounts, m[1])
|
||||||
}
|
}
|
||||||
return lower(unique(mentionedAccounts))
|
return lower(unique(mentionedAccounts))
|
||||||
|
|
@ -43,7 +43,7 @@ func DeriveMentions(status string) []string {
|
||||||
// tags will be lowered, for consistency.
|
// tags will be lowered, for consistency.
|
||||||
func DeriveHashtags(status string) []string {
|
func DeriveHashtags(status string) []string {
|
||||||
tags := []string{}
|
tags := []string{}
|
||||||
for _, m := range hashtagRegex.FindAllStringSubmatch(status, -1) {
|
for _, m := range hashtagFinderRegex.FindAllStringSubmatch(status, -1) {
|
||||||
tags = append(tags, m[1])
|
tags = append(tags, m[1])
|
||||||
}
|
}
|
||||||
return lower(unique(tags))
|
return lower(unique(tags))
|
||||||
|
|
@ -55,7 +55,7 @@ func DeriveHashtags(status string) []string {
|
||||||
// emojis will be lowered, for consistency.
|
// emojis will be lowered, for consistency.
|
||||||
func DeriveEmojis(status string) []string {
|
func DeriveEmojis(status string) []string {
|
||||||
emojis := []string{}
|
emojis := []string{}
|
||||||
for _, m := range emojiRegex.FindAllStringSubmatch(status, -1) {
|
for _, m := range emojiFinderRegex.FindAllStringSubmatch(status, -1) {
|
||||||
emojis = append(emojis, m[1])
|
emojis = append(emojis, m[1])
|
||||||
}
|
}
|
||||||
return lower(unique(emojis))
|
return lower(unique(emojis))
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,14 @@ package util
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// UsersPath is for serving users info
|
// UsersPath is for serving users info
|
||||||
UsersPath = "users"
|
UsersPath = "users"
|
||||||
|
// ActorsPath is for serving actors info
|
||||||
|
ActorsPath = "actors"
|
||||||
// StatusesPath is for serving statuses
|
// StatusesPath is for serving statuses
|
||||||
StatusesPath = "statuses"
|
StatusesPath = "statuses"
|
||||||
// InboxPath represents the webfinger inbox location
|
// InboxPath represents the webfinger inbox location
|
||||||
|
|
@ -34,6 +37,10 @@ const (
|
||||||
OutboxPath = "outbox"
|
OutboxPath = "outbox"
|
||||||
// FollowersPath represents the webfinger followers location
|
// FollowersPath represents the webfinger followers location
|
||||||
FollowersPath = "followers"
|
FollowersPath = "followers"
|
||||||
|
// FollowingPath represents the webfinger following location
|
||||||
|
FollowingPath = "following"
|
||||||
|
// LikedPath represents the webfinger liked location
|
||||||
|
LikedPath = "liked"
|
||||||
// CollectionsPath represents the webfinger collections location
|
// CollectionsPath represents the webfinger collections location
|
||||||
CollectionsPath = "collections"
|
CollectionsPath = "collections"
|
||||||
// FeaturedPath represents the webfinger featured location
|
// FeaturedPath represents the webfinger featured location
|
||||||
|
|
@ -59,6 +66,10 @@ type UserURIs struct {
|
||||||
OutboxURI string
|
OutboxURI string
|
||||||
// The webfinger URI for this user's followers, eg., https://example.org/users/example_user/followers
|
// The webfinger URI for this user's followers, eg., https://example.org/users/example_user/followers
|
||||||
FollowersURI string
|
FollowersURI string
|
||||||
|
// The webfinger URI for this user's following, eg., https://example.org/users/example_user/following
|
||||||
|
FollowingURI string
|
||||||
|
// The webfinger URI for this user's liked posts eg., https://example.org/users/example_user/liked
|
||||||
|
LikedURI string
|
||||||
// The webfinger URI for this user's featured collections, eg., https://example.org/users/example_user/collections/featured
|
// The webfinger URI for this user's featured collections, eg., https://example.org/users/example_user/collections/featured
|
||||||
CollectionURI string
|
CollectionURI string
|
||||||
}
|
}
|
||||||
|
|
@ -76,6 +87,8 @@ func GenerateURIsForAccount(username string, protocol string, host string) *User
|
||||||
inboxURI := fmt.Sprintf("%s/%s", userURI, InboxPath)
|
inboxURI := fmt.Sprintf("%s/%s", userURI, InboxPath)
|
||||||
outboxURI := fmt.Sprintf("%s/%s", userURI, OutboxPath)
|
outboxURI := fmt.Sprintf("%s/%s", userURI, OutboxPath)
|
||||||
followersURI := fmt.Sprintf("%s/%s", userURI, FollowersPath)
|
followersURI := fmt.Sprintf("%s/%s", userURI, FollowersPath)
|
||||||
|
followingURI := fmt.Sprintf("%s/%s", userURI, FollowingPath)
|
||||||
|
likedURI := fmt.Sprintf("%s/%s", userURI, LikedPath)
|
||||||
collectionURI := fmt.Sprintf("%s/%s/%s", userURI, CollectionsPath, FeaturedPath)
|
collectionURI := fmt.Sprintf("%s/%s/%s", userURI, CollectionsPath, FeaturedPath)
|
||||||
return &UserURIs{
|
return &UserURIs{
|
||||||
HostURL: hostURL,
|
HostURL: hostURL,
|
||||||
|
|
@ -87,10 +100,61 @@ func GenerateURIsForAccount(username string, protocol string, host string) *User
|
||||||
InboxURI: inboxURI,
|
InboxURI: inboxURI,
|
||||||
OutboxURI: outboxURI,
|
OutboxURI: outboxURI,
|
||||||
FollowersURI: followersURI,
|
FollowersURI: followersURI,
|
||||||
|
FollowingURI: followingURI,
|
||||||
|
LikedURI: likedURI,
|
||||||
CollectionURI: collectionURI,
|
CollectionURI: collectionURI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseActivityPubRequestURL(id *url.URL) error {
|
// IsUserPath returns true if the given URL path corresponds to eg /users/example_username
|
||||||
return nil
|
func IsUserPath(id *url.URL) bool {
|
||||||
|
return userPathRegex.MatchString(strings.ToLower(id.Path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInstanceActorPath returns true if the given URL path corresponds to eg /actors/example_username
|
||||||
|
func IsInstanceActorPath(id *url.URL) bool {
|
||||||
|
return actorPathRegex.MatchString(strings.ToLower(id.Path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFollowersPath returns true if the given URL path corresponds to eg /users/example_username/followers
|
||||||
|
func IsFollowersPath(id *url.URL) bool {
|
||||||
|
return followersPathRegex.MatchString(strings.ToLower(id.Path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFollowingPath returns true if the given URL path corresponds to eg /users/example_username/following
|
||||||
|
func IsFollowingPath(id *url.URL) bool {
|
||||||
|
return followingPathRegex.MatchString(strings.ToLower(id.Path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLikedPath returns true if the given URL path corresponds to eg /users/example_username/liked
|
||||||
|
func IsLikedPath(id *url.URL) bool {
|
||||||
|
return followingPathRegex.MatchString(strings.ToLower(id.Path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsStatusesPath returns true if the given URL path corresponds to eg /users/example_username/statuses/SOME_UUID_OF_A_STATUS
|
||||||
|
func IsStatusesPath(id *url.URL) bool {
|
||||||
|
return statusesPathRegex.MatchString(strings.ToLower(id.Path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseStatusesPath returns the username and uuid from a path such as /users/example_username/statuses/SOME_UUID_OF_A_STATUS
|
||||||
|
func ParseStatusesPath(id *url.URL) (username string, uuid string, err error) {
|
||||||
|
matches := statusesPathRegex.FindStringSubmatch(id.Path)
|
||||||
|
if len(matches) != 3 {
|
||||||
|
err = fmt.Errorf("expected 3 matches but matches length was %d", len(matches))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
username = matches[1]
|
||||||
|
uuid = matches[2]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseUserPath returns the username from a path such as /users/example_username
|
||||||
|
func ParseUserPath(id *url.URL) (username string, err error) {
|
||||||
|
matches := userPathRegex.FindStringSubmatch(id.Path)
|
||||||
|
if len(matches) != 2 {
|
||||||
|
err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
username = matches[1]
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,45 +22,22 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"regexp"
|
|
||||||
|
|
||||||
pwv "github.com/wagslane/go-password-validator"
|
pwv "github.com/wagslane/go-password-validator"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// MinimumPasswordEntropy dictates password strength. See https://github.com/wagslane/go-password-validator
|
|
||||||
MinimumPasswordEntropy = 60
|
|
||||||
// MinimumReasonLength is the length of chars we expect as a bare minimum effort
|
|
||||||
MinimumReasonLength = 40
|
|
||||||
// MaximumReasonLength is the maximum amount of chars we're happy to accept
|
|
||||||
MaximumReasonLength = 500
|
|
||||||
// MaximumEmailLength is the maximum length of an email address we're happy to accept
|
|
||||||
MaximumEmailLength = 256
|
|
||||||
// MaximumUsernameLength is the maximum length of a username we're happy to accept
|
|
||||||
MaximumUsernameLength = 64
|
|
||||||
// MaximumPasswordLength is the maximum length of a password we're happy to accept
|
|
||||||
MaximumPasswordLength = 64
|
|
||||||
// NewUsernameRegexString is string representation of the regular expression for validating usernames
|
|
||||||
NewUsernameRegexString = `^[a-z0-9_]+$`
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// NewUsernameRegex is the compiled regex for validating new usernames
|
|
||||||
NewUsernameRegex = regexp.MustCompile(NewUsernameRegexString)
|
|
||||||
)
|
|
||||||
|
|
||||||
// ValidateNewPassword returns an error if the given password is not sufficiently strong, or nil if it's ok.
|
// ValidateNewPassword returns an error if the given password is not sufficiently strong, or nil if it's ok.
|
||||||
func ValidateNewPassword(password string) error {
|
func ValidateNewPassword(password string) error {
|
||||||
if password == "" {
|
if password == "" {
|
||||||
return errors.New("no password provided")
|
return errors.New("no password provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(password) > MaximumPasswordLength {
|
if len(password) > maximumPasswordLength {
|
||||||
return fmt.Errorf("password should be no more than %d chars", MaximumPasswordLength)
|
return fmt.Errorf("password should be no more than %d chars", maximumPasswordLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pwv.Validate(password, MinimumPasswordEntropy)
|
return pwv.Validate(password, minimumPasswordEntropy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateUsername makes sure that a given username is valid (ie., letters, numbers, underscores, check length).
|
// ValidateUsername makes sure that a given username is valid (ie., letters, numbers, underscores, check length).
|
||||||
|
|
@ -70,11 +47,11 @@ func ValidateUsername(username string) error {
|
||||||
return errors.New("no username provided")
|
return errors.New("no username provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(username) > MaximumUsernameLength {
|
if len(username) > maximumUsernameLength {
|
||||||
return fmt.Errorf("username should be no more than %d chars but '%s' was %d", MaximumUsernameLength, username, len(username))
|
return fmt.Errorf("username should be no more than %d chars but '%s' was %d", maximumUsernameLength, username, len(username))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !NewUsernameRegex.MatchString(username) {
|
if !usernameValidationRegex.MatchString(username) {
|
||||||
return fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores", username)
|
return fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores", username)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,8 +65,8 @@ func ValidateEmail(email string) error {
|
||||||
return errors.New("no email provided")
|
return errors.New("no email provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(email) > MaximumEmailLength {
|
if len(email) > maximumEmailLength {
|
||||||
return fmt.Errorf("email address should be no more than %d chars but '%s' was %d", MaximumEmailLength, email, len(email))
|
return fmt.Errorf("email address should be no more than %d chars but '%s' was %d", maximumEmailLength, email, len(email))
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := mail.ParseAddress(email)
|
_, err := mail.ParseAddress(email)
|
||||||
|
|
@ -118,12 +95,12 @@ func ValidateSignUpReason(reason string, reasonRequired bool) error {
|
||||||
return errors.New("no reason provided")
|
return errors.New("no reason provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(reason) < MinimumReasonLength {
|
if len(reason) < minimumReasonLength {
|
||||||
return fmt.Errorf("reason should be at least %d chars but '%s' was %d", MinimumReasonLength, reason, len(reason))
|
return fmt.Errorf("reason should be at least %d chars but '%s' was %d", minimumReasonLength, reason, len(reason))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(reason) > MaximumReasonLength {
|
if len(reason) > maximumReasonLength {
|
||||||
return fmt.Errorf("reason should be no more than %d chars but given reason was %d", MaximumReasonLength, len(reason))
|
return fmt.Errorf("reason should be no more than %d chars but given reason was %d", maximumReasonLength, len(reason))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +127,7 @@ func ValidatePrivacy(privacy string) error {
|
||||||
// for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters,
|
// for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters,
|
||||||
// lowercase a-z, numbers, and underscores.
|
// lowercase a-z, numbers, and underscores.
|
||||||
func ValidateEmojiShortcode(shortcode string) error {
|
func ValidateEmojiShortcode(shortcode string) error {
|
||||||
if !emojiShortcodeRegex.MatchString(shortcode) {
|
if !emojiShortcodeValidationRegex.MatchString(shortcode) {
|
||||||
return fmt.Errorf("shortcode %s did not pass validation, must be between 2 and 30 characters, lowercase letters, numbers, and underscores only", shortcode)
|
return fmt.Errorf("shortcode %s did not pass validation, must be between 2 and 30 characters, lowercase letters, numbers, and underscores only", shortcode)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue