Improve flows in preparation for MFA
This commit is contained in:
parent
60301c9892
commit
f272514c65
5 changed files with 122 additions and 95 deletions
|
@ -1,6 +1,7 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
|
@ -19,6 +20,7 @@ type AuthFlowManager struct {
|
|||
type LoginFlow struct {
|
||||
flow.FlowHandler
|
||||
|
||||
prov provider.AuthProvider
|
||||
ClientID common.ClientID
|
||||
FlowContext
|
||||
}
|
||||
|
@ -52,9 +54,10 @@ func NewAuthFlowManager() *AuthFlowManager {
|
|||
|
||||
func (afm *AuthFlowManager) NewLoginFlow(req *LoginFlowRequest, prov provider.AuthProvider) *LoginFlow {
|
||||
lf := &LoginFlow{
|
||||
FlowHandler: flow.NewFlowHandlerBase(prov, prov.ProviderType()),
|
||||
FlowHandler: flow.NewFlowHandlerBase(prov.ProviderType()),
|
||||
ClientID: req.ClientID,
|
||||
FlowContext: req.FlowContext(),
|
||||
prov: prov,
|
||||
}
|
||||
|
||||
afm.Register(lf)
|
||||
|
@ -80,9 +83,9 @@ func (a *authenticator) NewFlow(r *LoginFlowRequest) *flow.Result {
|
|||
return nil
|
||||
}
|
||||
|
||||
flow := a.flows.NewLoginFlow(r, prov)
|
||||
lf := a.flows.NewLoginFlow(r, prov)
|
||||
|
||||
return flow.ShowForm(nil)
|
||||
return lf.ShowForm(lf.WithSchema(prov), lf.WithStep(flow.StepInit))
|
||||
}
|
||||
|
||||
func (f *LoginFlow) redirect(c echo.Context) {
|
||||
|
@ -99,15 +102,19 @@ func (f *LoginFlow) progress(a *authenticator, c echo.Context) error {
|
|||
return c.String(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
err = f.Schema.CheckRequired(rm)
|
||||
err = f.prov.FlowSchema().CheckRequired(rm)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, f.ShowForm([]string{err.Error()}))
|
||||
return c.JSON(http.StatusBadRequest, f.ShowForm(f.WithErrors([]string{err.Error()})))
|
||||
}
|
||||
|
||||
user, clientID, err := a.Check(f, c.Request(), rm)
|
||||
switch err {
|
||||
case nil:
|
||||
creds := a.store.GetCredential(user)
|
||||
if creds == nil {
|
||||
return fmt.Errorf("flow progress: no such credential for %v", user.UserData())
|
||||
}
|
||||
|
||||
finishedFlow := flow.Result{}
|
||||
a.flows.Remove(f)
|
||||
copier.Copy(&finishedFlow, f)
|
||||
|
@ -124,14 +131,14 @@ func (f *LoginFlow) progress(a *authenticator, c echo.Context) error {
|
|||
case ErrInvalidAuth:
|
||||
fallthrough
|
||||
default:
|
||||
return c.JSON(http.StatusOK, f.ShowForm(map[string]interface{}{
|
||||
return c.JSON(http.StatusOK, f.ShowForm(f.WithErrors(map[string]interface{}{
|
||||
"base": "invalid_auth",
|
||||
}))
|
||||
})))
|
||||
}
|
||||
default:
|
||||
return c.JSON(http.StatusOK, f.ShowForm(map[string]interface{}{
|
||||
return c.JSON(http.StatusOK, f.ShowForm(f.WithErrors(map[string]interface{}{
|
||||
"base": "unknown_flow_step",
|
||||
}))
|
||||
})))
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,18 +162,7 @@ func (hap *HomeAssistantProvider) NewCredData() interface{} {
|
|||
}
|
||||
|
||||
func (hap *HomeAssistantProvider) FlowSchema() flow.Schema {
|
||||
return []flow.SchemaItem{
|
||||
{
|
||||
Type: "string",
|
||||
Name: "username",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Type: "string",
|
||||
Name: "password",
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
return flow.NewSchema(flow.RequiredString("username"), flow.RequiredString("password"))
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -79,18 +79,7 @@ func (hap *TrustedNetworksProvider) NewCredData() interface{} {
|
|||
}
|
||||
|
||||
func (hap *TrustedNetworksProvider) FlowSchema() flow.Schema {
|
||||
return []flow.SchemaItem{
|
||||
{
|
||||
Type: "string",
|
||||
Name: "username",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Type: "string",
|
||||
Name: "password",
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
126
pkg/flow/flow.go
126
pkg/flow/flow.go
|
@ -8,71 +8,53 @@ import (
|
|||
"dynatron.me/x/blasphem/internal/generate"
|
||||
)
|
||||
|
||||
type (
|
||||
ResultType string
|
||||
FlowID string
|
||||
Step string
|
||||
HandlerKey string
|
||||
Errors interface{}
|
||||
type ResultType string
|
||||
type FlowID string
|
||||
type Step string
|
||||
type HandlerKey string
|
||||
type Errors interface{}
|
||||
|
||||
Context interface{}
|
||||
type FlowStore map[FlowID]Handler
|
||||
|
||||
FlowStore map[FlowID]Handler
|
||||
type FlowManager struct {
|
||||
flows FlowStore
|
||||
}
|
||||
|
||||
FlowManager struct {
|
||||
flows FlowStore
|
||||
}
|
||||
type Result struct {
|
||||
Type ResultType `json:"type"`
|
||||
ID FlowID `json:"flow_id"`
|
||||
Handler []*HandlerKey `json:"handler"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
Data map[string]interface{} `json:"data,omitempty"`
|
||||
StepID *Step `json:"step_id,omitempty"`
|
||||
Schema []SchemaItem `json:"data_schema"`
|
||||
Extra *string `json:"extra,omitempty"`
|
||||
Required *bool `json:"required,omitempty"`
|
||||
Errors interface{} `json:"errors"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
DescPlace *string `json:"description_placeholders"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
Reason *string `json:"reason,omitempty"`
|
||||
Context *string `json:"context,omitempty"`
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
LastStep *string `json:"last_step"`
|
||||
Options map[string]interface{} `json:"options,omitempty"`
|
||||
Version *int `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
Result struct {
|
||||
Type ResultType `json:"type"`
|
||||
ID FlowID `json:"flow_id"`
|
||||
Handler []*HandlerKey `json:"handler"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
Data map[string]interface{} `json:"data,omitempty"`
|
||||
StepID *Step `json:"step_id,omitempty"`
|
||||
Schema []SchemaItem `json:"data_schema"`
|
||||
Extra *string `json:"extra,omitempty"`
|
||||
Required *bool `json:"required,omitempty"`
|
||||
Errors interface{} `json:"errors"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
DescPlace *string `json:"description_placeholders"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
Reason *string `json:"reason,omitempty"`
|
||||
Context *string `json:"context,omitempty"`
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
LastStep *string `json:"last_step"`
|
||||
Options map[string]interface{} `json:"options,omitempty"`
|
||||
Version *int `json:"version,omitempty"`
|
||||
}
|
||||
type Handler interface {
|
||||
BaseHandler() FlowHandler
|
||||
FlowID() FlowID
|
||||
|
||||
SchemaItem struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Required bool `json:"required"`
|
||||
}
|
||||
|
||||
Schema []SchemaItem
|
||||
)
|
||||
|
||||
type (
|
||||
Schemer interface {
|
||||
FlowSchema() Schema
|
||||
}
|
||||
|
||||
Handler interface {
|
||||
BaseHandler() FlowHandler
|
||||
FlowID() FlowID
|
||||
|
||||
flowCtime() time.Time
|
||||
}
|
||||
)
|
||||
flowCtime() time.Time
|
||||
}
|
||||
|
||||
const (
|
||||
StepInit Step = "init"
|
||||
)
|
||||
|
||||
func (fs *Schema) CheckRequired(rm map[string]interface{}) error {
|
||||
for _, si := range *fs {
|
||||
func (fs Schema) CheckRequired(rm map[string]interface{}) error {
|
||||
for _, si := range fs {
|
||||
if si.Required {
|
||||
if _, ok := rm[si.Name]; !ok {
|
||||
return fmt.Errorf("missing required param %s", si.Name)
|
||||
|
@ -94,8 +76,6 @@ func stepPtr(s Step) *Step { return &s }
|
|||
type FlowHandler struct {
|
||||
ID FlowID // ID is the FlowID
|
||||
Handler HandlerKey // Handler key
|
||||
Context Context // flow Context
|
||||
Schema Schema
|
||||
|
||||
// curStep is the current step set by the flow manager
|
||||
curStep Step
|
||||
|
@ -113,11 +93,10 @@ func (f *FlowHandler) FlowID() FlowID {
|
|||
|
||||
func (f *FlowHandler) flowCtime() time.Time { return f.ctime }
|
||||
|
||||
func NewFlowHandlerBase(sch Schemer, hand string) FlowHandler {
|
||||
func NewFlowHandlerBase(hand string) FlowHandler {
|
||||
return FlowHandler{
|
||||
ID: FlowID(generate.UUID()),
|
||||
Handler: HandlerKey(hand),
|
||||
Schema: sch.FlowSchema(),
|
||||
|
||||
curStep: StepInit,
|
||||
ctime: time.Now(),
|
||||
|
@ -140,16 +119,39 @@ func resultErrs(e Errors) Errors {
|
|||
return e
|
||||
}
|
||||
|
||||
func (fm *FlowHandler) ShowForm(errs Errors) *Result {
|
||||
type FormOption func(*Result)
|
||||
|
||||
func (*FlowHandler) WithErrors(e Errors) FormOption {
|
||||
return func(r *Result) {
|
||||
r.Errors = e
|
||||
}
|
||||
}
|
||||
|
||||
func (*FlowHandler) WithStep(s Step) FormOption {
|
||||
return func(r *Result) {
|
||||
r.StepID = stepPtr(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (*FlowHandler) WithSchema(sch Schemer) FormOption {
|
||||
return func(r *Result) {
|
||||
r.Schema = sch.FlowSchema()
|
||||
}
|
||||
}
|
||||
|
||||
func (fm *FlowHandler) ShowForm(opts ...FormOption) *Result {
|
||||
res := &Result{
|
||||
Type: TypeForm,
|
||||
ID: fm.ID,
|
||||
StepID: stepPtr(fm.curStep),
|
||||
Schema: fm.Schema,
|
||||
Handler: fm.Handlers(),
|
||||
Errors: resultErrs(errs),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(res)
|
||||
}
|
||||
|
||||
res.Errors = resultErrs(res.Errors)
|
||||
return res
|
||||
}
|
||||
|
||||
|
|
40
pkg/flow/schema.go
Normal file
40
pkg/flow/schema.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package flow
|
||||
|
||||
type Type string
|
||||
|
||||
const (
|
||||
TypeString Type = "string"
|
||||
)
|
||||
|
||||
func (t Type) IsValid() bool {
|
||||
switch t {
|
||||
case TypeString:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type SchemaItem struct {
|
||||
Type Type `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Required bool `json:"required"`
|
||||
}
|
||||
|
||||
type Schema []SchemaItem
|
||||
|
||||
type Schemer interface {
|
||||
FlowSchema() Schema
|
||||
}
|
||||
|
||||
func NewSchema(items ...SchemaItem) Schema {
|
||||
return items
|
||||
}
|
||||
|
||||
func RequiredString(name string) SchemaItem {
|
||||
return SchemaItem{
|
||||
Type: TypeString,
|
||||
Name: name,
|
||||
Required: true,
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue