diff --git a/client.go b/client.go index 3286b92..bbd4326 100644 --- a/client.go +++ b/client.go @@ -696,6 +696,10 @@ func GetSDKV3(configJSONFilePath string) (*ToznySDKV3, error) { if err != nil { return nil, err } + return sdkV3FromConfig(config) +} + +func sdkV3FromConfig(config ToznySDKJSONConfig) (*ToznySDKV3, error) { return NewToznySDKV3(ToznySDKConfig{ ClientConfig: e3dbClients.ClientConfig{ ClientID: config.ClientID, @@ -730,6 +734,141 @@ func GetSDKV3(configJSONFilePath string) (*ToznySDKV3, error) { }) } +type LoginActionData = map[string]string + +type TozIDLoginRequest struct { + Username string + Password string + RealmName string + + APIBaseURL string + LoginHandler func(response *identityClient.IdentitySessionRequestResponse) (LoginActionData, error) +} + +//GetSDKV3ForTozIDUser logs in a TozID user and returns the storage client of that user as a ToznySDKV3 +func GetSDKV3ForTozIDUser(login TozIDLoginRequest) (*ToznySDKV3, error) { + if login.APIBaseURL == "" { + login.APIBaseURL = "https://api.e3db.com" + } + username := strings.ToLower(login.Username) + anonConfig := e3dbClients.ClientConfig{ + Host: login.APIBaseURL, + AuthNHost: login.APIBaseURL, + } + ctx := context.Background() + anonymousClient := identityClient.New(anonConfig) + realmInfo, err := anonymousClient.RealmInfo(ctx, login.RealmName) + if err != nil { + // TODO: better error message for failure to get realmInfo + return nil, err + } + noteName, encryptionKeys, signingKeys, err := e3dbClients.DeriveCredentials(username, login.Password, realmInfo.Name, "") + if err != nil { + return nil, err + } + clientConfig := e3dbClients.ClientConfig{ + Host: login.APIBaseURL, + AuthNHost: login.APIBaseURL, + SigningKeys: signingKeys, + EncryptionKeys: encryptionKeys, + } + idClient := identityClient.New(clientConfig) + loginResponse, err := idClient.InitiateIdentityLogin(ctx, identityClient.IdentityLoginRequest{ + Username: username, + RealmName: login.RealmName, + AppName: "account", + LoginStyle: "api", + }) + if err != nil { + return nil, err + } + sessionResponse, err := idClient.IdentitySessionRequest(ctx, realmInfo.Name, *loginResponse) + if err != nil { + return nil, err + } + // TODO: rework this to support brokered logins. See JS SDK for examples + for { + if sessionResponse.LoginActionType == "fetch" { + break + } + switch sessionResponse.LoginActionType { + case "continue": + data := url.Values{} + request, err := http.NewRequest("POST", sessionResponse.ActionURL, strings.NewReader(data.Encode())) + if err != nil { + return nil, err + } + err = e3dbClients.MakeSignedServiceCall(ctx, &http.Client{}, request, signingKeys, "", &sessionResponse) + if err != nil { + return nil, err + } + default: + if login.LoginHandler == nil { + return nil, fmt.Errorf("A Login handler must be provided for action type %s", sessionResponse.LoginActionType) + } + data, err := login.LoginHandler(sessionResponse) + if err != nil { + return nil, err + } + var reader io.Reader + if sessionResponse.ContentType == "application/x-www-form-urlencoded" { + values := url.Values{} + for key, value := range data { + values.Set(key, value) + } + reader = strings.NewReader(values.Encode()) + } else { + var buf bytes.Buffer + err := json.NewEncoder(&buf).Encode(&data) + if err != nil { + return nil, err + } + reader = &buf + } + request, err := http.NewRequest("POST", sessionResponse.ActionURL, reader) + request.Header.Set("Content-Type", sessionResponse.ContentType) + if err != nil { + return nil, err + } + err = e3dbClients.MakeSignedServiceCall(ctx, &http.Client{}, request, signingKeys, "", &sessionResponse) + if err != nil { + return nil, err + } else if sessionResponse.Message.IsError { + return nil, fmt.Errorf(sessionResponse.Message.Summary) + } + } + } + redirectRequest := identityClient.IdentityLoginRedirectRequest{ + RealmName: realmInfo.Domain, + // The following map values if not present will be set to the empty string and identity service will handle appropriately + SessionCode: sessionResponse.Context["session_code"], + Execution: sessionResponse.Context["execution"], + TabID: sessionResponse.Context["tab_id"], + ClientID: sessionResponse.Context["client_id"], + AuthSessionId: sessionResponse.Context["auth_session_id"], + } + redirect, err := idClient.IdentityLoginRedirect(ctx, redirectRequest) + if err != nil { + return nil, err + } + storage := storageClient.New(clientConfig) + note, err := storage.ReadNoteByName(ctx, noteName, map[string]string{storageClient.TozIDLoginTokenHeader: redirect.AccessToken}) + if err != nil { + return nil, err + } + err = storage.DecryptNote(note) + if err != nil { + return nil, err + } + var config ToznySDKJSONConfig + err = json.Unmarshal([]byte(note.Data["storage"]), &config) + if err != nil { + return nil, err + } + return sdkV3FromConfig(config) + +} + // CreateResponse wraps the value return from the account creation method type RegisterAccountResponse struct { PaperKey string diff --git a/go.mod b/go.mod index 1d596a5..bd849c9 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ go 1.13 require ( github.com/google/uuid v1.1.0 + github.com/gorilla/websocket v1.4.0 // indirect github.com/jawher/mow.cli v1.0.4 github.com/mitchellh/go-homedir v1.0.0 github.com/stretchr/testify v1.6.1 // indirect - github.com/tozny/e3db-clients-go v0.0.132 - golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 + github.com/tozny/e3db-clients-go v0.0.143-0.20210426210459-320a9cfdfc68 // indirect + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 - google.golang.org/appengine v1.6.6 // indirect ) diff --git a/go.sum b/go.sum index d6cfc52..0042c99 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,9 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/uuid v1.1.0 h1:Jf4mxPC/ziBnoPIdpQdPJ9OeiomAUHLvxmPRSPH9m4s= github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= +github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jawher/mow.cli v1.0.4 h1:hKjm95J7foZ2ngT8tGb15Aq9rj751R7IUDjG+5e3cGA= github.com/jawher/mow.cli v1.0.4/go.mod h1:5hQj2V8g+qYmLUVWqu4Wuja1pI57M83EChYLVZ0sMKk= @@ -38,15 +41,20 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tozny/e3db-clients-go v0.0.132 h1:0jddtG7GNC5vtVRGMsRROg0a0yjPNK3dAfl3J/PZm8A= -github.com/tozny/e3db-clients-go v0.0.132/go.mod h1:AF1aBlYWYm0c8gUTd74FXbVOiH6HLF/TKUAwXSOlgTg= +github.com/tozny/e3db-clients-go v0.0.142-0.20210414182137-dca883891b01 h1:+/XdmMfT0anY2lbkjmJwCKJYMQNS2xoqm9dCpuqNmeQ= +github.com/tozny/e3db-clients-go v0.0.142-0.20210414182137-dca883891b01/go.mod h1:fmlw8jD3qHE3l/gcUSDvytj+Kr0nlADLIrPxOdi7EoE= +github.com/tozny/e3db-clients-go v0.0.142-0.20210424002739-7b5ffef09a62 h1:Sw7GzlgUDUx8c8IFysWTNhoBkhNjrhM1f+WjUNVCACs= +github.com/tozny/e3db-clients-go v0.0.142-0.20210424002739-7b5ffef09a62/go.mod h1:iitohy1iYQux+xJhZBZZ+SA7NeFtxoo/XW1Llydyizo= +github.com/tozny/e3db-clients-go v0.0.143-0.20210426210459-320a9cfdfc68 h1:UMGtWQ2dgpE27/EEyB2ajIZe8MCQuQJnae8LmZenR9A= +github.com/tozny/e3db-clients-go v0.0.143-0.20210426210459-320a9cfdfc68/go.mod h1:xqnK5S5r0qLrKCUms5Mi/3oij2ppNg2lk/8iggyn7IQ= github.com/tozny/utils-go v0.0.35 h1:gPvhlQ8QCoLBUjIx1COfYy6o4dfSM8Lrh+2FV9Ask+g= github.com/tozny/utils-go v0.0.35/go.mod h1:SHi9wnpPEEzAxbwcBhRd+jW32r+gY6S+AcWweuGytRw= golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -55,6 +63,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -65,12 +75,14 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c h1:+EXw7AwNOKzPFXMZ1yNjO40aWCh3PIquJB2fYlv9wcs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/identity.go b/identity.go index 148ebde..53bda36 100644 --- a/identity.go +++ b/identity.go @@ -158,57 +158,17 @@ func (r *Realm) Register(username, password, registrationToken, email, firstName return identity, nil } -func (i *Identity) deriveCredentails(password string, nameSalt string) (string, e3dbClients.EncryptionKeys, e3dbClients.SigningKeys, error) { - nameSeed := fmt.Sprintf("%s@realm:%s", i.Username, i.Realm.Name) - if nameSalt != "" { - nameSeed = fmt.Sprintf("%s:%s", nameSalt, nameSeed) - } - hashedMessageAccumlator, err := blake2b.New(e3dbClients.Blake2BBytes, nil) - if err != nil { - return "", e3dbClients.EncryptionKeys{}, e3dbClients.SigningKeys{}, err - } - hashedMessageAccumlator.Write([]byte(nameSeed)) - hashedMessageBytes := hashedMessageAccumlator.Sum(nil) - noteName := e3dbClients.Base64Encode(hashedMessageBytes) - cryptoPublicKey, cryptoPrivateKey := e3dbClients.DeriveCryptoKey( - []byte(password), - []byte(nameSeed), - e3dbClients.IdentityDerivationRounds, - ) - cryptoKeyPair := e3dbClients.EncryptionKeys{ - Public: e3dbClients.Key{ - Type: e3dbClients.DefaultEncryptionKeyType, - Material: e3dbClients.Base64Encode(cryptoPublicKey[:]), - }, - Private: e3dbClients.Key{ - Type: e3dbClients.DefaultEncryptionKeyType, - Material: e3dbClients.Base64Encode(cryptoPrivateKey[:]), - }, - } - signingPublicKey, signingPrivateKey := e3dbClients.DeriveSigningKey( - []byte(password), - []byte(cryptoKeyPair.Public.Material+cryptoKeyPair.Private.Material), - e3dbClients.IdentityDerivationRounds, - ) - signingKeyPair := e3dbClients.SigningKeys{ - Public: e3dbClients.Key{ - Type: e3dbClients.DefaultSigningKeyType, - Material: e3dbClients.Base64Encode(signingPublicKey[:]), - }, - Private: e3dbClients.Key{ - Type: e3dbClients.DefaultSigningKeyType, - Material: e3dbClients.Base64Encode(signingPrivateKey[:]), - }, - } - return noteName, cryptoKeyPair, signingKeyPair, nil +func (i *Identity) DeriveCredentails(password string, nameSalt string) (string, e3dbClients.EncryptionKeys, e3dbClients.SigningKeys, error) { + return e3dbClients.DeriveCredentials(i.Username, password, i.Realm.Name, nameSalt) } + func (i *Identity) writePasswordNote(password string) (*storageClient.Note, error) { info, err := i.Realm.Info() if err != nil { return &storageClient.Note{}, err } - noteName, encryptionKeyPair, signingKeyPair, err := i.deriveCredentails(password, "") + noteName, encryptionKeyPair, signingKeyPair, err := i.DeriveCredentails(password, "") if err != nil { return &storageClient.Note{}, err } @@ -291,7 +251,7 @@ func (i *Identity) writeBrokerNotes(email string) ([]*storageClient.Note, error) if err != nil { return []*storageClient.Note{}, err } - noteName, cryptoKeyPair, signingKeyPair, err := i.deriveCredentails(noteKey, noteInfo.keyType) + noteName, cryptoKeyPair, signingKeyPair, err := i.DeriveCredentails(noteKey, noteInfo.keyType) if err != nil { return []*storageClient.Note{}, err } diff --git a/identity_test.go b/identity_test.go new file mode 100644 index 0000000..861b82d --- /dev/null +++ b/identity_test.go @@ -0,0 +1,31 @@ +package e3db + +import ( + "fmt" + "github.com/tozny/e3db-clients-go/identityClient" + "testing" +) + +func TestToznySDKV3_Login(t *testing.T) { + request := TozIDLoginRequest{ + Username: "", + Password: "", + RealmName: "", + APIBaseURL: "https://api.e3db.com", + LoginHandler: mfaHandler, + } + sdk, err := GetSDKV3ForTozIDUser(request) + if err != nil { + t.Fatal("Abort", err) + } + fmt.Printf("%v", sdk) +} + +func mfaHandler(sessionResponse *identityClient.IdentitySessionRequestResponse) (LoginActionData, error) { + if sessionResponse.LoginActionType == "login-totp" { + totpValue := make(map[string]string) + totpValue["otp"] = "" + return totpValue, nil + } + return nil, fmt.Errorf("mfaHandler cannot support \"%s\" action types", sessionResponse.LoginActionType) +} \ No newline at end of file