snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
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

This commit is contained in:
upstream-mirror
2026-09-15 21:19:12 +05:00
commit c60cc7ca75
8092 changed files with 394160 additions and 0 deletions

19
text/context.go Normal file
View File

@@ -0,0 +1,19 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
import (
"encoding/json"
)
func context(ctx map[string]any) []byte {
if len(ctx) == 0 {
panic("context must not be empty")
}
res, err := json.Marshal(ctx)
if err != nil {
panic(err)
}
return res
}

233
text/id.go Normal file
View File

@@ -0,0 +1,233 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
// This file MUST not have any imports to modules that are not in the standard library.
// Otherwise, `make docs/cli` will fail.
type ID int
const (
InfoSelfServiceLoginRoot ID = 1010000 + iota // 1010000
InfoSelfServiceLogin // 1010001
InfoSelfServiceLoginWith // 1010002
InfoSelfServiceLoginReAuth // 1010003
InfoSelfServiceLoginMFA // 1010004
InfoSelfServiceLoginVerify // 1010005
InfoSelfServiceLoginTOTPLabel // 1010006
InfoLoginLookupLabel // 1010007
InfoSelfServiceLoginWebAuthn // 1010008
InfoLoginTOTP // 1010009
InfoLoginLookup // 1010010
InfoSelfServiceLoginContinueWebAuthn // 1010011
InfoSelfServiceLoginWebAuthnPasswordless // 1010012
InfoSelfServiceLoginContinue // 1010013
InfoSelfServiceLoginCodeSent // 1010014
InfoSelfServiceLoginCode // 1010015
InfoSelfServiceLoginLink // 1010016
InfoSelfServiceLoginAndLink // 1010017
InfoSelfServiceLoginWithAndLink // 1010018
InfoSelfServiceLoginCodeMFA // 1010019
InfoSelfServiceLoginCodeMFAHint // 1010020
InfoSelfServiceLoginPasskey // 1010021
InfoSelfServiceLoginPassword // 1010022
InfoSelfServiceLoginAAL2CodeAddress // 1010023
InfoSelfServiceLoginDeviceAuthn // 1010024
InfoSelfServiceLoginCodeSentForAuthenticatedUser // 1010025
)
const (
InfoSelfServiceLogout ID = 1020000 + iota
)
const (
InfoSelfServiceMFA ID = 1030000 + iota
)
const (
InfoSelfServiceRegistrationRoot ID = 1040000 + iota // 1040000
InfoSelfServiceRegistration // 1040001
InfoSelfServiceRegistrationWith // 1040002
InfoSelfServiceRegistrationContinue // 1040003
InfoSelfServiceRegistrationRegisterWebAuthn // 1040004
InfoSelfServiceRegistrationEmailWithCodeSent // 1040005
InfoSelfServiceRegistrationRegisterCode // 1040006
InfoSelfServiceRegistrationRegisterPasskey // 1040007
InfoSelfServiceRegistrationBack // 1040008
InfoSelfServiceRegistrationChooseCredentials // 1040009
)
const (
InfoSelfServiceSettings ID = 1050000 + iota
InfoSelfServiceSettingsUpdateSuccess
InfoSelfServiceSettingsUpdateLinkOidc
InfoSelfServiceSettingsUpdateUnlinkOidc
InfoSelfServiceSettingsUpdateUnlinkTOTP
InfoSelfServiceSettingsTOTPQRCode
InfoSelfServiceSettingsTOTPSecret
InfoSelfServiceSettingsRevealLookup
InfoSelfServiceSettingsRegenerateLookup
InfoSelfServiceSettingsLookupSecret
InfoSelfServiceSettingsLookupSecretLabel
InfoSelfServiceSettingsLookupConfirm
InfoSelfServiceSettingsRegisterWebAuthn
InfoSelfServiceSettingsRegisterWebAuthnDisplayName
InfoSelfServiceSettingsLookupSecretUsed
InfoSelfServiceSettingsLookupSecretList
InfoSelfServiceSettingsDisableLookup
InfoSelfServiceSettingsTOTPSecretLabel
InfoSelfServiceSettingsRemoveWebAuthn
InfoSelfServiceSettingsRegisterPasskey
InfoSelfServiceSettingsRemovePasskey
InfoSelfServiceSettingsRemoveDeviceAuthnKey
InfoSelfServiceSettingsDeviceAuthnNonce
InfoSelfServiceSettingsManagedByOrganization
)
const (
InfoSelfServiceRecovery ID = 1060000 + iota // 1060000
InfoSelfServiceRecoverySuccessful // 1060001
InfoSelfServiceRecoveryEmailSent // 1060002
InfoSelfServiceRecoveryEmailWithCodeSent // 1060003
InfoSelfServiceRecoveryMessageMaskedWithCodeSent // 1060004
InfoSelfServiceRecoveryAskForFullAddress // 1060005
InfoSelfServiceRecoveryAskToChooseAddress // 1060006
InfoSelfServiceRecoveryBack // 1060007
)
const (
InfoNodeLabel ID = 1070000 + iota // 1070000
InfoNodeLabelInputPassword // 1070001
InfoNodeLabelGenerated // 1070002
InfoNodeLabelSave // 1070003
InfoNodeLabelID // 1070004
InfoNodeLabelSubmit // 1070005
InfoNodeLabelVerifyOTP // 1070006
InfoNodeLabelEmail // 1070007
InfoNodeLabelResendOTP // 1070008
InfoNodeLabelContinue // 1070009
InfoNodeLabelRecoveryCode // 1070010
InfoNodeLabelVerificationCode // 1070011
InfoNodeLabelRegistrationCode // 1070012
InfoNodeLabelLoginCode // 1070013
InfoNodeLabelLoginAndLinkCredential // 1070014
InfoNodeLabelCaptcha // 1070015
InfoNodeLabelRecoveryAddress // 1070016
InfoNodeLabelPhoneNumber // 1070017
InfoNodeLabelEmailOrPhone // 1070018
)
const (
InfoSelfServiceVerification ID = 1080000 + iota // 1080000
InfoSelfServiceVerificationEmailSent // 1080001
InfoSelfServiceVerificationSuccessful // 1080002
InfoSelfServiceVerificationEmailWithCodeSent // 1080003
InfoSelfServiceVerificationPhoneWithCodeSent // 1080004
InfoSelfServiceVerificationPhoneSuccessful // 1080005
)
const (
ErrorValidation ID = 4000000 + iota
ErrorValidationGeneric
ErrorValidationRequired
ErrorValidationMinLength
ErrorValidationInvalidFormat
ErrorValidationPasswordPolicyViolationGeneric
ErrorValidationInvalidCredentials
ErrorValidationDuplicateCredentials
ErrorValidationTOTPVerifierWrong
ErrorValidationIdentifierMissing
ErrorValidationAddressNotVerified
ErrorValidationNoTOTPDevice
ErrorValidationLookupAlreadyUsed
ErrorValidationNoWebAuthnDevice
ErrorValidationNoLookup
ErrorValidationSuchNoWebAuthnUser
ErrorValidationLookupInvalid
ErrorValidationMaxLength
ErrorValidationMinimum
ErrorValidationExclusiveMinimum
ErrorValidationMaximum
ErrorValidationExclusiveMaximum
ErrorValidationMultipleOf
ErrorValidationMaxItems
ErrorValidationMinItems
ErrorValidationUniqueItems
ErrorValidationWrongType
ErrorValidationDuplicateCredentialsOnOIDCLink
ErrorValidationDuplicateCredentialsWithHints
ErrorValidationConst
ErrorValidationConstGeneric
ErrorValidationPasswordIdentifierTooSimilar
ErrorValidationPasswordMinLength
ErrorValidationPasswordMaxLength
ErrorValidationPasswordTooManyBreaches
ErrorValidationNoCodeUser
ErrorValidationTraitsMismatch
ErrorValidationAccountNotFound
ErrorValidationCaptchaError
ErrorValidationPasswordNewSameAsOld
ErrorValidationEmail
ErrorValidationPhone
ErrorValidationNoDeviceAuthnDevice
ErrorValidationWebAuthnVerifierWrong
ErrorValidationDeviceAuthnVerifierWrong
ErrorValidationDeviceAuthnRelaxedAttestationNoLongerValid
ErrorValidationDeviceAuthnKeyReenrollmentRequired
)
const (
ErrorValidationLogin ID = 4010000 + iota // 4010000
ErrorValidationLoginFlowExpired // 4010001
ErrorValidationLoginNoStrategyFound // 4010002
ErrorValidationRegistrationNoStrategyFound // 4010003
ErrorValidationSettingsNoStrategyFound // 4010004
ErrorValidationRecoveryNoStrategyFound // 4010005
ErrorValidationVerificationNoStrategyFound // 4010006
ErrorValidationLoginRetrySuccess // 4010007
ErrorValidationLoginCodeInvalidOrAlreadyUsed // 4010008
ErrorValidationLoginLinkedCredentialsDoNotMatch // 4010009
ErrorValidationLoginAddressUnknown // 4010010
ErrorValidationIdentityDisabled // 4010011
)
const (
ErrorValidationRegistration ID = 4040000 + iota
ErrorValidationRegistrationFlowExpired // 4040001
ErrorValidateionRegistrationRetrySuccess // 4040002
ErrorValidationRegistrationCodeInvalidOrAlreadyUsed // 4040003
)
const (
ErrorValidationSettings ID = 4050000 + iota
ErrorValidationSettingsFlowExpired // 4050001
ErrorValidationSettingsTooManyAddressChanges // 4050002
)
const (
ErrorValidationRecovery ID = 4060000 + iota // 4060000
ErrorValidationRecoveryRetrySuccess // 4060001
ErrorValidationRecoveryStateFailure // 4060002
ErrorValidationRecoveryMissingRecoveryToken // 4060003
ErrorValidationRecoveryTokenInvalidOrAlreadyUsed // 4060004
ErrorValidationRecoveryFlowExpired // 4060005
ErrorValidationRecoveryCodeInvalidOrAlreadyUsed // 4060006
)
const (
ErrorValidationVerification ID = 4070000 + iota // 4070000
ErrorValidationVerificationTokenInvalidOrAlreadyUsed // 4070001
ErrorValidationVerificationRetrySuccess // 4070002
ErrorValidationVerificationStateFailure // 4070003
ErrorValidationVerificationMissingVerificationToken // 4070004
ErrorValidationVerificationFlowExpired // 4070005
ErrorValidationVerificationCodeInvalidOrAlreadyUsed // 4070006
)
const (
ErrorSystem ID = 5000000 + iota // 5000000
ErrorSystemGeneric // 5000001
ErrorSystemNoAuthenticationMethodsAvailable // 5000002
ErrorSystemOrganizationNoSSOProvidersAvailable // 5000003
)

