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

     1  /*
     2   * Copyright (c) 2022-present unTill Pro, Ltd.
     3   */
     4  
     5  package sys_it
     6  
     7  import (
     8  	"fmt"
     9  	"log"
    10  	"regexp"
    11  	"testing"
    12  
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/voedger/voedger/pkg/istructs"
    16  	payloads "github.com/voedger/voedger/pkg/itokens-payloads"
    17  	"github.com/voedger/voedger/pkg/sys/verifier"
    18  	coreutils "github.com/voedger/voedger/pkg/utils"
    19  	it "github.com/voedger/voedger/pkg/vit"
    20  )
    21  
    22  func TestBasicUsage_Verifier(t *testing.T) {
    23  	require := require.New(t)
    24  	vit := it.NewVIT(t, &it.SharedConfig_App1)
    25  	defer vit.TearDown()
    26  
    27  	userPrincipal := vit.GetPrincipal(istructs.AppQName_test1_app1, it.TestEmail)
    28  
    29  	verificationToken := ""
    30  	verificationCode := ""
    31  	t.Run("initiate verification and get the verification token", func(t *testing.T) {
    32  		body := fmt.Sprintf(`
    33  			{
    34  				"args":{
    35  					"Entity":"%s",
    36  					"Field":"EmailField",
    37  					"Email":"%s",
    38  					"TargetWSID": %d,
    39  					"Language":"fr"
    40  				},
    41  				"elements":[{"fields":["VerificationToken"]}]
    42  			}
    43  		`, it.QNameApp1_TestEmailVerificationDoc, it.TestEmail, userPrincipal.ProfileWSID) // targetWSID - is the workspace we're going to use the verified value at
    44  		// call q.sys.InitiateEmailVerification at user profile to avoid guests
    45  		// call in target app
    46  		resp := vit.PostProfile(userPrincipal, "q.sys.InitiateEmailVerification", body)
    47  		email := vit.CaptureEmail()
    48  		require.Equal([]string{it.TestEmail}, email.To)
    49  		require.Equal("Votre code de vérification", email.Subject)
    50  		require.Equal(it.TestSMTPCfg.GetFrom(), email.From)
    51  		require.Empty(email.CC)
    52  		require.Empty(email.BCC)
    53  		r := regexp.MustCompile(`(?P<code>\d{6})`)
    54  		matches := r.FindStringSubmatch(email.Body)
    55  		verificationCode = matches[0]
    56  		verificationToken = resp.SectionRow()[0].(string)
    57  		log.Println(verificationCode)
    58  		match, _ := regexp.MatchString(`Voici votre code de vérification`, email.Body)
    59  		require.True(match)
    60  	})
    61  
    62  	verifiedValueToken := ""
    63  	t.Run("get the verified value token using the verification token", func(t *testing.T) {
    64  		body := fmt.Sprintf(`
    65  		{
    66  			"args":{
    67  				"VerificationToken":"%s",
    68  				"VerificationCode":"%s"
    69  			},
    70  			"elements":[{"fields":["VerifiedValueToken"]}]
    71  		}
    72  		`, verificationToken, verificationCode)
    73  		resp := vit.PostProfile(userPrincipal, "q.sys.IssueVerifiedValueToken", body)
    74  		verifiedValueToken = resp.SectionRow()[0].(string)
    75  	})
    76  
    77  	log.Println(verifiedValueToken)
    78  
    79  	t.Run("decode the verified value token and check the verified value", func(t *testing.T) {
    80  		vvp := payloads.VerifiedValuePayload{}
    81  		as, err := vit.AppStructs(istructs.AppQName_test1_app1)
    82  		require.NoError(err)
    83  		gp, err := as.AppTokens().ValidateToken(verifiedValueToken, &vvp)
    84  		require.NoError(err)
    85  		require.Equal(istructs.AppQName_test1_app1, gp.AppQName)
    86  		require.Equal(verifier.VerifiedValueTokenDuration, gp.Duration)
    87  		require.Equal(vit.Now(), gp.IssuedAt)
    88  		require.Equal(it.QNameApp1_TestEmailVerificationDoc, vvp.Entity)
    89  		require.Equal("EmailField", vvp.Field)
    90  		require.Equal(it.TestEmail, vvp.Value)
    91  	})
    92  
    93  	t.Run("create a doc providing the token as the value for the verifiable field", func(t *testing.T) {
    94  		body := fmt.Sprintf(`
    95  			{
    96  				"cuds": [
    97  					{
    98  						"fields": {
    99  							"sys.ID": 1,
   100  							"sys.QName": "%s",
   101  							"EmailField": "%s"
   102  						}
   103  					}
   104  				]
   105  			}`, it.QNameApp1_TestEmailVerificationDoc, verifiedValueToken)
   106  		ws := vit.WS(istructs.AppQName_test1_app1, "test_ws")
   107  		vit.PostWS(ws, "c.sys.CUD", body)
   108  	})
   109  
   110  	t.Run("bug: one token could be used in any wsid", func(t *testing.T) {
   111  		body := fmt.Sprintf(`{"cuds": [{"fields": {"sys.ID": 1,"sys.QName": "%s","EmailField": "%s"}}]}`, it.QNameApp1_TestEmailVerificationDoc, verifiedValueToken)
   112  		ws2 := vit.CreateWorkspace(it.SimpleWSParams("testws"+vit.NextName()), userPrincipal)
   113  		vit.PostWS(ws2, "c.sys.CUD", body)
   114  	})
   115  
   116  	t.Run("read the actual verified field value - it should be the value decoded from the token", func(t *testing.T) {
   117  		body := fmt.Sprintf(`{"args":{"Schema":"%s"},"elements":[{"fields": ["EmailField"]}]}`, it.QNameApp1_TestEmailVerificationDoc)
   118  		ws := vit.WS(istructs.AppQName_test1_app1, "test_ws")
   119  		resp := vit.PostWS(ws, "q.sys.Collection", body)
   120  		require.Equal(it.TestEmail, resp.SectionRow()[0])
   121  	})
   122  }
   123  
   124  func TestVerifierErrors(t *testing.T) {
   125  	vit := it.NewVIT(t, &it.SharedConfig_App1)
   126  	defer vit.TearDown()
   127  
   128  	// funcs should be called in the user profile
   129  	userPrincipal := vit.GetPrincipal(istructs.AppQName_test1_app1, it.TestEmail)
   130  	// ws := vit.DummyWS(istructs.AppQName_test1_app1, userPrincipal.ProfileWSID)
   131  
   132  	verificationToken, verificationCode := InitiateEmailVerification(vit, userPrincipal, it.QNameApp1_TestEmailVerificationDoc,
   133  		"EmailField", it.TestEmail, userPrincipal.ProfileWSID, coreutils.WithAuthorizeBy(userPrincipal.Token))
   134  
   135  	t.Run("error 400 on set the raw value instead of verified value token for the verified field", func(t *testing.T) {
   136  		body := fmt.Sprintf(`{"cuds": [{"fields": {"sys.ID": 1,"sys.QName": "%s","EmailField": "%s"}}]}`, it.QNameApp1_TestEmailVerificationDoc, it.TestEmail)
   137  		vit.PostProfile(userPrincipal, "c.sys.CUD", body, coreutils.Expect400()).Println()
   138  	})
   139  
   140  	emailVerifiedValueToken := ""
   141  	t.Run("error 400 on different verification algorithm", func(t *testing.T) {
   142  		// issue a token for email field
   143  		body := fmt.Sprintf(`{"args":{"VerificationToken":"%s","VerificationCode":"%s"},"elements":[{"fields":["VerifiedValueToken"]}]}`, verificationToken, verificationCode)
   144  		resp := vit.PostProfile(userPrincipal, "q.sys.IssueVerifiedValueToken", body)
   145  		emailVerifiedValueToken = resp.SectionRow()[0].(string)
   146  
   147  		// use the email token for the phone field
   148  		body = fmt.Sprintf(`{"cuds": [{"fields": {"sys.ID": 1,"sys.QName": "%s","PhoneField": "%s"}}]}`, it.QNameApp1_TestEmailVerificationDoc, emailVerifiedValueToken)
   149  		vit.PostProfile(userPrincipal, "c.sys.CUD", body, coreutils.Expect400()).Println()
   150  	})
   151  
   152  	t.Run("error 400 on wrong app", func(t *testing.T) {
   153  		body := fmt.Sprintf(`{"cuds": [{"fields": {"sys.ID": 1,"sys.QName": "%s","EmailField": "%s"}}]}`, it.QNameApp1_TestEmailVerificationDoc, emailVerifiedValueToken)
   154  		userPrincipal := vit.GetPrincipal(istructs.AppQName_test1_app2, "login")
   155  		// wsApp2 := vit.DummyWS(istructs.AppQName_test1_app2, userPrincipal.ProfileWSID)
   156  		vit.PostProfile(userPrincipal, "c.sys.CUD", body, coreutils.Expect400()).Println()
   157  	})
   158  
   159  	t.Run("error 400 issue token for one WSID but use it in different WSID", func(t *testing.T) {
   160  		t.Skip("WSID check is not implemented in istructsmem yet")
   161  		body := fmt.Sprintf(`{"args":{"VerificationToken":"%s","VerificationCode":"%s"},"elements":[{"fields":["VerifiedValueToken"]}]}`, verificationToken, verificationCode)
   162  		resp := vit.PostProfile(userPrincipal, "q.sys.IssueVerifiedValueToken", body)
   163  		emailVerifiedValueToken = resp.SectionRow()[0].(string)
   164  
   165  		body = fmt.Sprintf(`{"cuds": [{"fields": {"sys.ID": 1,"sys.QName": "%s","EmailField": "%s"}}]}`, it.QNameApp1_TestEmailVerificationDoc, emailVerifiedValueToken)
   166  		// dws := vit.DummyWS(istructs.AppQName_test1_app1, ws.WSID+1)
   167  		userPrincipal2 := vit.GetPrincipal(istructs.AppQName_test1_app2, "login")
   168  		vit.PostProfile(userPrincipal2, "c.sys.CUD", body, coreutils.Expect500()).Println()
   169  	})
   170  }
   171  
   172  // func TestVerificationLimits(t *testing.T) {
   173  // 	vit := it.NewVIT(t, &it.SharedConfig_App1)
   174  // 	defer vit.TearDown()
   175  
   176  // 	rateLimitName_InitiateEmailVerification := istructsmem.GetFunctionRateLimitName(verifier.QNameQueryInitiateEmailVerification, istructs.RateLimitKind_byWorkspace)
   177  
   178  // 	vit.MockBuckets(istructs.AppQName_test1_app1, rateLimitName_InitiateEmailVerification, irates.BucketState{
   179  // 		Period:             time.Minute,
   180  // 		MaxTokensPerPeriod: 1,
   181  // 	})
   182  
   183  // 	// funcs should be called in the user profile
   184  // 	userPrincipal := vit.GetPrincipal(istructs.AppQName_test1_app1, it.TestEmail)
   185  // 	var token, code string
   186  
   187  // 	testWSID := istructs.WSID(1)
   188  
   189  // 	t.Run("q.sys.InitiateEmailVerification limits", func(t *testing.T) {
   190  
   191  // 		// first q.sys.InitiateEmailVerifications are ok
   192  // 		InitiateEmailVerification(vit, userPrincipal, it.QNameApp1_TestEmailVerificationDoc, "EmailField", it.TestEmail, testWSID, coreutils.WithAuthorizeBy(userPrincipal.Token))
   193  
   194  // 		// 2nd exceeds the limit -> 429 Too many requests
   195  // 		body := fmt.Sprintf(`{"args":{"Entity":"%s","Field":"%s","Email":"%s"},"elements":[{"fields":["VerificationToken"]}]}`, it.QNameApp1_TestEmailVerificationDoc, "EmailField", it.TestEmail)
   196  // 		vit.PostProfile(userPrincipal, "q.sys.InitiateEmailVerification", body, coreutils.Expect429())
   197  
   198  // 		// still able to send to call in antoher profile because the limit is per-profile
   199  // 		otherPrn := vit.GetPrincipal(istructs.AppQName_test1_app1, it.TestEmail2)
   200  // 		InitiateEmailVerification(vit, otherPrn, it.QNameApp1_TestEmailVerificationDoc, "EmailField", it.TestEmail2, testWSID, coreutils.WithAuthorizeBy(otherPrn.Token))
   201  
   202  // 		// proceed to the next minute -> limits will be reset
   203  // 		vit.TimeAdd(time.Minute)
   204  
   205  // 		// expect no errors
   206  // 		token, code = InitiateEmailVerification(vit, userPrincipal, it.QNameApp1_TestEmailVerificationDoc, "EmailField", it.TestEmail, testWSID, coreutils.WithAuthorizeBy(userPrincipal.Token))
   207  // 	})
   208  
   209  // 	t.Run("q.sys.IssueVerifiedValueToken limits", func(t *testing.T) {
   210  // 		bodyWrongCode := fmt.Sprintf(`{"args":{"VerificationToken":"%s","VerificationCode":"%s"},"elements":[{"fields":["VerifiedValueToken"]}]}`, token, code+"1")
   211  // 		bodyGoodCode := fmt.Sprintf(`{"args":{"VerificationToken":"%s","VerificationCode":"%s"},"elements":[{"fields":["VerifiedValueToken"]}]}`, token, code)
   212  
   213  // 		for i := 0; i < int(verifier.IssueVerifiedValueToken_MaxAllowed); i++ {
   214  // 			// first 3 calls per hour with a wrong code are allowed, just "code wrong" error is returned
   215  // 			vit.PostProfile(userPrincipal, "q.sys.IssueVerifiedValueToken", bodyWrongCode, coreutils.Expect400())
   216  // 		}
   217  
   218  // 		// 4th code check with a good code is failed as well because the function call limit is exceeded
   219  // 		vit.PostProfile(userPrincipal, "q.sys.IssueVerifiedValueToken", bodyGoodCode, coreutils.Expect429())
   220  
   221  // 		// proceed to the next hour to reset limits
   222  // 		vit.TimeAdd(verifier.IssueVerifiedValueToken_Period)
   223  
   224  // 		// regenerate token and code because previous ones are expired already
   225  // 		token, code = InitiateEmailVerification(vit, userPrincipal, it.QNameApp1_TestEmailVerificationDoc, "EmailField", it.TestEmail, testWSID, coreutils.WithAuthorizeBy(userPrincipal.Token))
   226  // 		bodyGoodCode = fmt.Sprintf(`{"args":{"VerificationToken":"%s","VerificationCode":"%s"},"elements":[{"fields":["VerifiedValueToken"]}]}`, token, code)
   227  
   228  // 		// now check that limits are restored and that limits are reset on successful code verification
   229  // 		for i := 0; i < int(verifier.IssueVerifiedValueToken_MaxAllowed+1); i++ {
   230  // 			vit.PostProfile(userPrincipal, "q.sys.IssueVerifiedValueToken", bodyGoodCode)
   231  // 		}
   232  // 	})
   233  // }
   234  
   235  func TestForRegistry(t *testing.T) {
   236  	vit := it.NewVIT(t, &it.SharedConfig_App1)
   237  	defer vit.TearDown()
   238  
   239  	// funcs should be called in the user profile
   240  	userPrincipal := vit.GetPrincipal(istructs.AppQName_test1_app1, it.TestEmail)
   241  
   242  	verificationToken, verificationCode := InitiateEmailVerificationFunc(vit, func() *coreutils.FuncResponse {
   243  		body := fmt.Sprintf(`{"args":{"Entity":"%s","Field":"EmailField","Email":"%s","TargetWSID":%d,"ForRegistry":true},"elements":[{"fields":["VerificationToken"]}]}`,
   244  			it.QNameApp1_TestEmailVerificationDoc, it.TestEmail, userPrincipal.ProfileWSID)
   245  		resp := vit.PostProfile(userPrincipal, "q.sys.InitiateEmailVerification", body)
   246  		return resp
   247  	})
   248  
   249  	body := fmt.Sprintf(`{"args":{"VerificationToken":"%s","VerificationCode":"%s","ForRegistry":true},"elements":[{"fields":["VerifiedValueToken"]}]}`, verificationToken, verificationCode)
   250  	resp := vit.PostProfile(userPrincipal, "q.sys.IssueVerifiedValueToken", body)
   251  	verifiedValueToken := resp.SectionRow()[0].(string)
   252  
   253  	// just expect no errors on validate token for sys/registry
   254  	vvp := payloads.VerifiedValuePayload{}
   255  	as, err := vit.AppStructs(istructs.AppQName_sys_registry)
   256  	require.NoError(t, err)
   257  	_, err = as.AppTokens().ValidateToken(verifiedValueToken, &vvp)
   258  	require.NoError(t, err)
   259  }