kratos/schema/extension.go

181 lines
4.2 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package schema
import (
"bytes"
"context"
"encoding/json"
"github.com/pkg/errors"
"github.com/ory/jsonschema/v3"
"github.com/ory/kratos/embedx"
"github.com/ory/x/jsonschemax"
)
const (
ExtensionName string = "ory.sh/kratos"
)
type (
ExtensionConfig struct {
Credentials struct {
Password struct {
Identifier bool `json:"identifier"`
} `json:"password"`
WebAuthn struct {
Identifier bool `json:"identifier"`
} `json:"webauthn"`
Passkey struct {
DisplayName bool `json:"display_name"`
} `json:"passkey"`
TOTP struct {
AccountName bool `json:"account_name"`
} `json:"totp"`
Code struct {
Identifier bool `json:"identifier"`
Via string `json:"via"`
} `json:"code"`
} `json:"credentials"`
Verification struct {
Via string `json:"via"`
} `json:"verification"`
Recovery struct {
Via string `json:"via"`
} `json:"recovery"`
Organization struct {
Matcher string `json:"matcher"`
} `json:"organizations"`
RawSchema map[string]interface{} `json:"-"`
}
ValidateExtension interface {
Run(ctx jsonschema.ValidationContext, config ExtensionConfig, value interface{}) error
Finish() error
}
CompileExtension interface {
Run(ctx jsonschema.CompilerContext, config ExtensionConfig, rawSchema map[string]interface{}) error
}
ExtensionRunner struct {
meta *jsonschema.Schema
compile func(ctx jsonschema.CompilerContext, rawSchema map[string]interface{}) (interface{}, error)
validate func(ctx jsonschema.ValidationContext, s interface{}, v interface{}) error
validateRunners []ValidateExtension
compileRunners []CompileExtension
}
ExtensionRunnerOption func(*ExtensionRunner)
)
func (e *ExtensionConfig) EnhancePath(path jsonschemax.Path) map[string]any {
props := path.CustomProperties
if props == nil {
props = make(map[string]any)
}
props[ExtensionName] = e
return props
}
func WithValidateRunners(runners ...ValidateExtension) ExtensionRunnerOption {
return func(r *ExtensionRunner) {
r.validateRunners = append(r.validateRunners, runners...)
}
}
func WithCompileRunners(runners ...CompileExtension) ExtensionRunnerOption {
return func(r *ExtensionRunner) {
r.compileRunners = append(r.compileRunners, runners...)
}
}
func NewExtensionRunner(ctx context.Context, opts ...ExtensionRunnerOption) (*ExtensionRunner, error) {
var err error
r := new(ExtensionRunner)
c := jsonschema.NewCompiler()
if err = embedx.AddSchemaResources(c, embedx.IdentityExtension); err != nil {
return nil, errors.WithStack(err)
}
r.meta, err = c.Compile(ctx, embedx.IdentityExtension.GetSchemaID())
if err != nil {
return nil, errors.WithStack(err)
}
r.compile = func(ctx jsonschema.CompilerContext, m map[string]interface{}) (interface{}, error) {
if raw, ok := m[ExtensionName]; ok {
var b bytes.Buffer
if err := json.NewEncoder(&b).Encode(raw); err != nil {
return nil, errors.WithStack(err)
}
var e ExtensionConfig
if err := json.NewDecoder(&b).Decode(&e); err != nil {
return nil, errors.WithStack(err)
}
for _, runner := range r.compileRunners {
if err := runner.Run(ctx, e, m); err != nil {
return nil, err
}
}
e.RawSchema = m
return &e, nil
}
return nil, nil
}
r.validate = func(ctx jsonschema.ValidationContext, s interface{}, v interface{}) error {
c, ok := s.(*ExtensionConfig)
if !ok {
return nil
}
for _, runner := range r.validateRunners {
if err := runner.Run(ctx, *c, v); err != nil {
return err
}
}
return nil
}
for _, opt := range opts {
opt(r)
}
return r, nil
}
func (r *ExtensionRunner) Register(compiler *jsonschema.Compiler) *ExtensionRunner {
compiler.Extensions[ExtensionName] = r.Extension()
return r
}
func (r *ExtensionRunner) Extension() jsonschema.Extension {
return jsonschema.Extension{
Meta: r.meta,
Compile: r.compile,
Validate: r.validate,
}
}
func (r *ExtensionRunner) AddRunner(run ValidateExtension) *ExtensionRunner {
r.validateRunners = append(r.validateRunners, run)
return r
}
func (r *ExtensionRunner) Finish() error {
for _, runner := range r.validateRunners {
if err := runner.Finish(); err != nil {
return err
}
}
return nil
}