86
text/id_test.go Normal file
View File

@@ -0,0 +1,86 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIDs(t *testing.T) {
assert.Equal(t, 1010000, int(InfoSelfServiceLoginRoot))
assert.Equal(t, 1020000, int(InfoSelfServiceLogout))
assert.Equal(t, 1030000, int(InfoSelfServiceMFA))
assert.Equal(t, 1040000, int(InfoSelfServiceRegistrationRoot))
assert.Equal(t, 1040001, int(InfoSelfServiceRegistration))
assert.Equal(t, 1050000, int(InfoSelfServiceSettings))
assert.Equal(t, 1050001, int(InfoSelfServiceSettingsUpdateSuccess))
assert.Equal(t, 1060000, int(InfoSelfServiceRecovery))
assert.Equal(t, 1060001, int(InfoSelfServiceRecoverySuccessful))
assert.Equal(t, 1060002, int(InfoSelfServiceRecoveryEmailSent))
assert.Equal(t, 1060003, int(InfoSelfServiceRecoveryEmailWithCodeSent))
assert.Equal(t, 1070000, int(InfoNodeLabel))
assert.Equal(t, 1070001, int(InfoNodeLabelInputPassword))
assert.Equal(t, 1070002, int(InfoNodeLabelGenerated))
assert.Equal(t, 1070003, int(InfoNodeLabelSave))
assert.Equal(t, 1070004, int(InfoNodeLabelID))
assert.Equal(t, 1070005, int(InfoNodeLabelSubmit))
assert.Equal(t, 1070006, int(InfoNodeLabelVerifyOTP))
assert.Equal(t, 1070007, int(InfoNodeLabelEmail))
assert.Equal(t, 1070008, int(InfoNodeLabelResendOTP))
assert.Equal(t, 1070009, int(InfoNodeLabelContinue))
assert.Equal(t, 1070010, int(InfoNodeLabelRecoveryCode))
assert.Equal(t, 1070011, int(InfoNodeLabelVerificationCode))
assert.Equal(t, 1070018, int(InfoNodeLabelEmailOrPhone))
assert.Equal(t, 1080000, int(InfoSelfServiceVerification))
assert.Equal(t, 4000000, int(ErrorValidation))
assert.Equal(t, 4000001, int(ErrorValidationGeneric))
assert.Equal(t, 4000002, int(ErrorValidationRequired))
assert.Equal(t, 4010000, int(ErrorValidationLogin))
assert.Equal(t, 4010001, int(ErrorValidationLoginFlowExpired))
assert.Equal(t, 4040000, int(ErrorValidationRegistration))
assert.Equal(t, 4040001, int(ErrorValidationRegistrationFlowExpired))
assert.Equal(t, 4050000, int(ErrorValidationSettings))
assert.Equal(t, 4050001, int(ErrorValidationSettingsFlowExpired))
assert.Equal(t, 4060000, int(ErrorValidationRecovery))
assert.Equal(t, 4060001, int(ErrorValidationRecoveryRetrySuccess))
assert.Equal(t, 4060002, int(ErrorValidationRecoveryStateFailure))
assert.Equal(t, 4070000, int(ErrorValidationVerification))
assert.Equal(t, 4070001, int(ErrorValidationVerificationTokenInvalidOrAlreadyUsed))
assert.Equal(t, 5000000, int(ErrorSystem))
assert.Equal(t, 5000001, int(ErrorSystemGeneric))
assert.Equal(t, 5000002, int(ErrorSystemNoAuthenticationMethodsAvailable))
assert.Equal(t, 5000003, int(ErrorSystemOrganizationNoSSOProvidersAvailable))
assert.Equal(t, 4060006, int(ErrorValidationRecoveryCodeInvalidOrAlreadyUsed))
assert.Equal(t, 4070006, int(ErrorValidationVerificationCodeInvalidOrAlreadyUsed))
assert.Equal(t, 1080000, int(InfoSelfServiceVerification))
assert.Equal(t, 1080001, int(InfoSelfServiceVerificationEmailSent))
assert.Equal(t, 1080002, int(InfoSelfServiceVerificationSuccessful))
assert.Equal(t, 1080003, int(InfoSelfServiceVerificationEmailWithCodeSent))
assert.Equal(t, 1080004, int(InfoSelfServiceVerificationPhoneWithCodeSent))
assert.Equal(t, 1080005, int(InfoSelfServiceVerificationPhoneSuccessful))
assert.Equal(t, 1070015, int(InfoNodeLabelCaptcha))
assert.Equal(t, 4000038, int(ErrorValidationCaptchaError))
assert.Equal(t, 4000040, int(ErrorValidationEmail))
assert.Equal(t, 4000041, int(ErrorValidationPhone))
}

89
text/message.go Normal file
View File

@@ -0,0 +1,89 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
import (
"database/sql/driver"
"encoding/json"
"github.com/ory/x/sqlxx"
)
// swagger:model uiTexts
type Messages []Message
func (h Messages) MarshalJSON() ([]byte, error) {
if len(h) == 0 {
return []byte("[]"), nil
}
return json.Marshal([]Message(h))
}
func (h *Messages) Scan(value interface{}) error {
return sqlxx.JSONScan(h, value)
}
func (h Messages) Value() (driver.Value, error) {
if h == nil {
return []byte("[]"), nil
}
return json.Marshal(h)
}
func (h *Messages) Add(m *Message) Messages {
*h = append(*h, *m)
return *h
}
func (h *Messages) Set(m *Message) Messages {
*h = Messages{*m}
return *h
}
func (h *Messages) Clear() Messages {
*h = *new(Messages)
return *h
}
// swagger:model uiText
type Message struct {
// The message ID.
//
// required: true
ID ID `json:"id"`
// The message text. Written in american english.
//
// required: true
Text string `json:"text"`
// The message type.
//
// required: true
Type UITextType `json:"type"`
// The message's context. Useful when customizing messages.
Context json.RawMessage `json:"context,omitempty" faker:"-"`
}
func (m *Message) Scan(value interface{}) error {
return sqlxx.JSONScan(m, value)
}
func (m Message) Value() (driver.Value, error) {
return json.Marshal(&m)
}
func (m *Message) Error() string {
return m.Text
}
func (m *Message) Is(err error) bool {
em, ok := err.(*Message)
if !ok {
return false
}
return m.ID == em.ID
}

