diff --git a/client/commands/commands.go b/client/commands/commands.go index 21923a2..1416c6b 100644 --- a/client/commands/commands.go +++ b/client/commands/commands.go @@ -25,13 +25,12 @@ import ( "path/filepath" "strings" + "github.com/reeflective/team/client" + "github.com/reeflective/team/internal/command" "github.com/rsteube/carapace" "github.com/rsteube/carapace/pkg/style" "github.com/spf13/cobra" "github.com/spf13/pflag" - - "github.com/reeflective/team/client" - "github.com/reeflective/team/internal/command" ) // Generate returns a command tree to embed in client applications connecting @@ -108,7 +107,7 @@ func clientCommands(cli *client.Client) *cobra.Command { importCmd := &cobra.Command{ Use: "import", - Short: fmt.Sprintf("Import a teamserver client configuration file for %s", cli.Name()), + Short: "Import a teamserver client configuration file for " + cli.Name(), Run: importCmd(cli), ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return []string{}, cobra.ShellCompDirectiveDefault diff --git a/client/commands/import.go b/client/commands/import.go index ff470c2..166a86c 100644 --- a/client/commands/import.go +++ b/client/commands/import.go @@ -22,11 +22,10 @@ import ( "encoding/json" "fmt" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/reeflective/team/client" "github.com/reeflective/team/internal/command" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" ) func importCmd(cli *client.Client) func(cmd *cobra.Command, args []string) { diff --git a/client/commands/users.go b/client/commands/users.go index 3b7f1ca..f3045c9 100644 --- a/client/commands/users.go +++ b/client/commands/users.go @@ -23,11 +23,10 @@ import ( "time" "github.com/jedib0t/go-pretty/v6/table" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/reeflective/team/client" "github.com/reeflective/team/internal/command" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" ) func usersCmd(cli *client.Client) func(cmd *cobra.Command, args []string) error { diff --git a/client/commands/version.go b/client/commands/version.go index 2920bca..975c0d9 100644 --- a/client/commands/version.go +++ b/client/commands/version.go @@ -22,11 +22,10 @@ import ( "fmt" "time" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/reeflective/team/client" "github.com/reeflective/team/internal/command" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" ) func versionCmd(cli *client.Client) func(cmd *cobra.Command, args []string) error { diff --git a/go.mod b/go.mod index 8ff8abf..2b5d39e 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,14 @@ require ( github.com/gofrs/uuid v4.4.0+incompatible github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 github.com/jedib0t/go-pretty/v6 v6.4.6 + github.com/lib/pq v1.10.9 github.com/ncruces/go-sqlite3 v0.8.4 github.com/ncruces/go-sqlite3/gormlite v0.8.4 github.com/psanford/memfs v0.0.0-20230130182539-4dbf7e3e865e github.com/rsteube/carapace v0.47.4 github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.8.0 - github.com/spf13/pflag v1.0.5 + github.com/spf13/cobra v1.8.1 + github.com/spf13/pflag v1.0.6 google.golang.org/grpc v1.56.1 google.golang.org/protobuf v1.31.0 gorm.io/driver/mysql v1.5.1 diff --git a/go.sum b/go.sum index 761538e..d3d6811 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,8 @@ github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -81,6 +81,8 @@ github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NB github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -115,8 +117,6 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rsteube/carapace v0.37.3 h1:Us0AnzZ0JiWVHmETSX8PBgp2UJCf/O7KhgSKSpokkII= -github.com/rsteube/carapace v0.37.3/go.mod h1:jkLt41Ne2TD2xPuMdX/2O05Smhy8vMgG7O2TYvC0yOc= github.com/rsteube/carapace v0.47.4 h1:LwnkFsvRxc2WhZjM63QS7sCi3DlM9XGuATQM5rehgps= github.com/rsteube/carapace v0.47.4/go.mod h1:4ZC5bulItu9t9sZ5yPcHgPREd8rPf274Q732n+wfl/o= github.com/rsteube/carapace-shlex v0.1.1 h1:fRQEBBKyYKm4TXUabm4tzH904iFWSmXJl3UZhMfQNYU= @@ -125,12 +125,14 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= diff --git a/internal/db/certificates.go b/internal/db/certificates.go index adb9a27..a51b57f 100644 --- a/internal/db/certificates.go +++ b/internal/db/certificates.go @@ -42,6 +42,8 @@ func (c *Certificate) BeforeCreate(tx *gorm.DB) (err error) { if err != nil { return err } + c.CreatedAt = time.Now() + return nil } diff --git a/internal/db/sql.go b/internal/db/sql.go index 6509086..b8f86d3 100644 --- a/internal/db/sql.go +++ b/internal/db/sql.go @@ -23,13 +23,12 @@ import ( "fmt" "time" + "github.com/reeflective/team/internal/log" "github.com/sirupsen/logrus" "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" - - "github.com/reeflective/team/internal/log" ) const ( diff --git a/internal/db/user.go b/internal/db/user.go index de220b7..29039fa 100644 --- a/internal/db/user.go +++ b/internal/db/user.go @@ -22,16 +22,18 @@ import ( "time" "github.com/gofrs/uuid" + "github.com/lib/pq" "gorm.io/gorm" ) // User - A teamserver user. type User struct { - ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"` - CreatedAt time.Time `gorm:"->;<-:create;"` - LastSeen time.Time - Name string - Token string `gorm:"uniqueIndex"` + ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"` + CreatedAt time.Time `gorm:"->;<-:create;"` + LastSeen time.Time + Name string + Token string `gorm:"uniqueIndex"` + Permissions pq.StringArray `gorm:"type:text[]"` } // BeforeCreate - GORM hook. @@ -45,3 +47,10 @@ func (o *User) BeforeCreate(tx *gorm.DB) (err error) { return nil } + +type Permissions struct { + ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"` + CreatedAt time.Time `gorm:"->;<-:create;"` + LastSeen time.Time + Name string +} diff --git a/server/commands/commands.go b/server/commands/commands.go index 67d2f73..e66847d 100644 --- a/server/commands/commands.go +++ b/server/commands/commands.go @@ -21,14 +21,13 @@ package commands import ( "fmt" - "github.com/rsteube/carapace" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/reeflective/team/client" cli "github.com/reeflective/team/client/commands" "github.com/reeflective/team/internal/command" "github.com/reeflective/team/server" + "github.com/rsteube/carapace" + "github.com/spf13/cobra" + "github.com/spf13/pflag" ) // Generate returns a "teamserver" command root and its tree for teamserver (server-side) management. @@ -121,6 +120,7 @@ func serverCommands(server *server.Server, client *client.Client) *cobra.Command if cmd.PersistentPreRunE != nil { cmd.PersistentPreRunE(cmd, args) } + if cmd.PreRunE != nil { cmd.PreRunE(cmd, args) } @@ -195,6 +195,7 @@ func serverCommands(server *server.Server, client *client.Client) *cobra.Command userFlags.StringP("save", "s", "", "directory/file in which to save config") userFlags.StringP("name", "n", "", "user name") userFlags.BoolP("system", "U", false, "Use the current OS user, and save its configuration directly in client dir") + userFlags.StringSliceP("permissions", "P", []string{}, "list of permission strings for custom RPC auth") userCmd.Flags().AddFlagSet(userFlags) userComps := make(carapace.ActionMap) @@ -221,6 +222,7 @@ func serverCommands(server *server.Server, client *client.Client) *cobra.Command if cmd.PersistentPreRunE != nil { cmd.PersistentPreRunE(cmd, args) } + if cmd.PreRunE != nil { cmd.PreRunE(cmd, args) } diff --git a/server/commands/completers.go b/server/commands/completers.go index 1b336ce..5bf98b8 100644 --- a/server/commands/completers.go +++ b/server/commands/completers.go @@ -23,10 +23,9 @@ import ( "net" "strings" - "github.com/rsteube/carapace" - "github.com/reeflective/team/client" "github.com/reeflective/team/server" + "github.com/rsteube/carapace" ) // interfacesCompleter completes interface addresses on the client host. @@ -75,10 +74,10 @@ func userCompleter(client *client.Client, server *server.Server) carapace.Comple } if len(results) == 0 { - return carapace.ActionMessage(fmt.Sprintf("%s teamserver has no users", server.Name())) + return carapace.ActionMessage(server.Name() + " teamserver has no users") } - return carapace.ActionValues(results...).Tag(fmt.Sprintf("%s teamserver users", server.Name())) + return carapace.ActionValues(results...).Tag(server.Name() + " teamserver users") } } @@ -136,6 +135,6 @@ func listenerTypeCompleter(client *client.Client, server *server.Server) carapac return carapace.ActionMessage(fmt.Sprintf("no additional listener types for %s teamserver", server.Name())) } - return carapace.ActionValues(results...).Tag(fmt.Sprintf("%s teamserver listener types", server.Name())) + return carapace.ActionValues(results...).Tag(server.Name() + " teamserver listener types") } } diff --git a/server/commands/teamserver.go b/server/commands/teamserver.go index 2cc8d50..efb6038 100644 --- a/server/commands/teamserver.go +++ b/server/commands/teamserver.go @@ -28,13 +28,12 @@ import ( "strings" "github.com/jedib0t/go-pretty/v6/table" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/reeflective/team/internal/command" "github.com/reeflective/team/internal/log" "github.com/reeflective/team/internal/systemd" "github.com/reeflective/team/server" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" ) func daemoncmd(serv *server.Server) func(cmd *cobra.Command, args []string) error { @@ -59,7 +58,7 @@ func daemoncmd(serv *server.Server) func(cmd *cobra.Command, args []string) erro // Also written to logs in the teamserver code. defer func() { if r := recover(); r != nil { - fmt.Fprintf(cmd.OutOrStdout(), "stacktrace from panic: \n"+string(debug.Stack())) + fmt.Fprintf(cmd.OutOrStdout(), "%s", "stacktrace from panic: \n"+string(debug.Stack())) } }() diff --git a/server/commands/user.go b/server/commands/user.go index 92b6f89..05682db 100644 --- a/server/commands/user.go +++ b/server/commands/user.go @@ -8,13 +8,12 @@ import ( "path/filepath" "strings" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/reeflective/team/client" "github.com/reeflective/team/internal/assets" "github.com/reeflective/team/internal/command" "github.com/reeflective/team/server" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" ) func createUserCmd(serv *server.Server, cli *client.Client) func(cmd *cobra.Command, args []string) { @@ -31,6 +30,7 @@ func createUserCmd(serv *server.Server, cli *client.Client) func(cmd *cobra.Comm lport, _ := cmd.Flags().GetUint16("port") save, _ := cmd.Flags().GetString("save") system, _ := cmd.Flags().GetBool("system") + perms, _ := cmd.Flags().GetStringSlice("permissions") if save == "" { save, _ = os.Getwd() @@ -57,6 +57,7 @@ func createUserCmd(serv *server.Server, cli *client.Client) func(cmd *cobra.Comm } } else { saveTo, _ = filepath.Abs(save) + userFile, err := os.Stat(saveTo) if !os.IsNotExist(err) && !userFile.IsDir() { fmt.Fprintf(cmd.ErrOrStderr(), command.Warn+"File already exists %s\n", err) @@ -70,7 +71,7 @@ func createUserCmd(serv *server.Server, cli *client.Client) func(cmd *cobra.Comm fmt.Fprintf(cmd.OutOrStdout(), command.Info+"Generating new client certificate, please wait ... \n") - config, err := serv.UserCreate(name, lhost, lport) + config, err := serv.UserCreate(name, lhost, lport, perms...) if err != nil { fmt.Fprintf(cmd.ErrOrStderr(), command.Warn+"%s\n", err) return @@ -145,8 +146,8 @@ func importCACmd(serv *server.Server) func(cmd *cobra.Command, args []string) { } importCA := &CA{} - err = json.Unmarshal(data, importCA) + err = json.Unmarshal(data, importCA) if err != nil { fmt.Fprintf(cmd.ErrOrStderr(), command.Warn+"Failed to parse file: %s\n", err) } diff --git a/server/db.go b/server/db.go index 7886e77..b967784 100644 --- a/server/db.go +++ b/server/db.go @@ -159,7 +159,7 @@ func (ts *Server) getDefaultDatabaseConfig() *db.Config { if ts.opts.inMemory { cfg.Database = db.SQLiteInMemoryHost } else { - cfg.Database = filepath.Join(ts.TeamDir(), fmt.Sprintf("%s.teamserver.db", ts.name)) + cfg.Database = filepath.Join(ts.TeamDir(), ts.name+".teamserver.db") } return cfg diff --git a/server/options.go b/server/options.go index fe0b148..1ea2933 100644 --- a/server/options.go +++ b/server/options.go @@ -19,7 +19,6 @@ package server */ import ( - "fmt" "os" "strings" @@ -77,7 +76,7 @@ func (ts *Server) apply(options ...Options) { // set once when created. ts.initOpts.Do(func() { // Application home directory. - homeDir := os.Getenv(fmt.Sprintf("%s_ROOT_DIR", strings.ToUpper(ts.name))) + homeDir := os.Getenv(strings.ToUpper(ts.name) + "_ROOT_DIR") if homeDir != "" { ts.homeDir = homeDir } else { diff --git a/server/users.go b/server/users.go index d94ac9d..58512f4 100644 --- a/server/users.go +++ b/server/users.go @@ -30,19 +30,22 @@ import ( "sync" "time" + "github.com/lib/pq" + "github.com/reeflective/team" "github.com/reeflective/team/client" "github.com/reeflective/team/internal/certs" "github.com/reeflective/team/internal/db" ) var namePattern = regexp.MustCompile("^[a-zA-Z0-9_-]*$") // Only allow alphanumeric chars +const permissionsSep = "," // UserCreate creates a new teamserver user, with all cryptographic material and server remote // endpoints needed by this user to connect to us. // // Certificate files and the API authentication token are saved into the teamserver database, // conformingly to its configured backend/filesystem (can be in-memory or on filesystem). -func (ts *Server) UserCreate(name string, lhost string, lport uint16) (*client.Config, error) { +func (ts *Server) UserCreate(name string, lhost string, lport uint16, perms ...string) (*client.Config, error) { if err := ts.initDatabase(); err != nil { return nil, ts.errorf("%w: %w", ErrDatabase, err) } @@ -70,8 +73,10 @@ func (ts *Server) UserCreate(name string, lhost string, lport uint16) (*client.C digest := sha256.Sum256([]byte(rawToken)) dbuser := &db.User{ - Name: name, - Token: hex.EncodeToString(digest[:]), + Name: name, + Token: hex.EncodeToString(digest[:]), + Permissions: pq.StringArray(perms), + // Permissions: strings.Join(perms, permissionsSep), } err = ts.dbSession().Save(dbuser).Error @@ -141,9 +146,9 @@ func (ts *Server) UserDelete(name string) error { // - No name, false for authenticated, and a database error, if was ignited now. // // This call updates the last time the user has been seen by the server. -func (ts *Server) UserAuthenticate(rawToken string) (name string, authorized bool, err error) { +func (ts *Server) UserAuthenticate(rawToken string) (*team.User, bool, error) { if err := ts.initDatabase(); err != nil { - return "", false, ts.errorf("%w: %w", ErrDatabase, err) + return nil, false, ts.errorf("%w: %w", ErrDatabase, err) } log := ts.NamedLogger("server", "auth") @@ -153,23 +158,34 @@ func (ts *Server) UserAuthenticate(rawToken string) (name string, authorized boo digest := sha256.Sum256([]byte(rawToken)) token := hex.EncodeToString(digest[:]) - if name, ok := ts.userTokens.Load(token); ok { + userFound, ok := ts.userTokens.Load(token) + + // If user is already connected or cached. + if ok { + user := userFound.(*team.User) + log.Debugf("Token in cache!") - ts.updateLastSeen(name.(string)) - return name.(string), true, nil + ts.updateLastSeen(user.Name) + + return user, true, nil } - user, err := ts.userByToken(token) - if err != nil || user == nil { - return "", false, ts.errorf("%w: %w", ErrUnauthenticated, err) + dbUser, err := ts.userByToken(token) + if err != nil || dbUser == nil { + return nil, false, ts.errorf("%w: %w", ErrUnauthenticated, err) } + // Transfer data to the exportable user type + user := &team.User{ + Name: dbUser.Name, + Permissions: []string(dbUser.Permissions), + } ts.updateLastSeen(user.Name) log.Debugf("Valid user token for %s", user.Name) - ts.userTokens.Store(token, user.Name) + ts.userTokens.Store(token, user) - return user.Name, true, nil + return user, true, nil } // UsersTLSConfig returns a server-side Mutual TLS configuration struct, ready to run. @@ -196,7 +212,7 @@ func (ts *Server) UsersTLSConfig() (*tls.Config, error) { _, _, err = ts.certs.UserServerGetCertificate() if errors.Is(err, certs.ErrCertDoesNotExist) { if _, _, err := ts.certs.UserServerGenerateCertificate(); err != nil { - return nil, ts.errorWith(log, err.Error()) + return nil, ts.errorWith(log, "%s", err.Error()) } } diff --git a/teamclient.go b/teamclient.go index 07fdefd..ddc0f3c 100644 --- a/teamclient.go +++ b/teamclient.go @@ -41,10 +41,16 @@ type Client interface { // be in possession of the user cryptographic materials required to serve him) // This type is returned by both team/clients and team/servers. type User struct { - Name string - Online bool - LastSeen time.Time - Clients int + Name string // Name of the user + Online bool // Are one or more of the user's clients connected. + LastSeen time.Time // Last time the user made an RPC call or something. + Clients int // Number of clients connected. + + // Permissions is a list of arbitrary strings that clients/servers + // might want to use for permissions: domain names, tools, tokens... + // These permissions can be specified with --permissions on the CLI, + // and will be always stored along with the user in the database. + Permissions []string } // Version returns complete version/compilation information for a given binary.