Files
upstream-ory-kratos/ui/node/attributes_input.go
upstream-mirror c60cc7ca75
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:19:12 +05:00

252 lines
6.7 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package node
import (
"encoding/json"
"github.com/ory/kratos/text"
"github.com/ory/kratos/x"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/x/jsonschemax"
)
const DisableFormField = "disableFormField"
// maxEnumOptions caps how many `enum` values a single identity-schema
// property may surface as a selectable option on a UI node. The cap bounds
// the size of flow responses when an operator (accidentally or otherwise)
// declares a very large enum, so a single misconfigured schema cannot
// inflate every active registration/settings/login flow on a tenant.
const maxEnumOptions = 500
func toFormType(n string, i interface{}) UiNodeInputAttributeType {
switch n {
case nosurfx.CSRFTokenName:
return InputAttributeTypeHidden
case "password":
return InputAttributeTypePassword
}
switch i.(type) {
case float64, int64, int32, float32, json.Number:
return InputAttributeTypeNumber
case bool:
return InputAttributeTypeCheckbox
}
return InputAttributeTypeText
}
type (
InputAttributesModifier func(attributes *InputAttributes)
InputAttributesModifiers []InputAttributesModifier
)
func WithRequiredInputAttribute(a *InputAttributes) {
a.Required = true
}
func WithMaxLengthInputAttribute(maxLength int) func(a *InputAttributes) {
return func(a *InputAttributes) {
a.MaxLength = maxLength
}
}
func WithInputAttributes(f func(a *InputAttributes)) func(a *InputAttributes) {
return func(a *InputAttributes) {
f(a)
}
}
func applyInputAttributes(opts []InputAttributesModifier, attributes *InputAttributes) *InputAttributes {
for _, f := range opts {
f(attributes)
}
return attributes
}
type (
ImageAttributesModifier func(attributes *ImageAttributes)
ImageAttributesModifiers []ImageAttributesModifier
)
func WithImageAttributes(f func(a *ImageAttributes)) func(a *ImageAttributes) {
return func(a *ImageAttributes) {
f(a)
}
}
func applyImageAttributes(opts ImageAttributesModifiers, attributes *ImageAttributes) *ImageAttributes {
for _, f := range opts {
f(attributes)
}
return attributes
}
type (
ScriptAttributesModifier func(attributes *ScriptAttributes)
ScriptAttributesModifiers []ScriptAttributesModifier
)
func applyScriptAttributes(opts ScriptAttributesModifiers, attributes *ScriptAttributes) *ScriptAttributes {
for _, f := range opts {
f(attributes)
}
return attributes
}
type (
DivisionAttributesModifier func(attributes *DivisionAttributes)
DivisionAttributesModifiers []DivisionAttributesModifier
)
func WithDivisionAttributes(f func(a *DivisionAttributes)) func(a *DivisionAttributes) {
return func(a *DivisionAttributes) {
f(a)
}
}
func applyDivisionAttributes(opts DivisionAttributesModifiers, attributes *DivisionAttributes) *DivisionAttributes {
for _, f := range opts {
f(attributes)
}
return attributes
}
func NewInputFieldFromJSON(name string, value interface{}, group UiNodeGroup, opts ...InputAttributesModifier) *Node {
return &Node{
Type: Input,
Group: group,
Attributes: applyInputAttributes(opts, &InputAttributes{Name: name, Type: toFormType(name, value), FieldValue: value}),
Meta: &Meta{},
}
}
func NewInputField(name string, value interface{}, group UiNodeGroup, inputType UiNodeInputAttributeType, opts ...InputAttributesModifier) *Node {
return &Node{
Type: Input,
Group: group,
Attributes: applyInputAttributes(opts, &InputAttributes{Name: name, Type: inputType, FieldValue: value}),
Meta: &Meta{},
}
}
func NewImageField(id string, src string, group UiNodeGroup, opts ...ImageAttributesModifier) *Node {
return &Node{
Type: Image,
Group: group,
Attributes: applyImageAttributes(opts, &ImageAttributes{Source: src, Identifier: id}),
Meta: &Meta{},
}
}
func NewTextField(id string, text *text.Message, group UiNodeGroup) *Node {
return &Node{
Type: Text,
Group: group,
Attributes: &TextAttributes{Text: text, Identifier: id},
Meta: &Meta{},
}
}
func NewDivisionField(id string, group UiNodeGroup, opts ...DivisionAttributesModifier) *Node {
return &Node{
Type: Division,
Group: group,
Attributes: applyDivisionAttributes(opts, &DivisionAttributes{Identifier: id}),
Meta: &Meta{},
}
}
func NewAnchorField(id string, href string, group UiNodeGroup, title *text.Message) *Node {
return &Node{
Type: Anchor,
Group: group,
Attributes: &AnchorAttributes{Title: title, HREF: href, Identifier: id},
Meta: &Meta{},
}
}
func NewScriptField(name string, src string, group UiNodeGroup, integrity string, opts ...ScriptAttributesModifier) *Node {
return &Node{
Type: Script,
Group: group,
Attributes: applyScriptAttributes(opts, &ScriptAttributes{
Identifier: name,
Type: "text/javascript",
Source: src,
Async: true,
ReferrerPolicy: "no-referrer",
CrossOrigin: "anonymous",
Integrity: integrity,
Nonce: x.NewUUID().String(),
}),
Meta: &Meta{},
}
}
func NewInputFieldFromSchema(name string, group UiNodeGroup, p jsonschemax.Path, opts ...InputAttributesModifier) *Node {
attr := &InputAttributes{
Name: name,
Type: toFormType(p.Name, p.Type),
Required: p.Required,
}
// If format is set, we can make a more distinct decision:
switch p.Format {
case "date-time":
attr.Type = InputAttributeTypeDateTimeLocal
case "email":
attr.Type = InputAttributeTypeEmail
attr.Autocomplete = InputAttributeAutocompleteEmail
case "tel":
attr.Type = InputAttributeTypeTel
attr.Autocomplete = InputAttributeAutocompleteTel
case "date":
attr.Type = InputAttributeTypeDate
case "uri":
attr.Type = InputAttributeTypeURI
attr.Autocomplete = InputAttributeAutocompleteUrl
case "regex":
attr.Type = InputAttributeTypeText
}
// Enum values become selectable options. The presence of options is the
// signal for consumers to render a dropdown instead of a free-form input.
// Cap the number of options to keep a pathological schema from inflating
// every flow response.
if len(p.Enum) > 0 {
n := min(len(p.Enum), maxEnumOptions)
attr.Options = make([]InputAttributesOption, n)
for i, v := range p.Enum[:n] {
attr.Options[i] = InputAttributesOption{Value: v}
}
}
// Other properties
if p.Pattern != nil {
attr.Pattern = p.Pattern.String()
}
// Set disabled if the custom property is set
if isDisabled, ok := p.CustomProperties[DisableFormField]; ok {
if isDisabled, ok := isDisabled.(bool); ok {
attr.Disabled = isDisabled
}
}
var meta Meta
if len(p.Title) > 0 {
meta.Label = text.NewInfoNodeLabelGenerated(p.Title, name)
}
return &Node{
Type: Input,
Attributes: applyInputAttributes(opts, attr),
Group: group,
Meta: &meta,
}
}