Repository: nats-io/nsc
Pull request #414 (2021-07-12 21:51:31)

[refactoring] all fields of edit user belonging to UserPermissionLimits
This is in preparation of scoped signing keys

Signed-off-by: Matthias Hanel <mh@synadia.com>


Patch:
diff --git a/cmd/dataparams.go b/cmd/dataparams.go
deleted file mode 100644
index 1f177aa..0000000
--- a/cmd/dataparams.go
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2018 The NATS Authors
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package cmd
-
-import (
-	cli "github.com/nats-io/cliprompts/v2"
-)
-
-type DataParams struct {
-	Value  string
-	Number int64
-}
-
-func (e *DataParams) Valid() error {
-	// flag already insures this is a number
-	return nil
-}
-
-func (e *DataParams) Edit(prompt string) error {
-	var err error
-	var nv int64
-	sv, err := cli.Prompt(prompt, e.Value, cli.Val(func(s string) error {
-		nv, err = ParseNumber(s)
-		return err
-	}))
-	if err != nil {
-		return err
-	}
-	e.Number = nv
-	e.Value = sv
-	return nil
-}
-
-func (e *DataParams) NumberValue() (int64, error) {
-	return ParseNumber(e.Value)
-}
diff --git a/cmd/editaccount.go b/cmd/editaccount.go
index 0bcd036..2fa0826 100644
--- a/cmd/editaccount.go
+++ b/cmd/editaccount.go
@@ -58,19 +58,27 @@ func createEditAccount() *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().Int64VarP(&params.conns.NumberValue, "conns", "", -1, "set maximum active connections for the account (-1 is unlimited)")
-	cmd.Flags().Int64VarP(&params.leafConns.NumberValue, "leaf-conns", "", 0, "set maximum active leaf node connections for the account (-1 is unlimited)")
-	cmd.Flags().StringVarP(&params.data.Value, "data", "", "-1", "set maximum data in bytes for the account (-1 is unlimited)")
-	cmd.Flags().Int64VarP(&params.exports.NumberValue, "exports", "", -1, "set maximum number of exports for the account (-1 is unlimited)")
-	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)")
+	params.conns = -1
+	cmd.Flags().VarP(&params.conns, "conns", "", "set maximum active connections for the account (-1 is unlimited)")
+	cmd.Flags().VarP(&params.leafConns, "leaf-conns", "", "set maximum active leaf node connections for the account (-1 is unlimited)")
+	params.data = -1
+	cmd.Flags().VarP(&params.data, "data", "", "set maximum data in bytes for the account (-1 is unlimited)")
+	params.exports = -1
+	cmd.Flags().VarP(&params.exports, "exports", "", "set maximum number of exports for the account (-1 is unlimited)")
+	params.imports = -1
+	cmd.Flags().VarP(&params.imports, "imports", "", "set maximum number of imports for the account (-1 is unlimited)")
+	params.payload = -1
+	cmd.Flags().VarP(&params.payload, "payload", "", "set maximum message payload in bytes for the account (-1 is unlimited)")
+	params.subscriptions = -1
+	cmd.Flags().VarP(&params.subscriptions, "subscriptions", "", "set maximum subscription for the account (-1 is unlimited)")
 	// changed flag names by prefixing them with js-
 	// to replace them mark the old names as hidden (so using them does not break) and add new flags with correct name
-	cmd.Flags().StringVarP(&params.memStorage.Value, "mem-storage", "", "0", "")
-	cmd.Flags().StringVarP(&params.diskStorage.Value, "disk-storage", "", "0", "")
-	cmd.Flags().Int64VarP(&params.streams.NumberValue, "streams", "", -1, "")
-	cmd.Flags().Int64VarP(&params.consumer.NumberValue, "consumer", "", -1, "")
+	cmd.Flags().VarP(&params.memStorage, "mem-storage", "", "")
+	cmd.Flags().VarP(&params.diskStorage, "disk-storage", "", "")
+	params.streams = -1
+	cmd.Flags().VarP(&params.streams, "streams", "", "")
+	params.consumer = -1
+	cmd.Flags().VarP(&params.consumer, "consumer", "", "")
 	cmd.Flags().MarkHidden("mem-storage")
 	cmd.Flags().MarkHidden("disk-storage")
 	cmd.Flags().MarkHidden("streams")