27
text/message_error.go Normal file
View File

@@ -0,0 +1,27 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
// This file contains error IDs for all system errors / JSON errors
const (
ErrIDNeedsPrivilegedSession = "session_refresh_required"
ErrIDSelfServiceFlowExpired = "self_service_flow_expired"
ErrIDSelfServiceFlowDisabled = "self_service_flow_disabled"
ErrIDSelfServiceBrowserLocationChangeRequiredError = "browser_location_change_required"
ErrIDSelfServiceFlowReplaced = "self_service_flow_replaced"
ErrIDAlreadyLoggedIn = "session_already_available"
ErrIDAddressNotVerified = "session_verified_address_required"
ErrIDSessionHasAALAlready = "session_aal_already_fulfilled"
ErrIDSessionRequiredForHigherAAL = "session_aal1_required"
ErrIDHigherAALRequired = "session_aal2_required"
ErrIDNoActiveSession = "session_inactive"
ErrIDRedirectURLNotAllowed = "self_service_flow_return_to_forbidden"
ErrIDInitiatedBySomeoneElse = "security_identity_mismatch"
ErrIDIdentityDisabled = "identity_disabled"
ErrIDCSRF = "security_csrf_violation"
)

324
text/message_login.go Normal file
View File

@@ -0,0 +1,324 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
import (
"fmt"
"time"
"github.com/ory/x/clock"
)
func NewInfoLoginReAuth() *Message {
return &Message{
ID: InfoSelfServiceLoginReAuth,
Type: Info,
Text: "Please confirm this action by verifying that it is you.",
}
}
func NewInfoLoginMFA() *Message {
return &Message{
ID: InfoSelfServiceLoginMFA,
Type: Info,
Text: "Please complete the second authentication challenge.",
}
}
func NewInfoLoginWebAuthnPasswordless() *Message {
return &Message{
ID: InfoSelfServiceLoginWebAuthnPasswordless,
Type: Info,
Text: "Prepare your WebAuthn device (e.g. security key, biometrics scanner, ...) and press continue.",
}
}
func NewInfoLoginTOTPLabel() *Message {
return &Message{
ID: InfoSelfServiceLoginTOTPLabel,
Type: Info,
Text: "Authentication code",
}
}
func NewInfoLoginLookupLabel() *Message {
return &Message{
ID: InfoLoginLookupLabel,
Type: Info,
Text: "Backup recovery code",
}
}
func NewInfoLogin() *Message {
return &Message{
ID: InfoSelfServiceLogin,
Text: "Sign in",
Type: Info,
}
}
func NewInfoLoginLinkMessage(dupIdentifier, provider, newLoginURL string, availableCredentials, availableProviders []string) *Message {
return &Message{
ID: InfoSelfServiceLoginLink,
Type: Info,
Text: fmt.Sprintf(
"You tried to sign in with %q, but that email is already used by another account. Sign in to your account with one of the options below to add your account %[1]q at %q as another way to sign in.",
dupIdentifier,
provider,
),
Context: context(map[string]any{
"duplicateIdentifier": dupIdentifier,
"provider": provider,
"newLoginUrl": newLoginURL,
"duplicate_identifier": dupIdentifier,
"new_login_url": newLoginURL,
"available_credential_types": availableCredentials,
"available_providers": availableProviders,
}),
}
}
func NewInfoLoginAndLink() *Message {
return &Message{
ID: InfoSelfServiceLoginAndLink,
Text: "Sign in and link",
Type: Info,
}
}
func NewInfoLoginTOTP() *Message {
return &Message{
ID: InfoLoginTOTP,
Text: "Use Authenticator",
Type: Info,
}
}
func NewInfoLoginPassword() *Message {
return &Message{
ID: InfoSelfServiceLoginPassword,
Text: "Sign in with password",
Type: Info,
}
}
func NewInfoLoginLookup() *Message {
return &Message{
ID: InfoLoginLookup,
Text: "Use backup recovery code",
Type: Info,
}
}
func NewInfoLoginVerify() *Message {
return &Message{
ID: InfoSelfServiceLoginVerify,
Text: "Verify",
Type: Info,
}
}
func NewInfoLoginWith(provider string, providerId string) *Message {
return &Message{
ID: InfoSelfServiceLoginWith,
Text: fmt.Sprintf("Sign in with %s", provider),
Type: Info,
Context: context(map[string]any{
"provider": provider,
"provider_id": providerId,
}),
}
}
func NewInfoLoginWithAndLink(provider string) *Message {
return &Message{
ID: InfoSelfServiceLoginWithAndLink,
Text: fmt.Sprintf("Confirm with %s", provider),
Type: Info,
Context: context(map[string]any{
"provider": provider,
}),
}
}
func NewErrorValidationLoginFlowExpired(c clock.Clock, expiredAt time.Time) *Message {
return &Message{
ID: ErrorValidationLoginFlowExpired,
Text: fmt.Sprintf("The login flow expired %.2f minutes ago, please try again.", c.Now().Sub(expiredAt).Minutes()),
Type: Error,
Context: context(map[string]any{
"expired_at": expiredAt,
"expired_at_unix": expiredAt.Unix(),
}),
}
}
func NewErrorValidationLoginNoStrategyFound() *Message {
return &Message{
ID: ErrorValidationLoginNoStrategyFound,
Text: "Could not find a strategy to log you in with. Did you fill out the form correctly?",
Type: Error,
}
}
func NewErrorValidationRegistrationNoStrategyFound() *Message {
return &Message{
ID: ErrorValidationRegistrationNoStrategyFound,
Text: "Could not find a strategy to sign you up with. Did you fill out the form correctly?",
Type: Error,
}
}
func NewErrorValidationSettingsNoStrategyFound() *Message {
return &Message{
ID: ErrorValidationSettingsNoStrategyFound,
Text: "Could not find a strategy to update your settings. Did you fill out the form correctly?",
Type: Error,
}
}
func NewErrorValidationRecoveryNoStrategyFound() *Message {
return &Message{
ID: ErrorValidationRecoveryNoStrategyFound,
Text: "Could not find a strategy to recover your account with. Did you fill out the form correctly?",
Type: Error,
}
}
func NewErrorValidationVerificationNoStrategyFound() *Message {
return &Message{
ID: ErrorValidationVerificationNoStrategyFound,
Text: "Could not find a strategy to verify your account with. Did you fill out the form correctly?",
Type: Error,
}
}
func NewInfoSelfServiceLoginWebAuthn() *Message {
return &Message{
ID: InfoSelfServiceLoginWebAuthn,
Text: "Sign in with a hardware key",
Type: Info,
}
}
func NewInfoSelfServiceLoginDeviceAuthn() *Message {
return &Message{
ID: InfoSelfServiceLoginDeviceAuthn,
Text: "Sign in with a hardware key",
Type: Info,
}
}
func NewInfoSelfServiceLoginPasskey() *Message {
return &Message{
ID: InfoSelfServiceLoginPasskey,
Text: "Sign in with passkey",
Type: Info,
}
}
func NewInfoSelfServiceContinueLoginWebAuthn() *Message {
return &Message{
ID: InfoSelfServiceLoginContinueWebAuthn,
Text: "Sign in with a hardware key",
Type: Info,
}
}
func NewInfoSelfServiceLoginContinue() *Message {
return &Message{
ID: InfoSelfServiceLoginContinue,
Text: "Continue",
Type: Info,
}
}
func NewLoginCodeSent() *Message {
return &Message{
ID: InfoSelfServiceLoginCodeSent,
Type: Info,
Text: "A code was sent to the address you provided. If you didn't receive it, please check the spelling of the address and try again.",
}
}
// NewLoginCodeSentForAuthenticatedUser is shown on refresh and second factor
// login flows where the address is bound to the authenticated identity rather
// than typed by the user. The wording avoids the inaccurate "address you
// provided" phrasing and the unhelpful "check the spelling" guidance, since
// the user cannot change the recipient mid-flow.
func NewLoginCodeSentForAuthenticatedUser() *Message {
return &Message{
ID: InfoSelfServiceLoginCodeSentForAuthenticatedUser,
Type: Info,
Text: "A code was sent to your address. If you didn't receive it, please try again.",
}
}
func NewErrorValidationLoginCodeInvalidOrAlreadyUsed() *Message {
return &Message{
ID: ErrorValidationLoginCodeInvalidOrAlreadyUsed,
Text: "The login code is invalid or has already been used. Please try again.",
Type: Error,
}
}
func NewErrorValidationLoginRetrySuccessful() *Message {
return &Message{
ID: ErrorValidationLoginRetrySuccess,
Type: Error,
Text: "The request was already completed successfully and can not be retried.",
}
}
func NewInfoSelfServiceLoginCode() *Message {
return &Message{
ID: InfoSelfServiceLoginCode,
Type: Info,
Text: "Send sign in code",
}
}
func NewErrorValidationLoginLinkedCredentialsDoNotMatch() *Message {
return &Message{
ID: ErrorValidationLoginLinkedCredentialsDoNotMatch,
Text: "Linked credentials do not match.",
Type: Error,
}
}
func NewErrorValidationAddressUnknown() *Message {
return &Message{
ID: ErrorValidationLoginAddressUnknown,
Text: "The address you entered does not match any known addresses in the current account.",
Type: Error,
}
}
func NewInfoSelfServiceLoginCodeMFA() *Message {
return &Message{
ID: InfoSelfServiceLoginCodeMFA,
Type: Info,
Text: "Request code to continue",
}
}
func NewInfoSelfServiceLoginAAL2CodeAddress(channel string, to string) *Message {
return &Message{
ID: InfoSelfServiceLoginAAL2CodeAddress,
Type: Info,
Text: fmt.Sprintf("Send code to %s", to),
Context: context(map[string]any{
"address": to,
"channel": channel,
}),
}
}
func NewErrorValidationIdentityDisabled() *Message {
return &Message{
ID: ErrorValidationIdentityDisabled,
Text: "This account has been disabled. Please contact support for assistance.",
Type: Error,
}
}

