Repository: nats-io/nsc
Pull request #329 (2020-12-07 22:27:55)

V2 fields 2
each commit is a separate change. (except first one, that just pulls in the latest jwtv2 changes)


Patch:
diff --git a/cmd/addaccount.go b/cmd/addaccount.go
index c00a7aa..0aa7b63 100644
--- a/cmd/addaccount.go
+++ b/cmd/addaccount.go
@@ -47,6 +47,7 @@ func CreateAddAccountCmd() *cobra.Command {
 	cmd.Flags().StringVarP(&params.name, "name", "n", "", "account name")
 	cmd.Flags().StringVarP(&params.keyPath, "public-key", "k", "", "public key identifying the account")
 	params.TimeParams.BindFlags(cmd)
+	params.PermissionsParams.bindSetFlags(cmd, "default permissions")
 
 	return cmd
 }
@@ -58,6 +59,7 @@ func init() {
 type AddAccountParams struct {
 	SignerParams
 	TimeParams
+	PermissionsParams
 	token    string
 	name     string
 	generate bool
@@ -213,6 +215,10 @@ func (p *AddAccountParams) Validate(ctx ActionCtx) error {
 		return err
 	}
 
+	if err := p.PermissionsParams.Validate(); err != nil {
+		return err
+	}
+
 	// the account doesn't exist, so insure self signed works
 	p.SignerParams.ForceManagedAccountKey(ctx, p.akp)
 	if err := p.SignerParams.Resolve(ctx); err != nil {
@@ -239,6 +245,7 @@ func (p *AddAccountParams) Run(ctx ActionCtx) (store.Status, error) {
 	if err != nil {
 		return nil, err
 	}
+	r := store.NewDetailedReport(false)
 
 	ac := jwt.NewAccountClaims(pk)
 	ac.Name = p.name
@@ -250,6 +257,12 @@ func (p *AddAccountParams) Run(ctx ActionCtx) (store.Status, error) {
 		ac.Expires, _ = p.TimeParams.ExpiryDate()
 	}
 
+	if s, err := p.PermissionsParams.Run(&ac.DefaultPermissions, ctx); err != nil {
+		return nil, err
+	} else if s != nil {
+		r.Add(s.Details...)
+	}
+
 	signer := p.akp
 	if !ctx.StoreCtx().Store.IsManaged() || p.signerKP != nil {
 		signer = p.signerKP
@@ -259,7 +272,6 @@ func (p *AddAccountParams) Run(ctx ActionCtx) (store.Status, error) {
 		return nil, err
 	}
 
-	r := store.NewDetailedReport(false)
 	if p.generate {
 		r.AddOK("generated and stored account key %q", pk)
 	}
diff --git a/cmd/adduser.go b/cmd/adduser.go
index 68e7d4f..03dcef5 100644
--- a/cmd/adduser.go
+++ b/cmd/adduser.go
@@ -67,14 +67,6 @@ nsc add user --name <n> --allow-pub-response=5
 		},
 	}
 
-	cmd.Flags().StringSliceVarP(&params.allowPubs, "allow-pub", "", nil, "publish permissions - comma separated list or option can be specified multiple times")
-	cmd.Flags().StringSliceVarP(&params.allowPubsub, "allow-pubsub", "", nil, "publish and subscribe permissions - comma separated list or option can be specified multiple times")
-	cmd.Flags().StringSliceVarP(&params.allowSubs, "allow-sub", "", nil, "subscribe permissions - comma separated list or option can be specified multiple times")
-
-	cmd.Flags().StringSliceVarP(&params.denyPubs, "deny-pub", "", nil, "deny publish permissions - comma separated list or option can be specified multiple times")
-	cmd.Flags().StringSliceVarP(&params.denyPubsub, "deny-pubsub", "", nil, "deny publish and subscribe permissions - comma separated list or option can be specified multiple times")
-	cmd.Flags().StringSliceVarP(&params.denySubs, "deny-sub", "", nil, "deny subscribe permissions - comma separated list or option can be specified multiple times")
-
 	cmd.Flags().StringSliceVarP(&params.tags, "tag", "", nil, "tags for user - comma separated list or option can be specified multiple times")
 	cmd.Flags().StringSliceVarP(&params.src, "source-network", "", nil, "source network for connection - comma separated list or option can be specified multiple times")
 
@@ -85,7 +77,7 @@ nsc add user --name <n> --allow-pub-response=5
 
 	params.TimeParams.BindFlags(cmd)
 	params.AccountContextParams.BindFlags(cmd)
-	params.ResponsePermsParams.bindSetFlags(cmd)
+	params.PermissionsParams.bindSetFlags(cmd, "permissions")
 
 	return cmd
 }
@@ -98,13 +90,7 @@ type AddUserParams struct {
 	AccountContextParams
 	SignerParams
 	TimeParams
-	ResponsePermsParams
-	allowPubs     []string
-	allowPubsub   []string
-	allowSubs     []string
-	denyPubs      []string
-	denyPubsub    []string
-	denySubs      []string
+	PermissionsParams
 	src           []string
 	tags          []string
 	credsFilePath string
@@ -166,7 +152,7 @@ func (p *AddUserParams) PreInteractive(ctx ActionCtx) error {
 	}
 
 	// FIXME: we won't do interactive on the response params until pub/sub/deny permissions are interactive
-	//if err := p.ResponsePermsParams.Edit(false); err != nil {
+	//if err := p.PermissionsParams.Edit(false); err != nil {
 	//	return err
 	//}
 
@@ -230,7 +216,7 @@ func (p *AddUserParams) Validate(ctx ActionCtx) error {
 		return err
 	}
 
-	if err := p.ResponsePermsParams.Validate(); err != nil {
+	if err := p.PermissionsParams.Validate(); err != nil {
 		return err
 	}
 
@@ -258,7 +244,8 @@ func (p *AddUserParams) Validate(ctx ActionCtx) error {
 }
 
 func (p *AddUserParams) Run(ctx ActionCtx) (store.Status, error) {
-	uc, err := p.generateUserClaim(ctx)
+	r := store.NewDetailedReport(false)
+	uc, err := p.generateUserClaim(ctx, r)
 	if err != nil {
 		return nil, err
 	}
@@ -268,7 +255,6 @@ func (p *AddUserParams) Run(ctx ActionCtx) (store.Status, error) {
 		return nil, err
 	}
 
-	r := store.NewDetailedReport(false)
 	st, err := ctx.StoreCtx().Store.StoreClaim([]byte(token))
 	if err != nil {
 		r.AddFromError(err)
@@ -315,7 +301,7 @@ func (p *AddUserParams) Run(ctx ActionCtx) (store.Status, error) {
 	return r, nil
 }
 
-func (p *AddUserParams) generateUserClaim(ctx ActionCtx) (*jwt.UserClaims, error) {
+func (p *AddUserParams) generateUserClaim(ctx ActionCtx, r *store.Report) (*jwt.UserClaims, error) {
 	pub, err := p.kp.PublicKey()
 	if err != nil {
 		return nil, err
@@ -339,26 +325,12 @@ func (p *AddUserParams) generateUserClaim(ctx ActionCtx) (*jwt.UserClaims, error
 		uc.Expires, _ = p.TimeParams.ExpiryDate()
 	}
 
-	if _, err := p.ResponsePermsParams.Run(uc, ctx); err != nil {
+	if s, err := p.PermissionsParams.Run(&uc.Permissions, ctx); err != nil {
 		return nil, err
+	} else if s != nil {
+		r.Add(s.Details...)
 	}
 
-	uc.Permissions.Pub.Allow.Add(p.allowPubs...)
-	uc.Permissions.Pub.Allow.Add(p.allowPubsub...)
-	sort.Strings(uc.Pub.Allow)
-
-	uc.Permissions.Pub.Deny.Add(p.denyPubs...)
-	uc.Permissions.Pub.Deny.Add(p.denyPubsub...)
-	sort.Strings(uc.Permissions.Pub.Deny)
-
-	uc.Permissions.Sub.Allow.Add(p.allowSubs...)
-	uc.Permissions.Sub.Allow.Add(p.allowPubsub...)
-	sort.Strings(uc.Permissions.Sub.Allow)
-
-	uc.Permissions.Sub.Deny.Add(p.denySubs...)
-	uc.Permissions.Sub.Deny.Add(p.denyPubsub...)
-	sort.Strings(uc.Permissions.Sub.Deny)
-
 	uc.Tags.Add(p.tags...)
 	sort.Strings(uc.Tags)
 
@@ -366,52 +338,67 @@ func (p *AddUserParams) generateUserClaim(ctx ActionCtx) (*jwt.UserClaims, error
 	return uc, nil
 }
 
-type ResponsePermsParams struct {
-	respTTL string
-	respMax int
-	rmResp  bool
+type PermissionsParams struct {
+	respTTL     string
+	respMax     int
+	rmResp      bool
+	allowPubs   []string
+	allowPubsub []string
+	allowSubs   []string
+	denyPubs    []string
+	denyPubsub  []string
+	denySubs    []string
+	rmPerms     []string
 }
 
-func (p *ResponsePermsParams) bindSetFlags(cmd *cobra.Command) {
-	cmd.Flags().StringVarP(&p.respTTL, "response-ttl", "", "", "the amount of time the permission is valid (global) - [#ms(millis) | #s(econds) | m(inutes) | h(ours)] - Default is no time limit.")
+func (p *PermissionsParams) bindSetFlags(cmd *cobra.Command, typeName string) {
+	cmd.Flags().StringVarP(&p.respTTL, "response-ttl", "", "", fmt.Sprintf("the amount of time the %s is valid (global) - [#ms(millis) | #s(econds) | m(inutes) | h(ours)] - Default is no time limit.", typeName))
 
-	cmd.Flags().IntVarP(&p.respMax, "allow-pub-response", "", 0, "client can publish only to reply subjects [with an optional count] (global)")
+	cmd.Flags().IntVarP(&p.respMax, "allow-pub-response", "", 0, fmt.Sprintf("%s to limit how often a client can publish to reply subjects [with an optional count, --allow-pub-response=n] (global)", typeName))
 	cmd.Flag("allow-pub-response").NoOptDefVal = "1"
 
-	cmd.Flags().IntVarP(&p.respMax, "max-responses", "", 0, "client can publish only to reply subjects [with an optional count] (global)")
+	cmd.Flags().IntVarP(&p.respMax, "max-responses", "", 0, fmt.Sprintf("%s to limit how ofthen a client can publish to reply subjects [with an optional count] (global)", typeName))
 	cmd.Flag("max-responses").Hidden = true
-	cmd.Flag("max-responses").Deprecated = "use --allow-pub-n-responses or --allow-pub-response"
+	cmd.Flag("max-responses").Deprecated = "use --allow-pub-response or --allow-pub-response=n"
+
+	cmd.Flags().StringSliceVarP(&p.allowPubs, "allow-pub", "", nil, fmt.Sprintf("add publish %s - comma separated list or option can be specified multiple times", typeName))
+	cmd.Flags().StringSliceVarP(&p.allowPubsub, "allow-pubsub", "", nil, fmt.Sprintf("add publish and subscribe %s - comma separated list or option can be specified multiple times", typeName))
+	cmd.Flags().StringSliceVarP(&p.allowSubs, "allow-sub", "", nil, fmt.Sprintf("add subscribe %s - comma separated list or option can be specified multiple times", typeName))
+	cmd.Flags().StringSliceVarP(&p.denyPubs, "deny-pub", "", nil, fmt.Sprintf("add deny publish %s - comma separated list or option can be specified multiple times", typeName))
+	cmd.Flags().StringSliceVarP(&p.denyPubsub, "deny-pubsub", "", nil, fmt.Sprintf("add deny publish and subscribe %s - comma separated list or option can be specified multiple times", typeName))
+	cmd.Flags().StringSliceVarP(&p.denySubs, "deny-sub", "", nil, fmt.Sprintf("add deny subscribe %s - comma separated list or option can be specified multiple times", typeName))
 }
 
-func (p *ResponsePermsParams) bindRemoveFlags(cmd *cobra.Command) {
-	cmd.Flags().BoolVarP(&p.rmResp, "rm-response-perms", "", false, "remove response settings")
+func (p *PermissionsParams) bindRemoveFlags(cmd *cobra.Command, typeName string) {
+	cmd.Flags().BoolVarP(&p.rmResp, "rm-response-perms", "", false, fmt.Sprintf("remove response settings from %s", typeName))
+	cmd.Flags().StringSliceVarP(&p.rmPerms, "rm", "", nil, fmt.Sprintf("remove publish/subscribe and deny %s - comma separated list or option can be specified multiple times", typeName))
 }
 
-func (p *ResponsePermsParams) maxResponseValidator(s string) error {
+func (p *PermissionsParams) maxResponseValidator(s string) error {
 	_, err := p.parseMaxResponse(s)
 	return err
 }
 
-func (p *ResponsePermsParams) parseMaxResponse(s string) (int, error) {
+func (p *PermissionsParams) parseMaxResponse(s string) (int, error) {
 	if s == "" {
 		return 0, nil
 	}
 	return strconv.Atoi(s)
 }
 
-func (p *ResponsePermsParams) ttlValidator(s string) error {
+func (p *PermissionsParams) ttlValidator(s string) error {
 	_, err := p.parseTTL(s)
 	return err
 }
 
-func (p *ResponsePermsParams) parseTTL(s string) (time.Duration, error) {
+func (p *PermissionsParams) parseTTL(s string) (time.Duration, error) {
 	if s == "" {
 		return time.Duration(0), nil
 	}
 	return time.ParseDuration(s)
 }
 
-func (p *ResponsePermsParams) Edit(hasPerm bool) error {
+func (p *PermissionsParams) Edit(hasPerm bool) error {
 	verb := "Set"
 	if hasPerm {
 		verb = "Edit"
@@ -442,25 +429,28 @@ func (p *ResponsePermsParams) Edit(hasPerm bool) error {
 	return nil
 }
 
-func (p *ResponsePermsParams) Validate() error {
+func (p *PermissionsParams) Validate() error {
 	if err := p.ttlValidator(p.respTTL); err != nil {
 		return err
 	}
 	return nil
 }
 
-func (p *ResponsePermsParams) Run(uc *jwt.UserClaims, ctx ActionCtx) (*store.Report, error) {
+func (p *PermissionsParams) Run(perms *jwt.Permissions, ctx ActionCtx) (*store.Report, error) {
 	r := store.NewDetailedReport(true)
 	if p.rmResp {
-		uc.Resp = nil
+		perms.Resp = nil
 		r.AddOK("removed response permissions")
 		return r, nil
 	}
+
+	fmt.Printf("%v\n", ctx.CurrentCmd().Flag("max-responses").Value)
+
 	if ctx.CurrentCmd().Flag("max-responses").Changed || p.respMax != 0 {
-		if uc.Resp == nil {
-			uc.Resp = &jwt.ResponsePermission{}
+		if perms.Resp == nil {
+			perms.Resp = &jwt.ResponsePermission{}
 		}
-		uc.Resp.MaxMsgs = p.respMax
+		perms.Resp.MaxMsgs = p.respMax
 		r.AddOK("set max responses to %d", p.respMax)
 	}
 
@@ -469,11 +459,58 @@ func (p *ResponsePermsParams) Run(uc *jwt.UserClaims, ctx ActionCtx) (*store.Rep
 		if err != nil {
 			return nil, err
 		}
-		if uc.Resp == nil {
-			uc.Resp = &jwt.ResponsePermission{}
+		if perms.Resp == nil {
+			perms.Resp = &jwt.ResponsePermission{}
 		}
-		uc.Resp.Expires = v
+		perms.Resp.Expires = v
 		r.AddOK("set response ttl to %v", v)
 	}
+
+	var ap []string
+	perms.Pub.Allow.Add(p.allowPubs...)
+	ap = append(ap, p.allowPubs...)
+	perms.Pub.Allow.Add(p.allowPubsub...)
+	ap = append(ap, p.allowPubsub...)
+	for _, v := range ap {
+		r.AddOK("added pub pub %q", v)
+	}
+	perms.Pub.Allow.Remove(p.rmPerms...)
+	for _, v := range p.rmPerms {
+		r.AddOK("removed pub %q", v)
+	}
+	sort.Strings(perms.Pub.Allow)
+
+	var dp []string
+	perms.Pub.Deny.Add(p.denyPubs...)
+	dp = append(dp, p.denyPubs...)
+	perms.Pub.Deny.Add(p.denyPubsub...)
+	dp = append(dp, p.denyPubsub...)
+	for _, v := range dp {
+		r.AddOK("added deny pub %q", v)
+	}
+	perms.Pub.Deny.Remove(p.rmPerms...)
+	for _, v := range p.rmPerms {
+		r.AddOK("removed deny pub %q", v)
+	}
+	sort.Strings(perms.Pub.Deny)
+
+	var sa []string
+	perms.Sub.Allow.Add(p.allowSubs...)
+	sa = append(sa, p.allowSubs...)
+	perms.Sub.Allow.Add(p.allowPubsub...)
+	sa = append(sa, p.allowPubsub...)
+	for _, v := range sa {
+		r.AddOK("added sub %q", v)
+	}
+	perms.Sub.Allow.Remove(p.rmPerms...)
+	for _, v := range p.rmPerms {
+		r.AddOK("removed sub %q", v)
+	}
+	sort.Strings(perms.Sub.Allow)
+
+	perms.Sub.Deny.Add(p.denySubs...)
+	perms.Sub.Deny.Add(p.denyPubsub...)
+	perms.Sub.Deny.Remove(p.rmPerms...)
+	sort.Strings(perms.Sub.Deny)
 	return r, nil
 }
diff --git a/cmd/describer.go b/cmd/describer.go
index f838b81..0771f55 100644
--- a/cmd/describer.go
+++ b/cmd/describer.go
@@ -45,18 +45,39 @@ func (a *AccountDescriber) Describe() string {
 	AddStandardClaimInfo(table, &a.AccountClaims)
 	table.AddSeparator()
 
+	info := false
+	if a.Description != "" {
+		table.AddRow("Description", strings.ReplaceAll(a.Description, "\n", " "))
+		info = true
+	}
+	if a.InfoURL != "" {
+		table.AddRow("Info Url", a.InfoURL)
+		info = true
+	}
+	if info {
+		table.AddSeparator()
+	}
+
 	if len(a.SigningKeys) > 0 {
 		AddListValues(table, "Signing Keys", a.SigningKeys)
 		table.AddSeparator()
 	}
 
-	lim := a.Limits
-	if lim.Conn > -1 {
-		table.AddRow("Max Connections", fmt.Sprintf("%d", lim.Conn))
-	} else {
-		table.AddRow("Max Connections", "Unlimited")
+	addLimitRow := func(table *tablewriter.Table, name string, limit int64, inBytes bool) {
+		if limit > -1 {
+			val := fmt.Sprintf("%d", limit)
+			if inBytes {
+				val = fmt.Sprintf("%s (%d bytes)", humanize.Bytes(uint64(limit)), limit)
+			}
+			table.AddRow(name, val)
+		} else {
+			table.AddRow(name, "Unlimited")
+		}
 	}
 
+	lim := a.Limits
+	addLimitRow(table, "Max Connections", lim.Conn, false)
+
 	if lim.LeafNodeConn == 0 {
 		table.AddRow("Max Leaf Node Connections", "Not Allowed")
 	} else if lim.LeafNodeConn > 0 {
@@ -65,35 +86,11 @@ func (a *AccountDescriber) Describe() string {
 		table.AddRow("Max Leaf Node Connections", "Unlimited")
 	}
 
-	if lim.Data > -1 {
-		table.AddRow("Max Data", fmt.Sprintf("%s (%d bytes)", humanize.Bytes(uint64(lim.Data)), lim.Data))
-	} else {
-		table.AddRow("Max Data", "Unlimited")
-	}
-
-	if lim.Exports > -1 {
-		table.AddRow("Max Exports", fmt.Sprintf("%d", lim.Exports))
-	} else {
-		table.AddRow("Max Exports", "Unlimited")
-	}
-
-	if lim.Imports > -1 {
-		table.AddRow("Max Imports", fmt.Sprintf("%d", lim.Imports))
-	} else {
-		table.AddRow("Max Imports", "Unlimited")
-	}
-
-	if lim.Payload > -1 {
-		table.AddRow("Max Msg Payload", fmt.Sprintf("%s (%d bytes)", humanize.Bytes(uint64(lim.Payload)), lim.Payload))
-	} else {
-		table.AddRow("Max Msg Payload", "Unlimited")
-	}
-
-	if lim.Subs > -1 {
-		table.AddRow("Max Subscriptions", fmt.Sprintf("%d", lim.Subs))
-	} else {
-		table.AddRow("Max Subscriptions", "Unlimited")
-	}
+	addLimitRow(table, "Max Data", lim.Data, true)
+	addLimitRow(table, "Max Exports", lim.Exports, false)
+	addLimitRow(table, "Max Imports", lim.Imports, false)
+	addLimitRow(table, "Max Msg Payload", lim.Payload, true)
+	addLimitRow(table, "Max Subscriptions", lim.Subs, false)
 
 	we := "False"
 	if lim.WildcardExports {
@@ -101,6 +98,33 @@ func (a *AccountDescriber) Describe() string {
 	}
 	table.AddRow("Exports Allows Wildcards", we)
 
+	AddPermissions(table, a.DefaultPermissions)
+
+	table.AddSeparator()
+	if a.Limits.DiskStorage == 0 && a.Limits.MemoryStorage == 0 {
+		table.AddRow("Jetstream", "Disabled")
+	} else {
+		table.AddRow("Jetstream", "Enabled")
+		switch {
+		case lim.DiskStorage > 0:
+			table.AddRow("Max Disk Storage", humanize.Bytes(uint64(lim.DiskStorage)))
+		case lim.DiskStorage == 0:
+			table.AddRow("Max Disk Storage", "Disabled")
+		default:
+			table.AddRow("Max Disk Storage", "Unlimited")
+		}
+		switch {
+		case lim.MemoryStorage > 0:
+			table.AddRow("Max Mem Storage", humanize.Bytes(uint64(lim.MemoryStorage)))
+		case lim.MemoryStorage == 0:
+			table.AddRow("Max Mem Storage", "Disabled")
+		default:
+			table.AddRow("Max Mem Storage", "Unlimited")
+		}
+		addLimitRow(table, "Max Streams", lim.Streams, false)
+		addLimitRow(table, "Max Consumer", lim.Consumer, false)
+	}
+
 	table.AddSeparator()
 
 	if len(a.Imports) == 0 {
@@ -157,7 +181,7 @@ func toYesNo(tf bool) string {
 func (e *ExportsDescriber) Describe() string {
 	table := tablewriter.CreateTable()
 	table.AddTitle("Exports")
-	table.AddHeaders("Name", "Type", "Subject", "Public", "Revocations", "Tracking")
+	table.AddHeaders("Name", "Type", "Subject", "Public", "Revocations", "Tracking", "Description", "Info URL")
 	for _, v := range e.Exports {
 		mon := "N/A"
 		rt := ""
@@ -177,7 +201,8 @@ func (e *ExportsDescriber) Describe() string {
 
 		st := strings.Title(v.Type.String())
 		k := fmt.Sprintf("%s%s", st, rt)
-		table.AddRow(v.Name, k, v.Subject, toYesNo(!v.TokenReq), len(v.Revocations), mon)
+		table.AddRow(v.Name, k, v.Subject, toYesNo(!v.TokenReq), len(v.Revocations), mon,
+			strings.ReplaceAll(v.Description, "\n", " "), v.InfoURL)
 	}
 	return table.Render()
 }
@@ -359,12 +384,7 @@ func NewUserDescriber(u jwt.UserClaims) *UserDescriber {
 	return &UserDescriber{UserClaims: u}
 }
 
-func (u *UserDescriber) Describe() string {
-	table := tablewriter.CreateTable()
-	table.AddTitle("User")
-	AddStandardClaimInfo(table, &u.UserClaims)
-	table.AddRow("Bearer Token", toYesNo(u.BearerToken))
-
+func AddPermissions(table *tablewriter.Table, u jwt.Permissions) {
 	if len(u.Pub.Allow) > 0 || len(u.Pub.Deny) > 0 ||
 		len(u.Sub.Allow) > 0 || len(u.Sub.Deny) > 0 {
 		table.AddSeparator()
@@ -373,13 +393,21 @@ func (u *UserDescriber) Describe() string {
 		AddListValues(table, "Sub Allow", u.Sub.Allow)
 		AddListValues(table, "Sub Deny", u.Sub.Deny)
 	}
-	table.AddSeparator()
 	if u.Resp == nil {
 		table.AddRow("Response Permissions", "Not Set")
 	} else {
 		table.AddRow("Max Responses", u.Resp.MaxMsgs)
 		table.AddRow("Response Permission TTL", u.Resp.Expires.String())
 	}
+}
+
+func (u *UserDescriber) Describe() string {
+	table := tablewriter.CreateTable()
+	table.AddTitle("User")
+	AddStandardClaimInfo(table, &u.UserClaims)
+	table.AddRow("Bearer Token", toYesNo(u.BearerToken))
+
+	AddPermissions(table, u.Permissions)
 
 	table.AddSeparator()
 	AddLimits(table, u.Limits)
@@ -389,6 +417,11 @@ func (u *UserDescriber) Describe() string {
 		AddListValues(table, "Tags", u.Tags)
 	}
 
+	if len(u.Tags) > 0 {
+		table.AddSeparator()
+		AddListValues(table, "Allowed Connection Types", u.AllowedConnectionTypes)
+	}
+
 	return table.Render()
 }
 
@@ -411,7 +444,13 @@ func (o *OperatorDescriber) Describe() string {
 	AddListValues(table, "Operator Service URLs", o.OperatorServiceURLs)
 
 	if o.SystemAccount != "" {
-		table.AddRow("System Account", o.SystemAccount)
+		decoration := ""
+		if fn, err := friendlyNames(o.Name); err == nil {
+			if name, ok := fn[o.SystemAccount]; ok {
+				decoration = " / " + name
+			}
+		}
+		table.AddRow("System Account", o.SystemAccount+decoration)
 	}
 
 	if len(o.SigningKeys) > 0 {
diff --git a/cmd/editaccount.go b/cmd/editaccount.go
index 6557317..11ec193 100644
--- a/cmd/editaccount.go
+++ b/cmd/editaccount.go
@@ -18,6 +18,8 @@ package cmd
 import (
 	"fmt"
 
+	cli "github.com/nats-io/cliprompts/v2"
+
 	"github.com/nats-io/nsc/cmd/store"
 
 	"github.com/nats-io/jwt/v2"
@@ -25,6 +27,24 @@ import (
 	"github.com/spf13/cobra"
 )
 
+func validatorUrlOrEmpty() cli.Opt {
+	return cli.Val(func(v string) error {
+		if v == "" {
+			return nil
+		}
+		return cli.URLValidator("http", "https")(v)
+	})
+}
+
+func validatorMaxLen(max int) cli.Opt {
+	return cli.Val(func(v string) error {
+		if len(v) > max {
+			return fmt.Errorf("value exceeds %d character", max)
+		}
+		return nil
+	})
+}
+
 func createEditAccount() *cobra.Command {
 	var params EditAccountParams
 	cmd := &cobra.Command{
@@ -45,13 +65,20 @@ func createEditAccount() *cobra.Command {
 	cmd.Flags().Int64VarP(&params.imports.NumberValue, "imports", "", -1, "set maximum number of imports for the account (-1 is unlimited)")
 	cmd.Flags().StringVarP(&params.payload.Value, "payload", "", "-1", "set maximum message payload in bytes for the account (-1 is unlimited)")
 	cmd.Flags().Int64VarP(&params.subscriptions.NumberValue, "subscriptions", "", -1, "set maximum subscription for the account (-1 is unlimited)")
+	cmd.Flags().StringVarP(&params.memStorage.Value, "mem-storage", "", "0", "set maximum memory storage in bytes for the account (-1 is unlimited / 0 disabled)")
+	cmd.Flags().StringVarP(&params.diskStorage.Value, "disk-storage", "", "0", "set maximum disk storage in bytes for the account (-1 is unlimited / 0 disabled)")
+	cmd.Flags().Int64VarP(&params.streams.NumberValue, "streams", "", -1, "set maximum streams for the account (-1 is unlimited)")
+	cmd.Flags().Int64VarP(&params.consumer.NumberValue, "consumer", "", -1, "set maximum consumer for the account (-1 is unlimited)")
 	cmd.Flags().BoolVarP(&params.exportsWc, "wildcard-exports", "", true, "exports can contain wildcards")
 	cmd.Flags().StringSliceVarP(&params.rmSigningKeys, "rm-sk", "", nil, "remove signing key - comma separated list or option can be specified multiple times")
+	cmd.Flags().StringVarP(&params.description, "description", "", "", "Description for this account")
+	cmd.Flags().StringVarP(&params.infoUrl, "info-url", "", "", "Link for more info on this account")
+	params.PermissionsParams.bindSetFlags(cmd, "default permissions")
+	params.PermissionsParams.bindRemoveFlags(cmd, "default permissions")
 
 	cmd.Flags().StringVarP(&params.AccountContextParams.Name, "name", "n", "", "account to edit")
 	params.signingKeys.BindFlags("sk", "", nkeys.PrefixByteAccount, cmd)
 	params.TimeParams.BindFlags(cmd)
-
 	return cmd
 }
 
@@ -63,11 +90,18 @@ type EditAccountParams struct {
 	AccountContextParams
 	SignerParams
 	GenericClaimsParams
+	PermissionsParams
 	claim         *jwt.AccountClaims
 	token         string
+	infoUrl       string
+	description   string
 	conns         NumberParams
 	leafConns     NumberParams
 	exports       NumberParams
+	memStorage    DataParams
+	diskStorage   DataParams
+	streams       NumberParams
+	consumer      NumberParams
 	exportsWc     bool
 	imports       NumberParams
 	subscriptions NumberParams
@@ -84,7 +118,11 @@ func (p *EditAccountParams) SetDefaults(ctx ActionCtx) error {
 	}
 	p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, true, ctx)
 
-	if !InteractiveFlag && ctx.NothingToDo("start", "expiry", "tag", "rm-tag", "conns", "leaf-conns", "exports", "imports", "subscriptions", "payload", "data", "wildcard-exports", "sk", "rm-sk") {
+	if !InteractiveFlag && ctx.NothingToDo(
+		"start", "expiry", "tag", "rm-tag", "conns", "leaf-conns", "exports", "imports", "subscriptions",
+		"payload", "data", "wildcard-exports", "sk", "rm-sk", "description", "info-url", "response-ttl", "allow-pub-response",
+		"allow-pub-response", "allow-pub", "allow-pubsub", "allow-sub", "deny-pub", "deny-pubsub", "deny-sub",
+		"rm-response-perms", "rm", "max-responses") {
 		ctx.CurrentCmd().SilenceUsage = false
 		return fmt.Errorf("specify an edit option")
 	}
@@ -139,6 +177,30 @@ func (p *EditAccountParams) Load(ctx ActionCtx) error {
 		p.subscriptions.NumberValue = p.claim.Limits.Subs
 	}
 
+	if !ctx.CurrentCmd().Flags().Changed("mem-storage") {
+		p.memStorage.Value = fmt.Sprintf("%d", p.claim.Limits.MemoryStorage)
+	}
+
+	if !ctx.CurrentCmd().Flags().Changed("disk-storage") {
+		p.diskStorage.Value = fmt.Sprintf("%d", p.claim.Limits.DiskStorage)
+	}
+
+	if !ctx.CurrentCmd().Flags().Changed("streams") {
+		p.streams.NumberValue = p.claim.Limits.Streams
+	}
+
+	if !ctx.CurrentCmd().Flags().Changed("consumer") {
+		p.consumer.NumberValue = p.claim.Limits.Consumer
+	}
+
+	if !ctx.CurrentCmd().Flags().Changed("description") {
+		p.description = p.claim.Description
+	}
+
+	if !ctx.CurrentCmd().Flags().Changed("info-url") {
+		p.infoUrl = p.claim.InfoURL
+	}
+
 	return err
 }
 
@@ -176,6 +238,24 @@ func (p *EditAccountParams) PostInteractive(ctx ActionCtx) error {
 		return err
 	}
 
+	if err = p.memStorage.Edit("max mem storage (-1 unlimited / 0 disabled)"); err != nil {
+		return err
+	}
+
+	if err = p.diskStorage.Edit("max disk storage (-1 unlimited / 0 disabled)"); err != nil {
+		return err
+	}
+
+	if p.memStorage.Number != 0 || p.diskStorage.Number != 0 {
+		if err = p.streams.Edit("max streams (-1 unlimited)"); err != nil {
+			return err
+		}
+
+		if err = p.consumer.Edit("max consumer (-1 unlimited)"); err != nil {
+			return err
+		}
+	}
+
 	if p.claim.NotBefore > 0 {
 		p.GenericClaimsParams.Start = UnixToDate(p.claim.NotBefore)
 	}
@@ -189,6 +269,15 @@ func (p *EditAccountParams) PostInteractive(ctx ActionCtx) error {
 	if err := p.SignerParams.Edit(ctx); err != nil {
 		return err
 	}
+
+	if p.description, err = cli.Prompt("Account Description", p.description, validatorMaxLen(jwt.MaxInfoLength)); err != nil {
+		return err
+	}
+
+	if p.infoUrl, err = cli.Prompt("Info url", p.infoUrl, validatorUrlOrEmpty()); err != nil {
+		return err
+	}
+
 	return nil
 }
 
@@ -200,7 +289,13 @@ func (p *EditAccountParams) Validate(ctx ActionCtx) error {
 	if err = p.GenericClaimsParams.Valid(); err != nil {
 		return err
 	}
-	if err = p.SignerParams.Resolve(ctx); err != nil {
+	if err = p.SignerParams.ResolveWithPriority(ctx, p.claim.Issuer); err != nil {
+		return err
+	}
+	if op, _ := ctx.StoreCtx().Store.ReadOperatorClaim(); op.SystemAccount == p.claim.Subject {
+		return fmt.Errorf("jetstream not available for system account")
+	}
+	if err := p.PermissionsParams.Validate(); err != nil {
 		return err
 	}
 	return nil
@@ -274,6 +369,50 @@ func (p *EditAccountParams) Run(ctx ActionCtx) (store.Status, error) {
 		r.AddOK("changed max subscriptions to %d", p.claim.Limits.Subs)
 	}
 
+	p.claim.Limits.MemoryStorage, err = p.memStorage.NumberValue()
+	if err != nil {
+		return nil, fmt.Errorf("error parsing %s: %s", "mem-storage", p.memStorage.Value)
+	}
+	if flags.Changed("mem-storage") {
+		r.AddOK("changed max mem storage to %d", p.claim.Limits.MemoryStorage)
+	}
+
+	p.claim.Limits.DiskStorage, err = p.diskStorage.NumberValue()
+	if err != nil {
+		return nil, fmt.Errorf("error parsing %s: %s", "disk-storage", p.diskStorage.Value)
+	}
+	if flags.Changed("disk-storage") {
+		r.AddOK("changed max disk storage to %d", p.claim.Limits.DiskStorage)
+	}
+
+	p.claim.Limits.Streams = p.streams.NumberValue
+	if flags.Changed("streams") {
+		r.AddOK("changed max streams to %d", p.claim.Limits.Streams)
+	}
+
+	p.claim.Limits.Consumer = p.consumer.NumberValue
+	if flags.Changed("consumer") {
+		r.AddOK("changed max consumer to %d", p.claim.Limits.Consumer)
+	}
+
+	p.claim.Description = p.description
+	if flags.Changed("description") {
+		r.AddOK(`changed description to %q`, p.claim.Description)
+	}
+
+	p.claim.InfoURL = p.infoUrl
+	if flags.Changed("info-url") {
+		r.AddOK(`changed info url to %q`, p.claim.InfoURL)
+	}
+
+	s, err := p.PermissionsParams.Run(&p.claim.DefaultPermissions, ctx)
+	if err != nil {
+		return nil, err
+	}
+	if s != nil {
+		r.Add(s.Details...)
+	}
+
 	p.token, err = p.claim.Encode(p.signerKP)
 	if err != nil {
 		return nil, err
diff --git a/cmd/editexport.go b/cmd/editexport.go
index a96d6b8..79bb8df 100644
--- a/cmd/editexport.go
+++ b/cmd/editexport.go
@@ -46,6 +46,8 @@ func createEditExportCmd() *cobra.Command {
 	cmd.Flags().StringVarP(&params.latSubject, "latency", "", "", "latency metrics subject (services only)")
 	cmd.Flags().IntVarP(&params.latSampling, "sampling", "", 0, "latency sampling percentage [0-100] - 0 disables it (services only)")
 	cmd.Flags().BoolVarP(&params.rmLatencySampling, "rm-latency-sampling", "", false, "remove latency sampling")
+	cmd.Flags().StringVarP(&params.description, "description", "", "", "Description for this export")
+	cmd.Flags().StringVarP(&params.infoUrl, "info-url", "", "", "Link for more info on this export")
 
 	hm := fmt.Sprintf("response type for the service [%s | %s | %s] (services only)", jwt.ResponseTypeSingleton, jwt.ResponseTypeStream, jwt.ResponseTypeChunked)
 	cmd.Flags().StringVarP(&params.responseType, "response-type", "", jwt.ResponseTypeSingleton, hm)
@@ -72,11 +74,14 @@ type EditExportParams struct {
 	private           bool
 	responseType      string
 	rmLatencySampling bool
+	infoUrl           string
+	description       string
 }
 
 func (p *EditExportParams) SetDefaults(ctx ActionCtx) error {
 	if !InteractiveFlag {
-		if ctx.NothingToDo("name", "subject", "service", "private", "latency", "sampling", "response-type") {
+		if ctx.NothingToDo("name", "subject", "service", "private", "latency", "sampling", "response-type",
+			"description", "info-url") {
 			return errors.New("please specify some options")
 		}
 	}
@@ -234,6 +239,14 @@ func (p *EditExportParams) PostInteractive(ctx ActionCtx) error {
 		return err
 	}
 
+	if p.description, err = cli.Prompt("Export Description", p.description, validatorMaxLen(jwt.MaxInfoLength)); err != nil {
+		return err
+	}
+
+	if p.infoUrl, err = cli.Prompt("Info url", p.infoUrl, validatorUrlOrEmpty()); err != nil {
+		return err
+	}
+
 	return nil
 }
 
@@ -302,6 +315,13 @@ func (p *EditExportParams) syncOptions(ctx ActionCtx) {
 		p.responseType = string(old.ResponseType)
 	}
 
+	if !(cmd.Flag("description").Changed) {
+		p.description = old.Description
+	}
+
+	if !(cmd.Flag("info-url").Changed) {
+		p.infoUrl = old.InfoURL
+	}
 }
 
 func (p *EditExportParams) Run(ctx ActionCtx) (store.Status, error) {
@@ -374,6 +394,16 @@ func (p *EditExportParams) Run(ctx ActionCtx) (store.Status, error) {
 		}
 	}
 
+	export.Description = p.description
+	if export.Description != old.Description {
+		r.AddOK(`changed description to %q`, p.description)
+	}
+
+	export.InfoURL = p.infoUrl
+	if export.InfoURL != old.InfoURL {
+		r.AddOK(`changed info url to %q`, p.infoUrl)
+	}
+
 	p.claim.Exports[p.index] = &export
 
 	var vr2 jwt.ValidationResults
diff --git a/cmd/editoperator.go b/cmd/editoperator.go
index 7587566..aeac018 100644
--- a/cmd/editoperator.go
+++ b/cmd/editoperator.go
@@ -42,6 +42,7 @@ func createEditOperatorCmd() *cobra.Command {
 	cmd.Flags().StringSliceVarP(&params.tags, "tag", "", nil, "add tags for user - comma separated list or option can be specified multiple times")
 	cmd.Flags().StringSliceVarP(&params.rmTags, "rm-tag", "", nil, "remove tag - comma separated list or option can be specified multiple times")
 	cmd.Flags().StringVarP(&params.asu, "account-jwt-server-url", "u", "", "set account jwt server url for nsc sync (only http/https urls supported if updating with nsc)")
+	cmd.Flags().StringVarP(&params.sysAcc, "system-account", "", "", "set system account by account by public key or name")
 	cmd.Flags().StringSliceVarP(&params.serviceURLs, "service-url", "n", nil, "add an operator service url for nsc where clients can access the NATS service (only nats/tls urls supported)")
 	cmd.Flags().StringSliceVarP(&params.rmServiceURLs, "rm-service-url", "", nil, "remove an operator service url for nsc where clients can access the NATS service (only nats/tls urls supported)")
 	params.TimeParams.BindFlags(cmd)
@@ -59,6 +60,7 @@ type EditOperatorParams struct {
 	claim         *jwt.OperatorClaims
 	token         string
 	asu           string
+	sysAcc        string
 	serviceURLs   []string
 	rmServiceURLs []string
 	signingKeys   SigningKeysParams
@@ -68,7 +70,7 @@ type EditOperatorParams struct {
 func (p *EditOperatorParams) SetDefaults(ctx ActionCtx) error {
 	p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, false, ctx)
 
-	if !InteractiveFlag && ctx.NothingToDo("sk", "rm-sk", "start", "expiry", "tag", "rm-tag", "account-jwt-server-url", "service-url", "rm-service-url") {
+	if !InteractiveFlag && ctx.NothingToDo("sk", "rm-sk", "start", "expiry", "tag", "rm-tag", "account-jwt-server-url", "service-url", "rm-service-url", "system-account") {
 		ctx.CurrentCmd().SilenceUsage = false
 		return fmt.Errorf("specify an edit option")
 	}
@@ -101,6 +103,10 @@ func (p *EditOperatorParams) Load(ctx ActionCtx) error {
 		p.asu = oc.AccountServerURL
 	}
 
+	if p.sysAcc == "" {
+		p.sysAcc = oc.SystemAccount
+	}
+
 	p.claim = oc
 	return nil
 }
@@ -159,6 +165,15 @@ func (p *EditOperatorParams) PostInteractive(ctx ActionCtx) error {
 		}
 	}
 
+	if ok, err := cli.Confirm("Set system account", false); err != nil {
+		return err
+	} else if ok {
+		p.sysAcc, err = ctx.StoreCtx().PickAccount("")
+		if err != nil {
+			return err
+		}
+	}
+
 	if err := p.signingKeys.Edit(); err != nil {
 		return err
 	}
@@ -177,7 +192,15 @@ func (p *EditOperatorParams) Validate(ctx ActionCtx) error {
 			return err
 		}
 	}
-
+	if p.sysAcc != "" {
+		if !nkeys.IsValidPublicAccountKey(p.sysAcc) {
+			if acc, err := ctx.StoreCtx().Store.ReadAccountClaim(p.sysAcc); err != nil {
+				return err
+			} else {
+				p.sysAcc = acc.Subject
+			}
+		}
+	}
 	if err = p.signingKeys.Valid(); err != nil {
 		return err
 	}
@@ -213,6 +236,11 @@ func (p *EditOperatorParams) Run(ctx ActionCtx) (store.Status, error) {
 		r.AddOK("set account jwt server url to %q", p.asu)
 	}
 
+	if p.claim.SystemAccount != p.sysAcc {
+		p.claim.SystemAccount = p.sysAcc
+		r.AddOK("set system account %q", p.sysAcc)
+	}
+
 	for _, v := range p.serviceURLs {
 		p.claim.OperatorServiceURLs.Add(strings.ToLower(v))
 		r.AddOK("added service url %q", v)
diff --git a/cmd/edituser.go b/cmd/edituser.go
index 69600e9..da8d74f 100644
--- a/cmd/edituser.go
+++ b/cmd/edituser.go
@@ -70,16 +70,6 @@ nsc edit user --name <n> --rm-response-perms
 	cmd.Flags().VarP(&params.times, "time", "", fmt.Sprintf(`add start-end time range of the form "%s-%s" (option can be specified multiple times)`, timeFormat, timeFormat))
 	cmd.Flags().StringSliceVarP(&params.rmTimes, "rm-time", "", nil, fmt.Sprintf(`remove start-end time by start time "%s" (option can be specified multiple times)`, timeFormat))
 
-	cmd.Flags().StringSliceVarP(&params.remove, "rm", "", nil, "remove publish/subscribe and deny permissions - comma separated list or option can be specified multiple times")
-
-	cmd.Flags().StringSliceVarP(&params.allowPubs, "allow-pub", "", nil, "add publish permissions - comma separated list or option can be specified multiple times")
-	cmd.Flags().StringSliceVarP(&params.allowPubsub, "allow-pubsub", "", nil, "add publish and subscribe permissions - comma separated list or option can be specified multiple times")
-	cmd.Flags().StringSliceVarP(&params.allowSubs, "allow-sub", "", nil, "add subscribe permissions - comma separated list or option can be specified multiple times")
-
-	cmd.Flags().StringSliceVarP(&params.denyPubs, "deny-pub", "", nil, "add deny publish permissions - comma separated list or option can be specified multiple times")
-	cmd.Flags().StringSliceVarP(&params.denyPubsub, "deny-pubsub", "", nil, "add deny publish and subscribe permissions - comma separated list or option can be specified multiple times")
-	cmd.Flags().StringSliceVarP(&params.denySubs, "deny-sub", "", nil, "add deny subscribe permissions - comma separated list or option can be specified multiple times")
-
 	cmd.Flags().StringSliceVarP(&params.tags, "tag", "", nil, "add tags for user - comma separated list or option can be specified multiple times")
 	cmd.Flags().StringSliceVarP(&params.rmTags, "rm-tag", "", nil, "remove tag - comma separated list or option can be specified multiple times")
 
@@ -89,13 +79,17 @@ nsc edit user --name <n> --rm-response-perms
 	cmd.Flags().Int64VarP(&params.payload.Number, "payload", "", -1, "set maximum message payload in bytes for the account (-1 is unlimited)")
 
 	cmd.Flags().StringVarP(&params.name, "name", "n", "", "user name")
+	cmd.Flags().StringSliceVarP(&params.connTypes, "conn-type", "", nil,
+		fmt.Sprintf("add connection types: %s %s %s %s - comma separated list or option can be specified multiple times",
+			jwt.ConnectionTypeLeafnode, jwt.ConnectionTypeMqtt, jwt.ConnectionTypeStandard, jwt.ConnectionTypeWebsocket))
+	cmd.Flags().StringSliceVarP(&params.rmConnTypes, "rm-conn-type", "", nil, "remove connection types - comma separated list or option can be specified multiple times")
 
 	cmd.Flags().BoolVarP(&params.bearer, "bearer", "", false, "no connect challenge required for user")
 
 	params.AccountContextParams.BindFlags(cmd)
 	params.GenericClaimsParams.BindFlags(cmd)
-	params.ResponsePermsParams.bindSetFlags(cmd)
-	params.ResponsePermsParams.bindRemoveFlags(cmd)
+	params.PermissionsParams.bindSetFlags(cmd, "permissions")
+	params.PermissionsParams.bindRemoveFlags(cmd, "permissions")
 
 	return cmd
 }
@@ -108,25 +102,20 @@ type EditUserParams struct {
 	AccountContextParams
 	SignerParams
 	GenericClaimsParams
-	ResponsePermsParams
+	PermissionsParams
 	claim         *jwt.UserClaims
 	name          string
 	token         string
 	credsFilePath string
 
-	allowPubs   []string
-	allowPubsub []string
-	allowSubs   []string
-	denyPubs    []string
-	denyPubsub  []string
-	denySubs    []string
-	remove      []string
 	rmSrc       []string
 	src         []string
 	times       timeSlice
 	rmTimes     []string
 	payload     DataParams
 	bearer      bool
+	connTypes   []string
+	rmConnTypes []string
 }
 
 func (p *EditUserParams) SetDefaults(ctx ActionCtx) error {
@@ -136,7 +125,7 @@ func (p *EditUserParams) SetDefaults(ctx ActionCtx) error {
 
 	if !InteractiveFlag && ctx.NothingToDo("start", "expiry", "rm", "allow-pub", "allow-sub", "allow-pubsub",
 		"deny-pub", "deny-sub", "deny-pubsub", "tag", "rm-tag", "source-network", "rm-source-network", "payload",
-		"rm-response-perms", "max-responses", "response-ttl", "allow-pub-response", "bearer", "rm-time", "time") {
+		"rm-response-perms", "max-responses", "response-ttl", "allow-pub-response", "bearer", "rm-time", "time", "conn-type", "rm-conn-type") {
 		ctx.CurrentCmd().SilenceUsage = false
 		return fmt.Errorf("specify an edit option")
 	}
@@ -201,7 +190,7 @@ func (p *EditUserParams) Load(ctx ActionCtx) error {
 
 func (p *EditUserParams) PostInteractive(ctx ActionCtx) error {
 	// FIXME: we won't do interactive on the response params until pub/sub/deny permissions are interactive
-	//if err := p.ResponsePermsParams.Edit(p.claim.Resp != nil); err != nil {
+	//if err := p.PermissionsParams.Edit(p.claim.Resp != nil); err != nil {
 	//	return err
 	//}
 	if err := p.payload.Edit("max payload (-1 unlimited)"); err != nil {
@@ -222,6 +211,22 @@ func (p *EditUserParams) PostInteractive(ctx ActionCtx) error {
 func (p *EditUserParams) Validate(ctx ActionCtx) error {
 	var err error
 
+	connTypes := make([]string, len(p.connTypes))
+	for i, k := range p.connTypes {
+		u := strings.ToUpper(k)
+		switch u {
+		case jwt.ConnectionTypeLeafnode, jwt.ConnectionTypeMqtt, jwt.ConnectionTypeStandard, jwt.ConnectionTypeWebsocket:
+		default:
+			return fmt.Errorf("unknown connection type %s", k)
+		}
+		connTypes[i] = u
+	}
+	rmConnTypes := make([]string, len(p.rmConnTypes))
+	for i, k := range p.rmConnTypes {
+		rmConnTypes[i] = strings.ToUpper(k)
+	}
+	p.rmConnTypes = rmConnTypes
+
 	_, err = p.payload.NumberValue()
 	if err != nil {
 		return fmt.Errorf("error parsing %s: %s", "payload", p.payload.Value)
@@ -229,14 +234,14 @@ func (p *EditUserParams) Validate(ctx ActionCtx) error {
 	if err = p.GenericClaimsParams.Valid(); err != nil {
 		return err
 	}
-	if err = p.SignerParams.Resolve(ctx); err != nil {
+	if err = p.SignerParams.ResolveWithPriority(ctx, p.claim.Issuer); err != nil {
 		return err
 	}
 	if err = p.payload.Valid(); err != nil {
 		return err
 	}
 
-	if err := p.ResponsePermsParams.Validate(); err != nil {
+	if err := p.PermissionsParams.Validate(); err != nil {
 		return err
 	}
 
@@ -250,53 +255,6 @@ func (p *EditUserParams) Run(ctx ActionCtx) (store.Status, error) {
 	var err error
 	p.GenericClaimsParams.Run(ctx, p.claim, r)
 
-	var ap []string
-	p.claim.Permissions.Pub.Allow.Add(p.allowPubs...)
-	ap = append(ap, p.allowPubs...)
-	p.claim.Permissions.Pub.Allow.Add(p.allowPubsub...)
-	ap = append(ap, p.allowPubsub...)
-	for _, v := range ap {
-		r.AddOK("added pub pub %q", v)
-	}
-	p.claim.Permissions.Pub.Allow.Remove(p.remove...)
-	for _, v := range p.remove {
-		r.AddOK("removed pub %q", v)
-	}
-	sort.Strings(p.claim.Pub.Allow)
-
-	var dp []string
-	p.claim.Permissions.Pub.Deny.Add(p.denyPubs...)
-	dp = append(dp, p.denyPubs...)
-	p.claim.Permissions.Pub.Deny.Add(p.denyPubsub...)
-	dp = append(dp, p.denyPubsub...)
-	for _, v := range dp {
-		r.AddOK("added deny pub %q", v)
-	}
-	p.claim.Permissions.Pub.Deny.Remove(p.remove...)
-	for _, v := range p.remove {
-		r.AddOK("removed deny pub %q", v)
-	}
-	sort.Strings(p.claim.Permissions.Pub.Deny)
-
-	var sa []string
-	p.claim.Permissions.Sub.Allow.Add(p.allowSubs...)
-	sa = append(sa, p.allowSubs...)
-	p.claim.Permissions.Sub.Allow.Add(p.allowPubsub...)
-	sa = append(sa, p.allowPubsub...)
-	for _, v := range sa {
-		r.AddOK("added sub %q", v)
-	}
-	p.claim.Permissions.Sub.Allow.Remove(p.remove...)
-	for _, v := range p.remove {
-		r.AddOK("removed sub %q", v)
-	}
-	sort.Strings(p.claim.Permissions.Sub.Allow)
-
-	p.claim.Permissions.Sub.Deny.Add(p.denySubs...)
-	p.claim.Permissions.Sub.Deny.Add(p.denyPubsub...)
-	p.claim.Permissions.Sub.Deny.Remove(p.remove...)
-	sort.Strings(p.claim.Permissions.Sub.Deny)
-
 	flags := ctx.CurrentCmd().Flags()
 	p.claim.Limits.Payload = p.payload.Number
 	if flags.Changed("payload") {
@@ -308,10 +266,22 @@ func (p *EditUserParams) Run(ctx ActionCtx) (store.Status, error) {
 		r.AddOK("changed bearer to %t", p.bearer)
 	}
 
+	var connTypes jwt.StringList
+	connTypes.Add(p.claim.AllowedConnectionTypes...)
+	connTypes.Add(p.connTypes...)
+	for _, v := range p.connTypes {
+		r.AddOK("added connection type %s", v)
+	}
+	connTypes.Remove(p.rmConnTypes...)
+	for _, v := range p.rmConnTypes {
+		r.AddOK("removed connection type %s", v)
+	}
+	p.claim.AllowedConnectionTypes = connTypes
+
 	var srcList jwt.CIDRList
 	srcList.Add(p.claim.Src...)
 	srcList.Add(p.src...)
-	for _, v := range p.claim.Src {
+	for _, v := range p.src {
 		r.AddOK("added src network %s", v)
 	}
 	srcList.Remove(p.rmSrc...)
@@ -339,7 +309,7 @@ func (p *EditUserParams) Run(ctx ActionCtx) (store.Status, error) {
 		}
 	}
 
-	s, err := p.ResponsePermsParams.Run(p.claim, ctx)
+	s, err := p.PermissionsParams.Run(&p.claim.Permissions, ctx)
 	if err != nil {
 		return nil, err
 	}
diff --git a/cmd/importcollector.go b/cmd/importcollector.go
index 9b0072d..d9872d9 100644
--- a/cmd/importcollector.go
+++ b/cmd/importcollector.go
@@ -63,7 +63,14 @@ func (aes AccountExportChoices) String() []string {
 func (aec *AccountExportChoice) String() string {
 	// label
 	if aec.Selection == nil {
-		return fmt.Sprintf("%s:", aec.Name)
+		footer := ""
+		if aec.AccountExport.Description != "" {
+			footer = fmt.Sprintf("%s", aec.AccountExport.Description)
+		}
+		if aec.AccountExport.InfoURL != "" {
+			footer = fmt.Sprintf("%s\n  Link: %s", footer, aec.AccountExport.InfoURL)
+		}
+		return fmt.Sprintf("%s: %s", aec.Name, footer)
 	}
 	// an actual export
 	k := "->"
@@ -76,11 +83,19 @@ func (aec *AccountExportChoice) String() string {
 		p = "[!]"
 	}
 
+	footer := ""
+	if aec.Selection.Description != "" {
+		footer = fmt.Sprintf("\n       Description: %s", aec.Selection.Description)
+	}
+	if aec.Selection.InfoURL != "" {
+		footer = fmt.Sprintf("%s\n       Link: %s", footer, aec.Selection.InfoURL)
+	}
+
 	if aec.Selection.Name == string(aec.Selection.Subject) {
-		return fmt.Sprintf("  %s %s %s", k, aec.Selection.Subject, p)
+		return fmt.Sprintf("  %s %s %s%s", k, aec.Selection.Subject, p, footer)
 	}
 
-	return fmt.Sprintf("  %s [%s] %s %s", k, aec.Selection.Name, aec.Selection.Subject, p)
+	return fmt.Sprintf("  %s [%s] %s %s%s", k, aec.Selection.Name, aec.Selection.Subject, p, footer)
 }
 
 type AccountImport struct {
diff --git a/cmd/signerparams.go b/cmd/signerparams.go
index f61d588..dcab8dd 100644
--- a/cmd/signerparams.go
+++ b/cmd/signerparams.go
@@ -157,6 +157,10 @@ func (p *SignerParams) getSigners(ctx ActionCtx) ([]string, error) {
 }
 
 func (p *SignerParams) Resolve(ctx ActionCtx) error {
+	return p.ResolveWithPriority(ctx, "")
+}
+
+func (p *SignerParams) ResolveWithPriority(ctx ActionCtx, preferKey string) error {
 	if p.signerKP != nil {
 		return nil
 	}
@@ -181,6 +185,13 @@ func (p *SignerParams) Resolve(ctx ActionCtx) error {
 	if err != nil {
 		return fmt.Errorf("error reading signers: %v", err)
 	}
+	for _, s := range signers {
+		if s == preferKey {
+			signers = append([]string{s}, signers...)
+			break
+		}
+	}
+
 	var selected string
 	for _, s := range signers {
 		fp := ctx.StoreCtx().KeyStore.GetKeyPath(s)
diff --git a/go.mod b/go.mod
index ced5ca6..e73e668 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/nats-io/cliprompts/v2 v2.0.0-20191226174129-372d79b36768
 	github.com/nats-io/jwt v1.2.2
-	github.com/nats-io/jwt/v2 v2.0.0-20201130160941-e23261c05d3e
+	github.com/nats-io/jwt/v2 v2.0.0-20201207222029-6d7e74b660de
 	github.com/nats-io/nats-server/v2 v2.1.8-0.20201125160748-de0c992ca6db
 	github.com/nats-io/nats.go v1.10.1-0.20201021145452-94be476ad6e0
 	github.com/nats-io/nkeys v0.2.0
diff --git a/go.sum b/go.sum
index 0701e25..e37b85d 100644
--- a/go.sum
+++ b/go.sum
@@ -84,8 +84,8 @@ github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34s
 github.com/nats-io/jwt/v2 v2.0.0-20200916203241-1f8ce17dff02/go.mod h1:vs+ZEjP+XKy8szkBmQwCB7RjYdIlMaPsFPs4VdS4bTQ=
 github.com/nats-io/jwt/v2 v2.0.0-20201015190852-e11ce317263c h1:Hc1D9ChlsCMVwCxJ6QT5xqfk2zJ4XNea+LtdfaYhd20=
 github.com/nats-io/jwt/v2 v2.0.0-20201015190852-e11ce317263c/go.mod h1:vs+ZEjP+XKy8szkBmQwCB7RjYdIlMaPsFPs4VdS4bTQ=
-github.com/nats-io/jwt/v2 v2.0.0-20201130160941-e23261c05d3e h1:/yNdVZ6g+laSgpQ/qH5zxN8+gHPSqfJuA9ZjPG2rMK4=
-github.com/nats-io/jwt/v2 v2.0.0-20201130160941-e23261c05d3e/go.mod h1:PuO5FToRL31ecdFqVjc794vK0Bj0CwzveQEDvkb7MoQ=
+github.com/nats-io/jwt/v2 v2.0.0-20201207222029-6d7e74b660de h1:AZER/vBA93FmJg6yQEDf1yGY/5lhujx7AcWub1Q41Go=
+github.com/nats-io/jwt/v2 v2.0.0-20201207222029-6d7e74b660de/go.mod h1:PuO5FToRL31ecdFqVjc794vK0Bj0CwzveQEDvkb7MoQ=
 github.com/nats-io/nats-server/v2 v2.1.8-0.20200524125952-51ebd92a9093/go.mod h1:rQnBf2Rv4P9adtAs/Ti6LfFmVtFG6HLhl/H7cVshcJU=
 github.com/nats-io/nats-server/v2 v2.1.8-0.20200601203034-f8d6dd992b71/go.mod h1:Nan/1L5Sa1JRW+Thm4HNYcIDcVRFc5zK9OpSZeI2kk4=
 github.com/nats-io/nats-server/v2 v2.1.8-0.20200929001935-7f44d075f7ad/go.mod h1:TkHpUIDETmTI7mrHN40D1pzxfzHZuGmtMbtb83TGVQw=
diff --git a/vendor/github.com/nats-io/jwt/v2/account_claims.go b/vendor/github.com/nats-io/jwt/v2/account_claims.go
index c586411..5db453d 100644
--- a/vendor/github.com/nats-io/jwt/v2/account_claims.go
+++ b/vendor/github.com/nats-io/jwt/v2/account_claims.go
@@ -92,6 +92,7 @@ type Account struct {
 	SigningKeys        StringList     `json:"signing_keys,omitempty"`
 	Revocations        RevocationList `json:"revocations,omitempty"`
 	DefaultPermissions Permissions    `json:"default_permissions,omitempty"`
+	Info
 	GenericFields
 }
 
@@ -131,6 +132,7 @@ func (a *Account) Validate(acct *AccountClaims, vr *ValidationResults) {
 			vr.AddError("%s is not an account public key", k)
 		}
 	}
+	a.Info.Validate(vr)
 }
 
 // AccountClaims defines the body of an account JWT
diff --git a/vendor/github.com/nats-io/jwt/v2/exports.go b/vendor/github.com/nats-io/jwt/v2/exports.go
index c43b9a3..04d900c 100644
--- a/vendor/github.com/nats-io/jwt/v2/exports.go
+++ b/vendor/github.com/nats-io/jwt/v2/exports.go
@@ -81,6 +81,7 @@ type Export struct {
 	ResponseThreshold    time.Duration   `json:"response_threshold,omitempty"`
 	Latency              *ServiceLatency `json:"service_latency,omitempty"`
 	AccountTokenPosition uint            `json:"account_token_position,omitempty"`
+	Info
 }
 
 // IsService returns true if an export is for a service
@@ -153,6 +154,7 @@ func (e *Export) Validate(vr *ValidationResults) {
 			}
 		}
 	}
+	e.Info.Validate(vr)
 }
 
 // Revoke enters a revocation by publickey using time.Now().
diff --git a/vendor/github.com/nats-io/jwt/v2/imports.go b/vendor/github.com/nats-io/jwt/v2/imports.go
index 04a9e36..e4838a4 100644
--- a/vendor/github.com/nats-io/jwt/v2/imports.go
+++ b/vendor/github.com/nats-io/jwt/v2/imports.go
@@ -107,13 +107,14 @@ func (i *Import) Validate(actPubKey string, vr *ValidationResults) {
 	}
 
 	if act != nil {
-		if act.Issuer != i.Account {
-			vr.AddWarning("activation token doesn't match account for import %q", i.Subject)
+		if !(act.Issuer == i.Account || act.IssuerAccount == i.Account) {
+			vr.AddError("activation token doesn't match account for import %q", i.Subject)
 		}
 
 		if act.ClaimsData.Subject != actPubKey {
-			vr.AddWarning("activation token doesn't match account it is being included in, %q", i.Subject)
+			vr.AddError("activation token doesn't match account it is being included in, %q", i.Subject)
 		}
+		act.Validate(vr)
 	} else {
 		vr.AddWarning("no activation provided for import %s", i.Subject)
 	}
diff --git a/vendor/github.com/nats-io/jwt/v2/types.go b/vendor/github.com/nats-io/jwt/v2/types.go
index 34a3209..78dc9cf 100644
--- a/vendor/github.com/nats-io/jwt/v2/types.go
+++ b/vendor/github.com/nats-io/jwt/v2/types.go
@@ -19,10 +19,36 @@ import (
 	"encoding/json"
 	"fmt"
 	"net"
+	"net/url"
 	"strings"
 	"time"
 )
 
+const MaxInfoLength = 255
+
+type Info struct {
+	Description string `json:"description,omitempty"`
+	InfoURL     string `json:"info_url,omitempty"`
+}
+
+func (s Info) Validate(vr *ValidationResults) {
+	if len(s.Description) > MaxInfoLength {
+		vr.AddError("Description is too long")
+	}
+	if s.InfoURL != "" {
+		if len(s.InfoURL) > MaxInfoLength {
+			vr.AddError("Info URL is too long")
+		}
+		u, err := url.Parse(s.InfoURL)
+		if err == nil && (u.Hostname() == "" || u.Scheme == "") {
+			err = fmt.Errorf("no hostname or scheme")
+		}
+		if err != nil {
+			vr.AddError("error parsing info url: %v", err)
+		}
+	}
+}
+
 // ExportType defines the type of import/export.
 type ExportType int
 
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 310d065..f65aa1a 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -72,7 +72,7 @@ github.com/nats-io/cliprompts/v2
 # github.com/nats-io/jwt v1.2.2
 ## explicit
 github.com/nats-io/jwt
-# github.com/nats-io/jwt/v2 v2.0.0-20201130160941-e23261c05d3e
+# github.com/nats-io/jwt/v2 v2.0.0-20201207222029-6d7e74b660de
 ## explicit
 github.com/nats-io/jwt/v2
 # github.com/nats-io/nats-server/v2 v2.1.8-0.20201125160748-de0c992ca6db


Test patch:
diff --git a/cmd/addaccount_test.go b/cmd/addaccount_test.go
index 24a6a6f..2b97823 100644
--- a/cmd/addaccount_test.go
+++ b/cmd/addaccount_test.go
@@ -266,3 +266,19 @@ func Test_AddAccountWithSigningKeyOnly(t *testing.T) {
 	_, err = ts.Store.ReadAccountClaim("A")
 	require.NoError(t, err)
 }
+
+func Test_AddAccount_Pubs(t *testing.T) {
+	ts := NewTestStore(t, "edit user")
+	defer ts.Done(t)
+
+	_, _, err := ExecuteCmd(CreateAddAccountCmd(), "-n", "A", "--allow-pub", "a,b", "--allow-pubsub", "c", "--deny-pub", "foo", "--deny-pubsub", "bar")
+	require.NoError(t, err)
+
+	cc, err := ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	require.NotNil(t, cc)
+	require.ElementsMatch(t, cc.DefaultPermissions.Pub.Allow, []string{"a", "b", "c"})
+	require.ElementsMatch(t, cc.DefaultPermissions.Sub.Allow, []string{"c"})
+	require.ElementsMatch(t, cc.DefaultPermissions.Pub.Deny, []string{"foo", "bar"})
+	require.ElementsMatch(t, cc.DefaultPermissions.Sub.Deny, []string{"bar"})
+}
diff --git a/cmd/editaccount_test.go b/cmd/editaccount_test.go
index 85486f0..cb42bbc 100644
--- a/cmd/editaccount_test.go
+++ b/cmd/editaccount_test.go
@@ -19,6 +19,9 @@ package cmd
 
 import (
 	"testing"
+	"time"
+
+	"github.com/nats-io/nkeys"
 
 	"github.com/stretchr/testify/require"
 )
@@ -32,6 +35,8 @@ func Test_EditAccount(t *testing.T) {
 
 	tests := CmdTests{
 		{createEditAccount(), []string{"edit", "account"}, nil, []string{"specify an edit option"}, true},
+		{createEditAccount(), []string{"edit", "account", "--info-url", "http://foo/bar"}, nil, []string{"changed info url to"}, false},
+		{createEditAccount(), []string{"edit", "account", "--description", "my account is about this"}, nil, []string{"changed description to"}, false},
 		{createEditAccount(), []string{"edit", "account", "--tag", "A", "--name", "A"}, nil, []string{"edited account \"A\""}, false},
 	}
 
@@ -110,7 +115,8 @@ func Test_EditAccountLimits(t *testing.T) {
 
 	ts.AddAccount(t, "A")
 	_, _, err := ExecuteCmd(createEditAccount(), "--conns", "5", "--data", "10M", "--exports", "15",
-		"--imports", "20", "--payload", "1K", "--subscriptions", "30", "--leaf-conns", "31")
+		"--imports", "20", "--payload", "1K", "--subscriptions", "30", "--leaf-conns", "31",
+		"--streams", "5", "--consumer", "6", "--disk-storage", "7", "--mem-storage", "8")
 	require.NoError(t, err)
 
 	ac, err := ts.Store.ReadAccountClaim("A")
@@ -122,6 +128,10 @@ func Test_EditAccountLimits(t *testing.T) {
 	require.Equal(t, int64(20), ac.Limits.Imports)
 	require.Equal(t, int64(1000), ac.Limits.Payload)
 	require.Equal(t, int64(30), ac.Limits.Subs)
+	require.Equal(t, int64(5), ac.Limits.Streams)
+	require.Equal(t, int64(6), ac.Limits.Consumer)
+	require.Equal(t, int64(7), ac.Limits.DiskStorage)
+	require.Equal(t, int64(8), ac.Limits.MemoryStorage)
 }
 
 func Test_EditAccountSigningKeys(t *testing.T) {
@@ -147,3 +157,81 @@ func Test_EditAccountSigningKeys(t *testing.T) {
 	require.NoError(t, err)
 	require.NotContains(t, ac.SigningKeys, pk)
 }
+
+func Test_EditAccount_Pubs(t *testing.T) {
+	ts := NewTestStore(t, "edit user")
+	defer ts.Done(t)
+
+	ts.AddAccount(t, "A")
+
+	_, _, err := ExecuteCmd(createEditAccount(), "--allow-pub", "a,b", "--allow-pubsub", "c", "--deny-pub", "foo", "--deny-pubsub", "bar")
+	require.NoError(t, err)
+
+	cc, err := ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	require.NotNil(t, cc)
+	require.ElementsMatch(t, cc.DefaultPermissions.Pub.Allow, []string{"a", "b", "c"})
+	require.ElementsMatch(t, cc.DefaultPermissions.Sub.Allow, []string{"c"})
+	require.ElementsMatch(t, cc.DefaultPermissions.Pub.Deny, []string{"foo", "bar"})
+	require.ElementsMatch(t, cc.DefaultPermissions.Sub.Deny, []string{"bar"})
+
+	_, _, err = ExecuteCmd(createEditAccount(), "--rm", "c,bar")
+	require.NoError(t, err)
+	cc, err = ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	require.NotNil(t, cc)
+
+	require.ElementsMatch(t, cc.DefaultPermissions.Pub.Allow, []string{"a", "b"})
+	require.Len(t, cc.DefaultPermissions.Sub.Allow, 0)
+	require.ElementsMatch(t, cc.DefaultPermissions.Pub.Deny, []string{"foo"})
+	require.Len(t, cc.DefaultPermissions.Sub.Deny, 0)
+}
+
+func Test_EditAccountResponsePermissions(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+	ts.AddAccount(t, "A")
+
+	_, _, err := ExecuteCmd(createEditAccount(), "--max-responses", "1000", "--response-ttl", "4ms")
+	require.NoError(t, err)
+
+	uc, err := ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	require.NotNil(t, uc.DefaultPermissions.Resp)
+	require.Equal(t, 1000, uc.DefaultPermissions.Resp.MaxMsgs)
+	d, _ := time.ParseDuration("4ms")
+	require.Equal(t, d, uc.DefaultPermissions.Resp.Expires)
+
+	_, _, err = ExecuteCmd(createEditAccount(), "--rm-response-perms")
+	require.NoError(t, err)
+
+	uc, err = ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	require.Nil(t, uc.DefaultPermissions.Resp)
+}
+
+func Test_EditAccountSk(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+
+	sk, err := nkeys.CreateOperator()
+	require.NoError(t, err)
+	_, err = ts.KeyStore.Store(sk)
+	require.NoError(t, err)
+	pSk, err := sk.PublicKey()
+	require.NoError(t, err)
+
+	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--sk", pSk)
+	require.NoError(t, err)
+
+	ts.AddAccountWithSigner(t, "A", sk)
+	ac, err := ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	require.Equal(t, ac.Issuer, pSk)
+
+	_, _, err = ExecuteCmd(createEditAccount(), "--tag", "foo")
+	require.NoError(t, err)
+	ac, err = ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	require.Equal(t, ac.Issuer, pSk)
+}
diff --git a/cmd/editexport_test.go b/cmd/editexport_test.go
index 4acc5e7..5818c07 100644
--- a/cmd/editexport_test.go
+++ b/cmd/editexport_test.go
@@ -66,7 +66,8 @@ func Test_EditExportInteractive(t *testing.T) {
 	ts.AddExport(t, "A", jwt.Service, "a", true)
 	ts.AddExport(t, "A", jwt.Service, "b", true)
 
-	_, _, err := ExecuteInteractiveCmd(createEditExportCmd(), []interface{}{1, 1, "c", "c", false, false, 1})
+	link := "http://foo/bar"
+	_, _, err := ExecuteInteractiveCmd(createEditExportCmd(), []interface{}{1, 1, "c", "c", false, false, 1, "desc", link})
 	require.NoError(t, err)
 
 	ac, err := ts.Store.ReadAccountClaim("A")
@@ -74,6 +75,8 @@ func Test_EditExportInteractive(t *testing.T) {
 	require.Len(t, ac.Exports, 2)
 	require.Equal(t, jwt.Subject("c"), ac.Exports[1].Subject)
 	require.EqualValues(t, jwt.ResponseTypeStream, ac.Exports[1].ResponseType)
+	require.Equal(t, ac.Exports[1].Description, "desc")
+	require.Equal(t, ac.Exports[1].InfoURL, link)
 }
 
 func Test_EditExportInteractiveLatency(t *testing.T) {
@@ -83,7 +86,7 @@ func Test_EditExportInteractiveLatency(t *testing.T) {
 	ts.AddAccount(t, "A")
 	ts.AddExport(t, "A", jwt.Service, "a", true)
 
-	_, _, err := ExecuteInteractiveCmd(createEditExportCmd(), []interface{}{0, 1, "c", "c", false, true, "100", "lat", 2})
+	_, _, err := ExecuteInteractiveCmd(createEditExportCmd(), []interface{}{0, 1, "c", "c", false, true, "100", "lat", 2, "", ""})
 	require.NoError(t, err)
 
 	ac, err := ts.Store.ReadAccountClaim("A")
diff --git a/cmd/editoperator_test.go b/cmd/editoperator_test.go
index 909c93c..2665ee0 100644
--- a/cmd/editoperator_test.go
+++ b/cmd/editoperator_test.go
@@ -28,9 +28,13 @@ import (
 func Test_EditOperator(t *testing.T) {
 	ts := NewTestStore(t, "O")
 	defer ts.Done(t)
+	ts.AddAccount(t, "TEST")
 
 	tests := CmdTests{
 		{createEditOperatorCmd(), []string{"edit", "operator"}, nil, []string{"specify an edit option"}, true},
+		{createEditOperatorCmd(), []string{"edit", "operator", "--system-account", "ABTFVAXATJEOKIBESJ3LO3JTAAMDCZ755DLAAGGSDMH5TU6HSFL7YNYY"}, nil, []string{"set system account"}, false},
+		{createEditOperatorCmd(), []string{"edit", "operator", "--system-account", "TEST"}, nil, []string{"set system account"}, false},
+		{createEditOperatorCmd(), []string{"edit", "operator", "--system-account", "DOESNOTEXIST"}, nil, []string{"account DOESNOTEXIST does not exist in the current operator"}, true},
 		{createEditOperatorCmd(), []string{"edit", "operator", "--sk"}, nil, []string{"flag needs an argument"}, true},
 		{createEditOperatorCmd(), []string{"edit", "operator", "--sk", "SAADOZRUTPZS6LIXS6CSSSW5GXY3DNMQMSDTVWHQNHQTIBPGNSADSMBPEU"}, nil, []string{"invalid operator signing key"}, true},
 		{createEditOperatorCmd(), []string{"edit", "operator", "--sk", "OBMWGGURAFWMH3AFDX65TVIH4ZYSL7UKZ3LOH2ZRWIAU7PGZ3IJNR6W5"}, nil, []string{"edited operator"}, false},
@@ -109,12 +113,16 @@ func Test_EditOperatorServiceURLs(t *testing.T) {
 func Test_EditOperatorServiceURLsInteractive(t *testing.T) {
 	ts := NewTestStore(t, "O")
 	defer ts.Done(t)
+	ts.AddAccount(t, "SYS")
+	pub := ts.GetAccountPublicKey(t, "SYS")
 
 	u1 := "nats://localhost:4222"
 	u2 := "tls://localhost:4333"
+	as := "nats://localhost:4222"
 
-	// valid from, valid until, add tags, acc jwt server, add service url, url, add another, url, add another, add signing key
-	inputs := []interface{}{"0", "0", true, "xxx", false, "", true, u1, true, u2, false, false}
+	// valid from, valid until, add tags, acc jwt server, add service url, url, add another, url, add another,
+	// system account (defaults to SYS), add signing key
+	inputs := []interface{}{"0", "0", true, "xxx", false, as, true, u1, true, u2, false, true, false, false}
 
 	_, _, err := ExecuteInteractiveCmd(createEditOperatorCmd(), inputs)
 	require.NoError(t, err)
@@ -124,9 +132,11 @@ func Test_EditOperatorServiceURLsInteractive(t *testing.T) {
 	require.Contains(t, oc.OperatorServiceURLs, u1)
 	require.Contains(t, oc.OperatorServiceURLs, u2)
 	require.Contains(t, oc.Tags, "xxx")
+	require.Equal(t, oc.AccountServerURL, as)
+	require.Equal(t, oc.SystemAccount, pub)
 
 	// valid from, valid until, acc jwt server, add service url, remove server urls, add signing key
-	inputs = []interface{}{"0", "0", true, []int{0}, false, "", false, true, []int{0}, false}
+	inputs = []interface{}{"0", "0", true, []int{0}, false, "", false, true, []int{0}, false, false}
 
 	_, _, err = ExecuteInteractiveCmd(createEditOperatorCmd(), inputs)
 	require.NoError(t, err)
@@ -135,4 +145,6 @@ func Test_EditOperatorServiceURLsInteractive(t *testing.T) {
 	require.NotContains(t, oc.OperatorServiceURLs, u1)
 	require.Contains(t, oc.OperatorServiceURLs, u2)
 	require.NotContains(t, oc.Tags, "xxx")
+	require.Equal(t, oc.AccountServerURL, "")
+	require.Equal(t, oc.SystemAccount, pub)
 }
diff --git a/cmd/edituser_test.go b/cmd/edituser_test.go
index c2a32cd..89c62cb 100644
--- a/cmd/edituser_test.go
+++ b/cmd/edituser_test.go
@@ -37,6 +37,7 @@ func Test_EditUser(t *testing.T) {
 	tests := CmdTests{
 		{createEditUserCmd(), []string{"edit", "user"}, nil, []string{"specify an edit option"}, true},
 		{createEditUserCmd(), []string{"edit", "user", "--tag", "A", "--account", "A"}, nil, []string{"edited user \"a\""}, false},
+		{createEditUserCmd(), []string{"edit", "user", "--conn-type", "MQTT", "--rm-conn-type", "LEAFNODE", "--account", "A"}, nil, []string{"added connection type MQTT", "added connection type MQTT"}, false},
 		{createEditUserCmd(), []string{"edit", "user", "--tag", "B", "--account", "B"}, nil, []string{"user name is required"}, true},
 		{createEditUserCmd(), []string{"edit", "user", "--tag", "B", "--account", "B", "--name", "bb"}, nil, []string{"edited user \"bb\""}, false},
 	}
@@ -441,3 +442,30 @@ func Test_EditUserWithSigningKeyInteractive(t *testing.T) {
 	require.NotEqual(t, claim.Issuer, claim.IssuerAccount)
 	require.Equal(t, claim.Issuer, pk)
 }
+
+func Test_EditUserSk(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+	ts.AddAccount(t, "A")
+
+	sk, err := nkeys.CreateAccount()
+	require.NoError(t, err)
+	_, err = ts.KeyStore.Store(sk)
+	require.NoError(t, err)
+	pSk, err := sk.PublicKey()
+	require.NoError(t, err)
+
+	_, _, err = ExecuteCmd(createEditAccount(), "--sk", pSk)
+	require.NoError(t, err)
+
+	ts.AddUserWithSigner(t, "A", "u", sk)
+	uc, err := ts.Store.ReadUserClaim("A", "u")
+	require.NoError(t, err)
+	require.Equal(t, uc.Issuer, pSk)
+
+	_, _, err = ExecuteCmd(createEditUserCmd(), "--tag", "foo")
+	require.NoError(t, err)
+	uc, err = ts.Store.ReadUserClaim("A", "u")
+	require.NoError(t, err)
+	require.Equal(t, uc.Issuer, pSk)
+}