Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ffs/manager: add API to regenerate token #834

Merged
merged 4 commits into from
May 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/client/admin/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ func (p *Users) Create(ctx context.Context) (*adminPb.CreateUserResponse, error)
return p.client.CreateUser(ctx, &adminPb.CreateUserRequest{})
}

// RegenerateAuth invalidates an existing token replacing it with a new one.
func (p *Users) RegenerateAuth(ctx context.Context, token string) (*adminPb.RegenerateAuthResponse, error) {
return p.client.RegenerateAuth(ctx, &adminPb.RegenerateAuthRequest{Token: token})
}

// List returns a list of existing users.
func (p *Users) List(ctx context.Context) (*adminPb.UsersResponse, error) {
return p.client.Users(ctx, &adminPb.UsersRequest{})
Expand Down
1,005 changes: 570 additions & 435 deletions api/gen/powergate/admin/v1/admin.pb.go

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions api/gen/powergate/admin/v1/admin_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions api/server/admin/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,25 @@ func (a *Service) CreateUser(ctx context.Context, req *adminPb.CreateUserRequest
}, nil
}

// RegenerateAuth invalidates an existing token replacing it with a new one.
func (a *Service) RegenerateAuth(ctx context.Context, req *adminPb.RegenerateAuthRequest) (*adminPb.RegenerateAuthResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "request is nil")
}
if req.Token == "" {
return nil, status.Errorf(codes.InvalidArgument, "token can't be empty")
}

newToken, err := a.m.RegenerateAuthToken(req.Token)
if err != nil {
return nil, status.Errorf(codes.Internal, "creating instance: %v", err)
}

return &adminPb.RegenerateAuthResponse{
NewToken: newToken,
}, nil
}

// Users lists all managed instances.
func (a *Service) Users(ctx context.Context, req *adminPb.UsersRequest) (*adminPb.UsersResponse, error) {
lst, err := a.m.List()
Expand Down
1 change: 1 addition & 0 deletions cli-docs/pow/pow_admin_users.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ Provides admin users commands
* [pow admin](pow_admin.md) - Provides admin commands
* [pow admin users create](pow_admin_users_create.md) - Create a Powergate user.
* [pow admin users list](pow_admin_users_list.md) - List all Powergate users.
* [pow admin users regenerate](pow_admin_users_regenerate.md) - Invalidates an existing token and replaces it with a new one.

30 changes: 30 additions & 0 deletions cli-docs/pow/pow_admin_users_regenerate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
## pow admin users regenerate

Invalidates an existing token and replaces it with a new one.

### Synopsis

Invalidates an existing token and replaces it with a new one.

```
pow admin users regenerate [token-to-regenerate] [flags]
```

### Options

```
-h, --help help for regenerate
```

### Options inherited from parent commands

```
--admin-token string admin auth token
--serverAddress string address of the powergate service api (default "127.0.0.1:5002")
-t, --token string user auth token
```

### SEE ALSO

* [pow admin users](pow_admin_users.md) - Provides admin users commands

40 changes: 40 additions & 0 deletions cmd/pow/cmd/admin/users/regenerate/regenerate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package regenerate

import (
"context"
"errors"
"fmt"

"github.com/spf13/cobra"
"github.com/spf13/viper"
c "github.com/textileio/powergate/v2/cmd/pow/common"
"google.golang.org/protobuf/encoding/protojson"
)

// Cmd is the command.
var Cmd = &cobra.Command{
Use: "regenerate [token-to-regenerate]",
Short: "Invalidates an existing token and replaces it with a new one.",
Long: `Invalidates an existing token and replaces it with a new one.`,
Args: cobra.ExactArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
err := viper.BindPFlags(cmd.Flags())
c.CheckErr(err)
},
Run: func(cmd *cobra.Command, args []string) {
ctx, cancel := context.WithTimeout(context.Background(), c.CmdTimeout)
defer cancel()

if len(args) != 1 {
c.Fatal(errors.New("token argument is mandatory"))
}

res, err := c.PowClient.Admin.Users.RegenerateAuth(c.AdminAuthCtx(ctx), args[0])
c.CheckErr(err)

json, err := protojson.MarshalOptions{Multiline: true, Indent: " ", EmitUnpopulated: true}.Marshal(res)
c.CheckErr(err)

fmt.Println(string(json))
},
}
3 changes: 2 additions & 1 deletion cmd/pow/cmd/admin/users/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import (
"github.com/spf13/cobra"
"github.com/textileio/powergate/v2/cmd/pow/cmd/admin/users/create"
"github.com/textileio/powergate/v2/cmd/pow/cmd/admin/users/list"
"github.com/textileio/powergate/v2/cmd/pow/cmd/admin/users/regenerate"
)

func init() {
Cmd.AddCommand(create.Cmd, list.Cmd)
Cmd.AddCommand(create.Cmd, list.Cmd, regenerate.Cmd)
}