136
text/message_node.go Normal file
View File

@@ -0,0 +1,136 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
func NewInfoNodeLabelVerifyOTP() *Message {
return &Message{
ID: InfoNodeLabelVerifyOTP,
Text: "Verify code",
Type: Info,
}
}
func NewInfoNodeLabelVerificationCode() *Message {
return &Message{
ID: InfoNodeLabelVerificationCode,
Text: "Verification code",
Type: Info,
}
}
func NewInfoNodeLabelRecoveryCode() *Message {
return &Message{
ID: InfoNodeLabelRecoveryCode,
Text: "Recovery code",
Type: Info,
}
}
func NewInfoNodeLabelRegistrationCode() *Message {
return &Message{
ID: InfoNodeLabelRegistrationCode,
Text: "Registration code",
Type: Info,
}
}
func NewInfoNodeLabelLoginCode() *Message {
return &Message{
ID: InfoNodeLabelLoginCode,
Text: "Login code",
Type: Info,
}
}
func NewInfoNodeInputPassword() *Message {
return &Message{
ID: InfoNodeLabelInputPassword,
Text: "Password",
Type: Info,
}
}
func NewInfoNodeLabelGenerated(title string, name string) *Message {
return &Message{
ID: InfoNodeLabelGenerated,
Text: title,
Type: Info,
Context: context(map[string]any{
"title": title,
"name": name,
}),
}
}
func NewInfoNodeLabelSave() *Message {
return &Message{
ID: InfoNodeLabelSave,
Text: "Save",
Type: Info,
}
}
func NewInfoNodeLabelSubmit() *Message {
return &Message{
ID: InfoNodeLabelSubmit,
Text: "Submit",
Type: Info,
}
}
func NewInfoNodeLabelContinue() *Message {
return &Message{
ID: InfoNodeLabelContinue,
Text: "Continue",
Type: Info,
}
}
func NewInfoNodeLabelID() *Message {
return &Message{
ID: InfoNodeLabelID,
Text: "ID",
Type: Info,
}
}
func NewInfoNodeInputEmail() *Message {
return &Message{
ID: InfoNodeLabelEmail,
Text: "Email",
Type: Info,
}
}
func NewInfoNodeInputPhoneNumber() *Message {
return &Message{
ID: InfoNodeLabelPhoneNumber,
Text: "Phone number",
Type: Info,
}
}
func NewInfoNodeInputEmailOrPhone() *Message {
return &Message{
ID: InfoNodeLabelEmailOrPhone,
Text: "Email or phone number",
Type: Info,
}
}
func NewInfoNodeResendOTP() *Message {
return &Message{
ID: InfoNodeLabelResendOTP,
Text: "Resend code",
Type: Info,
}
}
func NewInfoNodeLoginAndLinkCredential() *Message {
return &Message{
ID: InfoNodeLabelLoginAndLinkCredential,
Text: "Login and link credential",
Type: Info,
}
}

128
text/message_recovery.go Normal file
View File

@@ -0,0 +1,128 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
import (
"fmt"
"time"
"github.com/ory/x/clock"
)
func NewErrorValidationRecoveryFlowExpired(c clock.Clock, expiredAt time.Time) *Message {
return &Message{
ID: ErrorValidationRecoveryFlowExpired,
Text: fmt.Sprintf("The recovery flow expired %.2f minutes ago, please try again.", c.Now().Sub(expiredAt).Minutes()),
Type: Error,
Context: context(map[string]any{
"expired_at": expiredAt,
"expired_at_unix": expiredAt.Unix(),
}),
}
}
func NewRecoverySuccessful(c clock.Clock, privilegedSessionExpiresAt time.Time) *Message {
hasLeft := privilegedSessionExpiresAt.Sub(c.Now())
return &Message{
ID: InfoSelfServiceRecoverySuccessful,
Type: Success,
Text: fmt.Sprintf("You successfully recovered your account. Please change your password or set up an alternative login method (e.g. social sign in) within the next %.2f minutes.", hasLeft.Minutes()),
Context: context(map[string]any{
"privilegedSessionExpiresAt": privilegedSessionExpiresAt,
"privileged_session_expires_at": privilegedSessionExpiresAt,
"privileged_session_expires_at_unix": privilegedSessionExpiresAt.Unix(),
}),
}
}
func NewRecoveryEmailSent() *Message {
return &Message{
ID: InfoSelfServiceRecoveryEmailSent,
Type: Info,
Text: "An email containing a recovery link has been sent to the email address you provided. If you have not received an email, check the spelling of the address and make sure to use the address you registered with.",
}
}
func NewRecoveryEmailWithCodeSent() *Message {
return &Message{
ID: InfoSelfServiceRecoveryEmailWithCodeSent,
Type: Info,
Text: "An email containing a recovery code has been sent to the email address you provided. If you have not received an email, check the spelling of the address and make sure to use the address you registered with.",
}
}
func NewRecoveryAskAnyRecoveryAddress() *Message {
return &Message{
ID: InfoNodeLabelRecoveryAddress,
Text: "Recovery address",
Type: Info,
}
}
func NewRecoveryCodeRecoverySelectAddressSent(maskedAddress string) *Message {
return &Message{
ID: InfoSelfServiceRecoveryMessageMaskedWithCodeSent,
Type: Info,
Text: fmt.Sprintf("A recovery code has been sent to %s. If you have not received it, check the spelling of the address and make sure to use the address you registered with.", maskedAddress),
Context: context(map[string]any{
"masked_address": maskedAddress,
}),
}
}
func NewRecoveryAskForFullAddress() *Message {
return &Message{
ID: InfoSelfServiceRecoveryAskForFullAddress,
Type: Info,
Text: "Recover access to your account by providing your recovery address in full.",
}
}
func NewRecoveryAskToChooseAddress() *Message {
return &Message{
ID: InfoSelfServiceRecoveryAskToChooseAddress,
Type: Info,
Text: "How do you want to recover your account?",
}
}
func NewRecoveryBack() *Message {
return &Message{
ID: InfoSelfServiceRecoveryBack,
Type: Info,
Text: "Back",
}
}
func NewErrorValidationRecoveryTokenInvalidOrAlreadyUsed() *Message {
return &Message{
ID: ErrorValidationRecoveryTokenInvalidOrAlreadyUsed,
Text: "The recovery token is invalid or has already been used. Please retry the flow.",
Type: Error,
}
}
func NewErrorValidationRecoveryCodeInvalidOrAlreadyUsed() *Message {
return &Message{
ID: ErrorValidationRecoveryCodeInvalidOrAlreadyUsed,
Text: "The recovery code is invalid or has already been used. Please try again.",
Type: Error,
}
}
func NewErrorValidationRecoveryRetrySuccess() *Message {
return &Message{
ID: ErrorValidationRecoveryRetrySuccess,
Text: "The request was already completed successfully and can not be retried.",
Type: Error,
}
}
func NewErrorValidationRecoveryStateFailure() *Message {
return &Message{
ID: ErrorValidationRecoveryStateFailure,
Text: "The recovery flow reached a failure state and must be retried.",
Type: Error,
}
}

