2019-08-02 15:03:32 -08:00
|
|
|
package invite
|
|
|
|
|
|
|
|
import (
|
2019-08-05 17:12:28 -08:00
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
|
2019-08-02 15:03:32 -08:00
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/account"
|
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
|
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/notify"
|
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/tests"
|
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/user"
|
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/user_account"
|
|
|
|
"github.com/dgrijalva/jwt-go"
|
|
|
|
"github.com/pborman/uuid"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
var test *tests.Test
|
|
|
|
|
|
|
|
// TestMain is the entry point for testing.
|
|
|
|
func TestMain(m *testing.M) {
|
|
|
|
os.Exit(testMain(m))
|
|
|
|
}
|
|
|
|
|
|
|
|
func testMain(m *testing.M) int {
|
|
|
|
test = tests.New()
|
|
|
|
defer test.TearDown()
|
|
|
|
return m.Run()
|
|
|
|
}
|
|
|
|
|
2019-08-04 14:48:43 -08:00
|
|
|
// TestSendUserInvites validates that invite users works.
|
|
|
|
func TestSendUserInvites(t *testing.T) {
|
2019-08-02 15:03:32 -08:00
|
|
|
|
|
|
|
t.Log("Given the need ensure a user an invite users to their account.")
|
|
|
|
{
|
|
|
|
ctx := tests.Context()
|
|
|
|
|
|
|
|
now := time.Date(2018, time.October, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
|
|
|
|
// Create a new user for testing.
|
|
|
|
initPass := uuid.NewRandom().String()
|
|
|
|
u, err := user.Create(ctx, auth.Claims{}, test.MasterDB, user.UserCreateRequest{
|
|
|
|
FirstName: "Lee",
|
|
|
|
LastName: "Brown",
|
|
|
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
|
|
|
Password: initPass,
|
|
|
|
PasswordConfirm: initPass,
|
|
|
|
}, now)
|
|
|
|
if err != nil {
|
|
|
|
t.Log("\t\tGot :", err)
|
|
|
|
t.Fatalf("\t%s\tCreate user failed.", tests.Failed)
|
|
|
|
}
|
|
|
|
|
|
|
|
a, err := account.Create(ctx, auth.Claims{}, test.MasterDB, account.AccountCreateRequest{
|
|
|
|
Name: uuid.NewRandom().String(),
|
|
|
|
Address1: "101 E Main",
|
|
|
|
City: "Valdez",
|
|
|
|
Region: "AK",
|
|
|
|
Country: "US",
|
|
|
|
Zipcode: "99686",
|
|
|
|
}, now)
|
|
|
|
if err != nil {
|
|
|
|
t.Log("\t\tGot :", err)
|
|
|
|
t.Fatalf("\t%s\tCreate account failed.", tests.Failed)
|
|
|
|
}
|
|
|
|
|
|
|
|
uRoles := []user_account.UserAccountRole{user_account.UserAccountRole_Admin}
|
|
|
|
_, err = user_account.Create(ctx, auth.Claims{}, test.MasterDB, user_account.UserAccountCreateRequest{
|
|
|
|
UserID: u.ID,
|
|
|
|
AccountID: a.ID,
|
|
|
|
Roles: uRoles,
|
|
|
|
}, now)
|
|
|
|
if err != nil {
|
|
|
|
t.Log("\t\tGot :", err)
|
|
|
|
t.Fatalf("\t%s\tCreate account failed.", tests.Failed)
|
|
|
|
}
|
|
|
|
|
|
|
|
claims := auth.Claims{
|
2019-08-05 17:12:28 -08:00
|
|
|
AccountIDs: []string{a.ID},
|
2019-08-02 15:03:32 -08:00
|
|
|
StandardClaims: jwt.StandardClaims{
|
|
|
|
Subject: u.ID,
|
|
|
|
Audience: a.ID,
|
|
|
|
IssuedAt: now.Unix(),
|
|
|
|
ExpiresAt: now.Add(time.Hour).Unix(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, r := range uRoles {
|
|
|
|
claims.Roles = append(claims.Roles, r.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mock the methods needed to make a password reset.
|
|
|
|
resetUrl := func(string) string {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
notify := ¬ify.MockEmail{}
|
|
|
|
|
|
|
|
secretKey := "6368616e676520746869732070617373"
|
|
|
|
|
|
|
|
// Ensure validation is working by trying ResetPassword with an empty request.
|
|
|
|
{
|
2019-08-04 14:48:43 -08:00
|
|
|
expectedErr := errors.New("Key: 'SendUserInvitesRequest.account_id' Error:Field validation for 'account_id' failed on the 'required' tag\n" +
|
|
|
|
"Key: 'SendUserInvitesRequest.user_id' Error:Field validation for 'user_id' failed on the 'required' tag\n" +
|
|
|
|
"Key: 'SendUserInvitesRequest.emails' Error:Field validation for 'emails' failed on the 'required' tag\n" +
|
|
|
|
"Key: 'SendUserInvitesRequest.roles' Error:Field validation for 'roles' failed on the 'required' tag")
|
|
|
|
_, err = SendUserInvites(ctx, claims, test.MasterDB, resetUrl, notify, SendUserInvitesRequest{}, secretKey, now)
|
2019-08-02 15:03:32 -08:00
|
|
|
if err == nil {
|
|
|
|
t.Logf("\t\tWant: %+v", expectedErr)
|
|
|
|
t.Fatalf("\t%s\tInviteUsers failed.", tests.Failed)
|
|
|
|
}
|
|
|
|
|
|
|
|
errStr := strings.Replace(err.Error(), "{{", "", -1)
|
|
|
|
errStr = strings.Replace(errStr, "}}", "", -1)
|
|
|
|
|
|
|
|
if errStr != expectedErr.Error() {
|
|
|
|
t.Logf("\t\tGot : %+v", errStr)
|
|
|
|
t.Logf("\t\tWant: %+v", expectedErr)
|
|
|
|
t.Fatalf("\t%s\tInviteUsers Validation failed.", tests.Failed)
|
|
|
|
}
|
|
|
|
t.Logf("\t%s\tInviteUsers Validation ok.", tests.Success)
|
|
|
|
}
|
|
|
|
|
|
|
|
ttl := time.Hour
|
|
|
|
|
|
|
|
inviteEmails := []string{
|
|
|
|
uuid.NewRandom().String() + "@geeksinthewoods.com",
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make the reset password request.
|
2019-08-04 14:48:43 -08:00
|
|
|
inviteHashes, err := SendUserInvites(ctx, claims, test.MasterDB, resetUrl, notify, SendUserInvitesRequest{
|
2019-08-02 15:03:32 -08:00
|
|
|
UserID: u.ID,
|
|
|
|
AccountID: a.ID,
|
|
|
|
Emails: inviteEmails,
|
|
|
|
Roles: []user_account.UserAccountRole{user_account.UserAccountRole_User},
|
|
|
|
TTL: ttl,
|
|
|
|
}, secretKey, now)
|
|
|
|
if err != nil {
|
|
|
|
t.Log("\t\tGot :", err)
|
|
|
|
t.Fatalf("\t%s\tInviteUsers failed.", tests.Failed)
|
|
|
|
} else if len(inviteHashes) != len(inviteEmails) {
|
|
|
|
t.Logf("\t\tGot : %+v", len(inviteHashes))
|
|
|
|
t.Logf("\t\tWant: %+v", len(inviteEmails))
|
|
|
|
t.Fatalf("\t%s\tInviteUsers failed.", tests.Failed)
|
|
|
|
}
|
|
|
|
t.Logf("\t%s\tInviteUsers ok.", tests.Success)
|
|
|
|
|
|
|
|
// Ensure validation is working by trying ResetConfirm with an empty request.
|
|
|
|
{
|
2019-08-05 18:47:42 -08:00
|
|
|
expectedErr := errors.New("Key: 'AcceptInviteUserRequest.invite_hash' Error:Field validation for 'invite_hash' failed on the 'required' tag\n" +
|
|
|
|
"Key: 'AcceptInviteUserRequest.email' Error:Field validation for 'email' failed on the 'required' tag\n" +
|
|
|
|
"Key: 'AcceptInviteUserRequest.first_name' Error:Field validation for 'first_name' failed on the 'required' tag\n" +
|
|
|
|
"Key: 'AcceptInviteUserRequest.last_name' Error:Field validation for 'last_name' failed on the 'required' tag\n" +
|
|
|
|
"Key: 'AcceptInviteUserRequest.password' Error:Field validation for 'password' failed on the 'required' tag\n" +
|
|
|
|
"Key: 'AcceptInviteUserRequest.password_confirm' Error:Field validation for 'password_confirm' failed on the 'required' tag")
|
|
|
|
_, err = AcceptInviteUser(ctx, test.MasterDB, AcceptInviteUserRequest{}, secretKey, now)
|
2019-08-02 15:03:32 -08:00
|
|
|
if err == nil {
|
|
|
|
t.Logf("\t\tWant: %+v", expectedErr)
|
|
|
|
t.Fatalf("\t%s\tResetConfirm failed.", tests.Failed)
|
|
|
|
}
|
|
|
|
|
|
|
|
errStr := strings.Replace(err.Error(), "{{", "", -1)
|
|
|
|
errStr = strings.Replace(errStr, "}}", "", -1)
|
|
|
|
|
|
|
|
if errStr != expectedErr.Error() {
|
|
|
|
t.Logf("\t\tGot : %+v", errStr)
|
|
|
|
t.Logf("\t\tWant: %+v", expectedErr)
|
|
|
|
t.Fatalf("\t%s\tResetConfirm Validation failed.", tests.Failed)
|
|
|
|
}
|
|
|
|
t.Logf("\t%s\tResetConfirm Validation ok.", tests.Success)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the TTL is enforced.
|
|
|
|
{
|
|
|
|
newPass := uuid.NewRandom().String()
|
2019-08-05 18:47:42 -08:00
|
|
|
_, err = AcceptInviteUser(ctx, test.MasterDB, AcceptInviteUserRequest{
|
2019-08-02 15:03:32 -08:00
|
|
|
InviteHash: inviteHashes[0],
|
2019-08-05 17:12:28 -08:00
|
|
|
Email: inviteEmails[0],
|
2019-08-02 15:03:32 -08:00
|
|
|
FirstName: "Foo",
|
|
|
|
LastName: "Bar",
|
|
|
|
Password: newPass,
|
|
|
|
PasswordConfirm: newPass,
|
|
|
|
}, secretKey, now.UTC().Add(ttl*2))
|
|
|
|
if errors.Cause(err) != ErrInviteExpired {
|
|
|
|
t.Logf("\t\tGot : %+v", errors.Cause(err))
|
|
|
|
t.Logf("\t\tWant: %+v", ErrInviteExpired)
|
|
|
|
t.Fatalf("\t%s\tInviteAccept enforce TTL failed.", tests.Failed)
|
|
|
|
}
|
|
|
|
t.Logf("\t%s\tInviteAccept enforce TTL ok.", tests.Success)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assuming we have received the email and clicked the link, we now can ensure accept works.
|
2019-08-05 17:12:28 -08:00
|
|
|
for idx, inviteHash := range inviteHashes {
|
2019-08-05 17:23:56 -08:00
|
|
|
|
2019-08-02 15:03:32 -08:00
|
|
|
newPass := uuid.NewRandom().String()
|
2019-08-05 18:47:42 -08:00
|
|
|
hash, err := AcceptInviteUser(ctx, test.MasterDB, AcceptInviteUserRequest{
|
2019-08-02 15:03:32 -08:00
|
|
|
InviteHash: inviteHash,
|
2019-08-05 17:12:28 -08:00
|
|
|
Email: inviteEmails[idx],
|
2019-08-02 15:03:32 -08:00
|
|
|
FirstName: "Foo",
|
|
|
|
LastName: "Bar",
|
|
|
|
Password: newPass,
|
|
|
|
PasswordConfirm: newPass,
|
|
|
|
}, secretKey, now)
|
|
|
|
if err != nil {
|
|
|
|
t.Log("\t\tGot :", err)
|
|
|
|
t.Fatalf("\t%s\tInviteAccept failed.", tests.Failed)
|
|
|
|
}
|
2019-08-05 17:12:28 -08:00
|
|
|
|
|
|
|
// Validate the result.
|
2019-08-05 17:23:56 -08:00
|
|
|
var res = struct {
|
|
|
|
UserID string `validate:"required,uuid"`
|
|
|
|
AccountID string `validate:"required,uuid"`
|
|
|
|
}{
|
|
|
|
UserID: hash.UserID,
|
|
|
|
AccountID: hash.AccountID,
|
|
|
|
}
|
|
|
|
err = webcontext.Validator().StructCtx(ctx, res)
|
2019-08-05 17:12:28 -08:00
|
|
|
if err != nil {
|
|
|
|
t.Log("\t\tGot :", err)
|
|
|
|
t.Fatalf("\t%s\tInviteAccept failed.", tests.Failed)
|
|
|
|
}
|
|
|
|
|
2019-08-02 15:03:32 -08:00
|
|
|
t.Logf("\t%s\tInviteAccept ok.", tests.Success)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the reset hash does not work after its used.
|
|
|
|
{
|
|
|
|
newPass := uuid.NewRandom().String()
|
2019-08-05 18:47:42 -08:00
|
|
|
_, err = AcceptInviteUser(ctx, test.MasterDB, AcceptInviteUserRequest{
|
2019-08-02 15:03:32 -08:00
|
|
|
InviteHash: inviteHashes[0],
|
2019-08-05 17:12:28 -08:00
|
|
|
Email: inviteEmails[0],
|
2019-08-02 15:03:32 -08:00
|
|
|
FirstName: "Foo",
|
|
|
|
LastName: "Bar",
|
|
|
|
Password: newPass,
|
|
|
|
PasswordConfirm: newPass,
|
|
|
|
}, secretKey, now)
|
2019-08-05 17:12:28 -08:00
|
|
|
if errors.Cause(err) != ErrUserAccountActive {
|
2019-08-02 15:03:32 -08:00
|
|
|
t.Logf("\t\tGot : %+v", errors.Cause(err))
|
2019-08-05 18:47:42 -08:00
|
|
|
t.Logf("\t\tWant: %+v", ErrUserAccountActive)
|
2019-08-02 15:03:32 -08:00
|
|
|
t.Fatalf("\t%s\tInviteAccept verify reuse failed.", tests.Failed)
|
|
|
|
}
|
|
|
|
t.Logf("\t%s\tInviteAccept verify reuse disabled ok.", tests.Success)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|