mirror of
https://github.com/teamhanko/hanko.git
synced 2025-10-29 23:59:46 +08:00
This pull request introduces the new Flowpilot system along with several new features and various improvements. The key enhancements include configurable authorization, registration, and profile flows, as well as the ability to enable and disable user identifiers (e.g., email addresses and usernames) and login methods. --------- Co-authored-by: Frederic Jahn <frederic.jahn@hanko.io> Co-authored-by: Lennart Fleischmann <lennart.fleischmann@hanko.io> Co-authored-by: lfleischmann <67686424+lfleischmann@users.noreply.github.com> Co-authored-by: merlindru <hello@merlindru.com>
296 lines
7.9 KiB
Go
296 lines
7.9 KiB
Go
package flowpilot
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"time"
|
|
)
|
|
|
|
// FlowName represents the name of the flow.
|
|
type FlowName string
|
|
|
|
// InputData holds input data in JSON format.
|
|
type InputData struct {
|
|
InputDataMap map[string]interface{} `json:"input_data"`
|
|
CSRFToken string `json:"csrf_token"`
|
|
}
|
|
|
|
// WithQueryParamKey sets the ActionName for flowExecutionOptions.
|
|
func WithQueryParamKey(key string) func(*defaultFlow) {
|
|
return func(f *defaultFlow) {
|
|
f.queryParamKey = key
|
|
}
|
|
}
|
|
|
|
// WithQueryParamValue sets the ActionName for flowExecutionOptions.
|
|
func WithQueryParamValue(value string) func(*defaultFlow) {
|
|
return func(f *defaultFlow) {
|
|
f.queryParamValue = value
|
|
}
|
|
}
|
|
|
|
// WithInputData sets the InputData for flowExecutionOptions.
|
|
func WithInputData(inputData InputData) func(*defaultFlow) {
|
|
return func(f *defaultFlow) {
|
|
f.inputData = inputData
|
|
}
|
|
}
|
|
|
|
// UseCompression causes the flow data to be compressed before stored to the db.
|
|
func UseCompression(b bool) func(*defaultFlow) {
|
|
return func(f *defaultFlow) {
|
|
f.useCompression = b
|
|
}
|
|
}
|
|
|
|
// StateName represents the name of a state in a flow.
|
|
type StateName string
|
|
|
|
// ActionName represents the name of an action.
|
|
type ActionName string
|
|
|
|
// Action defines the interface for flow actions.
|
|
type Action interface {
|
|
GetName() ActionName // Get the action name.
|
|
GetDescription() string // Get the action description.
|
|
Initialize(InitializationContext) // Initialize the action.
|
|
Execute(ExecutionContext) error // Execute the action.
|
|
}
|
|
|
|
// Actions represents a list of Action
|
|
type Actions []Action
|
|
|
|
// HookAction defines the interface for a hook action.
|
|
type HookAction interface {
|
|
Execute(HookExecutionContext) error
|
|
}
|
|
|
|
// hookActions represents a list of HookAction interfaces.
|
|
type hookActions []HookAction
|
|
|
|
func (actions hookActions) makeUnique() hookActions {
|
|
seen := make(map[HookAction]bool)
|
|
var uniqueSlice []HookAction
|
|
|
|
for _, action := range actions {
|
|
if _, found := seen[action]; !found {
|
|
seen[action] = true
|
|
uniqueSlice = append(uniqueSlice, action)
|
|
}
|
|
}
|
|
|
|
return uniqueSlice
|
|
}
|
|
|
|
func (actions hookActions) reverse() hookActions {
|
|
a := make(hookActions, len(actions))
|
|
copy(a, actions)
|
|
n := reflect.ValueOf(a).Len()
|
|
swap := reflect.Swapper(a)
|
|
for i, j := 0, n-1; i < j; i, j = i+1, j-1 {
|
|
swap(i, j)
|
|
}
|
|
return a
|
|
}
|
|
|
|
// stateActions maps state names to associated actions.
|
|
type stateActions map[StateName]Actions
|
|
|
|
// stateActions maps state names to associated hook actions.
|
|
type stateHooks map[StateName]hookActions
|
|
|
|
func (sh stateHooks) makeUnique() {
|
|
for stateName, actions := range sh {
|
|
sh[stateName] = actions.makeUnique()
|
|
}
|
|
}
|
|
|
|
// flowHooks maps state names to associated hook actions.
|
|
type flowHooks map[FlowName]hookActions
|
|
|
|
func (fh flowHooks) makeUnique() {
|
|
for stateName, actions := range fh {
|
|
fh[stateName] = actions.makeUnique()
|
|
}
|
|
}
|
|
|
|
// stateExists checks if a state exists in the flow.
|
|
func (st stateActions) stateExists(stateName StateName) bool {
|
|
_, ok := st[stateName]
|
|
return ok
|
|
}
|
|
|
|
// SubFlows represents a list of SubFlow interfaces.
|
|
type SubFlows []subFlow
|
|
|
|
// stateExists checks if the given state exists in a sub-flow of the current flow.
|
|
func (sfs SubFlows) stateExists(state StateName) bool {
|
|
for _, sf := range sfs {
|
|
if sf.getFlow().stateExists(state) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (sfs SubFlows) getSubFlowFromStateName(state StateName) subFlow {
|
|
for _, sf := range sfs {
|
|
if sf.getFlow().stateExists(state) {
|
|
return sf
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// flowBase represents the base of the flow interfaces.
|
|
type flowBase interface {
|
|
getName() FlowName
|
|
getSubFlows() SubFlows
|
|
getFlow() stateActions
|
|
getBeforeStateHooks() stateHooks
|
|
getAfterStateHooks() stateHooks
|
|
getAfterFlowHooks() hookActions
|
|
}
|
|
|
|
// Flow represents a flow.
|
|
type Flow interface {
|
|
// Execute executes the flow using the provided FlowDB and options.
|
|
// It returns the result of the flow execution and an error if any.
|
|
Execute(db FlowDB, opts ...func(*defaultFlow)) (FlowResult, error)
|
|
// ResultFromError converts an error into a FlowResult.
|
|
ResultFromError(err error) FlowResult
|
|
// Set sets a value with the given key in the flow context.
|
|
Set(string, interface{})
|
|
// setDefaults sets the default values for the flow.
|
|
setDefaults()
|
|
// getState retrieves the details of a specific state in the flow.
|
|
getState(stateName StateName) (stateDetail, error)
|
|
// Embed the flowBase interface.
|
|
flowBase
|
|
}
|
|
|
|
// subFlow represents a sub-flow.
|
|
type subFlow interface {
|
|
flowBase
|
|
}
|
|
|
|
type contextValues map[string]interface{}
|
|
|
|
type defaultFlowBase struct {
|
|
name FlowName
|
|
flow stateActions // StateName to Actions mapping.
|
|
subFlows SubFlows // The sub-flows of the current flow.
|
|
beforeStateHooks stateHooks // StateName to hookActions mapping.
|
|
afterStateHooks stateHooks // StateName to hookActions mapping.
|
|
beforeEachActionHooks hookActions // List of hookActions that run before each action.
|
|
afterEachActionHooks hookActions // List of hookActions that run after each action.
|
|
afterFlowHooks flowHooks
|
|
}
|
|
|
|
// defaultFlow defines a flow structure with states, actions, and settings.
|
|
type defaultFlow struct {
|
|
stateDetails stateDetails // Maps state names to flow details.
|
|
initialStateNames []StateName // A list of next states in case a sub-flow should be invoked initially.
|
|
errorStateName StateName // State representing errors.
|
|
ttl time.Duration // Time-to-live for the flow.
|
|
debug bool // Enables debug mode.
|
|
queryParam queryParam // TODO
|
|
contextValues contextValues // Values to be used within the flow context.
|
|
inputData InputData
|
|
useCompression bool
|
|
queryParamKey string
|
|
queryParamValue string
|
|
|
|
*defaultFlowBase
|
|
}
|
|
|
|
func (f *defaultFlow) Set(name string, value interface{}) {
|
|
f.contextValues[name] = value
|
|
}
|
|
|
|
// getActionsForState returns state details for the specified state.
|
|
func (f *defaultFlow) getState(stateName StateName) (stateDetail, error) {
|
|
if state, ok := f.stateDetails[stateName]; ok {
|
|
return state, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("unknown state: %s", stateName)
|
|
}
|
|
|
|
// getName returns the flow name.
|
|
func (f *defaultFlowBase) getName() FlowName {
|
|
return f.name
|
|
}
|
|
|
|
// getSubFlows returns the sub-flows of the current flow.
|
|
func (f *defaultFlowBase) getSubFlows() SubFlows {
|
|
return f.subFlows
|
|
}
|
|
|
|
// getFlow returns the state to action mapping of the current flow.
|
|
func (f *defaultFlowBase) getFlow() stateActions {
|
|
return f.flow
|
|
}
|
|
|
|
func (f *defaultFlowBase) getBeforeStateHooks() stateHooks {
|
|
return f.beforeStateHooks
|
|
}
|
|
|
|
func (f *defaultFlowBase) getAfterStateHooks() stateHooks {
|
|
return f.afterStateHooks
|
|
}
|
|
|
|
func (f *defaultFlowBase) getAfterFlowHooks() hookActions {
|
|
return f.afterFlowHooks[f.name]
|
|
}
|
|
|
|
// setDefaults sets default values for defaultFlow settings.
|
|
func (f *defaultFlow) setDefaults() {
|
|
if f.ttl.Seconds() == 0 {
|
|
f.ttl = time.Minute * 60
|
|
}
|
|
|
|
if len(f.queryParamKey) == 0 {
|
|
f.queryParamKey = "flowpilot_action"
|
|
}
|
|
}
|
|
|
|
// Execute handles the execution of actions for a defaultFlow.
|
|
func (f *defaultFlow) Execute(db FlowDB, opts ...func(flow *defaultFlow)) (FlowResult, error) {
|
|
for _, option := range opts {
|
|
option(f)
|
|
}
|
|
|
|
// Set default values for flow settings.
|
|
f.setDefaults()
|
|
|
|
// If the action is empty, create a new flow.
|
|
if len(f.queryParamValue) == 0 {
|
|
return createAndInitializeFlow(db, *f)
|
|
}
|
|
|
|
// Otherwise, update an existing flow...
|
|
q, err := newQueryParam(f.queryParamKey, f.queryParamValue)
|
|
if err != nil {
|
|
return newFlowResultFromError(f.errorStateName, ErrorTechnical.Wrap(err), f.debug), nil
|
|
}
|
|
|
|
f.queryParam = q
|
|
|
|
return executeFlowAction(db, *f)
|
|
}
|
|
|
|
// ResultFromError returns an error response for the defaultFlow.
|
|
func (f *defaultFlow) ResultFromError(err error) FlowResult {
|
|
flowError := ErrorTechnical
|
|
|
|
if e, ok := err.(FlowError); ok {
|
|
flowError = e
|
|
} else {
|
|
flowError = flowError.Wrap(err)
|
|
}
|
|
|
|
return newFlowResultFromError(f.errorStateName, flowError, f.debug)
|
|
}
|