@@ -79,10 +87,12 @@ func createEditAccount() *cobra.Command {
 	cmd.Flags().MarkDeprecated("disk-storage", "it got renamed to --js-disk-storage")
 	cmd.Flags().MarkDeprecated("streams", "it got renamed to --js-streams")
 	cmd.Flags().MarkDeprecated("consumer", "it got renamed to --js-consumer")
-	cmd.Flags().StringVarP(&params.memStorage.Value, "js-mem-storage", "", "0", "Jetstream: set maximum memory storage in bytes for the account (-1 is unlimited / 0 disabled) (units: k/m/g/t kib/mib/gib/tib)")
-	cmd.Flags().StringVarP(&params.diskStorage.Value, "js-disk-storage", "", "0", "Jetstream: set maximum disk storage in bytes for the account (-1 is unlimited / 0 disabled) (units: k/m/g/t kib/mib/gib/tib)")
-	cmd.Flags().Int64VarP(&params.streams.NumberValue, "js-streams", "", -1, "Jetstream: set maximum streams for the account (-1 is unlimited)")
-	cmd.Flags().Int64VarP(&params.consumer.NumberValue, "js-consumer", "", -1, "Jetstream: set maximum consumer for the account (-1 is unlimited)")
+	cmd.Flags().VarP(&params.memStorage, "js-mem-storage", "", "Jetstream: set maximum memory storage in bytes for the account (-1 is unlimited / 0 disabled) (units: k/m/g/t kib/mib/gib/tib)")
+	cmd.Flags().VarP(&params.diskStorage, "js-disk-storage", "", "Jetstream: set maximum disk storage in bytes for the account (-1 is unlimited / 0 disabled) (units: k/m/g/t kib/mib/gib/tib)")
+	params.streams = -1
+	cmd.Flags().VarP(&params.streams, "js-streams", "", "Jetstream: set maximum streams for the account (-1 is unlimited)")
+	params.consumer = -1
+	cmd.Flags().VarP(&params.consumer, "js-consumer", "", "Jetstream: 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")
@@ -112,15 +122,15 @@ type EditAccountParams struct {
 	conns         NumberParams
 	leafConns     NumberParams
 	exports       NumberParams
-	memStorage    DataParams
-	diskStorage   DataParams
+	memStorage    NumberParams
+	diskStorage   NumberParams
 	streams       NumberParams
 	consumer      NumberParams
 	exportsWc     bool
 	imports       NumberParams
 	subscriptions NumberParams
-	payload       DataParams
-	data          DataParams
+	payload       NumberParams
+	data          NumberParams
 	signingKeys   SigningKeysParams
 	rmSigningKeys []string
 }
@@ -165,47 +175,47 @@ func (p *EditAccountParams) Load(ctx ActionCtx) error {
 	}
 
 	if !ctx.CurrentCmd().Flags().Changed("conns") {
-		p.conns.NumberValue = p.claim.Limits.Conn
+		p.conns = NumberParams(p.claim.Limits.Conn)
 	}
 
 	if !ctx.CurrentCmd().Flags().Changed("leaf-conns") {
-		p.leafConns.NumberValue = p.claim.Limits.LeafNodeConn
+		p.leafConns = NumberParams(p.claim.Limits.LeafNodeConn)
 	}
 
 	if !ctx.CurrentCmd().Flags().Changed("data") {
-		p.data.Value = fmt.Sprintf("%d", p.claim.Limits.Data)
+		p.data = NumberParams(p.claim.Limits.Data)
 	}
 
 	if !ctx.CurrentCmd().Flags().Changed("exports") {
-		p.exports.NumberValue = p.claim.Limits.Exports
+		p.exports = NumberParams(p.claim.Limits.Exports)
 	}
 
 	if !ctx.CurrentCmd().Flags().Changed("imports") {
-		p.imports.NumberValue = p.claim.Limits.Imports
+		p.imports = NumberParams(p.claim.Limits.Imports)
 	}
 
 	if !ctx.CurrentCmd().Flags().Changed("payload") {
-		p.payload.Value = fmt.Sprintf("%d", p.claim.Limits.Payload)
+		p.payload = NumberParams(p.claim.Limits.Payload)
 	}
 
 	if !ctx.CurrentCmd().Flags().Changed("subscriptions") {
-		p.subscriptions.NumberValue = p.claim.Limits.Subs
+		p.subscriptions = NumberParams(p.claim.Limits.Subs)
 	}
 
 	if !(ctx.CurrentCmd().Flags().Changed("js-mem-storage") || ctx.CurrentCmd().Flags().Changed("mem-storage")) {
-		p.memStorage.Value = fmt.Sprintf("%d", p.claim.Limits.MemoryStorage)
+		p.memStorage = NumberParams(p.claim.Limits.MemoryStorage)
 	}
 
 	if !(ctx.CurrentCmd().Flags().Changed("js-disk-storage") || ctx.CurrentCmd().Flags().Changed("disk-storage")) {
-		p.diskStorage.Value = fmt.Sprintf("%d", p.claim.Limits.DiskStorage)
+		p.diskStorage = NumberParams(p.claim.Limits.DiskStorage)
 	}
 
 	if !(ctx.CurrentCmd().Flags().Changed("js-streams") || ctx.CurrentCmd().Flags().Changed("streams")) {
-		p.streams.NumberValue = p.claim.Limits.Streams
+		p.streams = NumberParams(p.claim.Limits.Streams)
 	}
 
 	if !(ctx.CurrentCmd().Flags().Changed("js-consumer") || ctx.CurrentCmd().Flags().Changed("consumer")) {
-		p.consumer.NumberValue = p.claim.Limits.Consumer
+		p.consumer = NumberParams(p.claim.Limits.Consumer)
 	}
 
 	if !ctx.CurrentCmd().Flags().Changed("description") {
@@ -261,7 +271,7 @@ func (p *EditAccountParams) PostInteractive(ctx ActionCtx) error {
 		return err
 	}
 
-	if p.memStorage.Number != 0 || p.diskStorage.Number != 0 {
+	if p.memStorage != 0 || p.diskStorage != 0 {
 		if err = p.streams.Edit("max streams (-1 unlimited)"); err != nil {
 			return err
 		}
@@ -308,7 +318,7 @@ func (p *EditAccountParams) Validate(ctx ActionCtx) error {
 		return err
 	}
 	if op, _ := ctx.StoreCtx().Store.ReadOperatorClaim(); op.SystemAccount == p.claim.Subject {
-		if p.memStorage.Number != 0 || p.diskStorage.Number != 0 || p.consumer.NumberValue != 0 || p.streams.NumberValue != 0 {
+		if p.memStorage != 0 || p.diskStorage != 0 || p.consumer != 0 || p.streams != 0 {
 			return fmt.Errorf("jetstream not available for system account")
 		}
 	}
@@ -340,25 +350,22 @@ func (p *EditAccountParams) Run(ctx ActionCtx) (store.Status, error) {
 	}
 
 	flags := ctx.CurrentCmd().Flags()
-	p.claim.Limits.Conn = p.conns.NumberValue
+	p.claim.Limits.Conn = p.conns.Int64()
 	if flags.Changed("conns") {
 		r.AddOK("changed max connections to %d", p.claim.Limits.Conn)
 	}
 
-	p.claim.Limits.LeafNodeConn = p.leafConns.NumberValue
+	p.claim.Limits.LeafNodeConn = p.leafConns.Int64()
 	if flags.Changed("leaf-conns") {
 		r.AddOK("changed leaf node connections to %d", p.claim.Limits.LeafNodeConn)
 	}
 
-	p.claim.Limits.Data, err = p.data.NumberValue()
-	if err != nil {
-		return nil, fmt.Errorf("error parsing %s: %s", "data", p.data.Value)
-	}
+	p.claim.Limits.Data = p.data.Int64()
 	if flags.Changed("data") {
 		r.AddOK("changed max data to %d bytes", p.claim.Limits.Data)
 	}
 
-	p.claim.Limits.Exports = p.exports.NumberValue
+	p.claim.Limits.Exports = p.exports.Int64()
 	if flags.Changed("exports") {
 		r.AddOK("changed max exports to %d", p.claim.Limits.Exports)
 	}
@@ -368,46 +375,37 @@ func (p *EditAccountParams) Run(ctx ActionCtx) (store.Status, error) {
 		r.AddOK("changed wild card exports to %t", p.claim.Limits.WildcardExports)
 	}
 
-	p.claim.Limits.Imports = p.imports.NumberValue
+	p.claim.Limits.Imports = p.imports.Int64()
 	if flags.Changed("imports") {
 		r.AddOK("changed max imports to %d", p.claim.Limits.Imports)
 	}
 
-	p.claim.Limits.Payload, err = p.payload.NumberValue()
-	if err != nil {
-		return nil, fmt.Errorf("error parsing %s: %s", "payload", p.data.Value)
-	}
+	p.claim.Limits.Payload = p.payload.Int64()
 	if flags.Changed("payload") {
 		r.AddOK("changed max imports to %d", p.claim.Limits.Payload)
 	}
 
-	p.claim.Limits.Subs = p.subscriptions.NumberValue
+	p.claim.Limits.Subs = p.subscriptions.Int64()
 	if flags.Changed("subscriptions") {
 		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)
-	}
+	p.claim.Limits.MemoryStorage = p.memStorage.Int64()
 	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)
-	}
+	p.claim.Limits.DiskStorage = p.diskStorage.Int64()
 	if flags.Changed("disk-storage") {
 		r.AddOK("changed max disk storage to %d", p.claim.Limits.DiskStorage)
 	}
 
-	p.claim.Limits.Streams = p.streams.NumberValue
+	p.claim.Limits.Streams = p.streams.Int64()
 	if flags.Changed("streams") {
 		r.AddOK("changed max streams to %d", p.claim.Limits.Streams)
 	}
 
-	p.claim.Limits.Consumer = p.consumer.NumberValue
+	p.claim.Limits.Consumer = p.consumer.Int64()
 	if flags.Changed("consumer") {
 		r.AddOK("changed max consumer to %d", p.claim.Limits.Consumer)
 	}
diff --git a/cmd/edituser.go b/cmd/edituser.go
index 1f2d681..ceacc3e 100644
--- a/cmd/edituser.go
+++ b/cmd/edituser.go
@@ -71,34 +71,12 @@ nsc edit user --name <n> --rm-response-perms
 			return RunAction(cmd, args, &params)
 		},
 	}