View File

@@ -0,0 +1,115 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
import (
"fmt"
"time"
"github.com/ory/x/clock"
)
func NewInfoRegistration() *Message {
return &Message{
ID: InfoSelfServiceRegistration,
Text: "Sign up",
Type: Info,
}
}
func NewInfoRegistrationWith(provider string, providerID string) *Message {
return &Message{
ID: InfoSelfServiceRegistrationWith,
Text: fmt.Sprintf("Sign up with %s", provider),
Type: Info,
Context: context(map[string]any{
"provider": provider,
"provider_id": providerID,
}),
}
}
func NewInfoRegistrationContinue() *Message {
return &Message{
ID: InfoSelfServiceRegistrationContinue,
Text: "Continue",
Type: Info,
}
}
func NewInfoRegistrationBack() *Message {
return &Message{
ID: InfoSelfServiceRegistrationBack,
Text: "Back",
Type: Info,
}
}
func NewInfoSelfServiceChooseCredentials() *Message {
return &Message{
ID: InfoSelfServiceRegistrationChooseCredentials,
Text: "Please choose a credential to authenticate yourself with.",
Type: Info,
}
}
func NewErrorValidationRegistrationFlowExpired(c clock.Clock, expiredAt time.Time) *Message {
return &Message{
ID: ErrorValidationRegistrationFlowExpired,
Text: fmt.Sprintf("The registration flow expired %.2f minutes ago, please try again.", c.Now().Sub(expiredAt).Minutes()),
Type: Error,
Context: context(map[string]any{
"expired_at": expiredAt,
"expired_at_unix": expiredAt.Unix(),
}),
}
}
func NewInfoSelfServiceRegistrationRegisterWebAuthn() *Message {
return &Message{
ID: InfoSelfServiceRegistrationRegisterWebAuthn,
Text: "Sign up with security key",
Type: Info,
}
}
func NewInfoSelfServiceRegistrationRegisterPasskey() *Message {
return &Message{
ID: InfoSelfServiceRegistrationRegisterPasskey,
Text: "Sign up with passkey",
Type: Info,
}
}
func NewRegistrationEmailWithCodeSent() *Message {
return &Message{
ID: InfoSelfServiceRegistrationEmailWithCodeSent,
Type: Info,
Text: "A code has been sent to the address(es) you provided. If you have not received a message, check the spelling of the address and retry the registration.",
}
}
func NewErrorValidationRegistrationCodeInvalidOrAlreadyUsed() *Message {
return &Message{
ID: ErrorValidationRegistrationCodeInvalidOrAlreadyUsed,
Text: "The registration code is invalid or has already been used. Please try again.",
Type: Error,
}
}
func NewErrorValidationRegistrationRetrySuccessful() *Message {
return &Message{
ID: ErrorValidateionRegistrationRetrySuccess,
Type: Error,
Text: "The request was already completed successfully and can not be retried.",
}
}
func NewInfoSelfServiceRegistrationRegisterCode() *Message {
return &Message{
ID: InfoSelfServiceRegistrationRegisterCode,
Text: "Send sign up code",
Type: Info,
}
}

227
text/message_settings.go Normal file
View File

@@ -0,0 +1,227 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
import (
"fmt"
"strings"
"time"
"github.com/ory/x/clock"
)
func NewErrorValidationSettingsFlowExpired(c clock.Clock, expiredAt time.Time) *Message {
return &Message{
ID: ErrorValidationSettingsFlowExpired,
Text: fmt.Sprintf("The settings flow expired %.2f minutes ago, please try again.", c.Now().Sub(expiredAt).Minutes()),
Type: Error,
Context: context(map[string]any{
"expired_at": expiredAt,
"expired_at_unix": expiredAt.Unix(),
}),
}
}
func NewErrorValidationSettingsTooManyAddressChanges() *Message {
return &Message{
ID: ErrorValidationSettingsTooManyAddressChanges,
Text: "You can only change one address at a time. Please update each address separately.",
Type: Error,
}
}
func NewInfoSelfServiceSettingsTOTPQRCode() *Message {
return &Message{
ID: InfoSelfServiceSettingsTOTPQRCode,
Text: "Authenticator app QR code",
Type: Info,
}
}
func NewInfoSelfServiceSettingsTOTPSecret(secret string) *Message {
return &Message{
ID: InfoSelfServiceSettingsTOTPSecret,
Text: secret,
Type: Info,
Context: context(map[string]any{
"secret": secret,
}),
}
}
func NewInfoSelfServiceSettingsTOTPSecretLabel() *Message {
return &Message{
ID: InfoSelfServiceSettingsTOTPSecretLabel,
Text: "This is your authenticator app secret. Use it if you can not scan the QR code.",
Type: Info,
}
}
func NewInfoSelfServiceSettingsUpdateSuccess() *Message {
return &Message{
ID: InfoSelfServiceSettingsUpdateSuccess,
Text: "Your changes have been saved!",
Type: Success,
}
}
func NewInfoSelfServiceSettingsUpdateUnlinkTOTP() *Message {
return &Message{
ID: InfoSelfServiceSettingsUpdateUnlinkTOTP,
Text: "Unlink TOTP Authenticator App",
Type: Info,
}
}
func NewInfoSelfServiceSettingsRevealLookup() *Message {
return &Message{
ID: InfoSelfServiceSettingsRevealLookup,
Text: "Reveal backup recovery codes",
Type: Info,
}
}
func NewInfoSelfServiceSettingsRegenerateLookup() *Message {
return &Message{
ID: InfoSelfServiceSettingsRegenerateLookup,
Text: "Generate new backup recovery codes",
Type: Info,
}
}
func NewInfoSelfServiceSettingsDisableLookup() *Message {
return &Message{
ID: InfoSelfServiceSettingsDisableLookup,
Text: "Disable this method",
Type: Info,
}
}
func NewInfoSelfServiceSettingsLookupConfirm() *Message {
return &Message{
ID: InfoSelfServiceSettingsLookupConfirm,
Text: "Confirm backup recovery codes",
Type: Info,
}
}
func NewInfoSelfServiceSettingsLookupSecretList(secrets []string, raw any) *Message {
return &Message{
ID: InfoSelfServiceSettingsLookupSecretList,
Text: strings.Join(secrets, ", "),
Type: Info,
Context: context(map[string]any{
"secrets": raw,
}),
}
}
func NewInfoSelfServiceSettingsLookupSecret(secret string) *Message {
return &Message{
ID: InfoSelfServiceSettingsLookupSecret,
Text: secret,
Type: Info,
Context: context(map[string]any{
"secret": secret,
}),
}
}
func NewInfoSelfServiceSettingsLookupSecretUsed(usedAt time.Time) *Message {
return &Message{
ID: InfoSelfServiceSettingsLookupSecretUsed,
Text: fmt.Sprintf("Secret was used at %s", usedAt),
Type: Info,
Context: context(map[string]any{
"used_at": usedAt,
"used_at_unix": usedAt.Unix(),
}),
}
}
func NewInfoSelfServiceSettingsLookupSecretsLabel() *Message {
return &Message{
ID: InfoSelfServiceSettingsLookupSecretLabel,
Text: "These are your back up recovery codes. Please keep them in a safe place!",
Type: Info,
}
}
func NewInfoSelfServiceSettingsUpdateLinkOIDC(provider string) *Message {
return &Message{
ID: InfoSelfServiceSettingsUpdateLinkOidc,
Text: fmt.Sprintf("Link %s", provider),
Type: Info,
Context: context(map[string]any{
"provider": provider,
}),
}
}
func NewInfoSelfServiceSettingsUpdateUnlinkOIDC(provider string) *Message {
return &Message{
ID: InfoSelfServiceSettingsUpdateUnlinkOidc,
Text: fmt.Sprintf("Unlink %s", provider),
Type: Info,
Context: context(map[string]any{
"provider": provider,
}),
}
}
func NewInfoSelfServiceSettingsRegisterWebAuthn() *Message {
return &Message{
ID: InfoSelfServiceSettingsRegisterWebAuthn,
Text: "Add security key",
Type: Info,
}
}
func NewInfoSelfServiceSettingsRegisterPasskey() *Message {
return &Message{
ID: InfoSelfServiceSettingsRegisterPasskey,
Text: "Add passkey",
Type: Info,
}
}
func NewInfoSelfServiceRegisterWebAuthnDisplayName() *Message {
return &Message{
ID: InfoSelfServiceSettingsRegisterWebAuthnDisplayName,
Text: "Name of the security key",
Type: Info,
}
}
func NewInfoSelfServiceRemoveWebAuthn(name string, createdAt time.Time) *Message {
return &Message{
ID: InfoSelfServiceSettingsRemoveWebAuthn,
Text: fmt.Sprintf("Remove security key \"%s\"", name),
Type: Info,
Context: context(map[string]any{
"display_name": name,
"added_at": createdAt,
"added_at_unix": createdAt.Unix(),
}),
}
}
func NewInfoSelfServiceRemovePasskey(name string, createdAt time.Time) *Message {
return &Message{
ID: InfoSelfServiceSettingsRemovePasskey,
Text: fmt.Sprintf("Remove passkey \"%s\"", name),
Type: Info,
Context: context(map[string]any{
"display_name": name,
"added_at": createdAt,
"added_at_unix": createdAt.Unix(),
}),
}
}
func NewInfoSelfServiceSettingsManagedByOrganization() *Message {
return &Message{
ID: InfoSelfServiceSettingsManagedByOrganization,
Text: "Your account is managed by your organization. To change these settings, contact your organization administrator.",
Type: Info,
}
}

