916 lines
30 KiB
Go
916 lines
30 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gofrs/uuid"
|
|
jwt "github.com/golang-jwt/jwt/v5"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
"github.com/supabase/auth/internal/conf"
|
|
"github.com/supabase/auth/internal/models"
|
|
)
|
|
|
|
type AdminTestSuite struct {
|
|
suite.Suite
|
|
User *models.User
|
|
API *API
|
|
Config *conf.GlobalConfiguration
|
|
|
|
token string
|
|
}
|
|
|
|
func TestAdmin(t *testing.T) {
|
|
api, config, err := setupAPIForTest()
|
|
require.NoError(t, err)
|
|
|
|
ts := &AdminTestSuite{
|
|
API: api,
|
|
Config: config,
|
|
}
|
|
defer api.db.Close()
|
|
|
|
suite.Run(t, ts)
|
|
}
|
|
|
|
func (ts *AdminTestSuite) SetupTest() {
|
|
models.TruncateAll(ts.API.db)
|
|
ts.Config.External.Email.Enabled = true
|
|
claims := &AccessTokenClaims{
|
|
Role: "supabase_admin",
|
|
}
|
|
token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(ts.Config.JWT.Secret))
|
|
require.NoError(ts.T(), err, "Error generating admin jwt")
|
|
ts.token = token
|
|
}
|
|
|
|
// TestAdminUsersUnauthorized tests API /admin/users route without authentication
|
|
func (ts *AdminTestSuite) TestAdminUsersUnauthorized() {
|
|
req := httptest.NewRequest(http.MethodGet, "/admin/users", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
assert.Equal(ts.T(), http.StatusUnauthorized, w.Code)
|
|
}
|
|
|
|
// TestAdminUsers tests API /admin/users route
|
|
func (ts *AdminTestSuite) TestAdminUsers() {
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/admin/users", nil)
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusOK, w.Code)
|
|
|
|
assert.Equal(ts.T(), "</admin/users?page=0>; rel=\"last\"", w.Header().Get("Link"))
|
|
assert.Equal(ts.T(), "0", w.Header().Get("X-Total-Count"))
|
|
}
|
|
|
|
// TestAdminUsers tests API /admin/users route
|
|
func (ts *AdminTestSuite) TestAdminUsers_Pagination() {
|
|
u, err := models.NewUser("12345678", "test1@example.com", "test", ts.Config.JWT.Aud, nil)
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
u, err = models.NewUser("987654321", "test2@example.com", "test", ts.Config.JWT.Aud, nil)
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/admin/users?per_page=1", nil)
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusOK, w.Code)
|
|
|
|
assert.Equal(ts.T(), "</admin/users?page=2&per_page=1>; rel=\"next\", </admin/users?page=2&per_page=1>; rel=\"last\"", w.Header().Get("Link"))
|
|
assert.Equal(ts.T(), "2", w.Header().Get("X-Total-Count"))
|
|
|
|
data := make(map[string]interface{})
|
|
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
|
|
for _, user := range data["users"].([]interface{}) {
|
|
assert.NotEmpty(ts.T(), user)
|
|
}
|
|
}
|
|
|
|
// TestAdminUsers tests API /admin/users route
|
|
func (ts *AdminTestSuite) TestAdminUsers_SortAsc() {
|
|
u, err := models.NewUser("", "test1@example.com", "test", ts.Config.JWT.Aud, nil)
|
|
u.CreatedAt = time.Now().Add(-time.Minute)
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
u, err = models.NewUser("", "test2@example.com", "test", ts.Config.JWT.Aud, nil)
|
|
u.CreatedAt = time.Now()
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/admin/users", nil)
|
|
qv := req.URL.Query()
|
|
qv.Set("sort", "created_at asc")
|
|
req.URL.RawQuery = qv.Encode()
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusOK, w.Code)
|
|
|
|
data := struct {
|
|
Users []*models.User `json:"users"`
|
|
Aud string `json:"aud"`
|
|
}{}
|
|
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
|
|
|
|
require.Len(ts.T(), data.Users, 2)
|
|
assert.Equal(ts.T(), "test1@example.com", data.Users[0].GetEmail())
|
|
assert.Equal(ts.T(), "test2@example.com", data.Users[1].GetEmail())
|
|
}
|
|
|
|
// TestAdminUsers tests API /admin/users route
|
|
func (ts *AdminTestSuite) TestAdminUsers_SortDesc() {
|
|
u, err := models.NewUser("12345678", "test1@example.com", "test", ts.Config.JWT.Aud, nil)
|
|
u.CreatedAt = time.Now().Add(-time.Minute)
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
u, err = models.NewUser("987654321", "test2@example.com", "test", ts.Config.JWT.Aud, nil)
|
|
u.CreatedAt = time.Now()
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/admin/users", nil)
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusOK, w.Code)
|
|
|
|
data := struct {
|
|
Users []*models.User `json:"users"`
|
|
Aud string `json:"aud"`
|
|
}{}
|
|
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
|
|
|
|
require.Len(ts.T(), data.Users, 2)
|
|
assert.Equal(ts.T(), "test2@example.com", data.Users[0].GetEmail())
|
|
assert.Equal(ts.T(), "test1@example.com", data.Users[1].GetEmail())
|
|
}
|
|
|
|
// TestAdminUsers tests API /admin/users route
|
|
func (ts *AdminTestSuite) TestAdminUsers_FilterEmail() {
|
|
u, err := models.NewUser("", "test1@example.com", "test", ts.Config.JWT.Aud, nil)
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/admin/users?filter=test1", nil)
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusOK, w.Code)
|
|
|
|
data := struct {
|
|
Users []*models.User `json:"users"`
|
|
Aud string `json:"aud"`
|
|
}{}
|
|
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
|
|
|
|
require.Len(ts.T(), data.Users, 1)
|
|
assert.Equal(ts.T(), "test1@example.com", data.Users[0].GetEmail())
|
|
}
|
|
|
|
// TestAdminUsers tests API /admin/users route
|
|
func (ts *AdminTestSuite) TestAdminUsers_FilterName() {
|
|
u, err := models.NewUser("", "test1@example.com", "test", ts.Config.JWT.Aud, map[string]interface{}{"full_name": "Test User"})
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
u, err = models.NewUser("", "test2@example.com", "test", ts.Config.JWT.Aud, nil)
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/admin/users?filter=User", nil)
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusOK, w.Code)
|
|
|
|
data := struct {
|
|
Users []*models.User `json:"users"`
|
|
Aud string `json:"aud"`
|
|
}{}
|
|
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
|
|
|
|
require.Len(ts.T(), data.Users, 1)
|
|
assert.Equal(ts.T(), "test1@example.com", data.Users[0].GetEmail())
|
|
}
|
|
|
|
// TestAdminUserCreate tests API /admin/user route (POST)
|
|
func (ts *AdminTestSuite) TestAdminUserCreate() {
|
|
cases := []struct {
|
|
desc string
|
|
params map[string]interface{}
|
|
expected map[string]interface{}
|
|
}{
|
|
{
|
|
desc: "Only phone",
|
|
params: map[string]interface{}{
|
|
"phone": "123456789",
|
|
"password": "test1",
|
|
},
|
|
expected: map[string]interface{}{
|
|
"email": "",
|
|
"phone": "123456789",
|
|
"isAuthenticated": true,
|
|
"provider": "phone",
|
|
"providers": []string{"phone"},
|
|
"password": "test1",
|
|
},
|
|
},
|
|
{
|
|
desc: "With password",
|
|
params: map[string]interface{}{
|
|
"email": "test1@example.com",
|
|
"phone": "123456789",
|
|
"password": "test1",
|
|
},
|
|
expected: map[string]interface{}{
|
|
"email": "test1@example.com",
|
|
"phone": "123456789",
|
|
"isAuthenticated": true,
|
|
"provider": "email",
|
|
"providers": []string{"email", "phone"},
|
|
"password": "test1",
|
|
},
|
|
},
|
|
{
|
|
desc: "Without password",
|
|
params: map[string]interface{}{
|
|
"email": "test2@example.com",
|
|
"phone": "",
|
|
},
|
|
expected: map[string]interface{}{
|
|
"email": "test2@example.com",
|
|
"phone": "",
|
|
"isAuthenticated": false,
|
|
"provider": "email",
|
|
"providers": []string{"email"},
|
|
},
|
|
},
|
|
{
|
|
desc: "With empty string password",
|
|
params: map[string]interface{}{
|
|
"email": "test3@example.com",
|
|
"phone": "",
|
|
"password": "",
|
|
},
|
|
expected: map[string]interface{}{
|
|
"email": "test3@example.com",
|
|
"phone": "",
|
|
"isAuthenticated": false,
|
|
"provider": "email",
|
|
"providers": []string{"email"},
|
|
"password": "",
|
|
},
|
|
},
|
|
{
|
|
desc: "Ban created user",
|
|
params: map[string]interface{}{
|
|
"email": "test4@example.com",
|
|
"phone": "",
|
|
"password": "test1",
|
|
"ban_duration": "24h",
|
|
},
|
|
expected: map[string]interface{}{
|
|
"email": "test4@example.com",
|
|
"phone": "",
|
|
"isAuthenticated": true,
|
|
"provider": "email",
|
|
"providers": []string{"email"},
|
|
"password": "test1",
|
|
},
|
|
},
|
|
{
|
|
desc: "With password hash",
|
|
params: map[string]interface{}{
|
|
"email": "test5@example.com",
|
|
"password_hash": "$2y$10$SXEz2HeT8PUIGQXo9yeUIem8KzNxgG0d7o/.eGj2rj8KbRgAuRVlq",
|
|
},
|
|
expected: map[string]interface{}{
|
|
"email": "test5@example.com",
|
|
"phone": "",
|
|
"isAuthenticated": true,
|
|
"provider": "email",
|
|
"providers": []string{"email"},
|
|
"password": "test",
|
|
},
|
|
},
|
|
{
|
|
desc: "With custom id",
|
|
params: map[string]interface{}{
|
|
"id": "fc56ab41-2010-4870-a9b9-767c1dc573fb",
|
|
"email": "test6@example.com",
|
|
"password": "test",
|
|
},
|
|
expected: map[string]interface{}{
|
|
"id": "fc56ab41-2010-4870-a9b9-767c1dc573fb",
|
|
"email": "test6@example.com",
|
|
"phone": "",
|
|
"isAuthenticated": true,
|
|
"provider": "email",
|
|
"providers": []string{"email"},
|
|
"password": "test",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
ts.Run(c.desc, func() {
|
|
var buffer bytes.Buffer
|
|
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(c.params))
|
|
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPost, "/admin/users", &buffer)
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
ts.Config.External.Phone.Enabled = true
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusOK, w.Code)
|
|
|
|
data := models.User{}
|
|
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
|
|
assert.Equal(ts.T(), c.expected["email"], data.GetEmail())
|
|
assert.Equal(ts.T(), c.expected["phone"], data.GetPhone())
|
|
assert.Equal(ts.T(), c.expected["provider"], data.AppMetaData["provider"])
|
|
assert.ElementsMatch(ts.T(), c.expected["providers"], data.AppMetaData["providers"])
|
|
|
|
u, err := models.FindUserByID(ts.API.db, data.ID)
|
|
require.NoError(ts.T(), err)
|
|
|
|
// verify that the corresponding identities were created
|
|
require.NotEmpty(ts.T(), u.Identities)
|
|
for _, identity := range u.Identities {
|
|
require.Equal(ts.T(), u.ID, identity.UserID)
|
|
if identity.Provider == "email" {
|
|
require.Equal(ts.T(), c.expected["email"], identity.IdentityData["email"])
|
|
}
|
|
if identity.Provider == "phone" {
|
|
require.Equal(ts.T(), c.expected["phone"], identity.IdentityData["phone"])
|
|
}
|
|
}
|
|
|
|
if _, ok := c.expected["password"]; ok {
|
|
expectedPassword := fmt.Sprintf("%v", c.expected["password"])
|
|
isAuthenticated, _, err := u.Authenticate(context.Background(), ts.API.db, expectedPassword, ts.API.config.Security.DBEncryption.DecryptionKeys, ts.API.config.Security.DBEncryption.Encrypt, ts.API.config.Security.DBEncryption.EncryptionKeyID)
|
|
require.NoError(ts.T(), err)
|
|
require.Equal(ts.T(), c.expected["isAuthenticated"], isAuthenticated)
|
|
}
|
|
|
|
if id, ok := c.expected["id"]; ok {
|
|
uid, err := uuid.FromString(id.(string))
|
|
require.NoError(ts.T(), err)
|
|
require.Equal(ts.T(), uid, data.ID)
|
|
}
|
|
|
|
// remove created user after each case
|
|
require.NoError(ts.T(), ts.API.db.Destroy(u))
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestAdminUserGet tests API /admin/user route (GET)
|
|
func (ts *AdminTestSuite) TestAdminUserGet() {
|
|
u, err := models.NewUser("12345678", "test1@example.com", "test", ts.Config.JWT.Aud, map[string]interface{}{"full_name": "Test Get User"})
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/admin/users/%s", u.ID), nil)
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusOK, w.Code)
|
|
|
|
data := make(map[string]interface{})
|
|
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
|
|
|
|
assert.Equal(ts.T(), data["email"], "test1@example.com")
|
|
assert.NotNil(ts.T(), data["app_metadata"])
|
|
assert.NotNil(ts.T(), data["user_metadata"])
|
|
md := data["user_metadata"].(map[string]interface{})
|
|
assert.Len(ts.T(), md, 1)
|
|
assert.Equal(ts.T(), "Test Get User", md["full_name"])
|
|
}
|
|
|
|
// TestAdminUserUpdate tests API /admin/user route (UPDATE)
|
|
func (ts *AdminTestSuite) TestAdminUserUpdate() {
|
|
u, err := models.NewUser("12345678", "test1@example.com", "test", ts.Config.JWT.Aud, nil)
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
var buffer bytes.Buffer
|
|
newEmail := "test2@example.com"
|
|
newPhone := "234567890"
|
|
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
|
|
"role": "testing",
|
|
"app_metadata": map[string]interface{}{
|
|
"roles": []string{"writer", "editor"},
|
|
},
|
|
"user_metadata": map[string]interface{}{
|
|
"name": "David",
|
|
},
|
|
"ban_duration": "24h",
|
|
"email": newEmail,
|
|
"phone": newPhone,
|
|
}))
|
|
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/admin/users/%s", u.ID), &buffer)
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusOK, w.Code)
|
|
|
|
data := models.User{}
|
|
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
|
|
|
|
assert.Equal(ts.T(), "testing", data.Role)
|
|
assert.NotNil(ts.T(), data.UserMetaData)
|
|
assert.Equal(ts.T(), "David", data.UserMetaData["name"])
|
|
assert.Equal(ts.T(), newEmail, data.GetEmail())
|
|
assert.Equal(ts.T(), newPhone, data.GetPhone())
|
|
|
|
assert.NotNil(ts.T(), data.AppMetaData)
|
|
assert.Len(ts.T(), data.AppMetaData["roles"], 2)
|
|
assert.Contains(ts.T(), data.AppMetaData["roles"], "writer")
|
|
assert.Contains(ts.T(), data.AppMetaData["roles"], "editor")
|
|
assert.NotNil(ts.T(), data.BannedUntil)
|
|
|
|
u, err = models.FindUserByID(ts.API.db, data.ID)
|
|
require.NoError(ts.T(), err)
|
|
|
|
// check if the corresponding identities were successfully created
|
|
require.NotEmpty(ts.T(), u.Identities)
|
|
|
|
for _, identity := range u.Identities {
|
|
// for email & phone identities, the providerId is the same as the userId
|
|
require.Equal(ts.T(), u.ID.String(), identity.ProviderID)
|
|
require.Equal(ts.T(), u.ID, identity.UserID)
|
|
if identity.Provider == "email" {
|
|
require.Equal(ts.T(), newEmail, identity.IdentityData["email"])
|
|
}
|
|
if identity.Provider == "phone" {
|
|
require.Equal(ts.T(), newPhone, identity.IdentityData["phone"])
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ts *AdminTestSuite) TestAdminUserUpdatePasswordFailed() {
|
|
u, err := models.NewUser("12345678", "test1@example.com", "test", ts.Config.JWT.Aud, nil)
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
var updateEndpoint = fmt.Sprintf("/admin/users/%s", u.ID)
|
|
ts.Config.Password.MinLength = 6
|
|
ts.Run("Password doesn't meet minimum length", func() {
|
|
var buffer bytes.Buffer
|
|
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
|
|
"password": "",
|
|
}))
|
|
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPut, updateEndpoint, &buffer)
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusUnprocessableEntity, w.Code)
|
|
})
|
|
}
|
|
|
|
func (ts *AdminTestSuite) TestAdminUserUpdateBannedUntilFailed() {
|
|
u, err := models.NewUser("", "test1@example.com", "test", ts.Config.JWT.Aud, nil)
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
var updateEndpoint = fmt.Sprintf("/admin/users/%s", u.ID)
|
|
ts.Config.Password.MinLength = 6
|
|
ts.Run("Incorrect format for ban_duration", func() {
|
|
var buffer bytes.Buffer
|
|
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
|
|
"ban_duration": "24",
|
|
}))
|
|
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPut, updateEndpoint, &buffer)
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusBadRequest, w.Code)
|
|
})
|
|
}
|
|
|
|
// TestAdminUserDelete tests API /admin/users route (DELETE)
|
|
func (ts *AdminTestSuite) TestAdminUserDelete() {
|
|
type expected struct {
|
|
code int
|
|
err error
|
|
}
|
|
signupParams := &SignupParams{
|
|
Email: "test-delete@example.com",
|
|
Password: "test",
|
|
Data: map[string]interface{}{"name": "test"},
|
|
Provider: "email",
|
|
Aud: ts.Config.JWT.Aud,
|
|
}
|
|
cases := []struct {
|
|
desc string
|
|
body map[string]interface{}
|
|
isSoftDelete string
|
|
isSSOUser bool
|
|
expected expected
|
|
}{
|
|
{
|
|
desc: "Test admin delete user (default)",
|
|
isSoftDelete: "",
|
|
isSSOUser: false,
|
|
expected: expected{code: http.StatusOK, err: models.UserNotFoundError{}},
|
|
body: nil,
|
|
},
|
|
{
|
|
desc: "Test admin delete user (hard deletion)",
|
|
isSoftDelete: "?is_soft_delete=false",
|
|
isSSOUser: false,
|
|
expected: expected{code: http.StatusOK, err: models.UserNotFoundError{}},
|
|
body: map[string]interface{}{
|
|
"should_soft_delete": false,
|
|
},
|
|
},
|
|
{
|
|
desc: "Test admin delete user (soft deletion)",
|
|
isSoftDelete: "?is_soft_delete=true",
|
|
isSSOUser: false,
|
|
expected: expected{code: http.StatusOK, err: models.UserNotFoundError{}},
|
|
body: map[string]interface{}{
|
|
"should_soft_delete": true,
|
|
},
|
|
},
|
|
{
|
|
desc: "Test admin delete user (soft deletion & sso user)",
|
|
isSoftDelete: "?is_soft_delete=true",
|
|
isSSOUser: true,
|
|
expected: expected{code: http.StatusOK, err: nil},
|
|
body: map[string]interface{}{
|
|
"should_soft_delete": true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
ts.Run(c.desc, func() {
|
|
var buffer bytes.Buffer
|
|
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(c.body))
|
|
u, err := signupParams.ToUserModel(false /* <- isSSOUser */)
|
|
require.NoError(ts.T(), err)
|
|
u, err = ts.API.signupNewUser(ts.API.db, u)
|
|
require.NoError(ts.T(), err)
|
|
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/admin/users/%s", u.ID), &buffer)
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), c.expected.code, w.Code)
|
|
|
|
if c.isSSOUser {
|
|
u, err = models.FindUserByID(ts.API.db, u.ID)
|
|
require.NotNil(ts.T(), u)
|
|
} else {
|
|
_, err = models.FindUserByEmailAndAudience(ts.API.db, signupParams.Email, ts.Config.JWT.Aud)
|
|
}
|
|
require.Equal(ts.T(), c.expected.err, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (ts *AdminTestSuite) TestAdminUserSoftDeletion() {
|
|
// create user
|
|
u, err := models.NewUser("123456789", "test@example.com", "secret", ts.Config.JWT.Aud, map[string]interface{}{"name": "test"})
|
|
require.NoError(ts.T(), err)
|
|
u.ConfirmationToken = "some_token"
|
|
u.RecoveryToken = "some_token"
|
|
u.EmailChangeTokenCurrent = "some_token"
|
|
u.EmailChangeTokenNew = "some_token"
|
|
u.PhoneChangeToken = "some_token"
|
|
u.AppMetaData = map[string]interface{}{
|
|
"provider": "email",
|
|
}
|
|
require.NoError(ts.T(), ts.API.db.Create(u))
|
|
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.ConfirmationToken, models.ConfirmationToken))
|
|
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.RecoveryToken, models.RecoveryToken))
|
|
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.EmailChangeTokenCurrent, models.EmailChangeTokenCurrent))
|
|
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetEmail(), u.EmailChangeTokenNew, models.EmailChangeTokenNew))
|
|
require.NoError(ts.T(), models.CreateOneTimeToken(ts.API.db, u.ID, u.GetPhone(), u.PhoneChangeToken, models.PhoneChangeToken))
|
|
|
|
// create user identities
|
|
_, err = ts.API.createNewIdentity(ts.API.db, u, "email", map[string]interface{}{
|
|
"sub": "123456",
|
|
"email": "test@example.com",
|
|
})
|
|
require.NoError(ts.T(), err)
|
|
_, err = ts.API.createNewIdentity(ts.API.db, u, "github", map[string]interface{}{
|
|
"sub": "234567",
|
|
"email": "test@example.com",
|
|
})
|
|
require.NoError(ts.T(), err)
|
|
|
|
var buffer bytes.Buffer
|
|
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
|
|
"should_soft_delete": true,
|
|
}))
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/admin/users/%s", u.ID), &buffer)
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusOK, w.Code)
|
|
|
|
// get soft-deleted user from db
|
|
deletedUser, err := models.FindUserByID(ts.API.db, u.ID)
|
|
require.NoError(ts.T(), err)
|
|
|
|
require.Empty(ts.T(), deletedUser.ConfirmationToken)
|
|
require.Empty(ts.T(), deletedUser.RecoveryToken)
|
|
require.Empty(ts.T(), deletedUser.EmailChangeTokenCurrent)
|
|
require.Empty(ts.T(), deletedUser.EmailChangeTokenNew)
|
|
require.Empty(ts.T(), deletedUser.EncryptedPassword)
|
|
require.Empty(ts.T(), deletedUser.PhoneChangeToken)
|
|
require.Empty(ts.T(), deletedUser.UserMetaData)
|
|
require.Empty(ts.T(), deletedUser.AppMetaData)
|
|
require.NotEmpty(ts.T(), deletedUser.DeletedAt)
|
|
require.NotEmpty(ts.T(), deletedUser.GetEmail())
|
|
|
|
// get soft-deleted user's identity from db
|
|
deletedIdentities, err := models.FindIdentitiesByUserID(ts.API.db, deletedUser.ID)
|
|
require.NoError(ts.T(), err)
|
|
|
|
for _, identity := range deletedIdentities {
|
|
require.Empty(ts.T(), identity.IdentityData)
|
|
}
|
|
}
|
|
|
|
func (ts *AdminTestSuite) TestAdminUserCreateWithDisabledLogin() {
|
|
var cases = []struct {
|
|
desc string
|
|
customConfig *conf.GlobalConfiguration
|
|
userData map[string]interface{}
|
|
expected int
|
|
}{
|
|
{
|
|
desc: "Email Signups Disabled",
|
|
customConfig: &conf.GlobalConfiguration{
|
|
JWT: ts.Config.JWT,
|
|
External: conf.ProviderConfiguration{
|
|
Email: conf.EmailProviderConfiguration{
|
|
Enabled: false,
|
|
},
|
|
},
|
|
},
|
|
userData: map[string]interface{}{
|
|
"email": "test1@example.com",
|
|
"password": "test1",
|
|
},
|
|
expected: http.StatusOK,
|
|
},
|
|
{
|
|
desc: "Phone Signups Disabled",
|
|
customConfig: &conf.GlobalConfiguration{
|
|
JWT: ts.Config.JWT,
|
|
External: conf.ProviderConfiguration{
|
|
Phone: conf.PhoneProviderConfiguration{
|
|
Enabled: false,
|
|
},
|
|
},
|
|
},
|
|
userData: map[string]interface{}{
|
|
"phone": "123456789",
|
|
"password": "test1",
|
|
},
|
|
expected: http.StatusOK,
|
|
},
|
|
{
|
|
desc: "All Signups Disabled",
|
|
customConfig: &conf.GlobalConfiguration{
|
|
JWT: ts.Config.JWT,
|
|
DisableSignup: true,
|
|
},
|
|
userData: map[string]interface{}{
|
|
"email": "test2@example.com",
|
|
"password": "test2",
|
|
},
|
|
expected: http.StatusOK,
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
ts.Run(c.desc, func() {
|
|
// Initialize user data
|
|
var buffer bytes.Buffer
|
|
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(c.userData))
|
|
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPost, "/admin/users", &buffer)
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.Config.JWT = c.customConfig.JWT
|
|
ts.Config.External = c.customConfig.External
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), c.expected, w.Code)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestAdminUserDeleteFactor tests API /admin/users/<user_id>/factors/<factor_id>/
|
|
func (ts *AdminTestSuite) TestAdminUserDeleteFactor() {
|
|
u, err := models.NewUser("123456789", "test-delete@example.com", "test", ts.Config.JWT.Aud, nil)
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
f := models.NewTOTPFactor(u, "testSimpleName")
|
|
require.NoError(ts.T(), f.UpdateStatus(ts.API.db, models.FactorStateVerified))
|
|
require.NoError(ts.T(), f.SetSecret("secretkey", ts.Config.Security.DBEncryption.Encrypt, ts.Config.Security.DBEncryption.EncryptionKeyID, ts.Config.Security.DBEncryption.EncryptionKey))
|
|
require.NoError(ts.T(), ts.API.db.Create(f), "Error saving new test factor")
|
|
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/admin/users/%s/factors/%s/", u.ID, f.ID), nil)
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusOK, w.Code)
|
|
|
|
_, err = models.FindFactorByFactorID(ts.API.db, f.ID)
|
|
require.EqualError(ts.T(), err, models.FactorNotFoundError{}.Error())
|
|
|
|
}
|
|
|
|
// TestAdminUserGetFactor tests API /admin/user/<user_id>/factors/
|
|
func (ts *AdminTestSuite) TestAdminUserGetFactors() {
|
|
u, err := models.NewUser("123456789", "test-delete@example.com", "test", ts.Config.JWT.Aud, nil)
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
f := models.NewTOTPFactor(u, "testSimpleName")
|
|
require.NoError(ts.T(), f.SetSecret("secretkey", ts.Config.Security.DBEncryption.Encrypt, ts.Config.Security.DBEncryption.EncryptionKeyID, ts.Config.Security.DBEncryption.EncryptionKey))
|
|
require.NoError(ts.T(), ts.API.db.Create(f), "Error saving new test factor")
|
|
|
|
// Setup request
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/admin/users/%s/factors/", u.ID), nil)
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusOK, w.Code)
|
|
getFactorsResp := []*models.Factor{}
|
|
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&getFactorsResp))
|
|
require.Equal(ts.T(), getFactorsResp[0].Secret, "")
|
|
}
|
|
|
|
func (ts *AdminTestSuite) TestAdminUserUpdateFactor() {
|
|
u, err := models.NewUser("123456789", "test-delete@example.com", "test", ts.Config.JWT.Aud, nil)
|
|
require.NoError(ts.T(), err, "Error making new user")
|
|
require.NoError(ts.T(), ts.API.db.Create(u), "Error creating user")
|
|
|
|
f := models.NewPhoneFactor(u, "123456789", "testSimpleName")
|
|
require.NoError(ts.T(), f.SetSecret("secretkey", ts.Config.Security.DBEncryption.Encrypt, ts.Config.Security.DBEncryption.EncryptionKeyID, ts.Config.Security.DBEncryption.EncryptionKey))
|
|
require.NoError(ts.T(), ts.API.db.Create(f), "Error saving new test factor")
|
|
|
|
var cases = []struct {
|
|
Desc string
|
|
FactorData map[string]interface{}
|
|
ExpectedCode int
|
|
}{
|
|
{
|
|
Desc: "Update Factor friendly name",
|
|
FactorData: map[string]interface{}{
|
|
"friendly_name": "john",
|
|
},
|
|
ExpectedCode: http.StatusOK,
|
|
},
|
|
{
|
|
Desc: "Update Factor phone number",
|
|
FactorData: map[string]interface{}{
|
|
"phone": "+1976154321",
|
|
},
|
|
ExpectedCode: http.StatusOK,
|
|
},
|
|
}
|
|
|
|
// Initialize factor data
|
|
for _, c := range cases {
|
|
ts.Run(c.Desc, func() {
|
|
var buffer bytes.Buffer
|
|
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(c.FactorData))
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/admin/users/%s/factors/%s/", u.ID, f.ID), &buffer)
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), c.ExpectedCode, w.Code)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (ts *AdminTestSuite) TestAdminUserCreateValidationErrors() {
|
|
cases := []struct {
|
|
desc string
|
|
params map[string]interface{}
|
|
}{
|
|
{
|
|
desc: "create user without email and phone",
|
|
params: map[string]interface{}{
|
|
"password": "test_password",
|
|
},
|
|
},
|
|
{
|
|
desc: "create user with password and password hash",
|
|
params: map[string]interface{}{
|
|
"email": "test@example.com",
|
|
"password": "test_password",
|
|
"password_hash": "$2y$10$Tk6yEdmTbb/eQ/haDMaCsuCsmtPVprjHMcij1RqiJdLGPDXnL3L1a",
|
|
},
|
|
},
|
|
{
|
|
desc: "invalid ban duration",
|
|
params: map[string]interface{}{
|
|
"email": "test@example.com",
|
|
"ban_duration": "never",
|
|
},
|
|
},
|
|
{
|
|
desc: "custom id is nil",
|
|
params: map[string]interface{}{
|
|
"id": "00000000-0000-0000-0000-000000000000",
|
|
"email": "test@example.com",
|
|
},
|
|
},
|
|
{
|
|
desc: "bad id format",
|
|
params: map[string]interface{}{
|
|
"id": "bad_uuid_format",
|
|
"email": "test@example.com",
|
|
},
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
ts.Run(c.desc, func() {
|
|
var buffer bytes.Buffer
|
|
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(c.params))
|
|
req := httptest.NewRequest(http.MethodPost, "/admin/users", &buffer)
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ts.token))
|
|
w := httptest.NewRecorder()
|
|
ts.API.handler.ServeHTTP(w, req)
|
|
require.Equal(ts.T(), http.StatusBadRequest, w.Code, w)
|
|
|
|
data := map[string]interface{}{}
|
|
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
|
|
require.Equal(ts.T(), data["error_code"], ErrorCodeValidationFailed)
|
|
})
|
|
|
|
}
|
|
}
|