github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/sys/it/impl_resetpassword_test.go (about)

     1  /*
     2   * Copyright (c) 2020-present unTill Pro, Ltd.
     3   */
     4  
     5  package sys_it
     6  
     7  import (
     8  	"fmt"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/voedger/voedger/pkg/irates"
    13  	"github.com/voedger/voedger/pkg/istructs"
    14  	"github.com/voedger/voedger/pkg/istructsmem"
    15  	"github.com/voedger/voedger/pkg/sys/verifier"
    16  	coreutils "github.com/voedger/voedger/pkg/utils"
    17  	it "github.com/voedger/voedger/pkg/vit"
    18  )
    19  
    20  func TestBasicUsage_ResetPassword(t *testing.T) {
    21  	vit := it.NewVIT(t, &it.SharedConfig_App1)
    22  	defer vit.TearDown()
    23  	loginName := vit.NextName() + "@123.com"
    24  	login := vit.SignUp(loginName, "1", istructs.AppQName_test1_app1)
    25  	vit.SignIn(login)
    26  
    27  	profileWSID := istructs.WSID(0)
    28  	token, code := InitiateEmailVerificationFunc(vit, func() *coreutils.FuncResponse {
    29  		body := fmt.Sprintf(`{"args":{"AppName":"%s","Email":"%s"},"elements":[{"fields":["VerificationToken","ProfileWSID"]}]}`, istructs.AppQName_test1_app1, login.Name)
    30  		resp := vit.PostApp(istructs.AppQName_sys_registry, login.PseudoProfileWSID, "q.registry.InitiateResetPasswordByEmail", body) // null auth policy
    31  
    32  		// here in test we're actually know the profileWSID. But in the realife we don't. So let's show how it should be got
    33  		profileWSID = istructs.WSID(resp.SectionRow()[1].(float64))
    34  		return resp
    35  	})
    36  
    37  	// sys/registry/pseudo-profile-wsid/q.registry.IssueVerifiedValueTokenForResetPassword
    38  	body := fmt.Sprintf(`{"args":{"VerificationToken":"%s","VerificationCode":"%s","ProfileWSID":%d,"AppName":"%s"},"elements":[{"fields":["VerifiedValueToken"]}]}`, token, code, profileWSID,
    39  		istructs.AppQName_test1_app1)
    40  	resp := vit.PostApp(istructs.AppQName_sys_registry, login.PseudoProfileWSID, "q.registry.IssueVerifiedValueTokenForResetPassword", body) // null auth policy
    41  	verifiedValueToken := resp.SectionRow()[0].(string)
    42  
    43  	// sys/registry/pseudo-profile-wsid/c.registry.ResetPasswordByEmail
    44  	newPwd := "newPwd"
    45  	body = fmt.Sprintf(`{"args":{"AppName":"%s"},"unloggedArgs":{"Email":"%s","NewPwd":"%s"}}`, istructs.AppQName_test1_app1, verifiedValueToken, newPwd)
    46  	vit.PostApp(istructs.AppQName_sys_registry, login.PseudoProfileWSID, "c.registry.ResetPasswordByEmail", body) // null auth policy
    47  
    48  	// expect no errors on login with new password
    49  	login.Pwd = newPwd
    50  	vit.SignIn(login)
    51  }
    52  
    53  func TestIntiateResetPasswordErrors(t *testing.T) {
    54  	vit := it.NewVIT(t, &it.SharedConfig_App1)
    55  	defer vit.TearDown()
    56  	prn := vit.GetPrincipal(istructs.AppQName_test1_app1, it.TestEmail)
    57  
    58  	t.Run("400 bad request on bad appQName", func(t *testing.T) {
    59  		body := fmt.Sprintf(`{"args":{"AppName":"wrong app","Email":"%s"},"elements":[{"fields":["VerificationToken","ProfileWSID"]}]}`, prn.Name)
    60  		vit.PostApp(istructs.AppQName_sys_registry, prn.PseudoProfileWSID, "q.registry.InitiateResetPasswordByEmail", body, coreutils.Expect400()).Println()
    61  	})
    62  
    63  	// note: test "called in non-AppWS" is senceless because now func is taken from the workspace -> 400 bad request + "func does not exist in the workspace" anyway
    64  
    65  	t.Run("400 bad request on an unknown login", func(t *testing.T) {
    66  		body := fmt.Sprintf(`{"args":{"AppName":"%s","Email":"unknown"},"elements":[{"fields":["VerificationToken","ProfileWSID"]}]}`, istructs.AppQName_test1_app1)
    67  		vit.PostApp(istructs.AppQName_sys_registry, coreutils.GetPseudoWSID(istructs.NullWSID, "unknown", istructs.MainClusterID), "q.registry.InitiateResetPasswordByEmail", body, coreutils.Expect400()).Println()
    68  	})
    69  }
    70  
    71  func TestIssueResetPasswordTokenErrors(t *testing.T) {
    72  	vit := it.NewVIT(t, &it.SharedConfig_App1)
    73  	defer vit.TearDown()
    74  	prn := vit.GetPrincipal(istructs.AppQName_test1_app1, it.TestEmail)
    75  
    76  	t.Run("400 bad request on an unknown login", func(t *testing.T) {
    77  		unknownLogin := "unknown"
    78  		pseudoWSID := coreutils.GetPseudoWSID(istructs.NullWSID, unknownLogin, istructs.MainClusterID)
    79  		body := fmt.Sprintf(`{"args":{"AppName":"%s","Email":"%s"},"elements":[{"fields":["VerificationToken","ProfileWSID"]}]}`, istructs.AppQName_test1_app1, unknownLogin)
    80  		vit.PostApp(istructs.AppQName_sys_registry, pseudoWSID, "q.registry.InitiateResetPasswordByEmail", body, coreutils.Expect400()).Println()
    81  	})
    82  
    83  	profileWSID := istructs.WSID(0)
    84  	token, code := InitiateEmailVerificationFunc(vit, func() *coreutils.FuncResponse {
    85  		body := fmt.Sprintf(`{"args":{"AppName":"%s","Email":"%s"},"elements":[{"fields":["VerificationToken","ProfileWSID"]}]}`, istructs.AppQName_test1_app1, prn.Name)
    86  		resp := vit.PostApp(istructs.AppQName_sys_registry, prn.PseudoProfileWSID, "q.registry.InitiateResetPasswordByEmail", body)
    87  		profileWSID = istructs.WSID(resp.SectionRow()[1].(float64))
    88  		return resp
    89  	})
    90  
    91  	t.Run("400 bad request on bad appQName", func(t *testing.T) {
    92  		body := fmt.Sprintf(`{"args":{"VerificationToken":"%s","VerificationCode":"%s","ProfileWSID":%d,"AppName":"wrong app"},"elements":[{"fields":["VerifiedValueToken"]}]}`,
    93  			token, code, profileWSID)
    94  		// note: was at profileWSID. It does not works since https://github.com/voedger/voedger/issues/1311
    95  		// because sys/registry:profileWSID workspace is not initialized -> call at pseudoProfileWSID
    96  		vit.PostApp(istructs.AppQName_sys_registry, prn.PseudoProfileWSID, "q.registry.IssueVerifiedValueTokenForResetPassword", body, coreutils.Expect400()).Println()
    97  	})
    98  }
    99  
   100  func TestResetPasswordLimits(t *testing.T) {
   101  	t.Skip("wait for https://github.com/voedger/voedger/issues/2090")
   102  	vit := it.NewVIT(t, &it.SharedConfig_App1)
   103  	defer vit.TearDown()
   104  	prn := vit.GetPrincipal(istructs.AppQName_test1_app1, it.TestEmail)
   105  	var (
   106  		profileWSID istructs.WSID
   107  		token       string
   108  		code        string
   109  	)
   110  
   111  	t.Run("InitiateResetPasswordByEmail", func(t *testing.T) {
   112  		// mock rate limits
   113  		rateLimitName_InitiateEmailVerification := istructsmem.GetFunctionRateLimitName(verifier.QNameQueryInitiateEmailVerification, istructs.RateLimitKind_byWorkspace)
   114  		vit.MockBuckets(istructs.AppQName_test1_app1, rateLimitName_InitiateEmailVerification, irates.BucketState{
   115  			Period:             time.Minute,
   116  			MaxTokensPerPeriod: 1,
   117  		})
   118  
   119  		// 1st call -> ok, do not store the code
   120  		_, _ = InitiateEmailVerificationFunc(vit, func() *coreutils.FuncResponse {
   121  			body := fmt.Sprintf(`{"args":{"AppName":"%s","Email":"%s"},"elements":[{"fields":["VerificationToken","ProfileWSID"]}]}`, istructs.AppQName_test1_app1, prn.Name)
   122  			return vit.PostApp(istructs.AppQName_sys_registry, prn.PseudoProfileWSID, "q.registry.InitiateResetPasswordByEmail", body)
   123  		})
   124  
   125  		// 2nd call -> limit exceeded
   126  		body := fmt.Sprintf(`{"args":{"AppName":"%s","Email":"%s"},"elements":[{"fields":["VerificationToken","ProfileWSID"]}]}`, istructs.AppQName_test1_app1, prn.Name)
   127  		vit.PostApp(istructs.AppQName_sys_registry, prn.PseudoProfileWSID, "q.registry.InitiateResetPasswordByEmail", body, coreutils.Expect429())
   128  
   129  		// proceed to the next minute to restore rates
   130  		vit.TimeAdd(time.Minute)
   131  
   132  		// call again to get actual token and code
   133  		token, code = InitiateEmailVerificationFunc(vit, func() *coreutils.FuncResponse {
   134  			body := fmt.Sprintf(`{"args":{"AppName":"%s","Email":"%s"},"elements":[{"fields":["VerificationToken","ProfileWSID"]}]}`, istructs.AppQName_test1_app1, prn.Name)
   135  			resp := vit.PostApp(istructs.AppQName_sys_registry, prn.PseudoProfileWSID, "q.registry.InitiateResetPasswordByEmail", body)
   136  
   137  			// here in test we're actually know the profileWSID. But in the realife we don't. So let's show how it should be got:
   138  			// q.sys.InitiateResetPasswordByEmail returns it
   139  			profileWSID = istructs.WSID(resp.SectionRow()[1].(float64))
   140  			return resp
   141  		})
   142  	})
   143  
   144  	t.Run("IssueVerifiedValueTokenForResetPassword", func(t *testing.T) {
   145  		// mock rate limits
   146  		rateLimitName_IssueVerifiedValueToken := istructsmem.GetFunctionRateLimitName(verifier.QNameQueryIssueVerifiedValueToken, istructs.RateLimitKind_byWorkspace)
   147  		vit.MockBuckets(istructs.AppQName_test1_app1, rateLimitName_IssueVerifiedValueToken, irates.BucketState{
   148  			Period:             time.Minute,
   149  			MaxTokensPerPeriod: 1,
   150  		})
   151  
   152  		wrongCode := code + "1"
   153  		wrongCodeBody := fmt.Sprintf(`{"args":{"VerificationToken":"%s","VerificationCode":"%s","ProfileWSID":%d,"AppName":"%s"},"elements":[{"fields":["VerifiedValueToken"]}]}`, token, wrongCode, profileWSID,
   154  			istructs.AppQName_test1_app1)
   155  
   156  		// 1st call with wrong code -> 400 bad request
   157  		vit.PostApp(istructs.AppQName_sys_registry, prn.PseudoProfileWSID, "q.registry.IssueVerifiedValueTokenForResetPassword", wrongCodeBody, coreutils.Expect400())
   158  
   159  		// 2nd call with wrong code -> mocked limit exceeded, 429 Too many reuqets
   160  		vit.PostApp(istructs.AppQName_sys_registry, prn.PseudoProfileWSID, "q.registry.IssueVerifiedValueTokenForResetPassword", wrongCodeBody, coreutils.Expect429())
   161  
   162  		// next calls with correct code -> 429 anyway
   163  		goodCodeBody := fmt.Sprintf(`{"args":{"VerificationToken":"%s","VerificationCode":"%s","ProfileWSID":%d,"AppName":"%s"},"elements":[{"fields":["VerifiedValueToken"]}]}`, token, code, profileWSID,
   164  			istructs.AppQName_test1_app1)
   165  		vit.PostApp(istructs.AppQName_sys_registry, prn.PseudoProfileWSID, "q.registry.IssueVerifiedValueTokenForResetPassword", goodCodeBody, coreutils.Expect429())
   166  
   167  		// proceed to the next minute to restore rates
   168  		vit.TimeAdd(time.Minute)
   169  
   170  		// expect no errors now
   171  		vit.PostApp(istructs.AppQName_sys_registry, prn.PseudoProfileWSID, "q.registry.IssueVerifiedValueTokenForResetPassword", goodCodeBody)
   172  
   173  	})
   174  }