github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/tequilapi/sso/mystnodes_test.go (about) 1 /* 2 * Copyright (C) 2023 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 package sso 18 19 import ( 20 "io" 21 "net/http" 22 "net/url" 23 "strings" 24 "testing" 25 26 "github.com/mysteriumnetwork/node/identity" 27 "github.com/mysteriumnetwork/node/tequilapi/pkce" 28 29 "encoding/base64" 30 "encoding/json" 31 32 "github.com/pkg/errors" 33 "github.com/stretchr/testify/assert" 34 ) 35 36 var signerFactory = func(id identity.Identity) identity.Signer { 37 return &mockSignerFactory{} 38 } 39 40 type mockSignerFactory struct { 41 } 42 43 func (s *mockSignerFactory) Sign(message []byte) (identity.Signature, error) { 44 return identity.SignatureBytes([]byte("pretty signature")), nil 45 } 46 47 func TestMystnodesSSOLink(t *testing.T) { 48 // given 49 sso := NewMystnodes(signerFactory, nil) 50 sso.lastUnlockedIdentity = identity.Identity{Address: "0x1"} 51 52 // expect 53 redirect, err := url.Parse("http://local:6969/#/auth-sso") 54 assert.NoError(t, err) 55 56 r, err := sso.SSOLink(redirect) 57 assert.NoError(t, err) 58 59 assert.Equal(t, r.Host, "") 60 assert.NotEmpty(t, r.Query().Get("message")) 61 62 decodedMessage, err := base64.RawURLEncoding.DecodeString(r.Query().Get("message")) 63 assert.NoError(t, err) 64 65 var mm MystnodesMessage 66 err = json.Unmarshal(decodedMessage, &mm) 67 assert.NoError(t, err) 68 assert.Equal(t, sso.lastUnlockedIdentity.Address, mm.Identity) 69 assert.Equal(t, "http://local:6969/#/auth-sso", mm.RedirectURL) 70 assert.NotEmpty(t, mm.CodeChallenge) 71 72 codeVerifierBytes, err := base64.RawURLEncoding.DecodeString(sso.lastCodeVerifierBase64url) 73 assert.NoError(t, err) 74 assert.True(t, pkce.ChallengeSHA256(string(codeVerifierBytes)) == mm.CodeChallenge) 75 76 decodedSignatureBytes, err := base64.RawURLEncoding.DecodeString(r.Query().Get("signature")) 77 assert.NoError(t, err) 78 assert.Equal(t, "pretty signature", string(decodedSignatureBytes)) 79 } 80 81 func TestMystnodesSSOLinkFail(t *testing.T) { 82 // given 83 sso := NewMystnodes(signerFactory, nil) 84 85 // expect 86 _, err := sso.SSOLink(nil) 87 assert.ErrorIs(t, err, ErrRedirectMissing) 88 89 _, err = sso.SSOLink(&url.URL{}) 90 assert.ErrorIs(t, err, ErrNoUnlockedIdentity) 91 } 92 93 func TestMystnodesSSOGrantVerification(t *testing.T) { 94 // given 95 redirect, err := url.Parse("http://not_important:6969/#/auth-sso") 96 assert.NoError(t, err) 97 98 // when 99 sso := NewMystnodes(signerFactory, newHttpClientMock(&http.Response{StatusCode: 200, Status: "200 OK", Body: &readCloser{Reader: strings.NewReader(`{"walletAddress": "0x111", "apiKey": "xxx", "isEligibleForFreeRegistration": true}`)}})) 100 sso.lastUnlockedIdentity = identity.Identity{Address: "0x1"} 101 _, err = sso.SSOLink(redirect) 102 assert.NoError(t, err) 103 104 // then 105 vi, err := sso.VerifyAuthorizationGrant("auth_grant", DefaultVerificationOptions) 106 assert.NoError(t, err) 107 assert.Equal(t, true, vi.IsEligibleForFreeRegistration) 108 assert.Equal(t, "0x111", vi.WalletAddress) 109 assert.Equal(t, "xxx", vi.APIkey) 110 111 // when 112 sso = NewMystnodes(signerFactory, newHttpClientMock(nil)) 113 sso.lastUnlockedIdentity = identity.Identity{Address: "0x1"} 114 _, err = sso.SSOLink(redirect) 115 assert.NoError(t, err) 116 117 // then 118 _, err = sso.VerifyAuthorizationGrant("auth_grant", DefaultVerificationOptions) 119 assert.Error(t, err) 120 } 121 122 type httpClientMock struct { 123 req *http.Request 124 125 res *http.Response 126 } 127 128 func newHttpClientMock(res *http.Response) *httpClientMock { 129 return &httpClientMock{ 130 res: res, 131 } 132 } 133 134 func (m *httpClientMock) Do(req *http.Request) (*http.Response, error) { 135 m.req = req 136 if m.res == nil { 137 return nil, errors.New("Oops") 138 } 139 return m.res, nil 140 } 141 142 func (m *httpClientMock) stub(res *http.Response) { 143 m.res = res 144 } 145 146 type readCloser struct { 147 io.Reader 148 Closed bool 149 } 150 151 func (tc *readCloser) Close() error { 152 tc.Closed = true 153 return nil 154 } 155 156 var _ io.ReadCloser = (*readCloser)(nil)