39
text/message_system.go Normal file
View File

@@ -0,0 +1,39 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
func NewErrorSystemGeneric(reason string) *Message {
return &Message{
ID: ErrorSystemGeneric,
Text: reason,
Type: Error,
Context: context(map[string]any{
"reason": reason,
}),
}
}
func NewErrorSystemNoAuthenticationMethodsAvailable() *Message {
return &Message{
ID: ErrorSystemNoAuthenticationMethodsAvailable,
Text: "No authentication methods are available. Please contact the system administrator.",
Type: Error,
}
}
func NewErrorSystemOrganizationNoSSOProvidersAvailable() *Message {
return &Message{
ID: ErrorSystemOrganizationNoSSOProvidersAvailable,
Text: "Your organization requires SSO authentication, but no SSO provider is configured. Please contact the system administrator.",
Type: Error,
}
}
func NewCaptchaContainerMessage() *Message {
return &Message{
ID: InfoNodeLabelCaptcha,
Text: "Please complete the captcha challenge to continue.",
Type: Info,
}
}

52
text/message_test.go Normal file
View File

@@ -0,0 +1,52 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMessage(t *testing.T) {
expected := &Message{ID: InfoSelfServiceSettingsUpdateSuccess, Text: "foo", Type: Info}
v, err := expected.Value()
require.NoError(t, err)
var actual Message
require.NoError(t, actual.Scan(v))
assert.EqualValues(t, expected, &actual, v)
}
func TestNewErrorValidationDuplicateCredentialsWithHints(t *testing.T) {
t.Run("title-cases real provider names", func(t *testing.T) {
msg := NewErrorValidationDuplicateCredentialsWithHints(nil, []string{"google", "github"}, "user@example.com")
assert.Contains(t, msg.Text, "Google, Github")
})
t.Run("preserves placeholder templates without title-casing", func(t *testing.T) {
msg := NewErrorValidationDuplicateCredentialsWithHints(
[]string{"{available_credential_types_list}"},
[]string{"{available_oidc_providers_list}"},
"{credential_identifier_hint}",
)
assert.Contains(t, msg.Text, "{available_oidc_providers_list}")
assert.NotContains(t, msg.Text, "{Available_oidc_providers_list}")
})
}
func TestMessages(t *testing.T) {
expected := Messages{{ID: InfoSelfServiceSettingsUpdateSuccess, Text: "foo", Type: Info}}
v, err := expected.Value()
require.NoError(t, err)
var actual Messages
require.NoError(t, actual.Scan(v))
assert.EqualValues(t, expected, actual, v)
}

511
text/message_validation.go Normal file
View File

