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 }