Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OTP voice support #420

Merged
merged 4 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ For rate limiting information, please confer to the [API Rate Limits](#api-rate-

### OTP Authentication

Send a user a one-time password (OTP) using your preferred delivery method (_email / SMS_). An email address or phone number must be provided accordingly.
Send a user a one-time password (OTP) using your preferred delivery method (_email / SMS / Voice call_). An email address or phone number must be provided accordingly.

The user can either `sign up`, `sign in` or `sign up or in`

Expand Down Expand Up @@ -1594,7 +1594,7 @@ assert.EqualValues(t, updateJWTWithCustomClaimsResponse, res)
### Utils for your end to end (e2e) tests and integration tests

To ease your e2e tests, we exposed dedicated management methods,
that way, you don't need to use 3rd party messaging services in order to receive sign-in/up Emails or SMS, and avoid the need of parsing the code and token from them.
that way, you don't need to use 3rd party messaging services in order to receive sign-in/up Emails, SMS or Voice call, and avoid the need of parsing the code and token from them.

```go
// User for test can be created, this user will be able to generate code/link without
Expand Down
11 changes: 11 additions & 0 deletions descope/internal/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,15 @@ func (*authenticationsBase) verifyDeliveryMethod(method descope.DeliveryMethod,
if !phoneRegex.MatchString(user.Phone) {
return utils.NewInvalidArgumentError(varName)
}
case descope.MethodVoice:
if len(user.Phone) == 0 {
user.Phone = loginID
} else {
varName = "user.Phone"
}
if !phoneRegex.MatchString(user.Phone) {
return utils.NewInvalidArgumentError(varName)
}
case descope.MethodWhatsApp:
if len(user.Phone) == 0 {
user.Phone = loginID
Expand Down Expand Up @@ -986,6 +995,8 @@ func getMaskedValue(method descope.DeliveryMethod) Masked {
switch method {
case descope.MethodSMS:
fallthrough
case descope.MethodVoice:
fallthrough
case descope.MethodWhatsApp:
m = &MaskedPhoneRes{}
case descope.MethodEmail:
Expand Down
7 changes: 7 additions & 0 deletions descope/internal/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ func TestVerifyDeliveryMethod(t *testing.T) {
assert.ErrorIs(t, err, descope.ErrInvalidArguments)
err = a.verifyDeliveryMethod(descope.MethodSMS, "[email protected]", &descope.User{})
assert.ErrorIs(t, err, descope.ErrInvalidArguments)
err = a.verifyDeliveryMethod(descope.MethodVoice, "[email protected]", &descope.User{})
assert.ErrorIs(t, err, descope.ErrInvalidArguments)
err = a.verifyDeliveryMethod(descope.MethodWhatsApp, "[email protected]", &descope.User{})
assert.ErrorIs(t, err, descope.ErrInvalidArguments)

Expand All @@ -171,13 +173,18 @@ func TestVerifyDeliveryMethod(t *testing.T) {
err = a.verifyDeliveryMethod(descope.MethodSMS, "+19999999999", u)
assert.Nil(t, err)
assert.NotEmpty(t, u.Phone)
err = a.verifyDeliveryMethod(descope.MethodVoice, "+19999999999", u)
assert.Nil(t, err)
assert.NotEmpty(t, u.Phone)
err = a.verifyDeliveryMethod(descope.MethodWhatsApp, "+19999999999", u)
assert.Nil(t, err)
assert.NotEmpty(t, u.Phone)

u = &descope.User{Phone: "+19999999999"}
err = a.verifyDeliveryMethod(descope.MethodSMS, "my username", u)
assert.Nil(t, err)
err = a.verifyDeliveryMethod(descope.MethodVoice, "my username", u)
assert.Nil(t, err)
err = a.verifyDeliveryMethod(descope.MethodWhatsApp, "my username", u)
assert.Nil(t, err)
}
Expand Down
2 changes: 1 addition & 1 deletion descope/internal/auth/otp.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (auth *otp) UpdateUserPhone(ctx context.Context, method descope.DeliveryMet
if !phoneRegex.MatchString(phone) {
return "", utils.NewInvalidArgumentError("phone")
}
if method != descope.MethodSMS && method != descope.MethodWhatsApp {
if method != descope.MethodSMS && method != descope.MethodVoice && method != descope.MethodWhatsApp {
return "", utils.NewInvalidArgumentError("method")
}
pswd, err := getValidRefreshToken(r)
Expand Down
112 changes: 112 additions & 0 deletions descope/internal/auth/otp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,28 @@ func TestSignUpSMS(t *testing.T) {
require.EqualValues(t, maskedPhone, mp)
}

func TestSignUpVoice(t *testing.T) {
phone := "943248329844"
maskedPhone := "*********44"
a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) {
assert.EqualValues(t, composeSignUpURL(descope.MethodVoice), r.URL.RequestURI())

body, err := readBodyMap(r)
require.NoError(t, err)
assert.EqualValues(t, phone, body["phone"])
assert.EqualValues(t, phone, body["loginId"])
assert.EqualValues(t, "test", body["user"].(map[string]interface{})["name"])
resp := MaskedPhoneRes{MaskedPhone: maskedPhone}
respBytes, err := utils.Marshal(resp)
require.NoError(t, err)
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBuffer(respBytes))}, nil
})
require.NoError(t, err)
mp, err := a.OTP().SignUp(context.Background(), descope.MethodVoice, phone, &descope.User{Name: "test"}, nil)
require.NoError(t, err)
require.EqualValues(t, maskedPhone, mp)
}

func TestSignUpWhatsApp(t *testing.T) {
phone := "943248329844"
maskedPhone := "*********44"
Expand Down Expand Up @@ -172,6 +194,21 @@ func TestSignUpOrInSMS(t *testing.T) {
require.NoError(t, err)
}

func TestSignUpOrInVoice(t *testing.T) {
loginID := "943248329844"
a, err := newTestAuth(nil, DoOk(func(r *http.Request) {
assert.EqualValues(t, composeSignUpOrInURL(descope.MethodVoice), r.URL.RequestURI())

body, err := readBodyMap(r)
require.NoError(t, err)
assert.EqualValues(t, loginID, body["loginId"])
assert.Nil(t, body["user"])
}))
require.NoError(t, err)
_, err = a.OTP().SignUpOrIn(context.Background(), descope.MethodVoice, loginID, nil)
require.NoError(t, err)
}

func TestSignUpOrInSMSWithSignUpOptions(t *testing.T) {
loginID := "943248329844"
a, err := newTestAuth(nil, DoOk(func(r *http.Request) {
Expand All @@ -191,6 +228,25 @@ func TestSignUpOrInSMSWithSignUpOptions(t *testing.T) {
require.NoError(t, err)
}

func TestSignUpOrInVoiceWithSignUpOptions(t *testing.T) {
loginID := "943248329844"
a, err := newTestAuth(nil, DoOk(func(r *http.Request) {
assert.EqualValues(t, composeSignUpOrInURL(descope.MethodVoice), r.URL.RequestURI())

body, err := readBodyMap(r)
require.NoError(t, err)
assert.EqualValues(t, loginID, body["loginId"])
assert.Nil(t, body["user"])
assert.EqualValues(t, map[string]interface{}{"customClaims": map[string]interface{}{"aa": "bb"}, "templateOptions": map[string]interface{}{"cc": "dd"}}, body["loginOptions"])
}))
require.NoError(t, err)
_, err = a.OTP().SignUpOrIn(context.Background(), descope.MethodVoice, loginID, &descope.SignUpOptions{
CustomClaims: map[string]interface{}{"aa": "bb"},
TemplateOptions: map[string]string{"cc": "dd"},
})
require.NoError(t, err)
}

func TestSignUpOrInEmail(t *testing.T) {
loginID := "943248329844"
a, err := newTestAuth(nil, DoOk(func(r *http.Request) {
Expand Down Expand Up @@ -351,6 +407,22 @@ func TestVerifyCodeWhatsApp(t *testing.T) {
require.NoError(t, err)
}

func TestVerifyCodeVoice(t *testing.T) {
phone := "943248329844"
code := "4914"
a, err := newTestAuth(nil, DoOk(func(r *http.Request) {
assert.EqualValues(t, composeVerifyCodeURL(descope.MethodVoice), r.URL.RequestURI())

body, err := readBodyMap(r)
require.NoError(t, err)
assert.EqualValues(t, phone, body["loginId"])
assert.EqualValues(t, code, body["code"])
}))
require.NoError(t, err)
_, err = a.OTP().VerifyCode(context.Background(), descope.MethodVoice, phone, code, nil)
require.NoError(t, err)
}

func TestVerifyCodeEmailResponseOption(t *testing.T) {
email := "[email protected]"
code := "4914"
Expand Down Expand Up @@ -558,6 +630,46 @@ func TestUpdatePhoneOTP(t *testing.T) {
require.EqualValues(t, maskedPhone, mp)
}

func TestUpdatePhoneOTPVoice(t *testing.T) {
loginID := "943248329844"
phone := "+111111111111"
maskedPhone := "+*******111"
checkOptions := true
a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) {
assert.EqualValues(t, composeUpdateUserPhoneOTP(descope.MethodVoice), r.URL.RequestURI())

body, err := readBodyMap(r)
require.NoError(t, err)
assert.EqualValues(t, loginID, body["loginId"])
assert.EqualValues(t, phone, body["phone"])
if checkOptions {
assert.EqualValues(t, true, body["addToLoginIDs"])
assert.EqualValues(t, true, body["onMergeUseExisting"])
} else {
assert.EqualValues(t, nil, body["addToLoginIDs"])
assert.EqualValues(t, nil, body["onMergeUseExisting"])
}

u, p := getProjectAndJwt(r)
assert.NotEmpty(t, u)
assert.NotEmpty(t, p)
resp := MaskedPhoneRes{MaskedPhone: maskedPhone}
respBytes, err := utils.Marshal(resp)
require.NoError(t, err)
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBuffer(respBytes))}, nil
})
require.NoError(t, err)
r := &http.Request{Header: http.Header{}}
r.AddCookie(&http.Cookie{Name: descope.RefreshCookieName, Value: jwtTokenValid})
mp, err := a.OTP().UpdateUserPhone(context.Background(), descope.MethodVoice, loginID, phone, &descope.UpdateOptions{AddToLoginIDs: true, OnMergeUseExisting: true}, r)
require.NoError(t, err)
require.EqualValues(t, maskedPhone, mp)
checkOptions = false
mp, err = a.OTP().UpdateUserPhone(context.Background(), descope.MethodVoice, loginID, phone, nil, r)
require.NoError(t, err)
require.EqualValues(t, maskedPhone, mp)
}

func TestUpdatePhoneOTPWithTemplateOptions(t *testing.T) {
loginID := "943248329844"
phone := "+111111111111"
Expand Down
2 changes: 2 additions & 0 deletions descope/internal/auth/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ func newSignUpRequestBody(method descope.DeliveryMethod, user *descope.User) *au
switch method {
case descope.MethodSMS:
return &authenticationSignUpRequestBody{Phone: user.Phone}
case descope.MethodVoice:
return &authenticationSignUpRequestBody{Phone: user.Phone}
case descope.MethodWhatsApp:
return &authenticationSignUpRequestBody{WhatsApp: user.Phone}
}
Expand Down
33 changes: 33 additions & 0 deletions descope/internal/mgmt/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,39 @@ func TestGenerateOTPForTestUserSuccess(t *testing.T) {
assert.EqualValues(t, code, resCode)
}

func TestGenerateOTPForTestUserSuccessMethodVoice(t *testing.T) {
loginID := "some-id"
code := "123456"
response := map[string]any{
"loginId": loginID,
"code": code,
}
loginOptions := descope.LoginOptions{
MFA: true,
}
visited := false
m := newTestMgmt(nil, helpers.DoOkWithBody(func(r *http.Request) {
visited = true
require.Equal(t, r.Header.Get("Authorization"), "Bearer a:key")
req := map[string]any{}
require.NoError(t, helpers.ReadBody(r, &req))
require.Equal(t, loginID, req["loginId"])
require.Equal(t, string(descope.MethodVoice), req["deliveryMethod"])
b, err := utils.Marshal(req["loginOptions"])
require.NoError(t, err)
var loginOptionsReq descope.LoginOptions
err = utils.Unmarshal(b, &loginOptionsReq)
require.NoError(t, err)
require.Equal(t, loginOptions, loginOptionsReq)
}, response))

resCode, err := m.User().GenerateOTPForTestUser(context.Background(), descope.MethodVoice, loginID, &loginOptions)
require.NoError(t, err)
require.NotEmpty(t, resCode)
require.True(t, visited)
assert.EqualValues(t, code, resCode)
}

func TestGenerateOTPForTestUserNoLoginID(t *testing.T) {
loginID := "some-id"
code := "123456"
Expand Down
4 changes: 2 additions & 2 deletions descope/sdk/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ type OTP interface {
UpdateUserEmail(ctx context.Context, loginID, email string, updateOptions *descope.UpdateOptions, request *http.Request) (maskedAddress string, err error)

// UpdateUserPhone - Use to update phone and validate via OTP
// allowed methods are phone based methods - whatsapp and SMS
// allowed methods are phone based methods - whatsapp and SMS or Voice call
dorsha marked this conversation as resolved.
Show resolved Hide resolved
// LoginID of user whom we want to update
// UpdateOptions to determine whether to add email as a login id and if to merge with existing user in that case
// Request is needed to obtain JWT and send it to Descope, for verification
Expand Down Expand Up @@ -152,7 +152,7 @@ type Password interface {
// redirectURL is an optional parameter that is used by Magic Link or Enchanted Link
// if those are the chosen reset methods. See the Magic Link and Enchanted Link sections
// for more details.
// templateOptions is used to pass dynamic options for the messaging (email / text message) template
// templateOptions is used to pass dynamic options for the messaging (email / text message, voice call) template
dorsha marked this conversation as resolved.
Show resolved Hide resolved
SendPasswordReset(ctx context.Context, loginID, redirectURL string, templateOptions map[string]string) error

// UpdateUserPassword - updates a user's password according to the given loginID.
Expand Down
1 change: 1 addition & 0 deletions descope/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,7 @@ type ProjectTag string
const (
MethodWhatsApp DeliveryMethod = "whatsapp"
MethodSMS DeliveryMethod = "sms"
MethodVoice DeliveryMethod = "voice"
MethodEmail DeliveryMethod = "email"
MethodEmbedded DeliveryMethod = "Embedded"

Expand Down
12 changes: 6 additions & 6 deletions examples/ginwebapp/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/descope/go-sdk/examples/ginwebapp
go 1.18

require (
github.com/descope/go-sdk v1.5.7
github.com/descope/go-sdk v1.6.2
github.com/descope/go-sdk/descope/gin v0.0.0-00010101000000-000000000000
github.com/gin-gonic/gin v1.9.1
)
Expand All @@ -23,9 +23,9 @@ require (
github.com/leodido/go-urn v1.2.4 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.4 // indirect
github.com/lestrrat-go/httprc v1.0.5 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/jwx/v2 v2.0.19 // indirect
github.com/lestrrat-go/jwx/v2 v2.0.21 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
Expand All @@ -35,10 +35,10 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20220921023135-46d9e7742f1e // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
22 changes: 11 additions & 11 deletions examples/ginwebapp/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk=
github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx/v2 v2.0.19 h1:ekv1qEZE6BVct89QA+pRF6+4pCpfVrOnEJnTnT4RXoY=
github.com/lestrrat-go/jwx/v2 v2.0.19/go.mod h1:l3im3coce1lL2cDeAjqmaR+Awx+X8Ih+2k8BuHNJ4CU=
github.com/lestrrat-go/jwx/v2 v2.0.21 h1:jAPKupy4uHgrHFEdjVjNkUgoBKtVDgrQPB/h55FHrR0=
github.com/lestrrat-go/jwx/v2 v2.0.21/go.mod h1:09mLW8zto6bWL9GbwnqAli+ArLf+5M33QLQPDggkUWM=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
Expand All @@ -71,24 +71,24 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20220921023135-46d9e7742f1e h1:Ctm9yurWsg7aWwIpH9Bnap/IdSVxixymIb3MhiMEQQA=
golang.org/x/exp v0.0.0-20220921023135-46d9e7742f1e/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
Loading
Loading