Files
upstream-ory-kratos/selfservice/flow/state.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

129 lines
3.7 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package flow
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"strings"
"github.com/pkg/errors"
)
// Flow State
//
// The state represents the state of the verification flow.
//
// - choose_method: ask the user to choose a method (e.g. recover account via email)
// - sent_email: the email has been sent to the user
// - passed_challenge: the request was successful and the recovery challenge was passed.
// - show_form: a form is shown to the user to perform the flow
// - success: the flow has been completed successfully
//
// swagger:enum selfServiceFlowState
type State string
// #nosec G101 -- only a key constant
// Define the various states for all flows.
// Recovery flows for V2 have different states (see below).
const (
StateChooseMethod State = "choose_method"
// Note: this state should actually be called `StateMessageSent`,
// where a 'Message' is a code or link sent to an address (e.g. `email`, `sms`, etc).
StateEmailSent State = "sent_email"
StatePassedChallenge State = "passed_challenge"
StateShowForm State = "show_form"
StateSuccess State = "success"
// Recovery V2.
// The initial state is different from `StateChooseMethod` to distinguish recovery v1 vs v2.
// This avoids the issue of the feature flag being toggled while some recovery flows are on-going.
// This would lead to an inconsistent state machine/logic for these flows.
StateRecoveryAwaitingAddress State = "recovery_awaiting_address"
StateRecoveryAwaitingAddressChoice State = "recovery_awaiting_address_choice"
StateRecoveryAwaitingAddressConfirm State = "recovery_confirming_address"
StateRecoveryAwaitingCode State = "recovery_awaiting_code"
// The final success state is the same as in Recovery V1 (`passed_challenge`).
// Since this is the terminal state, it is not affected by toggling the feature flag.
// State machine diagrams:
// - ./state_recovery_v1.mermaid
// - ./state_recovery_v2.mermaid
)
var states = []State{
StateChooseMethod,
StateEmailSent,
StatePassedChallenge,
}
func indexOf(current State) int {
for k, s := range states {
if s == current {
return k
}
}
return 0
}
func HasReachedState(expected, actual State) bool {
return indexOf(actual) >= indexOf(expected)
}
func IsStateRecoveryV2(state State) bool {
return strings.HasPrefix(state.String(), "recovery_")
}
func NextState(current State) State {
if current == StatePassedChallenge {
return StatePassedChallenge
}
return states[indexOf(current)+1]
}
// For some reason using sqlxx.NullString as the State type does not work here.
// Reimplementing the Scanner interface on type State does work and allows
// the state to be NULL in the database.
// MarshalJSON returns m as the JSON encoding of m.
func (ns State) MarshalJSON() ([]byte, error) {
return json.Marshal(string(ns))
}
// UnmarshalJSON sets *m to a copy of data.
func (ns *State) UnmarshalJSON(data []byte) error {
if ns == nil {
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
}
if len(data) == 0 {
return nil
}
return errors.WithStack(json.Unmarshal(data, (*string)(ns)))
}
// Scan implements the Scanner interface.
func (ns *State) Scan(value interface{}) error {
var v sql.NullString
if err := (&v).Scan(value); err != nil {
return err
}
*ns = State(v.String)
return nil
}
// Value implements the driver Valuer interface.
func (ns State) Value() (driver.Value, error) {
if len(ns) == 0 {
return sql.NullString{}.Value()
}
return sql.NullString{Valid: true, String: string(ns)}.Value()
}
// String implements the Stringer interface.
func (ns State) String() string {
return string(ns)
}