Skip to content

Commit

Permalink
Merge 4f4989e into 9c01156
Browse files Browse the repository at this point in the history
  • Loading branch information
efabens committed Apr 30, 2021
2 parents 9c01156 + 4f4989e commit 73d12ff
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 121 deletions.
166 changes: 166 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,7 @@ type ToznySDKV3 struct {
APIEndpoint string
// Tozny server defined globally unique id for this Client.
ClientID string
config e3dbClients.ClientConfig
}

// ToznySDKConfig wraps parameters needed to configure a ToznySDK
Expand Down Expand Up @@ -686,6 +687,7 @@ func NewToznySDKV3(config ToznySDKConfig) (*ToznySDKV3, error) {
AccountPassword: config.AccountPassword,
APIEndpoint: config.APIEndpoint,
ClientID: config.ClientID,
config: config.ClientConfig,
}, nil
}

Expand All @@ -696,6 +698,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,
Expand Down Expand Up @@ -730,6 +736,145 @@ func GetSDKV3(configJSONFilePath string) (*ToznySDKV3, error) {
})
}

type LoginActionData = map[string]string

type IdentitySessionIntermediateResponse = identityClient.IdentitySessionRequestResponse

// TozIDLoginRequest is used to login to a TozID account to get a ToznySDKV3 or active TozID session (future plan)
type TozIDLoginRequest struct {
Username string
Password string
RealmName string
APIBaseURL string
LoginHandler func(response *IdentitySessionIntermediateResponse) (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"
} else {
login.APIBaseURL = strings.TrimSuffix(strings.TrimSpace(login.APIBaseURL), "/")
}
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, fmt.Errorf("GetSDKV3ForTozIDUser: failed to get realm infor with error %w", err)
}
noteName, encryptionKeys, signingKeys, err := e3dbClients.DeriveIdentityCredentials(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)
if err != nil {
return nil, err
}
request.Header.Set("Content-Type", sessionResponse.ContentType)
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
Expand Down Expand Up @@ -781,6 +926,27 @@ type ClientConfig struct {
PrivateSigningKey string `json:"private_signing_key"`
}

// StoreConfigFile stores a ToznySDKV3 config file at the specified path, returning an error if any
func (c *ToznySDKV3) StoreConfigFile(path string) error {
config := ToznySDKJSONConfig{
ConfigFile: ConfigFile{
Version: 2,
APIBaseURL: c.APIEndpoint,
APIKeyID: c.config.APIKey,
APISecret: c.config.APISecret,
ClientID: c.config.ClientID,
ClientEmail: "",
PublicKey: c.config.EncryptionKeys.Public.Material,
PrivateKey: c.config.EncryptionKeys.Private.Material,
},
PublicSigningKey: c.config.SigningKeys.Public.Material,
PrivateSigningKey: c.config.SigningKeys.Private.Material,
AccountUsername: c.AccountUsername,
AccountPassword: c.AccountPassword,
}
return saveJson(path, config)
}

