mirror of
https://github.com/unmojang/drasl.git
synced 2025-08-03 10:56:06 -04:00
1586 lines
59 KiB
Go
1586 lines
59 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"errors"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"gorm.io/gorm"
|
|
"html"
|
|
"lukechampine.com/blake3"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"regexp"
|
|
"testing"
|
|
)
|
|
|
|
var FAKE_BROWSER_TOKEN = "deadbeef"
|
|
|
|
var EXISTING_PLAYER_NAME = "Existing"
|
|
var EXISTING_OTHER_PLAYER_NAME = "ExistingOther"
|
|
|
|
func setupRegistrationExistingPlayerTS(t *testing.T, requireSkinVerification bool, requireInvite bool) *TestSuite {
|
|
ts := &TestSuite{}
|
|
|
|
auxConfig := testConfig()
|
|
ts.SetupAux(auxConfig)
|
|
|
|
config := testConfig()
|
|
config.RegistrationNewPlayer.Allow = false
|
|
config.RegistrationExistingPlayer = registrationExistingPlayerConfig{
|
|
Allow: true,
|
|
RequireInvite: requireInvite,
|
|
}
|
|
config.ImportExistingPlayer = importExistingPlayerConfig{
|
|
Allow: true,
|
|
Nickname: "Aux",
|
|
SessionURL: ts.AuxApp.SessionURL,
|
|
AccountURL: ts.AuxApp.AccountURL,
|
|
RequireSkinVerification: requireSkinVerification,
|
|
}
|
|
config.FallbackAPIServers = []FallbackAPIServer{
|
|
{
|
|
Nickname: "Aux",
|
|
SessionURL: ts.AuxApp.SessionURL,
|
|
AccountURL: ts.AuxApp.AccountURL,
|
|
ServicesURL: ts.AuxApp.ServicesURL,
|
|
},
|
|
}
|
|
ts.Setup(config)
|
|
|
|
ts.CreateTestUser(t, ts.AuxApp, ts.AuxServer, EXISTING_PLAYER_NAME)
|
|
ts.CreateTestUser(t, ts.AuxApp, ts.AuxServer, EXISTING_OTHER_PLAYER_NAME)
|
|
|
|
return ts
|
|
}
|
|
|
|
func (ts *TestSuite) testStatusOK(t *testing.T, path string) {
|
|
req := httptest.NewRequest(http.MethodGet, path, nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
ts.Server.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
}
|
|
|
|
func (ts *TestSuite) testWebManifest(t *testing.T) {
|
|
ts.testStatusOK(t, "/web/manifest.webmanifest")
|
|
}
|
|
|
|
func (ts *TestSuite) testPublic(t *testing.T) {
|
|
ts.testStatusOK(t, "/")
|
|
ts.testStatusOK(t, "/web/registration")
|
|
ts.testStatusOK(t, "/web/manifest.webmanifest")
|
|
ts.testStatusOK(t, ts.App.PublicURL+"/bundle.js")
|
|
ts.testStatusOK(t, ts.App.PublicURL+"/style.css")
|
|
ts.testStatusOK(t, ts.App.PublicURL+"/logo.svg")
|
|
ts.testStatusOK(t, ts.App.PublicURL+"/icon.png")
|
|
{
|
|
rec := ts.Get(t, ts.Server, "/web/thisdoesnotexist", nil, nil)
|
|
assert.Equal(t, http.StatusNotFound, rec.Code)
|
|
}
|
|
}
|
|
|
|
func getErrorMessage(rec *httptest.ResponseRecorder) string {
|
|
return Unwrap(url.QueryUnescape(getCookie(rec, ERROR_MESSAGE_COOKIE_NAME).Value))
|
|
}
|
|
|
|
func (ts *TestSuite) registrationShouldFail(t *testing.T, rec *httptest.ResponseRecorder, errorMessage string, returnURL string) {
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, errorMessage, getErrorMessage(rec))
|
|
assert.Equal(t, "", getCookie(rec, BROWSER_TOKEN_COOKIE_NAME).Value)
|
|
assert.Equal(t, returnURL, rec.Header().Get("Location"))
|
|
}
|
|
|
|
func (ts *TestSuite) registrationShouldSucceed(t *testing.T, rec *httptest.ResponseRecorder) {
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, "", getErrorMessage(rec))
|
|
assert.NotEqual(t, "", getCookie(rec, BROWSER_TOKEN_COOKIE_NAME).Value)
|
|
assert.Equal(t, ts.App.FrontEndURL+"/web/user", rec.Header().Get("Location"))
|
|
}
|
|
|
|
func (ts *TestSuite) createPlayerShouldFail(t *testing.T, rec *httptest.ResponseRecorder, errorMessage string, returnURL string) {
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, errorMessage, getErrorMessage(rec))
|
|
assert.Equal(t, returnURL, rec.Header().Get("Location"))
|
|
}
|
|
|
|
func (ts *TestSuite) createPlayerShouldSucceed(t *testing.T, rec *httptest.ResponseRecorder) string {
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, "", getErrorMessage(rec))
|
|
|
|
returnURLExp := regexp.MustCompile("^" + regexp.QuoteMeta(ts.App.FrontEndURL+"/web/player/") + "(.+)$")
|
|
uuidMatch := returnURLExp.FindStringSubmatch(rec.Header().Get("Location"))
|
|
assert.True(t, uuidMatch != nil && len(uuidMatch) == 2)
|
|
uuid_ := uuidMatch[1]
|
|
_, err := uuid.Parse(uuid_)
|
|
assert.Nil(t, err)
|
|
return uuid_
|
|
}
|
|
|
|
func (ts *TestSuite) updateUserShouldFail(t *testing.T, rec *httptest.ResponseRecorder, errorMessage string, returnURL string) {
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, errorMessage, getErrorMessage(rec))
|
|
assert.Equal(t, returnURL, rec.Header().Get("Location"))
|
|
}
|
|
|
|
func (ts *TestSuite) updateUserShouldSucceed(t *testing.T, rec *httptest.ResponseRecorder) {
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, "", getErrorMessage(rec))
|
|
assert.Equal(t, ts.App.FrontEndURL+"/web/user", rec.Header().Get("Location"))
|
|
}
|
|
|
|
func (ts *TestSuite) updatePlayerShouldFail(t *testing.T, rec *httptest.ResponseRecorder, errorMessage string, returnURL string) {
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, errorMessage, getErrorMessage(rec))
|
|
assert.Equal(t, returnURL, rec.Header().Get("Location"))
|
|
}
|
|
|
|
func (ts *TestSuite) updatePlayerShouldSucceed(t *testing.T, rec *httptest.ResponseRecorder, playerUUID string) {
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, "", getErrorMessage(rec))
|
|
assert.Equal(t, ts.App.FrontEndURL+"/web/player/"+playerUUID, rec.Header().Get("Location"))
|
|
}
|
|
|
|
func (ts *TestSuite) loginShouldSucceed(t *testing.T, rec *httptest.ResponseRecorder) {
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, "", getErrorMessage(rec))
|
|
assert.NotEqual(t, "", getCookie(rec, BROWSER_TOKEN_COOKIE_NAME).Value)
|
|
assert.Equal(t, ts.App.FrontEndURL+"/web/user", rec.Header().Get("Location"))
|
|
}
|
|
|
|
func (ts *TestSuite) loginShouldFail(t *testing.T, rec *httptest.ResponseRecorder, errorMessage string) {
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, errorMessage, getErrorMessage(rec))
|
|
assert.Equal(t, "", getCookie(rec, BROWSER_TOKEN_COOKIE_NAME).Value)
|
|
assert.Equal(t, ts.App.FrontEndURL, rec.Header().Get("Location"))
|
|
}
|
|
|
|
func TestFront(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
{
|
|
// Registration as existing player not allowed
|
|
ts := &TestSuite{}
|
|
|
|
config := testConfig()
|
|
config.DefaultAdmins = []string{"registrationNewA"}
|
|
ts.Setup(config)
|
|
defer ts.Teardown()
|
|
|
|
t.Run("Test public pages and assets", ts.testPublic)
|
|
t.Run("Test web app manifest", ts.testWebManifest)
|
|
t.Run("Test registration as new player", ts.testRegistrationNewPlayer)
|
|
t.Run("Test registration as new player, chosen UUID, chosen UUID not allowed", ts.testRegistrationNewPlayerChosenUUIDNotAllowed)
|
|
t.Run("Test user update", ts.testUserUpdate)
|
|
t.Run("Test player update", ts.testPlayerUpdate)
|
|
t.Run("Test creating/deleting invites", ts.testNewInviteDeleteInvite)
|
|
t.Run("Test login, logout", ts.testLoginLogout)
|
|
t.Run("Test delete account", ts.testDeleteAccount)
|
|
}
|
|
{
|
|
ts := &TestSuite{}
|
|
|
|
config := testConfig()
|
|
config.AllowSkins = false
|
|
config.AllowCapes = false
|
|
ts.Setup(config)
|
|
defer ts.Teardown()
|
|
|
|
t.Run("Test profile update, skins and capes not allowed", ts.testUpdateSkinsCapesNotAllowed)
|
|
}
|
|
{
|
|
ts := &TestSuite{}
|
|
config := testConfig()
|
|
ts.Setup(config)
|
|
defer ts.Teardown()
|
|
t.Run("Test admin", ts.testAdmin)
|
|
}
|
|
{
|
|
// Choosing UUID allowed
|
|
ts := &TestSuite{}
|
|
|
|
config := testConfig()
|
|
config.RegistrationNewPlayer.AllowChoosingUUID = true
|
|
ts.Setup(config)
|
|
defer ts.Teardown()
|
|
|
|
t.Run("Test registration as new player, chosen UUID, chosen UUID allowed", ts.testRegistrationNewPlayerChosenUUID)
|
|
t.Run("Test create new player, chosen UUID, chosen UUID allowed", ts.testCreateNewPlayer)
|
|
}
|
|
{
|
|
// Low rate limit
|
|
ts := &TestSuite{}
|
|
|
|
config := testConfig()
|
|
config.RateLimit = rateLimitConfig{
|
|
Enable: true,
|
|
RequestsPerSecond: 2,
|
|
}
|
|
ts.Setup(config)
|
|
defer ts.Teardown()
|
|
|
|
t.Run("Test rate limiting", ts.testRateLimit)
|
|
}
|
|
{
|
|
// Low body limit
|
|
ts := &TestSuite{}
|
|
|
|
config := testConfig()
|
|
config.BodyLimit = bodyLimitConfig{
|
|
Enable: true,
|
|
SizeLimitKiB: 1,
|
|
}
|
|
ts.Setup(config)
|
|
defer ts.Teardown()
|
|
|
|
t.Run("Test body size limiting", ts.testBodyLimit)
|
|
}
|
|
{
|
|
// Set skin texture from URL
|
|
ts := &TestSuite{}
|
|
|
|
auxConfig := testConfig()
|
|
ts.SetupAux(auxConfig)
|
|
ts.CreateTestUser(t, ts.AuxApp, ts.AuxServer, EXISTING_PLAYER_NAME)
|
|
|
|
config := testConfig()
|
|
config.AllowTextureFromURL = true
|
|
ts.Setup(config)
|
|
defer ts.Teardown()
|
|
|
|
t.Run("Test setting texture from URL", ts.testTextureFromURL)
|
|
}
|
|
{
|
|
// Registration as existing player allowed, skin verification not required
|
|
ts := setupRegistrationExistingPlayerTS(
|
|
t,
|
|
false, // requireSkinVerification
|
|
false, // requireInvite
|
|
)
|
|
defer ts.Teardown()
|
|
|
|
t.Run("Test registration as existing player, no skin verification", ts.testRegistrationExistingPlayerNoVerification)
|
|
t.Run("Test import player, no skin verification", ts.testImportPlayerNoVerification)
|
|
}
|
|
{
|
|
// Registration as existing player allowed, skin verification required
|
|
ts := setupRegistrationExistingPlayerTS(
|
|
t,
|
|
true, // requireSkinVerification
|
|
false, // requireInvite
|
|
)
|
|
defer ts.Teardown()
|
|
|
|
t.Run("Test registration as existing player, with skin verification", ts.testRegistrationExistingPlayerVerification)
|
|
t.Run("Test import player, with skin verification", ts.testImportPlayerVerification)
|
|
}
|
|
{
|
|
// Invite required, new player
|
|
ts := &TestSuite{}
|
|
|
|
config := testConfig()
|
|
config.RegistrationNewPlayer.RequireInvite = true
|
|
ts.Setup(config)
|
|
defer ts.Teardown()
|
|
|
|
t.Run("Test registration as new player, invite only", ts.testRegistrationNewPlayerInvite)
|
|
}
|
|
{
|
|
// Invite required, existing player, skin verification
|
|
ts := setupRegistrationExistingPlayerTS(t, true, true)
|
|
defer ts.Teardown()
|
|
|
|
t.Run("Test registration as existing player, with skin verification, invite only", ts.testRegistrationExistingPlayerInvite)
|
|
}
|
|
}
|
|
|
|
func (ts *TestSuite) testRateLimit(t *testing.T) {
|
|
form := url.Values{}
|
|
form.Set("username", "")
|
|
form.Set("password", "")
|
|
|
|
// Login should fail the first time due to missing account, then
|
|
// soon get rate-limited
|
|
rec := ts.PostForm(t, ts.Server, "/web/login", form, nil, nil)
|
|
ts.loginShouldFail(t, rec, "User not found.")
|
|
rec = ts.PostForm(t, ts.Server, "/web/login", form, nil, nil)
|
|
ts.loginShouldFail(t, rec, "User not found.")
|
|
rec = ts.PostForm(t, ts.Server, "/web/login", form, nil, nil)
|
|
ts.loginShouldFail(t, rec, "Too many requests. Try again later.")
|
|
|
|
// Static paths should not be rate-limited
|
|
rec = ts.Get(t, ts.Server, "/web/registration", nil, nil)
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
rec = ts.Get(t, ts.Server, "/web/registration", nil, nil)
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
rec = ts.Get(t, ts.Server, "/web/registration", nil, nil)
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
}
|
|
|
|
func (ts *TestSuite) testBodyLimit(t *testing.T) {
|
|
form := url.Values{}
|
|
form.Set("bogus", Unwrap(RandomHex(2048)))
|
|
rec := ts.PostForm(t, ts.Server, "/web/login", form, nil, nil)
|
|
assert.Equal(t, "Request Entity Too Large", getErrorMessage(rec))
|
|
}
|
|
|
|
func (ts *TestSuite) testRegistrationNewPlayer(t *testing.T) {
|
|
usernameA := "registrationNewA"
|
|
usernameAUppercase := "REGISTRATIONNEWA"
|
|
usernameB := "registrationNewB"
|
|
usernameC := "registrationNewC"
|
|
returnURL := ts.App.FrontEndURL + "/web/registration"
|
|
{
|
|
// Tripping the honeypot should fail
|
|
form := url.Values{}
|
|
form.Set("playerName", usernameA)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("email", "mail@example.com")
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/registration")
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldFail(t, rec, "You are now covered in bee stings.", returnURL)
|
|
}
|
|
{
|
|
// Register
|
|
form := url.Values{}
|
|
form.Set("playerName", usernameA)
|
|
form.Set("password", TEST_PASSWORD)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldSucceed(t, rec)
|
|
browserTokenCookie := getCookie(rec, BROWSER_TOKEN_COOKIE_NAME)
|
|
|
|
// Check that the user has been created with a correct password hash/salt
|
|
var user User
|
|
result := ts.App.DB.First(&user, "username = ?", usernameA)
|
|
assert.Nil(t, result.Error)
|
|
passwordHash, err := HashPassword(TEST_PASSWORD, user.PasswordSalt)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, passwordHash, user.PasswordHash)
|
|
|
|
// Users in the DefaultAdmins list should be admins
|
|
assert.True(t, ts.App.IsDefaultAdmin(&user))
|
|
assert.True(t, user.IsAdmin)
|
|
|
|
// Get the profile
|
|
{
|
|
rec := ts.Get(t, ts.Server, "/web/user", []http.Cookie{*browserTokenCookie}, nil)
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
assert.Equal(t, "", getErrorMessage(rec))
|
|
}
|
|
|
|
// Get admin page
|
|
{
|
|
rec := ts.Get(t, ts.Server, "/web/admin", []http.Cookie{*browserTokenCookie}, nil)
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
assert.Equal(t, "", getErrorMessage(rec))
|
|
}
|
|
}
|
|
{
|
|
// Register
|
|
form := url.Values{}
|
|
form.Set("playerName", usernameB)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/registration")
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldSucceed(t, rec)
|
|
browserTokenCookie := getCookie(rec, BROWSER_TOKEN_COOKIE_NAME)
|
|
|
|
// Users not in the DefaultAdmins list should not be admins
|
|
var user User
|
|
result := ts.App.DB.First(&user, "username = ?", usernameB)
|
|
assert.Nil(t, result.Error)
|
|
assert.False(t, ts.App.IsDefaultAdmin(&user))
|
|
assert.False(t, user.IsAdmin)
|
|
|
|
// Getting admin page should fail and redirect back to /
|
|
rec = ts.Get(t, ts.Server, "/web/admin", []http.Cookie{*browserTokenCookie}, nil)
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, "You are not an admin.", getErrorMessage(rec))
|
|
assert.Equal(t, ts.App.FrontEndURL, rec.Header().Get("Location"))
|
|
}
|
|
{
|
|
// Try registering again with the same username
|
|
form := url.Values{}
|
|
form.Set("playerName", usernameA)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/registration")
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
|
|
ts.registrationShouldFail(t, rec, "That username is taken.", returnURL)
|
|
}
|
|
{
|
|
// Test case insensitivity: try registering again with the "same"
|
|
// username, but uppercase. Usernames are case-sensitive, but player
|
|
// names are.
|
|
form := url.Values{}
|
|
form.Set("playerName", usernameAUppercase)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/registration")
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
|
|
ts.registrationShouldFail(t, rec, "That username is in use as the name of another user's player.", returnURL)
|
|
}
|
|
{
|
|
// Registration with a too-long username should fail
|
|
form := url.Values{}
|
|
form.Set("playerName", "AReallyReallyReallyLongUsername")
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
|
|
ts.registrationShouldFail(t, rec, "Invalid username: neither a valid player name (can't be longer than 16 characters) nor an email address", returnURL)
|
|
}
|
|
{
|
|
// Registration with a too-short password should fail
|
|
form := url.Values{}
|
|
form.Set("playerName", usernameC)
|
|
form.Set("password", "")
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
|
|
ts.registrationShouldFail(t, rec, "Invalid password: can't be blank", returnURL)
|
|
}
|
|
{
|
|
// Registration from an existing player should fail
|
|
form := url.Values{}
|
|
form.Set("playerName", usernameC)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("challengeToken", "This is not a valid challenge token.")
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
|
|
ts.registrationShouldFail(t, rec, "Registration from an existing player is not allowed.", returnURL)
|
|
}
|
|
}
|
|
|
|
func (ts *TestSuite) testRegistrationNewPlayerChosenUUIDNotAllowed(t *testing.T) {
|
|
username := "noChosenUUID"
|
|
ts.CreateTestUser(t, ts.App, ts.Server, username)
|
|
|
|
uuid := "11111111-2222-3333-4444-555555555555"
|
|
|
|
ts.App.Config.RegistrationNewPlayer.AllowChoosingUUID = false
|
|
|
|
returnURL := ts.App.FrontEndURL + "/web/registration"
|
|
form := url.Values{}
|
|
form.Set("playerName", username)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("uuid", uuid)
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
|
|
ts.registrationShouldFail(t, rec, "Choosing a UUID is not allowed.", returnURL)
|
|
}
|
|
|
|
func (ts *TestSuite) testRegistrationNewPlayerChosenUUID(t *testing.T) {
|
|
usernameA := "chosenUUIDA"
|
|
usernameB := "chosenUUIDB"
|
|
uuid := "11111111-2222-3333-4444-555555555555"
|
|
returnURL := ts.App.FrontEndURL + "/web/registration"
|
|
{
|
|
// Register
|
|
form := url.Values{}
|
|
form.Set("playerName", usernameA)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("uuid", uuid)
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/registration")
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
|
|
// Registration should succeed, grant a browserToken, and redirect to user page
|
|
assert.NotEqual(t, "", getCookie(rec, BROWSER_TOKEN_COOKIE_NAME))
|
|
ts.registrationShouldSucceed(t, rec)
|
|
|
|
// Check that the user has been created and has a player with the chosen UUID
|
|
var user User
|
|
result := ts.App.DB.First(&user, "username = ?", usernameA)
|
|
assert.Nil(t, result.Error)
|
|
assert.Equal(t, 1, len(user.Players))
|
|
assert.Equal(t, uuid, user.Players[0].UUID)
|
|
}
|
|
{
|
|
// Try registering again with the same UUID
|
|
form := url.Values{}
|
|
form.Set("playerName", usernameB)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("uuid", uuid)
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/registration")
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
|
|
ts.registrationShouldFail(t, rec, "That UUID is taken.", returnURL)
|
|
}
|
|
{
|
|
// Try registering with a garbage UUID
|
|
form := url.Values{}
|
|
form.Set("playerName", usernameB)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("uuid", "This is not a UUID.")
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/registration")
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
|
|
ts.registrationShouldFail(t, rec, "Invalid UUID: invalid UUID length: 19", returnURL)
|
|
}
|
|
}
|
|
|
|
func (ts *TestSuite) testRegistrationNewPlayerInvite(t *testing.T) {
|
|
usernameA := "inviteA"
|
|
{
|
|
// Registration without an invite should fail
|
|
returnURL := ts.App.FrontEndURL + "/web/registration"
|
|
form := url.Values{}
|
|
form.Set("playerName", usernameA)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/registration")
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldFail(t, rec, "Registration requires an invite.", returnURL)
|
|
}
|
|
{
|
|
// Registration with an invalid invite should fail, and redirect to
|
|
// registration page without ?invite
|
|
returnURL := ts.App.FrontEndURL + "/web/registration"
|
|
form := url.Values{}
|
|
form.Set("playerName", usernameA)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("inviteCode", "invalid")
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/registration?invite=invalid")
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldFail(t, rec, InviteNotFoundError.Error(), returnURL)
|
|
}
|
|
{
|
|
// Registration with an invite
|
|
|
|
// Create an invite
|
|
invite, err := ts.App.CreateInvite()
|
|
assert.Nil(t, err)
|
|
|
|
var invites []Invite
|
|
result := ts.App.DB.Find(&invites)
|
|
assert.Nil(t, result.Error)
|
|
inviteCount := len(invites)
|
|
|
|
// Registration with an invalid username should redirect to the
|
|
// registration page with the same unused invite code
|
|
returnURL := ts.App.FrontEndURL + "/web/registration?invite=" + invite.Code
|
|
form := url.Values{}
|
|
form.Set("playerName", "")
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("inviteCode", invite.Code)
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldFail(t, rec, "Invalid username: neither a valid player name (can't be blank) nor an email address", returnURL)
|
|
|
|
// Then, set a valid username and continnue
|
|
form.Set("playerName", usernameA)
|
|
rec = ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldSucceed(t, rec)
|
|
|
|
// Invite should be deleted
|
|
result = ts.App.DB.Find(&invites)
|
|
assert.Nil(t, result.Error)
|
|
assert.Equal(t, inviteCount-1, len(invites))
|
|
}
|
|
}
|
|
|
|
func (ts *TestSuite) solveRegisterChallenge(t *testing.T, username string) *http.Cookie {
|
|
// Get challenge skin
|
|
req := httptest.NewRequest(http.MethodGet, "/web/register-challenge?playerName="+username, nil)
|
|
rec := httptest.NewRecorder()
|
|
ts.Server.ServeHTTP(rec, req)
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
challengeToken := getCookie(rec, CHALLENGE_TOKEN_COOKIE_NAME)
|
|
assert.NotEqual(t, "", challengeToken.Value)
|
|
|
|
base64Exp, err := regexp.Compile("src=\"data:image\\/png;base64,([A-Za-z0-9+/&#;]*={0,2})\"")
|
|
assert.Nil(t, err)
|
|
match := base64Exp.FindStringSubmatch(rec.Body.String())
|
|
assert.Equal(t, 2, len(match))
|
|
// The base64 will come back HTML-escaped...
|
|
base64String := html.UnescapeString(match[1])
|
|
|
|
challengeSkin, err := base64.StdEncoding.DecodeString(base64String)
|
|
assert.Nil(t, err)
|
|
|
|
var auxPlayer Player
|
|
result := ts.AuxApp.DB.First(&auxPlayer, "name = ?", username)
|
|
assert.Nil(t, result.Error)
|
|
|
|
// Bypass the controller for setting the skin here, we can test that with the rest of /update
|
|
err = ts.AuxApp.SetSkinAndSave(&auxPlayer, bytes.NewReader(challengeSkin))
|
|
assert.Nil(t, err)
|
|
|
|
return challengeToken
|
|
}
|
|
|
|
func (ts *TestSuite) solveCreatePlayerChallenge(t *testing.T, playerName string) *http.Cookie {
|
|
// Get challenge skin
|
|
req := httptest.NewRequest(http.MethodGet, "/web/create-player-challenge?playerName="+playerName, nil)
|
|
rec := httptest.NewRecorder()
|
|
ts.Server.ServeHTTP(rec, req)
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
challengeToken := getCookie(rec, CHALLENGE_TOKEN_COOKIE_NAME)
|
|
assert.NotEqual(t, "", challengeToken.Value)
|
|
|
|
base64Exp, err := regexp.Compile("src=\"data:image\\/png;base64,([A-Za-z0-9+/&#;]*={0,2})\"")
|
|
assert.Nil(t, err)
|
|
match := base64Exp.FindStringSubmatch(rec.Body.String())
|
|
assert.Equal(t, 2, len(match))
|
|
// The base64 will come back HTML-escaped...
|
|
base64String := html.UnescapeString(match[1])
|
|
|
|
challengeSkin, err := base64.StdEncoding.DecodeString(base64String)
|
|
assert.Nil(t, err)
|
|
|
|
var auxPlayer Player
|
|
result := ts.AuxApp.DB.First(&auxPlayer, "name = ?", playerName)
|
|
assert.Nil(t, result.Error)
|
|
|
|
// Bypass the controller for setting the skin here, we can test that with the rest of /update
|
|
err = ts.AuxApp.SetSkinAndSave(&auxPlayer, bytes.NewReader(challengeSkin))
|
|
assert.Nil(t, err)
|
|
|
|
return challengeToken
|
|
}
|
|
|
|
func (ts *TestSuite) testRegistrationExistingPlayerInvite(t *testing.T) {
|
|
username := EXISTING_PLAYER_NAME
|
|
{
|
|
// Registration without an invite should fail
|
|
returnURL := ts.App.FrontEndURL + "/web/registration"
|
|
form := url.Values{}
|
|
form.Set("playerName", username)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/registration")
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldFail(t, rec, InviteMissingError.Error(), returnURL)
|
|
}
|
|
{
|
|
// Registration with an invalid invite should fail, and redirect to
|
|
// registration page without ?invite
|
|
returnURL := ts.App.FrontEndURL + "/web/registration"
|
|
form := url.Values{}
|
|
form.Set("playerName", username)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("inviteCode", "invalid")
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/registration?invite=invalid")
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldFail(t, rec, InviteNotFoundError.Error(), returnURL)
|
|
}
|
|
{
|
|
// Registration with an invite
|
|
|
|
// Create an invite
|
|
invite, err := ts.App.CreateInvite()
|
|
assert.Nil(t, err)
|
|
|
|
var invites []Invite
|
|
result := ts.App.DB.Find(&invites)
|
|
assert.Nil(t, result.Error)
|
|
inviteCount := len(invites)
|
|
|
|
challengeToken := ts.solveRegisterChallenge(t, username)
|
|
returnURL := ts.App.FrontEndURL + "/web/registration?invite=" + invite.Code
|
|
{
|
|
// Registration with an invalid username should redirect to the
|
|
// registration page with the same unused invite code
|
|
form := url.Values{}
|
|
form.Set("playerName", "")
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("inviteCode", invite.Code)
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldFail(t, rec, "Invalid username: neither a valid player name (can't be blank) nor an email address", returnURL)
|
|
}
|
|
{
|
|
// Registration should fail if we give the wrong challenge token, and the invite should not be used
|
|
form := url.Values{}
|
|
form.Set("playerName", username)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("inviteCode", invite.Code)
|
|
form.Set("challengeToken", "invalid-challenge-token")
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
|
|
ts.registrationShouldFail(t, rec, "Couldn't verify your skin, maybe try again: skin does not match", returnURL)
|
|
}
|
|
{
|
|
// Registration should succeed if everything is correct
|
|
form := url.Values{}
|
|
form.Set("playerName", username)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("inviteCode", invite.Code)
|
|
form.Set("challengeToken", challengeToken.Value)
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
|
|
ts.registrationShouldSucceed(t, rec)
|
|
|
|
// Check that the created user has a player with the same UUID
|
|
var user User
|
|
result = ts.App.DB.First(&user, "username = ?", username)
|
|
assert.Nil(t, result.Error)
|
|
player := user.Players[0]
|
|
|
|
var auxPlayer Player
|
|
result = ts.AuxApp.DB.First(&auxPlayer, "name = ?", username)
|
|
assert.Nil(t, result.Error)
|
|
assert.Equal(t, auxPlayer.UUID, player.UUID)
|
|
|
|
// Invite should be deleted
|
|
result = ts.App.DB.Find(&invites)
|
|
assert.Nil(t, result.Error)
|
|
assert.Equal(t, inviteCount-1, len(invites))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ts *TestSuite) testLoginLogout(t *testing.T) {
|
|
username := "loginLogout"
|
|
user, _ := ts.CreateTestUser(t, ts.App, ts.Server, username)
|
|
|
|
{
|
|
// Login
|
|
form := url.Values{}
|
|
form.Set("username", username)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/registration")
|
|
rec := ts.PostForm(t, ts.Server, "/web/login", form, nil, nil)
|
|
ts.loginShouldSucceed(t, rec)
|
|
browserTokenCookie := getCookie(rec, BROWSER_TOKEN_COOKIE_NAME)
|
|
|
|
// The BrowserToken we get should match the one in the database
|
|
var user User
|
|
result := ts.App.DB.First(&user, "username = ?", username)
|
|
assert.Nil(t, result.Error)
|
|
assert.Equal(t, *UnmakeNullString(&user.BrowserToken), browserTokenCookie.Value)
|
|
|
|
// Get user page
|
|
req := httptest.NewRequest(http.MethodGet, "/web/user", nil)
|
|
req.AddCookie(browserTokenCookie)
|
|
rec = httptest.NewRecorder()
|
|
ts.Server.ServeHTTP(rec, req)
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
assert.Equal(t, "", getErrorMessage(rec))
|
|
|
|
// Logout should redirect to / and clear the browserToken
|
|
rec = ts.PostForm(t, ts.Server, "/web/logout", url.Values{}, []http.Cookie{*browserTokenCookie}, nil)
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, ts.App.FrontEndURL, rec.Header().Get("Location"))
|
|
result = ts.App.DB.First(&user, "username = ?", username)
|
|
assert.Nil(t, result.Error)
|
|
assert.Nil(t, UnmakeNullString(&user.BrowserToken))
|
|
}
|
|
{
|
|
// Login with incorrect password should fail
|
|
form := url.Values{}
|
|
form.Set("username", username)
|
|
form.Set("password", "wrong password")
|
|
rec := ts.PostForm(t, ts.Server, "/web/login", form, nil, nil)
|
|
ts.loginShouldFail(t, rec, "Incorrect password.")
|
|
}
|
|
{
|
|
// Web login with the user's Minecraft token should fail
|
|
form := url.Values{}
|
|
form.Set("username", username)
|
|
form.Set("password", user.MinecraftToken)
|
|
rec := ts.PostForm(t, ts.Server, "/web/login", form, nil, nil)
|
|
ts.loginShouldFail(t, rec, "Incorrect password.")
|
|
}
|
|
{
|
|
// GET /web/user without valid BrowserToken should fail
|
|
req := httptest.NewRequest(http.MethodGet, "/web/user", nil)
|
|
rec := httptest.NewRecorder()
|
|
ts.Server.ServeHTTP(rec, req)
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, ts.App.FrontEndURL+"?destination=%2Fweb%2Fuser", rec.Header().Get("Location"))
|
|
assert.Equal(t, "You are not logged in.", getErrorMessage(rec))
|
|
|
|
// Logout without valid BrowserToken should fail
|
|
rec = ts.PostForm(t, ts.Server, "/web/logout", url.Values{}, nil, nil)
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, ts.App.FrontEndURL, rec.Header().Get("Location"))
|
|
assert.Equal(t, "You are not logged in.", getErrorMessage(rec))
|
|
}
|
|
}
|
|
|
|
func (ts *TestSuite) testRegistrationExistingPlayerNoVerification(t *testing.T) {
|
|
username := EXISTING_PLAYER_NAME
|
|
returnURL := ts.App.FrontEndURL + "/web/registration"
|
|
|
|
// Register from the existing account
|
|
form := url.Values{}
|
|
form.Set("playerName", username)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldSucceed(t, rec)
|
|
|
|
// Check that the new user was created and has a player with the same UUID
|
|
// as the player on the auxiliary server
|
|
var auxPlayer Player
|
|
result := ts.AuxApp.DB.First(&auxPlayer, "name = ?", username)
|
|
assert.Nil(t, result.Error)
|
|
|
|
var user User
|
|
result = ts.App.DB.First(&user, "username = ?", username)
|
|
assert.Nil(t, result.Error)
|
|
assert.Equal(t, 1, len(user.Players))
|
|
player := user.Players[0]
|
|
assert.Equal(t, auxPlayer.UUID, player.UUID)
|
|
|
|
{
|
|
// Registration as a new user should fail
|
|
form := url.Values{}
|
|
form.Set("playerName", username)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldFail(t, rec, "Registration without some existing player is not allowed.", returnURL)
|
|
}
|
|
{
|
|
// Registration with a missing existing account should fail
|
|
returnURL := ts.App.FrontEndURL + "/web/registration"
|
|
form := url.Values{}
|
|
form.Set("playerName", "nonexistent")
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldFail(t, rec, "Couldn't find your account, maybe try again: registration server returned error", returnURL)
|
|
}
|
|
}
|
|
|
|
func (ts *TestSuite) testImportPlayerNoVerification(t *testing.T) {
|
|
user, browserTokenCookie := ts.CreateTestUser(t, ts.App, ts.Server, "ImportPlayer")
|
|
user.MaxPlayerCount = ts.App.Constants.MaxPlayerCountUnlimited
|
|
assert.Nil(t, ts.App.DB.Save(&user).Error)
|
|
|
|
returnURL := ts.App.FrontEndURL + "/web/user"
|
|
|
|
form := url.Values{}
|
|
form.Set("userUuid", user.UUID)
|
|
form.Set("playerName", EXISTING_OTHER_PLAYER_NAME)
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/create-player", form, []http.Cookie{*browserTokenCookie}, nil)
|
|
createdUUID := ts.createPlayerShouldSucceed(t, rec)
|
|
|
|
// Check that the new player was created with the same UUID as the player
|
|
// on the auxiliary server
|
|
var auxPlayer Player
|
|
result := ts.AuxApp.DB.First(&auxPlayer, "name = ?", EXISTING_OTHER_PLAYER_NAME)
|
|
assert.Nil(t, result.Error)
|
|
|
|
var player Player
|
|
assert.Nil(t, ts.App.DB.First(&player, "uuid = ?", auxPlayer.UUID).Error)
|
|
assert.Equal(t, user.UUID, player.UserUUID)
|
|
assert.Equal(t, createdUUID, player.UUID)
|
|
|
|
assert.Nil(t, ts.App.DB.First(&user, "uuid = ?", user.UUID).Error)
|
|
assert.Equal(t, 2, len(user.Players))
|
|
|
|
{
|
|
// Creating a new player should fail
|
|
form := url.Values{}
|
|
form.Set("userUuid", user.UUID)
|
|
form.Set("playerName", "SomeJunk")
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/create-player", form, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.createPlayerShouldFail(t, rec, "Creating a new player is not allowed.", returnURL)
|
|
}
|
|
{
|
|
// Creating a player with a missing existing player should fail
|
|
form := url.Values{}
|
|
form.Set("userUuid", user.UUID)
|
|
form.Set("playerName", "Nonexistent")
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/create-player", form, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.createPlayerShouldFail(t, rec, "Couldn't find your account, maybe try again: registration server returned error", returnURL)
|
|
}
|
|
}
|
|
|
|
func (ts *TestSuite) testImportPlayerVerification(t *testing.T) {
|
|
user, browserTokenCookie := ts.CreateTestUser(t, ts.App, ts.Server, "ImportPlayer")
|
|
user.MaxPlayerCount = ts.App.Constants.MaxPlayerCountUnlimited
|
|
assert.Nil(t, ts.App.DB.Save(&user).Error)
|
|
|
|
returnURL := ts.App.FrontEndURL + "/web/user"
|
|
|
|
challengeToken := ts.solveCreatePlayerChallenge(t, EXISTING_OTHER_PLAYER_NAME)
|
|
|
|
{
|
|
// Importing player should fail if we give the wrong challenge token
|
|
form := url.Values{}
|
|
form.Set("userUuid", user.UUID)
|
|
form.Set("playerName", EXISTING_OTHER_PLAYER_NAME)
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("challengeToken", "invalid-challenge-token")
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/create-player", form, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.createPlayerShouldFail(t, rec, "Couldn't verify your skin, maybe try again: skin does not match", returnURL)
|
|
}
|
|
{
|
|
// Import should succeed when we give the correct challenge token
|
|
form := url.Values{}
|
|
form.Set("userUuid", user.UUID)
|
|
form.Set("playerName", EXISTING_OTHER_PLAYER_NAME)
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("challengeToken", challengeToken.Value)
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/create-player", form, []http.Cookie{*browserTokenCookie}, nil)
|
|
createdUUID := ts.createPlayerShouldSucceed(t, rec)
|
|
|
|
// Check that the new player was created with the same UUID as the player
|
|
// on the auxiliary server
|
|
var auxPlayer Player
|
|
result := ts.AuxApp.DB.First(&auxPlayer, "name = ?", EXISTING_OTHER_PLAYER_NAME)
|
|
assert.Nil(t, result.Error)
|
|
|
|
var player Player
|
|
assert.Nil(t, ts.App.DB.First(&player, "uuid = ?", auxPlayer.UUID).Error)
|
|
assert.Equal(t, user.UUID, player.UserUUID)
|
|
assert.Equal(t, createdUUID, player.UUID)
|
|
|
|
assert.Nil(t, ts.App.DB.First(&user, "uuid = ?", user.UUID).Error)
|
|
assert.Equal(t, 2, len(user.Players))
|
|
}
|
|
}
|
|
|
|
func (ts *TestSuite) testRegistrationExistingPlayerVerification(t *testing.T) {
|
|
username := EXISTING_PLAYER_NAME
|
|
returnURL := ts.App.FrontEndURL + "/web/registration"
|
|
{
|
|
// Registration without setting a skin should fail
|
|
form := url.Values{}
|
|
form.Set("playerName", username)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/registration")
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldFail(t, rec, "Couldn't verify your skin, maybe try again: player does not have a skin", returnURL)
|
|
}
|
|
{
|
|
// Get challenge skin with invalid player name should fail
|
|
req := httptest.NewRequest(http.MethodGet, "/web/register-challenge?playerName=AReallyReallyReallyLongPlayerName&returnUrl="+ts.App.FrontEndURL+"/web/registration", nil)
|
|
rec := httptest.NewRecorder()
|
|
ts.Server.ServeHTTP(rec, req)
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, "Invalid player name: can't be longer than 16 characters", getErrorMessage(rec))
|
|
assert.Equal(t, returnURL, rec.Header().Get("Location"))
|
|
}
|
|
{
|
|
challengeToken := ts.solveRegisterChallenge(t, username)
|
|
{
|
|
// Registration should fail if we give the wrong challenge token
|
|
form := url.Values{}
|
|
form.Set("playerName", username)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("challengeToken", "invalid-challenge-token")
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
|
|
ts.registrationShouldFail(t, rec, "Couldn't verify your skin, maybe try again: skin does not match", returnURL)
|
|
}
|
|
{
|
|
// Registration should succeed if everything is correct
|
|
form := url.Values{}
|
|
form.Set("playerName", username)
|
|
form.Set("password", TEST_PASSWORD)
|
|
form.Set("existingPlayer", "on")
|
|
form.Set("challengeToken", challengeToken.Value)
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
|
|
ts.registrationShouldSucceed(t, rec)
|
|
|
|
// Check that the user has been created with the same UUID
|
|
var auxPlayer Player
|
|
result := ts.AuxApp.DB.First(&auxPlayer, "name = ?", username)
|
|
assert.Nil(t, result.Error)
|
|
|
|
var user User
|
|
result = ts.App.DB.First(&user, "username = ?", username)
|
|
assert.Nil(t, result.Error)
|
|
assert.Equal(t, 1, len(user.Players))
|
|
player := user.Players[0]
|
|
assert.Equal(t, auxPlayer.UUID, player.UUID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ts *TestSuite) testNewInviteDeleteInvite(t *testing.T) {
|
|
username := "inviteAdmin"
|
|
user, browserTokenCookie := ts.CreateTestUser(t, ts.App, ts.Server, username)
|
|
|
|
user.IsAdmin = true
|
|
result := ts.App.DB.Save(&user)
|
|
assert.Nil(t, result.Error)
|
|
|
|
// Create an invite
|
|
returnURL := ts.App.FrontEndURL + "/web/admin"
|
|
form := url.Values{}
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/admin/new-invite", form, []http.Cookie{*browserTokenCookie}, nil)
|
|
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, "", getErrorMessage(rec))
|
|
assert.Equal(t, returnURL, rec.Header().Get("Location"))
|
|
|
|
// Check that invite was created
|
|
var invites []Invite
|
|
result = ts.App.DB.Find(&invites)
|
|
assert.Nil(t, result.Error)
|
|
assert.Equal(t, 1, len(invites))
|
|
|
|
// Delete the invite
|
|
form = url.Values{}
|
|
form.Set("inviteCode", invites[0].Code)
|
|
form.Set("returnUrl", returnURL)
|
|
rec = ts.PostForm(t, ts.Server, "/web/admin/delete-invite", form, []http.Cookie{*browserTokenCookie}, nil)
|
|
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, "", getErrorMessage(rec))
|
|
assert.Equal(t, returnURL, rec.Header().Get("Location"))
|
|
|
|
// Check that invite was deleted
|
|
result = ts.App.DB.Find(&invites)
|
|
assert.Nil(t, result.Error)
|
|
assert.Equal(t, 0, len(invites))
|
|
}
|
|
|
|
func (ts *TestSuite) testUserUpdate(t *testing.T) {
|
|
username := "userUpdate"
|
|
takenUsername := "userUpdateTaken"
|
|
user, browserTokenCookie := ts.CreateTestUser(t, ts.App, ts.Server, username)
|
|
takenUser, takenBrowserTokenCookie := ts.CreateTestUser(t, ts.App, ts.Server, takenUsername)
|
|
|
|
assert.Equal(t, "en", user.PreferredLanguage)
|
|
user.IsAdmin = true
|
|
assert.Nil(t, ts.App.DB.Save(&user).Error)
|
|
|
|
{
|
|
// Successful update
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
|
|
assert.Nil(t, writer.WriteField("preferredLanguage", "es"))
|
|
assert.Nil(t, writer.WriteField("password", "newpassword"))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/user"))
|
|
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-user", body, writer, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.updateUserShouldSucceed(t, rec)
|
|
|
|
var updatedUser User
|
|
result := ts.App.DB.First(&updatedUser, "uuid = ?", user.UUID)
|
|
assert.Nil(t, result.Error)
|
|
assert.Equal(t, "es", updatedUser.PreferredLanguage)
|
|
|
|
// Make sure we can log in with the new password
|
|
form := url.Values{}
|
|
form.Set("username", username)
|
|
form.Set("password", "newpassword")
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/registration")
|
|
rec = ts.PostForm(t, ts.Server, "/web/login", form, nil, nil)
|
|
ts.loginShouldSucceed(t, rec)
|
|
browserTokenCookie = getCookie(rec, BROWSER_TOKEN_COOKIE_NAME)
|
|
}
|
|
{
|
|
// As an admin, test updating another user's account
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("uuid", takenUser.UUID))
|
|
assert.Nil(t, writer.WriteField("maxPlayerCount", "3"))
|
|
assert.Nil(t, writer.WriteField("preferredLanguage", "es"))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/user"))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-user", body, writer, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.updateUserShouldSucceed(t, rec)
|
|
}
|
|
{
|
|
// Non-admin should not be able to edit another user
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("uuid", user.UUID))
|
|
assert.Nil(t, writer.WriteField("preferredLanguage", "es"))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/user"))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-user", body, writer, []http.Cookie{*takenBrowserTokenCookie}, nil)
|
|
ts.updateUserShouldFail(t, rec, "You are not an admin.", ts.App.FrontEndURL)
|
|
}
|
|
{
|
|
// Non-admin should not be able to increase their max player count
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("uuid", takenUser.UUID))
|
|
assert.Nil(t, writer.WriteField("maxPlayerCount", "-1"))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/user"))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-user", body, writer, []http.Cookie{*takenBrowserTokenCookie}, nil)
|
|
ts.updateUserShouldFail(t, rec, "Cannot set a max player count without admin privileges.", ts.App.FrontEndURL+"/web/user")
|
|
}
|
|
{
|
|
// Non-admin should be able to change other settings
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("preferredLanguage", "ar"))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/user"))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-user", body, writer, []http.Cookie{*takenBrowserTokenCookie}, nil)
|
|
ts.updateUserShouldSucceed(t, rec)
|
|
}
|
|
{
|
|
// Invalid preferred language should fail
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("preferredLanguage", "xx"))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/user"))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-user", body, writer, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.updateUserShouldFail(t, rec, "Invalid preferred language.", ts.App.FrontEndURL+"/web/user")
|
|
}
|
|
{
|
|
// Setting an invalid password should fail
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("password", "short"))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/user"))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-user", body, writer, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.updateUserShouldFail(t, rec, "Invalid password: password must be longer than 8 characters", ts.App.FrontEndURL+"/web/user")
|
|
}
|
|
}
|
|
|
|
func (ts *TestSuite) testPlayerUpdate(t *testing.T) {
|
|
playerName := "playerUpdate"
|
|
takenPlayerName := "pUpdateTaken"
|
|
user, browserTokenCookie := ts.CreateTestUser(t, ts.App, ts.Server, playerName)
|
|
player := user.Players[0]
|
|
takenUser, takenBrowserTokenCookie := ts.CreateTestUser(t, ts.App, ts.Server, takenPlayerName)
|
|
takenPlayer := takenUser.Players[0]
|
|
|
|
sum := blake3.Sum256(RED_SKIN)
|
|
redSkinHash := hex.EncodeToString(sum[:])
|
|
|
|
sum = blake3.Sum256(RED_CAPE)
|
|
redCapeHash := hex.EncodeToString(sum[:])
|
|
|
|
assert.Equal(t, "en", user.PreferredLanguage)
|
|
user.IsAdmin = true
|
|
assert.Nil(t, ts.App.DB.Save(&user).Error)
|
|
|
|
{
|
|
// Successful update
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
|
|
newPlayerName := "newPlayerUpdate"
|
|
|
|
assert.Nil(t, writer.WriteField("uuid", player.UUID))
|
|
assert.Nil(t, writer.WriteField("playerName", newPlayerName))
|
|
assert.Nil(t, writer.WriteField("fallbackPlayer", newPlayerName))
|
|
assert.Nil(t, writer.WriteField("skinModel", "slim"))
|
|
skinFileField, err := writer.CreateFormFile("skinFile", "redSkin.png")
|
|
assert.Nil(t, err)
|
|
_, err = skinFileField.Write(RED_SKIN)
|
|
assert.Nil(t, err)
|
|
|
|
capeFileField, err := writer.CreateFormFile("capeFile", "redCape.png")
|
|
assert.Nil(t, err)
|
|
_, err = capeFileField.Write(RED_CAPE)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/player/"+player.UUID))
|
|
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-player", body, writer, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.updatePlayerShouldSucceed(t, rec, player.UUID)
|
|
|
|
var updatedPlayer Player
|
|
result := ts.App.DB.First(&updatedPlayer, "name = ?", newPlayerName)
|
|
assert.Nil(t, result.Error)
|
|
assert.Equal(t, "slim", updatedPlayer.SkinModel)
|
|
assert.Equal(t, redSkinHash, *UnmakeNullString(&updatedPlayer.SkinHash))
|
|
assert.Equal(t, redCapeHash, *UnmakeNullString(&updatedPlayer.CapeHash))
|
|
}
|
|
{
|
|
// As an admin, test updating another user's player
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("uuid", takenPlayer.UUID))
|
|
assert.Nil(t, writer.WriteField("skinModel", "slim"))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/player/"+takenPlayer.UUID))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-player", body, writer, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.updatePlayerShouldSucceed(t, rec, takenPlayer.UUID)
|
|
}
|
|
{
|
|
// Non-admin should not be able to edit another user's player
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("uuid", player.UUID))
|
|
assert.Nil(t, writer.WriteField("preferredLanguage", "es"))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/player/"+player.UUID))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-player", body, writer, []http.Cookie{*takenBrowserTokenCookie}, nil)
|
|
ts.updatePlayerShouldFail(t, rec, "Can't update a player belonging to another user unless you're an admin.", ts.App.FrontEndURL+"/web/player/"+player.UUID)
|
|
}
|
|
{
|
|
// Deleting skin should succeed
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("uuid", player.UUID))
|
|
assert.Nil(t, writer.WriteField("deleteSkin", "on"))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/player/"+player.UUID))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-player", body, writer, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.updatePlayerShouldSucceed(t, rec, player.UUID)
|
|
|
|
var updatedPlayer Player
|
|
result := ts.App.DB.First(&updatedPlayer, "uuid = ?", player.UUID)
|
|
assert.Nil(t, result.Error)
|
|
assert.Nil(t, UnmakeNullString(&updatedPlayer.SkinHash))
|
|
assert.NotNil(t, UnmakeNullString(&updatedPlayer.CapeHash))
|
|
assert.Nil(t, ts.App.SetSkinAndSave(&updatedPlayer, bytes.NewReader(RED_SKIN)))
|
|
}
|
|
{
|
|
// Deleting cape should succeed
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("uuid", player.UUID))
|
|
assert.Nil(t, writer.WriteField("deleteCape", "on"))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/player/"+player.UUID))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-player", body, writer, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.updatePlayerShouldSucceed(t, rec, player.UUID)
|
|
|
|
var updatedPlayer Player
|
|
result := ts.App.DB.First(&updatedPlayer, "uuid = ?", player.UUID)
|
|
assert.Nil(t, result.Error)
|
|
assert.Nil(t, UnmakeNullString(&updatedPlayer.CapeHash))
|
|
assert.NotNil(t, UnmakeNullString(&updatedPlayer.SkinHash))
|
|
assert.Nil(t, ts.App.SetCapeAndSave(&updatedPlayer, bytes.NewReader(RED_CAPE)))
|
|
}
|
|
{
|
|
// Invalid player name should fail
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("uuid", player.UUID))
|
|
assert.Nil(t, writer.WriteField("playerName", "AReallyReallyReallyLongUsername"))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/player/"+player.UUID))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-player", body, writer, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.updatePlayerShouldFail(t, rec, "Invalid player name: can't be longer than 16 characters", ts.App.FrontEndURL+"/web/player/"+player.UUID)
|
|
}
|
|
{
|
|
// Setting a skin from URL should fail for non-admin (config.AllowTextureFromURL is false by default)
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
|
|
assert.Nil(t, writer.WriteField("uuid", takenPlayer.UUID))
|
|
assert.Nil(t, writer.WriteField("skinUrl", "https://example.com/skin.png"))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/profile"))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-player", body, writer, []http.Cookie{*takenBrowserTokenCookie}, nil)
|
|
ts.updatePlayerShouldFail(t, rec, "Setting a skin from a URL is not allowed.", ts.App.FrontEndURL+"/web/profile")
|
|
}
|
|
{
|
|
// Invalid fallback player should fail
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("uuid", player.UUID))
|
|
assert.Nil(t, writer.WriteField("fallbackPlayer", "521759201-invalid-uuid-057219"))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/player/"+player.UUID))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-player", body, writer, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.updatePlayerShouldFail(t, rec, "Invalid fallback player: not a valid player name or UUID", ts.App.FrontEndURL+"/web/player/"+player.UUID)
|
|
}
|
|
{
|
|
// Changing to a taken player name should fail
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("uuid", player.UUID))
|
|
assert.Nil(t, writer.WriteField("playerName", takenPlayerName))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/player/"+player.UUID))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-player", body, writer, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.updatePlayerShouldFail(t, rec, "That player name is taken.", ts.App.FrontEndURL+"/web/player/"+player.UUID)
|
|
}
|
|
}
|
|
|
|
func (ts *TestSuite) testCreateNewPlayer(t *testing.T) {
|
|
username := "createNewPlayer1"
|
|
user, browserTokenCookie := ts.CreateTestUser(t, ts.App, ts.Server, username)
|
|
user.MaxPlayerCount = ts.App.Constants.MaxPlayerCountUnlimited
|
|
assert.Nil(t, ts.App.DB.Save(&user).Error)
|
|
|
|
chosenUUID := "2f7b0267-2502-49f9-ba05-8f9c958df02c"
|
|
|
|
form := url.Values{}
|
|
form.Set("userUuid", user.UUID)
|
|
form.Set("playerName", "createNewPlayer2")
|
|
form.Set("playerUuid", chosenUUID)
|
|
form.Set("returnUrl", ts.App.FrontEndURL+"/web/user")
|
|
rec := ts.PostForm(t, ts.Server, "/web/create-player", form, []http.Cookie{*browserTokenCookie}, nil)
|
|
createdUUID := ts.createPlayerShouldSucceed(t, rec)
|
|
assert.Equal(t, chosenUUID, createdUUID)
|
|
}
|
|
|
|
func (ts *TestSuite) testUpdateSkinsCapesNotAllowed(t *testing.T) {
|
|
playerName := "updateNoSkinCape"
|
|
user, browserTokenCookie := ts.CreateTestUser(t, ts.App, ts.Server, playerName)
|
|
player := user.Players[0]
|
|
|
|
{
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("uuid", player.UUID))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/player/"+player.UUID))
|
|
assert.Nil(t, writer.WriteField("skinModel", "classic"))
|
|
skinFileField, err := writer.CreateFormFile("skinFile", "redSkin.png")
|
|
assert.Nil(t, err)
|
|
_, err = skinFileField.Write(RED_SKIN)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-player", body, writer, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.updateUserShouldFail(t, rec, "Setting a skin texture is not allowed.", ts.App.FrontEndURL+"/web/player/"+player.UUID)
|
|
|
|
// The player should not have a skin set
|
|
result := ts.App.DB.First(&player, "uuid = ?", player.UUID)
|
|
assert.Nil(t, result.Error)
|
|
assert.Nil(t, UnmakeNullString(&user.Players[0].SkinHash))
|
|
}
|
|
{
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
assert.Nil(t, writer.WriteField("uuid", player.UUID))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/player/"+player.UUID))
|
|
capeFileField, err := writer.CreateFormFile("capeFile", "redCape.png")
|
|
assert.Nil(t, err)
|
|
_, err = capeFileField.Write(RED_CAPE)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-player", body, writer, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.updateUserShouldFail(t, rec, "Setting a cape texture is not allowed.", ts.App.FrontEndURL+"/web/player/"+player.UUID)
|
|
|
|
// The player should not have a cape set
|
|
result := ts.App.DB.First(&player, "uuid = ?", player.UUID)
|
|
assert.Nil(t, result.Error)
|
|
assert.Nil(t, UnmakeNullString(&player.CapeHash))
|
|
}
|
|
}
|
|
|
|
func (ts *TestSuite) testTextureFromURL(t *testing.T) {
|
|
// Test setting skin from URL
|
|
username := "textureFromURL"
|
|
user, browserTokenCookie := ts.CreateTestUser(t, ts.App, ts.Server, username)
|
|
player := user.Players[0]
|
|
|
|
var auxPlayer Player
|
|
result := ts.AuxApp.DB.First(&auxPlayer, "name = ?", EXISTING_PLAYER_NAME)
|
|
assert.Nil(t, result.Error)
|
|
|
|
// Set a skin on the existing account
|
|
assert.Nil(t, ts.AuxApp.SetSkinAndSave(&auxPlayer, bytes.NewReader(BLUE_SKIN)))
|
|
skinHash := *UnmakeNullString(&auxPlayer.SkinHash)
|
|
skinURL, err := ts.AuxApp.SkinURL(skinHash)
|
|
assert.Nil(t, err)
|
|
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
|
|
assert.Nil(t, writer.WriteField("uuid", player.UUID))
|
|
assert.Nil(t, writer.WriteField("skinUrl", skinURL))
|
|
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/player/"+player.UUID))
|
|
assert.Nil(t, writer.Close())
|
|
rec := ts.PostMultipart(t, ts.Server, "/web/update-player", body, writer, []http.Cookie{*browserTokenCookie}, nil)
|
|
ts.updatePlayerShouldSucceed(t, rec, player.UUID)
|
|
|
|
assert.Nil(t, ts.App.DB.First(&player, "uuid = ?", player.UUID).Error)
|
|
assert.Equal(t, skinHash, *UnmakeNullString(&player.SkinHash))
|
|
}
|
|
|
|
func (ts *TestSuite) testDeleteAccount(t *testing.T) {
|
|
usernameA := "deleteA"
|
|
usernameB := "deleteB"
|
|
|
|
ts.CreateTestUser(t, ts.App, ts.Server, usernameA)
|
|
{
|
|
var user User
|
|
result := ts.App.DB.First(&user, "username = ?", usernameA)
|
|
assert.Nil(t, result.Error)
|
|
player := user.Players[0]
|
|
|
|
// Set red skin and cape on usernameA
|
|
err := ts.App.SetSkinAndSave(&player, bytes.NewReader(RED_SKIN))
|
|
assert.Nil(t, err)
|
|
err = ts.App.SetCapeAndSave(&player, bytes.NewReader(RED_CAPE))
|
|
assert.Nil(t, err)
|
|
|
|
// Register usernameB
|
|
_, browserTokenCookie := ts.CreateTestUser(t, ts.App, ts.Server, usernameB)
|
|
|
|
// Check that usernameB has been created
|
|
var otherUser User
|
|
result = ts.App.DB.First(&otherUser, "username = ?", usernameB)
|
|
assert.Nil(t, result.Error)
|
|
otherPlayer := user.Players[0]
|
|
|
|
// Set red skin and cape on usernameB
|
|
err = ts.App.SetSkinAndSave(&otherPlayer, bytes.NewReader(RED_SKIN))
|
|
assert.Nil(t, err)
|
|
err = ts.App.SetCapeAndSave(&otherPlayer, bytes.NewReader(RED_CAPE))
|
|
assert.Nil(t, err)
|
|
|
|
// Delete account usernameB
|
|
rec := ts.PostForm(t, ts.Server, "/web/delete-user", url.Values{}, []http.Cookie{*browserTokenCookie}, nil)
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, "", getErrorMessage(rec))
|
|
assert.Equal(t, ts.App.FrontEndURL, rec.Header().Get("Location"))
|
|
|
|
// Check that usernameB has been deleted
|
|
result = ts.App.DB.First(&otherUser, "username = ?", usernameB)
|
|
assert.True(t, errors.Is(result.Error, gorm.ErrRecordNotFound))
|
|
|
|
// Check that the red skin and cape still exist in the filesystem
|
|
_, err = os.Stat(ts.App.GetSkinPath(*UnmakeNullString(&player.SkinHash)))
|
|
assert.Nil(t, err)
|
|
_, err = os.Stat(ts.App.GetCapePath(*UnmakeNullString(&player.CapeHash)))
|
|
assert.Nil(t, err)
|
|
}
|
|
{
|
|
// Register usernameB again
|
|
form := url.Values{}
|
|
form.Set("playerName", usernameB)
|
|
form.Set("password", TEST_PASSWORD)
|
|
rec := ts.PostForm(t, ts.Server, "/web/register", form, nil, nil)
|
|
ts.registrationShouldSucceed(t, rec)
|
|
browserTokenCookie := getCookie(rec, BROWSER_TOKEN_COOKIE_NAME)
|
|
|
|
// Check that usernameB has been created
|
|
var otherUser User
|
|
result := ts.App.DB.First(&otherUser, "username = ?", usernameB)
|
|
assert.Nil(t, result.Error)
|
|
otherPlayer := otherUser.Players[0]
|
|
|
|
// Set blue skin and cape on usernameB
|
|
err := ts.App.SetSkinAndSave(&otherPlayer, bytes.NewReader(BLUE_SKIN))
|
|
assert.Nil(t, err)
|
|
err = ts.App.SetCapeAndSave(&otherPlayer, bytes.NewReader(BLUE_CAPE))
|
|
assert.Nil(t, err)
|
|
|
|
blueSkinHash := *UnmakeNullString(&otherPlayer.SkinHash)
|
|
blueCapeHash := *UnmakeNullString(&otherPlayer.CapeHash)
|
|
|
|
// Delete account usernameB
|
|
rec = ts.PostForm(t, ts.Server, "/web/delete-user", url.Values{}, []http.Cookie{*browserTokenCookie}, nil)
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, "", getErrorMessage(rec))
|
|
assert.Equal(t, ts.App.FrontEndURL, rec.Header().Get("Location"))
|
|
|
|
// Check that the blue skin and cape no longer exist in the filesystem
|
|
_, err = os.Stat(ts.App.GetSkinPath(blueSkinHash))
|
|
assert.True(t, os.IsNotExist(err))
|
|
_, err = os.Stat(ts.App.GetCapePath(blueCapeHash))
|
|
assert.True(t, os.IsNotExist(err))
|
|
}
|
|
{
|
|
// Delete account without valid BrowserToken should fail
|
|
rec := ts.PostForm(t, ts.Server, "/web/delete-user", url.Values{}, nil, nil)
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, ts.App.FrontEndURL, rec.Header().Get("Location"))
|
|
assert.Equal(t, "You are not logged in.", getErrorMessage(rec))
|
|
}
|
|
}
|
|
|
|
// Admin
|
|
func (ts *TestSuite) testAdmin(t *testing.T) {
|
|
returnURL := ts.App.FrontEndURL + "/web/admin"
|
|
|
|
username := "admin"
|
|
user, browserTokenCookie := ts.CreateTestUser(t, ts.App, ts.Server, username)
|
|
|
|
otherUsername := "adminOther"
|
|
otherUser, otherBrowserTokenCookie := ts.CreateTestUser(t, ts.App, ts.Server, otherUsername)
|
|
|
|
anotherUsername := "adminAnother"
|
|
anotherUser, anotherBrowserTokenCookie := ts.CreateTestUser(t, ts.App, ts.Server, anotherUsername)
|
|
|
|
// Make `username` an admin
|
|
user.IsAdmin = true
|
|
result := ts.App.DB.Save(&user)
|
|
assert.Nil(t, result.Error)
|
|
|
|
{
|
|
// Revoke admin from `username` should fail
|
|
form := url.Values{}
|
|
form.Set("returnUrl", returnURL)
|
|
rec := ts.PostForm(t, ts.Server, "/web/admin/update-users", form, []http.Cookie{*browserTokenCookie}, nil)
|
|
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, "There must be at least one unlocked admin account.", getErrorMessage(rec))
|
|
assert.Equal(t, returnURL, rec.Header().Get("Location"))
|
|
}
|
|
|
|
// Make `otherUser` and `anotherUser` admins, lock their accounts, and set max player counts
|
|
form := url.Values{}
|
|
form.Set("returnUrl", returnURL)
|
|
form.Set("admin-"+user.UUID, "on")
|
|
form.Set("admin-"+otherUser.UUID, "on")
|
|
form.Set("locked-"+otherUser.UUID, "on")
|
|
form.Set("admin-"+anotherUser.UUID, "on")
|
|
form.Set("locked-"+anotherUser.UUID, "on")
|
|
form.Set("max-player-count-"+otherUser.UUID, "3")
|
|
form.Set("max-player-count-"+anotherUser.UUID, "-1")
|
|
rec := ts.PostForm(t, ts.Server, "/web/admin/update-users", form, []http.Cookie{*browserTokenCookie}, nil)
|
|
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, "", getErrorMessage(rec))
|
|
assert.Equal(t, returnURL, rec.Header().Get("Location"))
|
|
|
|
result = ts.App.DB.First(&otherUser, "uuid = ?", otherUser.UUID)
|
|
assert.Nil(t, result.Error)
|
|
assert.True(t, otherUser.IsAdmin)
|
|
assert.True(t, otherUser.IsLocked)
|
|
assert.Equal(t, 3, otherUser.MaxPlayerCount)
|
|
// `otherUser` should be logged out of the web interface
|
|
assert.NotEqual(t, "", otherBrowserTokenCookie.Value)
|
|
assert.Nil(t, UnmakeNullString(&otherUser.BrowserToken))
|
|
|
|
result = ts.App.DB.First(&anotherUser, "uuid = ?", anotherUser.UUID)
|
|
assert.Nil(t, result.Error)
|
|
assert.True(t, anotherUser.IsAdmin)
|
|
assert.True(t, anotherUser.IsLocked)
|
|
assert.Equal(t, -1, anotherUser.MaxPlayerCount)
|
|
// `anotherUser` should be logged out of the web interface
|
|
assert.NotEqual(t, "", anotherBrowserTokenCookie.Value)
|
|
assert.Nil(t, UnmakeNullString(&anotherUser.BrowserToken))
|
|
|
|
// Delete `otherUser`
|
|
form = url.Values{}
|
|
form.Set("returnUrl", returnURL)
|
|
form.Set("uuid", otherUser.UUID)
|
|
rec = ts.PostForm(t, ts.Server, "/web/delete-user", form, []http.Cookie{*browserTokenCookie}, nil)
|
|
|
|
assert.Equal(t, http.StatusSeeOther, rec.Code)
|
|
assert.Equal(t, "", getErrorMessage(rec))
|
|
assert.Equal(t, returnURL, rec.Header().Get("Location"))
|
|
|
|
err := ts.App.DB.First(&otherUser, "uuid = ?", otherUser.UUID).Error
|
|
assert.NotNil(t, err)
|
|
assert.True(t, errors.Is(err, gorm.ErrRecordNotFound))
|
|
}
|