Skip to content

Commit

Permalink
store node data in token, rework login
Browse files Browse the repository at this point in the history
  • Loading branch information
djelusic committed Mar 8, 2022
1 parent 48c7489 commit 72a8e77
Show file tree
Hide file tree
Showing 12 changed files with 320 additions and 191 deletions.
14 changes: 7 additions & 7 deletions cli/controller/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,21 @@ const (
func AuthToken(n *domain.Node) (string, error) {
t, err := n.AuthToken()
var terr *domain.TokenExpiredError
if errors.As(err, &terr) && n.GithubUser != "" {
if errors.As(err, &terr) && n.GithubAuthEnabled() {
var err error
t, err = githubAuth(n)
t, err = githubAuth(n.Endpoints.Rest)
if err != nil {
return "", log.Wrap(err)
}
n.JWT = t
n.UpdateToken(t)
} else if err != nil {
return "", log.Wrap(err)
}
return t, nil
}

func githubAuth(n *domain.Node) (string, error) {
s, err := createState(n)
func githubAuth(nodeEndpoint string) (string, error) {
s, err := createState(nodeEndpoint)
if err != nil {
return "", err
}
Expand All @@ -55,11 +55,11 @@ type state struct {
NodeEndpoint string `json:"node_endpoint"`
}

func createState(n *domain.Node) (*state, error) {
func createState(nodeEndpoint string) (*state, error) {
inbox := nats.NewInbox()
s := state{
Inbox: inbox,
NodeEndpoint: n.Endpoints.Rest,
NodeEndpoint: nodeEndpoint,
}
return &s, nil
}
Expand Down
18 changes: 4 additions & 14 deletions cli/controller/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package controller
import (
"fmt"

"github.com/mantil-io/mantil/cli/controller/invoke"
"github.com/mantil-io/mantil/cli/ui"
"github.com/mantil-io/mantil/domain"
"github.com/mantil-io/mantil/node/dto"
)
Expand Down Expand Up @@ -71,17 +69,16 @@ type NodeLoginArgs struct {
}

func NodeLogin(a NodeLoginArgs) error {
i := invoke.Node(a.NodeURL, "", ui.NodeLogsSink)
var rsp dto.LoginResponse
if err := i.Do(LoginHTTPMethod, nil, &rsp); err != nil {
t, err := githubAuth(a.NodeURL)
if err != nil {
return err
}
fs, err := domain.NewSingleDeveloperWorkspaceStore()
if err != nil {
return err
}
w := fs.Workspace()
w.AddNode(rsp.Node)
w.AddNodeToken(t)
return fs.Store()
}

Expand All @@ -95,13 +92,6 @@ func NodeLogout(a NodeLogoutArgs) error {
return err
}
w := fs.Workspace()
if len(w.Nodes) == 0 {
return fmt.Errorf("no nodes avaiable")
}
n := w.FindNode(a.NodeName)
if n == nil {
return fmt.Errorf("node not found")
}
n.JWT = ""
w.RemoveNode(a.NodeName)
return fs.Store()
}
4 changes: 4 additions & 0 deletions cli/controller/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ func (c *Setup) create(n *domain.Node) error {
n.CliRole = rsp.CliRole
infrastructureDuration := tmr()

if n.GithubAuthEnabled() {
c.store.Workspace().AddNodeToken(rsp.Token)
}

log.Event(domain.Event{NodeCreate: &domain.NodeEvent{
AWSCredentialsProvider: c.credentialsProvider,
StackDuration: stackDuration,
Expand Down
11 changes: 6 additions & 5 deletions domain/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type AccessTokenClaims struct {
Runtime string `json:"r,omitempty"`
Username string `json:"u,omitempty"`
Role Role `json:"o,omitempty"`
Node *Node `json:"n,omitempty"`
}

type Role int
Expand All @@ -38,17 +39,17 @@ var (

func ReadAccessToken(headers map[string]string, publicKey string) (*AccessTokenClaims, error) {
if at, ok := headers[AccessTokenHeader]; ok {
return decodeAccessToken(at, publicKey)
return verifyAccessToken(at, publicKey)
}
if at, ok := headers[strings.ToLower(AccessTokenHeader)]; ok {
return decodeAccessToken(at, publicKey)
return verifyAccessToken(at, publicKey)
}
return nil, fmt.Errorf("access token not found in %s header", AccessTokenHeader)
}

func decodeAccessToken(at, pk string) (*AccessTokenClaims, error) {
func verifyAccessToken(at, pk string) (*AccessTokenClaims, error) {
var claims AccessTokenClaims
if err := token.Decode(at, pk, &claims); err != nil {
if err := token.Verify(at, pk, &claims); err != nil {
return nil, err
}
return &claims, nil
Expand All @@ -59,7 +60,7 @@ func StoreUserClaims(claims *AccessTokenClaims, context map[string]interface{})
context[ContextUserClaimsKey] = string(buf)
}

func IsOwner(ctx context.Context) (bool, error) {
func IsAdmin(ctx context.Context) (bool, error) {
claims, err := ClaimsFromContext(ctx)
if err != nil {
return false, err
Expand Down
247 changes: 247 additions & 0 deletions domain/node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
package domain

import (
"fmt"
"time"

"github.com/mantil-io/mantil/kit/token"
)

type Node struct {
Name string `yaml:"name,omitempty"`
ID string `yaml:"id"`
Version string `yaml:"version"`
// AWS related attributes
AccountID string `yaml:"accountID"` // AWS account id
Region string `yaml:"region"` // AWS region
Bucket string `yaml:"bucket"` // bucket name created on AWS
CliRole string `yaml:"cli_role"` // role name for security node lambda function

Keys NodeKeys `yaml:"keys,omitempty"`
Endpoints NodeEndpoints `yaml:"endpoints"`
Functions NodeFunctions `yaml:"functions"`
Stages []*NodeStage `yaml:"stages,omitempty"`

GithubUser string `yaml:"github_user,omitempty"`

workspace *Workspace
}

type NodeKeys struct {
Public string `yaml:"public"`
Private string `yaml:"private"`
}

type NodeEndpoints struct {
Rest string `yaml:"rest"`
}

type NodeFunctions struct {
Bucket string `yaml:"bucket"`
Path string `yaml:"key"`
}

type NodeStage struct {
Name string `yaml:"name"`
ProjectName string `yaml:"project_name"`
}

func (n *Node) ResourceTags() map[string]string {
return map[string]string{
TagKey: n.ID,
}
}

func (n *Node) UpgradeVersion(version, functionsBbucket, functionsPath string) {
n.Version = version
n.Functions.Bucket = functionsBbucket
n.Functions.Path = functionsPath
}

func (n *Node) AuthEnv() map[string]string {
return map[string]string{
EnvPublicKey: n.Keys.Public,
EnvKVTable: n.KVTableName(),
EnvSSMPathPrefix: fmt.Sprintf("/mantil-node-%s", n.ID),
}
}

func (n *Node) SetupEnv() map[string]string {
return map[string]string{
EnvKVTable: n.KVTableName(),
}
}

func (n *Node) KVTableName() string {
return fmt.Sprintf("mantil-kv-%s", n.ID)
}

const (
nodeResourcePrefix = "mantil-setup"
)

func (n *Node) ResourceSuffix() string {
return n.ID
}

func (n *Node) ResourceNamingTemplate() string {
return "mantil-%s-" + n.ID
}

func (n *Node) SetupStackName() string {
return n.SetupLambdaName()
}

func (n *Node) SetupLambdaName() string {
return fmt.Sprintf("%s-%s", nodeResourcePrefix, n.ID)
}

func (n *Node) AuthToken() (string, error) {
if !n.GithubAuthEnabled() {
claims := &AccessTokenClaims{
Role: Admin,
Workspace: n.workspace.ID,
}
return token.JWT(n.Keys.Private, claims, 7*24*time.Hour)
}
t := n.workspace.NodeStore.Token(n.Name)
if t == "" {
return "", &TokenExpiredError{}
}
exp, err := token.ExpiresAt(t)
if err != nil {
return "", err
}
if exp.Before(time.Now()) {
return "", &TokenExpiredError{}
}
return t, nil
}

func (n *Node) UpdateToken(token string) {
n.workspace.NodeStore.UpsertNodeToken(token)
}

func (n *Node) AddStage(name, projectName, path string) {
n.Stages = append(n.Stages, &NodeStage{
Name: name,
ProjectName: projectName,
})
}

func (n *Node) RemoveStage(name string) {
for idx, s := range n.Stages {
if s.Name == name {
n.Stages = append(n.Stages[:idx], n.Stages[idx+1:]...)
return
}
}
}

func (n *Node) resourceName(name string) string {
return fmt.Sprintf("mantil-%s-%s", name, n.ID)
}

func (n *Node) Resources() []AwsResource {
var ar []AwsResource
for _, name := range []string{"setup", "authorizer", "deploy", "destroy", "security"} {
ar = append(ar, AwsResource{name, n.resourceName(name), AwsResourceLambda})
}
ar = append(ar, AwsResource{"setup", n.SetupStackName(), AwsResourceStack})
ar = append(ar, AwsResource{"http", n.resourceName("http"), AwsResourceAPIGateway})
ar = append(ar, AwsResource{"", n.Bucket, AwsResourceS3Bucket})

return ar
}

func (n *Node) GithubAuthEnabled() bool {
return n.GithubUser != ""
}

type NodeStore struct {
Nodes []*NodeStoreEntry `yaml:"nodes"`
workspace *Workspace
}

type NodeStoreEntry struct {
Name string `yaml:"name"`
Token string `yaml:"token"`
store *NodeStore
}

func (s *NodeStore) afterRestore() {
for _, n := range s.Nodes {
n.store = s
}
}

func (s *NodeStore) Node(name string) (*Node, error) {
var ne *NodeStoreEntry
for _, n := range s.Nodes {
if n.Name == name {
ne = n
break
}
}
if ne == nil {
return nil, &NodeNotFoundError{Name: name}
}
n, err := nodeFromToken(ne.Token)
if err != nil {
return nil, err
}
n.workspace = ne.store.workspace
return n, nil
}

func (s *NodeStore) FindNode(name string) (*Node, error) {
if name == "" && len(s.Nodes) == 1 {
return s.Node(s.Nodes[0].Name)
}
return s.Node(name)
}

func (s *NodeStore) UpsertNodeToken(token string) error {
n, err := nodeFromToken(token)
if err != nil {
return err
}
e := &NodeStoreEntry{
Name: n.Name,
Token: token,
}
for idx, no := range s.Nodes {
if no.Name == n.Name {
s.Nodes[idx] = e
return nil
}
}
s.Nodes = append(s.Nodes, e)
return nil
}

func nodeFromToken(t string) (*Node, error) {
var claims AccessTokenClaims
if err := token.Decode(t, &claims); err != nil {
return nil, err
}
return claims.Node, nil
}

func (s *NodeStore) RemoveNode(name string) {
for idx, n := range s.Nodes {
if n.Name == name {
s.Nodes = append(s.Nodes[:idx], s.Nodes[idx+1:]...)
return
}
}
}

func (s *NodeStore) Token(nodeName string) string {
for _, n := range s.Nodes {
if n.Name == nodeName {
return n.Token
}
}
return ""
}
Loading

0 comments on commit 72a8e77

Please sign in to comment.