// Register attempts to create a valid TozStore account returning the root client config for the created account and error (if any).
func (c *ToznySDKV3) Register(ctx context.Context, name string, email string, password string, apiURL string) (RegisterAccountResponse, error) {
if apiURL == "" {
Expand Down
20 changes: 14 additions & 6 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func loadConfig(configPath string) (*ClientOpts, error) {
}, nil
}

func saveConfig(configPath string, opts *ClientOpts) error {
func saveJson(configPath string, obj interface{}) error {
configFullPath, err := homedir.Expand(configPath)
if err != nil {
return err
Expand All @@ -126,6 +126,14 @@ func saveConfig(configPath string, opts *ClientOpts) error {
}
defer configFd.Close()

if err = json.NewEncoder(configFd).Encode(&obj); err != nil {
return err
}

return nil
}

func saveConfig(configPath string, opts *ClientOpts) error {
configObj := configFile{
Version: 1,
ClientID: opts.ClientID,
Expand All @@ -137,11 +145,7 @@ func saveConfig(configPath string, opts *ClientOpts) error {
PrivateKey: encodePrivateKey(opts.PrivateKey),
}

if err = json.NewEncoder(configFd).Encode(&configObj); err != nil {
return err
}

return nil
return saveJson(configPath, configObj)
}

func fileExists(name string) (bool, error) {
Expand Down Expand Up @@ -214,3 +218,7 @@ func LoadConfigFile(configPath string) (ToznySDKJSONConfig, error) {
}
return config, nil
}

func StoreConfigFile(configPath string, config ToznySDKJSONConfig) error {
return saveJson(configPath, config)
}
108 changes: 54 additions & 54 deletions example_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,77 +4,77 @@ package e3db_test
// this code, it will share text with Tozny in an end-to-end encrypted manner.

import (
"context"
"fmt"
"github.com/tozny/e3db-go/v2"
"log"
"context"
"fmt"
"github.com/tozny/e3db-go/v2"
"log"
)

func chk(err error) {
if err != nil {
log.Fatal(err)
}
if err != nil {
log.Fatal(err)
}
}

func printRecords(recordType string) {
client, err := e3db.GetDefaultClient()
chk(err)
client, err := e3db.GetDefaultClient()
chk(err)

// Query for all records and print them out
query := e3db.Q{} // queries all records
if recordType != "" {
query = e3db.Q{ContentTypes: []string{recordType}}
}
cursor := client.Query(context.Background(), query)
for {
record, err := cursor.Next()
if err == e3db.Done {
break
} else if err != nil {
chk(err)
}
fmt.Println("\t" + record.Meta.RecordID + " " + record.Meta.Type)
}
// Query for all records and print them out
query := e3db.Q{} // queries all records
if recordType != "" {
query = e3db.Q{ContentTypes: []string{recordType}}
}
cursor := client.Query(context.Background(), query)
for {
record, err := cursor.Next()
if err == e3db.Done {
break
} else if err != nil {
chk(err)
}
fmt.Println("\t" + record.Meta.RecordID + " " + record.Meta.Type)
}
}

func Example() {

// Accessing the default profile.
// You must run e3db RegisterClient before this will work:
client, err := e3db.GetDefaultClient()
chk(err)
// Accessing the default profile.
// You must run e3db RegisterClient before this will work:
client, err := e3db.GetDefaultClient()
chk(err)

fmt.Println("Current list of records:")
printRecords("")
fmt.Println("Current list of records:")
printRecords("")

// Create a new "feedback" record; this is the type the CLI uses
feedbackData := make(map[string]string)
feedbackData["comment"] = "This is some example feedback!"
feedbackData["interface"] = "Go Example Code"
record, err := client.Write(context.Background(), "feedback", feedbackData, nil)
chk(err)
// Create a new "feedback" record; this is the type the CLI uses
feedbackData := make(map[string]string)
feedbackData["comment"] = "This is some example feedback!"
feedbackData["interface"] = "Go Example Code"
record, err := client.Write(context.Background(), "feedback", feedbackData, nil)
chk(err)

// Read back the feedback we just put into the database
newFeedbackRecord, err := client.Read(context.Background(), record.Meta.RecordID)
chk(err)
fmt.Println("Read record id " + record.Meta.RecordID + ": " + newFeedbackRecord.Data["comment"])
// Read back the feedback we just put into the database
newFeedbackRecord, err := client.Read(context.Background(), record.Meta.RecordID)
chk(err)
fmt.Println("Read record id " + record.Meta.RecordID + ": " + newFeedbackRecord.Data["comment"])

// Fetch the Tozny feedback email address public key and client ID
feedbackClient, err := client.GetClientInfo(context.Background(), "db1744b9-3fb6-4458-a291-0bc677dba08b")
chk(err)
// Fetch the Tozny feedback email address public key and client ID
feedbackClient, err := client.GetClientInfo(context.Background(), "db1744b9-3fb6-4458-a291-0bc677dba08b")
chk(err)

// Share all "feedback" records with that user ID.
err = client.Share(context.Background(), "feedback", feedbackClient.ClientID)
chk(err)
// Share all "feedback" records with that user ID.
err = client.Share(context.Background(), "feedback", feedbackClient.ClientID)
chk(err)

fmt.Println("Current list of records after adding:")
printRecords("feedback")
fmt.Println("Current list of records after adding:")
printRecords("feedback")

// Delete the record we just created to keep things tidy.
// Comment out this line if you want to keep it
err = client.Delete(context.Background(), record.Meta.RecordID, record.Meta.Version)
chk(err)
// Delete the record we just created to keep things tidy.
// Comment out this line if you want to keep it
err = client.Delete(context.Background(), record.Meta.RecordID, record.Meta.Version)
chk(err)

fmt.Println("Current list of records after deleting:")
printRecords("feedback")
fmt.Println("Current list of records after deleting:")
printRecords("feedback")
}
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ require (
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.144
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
)

0 comments on commit 73d12ff

Please sign in to comment.