blasphem/pkg/auth/flow.go
2022-11-20 08:49:24 -05:00

156 lines
3.4 KiB
Go

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)
}