package auth import ( "net/http" "strings" "github.com/jinzhu/copier" "github.com/labstack/echo/v4" "dynatron.me/x/blasphem/internal/common" "dynatron.me/x/blasphem/pkg/auth/provider" "dynatron.me/x/blasphem/pkg/flow" ) type AuthFlowManager struct { *flow.FlowManager } type LoginFlow struct { flow.FlowHandlerBase } func NewAuthFlowManager() *AuthFlowManager { return &AuthFlowManager{FlowManager: flow.NewFlowManager()} } func (afm *AuthFlowManager) NewLoginFlow(f *flow.FlowRequest, prov provider.AuthProvider) *LoginFlow { lf := &LoginFlow{ FlowHandlerBase: flow.NewFlowHandlerBase(f, prov, prov.ProviderType()), } afm.Register(lf) return lf } func (a *Authenticator) NewFlow(r *flow.FlowRequest) *flow.FlowResult { var prov provider.AuthProvider for _, h := range r.Handler { if h == nil { break } prov = a.Provider(h.String()) if prov != nil { break } } if prov == nil { return nil } flow := a.flows.NewLoginFlow(r, prov) return flow.ShowForm(nil) } func (f *LoginFlow) redirect(c echo.Context) { c.Request().Header.Set("Location", f.RedirectURI) } func (f *LoginFlow) progress(a *Authenticator, c echo.Context) error { switch f.Step() { case flow.StepInit: rm := make(map[string]interface{}) err := c.Bind(&rm) if err != nil { return c.String(http.StatusBadRequest, err.Error()) } err = f.Schema.CheckRequired(rm) if err != nil { return c.JSON(http.StatusBadRequest, f.ShowForm([]string{err.Error()})) } user, err := a.Check(f, c.Request(), rm) switch err { case nil: finishedFlow := flow.FlowResult{} a.flows.Remove(f) copier.Copy(&finishedFlow, f) finishedFlow.Type = flow.TypeCreateEntry finishedFlow.Title = common.AppNamePtr() finishedFlow.Version = common.IntPtr(1) finishedFlow.Result = a.NewAccessToken(c.Request(), user) f.redirect(c) return c.JSON(http.StatusCreated, &finishedFlow) case ErrInvalidHandler: return c.String(http.StatusNotFound, err.Error()) case ErrInvalidAuth: fallthrough default: fr := f.ShowForm(map[string]interface{}{ "base": "invalid_auth", }) return c.JSON(http.StatusOK, fr) } default: return c.String(http.StatusBadRequest, "unknown flow step") } } func (a *Authenticator) LoginFlowDeleteHandler(c echo.Context) error { flowID := flow.FlowID(c.Param("flow_id")) if flowID == "" { return c.String(http.StatusBadRequest, "empty flow ID") } a.flows.Delete(flowID) return c.String(http.StatusOK, "deleted") } func setJSON(c echo.Context) { if c.Request().Method == http.MethodPost && strings.HasPrefix(c.Request().Header.Get(echo.HeaderContentType), "text/plain") { // hack around the content-type, Context.JSON refuses to work otherwise c.Request().Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) } } func (a *Authenticator) BeginLoginFlowHandler(c echo.Context) error { setJSON(c) var flowReq flow.FlowRequest err := c.Bind(&flowReq) if err != nil { return c.String(http.StatusBadRequest, err.Error()) } resp := a.NewFlow(&flowReq) if resp == nil { return c.String(http.StatusBadRequest, "no such handler") } return c.JSON(http.StatusOK, resp) } func (a *Authenticator) LoginFlowHandler(c echo.Context) error { setJSON(c) flowID := c.Param("flow_id") flow := a.flows.Get(flow.FlowID(flowID)) if flow == nil { return c.String(http.StatusNotFound, "no such flow") } return flow.(*LoginFlow).progress(a, c) }