@@ -0,0 +1,511 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
import (
"fmt"
"strings"
"github.com/ory/x/stringslice"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
func NewValidationErrorGeneric(reason string) *Message {
return &Message{
ID: ErrorValidationGeneric,
Text: reason,
Type: Error,
Context: context(map[string]any{
"reason": reason,
}),
}
}
func NewValidationErrorRequired(missing string) *Message {
return &Message{
ID: ErrorValidationRequired,
Text: fmt.Sprintf("Property %s is missing.", missing),
Type: Error,
Context: context(map[string]any{
"property": missing,
}),
}
}
func NewErrorValidationMinLength(minLength, actualLength int) *Message {
return &Message{
ID: ErrorValidationMinLength,
Text: fmt.Sprintf("length must be >= %d, but got %d", minLength, actualLength),
Type: Error,
Context: context(map[string]any{
"min_length": minLength,
"actual_length": actualLength,
}),
}
}
func NewErrorValidationMaxLength(maxLength, actualLength int) *Message {
return &Message{
ID: ErrorValidationMaxLength,
Text: fmt.Sprintf("length must be <= %d, but got %d", maxLength, actualLength),
Type: Error,
Context: context(map[string]any{
"max_length": maxLength,
"actual_length": actualLength,
}),
}
}
func NewErrorValidationInvalidFormat(pattern string) *Message {
return &Message{
ID: ErrorValidationInvalidFormat,
Text: fmt.Sprintf("does not match pattern %q", pattern),
Type: Error,
Context: context(map[string]any{
"pattern": pattern,
}),
}
}
func NewErrorValidationMinimum(minimum, actual float64) *Message {
return &Message{
ID: ErrorValidationMinimum,
Text: fmt.Sprintf("must be >= %v but found %v", minimum, actual),
Type: Error,
Context: context(map[string]any{
"minimum": minimum,
"actual": actual,
}),
}
}
func NewErrorValidationExclusiveMinimum(minimum, actual float64) *Message {
return &Message{
ID: ErrorValidationExclusiveMinimum,
Text: fmt.Sprintf("must be > %v but found %v", minimum, actual),
Type: Error,
Context: context(map[string]any{
"minimum": minimum,
"actual": actual,
}),
}
}
func NewErrorValidationMaximum(maximum, actual float64) *Message {
return &Message{
ID: ErrorValidationMaximum,
Text: fmt.Sprintf("must be <= %v but found %v", maximum, actual),
Type: Error,
Context: context(map[string]any{
"maximum": maximum,
"actual": actual,
}),
}
}
func NewErrorValidationExclusiveMaximum(maximum, actual float64) *Message {
return &Message{
ID: ErrorValidationExclusiveMaximum,
Text: fmt.Sprintf("must be < %v but found %v", maximum, actual),
Type: Error,
Context: context(map[string]any{
"maximum": maximum,
"actual": actual,
}),
}
}
func NewErrorValidationMultipleOf(base, actual float64) *Message {
return &Message{
ID: ErrorValidationMultipleOf,
Text: fmt.Sprintf("%v not multipleOf %v", actual, base),
Type: Error,
Context: context(map[string]any{
"base": base,
"actual": actual,
}),
}
}
func NewErrorValidationMaxItems(maxItems, actualItems int) *Message {
return &Message{
ID: ErrorValidationMaxItems,
Text: fmt.Sprintf("maximum %d items allowed, but found %d items", maxItems, actualItems),
Type: Error,
Context: context(map[string]any{
"max_items": maxItems,
"actual_items": actualItems,
}),
}
}
func NewErrorValidationMinItems(minItems, actualItems int) *Message {
return &Message{
ID: ErrorValidationMinItems,
Text: fmt.Sprintf("minimum %d items allowed, but found %d items", minItems, actualItems),
Type: Error,
Context: context(map[string]any{
"min_items": minItems,
"actual_items": actualItems,
}),
}
}
func NewErrorValidationUniqueItems(indexA, indexB int) *Message {
return &Message{
ID: ErrorValidationUniqueItems,
Text: fmt.Sprintf("items at index %d and %d are equal", indexA, indexB),
Type: Error,
Context: context(map[string]any{
"index_a": indexA,
"index_b": indexB,
}),
}
}
func NewErrorValidationWrongType(allowedTypes []string, actualType string) *Message {
return &Message{
ID: ErrorValidationWrongType,
Text: fmt.Sprintf("expected %s, but got %s", strings.Join(allowedTypes, " or "), actualType),
Type: Error,
Context: context(map[string]any{
"allowed_types": allowedTypes,
"actual_type": actualType,
}),
}
}
func NewErrorValidationConst(expected any) *Message {
return &Message{
ID: ErrorValidationConst,
Text: fmt.Sprintf("must be equal to constant %v", expected),
Type: Error,
Context: context(map[string]any{
"expected": expected,
}),
}
}
func NewErrorValidationConstGeneric() *Message {
return &Message{
ID: ErrorValidationConstGeneric,
Text: "const failed",
Type: Error,
}
}
func NewErrorValidationEmail(value string) *Message {
return &Message{
ID: ErrorValidationEmail,
Text: "Enter a valid email address",
Type: Error,
Context: context(map[string]any{
"value": value,
}),
}
}
func NewErrorValidationPhone(value string) *Message {
return &Message{
ID: ErrorValidationPhone,
Text: "Enter a valid phone number",
Type: Error,
Context: context(map[string]any{
"value": value,
}),
}
}
func NewErrorValidationPasswordPolicyViolationGeneric(reason string) *Message {
return &Message{
ID: ErrorValidationPasswordPolicyViolationGeneric,
Text: fmt.Sprintf("The password can not be used because %s.", reason),
Type: Error,
Context: context(map[string]any{
"reason": reason,
}),
}
}
func NewErrorValidationPasswordIdentifierTooSimilar() *Message {
return &Message{
ID: ErrorValidationPasswordIdentifierTooSimilar,
Text: "The password can not be used because it is too similar to the identifier.",
Type: Error,
}
}
func NewErrorValidationPasswordMinLength(minLength, actualLength int) *Message {
return &Message{
ID: ErrorValidationPasswordMinLength,
Text: fmt.Sprintf("The password must be at least %d characters long, but got %d.", minLength, actualLength),
Type: Error,
Context: context(map[string]any{
"min_length": minLength,
"actual_length": actualLength,
}),
}
}
func NewErrorValidationPasswordMaxLength(maxLength, actualLength int) *Message {
return &Message{
ID: ErrorValidationPasswordMaxLength,
Text: fmt.Sprintf("The password must be at most %d characters long, but got %d.", maxLength, actualLength),
Type: Error,
Context: context(map[string]any{
"max_length": maxLength,
"actual_length": actualLength,
}),
}
}
func NewErrorValidationPasswordNewSameAsOld() *Message {
return &Message{
ID: ErrorValidationPasswordNewSameAsOld,
Text: "The new password must be different from the old password.",
Type: Error,
}
}
func NewErrorValidationPasswordTooManyBreaches(breaches int64) *Message {
return &Message{
ID: ErrorValidationPasswordTooManyBreaches,
Text: "The password has been found in data breaches and must no longer be used.",
Type: Error,
Context: context(map[string]any{
"breaches": breaches,
}),
}
}
func NewErrorValidationInvalidCredentials() *Message {
return &Message{
ID: ErrorValidationInvalidCredentials,
Text: "The provided credentials are invalid, check for spelling mistakes in your password or username, email address, or phone number.",
Type: Error,
}
}
func NewErrorValidationAccountNotFound() *Message {
return &Message{
ID: ErrorValidationAccountNotFound,
Text: "This account does not exist or has no login method configured.",
Type: Error,
}
}
func NewErrorValidationDuplicateCredentials() *Message {
return &Message{
ID: ErrorValidationDuplicateCredentials,
Text: "An account with the same identifier (email, phone, username, ...) exists already.",
Type: Error,
}
}
func NewErrorValidationDuplicateCredentialsWithHints(availableCredentialTypes []string, availableOIDCProviders []string, credentialIdentifierHint string) *Message {
identifier := credentialIdentifierHint
if identifier == "" {
identifier = "an email, phone, or username"
}
oidcProviders := make([]string, 0, len(availableOIDCProviders))
for _, provider := range availableOIDCProviders {
if strings.ContainsAny(provider, "{}") {
oidcProviders = append(oidcProviders, provider)
} else {
oidcProviders = append(oidcProviders, cases.Title(language.English).String(provider))
}
}
reason := fmt.Sprintf("You tried signing in with %s which is already in use by another account.", identifier)
if len(availableCredentialTypes) > 0 {
humanReadable := make([]string, 0, len(availableCredentialTypes))
for _, cred := range availableCredentialTypes {
switch cred {
case "password":
humanReadable = append(humanReadable, "your password")
case "oidc", "saml":
humanReadable = append(humanReadable, "social sign in")
case "webauthn":
humanReadable = append(humanReadable, "your passkey or a security key")
case "passkey":
humanReadable = append(humanReadable, "your passkey")
}
}
if len(humanReadable) == 0 {
// show at least some hint
// also our example message generation tool runs into this case
humanReadable = append(humanReadable, availableCredentialTypes...)
}
humanReadable = stringslice.Unique(humanReadable)
// Final format: "You can sign in using foo, bar, or baz."
if len(humanReadable) > 1 {
humanReadable[len(humanReadable)-1] = "or " + humanReadable[len(humanReadable)-1]
}
if len(humanReadable) > 0 {
reason += fmt.Sprintf(" You can sign in using %s.", strings.Join(humanReadable, ", "))
}
}
if len(oidcProviders) > 0 {
reason += fmt.Sprintf(" You can sign in using one of the following social sign in providers: %s.", strings.Join(oidcProviders, ", "))
}
return &Message{
ID: ErrorValidationDuplicateCredentialsWithHints,
Text: reason,
Type: Error,
Context: context(map[string]any{
"available_credential_types": availableCredentialTypes,
"available_oidc_providers": availableOIDCProviders,
"credential_identifier_hint": credentialIdentifierHint,
}),
}
}
func NewErrorValidationDuplicateCredentialsOnOIDCLink() *Message {
return &Message{
ID: ErrorValidationDuplicateCredentialsOnOIDCLink,
Text: "An account with the same identifier (email, phone, username, ...) exists already. " +
"Please sign in to your existing account to link your social profile.",
Type: Error,
}
}
func NewErrorValidationTOTPVerifierWrong() *Message {
return &Message{
ID: ErrorValidationTOTPVerifierWrong,
Text: "The provided authentication code is invalid, please try again.",
Type: Error,
}
}
func NewErrorValidationWebAuthnVerifierWrong() *Message {
return &Message{
ID: ErrorValidationWebAuthnVerifierWrong,
Text: "The provided web authn login is invalid, please try again.",
Type: Error,
}
}
func NewErrorValidationDeviceAuthnVerifierWrong() *Message {
return &Message{
ID: ErrorValidationDeviceAuthnVerifierWrong,
Text: "The provided DeviceAuthn signature is invalid.",
Type: Error,
}
}
func NewErrorValidationDeviceAuthnRelaxedAttestationNoLongerValid() *Message {
return &Message{
ID: ErrorValidationDeviceAuthnRelaxedAttestationNoLongerValid,
Text: "This DeviceAuthn key can no longer be used because relaxed attestation is expired or disabled. Please enroll your device again.",
Type: Error,
}
}
func NewErrorValidationDeviceAuthnKeyReenrollmentRequired() *Message {
return &Message{
ID: ErrorValidationDeviceAuthnKeyReenrollmentRequired,
Text: "This DeviceAuthn key was enrolled before user verification was introduced and can no longer be used. Please enroll your device again.",
Type: Error,
}
}
func NewErrorValidationLookupAlreadyUsed() *Message {
return &Message{
ID: ErrorValidationLookupAlreadyUsed,
Text: "This backup recovery code has already been used.",
Type: Error,
}
}
func NewErrorValidationLookupInvalid() *Message {
return &Message{
ID: ErrorValidationLookupInvalid,
Text: "The backup recovery code is not valid.",
Type: Error,
}
}
func NewErrorValidationIdentifierMissing() *Message {
return &Message{
ID: ErrorValidationIdentifierMissing,
Text: "Could not find any login identifiers. Did you forget to set them? This could also be caused by a server misconfiguration.",
Type: Error,
}
}
func NewErrorValidationAddressNotVerified() *Message {
return &Message{
ID: ErrorValidationAddressNotVerified,
Text: "Account not active yet. Did you forget to verify your email address?",
Type: Error,
}
}
func NewErrorValidationNoTOTPDevice() *Message {
return &Message{
ID: ErrorValidationNoTOTPDevice,
Text: "You have no TOTP device set up.",
Type: Error,
}
}
func NewErrorValidationNoLookup() *Message {
return &Message{
ID: ErrorValidationNoLookup,
Text: "You have no backup recovery codes set up.",
Type: Error,
}
}
func NewErrorValidationNoWebAuthnDevice() *Message {
return &Message{
ID: ErrorValidationNoWebAuthnDevice,
Text: "You have no WebAuthn device set up.",
Type: Error,
}
}
func NewErrorValidationSuchNoWebAuthnUser() *Message {
return &Message{
ID: ErrorValidationSuchNoWebAuthnUser,
Text: "This account does not exist or has no security key set up.",
Type: Error,
}
}
func NewErrorValidationNoDeviceAuthnDevice() *Message {
return &Message{
ID: ErrorValidationNoDeviceAuthnDevice,
Text: "You have no DeviceAuthn device set up.",
Type: Error,
}
}
func NewErrorValidationNoCodeUser() *Message {
return &Message{
ID: ErrorValidationNoCodeUser,
Text: "This account does not exist or has not setup sign in with code.",
Type: Error,
}
}
func NewErrorValidationTraitsMismatch() *Message {
return &Message{
ID: ErrorValidationTraitsMismatch,
Text: "The provided traits do not match the traits previously associated with this flow.",
Type: Error,
}
}
func NewErrorCaptchaFailed() *Message {
return &Message{
ID: ErrorValidationCaptchaError,
Text: "Captcha verification failed, please try again.",
Type: Error,
}
}

View File

@@ -0,0 +1,105 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
import (
"fmt"
"time"
"github.com/ory/x/clock"
)
func NewErrorValidationVerificationFlowExpired(c clock.Clock, expiredAt time.Time) *Message {
return &Message{
ID: ErrorValidationVerificationFlowExpired,
Text: fmt.Sprintf("The verification flow expired %.2f minutes ago, please try again.", c.Now().Sub(expiredAt).Minutes()),
Type: Error,
Context: context(map[string]any{
"expired_at": expiredAt,
"expired_at_unix": expiredAt.Unix(),
}),
}
}
func NewInfoSelfServiceVerificationSuccessful() *Message {
return &Message{
ID: InfoSelfServiceVerificationSuccessful,
Type: Success,
Text: "You successfully verified your email address.",
}
}
func NewInfoSelfServiceVerificationPhoneSuccessful() *Message {
return &Message{
ID: InfoSelfServiceVerificationPhoneSuccessful,
Type: Success,
Text: "You successfully verified your phone number.",
}
}
func NewVerificationEmailSent() *Message {
return &Message{
ID: InfoSelfServiceVerificationEmailSent,
Type: Info,
Text: "An email containing a verification link has been sent to the email address you provided. If you have not received an email, check the spelling of the address and make sure to use the address you registered with.",
}
}
func NewErrorValidationVerificationTokenInvalidOrAlreadyUsed() *Message {
return &Message{
ID: ErrorValidationVerificationTokenInvalidOrAlreadyUsed,
Text: "The verification token is invalid or has already been used. Please retry the flow.",
Type: Error,
}
}
func NewErrorValidationVerificationRetrySuccess() *Message {
return &Message{
ID: ErrorValidationVerificationRetrySuccess,
Text: "The request was already completed successfully and can not be retried.",
Type: Error,
}
}
func NewErrorValidationVerificationStateFailure() *Message {
return &Message{
ID: ErrorValidationVerificationStateFailure,
Text: "The verification flow reached a failure state and must be retried.",
Type: Error,
}
}
func NewErrorValidationVerificationCodeInvalidOrAlreadyUsed() *Message {
return &Message{
ID: ErrorValidationVerificationCodeInvalidOrAlreadyUsed,
Text: "The verification code is invalid or has already been used. Please try again.",
Type: Error,
}
}
func NewVerificationEmailWithCodeSent() *Message {
return &Message{
ID: InfoSelfServiceVerificationEmailWithCodeSent,
Type: Info,
Text: "An email containing a verification code has been sent to the email address you provided. If you have not received an email, check the spelling of the address and make sure to use the address you registered with.",
}
}
func NewVerificationPhoneWithCodeSent() *Message {
return &Message{
ID: InfoSelfServiceVerificationPhoneWithCodeSent,
Type: Info,
Text: "A text message containing a verification code has been sent to the phone number you provided. If you have not received a text message, check the spelling of the number and make sure to use the number you registered with.",
}
}
// VerificationCodeSentMessage returns the "code has been sent" message for the
// channel the code was delivered over. SMS verifications get the phone wording;
// every other channel falls back to the email wording.
func VerificationCodeSentMessage(via string) *Message {
if via == "sms" {
return NewVerificationPhoneWithCodeSent()
}
return NewVerificationEmailWithCodeSent()
}

View File

@@ -0,0 +1,45 @@
// Copyright © 2026 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/ory/x/clock"
)
func TestNewErrorValidationVerificationFlowExpired(t *testing.T) {
t.Parallel()
expiredAt := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
// "Now" is exactly two minutes after the flow expired.
c := clock.NewMock(expiredAt.Add(2 * time.Minute))
msg := NewErrorValidationVerificationFlowExpired(c, expiredAt)
assert.Equal(t, ErrorValidationVerificationFlowExpired, msg.ID)
assert.Equal(t, "The verification flow expired 2.00 minutes ago, please try again.", msg.Text)
}
func TestVerificationCodeSentMessage(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
via string
wantID ID
}{
{via: "sms", wantID: InfoSelfServiceVerificationPhoneWithCodeSent},
{via: "email", wantID: InfoSelfServiceVerificationEmailWithCodeSent},
// Any unknown channel falls back to the email message.
{via: "", wantID: InfoSelfServiceVerificationEmailWithCodeSent},
} {
t.Run("via="+tc.via, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.wantID, VerificationCodeSentMessage(tc.via).ID)
})
}
}

14
text/type.go Normal file
View File

@@ -0,0 +1,14 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package text
// swagger:enum UITextType
type UITextType string
// aligned with https://github.com/ory/elements/blob/main/src/theme/message.css.ts
const (
Info UITextType = "info"
Error UITextType = "error"
Success UITextType = "success"
)