Repository: nats-io/nsc
Pull request #359 (2021-02-13 00:23:20)

Minor fixes
[chore] remove mentioning of import prefix
[Added] support for rfc3339 formatted time when revoking
Fix js error when modifying the system account


Patch:
diff --git a/cmd/addimport.go b/cmd/addimport.go
index 2f9cdfe..fa6c720 100644
--- a/cmd/addimport.go
+++ b/cmd/addimport.go
@@ -42,7 +42,7 @@ func createAddImportCmd() *cobra.Command {
 	cmd.Flags().StringVarP(&params.tokenSrc, "token", "u", "", "path to token file can be a local path or an url (private imports only)")
 
 	cmd.Flags().StringVarP(&params.name, "name", "n", "", "import name")
-	cmd.Flags().StringVarP(&params.local, "local-subject", "s", "", "local subject or prefix")
+	cmd.Flags().StringVarP(&params.local, "local-subject", "s", "", "local subject")
 	params.srcAccount.BindFlags("src-account", "", nkeys.PrefixByteAccount, cmd)
 	cmd.Flags().StringVarP(&params.remote, "remote-subject", "", "", "remote subject (only public imports)")
 	cmd.Flags().BoolVarP(&params.service, "service", "", false, "service (only public imports)")
@@ -410,14 +410,7 @@ func (p *AddImportParams) PostInteractive(ctx ActionCtx) error {
 		p.local = p.remote
 	}
 
-	// services have to have a local subject - streams can be blank and import on source subject
-	m := "stream prefix subject"
-	prefix := ""
-	if p.service {
-		m = "local subject"
-		prefix = p.local
-	}
-	p.local, err = cli.Prompt(m, prefix, cli.Val(func(s string) error {
+	p.local, err = cli.Prompt("local subject", prefix, cli.Val(func(s string) error {
 		if !p.service && s == "" {
 			return nil
 		}
diff --git a/cmd/common.go b/cmd/common.go
index 1ea1761..da77f8f 100644
--- a/cmd/common.go
+++ b/cmd/common.go
@@ -582,3 +582,31 @@ func promptDuration(label string, defaultValue time.Duration) (time.Duration, er
 	}
 	return time.ParseDuration(value)
 }
+
+type dateTime int64
+
+func (t *dateTime) Set(val string) error {
+	if strings.TrimSpace(val) == "0" {
+		*t = 0
+		return nil
+	}
+	if v, err := time.Parse(time.RFC3339, val); err == nil {
+		*t = dateTime(v.Unix())
+		return nil
+	}
+	num, err := strconv.ParseInt(val, 10, 64)
+	if err != nil {
+		return fmt.Errorf("provided value %q is not a number nor parsable as RFC3339", val)
+	}
+	*t = dateTime(num)
+	return nil
+}
+
+func (t *dateTime) String() string {
+	v := time.Unix(int64(*t), 0)
+	return v.Format(time.RFC3339)
+}
+
+func (t *dateTime) Type() string {
+	return "date-time"
+}
diff --git a/cmd/editaccount.go b/cmd/editaccount.go
index 935e8b6..1c73d24 100644
--- a/cmd/editaccount.go
+++ b/cmd/editaccount.go
@@ -293,7 +293,9 @@ func (p *EditAccountParams) Validate(ctx ActionCtx) error {
 		return err
 	}
 	if op, _ := ctx.StoreCtx().Store.ReadOperatorClaim(); op.SystemAccount == p.claim.Subject {
-		return fmt.Errorf("jetstream not available for system account")
+		if p.memStorage.Number != 0 || p.diskStorage.Number != 0 || p.consumer.NumberValue != 0 || p.streams.NumberValue != 0 {
+			return fmt.Errorf("jetstream not available for system account")
+		}
 	}
 	if err := p.PermissionsParams.Validate(); err != nil {
 		return err
diff --git a/cmd/revokeactivation.go b/cmd/revokeactivation.go
index d37311c..f964c65 100644
--- a/cmd/revokeactivation.go
+++ b/cmd/revokeactivation.go
@@ -17,7 +17,6 @@ package cmd
 
 import (
 	"fmt"
-	"strconv"
 	"time"
 
 	cli "github.com/nats-io/cliprompts/v2"
@@ -40,7 +39,8 @@ func createRevokeActivationCmd() *cobra.Command {
 		},
 	}
 
-	cmd.Flags().IntVarP(&params.at, "at", "", 0, "revokes all user credentials created before a Unix timestamp ('0' is treated as now)")
+	cmd.Flags().VarP(&params.at, "at", "", "revokes all activations for an account created"+
+		" or edited before a Unix timestamp ('0' is treated as now, accepted formats are RFC3339 or #seconds since epoch)")
 	cmd.Flags().StringVarP(&params.subject, "subject", "s", "", "export subject")
 	cmd.Flags().BoolVarP(&params.service, "service", "", false, "service")
 	params.accountKey.BindFlags("target-account", "t", nkeys.PrefixByteAccount, cmd)
@@ -61,7 +61,7 @@ type RevokeActivationParams struct {
 	claim           *jwt.AccountClaims
 	export          *jwt.Export
 	possibleExports jwt.Exports
-	at              int
+	at              dateTime
 	subject         string
 	service         bool
 	accountKey      PubKeyParams
@@ -74,14 +74,6 @@ func (p *RevokeActivationParams) SetDefaults(ctx ActionCtx) error {
 	return nil
 }
 
-func (p *RevokeActivationParams) canParse(s string) error {
-	_, err := strconv.Atoi(s)
-	if err != nil {
-		return fmt.Errorf("%s is invalid: %v", s, err)
-	}
-	return nil
-}
-
 func (p *RevokeActivationParams) PreInteractive(ctx ActionCtx) error {
 	var err error
 
@@ -159,13 +151,8 @@ func (p *RevokeActivationParams) PostInteractive(ctx ActionCtx) error {
 	}
 
 	if p.at == 0 {
-		at := fmt.Sprintf("%d", p.at)
-		at, err = cli.Prompt("revoke all credentials created before (0 is now)", at, cli.Val(p.canParse))
-		if err != nil {
-			return err
-		}
-		p.at, err = strconv.Atoi(at)
-		if err != nil {
+		if _, err := cli.Prompt("revoke all credentials created before (0 is now, formats are RFC3339 or #seconds since epoch)",
+			fmt.Sprintf("%d", p.at), cli.Val(p.at.Set)); err != nil {
 			return err
 		}
 	}
diff --git a/cmd/revokeuser.go b/cmd/revokeuser.go
index cd8fcf8..7d92dd2 100644
--- a/cmd/revokeuser.go
+++ b/cmd/revokeuser.go
@@ -17,7 +17,6 @@ package cmd
 
 import (
 	"fmt"
-	"strconv"
 	"strings"
 	"time"
 
@@ -41,7 +40,8 @@ func createRevokeUserCmd() *cobra.Command {
 		},
 	}
 	cmd.Flags().StringVarP(&params.user, "name", "n", "", "user name")
-	cmd.Flags().IntVarP(&params.at, "at", "", 0, "revokes all user credentials created before a Unix timestamp ('0' is treated as now)")
+	cmd.Flags().VarP(&params.at, "at", "", "revokes all user credentials created"+
+		" or edited before a Unix timestamp ('0' is treated as now, accepted formats are RFC3339 or #seconds since epoch)")
 	params.userKey.BindFlags("user-public-key", "u", nkeys.PrefixByteUser, cmd)
 	params.AccountContextParams.BindFlags(cmd)
 
@@ -55,7 +55,7 @@ func init() {
 // RevokeUserParams hold the info necessary to add a user to the revocation list in an account
 type RevokeUserParams struct {
 	AccountContextParams
-	at      int
+	at      dateTime
 	user    string
 	userKey PubKeyParams
 	claim   *jwt.AccountClaims
@@ -72,14 +72,6 @@ func (p *RevokeUserParams) SetDefaults(ctx ActionCtx) error {
 	return nil
 }
 
-func (p *RevokeUserParams) canParse(s string) error {
-	_, err := strconv.Atoi(s)
-	if err != nil {
-		return fmt.Errorf("%s is invalid: %v", s, err)
-	}
-	return nil
-}
-
 func (p *RevokeUserParams) PreInteractive(ctx ActionCtx) error {
 	return p.AccountContextParams.Edit(ctx)
 }
@@ -159,13 +151,8 @@ func (p *RevokeUserParams) PostInteractive(ctx ActionCtx) error {
 	}
 
 	if p.at == 0 {
-		at := fmt.Sprintf("%d", p.at)
-		at, err := cli.Prompt("revoke all credentials created before (0 is now)", at, cli.Val(p.canParse))
-		if err != nil {
-			return err
-		}
-		p.at, err = strconv.Atoi(at)
-		if err != nil {
+		if _, err := cli.Prompt("revoke all credentials created before (0 is now, formats are RFC3339 or #seconds since epoch)",
+			fmt.Sprintf("%d", p.at), cli.Val(p.at.Set)); err != nil {
 			return err
 		}
 	}


Test patch:
diff --git a/cmd/revokeactivation_test.go b/cmd/revokeactivation_test.go
index 9e1989b..ba90805 100644
--- a/cmd/revokeactivation_test.go
+++ b/cmd/revokeactivation_test.go
@@ -241,5 +241,5 @@ func TestRevokeActivationBadInteractiveAt(t *testing.T) {
 	input := []interface{}{true, 0, "*", "hello"}
 	_, _, err := ExecuteInteractiveCmd(createRevokeActivationCmd(), input)
 	require.Error(t, err)
-	require.Contains(t, err.Error(), "hello is invalid:")
+	require.Contains(t, err.Error(), `provided value "hello" is not`)
 }
diff --git a/cmd/revokeuser_test.go b/cmd/revokeuser_test.go
index 0b89b1e..7b84a22 100644
--- a/cmd/revokeuser_test.go
+++ b/cmd/revokeuser_test.go
@@ -271,6 +271,20 @@ func TestRevokeBadUnixTime(t *testing.T) {
 	require.Contains(t, err.Error(), "invalid argument")
 }
 
+func TestRevokeRFC3339Time(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+	ts.AddAccount(t, "A")
+	at := time.Now()
+	_, _, err := ExecuteCmd(createRevokeUserCmd(), "-u", "*", "--at", at.Format(time.RFC3339))
+	require.NoError(t, err)
+
+	c, err := ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	require.True(t, c.Revocations.IsRevoked("foo", at))
+	require.False(t, c.Revocations.IsRevoked("foo", at.Add(time.Second)))
+}
+
 func TestRevokeBadUnixTimeInteractive(t *testing.T) {
 	ts := NewTestStore(t, "O")
 	defer ts.Done(t)
@@ -278,5 +292,5 @@ func TestRevokeBadUnixTimeInteractive(t *testing.T) {
 	input := []interface{}{"*", "hello"}
 	_, _, err := ExecuteInteractiveCmd(createRevokeUserCmd(), input)
 	require.Error(t, err)
-	require.Contains(t, err.Error(), "hello is invalid:")
+	require.Contains(t, err.Error(), `provided value "hello" is not`)
 }