2021-04-01 20:46:45 +02:00
/ *
GoToSocial
2021-12-20 18:42:19 +01:00
Copyright ( C ) 2021 - 2022 GoToSocial Authors admin @ gotosocial . org
2021-04-01 20:46:45 +02:00
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
2021-09-01 18:29:25 +02:00
package validate
2021-04-01 20:46:45 +02:00
import (
"errors"
"fmt"
"net/mail"
2022-05-09 10:31:46 +02:00
"strings"
2021-04-01 20:46:45 +02:00
2021-09-11 13:19:06 +02:00
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
2022-09-12 13:14:29 +02:00
"github.com/superseriousbusiness/gotosocial/internal/config"
2021-09-01 18:29:25 +02:00
"github.com/superseriousbusiness/gotosocial/internal/regexes"
2021-04-01 20:46:45 +02:00
pwv "github.com/wagslane/go-password-validator"
"golang.org/x/text/language"
)
2021-06-23 16:35:57 +02:00
const (
maximumPasswordLength = 64
minimumPasswordEntropy = 60 // dictates password strength. See https://github.com/wagslane/go-password-validator
minimumReasonLength = 40
maximumReasonLength = 500
maximumSiteTitleLength = 40
maximumShortDescriptionLength = 500
maximumDescriptionLength = 5000
maximumSiteTermsLength = 5000
2021-09-01 18:29:25 +02:00
maximumUsernameLength = 64
2022-09-12 13:14:29 +02:00
maximumCustomCSSLength = 5000
2021-06-23 16:35:57 +02:00
)
2021-09-01 18:29:25 +02:00
// NewPassword returns an error if the given password is not sufficiently strong, or nil if it's ok.
func NewPassword ( password string ) error {
2021-04-01 20:46:45 +02:00
if password == "" {
return errors . New ( "no password provided" )
}
2021-05-08 14:25:55 +02:00
if len ( password ) > maximumPasswordLength {
return fmt . Errorf ( "password should be no more than %d chars" , maximumPasswordLength )
2021-04-01 20:46:45 +02:00
}
2022-05-09 10:31:46 +02:00
if err := pwv . Validate ( password , minimumPasswordEntropy ) ; err != nil {
// Modify error message to include percentage requred entropy the password has
percent := int ( 100 * pwv . GetEntropy ( password ) / minimumPasswordEntropy )
return errors . New ( strings . ReplaceAll (
err . Error ( ) ,
"insecure password" ,
2022-08-08 10:40:51 +02:00
fmt . Sprintf ( "password is only %d%% strength" , percent ) ) )
2022-05-09 10:31:46 +02:00
}
return nil // pasword OK
2021-04-01 20:46:45 +02:00
}
2021-09-01 18:29:25 +02:00
// Username makes sure that a given username is valid (ie., letters, numbers, underscores, check length).
2021-04-01 20:46:45 +02:00
// Returns an error if not.
2021-09-01 18:29:25 +02:00
func Username ( username string ) error {
2021-04-01 20:46:45 +02:00
if username == "" {
return errors . New ( "no username provided" )
}
2021-09-01 18:29:25 +02:00
if ! regexes . Username . MatchString ( username ) {
2021-06-23 16:35:57 +02:00
return fmt . Errorf ( "given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max %d characters" , username , maximumUsernameLength )
2021-04-01 20:46:45 +02:00
}
return nil
}
2021-09-01 18:29:25 +02:00
// Email makes sure that a given email address is a valid address.
2021-04-01 20:46:45 +02:00
// Returns an error if not.
2021-09-01 18:29:25 +02:00
func Email ( email string ) error {
2021-04-01 20:46:45 +02:00
if email == "" {
return errors . New ( "no email provided" )
}
_ , err := mail . ParseAddress ( email )
return err
}
2021-09-01 18:29:25 +02:00
// Language checks that the given language string is a 2- or 3-letter ISO 639 code.
2021-04-01 20:46:45 +02:00
// Returns an error if the language cannot be parsed. See: https://pkg.go.dev/golang.org/x/text/language
2021-09-01 18:29:25 +02:00
func Language ( lang string ) error {
2021-04-01 20:46:45 +02:00
if lang == "" {
return errors . New ( "no language provided" )
}
_ , err := language . ParseBase ( lang )
return err
}
2021-09-01 18:29:25 +02:00
// SignUpReason checks that a sufficient reason is given for a server signup request
func SignUpReason ( reason string , reasonRequired bool ) error {
2021-04-01 20:46:45 +02:00
if ! reasonRequired {
// we don't care!
// we're not going to do anything with this text anyway if no reason is required
return nil
}
if reason == "" {
return errors . New ( "no reason provided" )
}
2021-05-08 14:25:55 +02:00
if len ( reason ) < minimumReasonLength {
return fmt . Errorf ( "reason should be at least %d chars but '%s' was %d" , minimumReasonLength , reason , len ( reason ) )
2021-04-01 20:46:45 +02:00
}
2021-05-08 14:25:55 +02:00
if len ( reason ) > maximumReasonLength {
return fmt . Errorf ( "reason should be no more than %d chars but given reason was %d" , maximumReasonLength , len ( reason ) )
2021-04-01 20:46:45 +02:00
}
return nil
}
2021-09-01 18:29:25 +02:00
// DisplayName checks that a requested display name is valid
func DisplayName ( displayName string ) error {
2021-04-01 20:46:45 +02:00
// TODO: add some validation logic here -- length, characters, etc
return nil
}
2021-09-01 18:29:25 +02:00
// Note checks that a given profile/account note/bio is valid
func Note ( note string ) error {
2021-04-01 20:46:45 +02:00
// TODO: add some validation logic here -- length, characters, etc
return nil
}
2021-09-01 18:29:25 +02:00
// Privacy checks that the desired privacy setting is valid
func Privacy ( privacy string ) error {
2021-09-11 13:19:06 +02:00
if privacy == "" {
return fmt . Errorf ( "empty string for privacy not allowed" )
}
switch apimodel . Visibility ( privacy ) {
case apimodel . VisibilityDirect , apimodel . VisibilityMutualsOnly , apimodel . VisibilityPrivate , apimodel . VisibilityPublic , apimodel . VisibilityUnlisted :
return nil
}
2022-08-06 12:09:21 +02:00
return fmt . Errorf ( "privacy '%s' was not recognized, valid options are 'direct', 'mutuals_only', 'private', 'public', 'unlisted'" , privacy )
}
// StatusFormat checks that the desired status format setting is valid.
func StatusFormat ( statusFormat string ) error {
if statusFormat == "" {
return fmt . Errorf ( "empty string for status format not allowed" )
}
switch apimodel . StatusFormat ( statusFormat ) {
case apimodel . StatusFormatPlain , apimodel . StatusFormatMarkdown :
return nil
}
return fmt . Errorf ( "status format '%s' was not recognized, valid options are 'plain', 'markdown'" , statusFormat )
2021-04-01 20:46:45 +02:00
}
2021-04-19 19:42:19 +02:00
2022-09-12 13:14:29 +02:00
func CustomCSS ( customCSS string ) error {
if ! config . GetAccountsAllowCustomCSS ( ) {
return errors . New ( "accounts-allow-custom-css is not enabled for this instance" )
}
if length := len ( customCSS ) ; length > maximumCustomCSSLength {
return fmt . Errorf ( "custom_css must be less than %d characters, but submitted custom_css was %d characters" , maximumCustomCSSLength , length )
}
return nil
}
2021-09-01 18:29:25 +02:00
// EmojiShortcode just runs the given shortcode through the regular expression
2021-04-19 19:42:19 +02:00
// for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters,
// lowercase a-z, numbers, and underscores.
2021-09-01 18:29:25 +02:00
func EmojiShortcode ( shortcode string ) error {
if ! regexes . EmojiShortcode . MatchString ( shortcode ) {
2021-04-19 19:42:19 +02:00
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
}
2021-06-23 16:35:57 +02:00
2021-09-01 18:29:25 +02:00
// SiteTitle ensures that the given site title is within spec.
func SiteTitle ( siteTitle string ) error {
2021-06-23 16:35:57 +02:00
if len ( siteTitle ) > maximumSiteTitleLength {
return fmt . Errorf ( "site title should be no more than %d chars but given title was %d" , maximumSiteTitleLength , len ( siteTitle ) )
}
return nil
}
2021-09-01 18:29:25 +02:00
// SiteShortDescription ensures that the given site short description is within spec.
func SiteShortDescription ( d string ) error {
2021-06-23 16:35:57 +02:00
if len ( d ) > maximumShortDescriptionLength {
return fmt . Errorf ( "short description should be no more than %d chars but given description was %d" , maximumShortDescriptionLength , len ( d ) )
}
return nil
}
2021-09-01 18:29:25 +02:00
// SiteDescription ensures that the given site description is within spec.
func SiteDescription ( d string ) error {
2021-06-23 16:35:57 +02:00
if len ( d ) > maximumDescriptionLength {
return fmt . Errorf ( "description should be no more than %d chars but given description was %d" , maximumDescriptionLength , len ( d ) )
}
return nil
}
2021-09-01 18:29:25 +02:00
// SiteTerms ensures that the given site terms string is within spec.
func SiteTerms ( t string ) error {
2021-06-23 16:35:57 +02:00
if len ( t ) > maximumSiteTermsLength {
return fmt . Errorf ( "terms should be no more than %d chars but given terms was %d" , maximumSiteTermsLength , len ( t ) )
}
return nil
}
2021-08-29 16:52:23 +02:00
2021-09-01 18:29:25 +02:00
// ULID returns true if the passed string is a valid ULID.
func ULID ( i string ) bool {
return regexes . ULID . MatchString ( i )
2021-08-29 16:52:23 +02:00
}