notify.moe/arn/UserAPI.go

176 lines
4.0 KiB
Go

package arn
import (
"errors"
"fmt"
"reflect"
"strings"
"github.com/animenotifier/notify.moe/arn/validate"
"github.com/aerogo/aero"
"github.com/aerogo/api"
"github.com/aerogo/http/client"
"github.com/animenotifier/notify.moe/arn/autocorrect"
)
// Force interface implementations
var (
_ PostParent = (*User)(nil)
_ api.Editable = (*User)(nil)
)
// Authorize returns an error if the given API POST request is not authorized.
func (user *User) Authorize(ctx aero.Context, action string) error {
editor := GetUserFromContext(ctx)
if editor == nil {
return errors.New("Not authorized")
}
if editor.ID != ctx.Get("id") && editor.Role != "admin" {
return errors.New("Can not modify data from other users")
}
return nil
}
// Edit updates the user object.
func (user *User) Edit(ctx aero.Context, key string, value reflect.Value, newValue reflect.Value) (bool, error) {
switch key {
case "Nick":
newNick := newValue.String()
err := user.SetNick(newNick)
return true, err
case "Email":
newEmail := newValue.String()
err := user.SetEmail(newEmail)
return true, err
case "Gender":
newGender := newValue.String()
if newGender != "male" && newGender != "female" {
return true, errors.New("Invalid gender")
}
user.Gender = newGender
return true, nil
case "Website":
newSite := newValue.String()
if newSite == "" {
user.Website = newSite
return true, nil
}
if autocorrect.IsTrackerLink(newSite) {
return true, errors.New("Not an actual personal website or homepage")
}
newSite = autocorrect.Website(newSite)
if !validate.URI("https://" + newSite) {
return true, errors.New("Not a valid website link")
}
response, err := client.Get("https://" + newSite).End()
if err != nil || response.StatusCode() >= 400 {
return true, fmt.Errorf("https://%s seems to be inaccessible", newSite)
}
user.Website = newSite
return true, nil
case "BirthDay":
newBirthDay := newValue.String()
if AgeInYears(newBirthDay) <= 0 {
return true, errors.New("Invalid birthday (make sure to use YYYY-MM-DD format, e.g. 2000-01-17)")
}
user.BirthDay = newBirthDay
return true, nil
case "ProExpires", "Avatar.Extension", "Avatar.LastModified":
editor := GetUserFromContext(ctx)
if editor == nil || editor.Role != "admin" {
return true, errors.New("Not authorized to edit")
}
case "Accounts.Discord.Nick":
newNick := newValue.String()
if newNick == "" {
value.SetString(newNick)
user.Accounts.Discord.Verified = false
return true, nil
}
if !validate.DiscordNick(newNick) {
return true, errors.New("Discord username must include your name and the 4-digit Discord tag (e.g. Yandere#1234)")
}
// Trim spaces
parts := strings.Split(newNick, "#")
parts[0] = strings.TrimSpace(parts[0])
parts[1] = strings.TrimSpace(parts[1])
newNick = strings.Join(parts, "#")
if value.String() != newNick {
value.SetString(newNick)
user.Accounts.Discord.Verified = false
}
return true, nil
}
// Automatically correct account nicks
if strings.HasPrefix(key, "Accounts.") && strings.HasSuffix(key, ".Nick") {
newNick := newValue.String()
newNick = autocorrect.AccountNick(newNick)
value.SetString(newNick)
return true, nil
}
return false, nil
}
// Save saves the user object in the database.
func (user *User) Save() {
DB.Set("User", user.ID, user)
}
// Filter removes privacy critical fields from the user object.
func (user *User) Filter() {
user.Email = ""
user.Gender = ""
user.FirstName = ""
user.LastName = ""
user.IP = ""
user.UserAgent = ""
user.LastLogin = ""
user.Accounts.Facebook.ID = ""
user.Accounts.Google.ID = ""
user.Accounts.Twitter.ID = ""
user.BirthDay = ""
user.Location = &Location{}
user.Browser = UserBrowser{}
user.OS = UserOS{}
}
// ShouldFilter tells whether data needs to be filtered in the given context.
func (user *User) ShouldFilter(ctx aero.Context) bool {
ctxUser := GetUserFromContext(ctx)
if ctxUser != nil && ctxUser.Role == "admin" {
return false
}
return true
}