diff --git a/cmd/web/cmd/root.go b/cmd/web/cmd/root.go index 8879515a..4ac1ce35 100644 --- a/cmd/web/cmd/root.go +++ b/cmd/web/cmd/root.go @@ -123,6 +123,7 @@ func (s *WebSrv) Start(ctx context.Context, ready server.ReadyFunc, run server.R // Accounts handler app.Get("/accounts", handlers.ListAccounts()) app.Get("/accounts/new", handlers.NewAccount()) + app.Post("/accounts/create", handlers.CreateAccount()) // Users handler app.Get("/users", handlers.ListUsers()) diff --git a/internal/web/adapters/handlers/handlers.go b/internal/web/adapters/handlers/handlers.go index b81b2f7f..5264c7b0 100644 --- a/internal/web/adapters/handlers/handlers.go +++ b/internal/web/adapters/handlers/handlers.go @@ -78,3 +78,8 @@ func (h *handlers) NewAccount() fiber.Handler { func (h *handlers) ListUsers() fiber.Handler { return htmx.NewHxControllerHandler(users.NewListUsersController(h.db)) } + +// CreateAccount ... +func (h *handlers) CreateAccount() fiber.Handler { + return htmx.NewHxControllerHandler(accounts.NewCreateController(h.db)) +} diff --git a/internal/web/controllers/accounts/create.go b/internal/web/controllers/accounts/create.go index 9c45c807..a7db203c 100644 --- a/internal/web/controllers/accounts/create.go +++ b/internal/web/controllers/accounts/create.go @@ -1,13 +1,15 @@ package accounts import ( + "fmt" + "github.com/go-playground/validator/v10" "github.com/google/uuid" + "github.com/nats-io/jwt" + "github.com/nats-io/nkeys" htmx "github.com/zeiss/fiber-htmx" - "github.com/zeiss/fiber-htmx/components/buttons" - "github.com/zeiss/fiber-htmx/components/cards" - "github.com/zeiss/fiber-htmx/components/forms" - "github.com/zeiss/typhoon/internal/web/components" + "github.com/zeiss/typhoon/internal/api/models" + "github.com/zeiss/typhoon/internal/utils" "github.com/zeiss/typhoon/internal/web/ports" ) @@ -15,18 +17,18 @@ var validate *validator.Validate // CreateControllerImpl ... type CreateControllerImpl struct { - OperatorID uuid.UUID `json:"operator_id" form:"operator_id" validate:"required:uuid"` + OperatorID uuid.UUID `json:"operator_id" form:"operator_id" validate:"required,uuid"` Name string `json:"name" form:"name" validate:"required,min=3,max=100"` Description string `json:"description" form:"description" validate:"required,min=3,max=1024"` - ports.Accounts + ports.Repository htmx.DefaultController } // NewCreateController ... -func NewCreateController(db ports.Accounts) *CreateControllerImpl { +func NewCreateController(db ports.Repository) *CreateControllerImpl { return &CreateControllerImpl{ - Accounts: db, + Repository: db, DefaultController: htmx.DefaultController{}, } } @@ -35,7 +37,7 @@ func NewCreateController(db ports.Accounts) *CreateControllerImpl { func (l *CreateControllerImpl) Prepare() error { validate = validator.New() - err := l.Ctx().BodyParser(&l) + err := l.Ctx().BodyParser(l) if err != nil { return err } @@ -48,158 +50,87 @@ func (l *CreateControllerImpl) Prepare() error { return nil } +// Error ... +func (l *CreateControllerImpl) Error(err error) error { + fmt.Println(err) + + return err +} + // Post ... func (l *CreateControllerImpl) Post() error { - // op := models.Account{ - // Name: l.Name, - // Description: utils.StrPtr(l.Description), - // } + account := models.Account{ + Name: l.Name, + OperatorID: l.OperatorID, + Description: utils.StrPtr(l.Description), + } + + operator := models.Operator{ + ID: l.OperatorID, + } + err := l.GetOperator(l.Context(), &operator) + if err != nil { + return err + } + + pk, err := nkeys.CreateAccount() + if err != nil { + return err + } + + id, err := pk.PublicKey() + if err != nil { + return err + } + + seed, err := pk.Seed() + if err != nil { + return err + } + account.Key = models.NKey{ID: id, Seed: seed} + + skg := models.SigningKeyGroup{Name: "Default", Description: "Default signing key group"} + + skgpk, err := nkeys.CreateAccount() + if err != nil { + return err + } + + skgid, err := skgpk.PublicKey() + if err != nil { + return err + } + + skgseed, err := skgpk.Seed() + if err != nil { + return err + } + skg.Key = models.NKey{ID: skgid, Seed: skgseed} + account.SigningKeyGroups = append(account.SigningKeyGroups, skg) + + // @katallaxie: this is a bit weird, but I think it's a good idea to have a default signing key group + osk, err := nkeys.FromSeed(operator.SigningKeyGroups[0].Key.Seed) + if err != nil { + return err + } - // op, err := models.NewOperator(query.Name, query.Description) - // if err != nil { - // return err - // } + ac := jwt.NewAccountClaims(id) + ac.Name = l.Name + ac.Issuer = operator.KeyID + ac.SigningKeys.Add(skg.Key.ID) + + token, err := ac.Encode(osk) + if err != nil { + return err + } + account.Token = models.Token{ID: id, Token: token} - // err = l.CreateOperator(l.Context(), &op) - // if err != nil { - // return err - // } + err = l.CreateAccount(l.Context(), &account) + if err != nil { + return err + } htmx.Redirect(l.Ctx(), "/accounts") return nil } - -// Get ... -func (l *CreateControllerImpl) Get() error { - return htmx.RenderComp( - l.Ctx(), - components.Page( - components.PageProps{}, - components.Layout( - components.LayoutProps{}, - htmx.FormElement( - htmx.HxPost("/accounts/new"), - cards.CardBordered( - cards.CardProps{}, - cards.Body( - cards.BodyProps{}, - cards.Title( - cards.TitleProps{}, - htmx.Text("Properties"), - ), - forms.FormControl( - forms.FormControlProps{ - ClassNames: htmx.ClassNames{ - "py-4": true, - }, - }, - forms.FormControlLabel( - forms.FormControlLabelProps{}, - forms.FormControlLabelText( - forms.FormControlLabelTextProps{ - ClassNames: htmx.ClassNames{ - "-my-4": true, - }, - }, - htmx.Text("Name"), - ), - ), - forms.FormControlLabel( - forms.FormControlLabelProps{}, - forms.FormControlLabelText( - forms.FormControlLabelTextProps{ - ClassNames: htmx.ClassNames{ - "text-neutral-500": true, - }, - }, - htmx.Text("A unique identifier for operator."), - ), - ), - forms.TextInputBordered( - forms.TextInputProps{ - Name: "name", - }, - ), - forms.FormControlLabel( - forms.FormControlLabelProps{}, - forms.FormControlLabelText( - forms.FormControlLabelTextProps{ - ClassNames: htmx.ClassNames{ - "text-neutral-500": true, - }, - }, - htmx.Text("The name must be from 3 to 100 characters. At least 3 characters must be non-whitespace."), - ), - ), - forms.FormControl( - forms.FormControlProps{ - ClassNames: htmx.ClassNames{ - "py-4": true, - }, - }, - forms.FormControlLabel( - forms.FormControlLabelProps{}, - forms.FormControlLabelText( - forms.FormControlLabelTextProps{ - ClassNames: htmx.ClassNames{ - "-my-4": true, - }, - }, - htmx.Text("Description"), - ), - ), - forms.FormControlLabel( - forms.FormControlLabelProps{}, - forms.FormControlLabelText( - forms.FormControlLabelTextProps{ - ClassNames: htmx.ClassNames{ - "text-neutral-500": true, - }, - }, - htmx.Text("A brief description of the operator to provide context."), - ), - ), - forms.TextareaBordered( - forms.TextareaProps{ - Name: "description", - }, - ), - forms.FormControlLabel( - forms.FormControlLabelProps{}, - forms.FormControlLabelText( - forms.FormControlLabelTextProps{ - ClassNames: htmx.ClassNames{ - "text-neutral-500": true, - }, - }, - htmx.Text("The description must be from 3 to 1024 characters."), - ), - ), - ), - ), - ), - ), - cards.CardBordered( - cards.CardProps{}, - cards.Body( - cards.BodyProps{}, - cards.Title( - cards.TitleProps{}, - htmx.Text("Tags - Optional"), - ), - cards.Actions( - cards.ActionsProps{}, - buttons.Outline( - buttons.ButtonProps{}, - htmx.Attribute("type", "submit"), - htmx.Text("Create Operator"), - ), - ), - ), - ), - ), - ), - ), - ) -} diff --git a/internal/web/controllers/accounts/new.go b/internal/web/controllers/accounts/new.go index 13910f5a..d90fb9a7 100644 --- a/internal/web/controllers/accounts/new.go +++ b/internal/web/controllers/accounts/new.go @@ -55,7 +55,7 @@ func (l *NewAccountControllerImpl) Get() error { components.Layout( components.LayoutProps{}, htmx.FormElement( - htmx.HxPost("/accounts/new"), + htmx.HxPost("/accounts/create"), cards.CardBordered( cards.CardProps{}, cards.Body( @@ -98,6 +98,7 @@ func (l *NewAccountControllerImpl) Get() error { }, htmx.Text("Select an operator"), ), + htmx.Name("operator_id"), htmx.ForEach(l.Operators, func(operator *models.Operator) htmx.Node { return forms.Option( forms.OptionProps{ diff --git a/internal/web/ports/handlers.go b/internal/web/ports/handlers.go index d0949b25..0ef03e26 100644 --- a/internal/web/ports/handlers.go +++ b/internal/web/ports/handlers.go @@ -22,10 +22,10 @@ type Handlers interface { DeleteOperator() fiber.Handler // ListAccounts ... ListAccounts() fiber.Handler - // // NewAccount ... - // NewAccount() fiber.Handler - // // CreateAccount ... - // CreateAccount() fiber.Handler + // NewAccount ... + NewAccount() fiber.Handler + // CreateAccount ... + CreateAccount() fiber.Handler // // ShowAccount ... // ShowAccount() fiber.Handler // // DeleteAccount ...