Extremely WIP cross-signing and SSSS support
This commit is contained in:
		@@ -104,6 +104,9 @@ func NewCommandProcessor(parent *MainView) *CommandProcessor {
 | 
			
		||||
			"e":          {"edit"},
 | 
			
		||||
			"dl":         {"download"},
 | 
			
		||||
			"o":          {"open"},
 | 
			
		||||
			"4s":         {"ssss"},
 | 
			
		||||
			"s4":         {"ssss"},
 | 
			
		||||
			"cs":         {"cross-signing"},
 | 
			
		||||
		},
 | 
			
		||||
		autocompleters: map[string]CommandAutocompleter{
 | 
			
		||||
			"devices":     autocompleteDevice,
 | 
			
		||||
@@ -172,6 +175,8 @@ func NewCommandProcessor(parent *MainView) *CommandProcessor {
 | 
			
		||||
			"import":        cmdImportKeys,
 | 
			
		||||
			"export":        cmdExportKeys,
 | 
			
		||||
			"export-room":   cmdExportRoomKeys,
 | 
			
		||||
			"ssss":          cmdSSSS,
 | 
			
		||||
			"cross-signing": cmdCrossSigning,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@
 | 
			
		||||
package ui
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
@@ -26,7 +27,10 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
	"unicode"
 | 
			
		||||
 | 
			
		||||
	ifc "maunium.net/go/gomuks/interface"
 | 
			
		||||
	"maunium.net/go/mautrix"
 | 
			
		||||
	"maunium.net/go/mautrix/crypto"
 | 
			
		||||
	"maunium.net/go/mautrix/crypto/ssss"
 | 
			
		||||
	"maunium.net/go/mautrix/id"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -243,7 +247,7 @@ func cmdImportKeys(cmd *Command) {
 | 
			
		||||
		cmd.Reply("Failed to read %s: %v", path, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	passphrase, ok := cmd.MainView.AskPassword("Key import", false)
 | 
			
		||||
	passphrase, ok := cmd.MainView.AskPassword("Key import", "passphrase", "", false)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		cmd.Reply("Passphrase entry cancelled")
 | 
			
		||||
		return
 | 
			
		||||
@@ -263,7 +267,7 @@ func exportKeys(cmd *Command, sessions []*crypto.InboundGroupSession) {
 | 
			
		||||
		cmd.Reply("Failed to get absolute path: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	passphrase, ok := cmd.MainView.AskPassword("Key export", true)
 | 
			
		||||
	passphrase, ok := cmd.MainView.AskPassword("Key export", "passphrase", "", true)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		cmd.Reply("Passphrase entry cancelled")
 | 
			
		||||
		return
 | 
			
		||||
@@ -299,3 +303,341 @@ func cmdExportRoomKeys(cmd *Command) {
 | 
			
		||||
	}
 | 
			
		||||
	exportKeys(cmd, sessions)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ssssHelp = `Usage: /%s <subcommand> [...]
 | 
			
		||||
 | 
			
		||||
Subcommands:
 | 
			
		||||
* status [key ID] - Check the status of your SSSS.
 | 
			
		||||
* generate [--set-default] - Generate a SSSS key and optionally set it as the default.
 | 
			
		||||
* set-default <key ID> - Set a SSSS key as the default.`
 | 
			
		||||
 | 
			
		||||
func cmdSSSS(cmd *Command) {
 | 
			
		||||
	if len(cmd.Args) == 0 {
 | 
			
		||||
		cmd.Reply(ssssHelp, cmd.OrigCommand)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mach := cmd.Matrix.Crypto().(*crypto.OlmMachine)
 | 
			
		||||
 | 
			
		||||
	switch strings.ToLower(cmd.Args[0]) {
 | 
			
		||||
	case "status":
 | 
			
		||||
		keyID := ""
 | 
			
		||||
		if len(cmd.Args) > 1 {
 | 
			
		||||
			keyID = cmd.Args[1]
 | 
			
		||||
		}
 | 
			
		||||
		cmdS4Status(cmd, mach, keyID)
 | 
			
		||||
	case "generate":
 | 
			
		||||
		setDefault := len(cmd.Args) > 1 && strings.ToLower(cmd.Args[1]) == "--set-default"
 | 
			
		||||
		cmdS4Generate(cmd, mach, setDefault)
 | 
			
		||||
	case "set-default":
 | 
			
		||||
		if len(cmd.Args) < 2 {
 | 
			
		||||
			cmd.Reply("Usage: /%s set-default <key ID>", cmd.OrigCommand)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		cmdS4SetDefault(cmd, mach, cmd.Args[1])
 | 
			
		||||
	default:
 | 
			
		||||
		cmd.Reply(ssssHelp, cmd.OrigCommand)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdS4Status(cmd *Command, mach *crypto.OlmMachine, keyID string) {
 | 
			
		||||
	var keyData *ssss.KeyMetadata
 | 
			
		||||
	var err error
 | 
			
		||||
	if len(keyID) == 0 {
 | 
			
		||||
		keyID, keyData, err = mach.SSSS.GetDefaultKeyData()
 | 
			
		||||
	} else {
 | 
			
		||||
		keyData, err = mach.SSSS.GetKeyData(keyID)
 | 
			
		||||
	}
 | 
			
		||||
	if errors.Is(err, ssss.ErrNoDefaultKeyAccountDataEvent) {
 | 
			
		||||
		cmd.Reply("SSSS is not set up: no default key set")
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to get key data: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	hasPassphrase := "no"
 | 
			
		||||
	if keyData.Passphrase != nil {
 | 
			
		||||
		hasPassphrase = fmt.Sprintf("yes (alg=%s,bits=%d,iter=%d)", keyData.Passphrase.Algorithm, keyData.Passphrase.Bits, keyData.Passphrase.Iterations)
 | 
			
		||||
	}
 | 
			
		||||
	algorithm := keyData.Algorithm
 | 
			
		||||
	if algorithm != ssss.AlgorithmAESHMACSHA2 {
 | 
			
		||||
		algorithm += " (not supported!)"
 | 
			
		||||
	}
 | 
			
		||||
	cmd.Reply("Default key is set.\n  Key ID: %s\n  Has passphrase: %s\n  Algorithm: %s", keyID, hasPassphrase, algorithm)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdS4Generate(cmd *Command, mach *crypto.OlmMachine, setDefault bool) {
 | 
			
		||||
	passphrase, ok := cmd.MainView.AskPassword("Passphrase", "", "", false)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	key, err := ssss.NewKey(passphrase)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to generate new key: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = mach.SSSS.SetKeyData(key.ID, key.Metadata)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to upload key metadata: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO if we start persisting command replies, the recovery key needs to be moved into a popup
 | 
			
		||||
	cmd.Reply("Successfully generated key %s\nRecovery key: %s", key.ID, key.RecoveryKey())
 | 
			
		||||
 | 
			
		||||
	if setDefault {
 | 
			
		||||
		err = mach.SSSS.SetDefaultKeyID(key.ID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			cmd.Reply("Failed to set key as default: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd.Reply("You can use `/%s set-default %s` to set it as the default", cmd.OrigCommand, key.ID)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdS4SetDefault(cmd *Command, mach *crypto.OlmMachine, keyID string) {
 | 
			
		||||
	_, err := mach.SSSS.GetKeyData(keyID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if errors.Is(err, mautrix.MNotFound) {
 | 
			
		||||
			cmd.Reply("Couldn't find key data on server")
 | 
			
		||||
		} else {
 | 
			
		||||
			cmd.Reply("Failed to fetch key data: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = mach.SSSS.SetDefaultKeyID(keyID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to set key as default: %v", err)
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd.Reply("Successfully set key %s as default", keyID)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const crossSigningHelp = `Usage: /%s <subcommand> [...]
 | 
			
		||||
 | 
			
		||||
Subcommands:
 | 
			
		||||
* status
 | 
			
		||||
    Check the status of your own cross-signing keys.
 | 
			
		||||
* generate [--force]
 | 
			
		||||
    Generate and upload new cross-signing keys.
 | 
			
		||||
    This will prompt you to enter your account password.
 | 
			
		||||
    If you already have existing keys, --force is required.
 | 
			
		||||
* fetch [--save-to-disk]
 | 
			
		||||
    Fetch your cross-signing keys from SSSS and decrypt them.
 | 
			
		||||
    If --save-to-disk is specified, the keys are saved to disk.
 | 
			
		||||
* upload
 | 
			
		||||
    Upload your cross-signing keys to SSSS.`
 | 
			
		||||
 | 
			
		||||
func cmdCrossSigning(cmd *Command) {
 | 
			
		||||
	if len(cmd.Args) == 0 {
 | 
			
		||||
		cmd.Reply(crossSigningHelp, cmd.OrigCommand)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := cmd.Matrix.Client()
 | 
			
		||||
	mach := cmd.Matrix.Crypto().(*crypto.OlmMachine)
 | 
			
		||||
 | 
			
		||||
	switch strings.ToLower(cmd.Args[0]) {
 | 
			
		||||
	case "status":
 | 
			
		||||
		cmdCrossSigningStatus(cmd, mach, client)
 | 
			
		||||
	case "generate":
 | 
			
		||||
		force := len(cmd.Args) > 1 && strings.ToLower(cmd.Args[1]) == "--force"
 | 
			
		||||
		cmdCrossSigningGenerate(cmd, cmd.Matrix, mach, client, force)
 | 
			
		||||
	case "fetch":
 | 
			
		||||
		saveToDisk := len(cmd.Args) > 1 && strings.ToLower(cmd.Args[1]) == "--save-to-disk"
 | 
			
		||||
		cmdCrossSigningFetch(cmd, mach, saveToDisk)
 | 
			
		||||
	case "upload":
 | 
			
		||||
		cmdCrossSigningUpload(cmd, mach)
 | 
			
		||||
	default:
 | 
			
		||||
		cmd.Reply(crossSigningHelp, cmd.OrigCommand)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseKeyResp(keys *mautrix.RespQueryKeys, userID id.UserID) (id.Ed25519, id.Ed25519, id.Ed25519, bool) {
 | 
			
		||||
	masterKeys, ok := keys.MasterKeys[userID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return "", "", "", false
 | 
			
		||||
	}
 | 
			
		||||
	selfSigningKeys, ok := keys.SelfSigningKeys[userID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return "", "", "", false
 | 
			
		||||
	}
 | 
			
		||||
	userSigningKeys, ok := keys.UserSigningKeys[userID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return masterKeys.FirstKey(), selfSigningKeys.FirstKey(), "", true
 | 
			
		||||
	}
 | 
			
		||||
	return masterKeys.FirstKey(), userSigningKeys.FirstKey(), selfSigningKeys.FirstKey(), true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdCrossSigningStatus(cmd *Command, mach *crypto.OlmMachine, client *mautrix.Client) {
 | 
			
		||||
	if mach.CrossSigningKeys != nil {
 | 
			
		||||
		cmd.Reply("Cross-signing is set up and private keys are cached")
 | 
			
		||||
		cmd.Reply("Master key: %s", mach.CrossSigningKeys.MasterKey.PublicKey)
 | 
			
		||||
		cmd.Reply("User signing key: %s", mach.CrossSigningKeys.UserSigningKey.PublicKey)
 | 
			
		||||
		cmd.Reply("Self-signing key: %s", mach.CrossSigningKeys.SelfSigningKey.PublicKey)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	keys, err := client.QueryKeys(&mautrix.ReqQueryKeys{
 | 
			
		||||
		DeviceKeys: mautrix.DeviceKeysRequest{
 | 
			
		||||
			client.UserID: mautrix.DeviceIDList{client.DeviceID},
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to query own keys: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	masterKey, selfSigningKey, userSigningKey, ok := parseKeyResp(keys, client.UserID)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		cmd.Reply("Didn't find published cross-signing keys")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	cmd.Reply("Cross-signing is set up, but private keys are not cached")
 | 
			
		||||
	cmd.Reply("Master key: %s", masterKey)
 | 
			
		||||
	cmd.Reply("User signing key: %s", userSigningKey)
 | 
			
		||||
	cmd.Reply("Self-signing key: %s", selfSigningKey)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdCrossSigningFetch(cmd *Command, mach *crypto.OlmMachine, saveToDisk bool) {
 | 
			
		||||
	key := getSSSS(cmd, mach)
 | 
			
		||||
	if key == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := mach.FetchCrossSigningKeysFromSSSS(key)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Error fetching cross-signing keys: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if saveToDisk {
 | 
			
		||||
		cmd.Reply("Saving keys to disk is not yet implemented")
 | 
			
		||||
	}
 | 
			
		||||
	cmd.Reply("Successfully unlocked cross-signing keys")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdCrossSigningGenerate(cmd *Command, container ifc.MatrixContainer, mach *crypto.OlmMachine, client *mautrix.Client, force bool) {
 | 
			
		||||
	if !force {
 | 
			
		||||
		keys, err := client.QueryKeys(&mautrix.ReqQueryKeys{
 | 
			
		||||
			DeviceKeys: mautrix.DeviceKeysRequest{
 | 
			
		||||
				client.UserID: mautrix.DeviceIDList{client.DeviceID},
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			_, _, _, ok := parseKeyResp(keys, client.UserID)
 | 
			
		||||
			if ok {
 | 
			
		||||
				cmd.Reply("Found existing cross-signing keys. Use `--force` if you want to overwrite them.")
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	keys, err := mach.GenerateCrossSigningKeys()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to generate cross-signing keys: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = mach.PublishCrossSigningKeys(keys, func(uia *mautrix.RespUserInteractive) interface{} {
 | 
			
		||||
		if !uia.HasSingleStageFlow(mautrix.AuthTypePassword) {
 | 
			
		||||
			for _, flow := range uia.Flows {
 | 
			
		||||
				if len(flow.Stages) != 1 {
 | 
			
		||||
					return nil
 | 
			
		||||
				}
 | 
			
		||||
				cmd.Reply("Opening browser for authentication")
 | 
			
		||||
				err := container.UIAFallback(flow.Stages[0], uia.Session)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					cmd.Reply("Authentication failed: %v", err)
 | 
			
		||||
					return nil
 | 
			
		||||
				}
 | 
			
		||||
				return &mautrix.BaseAuthData{
 | 
			
		||||
					Type:    flow.Stages[0],
 | 
			
		||||
					Session: uia.Session,
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			cmd.Reply("No supported authentication mechanisms found")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		password, ok := cmd.MainView.AskPassword("Account password", "", "correct horse battery staple", false)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return &mautrix.ReqUIAuthLogin{
 | 
			
		||||
			BaseAuthData: mautrix.BaseAuthData{
 | 
			
		||||
				Type:    mautrix.AuthTypePassword,
 | 
			
		||||
				Session: uia.Session,
 | 
			
		||||
			},
 | 
			
		||||
			User:     mach.Client.UserID.String(),
 | 
			
		||||
			Password: password,
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to publish cross-signing keys: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mach.CrossSigningKeys = keys
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getSSSS(cmd *Command, mach *crypto.OlmMachine) *ssss.Key {
 | 
			
		||||
	_, keyData, err := mach.SSSS.GetDefaultKeyData()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if errors.Is(err, mautrix.MNotFound) {
 | 
			
		||||
			cmd.Reply("SSSS not set up, use `!ssss generate --set-default` first")
 | 
			
		||||
		} else {
 | 
			
		||||
			cmd.Reply("Failed to fetch default SSSS key data: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var key *ssss.Key
 | 
			
		||||
	if keyData.Passphrase != nil && keyData.Passphrase.Algorithm == ssss.PassphraseAlgorithmPBKDF2 {
 | 
			
		||||
		passphrase, ok := cmd.MainView.AskPassword("Passphrase", "", "correct horse battery staple", false)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		key, err = keyData.VerifyPassphrase(passphrase)
 | 
			
		||||
		if errors.Is(err, ssss.ErrIncorrectSSSSKey) {
 | 
			
		||||
			cmd.Reply("Incorrect passphrase")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		recoveryKey, ok := cmd.MainView.AskPassword("Recovery key", "", "tDAK LMRH PiYE bdzi maCe xLX5 wV6P Nmfd c5mC wLef 15Fs VVSc", false)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		key, err = keyData.VerifyRecoveryKey(recoveryKey)
 | 
			
		||||
		if errors.Is(err, ssss.ErrInvalidRecoveryKey) {
 | 
			
		||||
			cmd.Reply("Malformed recovery key")
 | 
			
		||||
			return nil
 | 
			
		||||
		} else if errors.Is(err, ssss.ErrIncorrectSSSSKey) {
 | 
			
		||||
			cmd.Reply("Incorrect recovery key")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// All the errors should already be handled above, this is just for backup
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to get SSSS key: %v", err)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return key
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmdCrossSigningUpload(cmd *Command, mach *crypto.OlmMachine) {
 | 
			
		||||
	if mach.CrossSigningKeys == nil {
 | 
			
		||||
		cmd.Reply("Cross-signing keys not cached, use `!%s generate` first", cmd.OrigCommand)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	key := getSSSS(cmd, mach)
 | 
			
		||||
	if key == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := mach.UploadCrossSigningKeysToSSSS(key, mach.CrossSigningKeys)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to upload keys to SSSS: %v", err)
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd.Reply("Successfully uploaded cross-signing keys to SSSS")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,9 @@
 | 
			
		||||
package ui
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"maunium.net/go/mauview"
 | 
			
		||||
	"maunium.net/go/tcell"
 | 
			
		||||
)
 | 
			
		||||
@@ -41,13 +44,20 @@ type PasswordModal struct {
 | 
			
		||||
	parent *MainView
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MainView) AskPassword(title string, isNew bool) (string, bool) {
 | 
			
		||||
	pwm := NewPasswordModal(view, title, isNew)
 | 
			
		||||
func (view *MainView) AskPassword(title, thing, placeholder string, isNew bool) (string, bool) {
 | 
			
		||||
	pwm := NewPasswordModal(view, title, thing, placeholder, isNew)
 | 
			
		||||
	view.ShowModal(pwm)
 | 
			
		||||
	view.parent.Render()
 | 
			
		||||
	return pwm.Wait()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewPasswordModal(parent *MainView, title string, isNew bool) *PasswordModal {
 | 
			
		||||
func NewPasswordModal(parent *MainView, title, thing, placeholder string, isNew bool) *PasswordModal {
 | 
			
		||||
	if placeholder == "" {
 | 
			
		||||
		placeholder = "correct horse battery staple"
 | 
			
		||||
	}
 | 
			
		||||
	if thing == "" {
 | 
			
		||||
		thing = strings.ToLower(title)
 | 
			
		||||
	}
 | 
			
		||||
	pwm := &PasswordModal{
 | 
			
		||||
		parent:     parent,
 | 
			
		||||
		form:       mauview.NewForm(),
 | 
			
		||||
@@ -64,13 +74,13 @@ func NewPasswordModal(parent *MainView, title string, isNew bool) *PasswordModal
 | 
			
		||||
 | 
			
		||||
	pwm.text = mauview.NewTextField()
 | 
			
		||||
	if isNew {
 | 
			
		||||
		pwm.text.SetText("Create a passphrase")
 | 
			
		||||
		pwm.text.SetText(fmt.Sprintf("Create a %s", thing))
 | 
			
		||||
	} else {
 | 
			
		||||
		pwm.text.SetText("Enter the passphrase")
 | 
			
		||||
		pwm.text.SetText(fmt.Sprintf("Enter the %s", thing))
 | 
			
		||||
	}
 | 
			
		||||
	pwm.input = mauview.NewInputField().
 | 
			
		||||
		SetMaskCharacter('*').
 | 
			
		||||
		SetPlaceholder("correct horse battery staple")
 | 
			
		||||
		SetPlaceholder(placeholder)
 | 
			
		||||
	pwm.form.AddComponent(pwm.text, 1, 1, 3, 1)
 | 
			
		||||
	pwm.form.AddFormItem(pwm.input, 1, 2, 3, 1)
 | 
			
		||||
 | 
			
		||||
@@ -78,10 +88,10 @@ func NewPasswordModal(parent *MainView, title string, isNew bool) *PasswordModal
 | 
			
		||||
		height += 3
 | 
			
		||||
		pwm.confirmInput = mauview.NewInputField().
 | 
			
		||||
			SetMaskCharacter('*').
 | 
			
		||||
			SetPlaceholder("correct horse battery staple").
 | 
			
		||||
			SetPlaceholder(placeholder).
 | 
			
		||||
			SetChangedFunc(pwm.HandleChange)
 | 
			
		||||
		pwm.input.SetChangedFunc(pwm.HandleChange)
 | 
			
		||||
		pwm.confirmText = mauview.NewTextField().SetText("Confirm passphrase")
 | 
			
		||||
		pwm.confirmText = mauview.NewTextField().SetText(fmt.Sprintf("Confirm %s", thing))
 | 
			
		||||
 | 
			
		||||
		pwm.form.SetRow(3, 1).SetRow(4, 1).SetRow(5, 1)
 | 
			
		||||
		pwm.form.AddComponent(pwm.confirmText, 1, 4, 3, 1)
 | 
			
		||||
@@ -125,9 +135,9 @@ func (pwm *PasswordModal) ClickSubmit() {
 | 
			
		||||
 | 
			
		||||
func (pwm *PasswordModal) Wait() (string, bool) {
 | 
			
		||||
	select {
 | 
			
		||||
	case result := <- pwm.outputChan:
 | 
			
		||||
	case result := <-pwm.outputChan:
 | 
			
		||||
		return result, true
 | 
			
		||||
	case <- pwm.cancelChan:
 | 
			
		||||
	case <-pwm.cancelChan:
 | 
			
		||||
		return "", false
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user