-
-	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().VarP(&params.locale, "locale", "", "set the locale with which time values are interpreted")
-
 	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().StringSliceVarP(&params.src, "source-network", "", nil, "add source network for connection - comma separated list or option can be specified multiple times")
-	cmd.Flags().StringSliceVarP(&params.rmSrc, "rm-source-network", "", nil, "remove source network for connection - comma separated list or option can be specified multiple times")
-
-	cmd.Flags().StringVarP(&params.payload.Value, "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")
-
-	cmd.Flags().Int64VarP(&params.maxSubs, "subs", "", -1, "set maximum number of subscriptions (-1 is unlimited)")
-	cmd.Flags().StringVarP(&params.maxData.Value, "data", "", "-1", "set maximum data in bytes for the user (-1 is unlimited)")
-
 	params.AccountContextParams.BindFlags(cmd)
 	params.GenericClaimsParams.BindFlags(cmd)
-	params.PermissionsParams.bindSetFlags(cmd, "permissions")
-	params.PermissionsParams.bindRemoveFlags(cmd, "permissions")
-
+	params.UserPermissionLimits.BindFlags(cmd)
 	return cmd
 }
 
@@ -110,23 +88,11 @@ type EditUserParams struct {
 	AccountContextParams
 	SignerParams
 	GenericClaimsParams
-	PermissionsParams
 	claim         *jwt.UserClaims
 	name          string
 	token         string
 	credsFilePath string
-
-	bearer      bool
-	connTypes   []string
-	locale      timeLocale
-	maxData     DataParams
-	maxSubs     int64
-	payload     DataParams
-	rmConnTypes []string
-	rmSrc       []string
-	rmTimes     []string
-	src         []string
-	times       timeSlice
+	UserPermissionLimits
 }
 
 func (p *EditUserParams) SetDefaults(ctx ActionCtx) error {
@@ -195,25 +161,17 @@ func (p *EditUserParams) Load(ctx ActionCtx) error {
 		return err
 	}
 
-	if !ctx.CurrentCmd().Flag("payload").Changed {
-		p.payload.Value = fmt.Sprintf("%d", p.claim.Limits.Payload)
-	}
-	if !ctx.CurrentCmd().Flag("data").Changed {
-		p.maxData.Value = fmt.Sprintf("%d", p.claim.Limits.Data)
-	}
-	if !ctx.CurrentCmd().Flag("subs").Changed {
-		p.maxSubs = p.claim.Limits.Subs
-	}
+	p.UserPermissionLimits.Load(ctx, p.claim.UserPermissionLimits)
 
 	return err
 }
 
