Files
bjoern-m 601ffaae92 Introduce Flowpilot - integration (#1532)
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>
2024-08-06 16:07:29 +02:00

283 lines
6.8 KiB
Go

package flowpilot
import (
"regexp"
)
// inputType represents the type of the input field.
type inputType string
// Input types enumeration.
const (
inputTypeString inputType = "string"
inputTypeBoolean inputType = "boolean"
inputTypeEmail inputType = "email"
inputTypeNumber inputType = "number"
inputTypePassword inputType = "password"
inputTypeJSON inputType = "json"
)
// Input defines the interface for input fields.
type Input interface {
MinLength(minLength int) Input
MaxLength(maxLength int) Input
Required(b bool) Input
Hidden(b bool) Input
Preserve(b bool) Input
AllowedValue(name string, value interface{}) Input
TrimSpace(b bool) Input
LowerCase(b bool) Input
setValue(value interface{}) Input
setError(inputError InputError)
getError() InputError
getName() string
shouldPreserve() bool
shouldTrimSpace() bool
shouldConvertToLowerCase() bool
validate(inputData readOnlyActionInput) bool
toResponseInput() *ResponseInput
}
// defaultExtraInputOptions holds additional input field options.
type defaultExtraInputOptions struct {
preserveValue bool
trimSpace bool
lowerCase bool
}
// defaultInput represents an input field with its options.
type defaultInput struct {
name string
dataType inputType
value interface{}
minLength *int
maxLength *int
required *bool
hidden *bool
error InputError
allowedValues allowedValues
defaultExtraInputOptions
}
func (i *defaultInput) AllowedValue(name string, value interface{}) Input {
i.allowedValues.add(&defaultAllowedValue{
value: value,
text: name,
})
return i
}
// newInput creates a new input instance with provided parameters.
func newInput(name string, inputType inputType) Input {
extraOptions := defaultExtraInputOptions{
preserveValue: false,
trimSpace: false,
lowerCase: false,
}
return &defaultInput{
name: name,
dataType: inputType,
defaultExtraInputOptions: extraOptions,
allowedValues: &defaultAllowedValues{},
}
}
// StringInput creates a new input field of string type.
func StringInput(name string) Input {
return newInput(name, inputTypeString)
}
// EmailInput creates a new input field of email type.
func EmailInput(name string) Input {
return newInput(name, inputTypeEmail)
}
// NumberInput creates a new input field of number type.
func NumberInput(name string) Input {
return newInput(name, inputTypeNumber)
}
// BooleanInput creates a new input field of boolean type.
func BooleanInput(name string) Input {
return newInput(name, inputTypeBoolean)
}
// PasswordInput creates a new input field of password type.
func PasswordInput(name string) Input {
return newInput(name, inputTypePassword)
}
// JSONInput creates a new input field of JSON type.
func JSONInput(name string) Input {
return newInput(name, inputTypeJSON)
}
// MinLength sets the minimum length for the input field.
func (i *defaultInput) MinLength(minLength int) Input {
i.minLength = &minLength
return i
}
// MaxLength sets the maximum length for the input field.
func (i *defaultInput) MaxLength(maxLength int) Input {
i.maxLength = &maxLength
return i
}
// Required sets whether the input field is required.
func (i *defaultInput) Required(b bool) Input {
i.required = &b
return i
}
// Hidden sets whether the input field is hidden.
func (i *defaultInput) Hidden(b bool) Input {
i.hidden = &b
return i
}
// Preserve sets whether the input field value should be preserved, so that the value is included in the response
// instead of being blanked out.
func (i *defaultInput) Preserve(b bool) Input {
i.preserveValue = b
return i
}
// TrimSpace sets whether the leading and trailing whitespaces should be trimmed.
func (i *defaultInput) TrimSpace(b bool) Input {
i.trimSpace = b
return i
}
// LowerCase sets whether the value should be converted to lower case.
func (i *defaultInput) LowerCase(b bool) Input {
i.lowerCase = b
return i
}
// setValue sets the value for the input field for the current response.
func (i *defaultInput) setValue(value interface{}) Input {
i.value = &value
return i
}
// getName returns the name of the input field.
func (i *defaultInput) getName() string {
return i.name
}
// setError sets an error to the given input field.
func (i *defaultInput) setError(inputError InputError) {
i.error = inputError
}
// getError returns the input error.
func (i *defaultInput) getError() InputError {
return i.error
}
// shouldPreserve indicates the value should be preserved.
func (i *defaultInput) shouldPreserve() bool {
return i.preserveValue
}
// shouldTrimSpace indicates the value should be trimmed.
func (i *defaultInput) shouldTrimSpace() bool {
return i.trimSpace
}
// shouldConvertToLowerCase indicates the value should be converted to lower case.
func (i *defaultInput) shouldConvertToLowerCase() bool {
return i.lowerCase
}
// validate performs validation on the input field.
func (i *defaultInput) validate(inputData readOnlyActionInput) bool {
// TODO: Replace with more structured validation logic.
var inputValue *string
if v := inputData.Get(i.name); v.Exists() {
inputValue = &v.Str
}
if i.dataType == inputTypeJSON {
// skip further validation
return true
}
if i.dataType == inputTypeBoolean {
return true
}
isRequired := i.required != nil && *i.required
hasEmptyOrNilValue := inputValue == nil || len(*inputValue) <= 0
if isRequired && hasEmptyOrNilValue {
i.error = ErrorValueMissing
return false
}
if !hasEmptyOrNilValue && i.minLength != nil {
if len(*inputValue) < *i.minLength {
i.error = ErrorValueTooShort
return false
}
}
if !hasEmptyOrNilValue && i.maxLength != nil {
if len(*inputValue) > *i.maxLength {
i.error = ErrorValueTooLong
return false
}
}
if i.dataType == inputTypeEmail && (isRequired || (!isRequired && !hasEmptyOrNilValue)) {
pattern := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
if matched := pattern.MatchString(*inputValue); !matched {
i.error = ErrorValueInvalid
return false
}
}
if i.dataType == inputTypeString && inputValue != nil {
if i.allowedValues.isAllowed(*inputValue) {
return true
}
i.error = createMustBeOneOfError(i.allowedValues.getValues())
return false
}
return true
}
// toResponseInput converts the defaultInput to a ResponseInput for public exposure.
func (i *defaultInput) toResponseInput() *ResponseInput {
var e *ResponseError
var av *ResponseAllowedValues
if i.error != nil {
e = i.error.toResponseError(true)
}
if i.allowedValues != nil && i.allowedValues.hasAny() {
av = i.allowedValues.toResponseAllowedValues()
}
return &ResponseInput{
Name: i.name,
Type: i.dataType,
Value: i.value,
MinLength: i.minLength,
MaxLength: i.maxLength,
Required: i.required,
Hidden: i.hidden,
Error: e,
AllowedValues: av,
}
}