// Cmd is the command.
Expand Down
49 changes: 47 additions & 2 deletions ffs/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ var (
// Auth contains a mapping between auth-tokens and Api instances.
type Auth struct {
lock sync.Mutex
ds ds.Datastore
ds ds.TxnDatastore
}

// New returns a new Auth.
func New(store ds.Datastore) *Auth {
func New(store ds.TxnDatastore) *Auth {
return &Auth{
ds: store,
}
Expand Down Expand Up @@ -72,6 +72,51 @@ func (r *Auth) Get(token string) (ffs.APIID, error) {
return e.APIID, nil
}

// RegenerateAuthToken invalidates a token regenerating a new one.
func (r *Auth) RegenerateAuthToken(token string) (string, error) {
r.lock.Lock()
defer r.lock.Unlock()

txn, err := r.ds.NewTransaction(false)
if err != nil {
return "", fmt.Errorf("creating transaction: %s", err)
}
defer txn.Discard()

buf, err := txn.Get(ds.NewKey(token))
if err != nil && err == ds.ErrNotFound {
return "", ErrNotFound
}
if err != nil {
return "", fmt.Errorf("getting token %s from datastore: %s", token, err)
}
var e ffs.AuthEntry
if err := json.Unmarshal(buf, &e); err != nil {
return "", fmt.Errorf("unmarshaling %s information from datastore: %s", token, err)
}

// Delete old token.
if err := txn.Delete(ds.NewKey(token)); err != nil {
return "", fmt.Errorf("deleting old token: %s", err)
}

// Set new token
e.Token = uuid.New().String()
buf, err = json.Marshal(&e)
if err != nil {
return "", fmt.Errorf("marshaling new regenerated token: %s", err)
}
if err := txn.Put(ds.NewKey(e.Token), buf); err != nil {
return "", fmt.Errorf("saving regenerated token: %s", err)
}

if err := txn.Commit(); err != nil {
return "", fmt.Errorf("committing transaction: %s", err)
}

return e.Token, nil
}

// List returns a list of all API instances.
func (r *Auth) List() ([]ffs.AuthEntry, error) {
r.lock.Lock()
Expand Down
18 changes: 16 additions & 2 deletions ffs/manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/textileio/powergate/v2/ffs/api"
"github.com/textileio/powergate/v2/ffs/auth"
"github.com/textileio/powergate/v2/ffs/scheduler"
txndstr "github.com/textileio/powergate/v2/txndstransform"
"github.com/textileio/powergate/v2/util"
)

Expand Down Expand Up @@ -83,7 +84,7 @@ type Manager struct {
}

// New returns a new Manager.
func New(ds datastore.Datastore, wm ffs.WalletManager, drm ffs.DealRecordsManager, sched *scheduler.Scheduler, ffsUseMasterAddr bool, onLocalnet bool) (*Manager, error) {
func New(ds datastore.TxnDatastore, wm ffs.WalletManager, drm ffs.DealRecordsManager, sched *scheduler.Scheduler, ffsUseMasterAddr bool, onLocalnet bool) (*Manager, error) {
if ffsUseMasterAddr && wm.MasterAddr() == address.Undef {
return nil, fmt.Errorf("ffsUseMasterAddr requires that master address is defined")
}
Expand All @@ -92,7 +93,7 @@ func New(ds datastore.Datastore, wm ffs.WalletManager, drm ffs.DealRecordsManage
return nil, fmt.Errorf("loading default storage config: %s", err)
}
return &Manager{
auth: auth.New(namespace.Wrap(ds, datastore.NewKey("auth"))),
auth: auth.New(txndstr.Wrap(ds, "auth")),
ds: ds,
wm: wm,
drm: drm,
Expand Down Expand Up @@ -179,6 +180,19 @@ func (m *Manager) List() ([]ffs.AuthEntry, error) {
return res, nil
}

// RegenerateAuthToken invalidates the provided token replacing it with a new one.
func (m *Manager) RegenerateAuthToken(token string) (string, error) {
m.lock.Lock()
defer m.lock.Unlock()

newToken, err := m.auth.RegenerateAuthToken(token)
if err == auth.ErrNotFound {
return "", ErrAuthTokenNotFound
}

return newToken, nil
}

// GetByAuthToken loads an existing instance using an auth-token. If auth-token doesn't exist,
// it returns ErrAuthTokenNotFound.
func (m *Manager) GetByAuthToken(token string) (*api.API, error) {
Expand Down
25 changes: 25 additions & 0 deletions ffs/manager/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,31 @@ func TestGetByAuthToken(t *testing.T) {
})
}

func TestRegenerateAuthToken(t *testing.T) {
t.Parallel()
ds := tests.NewTxMapDatastore()
ctx := context.Background()
client, addr, _ := tests.CreateLocalDevnet(t, 1, 300)
m, cls, err := newManager(client, ds, addr, false)
require.NoError(t, err)
defer require.NoError(t, cls())
originalAuth, err := m.Create(ctx)
require.NoError(t, err)

regeneratedAuth, err := m.RegenerateAuthToken(originalAuth.Token)
require.NoError(t, err)

// The old token should be invalid
_, err = m.GetByAuthToken(originalAuth.Token)
require.Equal(t, err, ErrAuthTokenNotFound)

// Get with new token and check the APIID is equal to the original one.
n, err := m.GetByAuthToken(regeneratedAuth)
require.NoError(t, err)
require.Equal(t, originalAuth.APIID, n.ID())
require.NotEmpty(t, n.Addrs())
}

func TestDefaultStorageConfig(t *testing.T) {
t.Parallel()
ds := tests.NewTxMapDatastore()
Expand Down
9 changes: 9 additions & 0 deletions proto/powergate/admin/v1/admin.proto
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ message CreateUserResponse {
User user = 1;
}

message RegenerateAuthRequest {
string token = 1;
}

message RegenerateAuthResponse {
string new_token = 1;
}

message UsersRequest {
}

Expand Down Expand Up @@ -188,6 +196,7 @@ service AdminService {

// Users
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {}
rpc RegenerateAuth(RegenerateAuthRequest) returns (RegenerateAuthResponse){}
rpc Users(UsersRequest) returns (UsersResponse) {}

// Storage Info
Expand Down