-func (p *EditUserParams) PostInteractive(_ 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.PermissionsParams.Edit(p.claim.Resp != nil); err != nil {
 	//	return err
 	//}
-	if err := p.payload.Edit("max payload (-1 unlimited)"); err != nil {
+	if err := p.UserPermissionLimits.PostInteractive(ctx); err != nil {
 		return err
 	}
 	if p.claim.NotBefore > 0 {
@@ -229,44 +187,12 @@ func (p *EditUserParams) PostInteractive(_ ActionCtx) error {
 }
 
 func (p *EditUserParams) Validate(ctx ActionCtx) error {
-	var err error
+	p.UserPermissionLimits.Validate(ctx)
 
-	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)
-	}
-	if _, err := p.maxData.NumberValue(); err != nil {
-		return fmt.Errorf("error parsing %s: %s", "data", p.payload.Value)
-	}
-	if err = p.GenericClaimsParams.Valid(); err != nil {
-		return err
-	}
-	if err = p.SignerParams.ResolveWithPriority(ctx, p.claim.Issuer); err != nil {
+	if err := p.GenericClaimsParams.Valid(); err != nil {
 		return err
 	}
-	if err = p.payload.Valid(); err != nil {
-		return err
-	}
-	if err = p.maxData.Valid(); err != nil {
-		return err
-	}
-	if err := p.PermissionsParams.Validate(); err != nil {
+	if err := p.SignerParams.ResolveWithPriority(ctx, p.claim.Issuer); err != nil {
 		return err
 	}
 
@@ -282,72 +208,7 @@ func (p *EditUserParams) Run(ctx ActionCtx) (store.Status, error) {
 		return nil, err
 	}
 
-	flags := ctx.CurrentCmd().Flags()
-	p.claim.Limits.Payload, _ = p.payload.NumberValue()
-	if flags.Changed("payload") {
-		r.AddOK("changed max imports to %d", p.claim.Limits.Payload)
-	}
-	p.claim.Limits.Data, _ = p.maxData.NumberValue()
-	if flags.Changed("data") {
-		r.AddOK("changed max data to %d", p.claim.Limits.Data)
-	}
-	p.claim.Limits.Subs = p.maxSubs
-	if flags.Changed("subs") {
-		r.AddOK("changed max number of subs to %d", p.claim.Limits.Subs)
-	}
-
-	if flags.Changed("bearer") {
-		p.claim.BearerToken = p.bearer
-		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.src {
-		r.AddOK("added src network %s", v)
-	}
-	srcList.Remove(p.rmSrc...)
-	for _, v := range p.rmSrc {
-		r.AddOK("removed src network %s", v)
-	}
-	sort.Strings(srcList)
-	p.claim.Src = srcList
-
-	if flags.Changed("locale") {
-		p.claim.Locale = p.locale.String()
-	}
-	for _, v := range p.times {
-		r.AddOK("added time range %s-%s", v.Start, v.End)
-		p.claim.Times = append(p.claim.Times, v)
-	}
-	for _, vDel := range p.rmTimes {
-		for i, v := range p.claim.Times {
-			if v.Start == vDel {
-				r.AddOK("removed time range %s-%s", v.Start, v.End)
-				a := p.claim.Times
-				// Remove the element at index i from a.
-				copy(a[i:], a[i+1:])          // Shift a[i+1:] left one index.
-				a[len(a)-1] = jwt.TimeRange{} // Erase last element (write zero value).
-				p.claim.Times = a[:len(a)-1]  // Truncate slice.
-				break
-			}
-		}
-	}
-
-	s, err := p.PermissionsParams.Run(&p.claim.Permissions, ctx)
+	s, err := p.UserPermissionLimits.Run(ctx, &p.claim.UserPermissionLimits)
 	if err != nil {
 		return nil, err
 	}
@@ -461,3 +322,167 @@ func (l *timeLocale) String() string {
 func (t *timeLocale) Type() string {
 	return "time-locale"
 }
+
+type UserPermissionLimits struct {
+	PermissionsParams
+	bearer      bool
+	payload     NumberParams
+	maxData     NumberParams
+	maxSubs     int64
+	rmConnTypes []string
+	connTypes   []string
+	rmSrc       []string
+	src         []string
+	locale      timeLocale
+	rmTimes     []string
+	times       timeSlice
+}
+
+func (p *UserPermissionLimits) BindFlags(cmd *cobra.Command) {
+	cmd.Flags().VarP(&p.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(&p.rmTimes, "rm-time", "", nil, fmt.Sprintf(`remove start-end time by start time "%s" (option can be specified multiple times)`, timeFormat))
+	cmd.Flags().VarP(&p.locale, "locale", "", "set the locale with which time values are interpreted")
+	cmd.Flags().StringSliceVarP(&p.src, "source-network", "", nil, "add source network for connection - comma separated list or option can be specified multiple times")
+	cmd.Flags().StringSliceVarP(&p.rmSrc, "rm-source-network", "", nil, "remove source network for connection - comma separated list or option can be specified multiple times")
+	cmd.Flags().StringSliceVarP(&p.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(&p.rmConnTypes, "rm-conn-type", "", nil, "remove connection types - comma separated list or option can be specified multiple times")
+	cmd.Flags().Int64VarP(&p.maxSubs, "subs", "", -1, "set maximum number of subscriptions (-1 is unlimited)")
+	p.maxData = -1
+	cmd.Flags().VarP(&p.maxData, "data", "", "set maximum data in bytes for the user (-1 is unlimited)")
+	p.payload = -1
+	cmd.Flags().VarP(&p.payload, "payload", "", "set maximum message payload in bytes for the account (-1 is unlimited)")
+	cmd.Flags().BoolVarP(&p.bearer, "bearer", "", false, "no connect challenge required for user")
+	p.PermissionsParams.bindSetFlags(cmd, "permissions")
+	p.PermissionsParams.bindRemoveFlags(cmd, "permissions")
+}
+
+func (p *UserPermissionLimits) Load(ctx ActionCtx, u jwt.UserPermissionLimits) error {
+	if !ctx.CurrentCmd().Flag("payload").Changed {
+		p.payload = NumberParams(u.Limits.Payload)
+	}
+	if !ctx.CurrentCmd().Flag("data").Changed {
+		p.maxData = NumberParams(u.Limits.Data)
+	}
+	if !ctx.CurrentCmd().Flag("subs").Changed {
+		p.maxSubs = u.Limits.Subs
+	}
+	return nil
+}
+
+func (p *UserPermissionLimits) PostInteractive(_ ActionCtx) error {
+	// FIXME: we won't do interactive on the response params until pub/sub/deny permissions are interactive
+	//if err := p.PermissionsParams.Edit(p.claim.Resp != nil); err != nil {
+	//	return err
+	//}
+	if err := p.payload.Edit("max payload (-1 unlimited)"); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (p *UserPermissionLimits) Validate(ctx ActionCtx) 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
+
+	if err := p.PermissionsParams.Validate(); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (p *UserPermissionLimits) Run(ctx ActionCtx, claim *jwt.UserPermissionLimits) (*store.Report, error) {
+	r := store.NewDetailedReport(true)
+	r.ReportSum = false
+
+	var err error
+
+	flags := ctx.CurrentCmd().Flags()
+	claim.Limits.Payload = p.payload.Int64()
+	if flags.Changed("payload") {
+		r.AddOK("changed max imports to %d", claim.Limits.Payload)
+	}
+	claim.Limits.Data = p.maxData.Int64()
+	if flags.Changed("data") {
+		r.AddOK("changed max data to %d", claim.Limits.Data)
+	}
+	claim.Limits.Subs = p.maxSubs
+	if flags.Changed("subs") {
+		r.AddOK("changed max number of subs to %d", claim.Limits.Subs)
+	}
+
+	if flags.Changed("bearer") {
+		claim.BearerToken = p.bearer
+		r.AddOK("changed bearer to %t", p.bearer)
+	}
+
+	var connTypes jwt.StringList
+	connTypes.Add(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)
+	}
+	claim.AllowedConnectionTypes = connTypes
+
+	var srcList jwt.CIDRList
+	srcList.Add(claim.Src...)
+	srcList.Add(p.src...)
+	for _, v := range p.src {
+		r.AddOK("added src network %s", v)
+	}
+	srcList.Remove(p.rmSrc...)
+	for _, v := range p.rmSrc {
+		r.AddOK("removed src network %s", v)
+	}
+	sort.Strings(srcList)
+	claim.Src = srcList
+
+	if flags.Changed("locale") {
+		claim.Locale = p.locale.String()
+	}
+	for _, v := range p.times {
+		r.AddOK("added time range %s-%s", v.Start, v.End)
+		claim.Times = append(claim.Times, v)
+	}
+	for _, vDel := range p.rmTimes {
+		for i, v := range claim.Times {
+			if v.Start == vDel {
+				r.AddOK("removed time range %s-%s", v.Start, v.End)
+				a := claim.Times
+				// Remove the element at index i from a.
+				copy(a[i:], a[i+1:])          // Shift a[i+1:] left one index.
+				a[len(a)-1] = jwt.TimeRange{} // Erase last element (write zero value).
+				claim.Times = a[:len(a)-1]    // Truncate slice.
+				break
+			}
+		}
+	}
+
+	s, err := p.PermissionsParams.Run(&claim.Permissions, ctx)
+	if err != nil {
+		return nil, err
+	}
+	if s != nil {
+		r.Add(s.Details...)
+	}
+
+	return r, nil
+}
diff --git a/cmd/numberparams.go b/cmd/numberparams.go
index ff1f39b..c7b71d0 100644
--- a/cmd/numberparams.go
+++ b/cmd/numberparams.go
@@ -17,13 +17,12 @@ package cmd
 
 import (
 	"fmt"
+	"github.com/dustin/go-humanize"
 
 	cli "github.com/nats-io/cliprompts/v2"
 )
 
-type NumberParams struct {
-	NumberValue int64
-}
+type NumberParams int64
 
 func (e *NumberParams) Valid() error {
 	// flag already insures this is a number
@@ -33,7 +32,7 @@ func (e *NumberParams) Valid() error {
 func (e *NumberParams) Edit(prompt string) error {
 	var err error
 	var nv int64
-	sv := fmt.Sprintf("%d", e.NumberValue)
+	sv := fmt.Sprintf("%d", e)
 	_, err = cli.Prompt(prompt, sv, cli.Val(func(s string) error {
 		nv, err = ParseNumber(s)
 		return err
@@ -41,6 +40,27 @@ func (e *NumberParams) Edit(prompt string) error {
 	if err != nil {
 		return err
 	}
-	e.NumberValue = nv
+	*e = NumberParams(nv)
+	return nil
+}
+
+func (e *NumberParams) Set(s string) error {
+	nv, err := ParseNumber(s)
+	if err != nil {
+		return err
+	}
+	*e = NumberParams(nv)
 	return nil
 }
+
+func (e *NumberParams) Type() string {
+	return "number"
+}
+
+func (e *NumberParams) String() string {
+	return humanize.Comma(int64(*e))
+}
+
+func (e *NumberParams) Int64() int64 {
+	return int64(*e)
+}


Test patch:
diff --git a/cmd/dataparams_test.go b/cmd/dataparams_test.go
deleted file mode 100644
index be4b889..0000000
--- a/cmd/dataparams_test.go
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2018-2021 The NATS Authors
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package cmd
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestDataParams(t *testing.T) {
-	var dp DataParams
-	dp.Value = "1"
-	v, err := dp.NumberValue()
-	require.NoError(t, err)
-	require.Equal(t, int64(1), v)
-
-	dp.Value = "1B"
-	v, err = dp.NumberValue()
-	require.NoError(t, err)
-	require.Equal(t, int64(1), v)
-
-	dp.Value = "1K"
-	v, err = dp.NumberValue()
-	require.NoError(t, err)
-	require.Equal(t, int64(1000), v)
-
-	dp.Value = "1KiB"
-	v, err = dp.NumberValue()
-	require.NoError(t, err)
-	require.Equal(t, int64(1024), v)
-
-	dp.Value = "1M"
-	v, err = dp.NumberValue()
-	require.NoError(t, err)
-	require.Equal(t, int64(1000*1000), v)
-
-	dp.Value = "1MiB"
-	v, err = dp.NumberValue()
-	require.NoError(t, err)
-	require.Equal(t, int64(1024*1024), v)
-
-	dp.Value = "1G"
-	v, err = dp.NumberValue()
-	require.NoError(t, err)
-	require.Equal(t, int64(1000*1000*1000), v)
-
-	dp.Value = "1Gib"
-	v, err = dp.NumberValue()
-	require.NoError(t, err)
-	require.Equal(t, int64(1024*1024*1024), v)
-}
diff --git a/cmd/numberparams_test.go b/cmd/numberparams_test.go
index 5bb8727..73918f8 100644
--- a/cmd/numberparams_test.go
+++ b/cmd/numberparams_test.go
@@ -39,7 +39,7 @@ func numberEditorCmd(params *NumberParams) *cobra.Command {
 			return nil
 		},
 	}
-	cmd.Flags().Int64VarP(&params.NumberValue, "number", "n", 0, "some number")
+	cmd.Flags().VarP(params, "number", "n", "some number")
 
 	return cmd
 }
@@ -50,7 +50,7 @@ func TestNumberParams_BindFlags(t *testing.T) {
 	_, _, err := ExecuteCmd(cmd, "--number", "10")
 	require.NoError(t, err)
 
-	require.Equal(t, int64(10), p.NumberValue)
+	require.Equal(t, NumberParams(10), p)
 }
 
 func TestNumberParams_Edit(t *testing.T) {
@@ -59,5 +59,5 @@ func TestNumberParams_Edit(t *testing.T) {
 	_, _, err := ExecuteInteractiveCmd(cmd, []interface{}{"404"})
 	require.NoError(t, err)
 
-	require.Equal(t, int64(404), p.NumberValue)
+	require.Equal(t, NumberParams(404), p)
 }