Server test/implement send verify-account email
This commit is contained in:
parent
f15875c4a6
commit
6672175a25
16 changed files with 249 additions and 125 deletions
25
auth/auth.go
25
auth/auth.go
|
@ -19,7 +19,7 @@ type Password string
|
||||||
type KDFKey string // KDF output
|
type KDFKey string // KDF output
|
||||||
type ClientSaltSeed string // part of client-side KDF input along with root password
|
type ClientSaltSeed string // part of client-side KDF input along with root password
|
||||||
type ServerSalt string // server-side KDF input for accounts
|
type ServerSalt string // server-side KDF input for accounts
|
||||||
type TokenString string
|
type AuthTokenString string
|
||||||
type VerifyTokenString string
|
type VerifyTokenString string
|
||||||
type AuthScope string
|
type AuthScope string
|
||||||
|
|
||||||
|
@ -28,30 +28,31 @@ const ScopeFull = AuthScope("*")
|
||||||
// For test stubs
|
// For test stubs
|
||||||
type AuthInterface interface {
|
type AuthInterface interface {
|
||||||
// TODO maybe have a "refresh token" thing if the client won't have email available all the time?
|
// TODO maybe have a "refresh token" thing if the client won't have email available all the time?
|
||||||
NewToken(UserId, DeviceId, AuthScope) (*AuthToken, error)
|
NewAuthToken(UserId, DeviceId, AuthScope) (*AuthToken, error)
|
||||||
|
NewVerifyTokenString() (VerifyTokenString, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Auth struct{}
|
type Auth struct{}
|
||||||
|
|
||||||
type AuthToken struct {
|
type AuthToken struct {
|
||||||
Token TokenString `json:"token"`
|
Token AuthTokenString `json:"token"`
|
||||||
DeviceId DeviceId `json:"deviceId"`
|
DeviceId DeviceId `json:"deviceId"`
|
||||||
Scope AuthScope `json:"scope"`
|
Scope AuthScope `json:"scope"`
|
||||||
UserId UserId `json:"userId"`
|
UserId UserId `json:"userId"`
|
||||||
Expiration *time.Time `json:"expiration"`
|
Expiration *time.Time `json:"expiration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthTokenLength = 32
|
const TokenLength = 32
|
||||||
|
|
||||||
func (a *Auth) NewToken(userId UserId, deviceId DeviceId, scope AuthScope) (*AuthToken, error) {
|
func (a *Auth) NewAuthToken(userId UserId, deviceId DeviceId, scope AuthScope) (*AuthToken, error) {
|
||||||
b := make([]byte, AuthTokenLength)
|
b := make([]byte, TokenLength)
|
||||||
// TODO - Is this is a secure random function? (Maybe audit)
|
// TODO - Is this is a secure random function? (Maybe audit)
|
||||||
if _, err := rand.Read(b); err != nil {
|
if _, err := rand.Read(b); err != nil {
|
||||||
return nil, fmt.Errorf("Error generating token: %+v", err)
|
return nil, fmt.Errorf("Error generating token: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AuthToken{
|
return &AuthToken{
|
||||||
Token: TokenString(hex.EncodeToString(b)),
|
Token: AuthTokenString(hex.EncodeToString(b)),
|
||||||
DeviceId: deviceId,
|
DeviceId: deviceId,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
|
@ -59,6 +60,16 @@ func (a *Auth) NewToken(userId UserId, deviceId DeviceId, scope AuthScope) (*Aut
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Auth) NewVerifyTokenString() (VerifyTokenString, error) {
|
||||||
|
b := make([]byte, TokenLength)
|
||||||
|
// TODO - Is this is a secure random function? (Maybe audit)
|
||||||
|
if _, err := rand.Read(b); err != nil {
|
||||||
|
return "", fmt.Errorf("Error generating token: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return VerifyTokenString(hex.EncodeToString(b)), nil
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE - not stubbing methods of structs like this. more convoluted than it's worth right now
|
// NOTE - not stubbing methods of structs like this. more convoluted than it's worth right now
|
||||||
func (at *AuthToken) ScopeValid(required AuthScope) bool {
|
func (at *AuthToken) ScopeValid(required AuthScope) bool {
|
||||||
// So far * is the only scope issued. Used to have more, didn't want to
|
// So far * is the only scope issued. Used to have more, didn't want to
|
||||||
|
|
|
@ -6,9 +6,9 @@ import (
|
||||||
|
|
||||||
// Test stubs for now
|
// Test stubs for now
|
||||||
|
|
||||||
func TestAuthNewToken(t *testing.T) {
|
func TestAuthNewAuthToken(t *testing.T) {
|
||||||
auth := Auth{}
|
auth := Auth{}
|
||||||
authToken, err := auth.NewToken(234, "dId", "my-scope")
|
authToken, err := auth.NewAuthToken(234, "dId", "my-scope")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error creating new token")
|
t.Fatalf("Error creating new token")
|
||||||
|
@ -20,8 +20,8 @@ func TestAuthNewToken(t *testing.T) {
|
||||||
t.Fatalf("authToken fields don't match expected values")
|
t.Fatalf("authToken fields don't match expected values")
|
||||||
}
|
}
|
||||||
|
|
||||||
// result.Token is in hex, AuthTokenLength is bytes in the original
|
// result.Token is in hex, TokenLength is bytes in the original
|
||||||
expectedTokenLength := AuthTokenLength * 2
|
expectedTokenLength := TokenLength * 2
|
||||||
if len(authToken.Token) != expectedTokenLength {
|
if len(authToken.Token) != expectedTokenLength {
|
||||||
t.Fatalf("authToken token string length isn't the expected length")
|
t.Fatalf("authToken token string length isn't the expected length")
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,8 @@ func (s *Server) register(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
var registerResponse RegisterResponse
|
var registerResponse RegisterResponse
|
||||||
|
|
||||||
|
var token auth.VerifyTokenString
|
||||||
|
|
||||||
modes:
|
modes:
|
||||||
switch verificationMode {
|
switch verificationMode {
|
||||||
case env.AccountVerificationModeAllowAll:
|
case env.AccountVerificationModeAllowAll:
|
||||||
|
@ -75,13 +77,19 @@ modes:
|
||||||
case env.AccountVerificationModeEmailVerify:
|
case env.AccountVerificationModeEmailVerify:
|
||||||
// Not verified until they click their email link.
|
// Not verified until they click their email link.
|
||||||
registerResponse.Verified = false
|
registerResponse.Verified = false
|
||||||
|
token, err = s.auth.NewVerifyTokenString()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
internalServiceErrorJson(w, err, "Error generating verify token string")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.store.CreateAccount(
|
err = s.store.CreateAccount(
|
||||||
registerRequest.Email,
|
registerRequest.Email,
|
||||||
registerRequest.Password,
|
registerRequest.Password,
|
||||||
registerRequest.ClientSaltSeed,
|
registerRequest.ClientSaltSeed,
|
||||||
registerResponse.Verified,
|
token, // if it's not set, the user is marked as verified
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -93,6 +101,15 @@ modes:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(token) > 0 {
|
||||||
|
err = s.mail.SendVerificationEmail(registerRequest.Email, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
internalServiceErrorJson(w, err, "Error sending verification email")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
response, err := json.Marshal(registerResponse)
|
response, err := json.Marshal(registerResponse)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -7,20 +7,21 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"lbryio/lbry-id/auth"
|
|
||||||
"lbryio/lbry-id/store"
|
"lbryio/lbry-id/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO - maybe this test could just be one of the TestServerRegisterAccountVerification tests now
|
||||||
func TestServerRegisterSuccess(t *testing.T) {
|
func TestServerRegisterSuccess(t *testing.T) {
|
||||||
testStore := &TestStore{}
|
testStore := &TestStore{}
|
||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
"ACCOUNT_VERIFICATION_MODE": "AllowAll",
|
"ACCOUNT_VERIFICATION_MODE": "EmailVerify",
|
||||||
}
|
}
|
||||||
s := Server{&TestAuth{}, testStore, &TestEnv{env}}
|
testMail := TestMail{}
|
||||||
|
testAuth := TestAuth{TestNewVerifyTokenString: "abcd1234abcd1234abcd1234abcd1234"}
|
||||||
|
s := Server{&testAuth, testStore, &TestEnv{env}, &testMail}
|
||||||
|
|
||||||
requestBody := []byte(`{"email": "abc@example.com", "password": "123", "clientSaltSeed": "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234" }`)
|
requestBody := []byte(`{"email": "abc@example.com", "password": "123", "clientSaltSeed": "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234" }`)
|
||||||
|
|
||||||
|
@ -35,14 +36,19 @@ func TestServerRegisterSuccess(t *testing.T) {
|
||||||
var result RegisterResponse
|
var result RegisterResponse
|
||||||
err := json.Unmarshal(body, &result)
|
err := json.Unmarshal(body, &result)
|
||||||
|
|
||||||
expectedResponse := RegisterResponse{Verified: true}
|
expectedResponse := RegisterResponse{Verified: false}
|
||||||
if err != nil || !reflect.DeepEqual(&result, &expectedResponse) {
|
if err != nil || result != expectedResponse {
|
||||||
t.Errorf("Unexpected value for register response. Want: %+v Got: %+v Err: %+v", expectedResponse, result, err)
|
t.Errorf("Unexpected value for register response. Want: %+v Got: %+v Err: %+v", expectedResponse, result, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if testStore.Called.CreateAccount == nil {
|
if testStore.Called.CreateAccount == nil {
|
||||||
t.Errorf("Expected Store.CreateAccount to be called")
|
t.Errorf("Expected Store.CreateAccount to be called")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if testMail.SendVerificationEmailCall == nil {
|
||||||
|
// We're doing EmailVerify for this test.
|
||||||
|
t.Fatalf("Expected Store.SendVerificationEmail to be called")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerRegisterErrors(t *testing.T) {
|
func TestServerRegisterErrors(t *testing.T) {
|
||||||
|
@ -51,14 +57,20 @@ func TestServerRegisterErrors(t *testing.T) {
|
||||||
email string
|
email string
|
||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
expectedErrorString string
|
expectedErrorString string
|
||||||
|
expectedCallSendVerificationEmail bool
|
||||||
|
expectedCallCreateAccount bool
|
||||||
|
|
||||||
storeErrors TestStoreFunctionsErrors
|
storeErrors TestStoreFunctionsErrors
|
||||||
|
mailError error
|
||||||
|
failGenToken bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "validation error", // missing email address
|
name: "validation error", // missing email address
|
||||||
email: "",
|
email: "",
|
||||||
expectedStatusCode: http.StatusBadRequest,
|
expectedStatusCode: http.StatusBadRequest,
|
||||||
expectedErrorString: http.StatusText(http.StatusBadRequest) + ": Request failed validation: Invalid or missing 'email'",
|
expectedErrorString: http.StatusText(http.StatusBadRequest) + ": Request failed validation: Invalid or missing 'email'",
|
||||||
|
expectedCallSendVerificationEmail: false,
|
||||||
|
expectedCallCreateAccount: false,
|
||||||
|
|
||||||
// Just check one validation error (missing email address) to make sure the
|
// Just check one validation error (missing email address) to make sure the
|
||||||
// validate function is called. We'll check the rest of the validation
|
// validate function is called. We'll check the rest of the validation
|
||||||
|
@ -69,6 +81,8 @@ func TestServerRegisterErrors(t *testing.T) {
|
||||||
email: "abc@example.com",
|
email: "abc@example.com",
|
||||||
expectedStatusCode: http.StatusConflict,
|
expectedStatusCode: http.StatusConflict,
|
||||||
expectedErrorString: http.StatusText(http.StatusConflict) + ": Error registering",
|
expectedErrorString: http.StatusText(http.StatusConflict) + ": Error registering",
|
||||||
|
expectedCallSendVerificationEmail: false,
|
||||||
|
expectedCallCreateAccount: true,
|
||||||
|
|
||||||
storeErrors: TestStoreFunctionsErrors{CreateAccount: store.ErrDuplicateEmail},
|
storeErrors: TestStoreFunctionsErrors{CreateAccount: store.ErrDuplicateEmail},
|
||||||
},
|
},
|
||||||
|
@ -77,19 +91,44 @@ func TestServerRegisterErrors(t *testing.T) {
|
||||||
email: "abc@example.com",
|
email: "abc@example.com",
|
||||||
expectedStatusCode: http.StatusInternalServerError,
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
||||||
|
expectedCallSendVerificationEmail: false,
|
||||||
|
expectedCallCreateAccount: true,
|
||||||
|
|
||||||
storeErrors: TestStoreFunctionsErrors{CreateAccount: fmt.Errorf("TestStore.CreateAccount fail")},
|
storeErrors: TestStoreFunctionsErrors{CreateAccount: fmt.Errorf("TestStore.CreateAccount fail")},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "fail to generate verifiy token",
|
||||||
|
email: "abc@example.com",
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
||||||
|
expectedCallSendVerificationEmail: false,
|
||||||
|
expectedCallCreateAccount: false,
|
||||||
|
|
||||||
|
failGenToken: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail to generate verification email",
|
||||||
|
email: "abc@example.com",
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
||||||
|
expectedCallSendVerificationEmail: true,
|
||||||
|
expectedCallCreateAccount: true,
|
||||||
|
|
||||||
|
mailError: fmt.Errorf("TestEmail.SendVerificationEmail fail"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
"ACCOUNT_VERIFICATION_MODE": "AllowAll",
|
"ACCOUNT_VERIFICATION_MODE": "EmailVerify",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set this up to fail according to specification
|
// Set this up to fail according to specification
|
||||||
s := Server{&TestAuth{}, &TestStore{Errors: tc.storeErrors}, &TestEnv{env}}
|
testAuth := TestAuth{TestNewVerifyTokenString: "abcd1234abcd1234abcd1234abcd1234", FailGenToken: tc.failGenToken}
|
||||||
|
testMail := TestMail{SendVerificationEmailError: tc.mailError}
|
||||||
|
testStore := TestStore{Errors: tc.storeErrors}
|
||||||
|
s := Server{&testAuth, &testStore, &TestEnv{env}, &testMail}
|
||||||
|
|
||||||
// Make request
|
// Make request
|
||||||
requestBody := fmt.Sprintf(`{"email": "%s", "password": "123", "clientSaltSeed": "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"}`, tc.email)
|
requestBody := fmt.Sprintf(`{"email": "%s", "password": "123", "clientSaltSeed": "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"}`, tc.email)
|
||||||
|
@ -102,6 +141,20 @@ func TestServerRegisterErrors(t *testing.T) {
|
||||||
|
|
||||||
expectStatusCode(t, w, tc.expectedStatusCode)
|
expectStatusCode(t, w, tc.expectedStatusCode)
|
||||||
expectErrorString(t, body, tc.expectedErrorString)
|
expectErrorString(t, body, tc.expectedErrorString)
|
||||||
|
|
||||||
|
if tc.expectedCallCreateAccount && testStore.Called.CreateAccount == nil {
|
||||||
|
t.Errorf("Expected Store.CreateAccount to be called")
|
||||||
|
}
|
||||||
|
if !tc.expectedCallCreateAccount && testStore.Called.CreateAccount != nil {
|
||||||
|
t.Errorf("Expected Store.CreateAccount not to be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectedCallSendVerificationEmail && testMail.SendVerificationEmailCall == nil {
|
||||||
|
t.Errorf("Expected Store.SendVerificationEmail to be called")
|
||||||
|
}
|
||||||
|
if !tc.expectedCallSendVerificationEmail && testMail.SendVerificationEmailCall != nil {
|
||||||
|
t.Errorf("Expected Store.SendVerificationEmail not to be called")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,6 +167,7 @@ func TestServerRegisterAccountVerification(t *testing.T) {
|
||||||
expectSuccess bool
|
expectSuccess bool
|
||||||
expectedVerified bool
|
expectedVerified bool
|
||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
|
expectedCallSendVerificationEmail bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "allow all",
|
name: "allow all",
|
||||||
|
@ -125,6 +179,7 @@ func TestServerRegisterAccountVerification(t *testing.T) {
|
||||||
expectedVerified: true,
|
expectedVerified: true,
|
||||||
expectSuccess: true,
|
expectSuccess: true,
|
||||||
expectedStatusCode: http.StatusCreated,
|
expectedStatusCode: http.StatusCreated,
|
||||||
|
expectedCallSendVerificationEmail: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "whitelist allowed",
|
name: "whitelist allowed",
|
||||||
|
@ -137,6 +192,7 @@ func TestServerRegisterAccountVerification(t *testing.T) {
|
||||||
expectedVerified: true,
|
expectedVerified: true,
|
||||||
expectSuccess: true,
|
expectSuccess: true,
|
||||||
expectedStatusCode: http.StatusCreated,
|
expectedStatusCode: http.StatusCreated,
|
||||||
|
expectedCallSendVerificationEmail: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "whitelist disallowed",
|
name: "whitelist disallowed",
|
||||||
|
@ -149,6 +205,7 @@ func TestServerRegisterAccountVerification(t *testing.T) {
|
||||||
expectedVerified: false,
|
expectedVerified: false,
|
||||||
expectSuccess: false,
|
expectSuccess: false,
|
||||||
expectedStatusCode: http.StatusForbidden,
|
expectedStatusCode: http.StatusForbidden,
|
||||||
|
expectedCallSendVerificationEmail: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "email verify",
|
name: "email verify",
|
||||||
|
@ -160,13 +217,16 @@ func TestServerRegisterAccountVerification(t *testing.T) {
|
||||||
expectedVerified: false,
|
expectedVerified: false,
|
||||||
expectSuccess: true,
|
expectSuccess: true,
|
||||||
expectedStatusCode: http.StatusCreated,
|
expectedStatusCode: http.StatusCreated,
|
||||||
|
expectedCallSendVerificationEmail: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
testStore := &TestStore{}
|
testStore := &TestStore{}
|
||||||
s := Server{&TestAuth{}, testStore, &TestEnv{tc.env}}
|
testAuth := TestAuth{TestNewVerifyTokenString: "abcd1234abcd1234abcd1234abcd1234"}
|
||||||
|
testMail := TestMail{}
|
||||||
|
s := Server{&testAuth, testStore, &TestEnv{tc.env}, &testMail}
|
||||||
|
|
||||||
requestBody := []byte(`{"email": "abc@example.com", "password": "123", "clientSaltSeed": "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234" }`)
|
requestBody := []byte(`{"email": "abc@example.com", "password": "123", "clientSaltSeed": "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234" }`)
|
||||||
|
|
||||||
|
@ -182,8 +242,12 @@ func TestServerRegisterAccountVerification(t *testing.T) {
|
||||||
if testStore.Called.CreateAccount == nil {
|
if testStore.Called.CreateAccount == nil {
|
||||||
t.Fatalf("Expected CreateAccount to be called")
|
t.Fatalf("Expected CreateAccount to be called")
|
||||||
}
|
}
|
||||||
if tc.expectedVerified != testStore.Called.CreateAccount.Verified {
|
tokenPassedIn := testStore.Called.CreateAccount.VerifyToken != ""
|
||||||
t.Errorf("Unexpected value in call to CreateAccount for `verified`. Want: %+v Got: %+v", tc.expectedVerified, testStore.Called.CreateAccount.Verified)
|
if tc.expectedVerified && tokenPassedIn {
|
||||||
|
t.Errorf("Expected new account to be verified, thus expected verifyToken *not to be passed in* to call to CreateAccount.")
|
||||||
|
}
|
||||||
|
if !tc.expectedVerified && !tokenPassedIn {
|
||||||
|
t.Errorf("Expected new account not to be verified, thus expected verifyToken not *to be passed in* to call to CreateAccount.")
|
||||||
}
|
}
|
||||||
var result RegisterResponse
|
var result RegisterResponse
|
||||||
err := json.Unmarshal(body, &result)
|
err := json.Unmarshal(body, &result)
|
||||||
|
@ -197,6 +261,13 @@ func TestServerRegisterAccountVerification(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tc.expectedCallSendVerificationEmail && testMail.SendVerificationEmailCall == nil {
|
||||||
|
t.Errorf("Expected Store.SendVerificationEmail to be called")
|
||||||
|
}
|
||||||
|
if !tc.expectedCallSendVerificationEmail && testMail.SendVerificationEmailCall != nil {
|
||||||
|
t.Errorf("Expected Store.SendVerificationEmail not to be called")
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,12 +325,12 @@ func TestServerValidateRegisterRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerVerifyAccountSuccess(t *testing.T) {
|
func TestServerVerifyAccountSuccess(t *testing.T) {
|
||||||
testStore := TestStore{TestVerifyTokenString: "abcd1234abcd1234abcd1234abcd1234"}
|
testStore := TestStore{}
|
||||||
s := Server{&TestAuth{}, &testStore, &TestEnv{}}
|
s := Server{&TestAuth{}, &testStore, &TestEnv{}, &TestMail{}}
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, PathVerify, nil)
|
req := httptest.NewRequest(http.MethodGet, PathVerify, nil)
|
||||||
q := req.URL.Query()
|
q := req.URL.Query()
|
||||||
q.Add("verifyToken", string(testStore.TestVerifyTokenString))
|
q.Add("verifyToken", "abcd1234abcd1234abcd1234abcd1234")
|
||||||
req.URL.RawQuery = q.Encode()
|
req.URL.RawQuery = q.Encode()
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
@ -280,7 +351,7 @@ func TestServerVerifyAccountSuccess(t *testing.T) {
|
||||||
func TestServerVerifyAccountErrors(t *testing.T) {
|
func TestServerVerifyAccountErrors(t *testing.T) {
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
name string
|
name string
|
||||||
token auth.VerifyTokenString
|
token string
|
||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
expectedErrorString string
|
expectedErrorString string
|
||||||
expectedCallVerifyAccount bool
|
expectedCallVerifyAccount bool
|
||||||
|
@ -315,13 +386,13 @@ func TestServerVerifyAccountErrors(t *testing.T) {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
||||||
// Set this up to fail according to specification
|
// Set this up to fail according to specification
|
||||||
testStore := TestStore{Errors: tc.storeErrors, TestVerifyTokenString: tc.token}
|
testStore := TestStore{Errors: tc.storeErrors}
|
||||||
s := Server{&TestAuth{}, &testStore, &TestEnv{}}
|
s := Server{&TestAuth{}, &testStore, &TestEnv{}, &TestMail{}}
|
||||||
|
|
||||||
// Make request
|
// Make request
|
||||||
req := httptest.NewRequest(http.MethodGet, PathVerify, nil)
|
req := httptest.NewRequest(http.MethodGet, PathVerify, nil)
|
||||||
q := req.URL.Query()
|
q := req.URL.Query()
|
||||||
q.Add("verifyToken", string(testStore.TestVerifyTokenString))
|
q.Add("verifyToken", tc.token)
|
||||||
req.URL.RawQuery = q.Encode()
|
req.URL.RawQuery = q.Encode()
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ func (s *Server) getAuthToken(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authToken, err := s.auth.NewToken(userId, authRequest.DeviceId, auth.ScopeFull)
|
authToken, err := s.auth.NewAuthToken(userId, authRequest.DeviceId, auth.ScopeFull)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
internalServiceErrorJson(w, err, "Error generating auth token")
|
internalServiceErrorJson(w, err, "Error generating auth token")
|
||||||
|
|
|
@ -15,9 +15,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServerAuthHandlerSuccess(t *testing.T) {
|
func TestServerAuthHandlerSuccess(t *testing.T) {
|
||||||
testAuth := TestAuth{TestNewTokenString: auth.TokenString("seekrit")}
|
testAuth := TestAuth{TestNewAuthTokenString: auth.AuthTokenString("seekrit")}
|
||||||
testStore := TestStore{}
|
testStore := TestStore{}
|
||||||
s := Server{&testAuth, &testStore, &TestEnv{}}
|
s := Server{&testAuth, &testStore, &TestEnv{}, &TestMail{}}
|
||||||
|
|
||||||
requestBody := []byte(`{"deviceId": "dev-1", "email": "abc@example.com", "password": "123"}`)
|
requestBody := []byte(`{"deviceId": "dev-1", "email": "abc@example.com", "password": "123"}`)
|
||||||
|
|
||||||
|
@ -32,12 +32,12 @@ func TestServerAuthHandlerSuccess(t *testing.T) {
|
||||||
var result auth.AuthToken
|
var result auth.AuthToken
|
||||||
err := json.Unmarshal(body, &result)
|
err := json.Unmarshal(body, &result)
|
||||||
|
|
||||||
if err != nil || result.Token != testAuth.TestNewTokenString {
|
if err != nil || result.Token != testAuth.TestNewAuthTokenString {
|
||||||
t.Errorf("Expected auth response to contain token: result: %+v err: %+v", string(body), err)
|
t.Errorf("Expected auth response to contain token: result: %+v err: %+v", string(body), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if testStore.Called.SaveToken != testAuth.TestNewTokenString {
|
if testStore.Called.SaveToken != testAuth.TestNewAuthTokenString {
|
||||||
t.Errorf("Expected Store.SaveToken to be called with %s", testAuth.TestNewTokenString)
|
t.Errorf("Expected Store.SaveToken to be called with %s", testAuth.TestNewAuthTokenString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,12 +98,12 @@ func TestServerAuthHandlerErrors(t *testing.T) {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
||||||
// Set this up to fail according to specification
|
// Set this up to fail according to specification
|
||||||
testAuth := TestAuth{TestNewTokenString: auth.TokenString("seekrit")}
|
testAuth := TestAuth{TestNewAuthTokenString: auth.AuthTokenString("seekrit")}
|
||||||
testStore := TestStore{Errors: tc.storeErrors}
|
testStore := TestStore{Errors: tc.storeErrors}
|
||||||
if tc.authFailGenToken { // TODO - TestAuth{Errors:authErrors}
|
if tc.authFailGenToken { // TODO - TestAuth{Errors:authErrors}
|
||||||
testAuth.FailGenToken = true
|
testAuth.FailGenToken = true
|
||||||
}
|
}
|
||||||
server := Server{&testAuth, &testStore, &TestEnv{}}
|
server := Server{&testAuth, &testStore, &TestEnv{}, &TestMail{}}
|
||||||
|
|
||||||
// Make request
|
// Make request
|
||||||
// So long as the JSON is well-formed, the content doesn't matter here since the password check will be stubbed out
|
// So long as the JSON is well-formed, the content doesn't matter here since the password check will be stubbed out
|
||||||
|
|
|
@ -66,7 +66,7 @@ func TestServerGetClientSalt(t *testing.T) {
|
||||||
Errors: tc.storeErrors,
|
Errors: tc.storeErrors,
|
||||||
}
|
}
|
||||||
|
|
||||||
s := Server{&testAuth, &testStore, &TestEnv{}}
|
s := Server{&testAuth, &testStore, &TestEnv{}, &TestMail{}}
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, PathClientSaltSeed, nil)
|
req := httptest.NewRequest(http.MethodGet, PathClientSaltSeed, nil)
|
||||||
q := req.URL.Query()
|
q := req.URL.Query()
|
||||||
|
|
|
@ -96,7 +96,7 @@ func TestIntegrationWalletUpdates(t *testing.T) {
|
||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
"ACCOUNT_VERIFICATION_MODE": "EmailVerify",
|
"ACCOUNT_VERIFICATION_MODE": "EmailVerify",
|
||||||
}
|
}
|
||||||
s := Server{&auth.Auth{}, &st, &TestEnv{env}}
|
s := Server{&auth.Auth{}, &st, &TestEnv{env}, &TestMail{}}
|
||||||
|
|
||||||
////////////////////
|
////////////////////
|
||||||
t.Log("Request: Register email address - any device")
|
t.Log("Request: Register email address - any device")
|
||||||
|
@ -130,8 +130,8 @@ func TestIntegrationWalletUpdates(t *testing.T) {
|
||||||
|
|
||||||
checkStatusCode(t, statusCode, responseBody)
|
checkStatusCode(t, statusCode, responseBody)
|
||||||
|
|
||||||
// result.Token is in hex, auth.AuthTokenLength is bytes in the original
|
// result.Token is in hex, auth.TokenLength is bytes in the original
|
||||||
expectedTokenLength := auth.AuthTokenLength * 2
|
expectedTokenLength := auth.TokenLength * 2
|
||||||
if len(authToken1.Token) != expectedTokenLength {
|
if len(authToken1.Token) != expectedTokenLength {
|
||||||
t.Fatalf("Expected auth response to contain token length 32: result: %+v", string(responseBody))
|
t.Fatalf("Expected auth response to contain token length 32: result: %+v", string(responseBody))
|
||||||
}
|
}
|
||||||
|
@ -265,7 +265,7 @@ func TestIntegrationChangePassword(t *testing.T) {
|
||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
"ACCOUNT_VERIFICATION_MODE": "EmailVerify",
|
"ACCOUNT_VERIFICATION_MODE": "EmailVerify",
|
||||||
}
|
}
|
||||||
s := Server{&auth.Auth{}, &st, &TestEnv{env}}
|
s := Server{&auth.Auth{}, &st, &TestEnv{env}, &TestMail{}}
|
||||||
|
|
||||||
////////////////////
|
////////////////////
|
||||||
t.Log("Request: Register email address")
|
t.Log("Request: Register email address")
|
||||||
|
@ -321,8 +321,8 @@ func TestIntegrationChangePassword(t *testing.T) {
|
||||||
|
|
||||||
checkStatusCode(t, statusCode, responseBody)
|
checkStatusCode(t, statusCode, responseBody)
|
||||||
|
|
||||||
// result.Token is in hex, auth.AuthTokenLength is bytes in the original
|
// result.Token is in hex, auth.TokenLength is bytes in the original
|
||||||
expectedTokenLength := auth.AuthTokenLength * 2
|
expectedTokenLength := auth.TokenLength * 2
|
||||||
if len(authToken.Token) != expectedTokenLength {
|
if len(authToken.Token) != expectedTokenLength {
|
||||||
t.Fatalf("Expected auth response to contain token length 32: result: %+v", string(responseBody))
|
t.Fatalf("Expected auth response to contain token length 32: result: %+v", string(responseBody))
|
||||||
}
|
}
|
||||||
|
@ -404,8 +404,8 @@ func TestIntegrationChangePassword(t *testing.T) {
|
||||||
|
|
||||||
checkStatusCode(t, statusCode, responseBody)
|
checkStatusCode(t, statusCode, responseBody)
|
||||||
|
|
||||||
// result.Token is in hex, auth.AuthTokenLength is bytes in the original
|
// result.Token is in hex, auth.TokenLength is bytes in the original
|
||||||
expectedTokenLength = auth.AuthTokenLength * 2
|
expectedTokenLength = auth.TokenLength * 2
|
||||||
if len(authToken.Token) != expectedTokenLength {
|
if len(authToken.Token) != expectedTokenLength {
|
||||||
t.Fatalf("Expected auth response to contain token length 32: result: %+v", string(responseBody))
|
t.Fatalf("Expected auth response to contain token length 32: result: %+v", string(responseBody))
|
||||||
}
|
}
|
||||||
|
@ -509,8 +509,8 @@ func TestIntegrationChangePassword(t *testing.T) {
|
||||||
|
|
||||||
checkStatusCode(t, statusCode, responseBody)
|
checkStatusCode(t, statusCode, responseBody)
|
||||||
|
|
||||||
// result.Token is in hex, auth.AuthTokenLength is bytes in the original
|
// result.Token is in hex, auth.TokenLength is bytes in the original
|
||||||
expectedTokenLength = auth.AuthTokenLength * 2
|
expectedTokenLength = auth.TokenLength * 2
|
||||||
if len(authToken.Token) != expectedTokenLength {
|
if len(authToken.Token) != expectedTokenLength {
|
||||||
t.Fatalf("Expected auth response to contain token length 32: result: %+v", string(responseBody))
|
t.Fatalf("Expected auth response to contain token length 32: result: %+v", string(responseBody))
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,7 +168,7 @@ func TestServerChangePassword(t *testing.T) {
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
testStore := TestStore{Errors: tc.storeErrors}
|
testStore := TestStore{Errors: tc.storeErrors}
|
||||||
s := Server{&TestAuth{}, &testStore, &TestEnv{}}
|
s := Server{&TestAuth{}, &testStore, &TestEnv{}, &TestMail{}}
|
||||||
|
|
||||||
// Whether we passed in wallet fields (these test cases should be passing
|
// Whether we passed in wallet fields (these test cases should be passing
|
||||||
// in all of them or none of them, so we only test EncryptedWallet). This
|
// in all of them or none of them, so we only test EncryptedWallet). This
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"lbryio/lbry-id/auth"
|
"lbryio/lbry-id/auth"
|
||||||
"lbryio/lbry-id/env"
|
"lbryio/lbry-id/env"
|
||||||
|
"lbryio/lbry-id/mail"
|
||||||
"lbryio/lbry-id/store"
|
"lbryio/lbry-id/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ type Server struct {
|
||||||
auth auth.AuthInterface
|
auth auth.AuthInterface
|
||||||
store store.StoreInterface
|
store store.StoreInterface
|
||||||
env env.EnvInterface
|
env env.EnvInterface
|
||||||
|
mail mail.MailInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO If I capitalize the `auth` `store` and `env` fields of Store{} I can
|
// TODO If I capitalize the `auth` `store` and `env` fields of Store{} I can
|
||||||
|
@ -42,8 +44,9 @@ func Init(
|
||||||
auth auth.AuthInterface,
|
auth auth.AuthInterface,
|
||||||
store store.StoreInterface,
|
store store.StoreInterface,
|
||||||
env env.EnvInterface,
|
env env.EnvInterface,
|
||||||
|
mail mail.MailInterface,
|
||||||
) *Server {
|
) *Server {
|
||||||
return &Server{auth, store, env}
|
return &Server{auth, store, env, mail}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrorResponse struct {
|
type ErrorResponse struct {
|
||||||
|
@ -149,7 +152,7 @@ func getGetData(w http.ResponseWriter, req *http.Request) bool {
|
||||||
// deviceId. Also this is apparently not idiomatic go error handling.
|
// deviceId. Also this is apparently not idiomatic go error handling.
|
||||||
func (s *Server) checkAuth(
|
func (s *Server) checkAuth(
|
||||||
w http.ResponseWriter,
|
w http.ResponseWriter,
|
||||||
token auth.TokenString,
|
token auth.AuthTokenString,
|
||||||
scope auth.AuthScope,
|
scope auth.AuthScope,
|
||||||
) *auth.AuthToken {
|
) *auth.AuthToken {
|
||||||
authToken, err := s.store.GetToken(token)
|
authToken, err := s.store.GetToken(token)
|
||||||
|
|
|
@ -17,6 +17,21 @@ import (
|
||||||
|
|
||||||
// Implementing interfaces for stubbed out packages
|
// Implementing interfaces for stubbed out packages
|
||||||
|
|
||||||
|
type SendVerificationEmailCall struct {
|
||||||
|
Email auth.Email
|
||||||
|
Token auth.VerifyTokenString
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestMail struct {
|
||||||
|
SendVerificationEmailError error
|
||||||
|
SendVerificationEmailCall *SendVerificationEmailCall
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TestMail) SendVerificationEmail(email auth.Email, token auth.VerifyTokenString) error {
|
||||||
|
m.SendVerificationEmailCall = &SendVerificationEmailCall{email, token}
|
||||||
|
return m.SendVerificationEmailError
|
||||||
|
}
|
||||||
|
|
||||||
type TestEnv struct {
|
type TestEnv struct {
|
||||||
env map[string]string
|
env map[string]string
|
||||||
}
|
}
|
||||||
|
@ -26,15 +41,23 @@ func (e *TestEnv) Getenv(key string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestAuth struct {
|
type TestAuth struct {
|
||||||
TestNewTokenString auth.TokenString
|
TestNewAuthTokenString auth.AuthTokenString
|
||||||
|
TestNewVerifyTokenString auth.VerifyTokenString
|
||||||
FailGenToken bool
|
FailGenToken bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *TestAuth) NewToken(userId auth.UserId, deviceId auth.DeviceId, scope auth.AuthScope) (*auth.AuthToken, error) {
|
func (a *TestAuth) NewAuthToken(userId auth.UserId, deviceId auth.DeviceId, scope auth.AuthScope) (*auth.AuthToken, error) {
|
||||||
if a.FailGenToken {
|
if a.FailGenToken {
|
||||||
return nil, fmt.Errorf("Test error: fail to generate token")
|
return nil, fmt.Errorf("Test error: fail to generate token")
|
||||||
}
|
}
|
||||||
return &auth.AuthToken{Token: a.TestNewTokenString, UserId: userId, DeviceId: deviceId, Scope: scope}, nil
|
return &auth.AuthToken{Token: a.TestNewAuthTokenString, UserId: userId, DeviceId: deviceId, Scope: scope}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TestAuth) NewVerifyTokenString() (auth.VerifyTokenString, error) {
|
||||||
|
if a.FailGenToken {
|
||||||
|
return "", fmt.Errorf("Test error: fail to generate token")
|
||||||
|
}
|
||||||
|
return a.TestNewVerifyTokenString, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type SetWalletCall struct {
|
type SetWalletCall struct {
|
||||||
|
@ -64,13 +87,13 @@ type CreateAccountCall struct {
|
||||||
Email auth.Email
|
Email auth.Email
|
||||||
Password auth.Password
|
Password auth.Password
|
||||||
ClientSaltSeed auth.ClientSaltSeed
|
ClientSaltSeed auth.ClientSaltSeed
|
||||||
Verified bool
|
VerifyToken auth.VerifyTokenString
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whether functions are called, and sometimes what they're called with
|
// Whether functions are called, and sometimes what they're called with
|
||||||
type TestStoreFunctionsCalled struct {
|
type TestStoreFunctionsCalled struct {
|
||||||
SaveToken auth.TokenString
|
SaveToken auth.AuthTokenString
|
||||||
GetToken auth.TokenString
|
GetToken auth.AuthTokenString
|
||||||
GetUserId bool
|
GetUserId bool
|
||||||
CreateAccount *CreateAccountCall
|
CreateAccount *CreateAccountCall
|
||||||
VerifyAccount bool
|
VerifyAccount bool
|
||||||
|
@ -103,7 +126,6 @@ type TestStore struct {
|
||||||
Errors TestStoreFunctionsErrors
|
Errors TestStoreFunctionsErrors
|
||||||
|
|
||||||
TestAuthToken auth.AuthToken
|
TestAuthToken auth.AuthToken
|
||||||
TestVerifyTokenString auth.VerifyTokenString
|
|
||||||
|
|
||||||
TestEncryptedWallet wallet.EncryptedWallet
|
TestEncryptedWallet wallet.EncryptedWallet
|
||||||
TestSequence wallet.Sequence
|
TestSequence wallet.Sequence
|
||||||
|
@ -117,7 +139,7 @@ func (s *TestStore) SaveToken(authToken *auth.AuthToken) error {
|
||||||
return s.Errors.SaveToken
|
return s.Errors.SaveToken
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TestStore) GetToken(token auth.TokenString) (*auth.AuthToken, error) {
|
func (s *TestStore) GetToken(token auth.AuthTokenString) (*auth.AuthToken, error) {
|
||||||
s.Called.GetToken = token
|
s.Called.GetToken = token
|
||||||
return &s.TestAuthToken, s.Errors.GetToken
|
return &s.TestAuthToken, s.Errors.GetToken
|
||||||
}
|
}
|
||||||
|
@ -127,12 +149,12 @@ func (s *TestStore) GetUserId(auth.Email, auth.Password) (auth.UserId, error) {
|
||||||
return 0, s.Errors.GetUserId
|
return 0, s.Errors.GetUserId
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TestStore) CreateAccount(email auth.Email, password auth.Password, clientSaltSeed auth.ClientSaltSeed, verified bool) error {
|
func (s *TestStore) CreateAccount(email auth.Email, password auth.Password, seed auth.ClientSaltSeed, verifyToken auth.VerifyTokenString) error {
|
||||||
s.Called.CreateAccount = &CreateAccountCall{
|
s.Called.CreateAccount = &CreateAccountCall{
|
||||||
Email: email,
|
Email: email,
|
||||||
Password: password,
|
Password: password,
|
||||||
ClientSaltSeed: clientSaltSeed,
|
ClientSaltSeed: seed,
|
||||||
Verified: verified,
|
VerifyToken: verifyToken,
|
||||||
}
|
}
|
||||||
return s.Errors.CreateAccount
|
return s.Errors.CreateAccount
|
||||||
}
|
}
|
||||||
|
@ -290,9 +312,9 @@ func TestServerHelperCheckAuth(t *testing.T) {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
testStore := TestStore{
|
testStore := TestStore{
|
||||||
Errors: tc.storeErrors,
|
Errors: tc.storeErrors,
|
||||||
TestAuthToken: auth.AuthToken{Token: auth.TokenString("seekrit"), Scope: tc.userScope},
|
TestAuthToken: auth.AuthToken{Token: auth.AuthTokenString("seekrit"), Scope: tc.userScope},
|
||||||
}
|
}
|
||||||
s := Server{&TestAuth{}, &testStore, &TestEnv{}}
|
s := Server{&TestAuth{}, &testStore, &TestEnv{}, &TestMail{}}
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
authToken := s.checkAuth(w, testStore.TestAuthToken.Token, tc.requiredScope)
|
authToken := s.checkAuth(w, testStore.TestAuthToken.Token, tc.requiredScope)
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type WalletRequest struct {
|
type WalletRequest struct {
|
||||||
Token auth.TokenString `json:"token"`
|
Token auth.AuthTokenString `json:"token"`
|
||||||
EncryptedWallet wallet.EncryptedWallet `json:"encryptedWallet"`
|
EncryptedWallet wallet.EncryptedWallet `json:"encryptedWallet"`
|
||||||
Sequence wallet.Sequence `json:"sequence"`
|
Sequence wallet.Sequence `json:"sequence"`
|
||||||
Hmac wallet.WalletHmac `json:"hmac"`
|
Hmac wallet.WalletHmac `json:"hmac"`
|
||||||
|
@ -54,7 +54,7 @@ func (s *Server) handleWallet(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
// TODO - There's probably a struct-based solution here like with POST/PUT.
|
// TODO - There's probably a struct-based solution here like with POST/PUT.
|
||||||
// We could put that struct up top as well.
|
// We could put that struct up top as well.
|
||||||
func getWalletParams(req *http.Request) (token auth.TokenString, err error) {
|
func getWalletParams(req *http.Request) (token auth.AuthTokenString, err error) {
|
||||||
tokenSlice, hasTokenSlice := req.URL.Query()["token"]
|
tokenSlice, hasTokenSlice := req.URL.Query()["token"]
|
||||||
|
|
||||||
if !hasTokenSlice || tokenSlice[0] == "" {
|
if !hasTokenSlice || tokenSlice[0] == "" {
|
||||||
|
@ -62,7 +62,7 @@ func getWalletParams(req *http.Request) (token auth.TokenString, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
token = auth.TokenString(tokenSlice[0])
|
token = auth.AuthTokenString(tokenSlice[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
func TestServerGetWallet(t *testing.T) {
|
func TestServerGetWallet(t *testing.T) {
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
name string
|
name string
|
||||||
tokenString auth.TokenString
|
tokenString auth.AuthTokenString
|
||||||
|
|
||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
expectedErrorString string
|
expectedErrorString string
|
||||||
|
@ -27,12 +27,12 @@ func TestServerGetWallet(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "success",
|
name: "success",
|
||||||
tokenString: auth.TokenString("seekrit"),
|
tokenString: auth.AuthTokenString("seekrit"),
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "validation error", // missing auth token
|
name: "validation error", // missing auth token
|
||||||
tokenString: auth.TokenString(""),
|
tokenString: auth.AuthTokenString(""),
|
||||||
expectedStatusCode: http.StatusBadRequest,
|
expectedStatusCode: http.StatusBadRequest,
|
||||||
expectedErrorString: http.StatusText(http.StatusBadRequest) + ": Missing token parameter",
|
expectedErrorString: http.StatusText(http.StatusBadRequest) + ": Missing token parameter",
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ func TestServerGetWallet(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "auth error",
|
name: "auth error",
|
||||||
tokenString: auth.TokenString("seekrit"),
|
tokenString: auth.AuthTokenString("seekrit"),
|
||||||
|
|
||||||
expectedStatusCode: http.StatusUnauthorized,
|
expectedStatusCode: http.StatusUnauthorized,
|
||||||
expectedErrorString: http.StatusText(http.StatusUnauthorized) + ": Token Not Found",
|
expectedErrorString: http.StatusText(http.StatusUnauthorized) + ": Token Not Found",
|
||||||
|
@ -51,7 +51,7 @@ func TestServerGetWallet(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "db error getting wallet",
|
name: "db error getting wallet",
|
||||||
tokenString: auth.TokenString("seekrit"),
|
tokenString: auth.AuthTokenString("seekrit"),
|
||||||
|
|
||||||
expectedStatusCode: http.StatusInternalServerError,
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
||||||
|
@ -65,7 +65,7 @@ func TestServerGetWallet(t *testing.T) {
|
||||||
testAuth := TestAuth{}
|
testAuth := TestAuth{}
|
||||||
testStore := TestStore{
|
testStore := TestStore{
|
||||||
TestAuthToken: auth.AuthToken{
|
TestAuthToken: auth.AuthToken{
|
||||||
Token: auth.TokenString(tc.tokenString),
|
Token: auth.AuthTokenString(tc.tokenString),
|
||||||
Scope: auth.ScopeFull,
|
Scope: auth.ScopeFull,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ func TestServerGetWallet(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
testEnv := TestEnv{}
|
testEnv := TestEnv{}
|
||||||
s := Server{&testAuth, &testStore, &testEnv}
|
s := Server{&testAuth, &testStore, &testEnv, &TestMail{}}
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, PathWallet, nil)
|
req := httptest.NewRequest(http.MethodGet, PathWallet, nil)
|
||||||
q := req.URL.Query()
|
q := req.URL.Query()
|
||||||
|
@ -228,14 +228,14 @@ func TestServerPostWallet(t *testing.T) {
|
||||||
testAuth := TestAuth{}
|
testAuth := TestAuth{}
|
||||||
testStore := TestStore{
|
testStore := TestStore{
|
||||||
TestAuthToken: auth.AuthToken{
|
TestAuthToken: auth.AuthToken{
|
||||||
Token: auth.TokenString("seekrit"),
|
Token: auth.AuthTokenString("seekrit"),
|
||||||
Scope: auth.ScopeFull,
|
Scope: auth.ScopeFull,
|
||||||
},
|
},
|
||||||
|
|
||||||
Errors: tc.storeErrors,
|
Errors: tc.storeErrors,
|
||||||
}
|
}
|
||||||
|
|
||||||
s := Server{&testAuth, &testStore, &TestEnv{}}
|
s := Server{&testAuth, &testStore, &TestEnv{}, &TestMail{}}
|
||||||
|
|
||||||
requestBody := []byte(
|
requestBody := []byte(
|
||||||
fmt.Sprintf(`{
|
fmt.Sprintf(`{
|
||||||
|
|
|
@ -43,7 +43,7 @@ func expectTokenExists(t *testing.T, s *Store, expectedToken auth.AuthToken) {
|
||||||
t.Fatalf("Expected token for: %s", expectedToken.Token)
|
t.Fatalf("Expected token for: %s", expectedToken.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func expectTokenNotExists(t *testing.T, s *Store, token auth.TokenString) {
|
func expectTokenNotExists(t *testing.T, s *Store, token auth.AuthTokenString) {
|
||||||
rows, err := s.db.Query("SELECT * FROM auth_tokens WHERE token=?", token)
|
rows, err := s.db.Query("SELECT * FROM auth_tokens WHERE token=?", token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error finding (lack of) token for: %s - %+v", token, err)
|
t.Fatalf("Error finding (lack of) token for: %s - %+v", token, err)
|
||||||
|
|
|
@ -17,7 +17,7 @@ func TestStoreChangePasswordSuccess(t *testing.T) {
|
||||||
defer StoreTestCleanup(sqliteTmpFile)
|
defer StoreTestCleanup(sqliteTmpFile)
|
||||||
|
|
||||||
userId, email, oldPassword, _ := makeTestUser(t, &s)
|
userId, email, oldPassword, _ := makeTestUser(t, &s)
|
||||||
token := auth.TokenString("my-token")
|
token := auth.AuthTokenString("my-token")
|
||||||
|
|
||||||
_, err := s.db.Exec(
|
_, err := s.db.Exec(
|
||||||
"INSERT INTO auth_tokens (token, user_id, device_id, scope, expiration) VALUES(?,?,?,?,?)",
|
"INSERT INTO auth_tokens (token, user_id, device_id, scope, expiration) VALUES(?,?,?,?,?)",
|
||||||
|
@ -117,7 +117,7 @@ func TestStoreChangePasswordErrors(t *testing.T) {
|
||||||
userId, email, oldPassword, oldSeed := makeTestUser(t, &s)
|
userId, email, oldPassword, oldSeed := makeTestUser(t, &s)
|
||||||
expiration := time.Now().UTC().Add(time.Hour * 24 * 14)
|
expiration := time.Now().UTC().Add(time.Hour * 24 * 14)
|
||||||
authToken := auth.AuthToken{
|
authToken := auth.AuthToken{
|
||||||
Token: auth.TokenString("my-token"),
|
Token: auth.AuthTokenString("my-token"),
|
||||||
DeviceId: auth.DeviceId("my-dev-id"),
|
DeviceId: auth.DeviceId("my-dev-id"),
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
Scope: auth.AuthScope("*"),
|
Scope: auth.AuthScope("*"),
|
||||||
|
@ -177,7 +177,7 @@ func TestStoreChangePasswordNoWalletSuccess(t *testing.T) {
|
||||||
defer StoreTestCleanup(sqliteTmpFile)
|
defer StoreTestCleanup(sqliteTmpFile)
|
||||||
|
|
||||||
userId, email, oldPassword, _ := makeTestUser(t, &s)
|
userId, email, oldPassword, _ := makeTestUser(t, &s)
|
||||||
token := auth.TokenString("my-token")
|
token := auth.AuthTokenString("my-token")
|
||||||
|
|
||||||
_, err := s.db.Exec(
|
_, err := s.db.Exec(
|
||||||
"INSERT INTO auth_tokens (token, user_id, device_id, scope, expiration) VALUES(?,?,?,?,?)",
|
"INSERT INTO auth_tokens (token, user_id, device_id, scope, expiration) VALUES(?,?,?,?,?)",
|
||||||
|
@ -249,7 +249,7 @@ func TestStoreChangePasswordNoWalletErrors(t *testing.T) {
|
||||||
userId, email, oldPassword, oldSeed := makeTestUser(t, &s)
|
userId, email, oldPassword, oldSeed := makeTestUser(t, &s)
|
||||||
expiration := time.Now().UTC().Add(time.Hour * 24 * 14)
|
expiration := time.Now().UTC().Add(time.Hour * 24 * 14)
|
||||||
authToken := auth.AuthToken{
|
authToken := auth.AuthToken{
|
||||||
Token: auth.TokenString("my-token"),
|
Token: auth.AuthTokenString("my-token"),
|
||||||
DeviceId: auth.DeviceId("my-dev-id"),
|
DeviceId: auth.DeviceId("my-dev-id"),
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
Scope: auth.AuthScope("*"),
|
Scope: auth.AuthScope("*"),
|
||||||
|
|
|
@ -37,11 +37,11 @@ var (
|
||||||
// For test stubs
|
// For test stubs
|
||||||
type StoreInterface interface {
|
type StoreInterface interface {
|
||||||
SaveToken(*auth.AuthToken) error
|
SaveToken(*auth.AuthToken) error
|
||||||
GetToken(auth.TokenString) (*auth.AuthToken, error)
|
GetToken(auth.AuthTokenString) (*auth.AuthToken, error)
|
||||||
SetWallet(auth.UserId, wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac) error
|
SetWallet(auth.UserId, wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac) error
|
||||||
GetWallet(auth.UserId) (wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac, error)
|
GetWallet(auth.UserId) (wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac, error)
|
||||||
GetUserId(auth.Email, auth.Password) (auth.UserId, error)
|
GetUserId(auth.Email, auth.Password) (auth.UserId, error)
|
||||||
CreateAccount(auth.Email, auth.Password, auth.ClientSaltSeed, bool) error
|
CreateAccount(auth.Email, auth.Password, auth.ClientSaltSeed, auth.VerifyTokenString) error
|
||||||
VerifyAccount(auth.VerifyTokenString) error
|
VerifyAccount(auth.VerifyTokenString) error
|
||||||
ChangePasswordWithWallet(auth.Email, auth.Password, auth.Password, auth.ClientSaltSeed, wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac) error
|
ChangePasswordWithWallet(auth.Email, auth.Password, auth.Password, auth.ClientSaltSeed, wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac) error
|
||||||
ChangePasswordNoWallet(auth.Email, auth.Password, auth.Password, auth.ClientSaltSeed) error
|
ChangePasswordNoWallet(auth.Email, auth.Password, auth.Password, auth.ClientSaltSeed) error
|
||||||
|
@ -143,7 +143,7 @@ func (s *Store) Migrate() error {
|
||||||
// (which I did previously)?
|
// (which I did previously)?
|
||||||
//
|
//
|
||||||
// TODO Put the timestamp in the token to avoid duplicates over time. And/or just use a library! Someone solved this already.
|
// TODO Put the timestamp in the token to avoid duplicates over time. And/or just use a library! Someone solved this already.
|
||||||
func (s *Store) GetToken(token auth.TokenString) (authToken *auth.AuthToken, err error) {
|
func (s *Store) GetToken(token auth.AuthTokenString) (authToken *auth.AuthToken, err error) {
|
||||||
expirationCutoff := time.Now().UTC()
|
expirationCutoff := time.Now().UTC()
|
||||||
|
|
||||||
authToken = &(auth.AuthToken{})
|
authToken = &(auth.AuthToken{})
|
||||||
|
@ -362,7 +362,7 @@ func (s *Store) GetUserId(email auth.Email, password auth.Password) (userId auth
|
||||||
// Account //
|
// Account //
|
||||||
/////////////
|
/////////////
|
||||||
|
|
||||||
func (s *Store) CreateAccount(email auth.Email, password auth.Password, seed auth.ClientSaltSeed, verified bool) (err error) {
|
func (s *Store) CreateAccount(email auth.Email, password auth.Password, seed auth.ClientSaltSeed, verifyToken auth.VerifyTokenString) (err error) {
|
||||||
key, salt, err := password.Create()
|
key, salt, err := password.Create()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in a new issue