mirror of
https://github.com/matrix-org/dendrite.git
synced 2026-01-09 07:03:10 -06:00
Login and Register tests for public key ethereum (#16)
* TestLoginPublicKeyNewSession * use asserts * setup, test, asserts * TestLoginPublicKeyValidAuthTypeMissingSession * invalid session id test * create a helper newSession function * TestLoginPublicKeyEthereumMissingUserId * TestLoginPublicKeyEthereumAccountNotAvailable * TestLoginPublicKeyEthereumInvalidUserId * createEip4361TestMessage * TestLoginPublicKeyEthereumMissingSignature * TestLoginPublicKeyEthereum * re-enable all publickey signin tests * move common publickey test util to its own file * register_public_key.go stub * refactored common ethereum test helpers to its own folder * refactor test helpers * return error in test helpers * fix regressions with ServerName * TestRegistrationUnimplementedAlgo * TestNewRegistration * TestNewRegistrationSession * verify new login session * remove assert * perform account creation * TestRegisterEthereum * Enable all tests * move helper functions into test file Co-authored-by: Tak Wai Wong <tak@hntlabs.com>
This commit is contained in:
parent
698369f5d6
commit
649aadb759
|
|
@ -30,10 +30,10 @@ import (
|
|||
|
||||
type LoginPublicKeyHandler interface {
|
||||
AccountExists(ctx context.Context) (string, *jsonerror.MatrixError)
|
||||
IsValidUserIdForRegistration(userId string) bool
|
||||
CreateLogin() *Login
|
||||
GetSession() string
|
||||
GetType() string
|
||||
IsValidUserId(userId string) bool
|
||||
ValidateLoginResponse() (bool, *jsonerror.MatrixError)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,10 @@ func (pk LoginPublicKeyEthereum) AccountExists(ctx context.Context) (string, *js
|
|||
return "", jsonerror.Forbidden("the address is incorrect, or the account does not exist.")
|
||||
}
|
||||
|
||||
if !pk.IsValidUserId(localPart) {
|
||||
return "", jsonerror.InvalidUsername("the username is not valid.")
|
||||
}
|
||||
|
||||
res := userapi.QueryAccountAvailabilityResponse{}
|
||||
if err := pk.userAPI.QueryAccountAvailability(ctx, &userapi.QueryAccountAvailabilityRequest{
|
||||
Localpart: localPart,
|
||||
|
|
@ -80,7 +84,7 @@ func (pk LoginPublicKeyEthereum) AccountExists(ctx context.Context) (string, *js
|
|||
return "", jsonerror.Unknown("failed to check availability: " + err.Error())
|
||||
}
|
||||
|
||||
if res.Available {
|
||||
if localPart == "" || res.Available {
|
||||
return "", jsonerror.Forbidden("the address is incorrect, account does not exist")
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +93,7 @@ func (pk LoginPublicKeyEthereum) AccountExists(ctx context.Context) (string, *js
|
|||
|
||||
var validChainAgnosticIdRegex = regexp.MustCompile("^eip155=3a[0-9]+=3a0x[0-9a-fA-F]+$")
|
||||
|
||||
func (pk LoginPublicKeyEthereum) IsValidUserIdForRegistration(userId string) bool {
|
||||
func (pk LoginPublicKeyEthereum) IsValidUserId(userId string) bool {
|
||||
// Verify that the user ID is a valid one according to spec.
|
||||
// https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md
|
||||
|
||||
|
|
@ -100,9 +104,9 @@ func (pk LoginPublicKeyEthereum) IsValidUserIdForRegistration(userId string) boo
|
|||
|
||||
isValid := validChainAgnosticIdRegex.MatchString(userId)
|
||||
|
||||
// In addition, double check that the user ID for registration
|
||||
// In addition, double check that the user ID
|
||||
// matches the authentication data in the request.
|
||||
return isValid && userId == pk.UserId
|
||||
return isValid && strings.ToLower(userId) == pk.UserId
|
||||
}
|
||||
|
||||
func (pk LoginPublicKeyEthereum) ValidateLoginResponse() (bool, *jsonerror.MatrixError) {
|
||||
|
|
|
|||
472
clientapi/auth/login_publickey_ethereum_test.go
Normal file
472
clientapi/auth/login_publickey_ethereum_test.go
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/internal/mapsutil"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/test"
|
||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type loginContext struct {
|
||||
config *config.ClientAPI
|
||||
userInteractive *UserInteractive
|
||||
}
|
||||
|
||||
func createLoginContext(t *testing.T) *loginContext {
|
||||
chainIds := []int{4}
|
||||
|
||||
cfg := &config.ClientAPI{
|
||||
Matrix: &config.Global{
|
||||
ServerName: test.TestServerName,
|
||||
},
|
||||
Derived: &config.Derived{},
|
||||
PasswordAuthenticationDisabled: true,
|
||||
PublicKeyAuthentication: config.PublicKeyAuthentication{
|
||||
Ethereum: config.EthereumAuthConfig{
|
||||
Enabled: true,
|
||||
Version: 1,
|
||||
ChainIDs: chainIds,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pkFlows := cfg.PublicKeyAuthentication.GetPublicKeyRegistrationFlows()
|
||||
cfg.Derived.Registration.Flows = append(cfg.Derived.Registration.Flows, pkFlows...)
|
||||
pkParams := cfg.PublicKeyAuthentication.GetPublicKeyRegistrationParams()
|
||||
cfg.Derived.Registration.Params = mapsutil.MapsUnion(cfg.Derived.Registration.Params, pkParams)
|
||||
|
||||
var userAPI fakePublicKeyUserApi
|
||||
var loginApi uapi.UserLoginAPI
|
||||
|
||||
userInteractive := NewUserInteractive(
|
||||
loginApi,
|
||||
&userAPI,
|
||||
cfg)
|
||||
|
||||
return &loginContext{
|
||||
config: cfg,
|
||||
userInteractive: userInteractive,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type fakePublicKeyUserApi struct {
|
||||
UserInternalAPIForLogin
|
||||
uapi.UserLoginAPI
|
||||
uapi.ClientUserAPI
|
||||
DeletedTokens []string
|
||||
}
|
||||
|
||||
func (ua *fakePublicKeyUserApi) QueryAccountAvailability(ctx context.Context, req *uapi.QueryAccountAvailabilityRequest, res *uapi.QueryAccountAvailabilityResponse) error {
|
||||
if req.Localpart == "does_not_exist" {
|
||||
res.Available = true
|
||||
return nil
|
||||
}
|
||||
|
||||
res.Available = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ua *fakePublicKeyUserApi) QueryAccountByPassword(ctx context.Context, req *uapi.QueryAccountByPasswordRequest, res *uapi.QueryAccountByPasswordResponse) error {
|
||||
if req.PlaintextPassword == "invalidpassword" {
|
||||
res.Account = nil
|
||||
return nil
|
||||
}
|
||||
res.Exists = true
|
||||
res.Account = &uapi.Account{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ua *fakePublicKeyUserApi) PerformLoginTokenDeletion(ctx context.Context, req *uapi.PerformLoginTokenDeletionRequest, res *uapi.PerformLoginTokenDeletionResponse) error {
|
||||
ua.DeletedTokens = append(ua.DeletedTokens, req.Token)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ua *fakePublicKeyUserApi) PerformLoginTokenCreation(ctx context.Context, req *uapi.PerformLoginTokenCreationRequest, res *uapi.PerformLoginTokenCreationResponse) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*fakePublicKeyUserApi) QueryLoginToken(ctx context.Context, req *uapi.QueryLoginTokenRequest, res *uapi.QueryLoginTokenResponse) error {
|
||||
if req.Token == "invalidtoken" {
|
||||
return nil
|
||||
}
|
||||
|
||||
res.Data = &uapi.LoginTokenData{UserID: "@auser:example.com"}
|
||||
return nil
|
||||
}
|
||||
|
||||
func publicKeyTestSession(
|
||||
ctx *context.Context,
|
||||
cfg *config.ClientAPI,
|
||||
userInteractive *UserInteractive,
|
||||
userAPI *fakePublicKeyUserApi,
|
||||
|
||||
) string {
|
||||
emptyAuth := struct {
|
||||
Body string
|
||||
}{
|
||||
Body: `{
|
||||
"type": "m.login.publickey"
|
||||
}`,
|
||||
}
|
||||
|
||||
_, cleanup, err := LoginFromJSONReader(
|
||||
*ctx,
|
||||
strings.NewReader(emptyAuth.Body),
|
||||
userAPI,
|
||||
userAPI,
|
||||
userAPI,
|
||||
userInteractive,
|
||||
cfg)
|
||||
|
||||
if cleanup != nil {
|
||||
cleanup(*ctx, nil)
|
||||
}
|
||||
|
||||
json := err.JSON.(Challenge)
|
||||
return json.Session
|
||||
}
|
||||
|
||||
func TestLoginPublicKeyEthereum(t *testing.T) {
|
||||
// Setup
|
||||
var userAPI fakePublicKeyUserApi
|
||||
ctx := context.Background()
|
||||
loginContext := createLoginContext(t)
|
||||
wallet, _ := test.CreateTestAccount()
|
||||
message, _ := test.CreateEip4361TestMessage(wallet.PublicAddress)
|
||||
signature, _ := test.SignMessage(message.String(), wallet.PrivateKey)
|
||||
sessionId := publicKeyTestSession(
|
||||
&ctx,
|
||||
loginContext.config,
|
||||
loginContext.userInteractive,
|
||||
&userAPI,
|
||||
)
|
||||
|
||||
// Escape \t and \n. Work around for marshalling and unmarshalling message.
|
||||
msgStr := test.FromEip4361MessageToString(message)
|
||||
body := fmt.Sprintf(`{
|
||||
"type": "m.login.publickey",
|
||||
"auth": {
|
||||
"type": "m.login.publickey.ethereum",
|
||||
"session": "%v",
|
||||
"user_id": "%v",
|
||||
"message": "%v",
|
||||
"signature": "%v"
|
||||
}
|
||||
}`,
|
||||
sessionId,
|
||||
wallet.Eip155UserId,
|
||||
msgStr,
|
||||
signature,
|
||||
)
|
||||
test := struct {
|
||||
Body string
|
||||
}{
|
||||
Body: body,
|
||||
}
|
||||
|
||||
// Test
|
||||
login, cleanup, err := LoginFromJSONReader(
|
||||
ctx,
|
||||
strings.NewReader(test.Body),
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
loginContext.userInteractive,
|
||||
loginContext.config)
|
||||
|
||||
if cleanup != nil {
|
||||
cleanup(ctx, nil)
|
||||
}
|
||||
|
||||
// Asserts
|
||||
assert := assert.New(t)
|
||||
assert.Nilf(err, "err actual: %v, expected: nil", err)
|
||||
assert.NotNil(login, "login: actual: nil, expected: not nil")
|
||||
assert.Truef(
|
||||
login.Identifier.Type == "m.id.decentralizedid",
|
||||
"login.Identifier.Type actual: %v, expected: %v", login.Identifier.Type, "m.id.decentralizedid")
|
||||
walletAddress := strings.ToLower(wallet.Eip155UserId)
|
||||
assert.Truef(
|
||||
login.Identifier.User == walletAddress,
|
||||
"login.Identifier.User actual: %v, expected: %v", login.Identifier.User, walletAddress)
|
||||
}
|
||||
|
||||
func TestLoginPublicKeyEthereumMissingSignature(t *testing.T) {
|
||||
// Setup
|
||||
var userAPI fakePublicKeyUserApi
|
||||
ctx := context.Background()
|
||||
loginContext := createLoginContext(t)
|
||||
wallet, _ := test.CreateTestAccount()
|
||||
message, _ := test.CreateEip4361TestMessage(wallet.PublicAddress)
|
||||
sessionId := publicKeyTestSession(
|
||||
&ctx,
|
||||
loginContext.config,
|
||||
loginContext.userInteractive,
|
||||
&userAPI,
|
||||
)
|
||||
|
||||
// Escape \t and \n. Work around for marshalling and unmarshalling message.
|
||||
msgStr := test.FromEip4361MessageToString(message)
|
||||
body := fmt.Sprintf(`{
|
||||
"type": "m.login.publickey",
|
||||
"auth": {
|
||||
"type": "m.login.publickey.ethereum",
|
||||
"session": "%v",
|
||||
"user_id": "%v",
|
||||
"message": "%v"
|
||||
}
|
||||
}`,
|
||||
sessionId,
|
||||
wallet.Eip155UserId,
|
||||
msgStr,
|
||||
)
|
||||
test := struct {
|
||||
Body string
|
||||
}{
|
||||
Body: body,
|
||||
}
|
||||
|
||||
// Test
|
||||
_, cleanup, err := LoginFromJSONReader(
|
||||
ctx,
|
||||
strings.NewReader(test.Body),
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
loginContext.userInteractive,
|
||||
loginContext.config)
|
||||
|
||||
if cleanup != nil {
|
||||
cleanup(ctx, nil)
|
||||
}
|
||||
|
||||
// Asserts
|
||||
assert := assert.New(t)
|
||||
assert.Truef(
|
||||
err.Code == http.StatusUnauthorized,
|
||||
"err.Code actual: %v, expected: %v", err.Code, http.StatusUnauthorized)
|
||||
json := err.JSON.(*jsonerror.MatrixError)
|
||||
expectedErr := jsonerror.InvalidSignature("")
|
||||
assert.Truef(
|
||||
json.ErrCode == expectedErr.ErrCode,
|
||||
"err.JSON.ErrCode actual: %v, expected: %v", json.ErrCode, expectedErr.ErrCode)
|
||||
}
|
||||
|
||||
func TestLoginPublicKeyEthereumEmptyMessage(t *testing.T) {
|
||||
// Setup
|
||||
var userAPI fakePublicKeyUserApi
|
||||
ctx := context.Background()
|
||||
loginContext := createLoginContext(t)
|
||||
wallet, _ := test.CreateTestAccount()
|
||||
sessionId := publicKeyTestSession(
|
||||
&ctx,
|
||||
loginContext.config,
|
||||
loginContext.userInteractive,
|
||||
&userAPI,
|
||||
)
|
||||
|
||||
body := fmt.Sprintf(`{
|
||||
"type": "m.login.publickey",
|
||||
"auth": {
|
||||
"type": "m.login.publickey.ethereum",
|
||||
"session": "%v",
|
||||
"user_id": "%v"
|
||||
}
|
||||
}`, sessionId, wallet.Eip155UserId)
|
||||
test := struct {
|
||||
Body string
|
||||
}{
|
||||
Body: body,
|
||||
}
|
||||
|
||||
// Test
|
||||
_, cleanup, err := LoginFromJSONReader(
|
||||
ctx,
|
||||
strings.NewReader(test.Body),
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
loginContext.userInteractive,
|
||||
loginContext.config)
|
||||
|
||||
if cleanup != nil {
|
||||
cleanup(ctx, nil)
|
||||
}
|
||||
|
||||
// Asserts
|
||||
assert := assert.New(t)
|
||||
assert.Truef(
|
||||
err.Code == http.StatusUnauthorized,
|
||||
"err.Code actual: %v, expected: %v", err.Code, http.StatusUnauthorized)
|
||||
json := err.JSON.(*jsonerror.MatrixError)
|
||||
expectedErr := jsonerror.InvalidParam("")
|
||||
assert.Truef(
|
||||
json.ErrCode == expectedErr.ErrCode,
|
||||
"err.JSON.ErrCode actual: %v, expected: %v", json.ErrCode, expectedErr.ErrCode)
|
||||
}
|
||||
|
||||
func TestLoginPublicKeyEthereumWrongUserId(t *testing.T) {
|
||||
// Setup
|
||||
var userAPI fakePublicKeyUserApi
|
||||
ctx := context.Background()
|
||||
loginContext := createLoginContext(t)
|
||||
wallet, _ := test.CreateTestAccount()
|
||||
sessionId := publicKeyTestSession(
|
||||
&ctx,
|
||||
loginContext.config,
|
||||
loginContext.userInteractive,
|
||||
&userAPI,
|
||||
)
|
||||
|
||||
body := fmt.Sprintf(`{
|
||||
"type": "m.login.publickey",
|
||||
"auth": {
|
||||
"type": "m.login.publickey.ethereum",
|
||||
"session": "%v",
|
||||
"user_id": "%v"
|
||||
}
|
||||
}`,
|
||||
sessionId,
|
||||
wallet.PublicAddress)
|
||||
test := struct {
|
||||
Body string
|
||||
}{
|
||||
Body: body,
|
||||
}
|
||||
|
||||
// Test
|
||||
_, cleanup, err := LoginFromJSONReader(
|
||||
ctx,
|
||||
strings.NewReader(test.Body),
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
loginContext.userInteractive,
|
||||
loginContext.config)
|
||||
|
||||
if cleanup != nil {
|
||||
cleanup(ctx, nil)
|
||||
}
|
||||
|
||||
// Asserts
|
||||
assert := assert.New(t)
|
||||
assert.Truef(
|
||||
err.Code == http.StatusForbidden,
|
||||
"err.Code actual: %v, expected: %v", err.Code, http.StatusForbidden)
|
||||
}
|
||||
|
||||
func TestLoginPublicKeyEthereumMissingUserId(t *testing.T) {
|
||||
// Setup
|
||||
var userAPI fakePublicKeyUserApi
|
||||
ctx := context.Background()
|
||||
loginContext := createLoginContext(t)
|
||||
sessionId := publicKeyTestSession(
|
||||
&ctx,
|
||||
loginContext.config,
|
||||
loginContext.userInteractive,
|
||||
&userAPI,
|
||||
)
|
||||
|
||||
body := fmt.Sprintf(`{
|
||||
"type": "m.login.publickey",
|
||||
"auth": {
|
||||
"type": "m.login.publickey.ethereum",
|
||||
"session": "%v"
|
||||
}
|
||||
}`, sessionId)
|
||||
test := struct {
|
||||
Body string
|
||||
}{
|
||||
Body: body,
|
||||
}
|
||||
|
||||
// Test
|
||||
_, cleanup, err := LoginFromJSONReader(
|
||||
ctx,
|
||||
strings.NewReader(test.Body),
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
loginContext.userInteractive,
|
||||
loginContext.config)
|
||||
|
||||
if cleanup != nil {
|
||||
cleanup(ctx, nil)
|
||||
}
|
||||
|
||||
// Asserts
|
||||
assert := assert.New(t)
|
||||
assert.Truef(
|
||||
err.Code == http.StatusForbidden,
|
||||
"err.Code actual: %v, expected: %v", err.Code, http.StatusForbidden)
|
||||
}
|
||||
|
||||
func TestLoginPublicKeyEthereumAccountNotAvailable(t *testing.T) {
|
||||
// Setup
|
||||
var userAPI fakePublicKeyUserApi
|
||||
ctx := context.Background()
|
||||
loginContext := createLoginContext(t)
|
||||
sessionId := publicKeyTestSession(
|
||||
&ctx,
|
||||
loginContext.config,
|
||||
loginContext.userInteractive,
|
||||
&userAPI,
|
||||
)
|
||||
|
||||
body := fmt.Sprintf(`{
|
||||
"type": "m.login.publickey",
|
||||
"auth": {
|
||||
"type": "m.login.publickey.ethereum",
|
||||
"session": "%v",
|
||||
"user_id": "does_not_exist"
|
||||
}
|
||||
}`, sessionId)
|
||||
test := struct {
|
||||
Body string
|
||||
}{
|
||||
Body: body,
|
||||
}
|
||||
|
||||
// Test
|
||||
_, cleanup, err := LoginFromJSONReader(
|
||||
ctx,
|
||||
strings.NewReader(test.Body),
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
loginContext.userInteractive,
|
||||
loginContext.config)
|
||||
|
||||
if cleanup != nil {
|
||||
cleanup(ctx, nil)
|
||||
}
|
||||
|
||||
// Asserts
|
||||
assert := assert.New(t)
|
||||
assert.Truef(
|
||||
err.Code == http.StatusForbidden,
|
||||
"err.Code actual: %v, expected: %v", err.Code, http.StatusForbidden)
|
||||
}
|
||||
161
clientapi/auth/login_publickey_test.go
Normal file
161
clientapi/auth/login_publickey_test.go
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLoginPublicKeyNewSession(t *testing.T) {
|
||||
// Setup
|
||||
var userAPI fakePublicKeyUserApi
|
||||
ctx := context.Background()
|
||||
loginContext := createLoginContext(t)
|
||||
|
||||
test := struct {
|
||||
Body string
|
||||
}{
|
||||
Body: `{ "type": "m.login.publickey" }`,
|
||||
}
|
||||
|
||||
// Test
|
||||
login, cleanup, err := LoginFromJSONReader(
|
||||
ctx,
|
||||
strings.NewReader(test.Body),
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
loginContext.userInteractive,
|
||||
loginContext.config)
|
||||
|
||||
if cleanup != nil {
|
||||
cleanup(ctx, nil)
|
||||
}
|
||||
|
||||
// Asserts
|
||||
assert := assert.New(t)
|
||||
assert.NotNilf(
|
||||
err,
|
||||
"err actual: not nil returned %+v, expected: nil", login)
|
||||
assert.Truef(
|
||||
err.Code == http.StatusUnauthorized,
|
||||
"err.Code actual: %v, expected: %v", err.Code, http.StatusUnauthorized)
|
||||
challenge := err.JSON.(Challenge)
|
||||
assert.NotEmptyf(challenge.Session, "challenge.Session")
|
||||
assert.NotEmptyf(challenge.Completed, "challenge.Completed")
|
||||
assert.Truef(
|
||||
authtypes.LoginTypePublicKeyEthereum == challenge.Flows[0].Stages[0],
|
||||
"challenge.Flows[0].Stages[0] actual: %v, expected: %v", challenge.Flows[0].Stages[0], authtypes.LoginTypePublicKeyEthereum)
|
||||
params := challenge.Params[authtypes.LoginTypePublicKeyEthereum]
|
||||
assert.NotEmptyf(
|
||||
params,
|
||||
"challenge.Params[\"%v\"] actual %v, expected %v",
|
||||
authtypes.LoginTypePublicKeyEthereum,
|
||||
params,
|
||||
"[object]")
|
||||
ethParams := params.(config.EthereumAuthParams)
|
||||
assert.NotEmptyf(ethParams.ChainIDs, "ChainIDs actual: empty, expected not empty")
|
||||
assert.NotEmptyf(ethParams.Nonce, "Nonce actual: \"\", expected: not empty")
|
||||
assert.NotEmptyf(ethParams.Version, "Version actual: \"\", expected: not empty")
|
||||
}
|
||||
|
||||
func TestLoginPublicKeyInvalidSessionId(t *testing.T) {
|
||||
// Setup
|
||||
var userAPI fakePublicKeyUserApi
|
||||
ctx := context.Background()
|
||||
loginContext := createLoginContext(t)
|
||||
|
||||
test := struct {
|
||||
Body string
|
||||
}{
|
||||
Body: `{
|
||||
"type": "m.login.publickey",
|
||||
"auth": {
|
||||
"type": "m.login.publickey.ethereum",
|
||||
"session": "invalid_session_id"
|
||||
}
|
||||
}`,
|
||||
}
|
||||
|
||||
// Test
|
||||
_, cleanup, err := LoginFromJSONReader(
|
||||
ctx,
|
||||
strings.NewReader(test.Body),
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
loginContext.userInteractive,
|
||||
loginContext.config)
|
||||
|
||||
if cleanup != nil {
|
||||
cleanup(ctx, nil)
|
||||
}
|
||||
|
||||
// Asserts
|
||||
assert := assert.New(t)
|
||||
assert.Truef(
|
||||
err.Code == http.StatusUnauthorized,
|
||||
"err.Code actual %v, expected %v", err.Code, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func TestLoginPublicKeyInvalidAuthType(t *testing.T) {
|
||||
// Setup
|
||||
var userAPI fakePublicKeyUserApi
|
||||
ctx := context.Background()
|
||||
loginContext := createLoginContext(t)
|
||||
|
||||
test := struct {
|
||||
Body string
|
||||
}{
|
||||
Body: `{
|
||||
"type": "m.login.publickey",
|
||||
"auth": {
|
||||
"type": "m.login.publickey.someAlgo"
|
||||
}
|
||||
}`,
|
||||
}
|
||||
|
||||
// Test
|
||||
_, cleanup, err := LoginFromJSONReader(
|
||||
ctx,
|
||||
strings.NewReader(test.Body),
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
&userAPI,
|
||||
loginContext.userInteractive,
|
||||
loginContext.config)
|
||||
|
||||
if cleanup != nil {
|
||||
cleanup(ctx, nil)
|
||||
}
|
||||
|
||||
// Asserts
|
||||
assert := assert.New(t)
|
||||
assert.NotNil(err, "Expected an err response.actual: nil")
|
||||
assert.Truef(
|
||||
err.Code == http.StatusUnauthorized,
|
||||
"err.Code actual %v, expected %v", err.Code, http.StatusUnauthorized)
|
||||
_, ok := err.JSON.(Challenge)
|
||||
assert.False(
|
||||
ok,
|
||||
"should not return a Challenge response")
|
||||
}
|
||||
|
|
@ -173,13 +173,13 @@ type Challenge struct {
|
|||
func (u *UserInteractive) Challenge(sessionID string) *util.JSONResponse {
|
||||
paramsCopy := mapsutil.MapCopy(u.Params)
|
||||
for key, element := range paramsCopy {
|
||||
p := getAuthParams(element)
|
||||
p := GetAuthParams(element)
|
||||
if p != nil {
|
||||
// If an auth flow has params,
|
||||
// send it as part of the challenge.
|
||||
paramsCopy[key] = p
|
||||
|
||||
// If an auth flow generated a nonce, track it as well.
|
||||
// If an auth flow generated a nonce, add it to the session.
|
||||
nonce := getAuthParamNonce(p)
|
||||
if nonce != "" {
|
||||
u.Sessions[sessionID] = append(u.Sessions[sessionID], nonce)
|
||||
|
|
@ -280,7 +280,7 @@ func (u *UserInteractive) Verify(ctx context.Context, bodyBytes []byte, device *
|
|||
return login, nil
|
||||
}
|
||||
|
||||
func getAuthParams(params interface{}) interface{} {
|
||||
func GetAuthParams(params interface{}) interface{} {
|
||||
v, ok := params.(config.AuthParams)
|
||||
if ok {
|
||||
p := v.GetParams()
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
"github.com/matrix-org/dendrite/internal/mapsutil"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
|
@ -247,7 +248,7 @@ type authDict struct {
|
|||
}
|
||||
|
||||
// http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#user-interactive-authentication-api
|
||||
type userInteractiveResponse struct {
|
||||
type UserInteractiveResponse struct {
|
||||
Flows []authtypes.Flow `json:"flows"`
|
||||
Completed []authtypes.LoginType `json:"completed"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
|
|
@ -260,9 +261,18 @@ func newUserInteractiveResponse(
|
|||
sessionID string,
|
||||
fs []authtypes.Flow,
|
||||
params map[string]interface{},
|
||||
) userInteractiveResponse {
|
||||
return userInteractiveResponse{
|
||||
fs, sessions.getCompletedStages(sessionID), params, sessionID,
|
||||
) UserInteractiveResponse {
|
||||
paramsCopy := mapsutil.MapCopy(params)
|
||||
for key, element := range paramsCopy {
|
||||
p := auth.GetAuthParams(element)
|
||||
if p != nil {
|
||||
// If an auth flow has params, make a new copy
|
||||
// and send it as part of the response.
|
||||
paramsCopy[key] = p
|
||||
}
|
||||
}
|
||||
return UserInteractiveResponse{
|
||||
fs, sessions.getCompletedStages(sessionID), paramsCopy, sessionID,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ func handlePublicKeyRegistration(
|
|||
}
|
||||
}
|
||||
|
||||
isValidUserId := authHandler.IsValidUserIdForRegistration(r.Username)
|
||||
isValidUserId := authHandler.IsValidUserId(r.Username)
|
||||
if !isValidUserId {
|
||||
return false, "", &util.JSONResponse{
|
||||
Code: http.StatusUnauthorized,
|
||||
|
|
|
|||
386
clientapi/routing/register_publickey_test.go
Normal file
386
clientapi/routing/register_publickey_test.go
Normal file
|
|
@ -0,0 +1,386 @@
|
|||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/internal/mapsutil"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/test"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const testCaip10UserId = "eip155=3a1=3a0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb"
|
||||
|
||||
type registerContext struct {
|
||||
config *config.ClientAPI
|
||||
userInteractive *auth.UserInteractive
|
||||
}
|
||||
|
||||
func createRegisterContext(t *testing.T) *registerContext {
|
||||
chainIds := []int{4}
|
||||
|
||||
cfg := &config.ClientAPI{
|
||||
Matrix: &config.Global{
|
||||
ServerName: test.TestServerName,
|
||||
},
|
||||
Derived: &config.Derived{},
|
||||
PasswordAuthenticationDisabled: true,
|
||||
PublicKeyAuthentication: config.PublicKeyAuthentication{
|
||||
Ethereum: config.EthereumAuthConfig{
|
||||
Enabled: true,
|
||||
Version: 1,
|
||||
ChainIDs: chainIds,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pkFlows := cfg.PublicKeyAuthentication.GetPublicKeyRegistrationFlows()
|
||||
cfg.Derived.Registration.Flows = append(cfg.Derived.Registration.Flows, pkFlows...)
|
||||
pkParams := cfg.PublicKeyAuthentication.GetPublicKeyRegistrationParams()
|
||||
cfg.Derived.Registration.Params = mapsutil.MapsUnion(cfg.Derived.Registration.Params, pkParams)
|
||||
|
||||
var userAPI fakePublicKeyUserApi
|
||||
var loginApi uapi.UserLoginAPI
|
||||
|
||||
userInteractive := auth.NewUserInteractive(
|
||||
loginApi,
|
||||
&userAPI,
|
||||
cfg)
|
||||
|
||||
return ®isterContext{
|
||||
config: cfg,
|
||||
userInteractive: userInteractive,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type fakeHttpRequest struct {
|
||||
request *http.Request
|
||||
body []byte
|
||||
registerRequest registerRequest
|
||||
}
|
||||
|
||||
func createFakeHttpRequest(body string) *fakeHttpRequest {
|
||||
var r registerRequest
|
||||
req, _ := http.NewRequest(http.MethodPost, "", strings.NewReader(body))
|
||||
reqBody := []byte(body)
|
||||
json.Unmarshal([]byte(body), &r)
|
||||
|
||||
return &fakeHttpRequest{
|
||||
request: req,
|
||||
body: reqBody,
|
||||
registerRequest: r,
|
||||
}
|
||||
}
|
||||
|
||||
type fakePublicKeyUserApi struct {
|
||||
auth.UserInternalAPIForLogin
|
||||
uapi.UserLoginAPI
|
||||
uapi.ClientUserAPI
|
||||
DeletedTokens []string
|
||||
}
|
||||
|
||||
func (ua *fakePublicKeyUserApi) QueryAccountAvailability(ctx context.Context, req *uapi.QueryAccountAvailabilityRequest, res *uapi.QueryAccountAvailabilityResponse) error {
|
||||
if req.Localpart == "does_not_exist" {
|
||||
res.Available = true
|
||||
return nil
|
||||
}
|
||||
|
||||
res.Available = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ua *fakePublicKeyUserApi) QueryAccountByPassword(ctx context.Context, req *uapi.QueryAccountByPasswordRequest, res *uapi.QueryAccountByPasswordResponse) error {
|
||||
if req.PlaintextPassword == "invalidpassword" {
|
||||
res.Account = nil
|
||||
return nil
|
||||
}
|
||||
res.Exists = true
|
||||
res.Account = &uapi.Account{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ua *fakePublicKeyUserApi) PerformDeviceCreation(
|
||||
ctx context.Context,
|
||||
req *uapi.PerformDeviceCreationRequest,
|
||||
res *uapi.PerformDeviceCreationResponse) error {
|
||||
res.DeviceCreated = true
|
||||
res.Device = &api.Device{
|
||||
ID: "device_id",
|
||||
UserID: req.Localpart,
|
||||
AccessToken: req.AccessToken,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ua *fakePublicKeyUserApi) PerformAccountCreation(
|
||||
ctx context.Context,
|
||||
req *uapi.PerformAccountCreationRequest,
|
||||
res *uapi.PerformAccountCreationResponse) error {
|
||||
res.AccountCreated = true
|
||||
res.Account = &api.Account{
|
||||
AppServiceID: req.AppServiceID,
|
||||
Localpart: req.Localpart,
|
||||
ServerName: test.TestServerName,
|
||||
UserID: fmt.Sprintf("@%s:%s", req.Localpart, test.TestServerName),
|
||||
AccountType: req.AccountType,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ua *fakePublicKeyUserApi) PerformLoginTokenDeletion(ctx context.Context, req *uapi.PerformLoginTokenDeletionRequest, res *uapi.PerformLoginTokenDeletionResponse) error {
|
||||
ua.DeletedTokens = append(ua.DeletedTokens, req.Token)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ua *fakePublicKeyUserApi) PerformLoginTokenCreation(ctx context.Context, req *uapi.PerformLoginTokenCreationRequest, res *uapi.PerformLoginTokenCreationResponse) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*fakePublicKeyUserApi) QueryLoginToken(ctx context.Context, req *uapi.QueryLoginTokenRequest, res *uapi.QueryLoginTokenResponse) error {
|
||||
if req.Token == "invalidtoken" {
|
||||
return nil
|
||||
}
|
||||
|
||||
res.Data = &uapi.LoginTokenData{UserID: "@auser:example.com"}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newRegistrationSession(
|
||||
t *testing.T,
|
||||
userId string,
|
||||
cfg *config.ClientAPI,
|
||||
userInteractive *auth.UserInteractive,
|
||||
userAPI *fakePublicKeyUserApi,
|
||||
) string {
|
||||
body := fmt.Sprintf(`{
|
||||
"auth": {
|
||||
"type": "m.login.publickey",
|
||||
"username": "%v"
|
||||
}
|
||||
}`,
|
||||
userId)
|
||||
|
||||
test := struct {
|
||||
Body string
|
||||
}{
|
||||
Body: body,
|
||||
}
|
||||
|
||||
fakeReq := createFakeHttpRequest(test.Body)
|
||||
sessionID := util.RandomString(sessionIDLength)
|
||||
registerContext := createRegisterContext(t)
|
||||
|
||||
// Test
|
||||
response := handleRegistrationFlow(
|
||||
fakeReq.request,
|
||||
fakeReq.body,
|
||||
fakeReq.registerRequest,
|
||||
sessionID,
|
||||
registerContext.config,
|
||||
userAPI,
|
||||
"",
|
||||
nil,
|
||||
)
|
||||
|
||||
json := response.JSON.(UserInteractiveResponse)
|
||||
return json.Session
|
||||
}
|
||||
|
||||
func TestRegisterEthereum(t *testing.T) {
|
||||
// Setup
|
||||
var userAPI fakePublicKeyUserApi
|
||||
wallet, _ := test.CreateTestAccount()
|
||||
message, _ := test.CreateEip4361TestMessage(wallet.PublicAddress)
|
||||
signature, _ := test.SignMessage(message.String(), wallet.PrivateKey)
|
||||
registerContext := createRegisterContext(t)
|
||||
sessionId := newRegistrationSession(
|
||||
t,
|
||||
wallet.Eip155UserId,
|
||||
registerContext.config,
|
||||
registerContext.userInteractive,
|
||||
&userAPI,
|
||||
)
|
||||
|
||||
// Escape \t and \n. Work around for marshalling and unmarshalling message.
|
||||
msgStr := test.FromEip4361MessageToString(message)
|
||||
body := fmt.Sprintf(`{
|
||||
"username": "%v",
|
||||
"auth": {
|
||||
"type": "m.login.publickey",
|
||||
"session": "%v",
|
||||
"public_key_response": {
|
||||
"type": "m.login.publickey.ethereum",
|
||||
"session": "%v",
|
||||
"user_id": "%v",
|
||||
"message": "%v",
|
||||
"signature": "%v"
|
||||
}
|
||||
}
|
||||
}`,
|
||||
wallet.Eip155UserId,
|
||||
sessionId,
|
||||
sessionId,
|
||||
wallet.Eip155UserId,
|
||||
msgStr,
|
||||
signature,
|
||||
)
|
||||
test := struct {
|
||||
Body string
|
||||
}{
|
||||
Body: body,
|
||||
}
|
||||
|
||||
fakeReq := createFakeHttpRequest(test.Body)
|
||||
|
||||
// Test
|
||||
response := handleRegistrationFlow(
|
||||
fakeReq.request,
|
||||
fakeReq.body,
|
||||
fakeReq.registerRequest,
|
||||
sessionId,
|
||||
registerContext.config,
|
||||
&userAPI,
|
||||
"",
|
||||
nil,
|
||||
)
|
||||
|
||||
// Asserts
|
||||
assert := assert.New(t)
|
||||
assert.NotNil(response, "response actual: nil, expected: not nil")
|
||||
registerRes := response.JSON.(registerResponse)
|
||||
assert.Truef(
|
||||
registerRes.UserID == wallet.Eip155UserId,
|
||||
"registerRes.UserID actual: %v, expected: %v", registerRes.UserID, wallet.Eip155UserId)
|
||||
assert.NotEmptyf(
|
||||
registerRes.AccessToken,
|
||||
"registerRes.AccessToken actual: empty, expected: not empty")
|
||||
}
|
||||
|
||||
func TestNewRegistrationSession(t *testing.T) {
|
||||
// Setup
|
||||
var userAPI fakePublicKeyUserApi
|
||||
|
||||
body := fmt.Sprintf(`{
|
||||
"auth": {
|
||||
"type": "m.login.publickey",
|
||||
"username": "%v"
|
||||
}
|
||||
}`,
|
||||
testCaip10UserId)
|
||||
|
||||
test := struct {
|
||||
Body string
|
||||
}{
|
||||
Body: body,
|
||||
}
|
||||
|
||||
fakeReq := createFakeHttpRequest(test.Body)
|
||||
sessionID := util.RandomString(sessionIDLength)
|
||||
registerContext := createRegisterContext(t)
|
||||
|
||||
// Test
|
||||
response := handleRegistrationFlow(
|
||||
fakeReq.request,
|
||||
fakeReq.body,
|
||||
fakeReq.registerRequest,
|
||||
sessionID,
|
||||
registerContext.config,
|
||||
&userAPI,
|
||||
"",
|
||||
nil,
|
||||
)
|
||||
|
||||
// Asserts
|
||||
assert := assert.New(t)
|
||||
assert.NotNilf(response, "response not nil")
|
||||
assert.Truef(
|
||||
response.Code == http.StatusUnauthorized,
|
||||
"response.Code actual %v, expected %v", response.Code, http.StatusUnauthorized)
|
||||
json := response.JSON.(UserInteractiveResponse)
|
||||
assert.NotEmptyf(json.Session, "response.Session")
|
||||
assert.NotEmptyf(json.Completed, "response.Completed")
|
||||
assert.Truef(
|
||||
json.Completed[0] == authtypes.LoginStagePublicKeyNewRegistration,
|
||||
"response.Completed[0] actual %v, expected %v", json.Completed[0], authtypes.LoginStagePublicKeyNewRegistration)
|
||||
assert.Truef(
|
||||
authtypes.LoginTypePublicKeyEthereum == json.Flows[0].Stages[0],
|
||||
"response.Flows[0].Stages[0] actual: %v, expected: %v", json.Flows[0].Stages[0], authtypes.LoginTypePublicKeyEthereum)
|
||||
|
||||
params := json.Params[authtypes.LoginTypePublicKeyEthereum]
|
||||
assert.NotEmptyf(
|
||||
params,
|
||||
"response.Params[\"%v\"] actual %v, expected %v",
|
||||
authtypes.LoginTypePublicKeyEthereum,
|
||||
params,
|
||||
"[object]")
|
||||
ethParams := params.(config.EthereumAuthParams)
|
||||
assert.NotEmptyf(ethParams.ChainIDs, "ChainIDs actual: empty, expected not empty")
|
||||
assert.NotEmptyf(ethParams.Nonce, "Nonce actual: \"\", expected: not empty")
|
||||
assert.NotEmptyf(ethParams.Version, "Version actual: \"\", expected: not empty")
|
||||
}
|
||||
|
||||
func TestRegistrationUnimplementedAlgo(t *testing.T) {
|
||||
// Setup
|
||||
var userAPI fakePublicKeyUserApi
|
||||
body := fmt.Sprintf(`{
|
||||
"auth": {
|
||||
"type": "m.login.publickey.someAlgo",
|
||||
"username": "%v"
|
||||
}
|
||||
}`,
|
||||
testCaip10UserId)
|
||||
|
||||
test := struct {
|
||||
Body string
|
||||
}{
|
||||
Body: body,
|
||||
}
|
||||
|
||||
fakeReq := createFakeHttpRequest(test.Body)
|
||||
sessionID := util.RandomString(sessionIDLength)
|
||||
registerContext := createRegisterContext(t)
|
||||
|
||||
// Test
|
||||
response := handleRegistrationFlow(
|
||||
fakeReq.request,
|
||||
fakeReq.body,
|
||||
fakeReq.registerRequest,
|
||||
sessionID,
|
||||
registerContext.config,
|
||||
&userAPI,
|
||||
"",
|
||||
nil,
|
||||
)
|
||||
|
||||
// Asserts
|
||||
assert := assert.New(t)
|
||||
assert.NotNilf(response, "response not nil")
|
||||
assert.Truef(
|
||||
response.Code == http.StatusNotImplemented,
|
||||
"response.Code actual %v, expected %v", response.Code, http.StatusNotImplemented)
|
||||
}
|
||||
3
go.mod
3
go.mod
|
|
@ -59,6 +59,8 @@ require (
|
|||
nhooyr.io/websocket v1.8.7
|
||||
)
|
||||
|
||||
require github.com/ethereum/go-ethereum v1.10.15
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
|
||||
|
|
@ -72,7 +74,6 @@ require (
|
|||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/ethereum/go-ethereum v1.10.15 // indirect
|
||||
github.com/frankban/quicktest v1.14.3 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ type ClientAPI struct {
|
|||
PasswordAuthenticationDisabled bool `yaml:"password_authentication_disabled"`
|
||||
|
||||
// Public key authentication
|
||||
PublicKeyAuthentication publicKeyAuthentication `yaml:"public_key_authentication"`
|
||||
PublicKeyAuthentication PublicKeyAuthentication `yaml:"public_key_authentication"`
|
||||
}
|
||||
|
||||
func (c *ClientAPI) Defaults(generate bool) {
|
||||
|
|
|
|||
|
|
@ -32,21 +32,21 @@ func (p EthereumAuthParams) GetNonce() string {
|
|||
return p.Nonce
|
||||
}
|
||||
|
||||
type ethereumAuthConfig struct {
|
||||
type EthereumAuthConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Version uint `yaml:"version"`
|
||||
ChainIDs []int `yaml:"chain_ids"`
|
||||
}
|
||||
|
||||
type publicKeyAuthentication struct {
|
||||
Ethereum ethereumAuthConfig `yaml:"ethereum"`
|
||||
type PublicKeyAuthentication struct {
|
||||
Ethereum EthereumAuthConfig `yaml:"ethereum"`
|
||||
}
|
||||
|
||||
func (pk *publicKeyAuthentication) Enabled() bool {
|
||||
func (pk *PublicKeyAuthentication) Enabled() bool {
|
||||
return pk.Ethereum.Enabled
|
||||
}
|
||||
|
||||
func (pk *publicKeyAuthentication) GetPublicKeyRegistrationFlows() []authtypes.Flow {
|
||||
func (pk *PublicKeyAuthentication) GetPublicKeyRegistrationFlows() []authtypes.Flow {
|
||||
var flows []authtypes.Flow
|
||||
if pk.Ethereum.Enabled {
|
||||
flows = append(flows, authtypes.Flow{Stages: []authtypes.LoginType{authtypes.LoginTypePublicKeyEthereum}})
|
||||
|
|
@ -55,7 +55,7 @@ func (pk *publicKeyAuthentication) GetPublicKeyRegistrationFlows() []authtypes.F
|
|||
return flows
|
||||
}
|
||||
|
||||
func (pk *publicKeyAuthentication) GetPublicKeyRegistrationParams() map[string]interface{} {
|
||||
func (pk *PublicKeyAuthentication) GetPublicKeyRegistrationParams() map[string]interface{} {
|
||||
params := make(map[string]interface{})
|
||||
if pk.Ethereum.Enabled {
|
||||
p := EthereumAuthParams{
|
||||
|
|
|
|||
108
test/publickey_utils.go
Normal file
108
test/publickey_utils.go
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/spruceid/siwe-go"
|
||||
)
|
||||
|
||||
const EthereumTestNetworkId = 4 // Rinkeby test network ID
|
||||
const TestServerName = "localhost"
|
||||
|
||||
type EthereumTestWallet struct {
|
||||
Eip155UserId string
|
||||
PublicAddress string
|
||||
PrivateKey *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
// https://goethereumbook.org/wallet-generate/
|
||||
func CreateTestAccount() (*EthereumTestWallet, error) {
|
||||
// Create a new public / private key pair.
|
||||
privateKey, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the public key
|
||||
publicKey := privateKey.Public()
|
||||
|
||||
// Transform public key to the Ethereum address
|
||||
publicKeyEcdsa, ok := publicKey.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("error casting public key to ECDSA")
|
||||
}
|
||||
|
||||
address := crypto.PubkeyToAddress(*publicKeyEcdsa).Hex()
|
||||
eip155UserId := fmt.Sprintf("eip155=3a%d=3a%s", EthereumTestNetworkId, address)
|
||||
|
||||
return &EthereumTestWallet{
|
||||
PublicAddress: address,
|
||||
PrivateKey: privateKey,
|
||||
Eip155UserId: eip155UserId,
|
||||
},
|
||||
nil
|
||||
}
|
||||
|
||||
func CreateEip4361TestMessage(
|
||||
publicAddress string,
|
||||
) (*siwe.Message, error) {
|
||||
options := make(map[string]interface{})
|
||||
options["chainId"] = 4 // Rinkeby test network
|
||||
options["statement"] = "This is a test statement"
|
||||
message, err := siwe.InitMessage(
|
||||
TestServerName,
|
||||
publicAddress,
|
||||
"https://localhost/login",
|
||||
siwe.GenerateNonce(),
|
||||
options,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return message, nil
|
||||
}
|
||||
|
||||
func FromEip4361MessageToString(message *siwe.Message) string {
|
||||
// Escape the formatting characters to
|
||||
// prevent unmarshal exceptions.
|
||||
str := strings.ReplaceAll(message.String(), "\n", "\\n")
|
||||
str = strings.ReplaceAll(str, "\t", "\\t")
|
||||
return str
|
||||
}
|
||||
|
||||
// https://goethereumbook.org/signature-generate/
|
||||
func SignMessage(message string, privateKey *ecdsa.PrivateKey) (string, error) {
|
||||
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(message), message)
|
||||
data := []byte(msg)
|
||||
hash := crypto.Keccak256Hash(data)
|
||||
|
||||
signature, err := crypto.Sign(hash.Bytes(), privateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L442
|
||||
signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||
return hexutil.Encode(signature), nil
|
||||
}
|
||||
Loading…
Reference in a new issue