diff --git a/driver/config/config.go b/driver/config/config.go index b1e16e393f13..0700eb8a780d 100644 --- a/driver/config/config.go +++ b/driver/config/config.go @@ -184,6 +184,7 @@ const ( ViperKeyLinkLifespan = "selfservice.methods.link.config.lifespan" ViperKeyLinkBaseURL = "selfservice.methods.link.config.base_url" ViperKeyCodeLifespan = "selfservice.methods.code.config.lifespan" + ViperKeyCodeShortLegacyCode = "selfservice.methods.code.config.legacy_short_code" ViperKeyCodeConfigMissingCredentialFallbackEnabled = "selfservice.methods.code.config.missing_credential_fallback_enabled" ViperKeyPasswordHaveIBeenPwnedHost = "selfservice.methods.password.config.haveibeenpwned_host" ViperKeyPasswordHaveIBeenPwnedEnabled = "selfservice.methods.password.config.haveibeenpwned_enabled" @@ -530,12 +531,12 @@ func (p *Config) cors(ctx context.Context, prefix string) (cors.Options, bool) { }) } -// Deprecated: use context-based WithConfigValue instead +// Deprecated: use context-based confighelpers.WithConfigValue instead func (p *Config) Set(_ context.Context, key string, value interface{}) error { return p.p.Set(key, value) } -// Deprecated: use context-based WithConfigValue instead +// Deprecated: use context-based confighelpers.WithConfigValue instead func (p *Config) MustSet(_ context.Context, key string, value interface{}) { if err := p.p.Set(key, value); err != nil { p.l.WithError(err).Fatalf("Unable to set \"%s\" to \"%s\".", key, value) @@ -1347,6 +1348,10 @@ func (p *Config) SelfServiceCodeMethodLifespan(ctx context.Context) time.Duratio return p.GetProvider(ctx).DurationF(ViperKeyCodeLifespan, time.Hour) } +func (p *Config) SelfServiceCodeMethodCodeShortLegacyCode(ctx context.Context) bool { + return p.GetProvider(ctx).BoolF(ViperKeyCodeShortLegacyCode, false) +} + func (p *Config) SelfServiceCodeMethodMissingCredentialFallbackEnabled(ctx context.Context) bool { return p.GetProvider(ctx).Bool(ViperKeyCodeConfigMissingCredentialFallbackEnabled) } diff --git a/embedx/config.schema.json b/embedx/config.schema.json index 5fcf826f4c2a..284fbe752e68 100644 --- a/embedx/config.schema.json +++ b/embedx/config.schema.json @@ -43,7 +43,10 @@ "description": "Ory Kratos redirects to this URL per default on completion of self-service flows and other browser interaction. Read this [article for more information on browser redirects](https://www.ory.sh/kratos/docs/concepts/browser-redirect-flow-completion).", "type": "string", "format": "uri-reference", - "examples": ["https://my-app.com/dashboard", "/dashboard"] + "examples": [ + "https://my-app.com/dashboard", + "/dashboard" + ] }, "selfServiceSessionRevokerHook": { "type": "object", @@ -53,7 +56,9 @@ } }, "additionalProperties": false, - "required": ["hook"] + "required": [ + "hook" + ] }, "selfServiceSessionIssuerHook": { "type": "object", @@ -63,7 +68,9 @@ } }, "additionalProperties": false, - "required": ["hook"] + "required": [ + "hook" + ] }, "selfServiceRequireVerifiedAddressHook": { "type": "object", @@ -73,7 +80,9 @@ } }, "additionalProperties": false, - "required": ["hook"] + "required": [ + "hook" + ] }, "selfServiceVerificationHook": { "type": "object", @@ -83,7 +92,9 @@ } }, "additionalProperties": false, - "required": ["hook"] + "required": [ + "hook" + ] }, "selfServiceShowVerificationUIHook": { "type": "object", @@ -93,7 +104,9 @@ } }, "additionalProperties": false, - "required": ["hook"] + "required": [ + "hook" + ] }, "b2bSSOHook": { "type": "object", @@ -107,7 +120,10 @@ } }, "additionalProperties": false, - "required": ["hook", "config"] + "required": [ + "hook", + "config" + ] }, "webHookAuthBasicAuthProperties": { "properties": { @@ -127,11 +143,17 @@ } }, "additionalProperties": false, - "required": ["user", "password"] + "required": [ + "user", + "password" + ] } }, "additionalProperties": false, - "required": ["type", "config"] + "required": [ + "type", + "config" + ] }, "httpRequestConfig": { "type": "object", @@ -139,7 +161,9 @@ "url": { "title": "HTTP address of API endpoint", "description": "This URL will be used to send the emails to.", - "examples": ["https://example.com/api/v1/email"], + "examples": [ + "https://example.com/api/v1/email" + ], "type": "string", "pattern": "^https?://" }, @@ -204,15 +228,25 @@ "in": { "type": "string", "description": "How the api key should be transferred", - "enum": ["header", "cookie"] + "enum": [ + "header", + "cookie" + ] } }, "additionalProperties": false, - "required": ["name", "value", "in"] + "required": [ + "name", + "value", + "in" + ] } }, "additionalProperties": false, - "required": ["type", "config"] + "required": [ + "type", + "config" + ] }, "selfServiceWebHook": { "type": "object", @@ -255,7 +289,10 @@ "const": true } }, - "required": ["ignore", "parse"] + "required": [ + "ignore", + "parse" + ] } }, "url": { @@ -328,30 +365,46 @@ "response": { "properties": { "ignore": { - "enum": [true] + "enum": [ + true + ] } }, - "required": ["ignore"] + "required": [ + "ignore" + ] } }, - "required": ["response"] + "required": [ + "response" + ] } }, { "properties": { "can_interrupt": { - "enum": [false] + "enum": [ + false + ] } }, - "require": ["can_interrupt"] + "require": [ + "can_interrupt" + ] } ], "additionalProperties": false, - "required": ["url", "method"] + "required": [ + "url", + "method" + ] } }, "additionalProperties": false, - "required": ["hook", "config"] + "required": [ + "hook", + "config" + ] }, "OIDCClaims": { "title": "OpenID Connect claims", @@ -384,7 +437,9 @@ "essential": true }, "acr": { - "values": ["urn:mace:incommon:iap:silver"] + "values": [ + "urn:mace:incommon:iap:silver" + ] } } } @@ -432,7 +487,9 @@ "properties": { "id": { "type": "string", - "examples": ["google"] + "examples": [ + "google" + ] }, "provider": { "title": "Provider", @@ -462,7 +519,9 @@ "lark", "x" ], - "examples": ["google"] + "examples": [ + "google" + ] }, "label": { "title": "Optional string which will be used when generating labels for UI buttons.", @@ -477,17 +536,23 @@ "issuer_url": { "type": "string", "format": "uri", - "examples": ["https://accounts.google.com"] + "examples": [ + "https://accounts.google.com" + ] }, "auth_url": { "type": "string", "format": "uri", - "examples": ["https://accounts.google.com/o/oauth2/v2/auth"] + "examples": [ + "https://accounts.google.com/o/oauth2/v2/auth" + ] }, "token_url": { "type": "string", "format": "uri", - "examples": ["https://www.googleapis.com/oauth2/v4/token"] + "examples": [ + "https://www.googleapis.com/oauth2/v4/token" + ] }, "mapper_url": { "title": "Jsonnet Mapper URL", @@ -504,7 +569,10 @@ "type": "array", "items": { "type": "string", - "examples": ["offline_access", "profile"] + "examples": [ + "offline_access", + "profile" + ] } }, "microsoft_tenant": { @@ -523,21 +591,31 @@ "title": "Microsoft subject source", "description": "Controls which source the subject identifier is taken from by microsoft provider. If set to `userinfo` (the default) then the identifier is taken from the `sub` field of OIDC ID token or data received from `/userinfo` standard OIDC endpoint. If set to `me` then the `id` field of data structure received from `https://graph.microsoft.com/v1.0/me` is taken as an identifier.", "type": "string", - "enum": ["userinfo", "me", "oid"], + "enum": [ + "userinfo", + "me", + "oid" + ], "default": "userinfo", - "examples": ["userinfo"] + "examples": [ + "userinfo" + ] }, "apple_team_id": { "title": "Apple Developer Team ID", "description": "Apple Developer Team ID needed for generating a JWT token for client secret", "type": "string", - "examples": ["KP76DQS54M"] + "examples": [ + "KP76DQS54M" + ] }, "apple_private_key_id": { "title": "Apple Private Key Identifier", "description": "Sign In with Apple Private Key Identifier needed for generating a JWT token for client secret", "type": "string", - "examples": ["UX56C66723"] + "examples": [ + "UX56C66723" + ] }, "apple_private_key": { "title": "Apple Private Key", @@ -554,34 +632,53 @@ "title": "Organization ID", "description": "The ID of the organization that this provider belongs to. Only effective in the Ory Network.", "type": "string", - "examples": ["12345678-1234-1234-1234-123456789012"] + "examples": [ + "12345678-1234-1234-1234-123456789012" + ] }, "additional_id_token_audiences": { "title": "Additional client ids allowed when using ID token submission", "type": "array", "items": { "type": "string", - "examples": ["12345678-1234-1234-1234-123456789012"] + "examples": [ + "12345678-1234-1234-1234-123456789012" + ] } }, "claims_source": { "title": "Claims source", "description": "Can be either `userinfo` (calls the userinfo endpoint to get the claims) or `id_token` (takes the claims from the id token). It defaults to `id_token`", "type": "string", - "enum": ["id_token", "userinfo"], + "enum": [ + "id_token", + "userinfo" + ], "default": "id_token", - "examples": ["id_token", "userinfo"] + "examples": [ + "id_token", + "userinfo" + ] }, "pkce": { "title": "Proof Key for Code Exchange", "description": "PKCE controls if the OpenID Connect OAuth2 flow should use PKCE (Proof Key for Code Exchange). IMPORTANT: If you set this to `force`, you must whitelist a different return URL for your OAuth2 client in the provider's configuration. Instead of /self-service/methods/oidc/callback/, you must use /self-service/methods/oidc/callback", "type": "string", - "enum": ["auto", "never", "force"], + "enum": [ + "auto", + "never", + "force" + ], "default": "auto" } }, "additionalProperties": false, - "required": ["id", "provider", "client_id", "mapper_url"], + "required": [ + "id", + "provider", + "client_id", + "mapper_url" + ], "allOf": [ { "if": { @@ -590,17 +687,23 @@ "const": "microsoft" } }, - "required": ["provider"] + "required": [ + "provider" + ] }, "then": { - "required": ["microsoft_tenant"] + "required": [ + "microsoft_tenant" + ] }, "else": { "not": { "properties": { "microsoft_tenant": {} }, - "required": ["microsoft_tenant"] + "required": [ + "microsoft_tenant" + ] } } }, @@ -611,7 +714,9 @@ "const": "apple" } }, - "required": ["provider"] + "required": [ + "provider" + ] }, "then": { "not": { @@ -621,7 +726,9 @@ "minLength": 1 } }, - "required": ["client_secret"] + "required": [ + "client_secret" + ] }, "required": [ "apple_private_key_id", @@ -630,7 +737,9 @@ ] }, "else": { - "required": ["client_secret"], + "required": [ + "client_secret" + ], "allOf": [ { "not": { @@ -640,7 +749,9 @@ "minLength": 1 } }, - "required": ["apple_team_id"] + "required": [ + "apple_team_id" + ] } }, { @@ -651,7 +762,9 @@ "minLength": 1 } }, - "required": ["apple_private_key_id"] + "required": [ + "apple_private_key_id" + ] } }, { @@ -662,7 +775,9 @@ "minLength": 1 } }, - "required": ["apple_private_key"] + "required": [ + "apple_private_key" + ] } } ] @@ -848,7 +963,10 @@ "title": "Required Authenticator Assurance Level", "description": "Sets what Authenticator Assurance Level (used for 2FA) is required to access this feature. If set to `highest_available` then this endpoint requires the highest AAL the identity has set up. If set to `aal1` then the identity can access this feature without 2FA.", "type": "string", - "enum": ["aal1", "highest_available"], + "enum": [ + "aal1", + "highest_available" + ], "default": "highest_available" }, "selfServiceAfterSettings": { @@ -1044,7 +1162,9 @@ "path": { "title": "Path to PEM-encoded Fle", "type": "string", - "examples": ["path/to/file.pem"] + "examples": [ + "path/to/file.pem" + ] }, "base64": { "title": "Base64 Encoded Inline", @@ -1092,7 +1212,9 @@ "$ref": "#/definitions/emailCourierTemplate" } }, - "required": ["email"] + "required": [ + "email" + ] }, "valid": { "additionalProperties": false, @@ -1105,7 +1227,9 @@ "$ref": "#/definitions/smsCourierTemplate" } }, - "required": ["email"] + "required": [ + "email" + ] } } }, @@ -1176,7 +1300,9 @@ "selfservice": { "type": "object", "additionalProperties": false, - "required": ["default_browser_return_url"], + "required": [ + "default_browser_return_url" + ], "properties": { "default_browser_return_url": { "$ref": "#/definitions/defaultReturnTo" @@ -1211,20 +1337,30 @@ "description": "URL where the Settings UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", - "examples": ["https://my-app.com/user/settings"], + "examples": [ + "https://my-app.com/user/settings" + ], "default": "https://www.ory.sh/kratos/docs/fallback/settings" }, "lifespan": { "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", - "examples": ["1h", "1m", "1s"] + "examples": [ + "1h", + "1m", + "1s" + ] }, "privileged_session_max_age": { "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", - "examples": ["1h", "1m", "1s"] + "examples": [ + "1h", + "1m", + "1s" + ] }, "required_aal": { "$ref": "#/definitions/featureRequiredAal" @@ -1273,14 +1409,20 @@ "description": "URL where the Registration UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", - "examples": ["https://my-app.com/signup"], + "examples": [ + "https://my-app.com/signup" + ], "default": "https://www.ory.sh/kratos/docs/fallback/registration" }, "lifespan": { "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", - "examples": ["1h", "1m", "1s"] + "examples": [ + "1h", + "1m", + "1s" + ] }, "before": { "$ref": "#/definitions/selfServiceBeforeRegistration" @@ -1305,20 +1447,29 @@ "description": "URL where the Login UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", - "examples": ["https://my-app.com/login"], + "examples": [ + "https://my-app.com/login" + ], "default": "https://www.ory.sh/kratos/docs/fallback/login" }, "lifespan": { "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", - "examples": ["1h", "1m", "1s"] + "examples": [ + "1h", + "1m", + "1s" + ] }, "style": { "title": "Login Flow Style", "description": "The style of the login flow. If set to `unified` the login flow will be a one-step process. If set to `identifier_first` (experimental!) the login flow will first ask for the identifier and then the credentials.", "type": "string", - "enum": ["unified", "identifier_first"], + "enum": [ + "unified", + "identifier_first" + ], "default": "unified" }, "before": { @@ -1345,7 +1496,9 @@ "description": "URL where the Ory Verify UI is hosted. This is the page where users activate and / or verify their email or telephone number. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", - "examples": ["https://my-app.com/verify"], + "examples": [ + "https://my-app.com/verify" + ], "default": "https://www.ory.sh/kratos/docs/fallback/verification" }, "after": { @@ -1357,7 +1510,11 @@ "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", - "examples": ["1h", "1m", "1s"] + "examples": [ + "1h", + "1m", + "1s" + ] }, "before": { "$ref": "#/definitions/selfServiceBeforeVerification" @@ -1366,7 +1523,10 @@ "title": "Verification Strategy", "description": "The strategy to use for verification requests", "type": "string", - "enum": ["link", "code"], + "enum": [ + "link", + "code" + ], "default": "code" }, "notify_unknown_recipients": { @@ -1393,7 +1553,9 @@ "description": "URL where the Ory Recovery UI is hosted. This is the page where users request and complete account recovery. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", - "examples": ["https://my-app.com/verify"], + "examples": [ + "https://my-app.com/verify" + ], "default": "https://www.ory.sh/kratos/docs/fallback/recovery" }, "after": { @@ -1405,7 +1567,11 @@ "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", - "examples": ["1h", "1m", "1s"] + "examples": [ + "1h", + "1m", + "1s" + ] }, "before": { "$ref": "#/definitions/selfServiceBeforeRecovery" @@ -1414,7 +1580,10 @@ "title": "Recovery Strategy", "description": "The strategy to use for recovery requests", "type": "string", - "enum": ["link", "code"], + "enum": [ + "link", + "code" + ], "default": "code" }, "notify_unknown_recipients": { @@ -1434,7 +1603,9 @@ "description": "URL where the Ory Kratos Error UI is hosted. Check the [reference implementation](https://github.com/ory/kratos-selfservice-ui-node).", "type": "string", "format": "uri-reference", - "examples": ["https://my-app.com/kratos-error"], + "examples": [ + "https://my-app.com/kratos-error" + ], "default": "https://www.ory.sh/kratos/docs/fallback/error" } } @@ -1463,19 +1634,25 @@ "type": "string", "description": "The ID of the organization.", "format": "uuid", - "examples": ["00000000-0000-0000-0000-000000000000"] + "examples": [ + "00000000-0000-0000-0000-000000000000" + ] }, "label": { "type": "string", "description": "The label of the organization.", - "examples": ["ACME SSO"] + "examples": [ + "ACME SSO" + ] }, "domains": { "type": "array", "items": { "type": "string", "format": "hostname", - "examples": ["my-app.com"], + "examples": [ + "my-app.com" + ], "description": "If this domain matches the email's domain, this provider is shown." } } @@ -1515,14 +1692,20 @@ "base_url": { "title": "Override the base URL which should be used as the base for recovery and verification links.", "type": "string", - "examples": ["https://my-app.com"] + "examples": [ + "https://my-app.com" + ] }, "lifespan": { "title": "How long a link is valid for", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", - "examples": ["1h", "1m", "1s"] + "examples": [ + "1h", + "1m", + "1s" + ] } } } @@ -1590,13 +1773,22 @@ "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "1h", - "examples": ["1h", "1m", "1s"] + "examples": [ + "1h", + "1m", + "1s" + ] }, "missing_credential_fallback_enabled": { "type": "boolean", "title": "Enable Code OTP as a Fallback", "description": "Enabling this allows users to sign in with the code method, even if their identity schema or their credentials are not set up to use the code method. If enabled, a verified address (such as an email) will be used to send the code to the user. Use with caution and only if actually needed.", - + "default": false + }, + "legacy_short_code": { + "type": "boolean", + "title": "Use Short Legacy Codes", + "description": "If set to true, the code will be a numeric 6-digit code. If set to false, the code will be an alphanumeric 8-digit code.", "default": false } } @@ -1775,13 +1967,17 @@ "type": "string", "title": "Relying Party Display Name", "description": "An name to help the user identify this RP.", - "examples": ["Ory Foundation"] + "examples": [ + "Ory Foundation" + ] }, "id": { "type": "string", "title": "Relying Party Identifier", "description": "The id must be a subset of the domain currently in the browser.", - "examples": ["ory.sh"] + "examples": [ + "ory.sh" + ] }, "origin": { "type": "string", @@ -1789,7 +1985,9 @@ "description": "An explicit RP origin. If left empty, this defaults to `id`, prepended with the current protocol schema (HTTP or HTTPS).", "format": "uri", "deprecationMessage": "This field is deprecated. Use `origins` instead.", - "examples": ["https://www.ory.sh"] + "examples": [ + "https://www.ory.sh" + ] }, "origins": { "type": "array", @@ -1810,13 +2008,18 @@ "description": "An icon to help the user identify this RP.", "format": "uri", "deprecationMessage": "This field is deprecated and ignored due to security considerations.", - "examples": ["https://www.ory.sh/an-icon.png"] + "examples": [ + "https://www.ory.sh/an-icon.png" + ] } }, "type": "object", "oneOf": [ { - "required": ["id", "display_name"], + "required": [ + "id", + "display_name" + ], "properties": { "origin": { "not": {} @@ -1827,7 +2030,11 @@ } }, { - "required": ["id", "display_name", "origin"], + "required": [ + "id", + "display_name", + "origin" + ], "properties": { "origin": { "type": "string" @@ -1838,7 +2045,11 @@ } }, { - "required": ["id", "display_name", "origins"], + "required": [ + "id", + "display_name", + "origins" + ], "properties": { "origin": { "not": {} @@ -1863,10 +2074,14 @@ "const": true } }, - "required": ["enabled"] + "required": [ + "enabled" + ] }, "then": { - "required": ["config"] + "required": [ + "config" + ] } }, "passkey": { @@ -1889,13 +2104,17 @@ "type": "string", "title": "Relying Party Display Name", "description": "A name to help the user identify this RP.", - "examples": ["Ory Foundation"] + "examples": [ + "Ory Foundation" + ] }, "id": { "type": "string", "title": "Relying Party Identifier", "description": "The id must be a subset of the domain currently in the browser.", - "examples": ["ory.sh"] + "examples": [ + "ory.sh" + ] }, "origins": { "type": "array", @@ -1911,7 +2130,10 @@ } }, "type": "object", - "required": ["display_name", "id"] + "required": [ + "display_name", + "id" + ] } }, "additionalProperties": false @@ -1923,10 +2145,14 @@ "const": true } }, - "required": ["enabled"] + "required": [ + "enabled" + ] }, "then": { - "required": ["config"] + "required": [ + "config" + ] } }, "oidc": { @@ -1949,7 +2175,9 @@ "title": "Base URL for OAuth2 Redirect URIs", "description": "Can be used to modify the base URL for OAuth2 Redirect URLs. If unset, the Public Base URL will be used.", "format": "uri", - "examples": ["https://auth.myexample.org/"] + "examples": [ + "https://auth.myexample.org/" + ] }, "providers": { "title": "OpenID Connect and OAuth2 Providers", @@ -2057,7 +2285,9 @@ "$ref": "#/definitions/smsCourierTemplate" } }, - "required": ["email"] + "required": [ + "email" + ] } } }, @@ -2076,7 +2306,9 @@ "$ref": "#/definitions/smsCourierTemplate" } }, - "required": ["email"] + "required": [ + "email" + ] } } } @@ -2086,13 +2318,18 @@ "type": "string", "title": "Override message templates", "description": "You can override certain or all message templates by pointing this key to the path where the templates are located.", - "examples": ["/conf/courier-templates"] + "examples": [ + "/conf/courier-templates" + ] }, "message_retries": { "description": "Defines the maximum number of times the sending of a message is retried after it failed before it is marked as abandoned", "type": "integer", "default": 5, - "examples": [10, 60] + "examples": [ + 10, + 60 + ] }, "worker": { "description": "Configures the dispatch worker.", @@ -2115,7 +2352,10 @@ "title": "Delivery Strategy", "description": "Defines how emails will be sent, either through SMTP (default) or HTTP.", "type": "string", - "enum": ["smtp", "http"], + "enum": [ + "smtp", + "http" + ], "default": "smtp" }, "http": { @@ -2172,7 +2412,9 @@ "title": "SMTP Sender Name", "description": "The recipient of an email will see this as the sender name.", "type": "string", - "examples": ["Bob"] + "examples": [ + "Bob" + ] }, "headers": { "title": "SMTP Headers", @@ -2209,19 +2451,26 @@ "title": "Channel id", "description": "The channel id. Corresponds to the .via property of the identity schema for recovery, verification, etc. Currently only sms is supported.", "maxLength": 32, - "enum": ["sms"] + "enum": [ + "sms" + ] }, "type": { "type": "string", "title": "Channel type", "description": "The channel type. Currently only http is supported.", - "enum": ["http"] + "enum": [ + "http" + ] }, "request_config": { "$ref": "#/definitions/httpRequestConfig" } }, - "required": ["id", "request_config"], + "required": [ + "id", + "request_config" + ], "additionalProperties": false } } @@ -2272,7 +2521,10 @@ "type": "string", "title": "Default Read Consistency Level", "description": "The default consistency level to use when reading from the database. Defaults to `strong` to not break existing API contracts. Only set this to `eventual` if you can accept that other read APIs will suddenly return eventually consistent results. It is only effective in Ory Network.", - "enum": ["strong", "eventual"], + "enum": [ + "strong", + "eventual" + ], "default": "strong" } } @@ -2300,7 +2552,9 @@ "description": "The URL where the admin endpoint is exposed at.", "type": "string", "format": "uri", - "examples": ["https://kratos.private-network:4434/"] + "examples": [ + "https://kratos.private-network:4434/" + ] }, "host": { "title": "Admin Host", @@ -2314,7 +2568,9 @@ "type": "integer", "minimum": 1, "maximum": 65535, - "examples": [4434], + "examples": [ + 4434 + ], "default": 4434 }, "socket": { @@ -2373,7 +2629,9 @@ ] }, "uniqueItems": true, - "default": ["*"], + "default": [ + "*" + ], "examples": [ [ "https://example.com", @@ -2385,7 +2643,13 @@ "allowed_methods": { "type": "array", "description": "A list of HTTP methods the user agent is allowed to use with cross-domain requests.", - "default": ["POST", "GET", "PUT", "PATCH", "DELETE"], + "default": [ + "POST", + "GET", + "PUT", + "PATCH", + "DELETE" + ], "items": { "type": "string", "enum": [ @@ -2419,7 +2683,9 @@ "exposed_headers": { "type": "array", "description": "Sets which headers are safe to expose to the API of a CORS API specification.", - "default": ["Content-Type"], + "default": [ + "Content-Type" + ], "items": { "type": "string" } @@ -2462,7 +2728,9 @@ "type": "integer", "minimum": 1, "maximum": 65535, - "examples": [4433], + "examples": [ + 4433 + ], "default": 4433 }, "socket": { @@ -2512,7 +2780,10 @@ "format": { "description": "The log format can either be text or JSON.", "type": "string", - "enum": ["json", "text"] + "enum": [ + "json", + "text" + ] } }, "additionalProperties": false @@ -2553,7 +2824,9 @@ "id": { "title": "The schema's ID.", "type": "string", - "examples": ["employee"] + "examples": [ + "employee" + ] }, "url": { "type": "string", @@ -2567,11 +2840,16 @@ ] } }, - "required": ["id", "url"] + "required": [ + "id", + "url" + ] } } }, - "required": ["schemas"], + "required": [ + "schemas" + ], "additionalProperties": false }, "secrets": { @@ -2620,7 +2898,10 @@ "description": "One of the values: argon2, bcrypt.\nAny other hashes will be migrated to the set algorithm once an identity authenticates using their password.", "type": "string", "default": "bcrypt", - "enum": ["argon2", "bcrypt"] + "enum": [ + "argon2", + "bcrypt" + ] }, "argon2": { "title": "Configuration for the Argon2id hasher.", @@ -2676,7 +2957,9 @@ "title": "Configuration for the Bcrypt hasher. Minimum is 4 when --dev flag is used and 12 otherwise.", "type": "object", "additionalProperties": false, - "required": ["cost"], + "required": [ + "cost" + ], "properties": { "cost": { "type": "integer", @@ -2698,7 +2981,11 @@ "description": "One of the values: noop, aes, xchacha20-poly1305", "type": "string", "default": "noop", - "enum": ["noop", "aes", "xchacha20-poly1305"] + "enum": [ + "noop", + "aes", + "xchacha20-poly1305" + ] } } }, @@ -2727,7 +3014,11 @@ "title": "HTTP Cookie Same Site Configuration", "description": "Sets the session and CSRF cookie SameSite.", "type": "string", - "enum": ["Strict", "Lax", "None"], + "enum": [ + "Strict", + "Lax", + "None" + ], "default": "Lax" } }, @@ -2757,7 +3048,9 @@ "patternProperties": { "[a-zA-Z0-9-_.]+": { "type": "object", - "required": ["jwks_url"], + "required": [ + "jwks_url" + ], "properties": { "ttl": { "type": "string", @@ -2790,7 +3083,11 @@ "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", "default": "24h", - "examples": ["1h", "1m", "1s"] + "examples": [ + "1h", + "1m", + "1s" + ] }, "cookie": { "type": "object", @@ -2826,7 +3123,11 @@ "title": "Session Cookie SameSite Configuration", "description": "Sets the session cookie SameSite. Overrides `cookies.same_site`.", "type": "string", - "enum": ["Strict", "Lax", "None"] + "enum": [ + "Strict", + "Lax", + "None" + ] } }, "additionalProperties": false @@ -2836,7 +3137,11 @@ "description": "Sets when a session can be extended. Settings this value to `24h` will prevent the session from being extended before until 24 hours before it expires. This setting prevents excessive writes to the database. We highly recommend setting this value.", "type": "string", "pattern": "^([0-9]+(ns|us|ms|s|m|h))+$", - "examples": ["1h", "1m", "1s"] + "examples": [ + "1h", + "1m", + "1s" + ] } } }, @@ -2860,7 +3165,9 @@ "description": "SemVer according to https://semver.org/ prefixed with `v` as in our releases.", "type": "string", "pattern": "^(v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)|$", - "examples": ["v0.5.0-alpha.1"] + "examples": [ + "v0.5.0-alpha.1" + ] }, "dev": { "type": "boolean" @@ -2884,7 +3191,9 @@ "type": "integer", "minimum": 0, "maximum": 65535, - "examples": [4434], + "examples": [ + 4434 + ], "default": 0 }, "config": { @@ -2990,10 +3299,14 @@ "const": true } }, - "required": ["enabled"] + "required": [ + "enabled" + ] } }, - "required": ["verification"] + "required": [ + "verification" + ] }, { "properties": { @@ -3003,21 +3316,31 @@ "const": true } }, - "required": ["enabled"] + "required": [ + "enabled" + ] } }, - "required": ["recovery"] + "required": [ + "recovery" + ] } ] } }, - "required": ["flows"] + "required": [ + "flows" + ] } }, - "required": ["selfservice"] + "required": [ + "selfservice" + ] }, "then": { - "required": ["courier"] + "required": [ + "courier" + ] } }, { @@ -3036,21 +3359,33 @@ ] } }, - "required": ["algorithm"] + "required": [ + "algorithm" + ] } }, - "required": ["ciphers"] + "required": [ + "ciphers" + ] }, "then": { - "required": ["secrets"], + "required": [ + "secrets" + ], "properties": { "secrets": { - "required": ["cipher"] + "required": [ + "cipher" + ] } } } } ], - "required": ["identity", "dsn", "selfservice"], + "required": [ + "identity", + "dsn", + "selfservice" + ], "additionalProperties": false } diff --git a/internal/testhelpers/strategy_code.go b/internal/testhelpers/strategy_code.go index 904ae136fb7e..4b4a8f099aa5 100644 --- a/internal/testhelpers/strategy_code.go +++ b/internal/testhelpers/strategy_code.go @@ -3,10 +3,4 @@ package testhelpers -import ( - "fmt" - - "github.com/ory/kratos/selfservice/strategy/code" -) - -var CodeRegex = fmt.Sprintf(`(\d{%d})`, code.CodeLength) +var CodeRegex = `(\d{6}|[0-9a-zA-Z]{8})` diff --git a/selfservice/strategy/code/.snapshots/TestAdminStrategy-case=form_should_not_contain_email_field_when_creating_recovery_code.json b/selfservice/strategy/code/.snapshots/TestAdminStrategy-case=form_should_not_contain_email_field_when_creating_recovery_code.json index 736578d0e543..9b46a554b597 100644 --- a/selfservice/strategy/code/.snapshots/TestAdminStrategy-case=form_should_not_contain_email_field_when_creating_recovery_code.json +++ b/selfservice/strategy/code/.snapshots/TestAdminStrategy-case=form_should_not_contain_email_field_when_creating_recovery_code.json @@ -6,9 +6,9 @@ "name": "code", "type": "text", "required": true, - "pattern": "[0-9]+", + "pattern": "[0-9a-zA-Z]+", "disabled": false, - "maxlength": 6, + "maxlength": 8, "node_type": "input" }, "messages": [], diff --git a/selfservice/strategy/code/code_sender.go b/selfservice/strategy/code/code_sender.go index 20e392836ce7..f48cc0f4a927 100644 --- a/selfservice/strategy/code/code_sender.go +++ b/selfservice/strategy/code/code_sender.go @@ -44,6 +44,7 @@ type ( LoginCodePersistenceProvider x.HTTPClientProvider + x.TracingProvider } SenderProvider interface { CodeSender() *Sender @@ -80,7 +81,7 @@ func (s *Sender) SendCode(ctx context.Context, f flow.Flow, id *identity.Identit // address was used to verify the code. // // See also [this discussion](https://github.com/ory/kratos/pull/3456#discussion_r1307560988). - rawCode := GenerateCode() + rawCode := GenerateCode(ctx, s.deps) switch f.GetFlowName() { case flow.RegistrationFlow: @@ -238,7 +239,7 @@ func (s *Sender) SendRecoveryCode(ctx context.Context, f *recovery.Flow, via ide return err } - rawCode := GenerateCode() + rawCode := GenerateCode(ctx, s.deps) var code *RecoveryCode if code, err = s.deps. @@ -328,7 +329,7 @@ func (s *Sender) SendVerificationCode(ctx context.Context, f *verification.Flow, return err } - rawCode := GenerateCode() + rawCode := GenerateCode(ctx, s.deps) var code *VerificationCode if code, err = s.deps.VerificationCodePersister().CreateVerificationCode(ctx, &CreateVerificationCodeParams{ RawCode: rawCode, diff --git a/selfservice/strategy/code/strategy.go b/selfservice/strategy/code/strategy.go index 85070509402c..55557c7d59b0 100644 --- a/selfservice/strategy/code/strategy.go +++ b/selfservice/strategy/code/strategy.go @@ -9,6 +9,8 @@ import ( "sort" "strings" + "github.com/ory/x/otelx/semconv" + "github.com/samber/lo" "github.com/pkg/errors" @@ -484,10 +486,17 @@ func SetDefaultFlowState(f flow.Flow, resend string) { } } -const CodeLength = 6 +func GenerateCode(ctx context.Context, c interface { + config.Provider + x.TracingProvider +}) string { + isLegacy := c.Config().SelfServiceCodeMethodCodeShortLegacyCode(ctx) + if isLegacy { + semconv.NewDeprecatedFeatureUsedEvent(ctx, "LegacyShortCodeGenerated") + return randx.MustString(6, randx.Numeric) + } -func GenerateCode() string { - return randx.MustString(CodeLength, randx.Numeric) + return randx.MustString(8, randx.AlphaNum) } // MaskAddress masks an address by replacing the middle part with asterisks. diff --git a/selfservice/strategy/code/strategy_login.go b/selfservice/strategy/code/strategy_login.go index 13e959299a5e..e13227abc60a 100644 --- a/selfservice/strategy/code/strategy_login.go +++ b/selfservice/strategy/code/strategy_login.go @@ -55,7 +55,7 @@ type updateLoginFlowWithCodeMethod struct { // required: true CSRFToken string `json:"csrf_token" form:"csrf_token"` - // Code is the 6 digits code sent to the user + // Code is the 8 lower alphanumeric code sent to the user // // required: false Code string `json:"code" form:"code"` diff --git a/selfservice/strategy/code/strategy_recovery.go b/selfservice/strategy/code/strategy_recovery.go index 178a1906fdde..d5dcd34f753e 100644 --- a/selfservice/strategy/code/strategy_recovery.go +++ b/selfservice/strategy/code/strategy_recovery.go @@ -409,8 +409,13 @@ func (s *Strategy) recoveryHandleFormSubmission(w http.ResponseWriter, r *http.R f.UI.Messages.Set(text.NewRecoveryEmailWithCodeSent()) f.UI.Nodes.Append(node.NewInputField("code", nil, node.CodeGroup, node.InputAttributeTypeText, node.WithInputAttributes(func(a *node.InputAttributes) { a.Required = true - a.Pattern = "[0-9]+" - a.MaxLength = CodeLength + if s.deps.Config().SelfServiceCodeMethodCodeShortLegacyCode(ctx) { + a.Pattern = "[0-9]+" + a.MaxLength = 6 + } else { + a.Pattern = "[0-9a-zA-Z]+" + a.MaxLength = 8 + } })). WithMetaLabel(text.NewInfoNodeLabelRecoveryCode()), ) diff --git a/selfservice/strategy/code/strategy_recovery_admin.go b/selfservice/strategy/code/strategy_recovery_admin.go index b64eb7b66e02..d79ce13cedd7 100644 --- a/selfservice/strategy/code/strategy_recovery_admin.go +++ b/selfservice/strategy/code/strategy_recovery_admin.go @@ -179,16 +179,20 @@ func (s *Strategy) createRecoveryCodeForIdentity(w http.ResponseWriter, r *http. s.deps.Writer().WriteError(w, r, err) return } + rawCode := GenerateCode(ctx, s.deps) + recoveryFlow.DangerousSkipCSRFCheck = true recoveryFlow.State = flow.StateEmailSent recoveryFlow.UI.Nodes = node.Nodes{} recoveryFlow.UI.Nodes.Append(node.NewInputField("code", nil, node.CodeGroup, node.InputAttributeTypeText, node.WithRequiredInputAttribute, node.WithInputAttributes(func(a *node.InputAttributes) { - a.Pattern = "[0-9]+" - a.MaxLength = CodeLength + a.Pattern = "[0-9a-zA-Z]+" + if s.deps.Config().SelfServiceCodeMethodCodeShortLegacyCode(ctx) { + a.Pattern = "[0-9]+" + } + a.MaxLength = len(rawCode) })). WithMetaLabel(text.NewInfoNodeLabelRecoveryCode()), ) - rawCode := GenerateCode() recoveryFlow.UI.Nodes. Append(node.NewInputField("method", s.RecoveryStrategyID(), node.CodeGroup, node.InputAttributeTypeSubmit). diff --git a/selfservice/strategy/code/strategy_test.go b/selfservice/strategy/code/strategy_test.go index 5edbef5ec718..d6fdaeb13351 100644 --- a/selfservice/strategy/code/strategy_test.go +++ b/selfservice/strategy/code/strategy_test.go @@ -37,12 +37,29 @@ func initViper(t *testing.T, ctx context.Context, c *config.Config) { } func TestGenerateCode(t *testing.T) { - codes := make([]string, 100) - for k := range codes { - codes[k] = code.GenerateCode() - } + _, reg := internal.NewFastRegistryWithMocks(t) + ctx := context.Background() + t.Run("case=legacy", func(t *testing.T) { + ctx := confighelpers.WithConfigValue(ctx, config.ViperKeyCodeShortLegacyCode, true) + codes := make([]string, 100) + for k := range codes { + codes[k] = code.GenerateCode(ctx, reg) + assert.Regexp(t, "^[0-9]{6}$", codes[k]) + } - assert.Len(t, stringslice.Unique(codes), len(codes)) + assert.Len(t, stringslice.Unique(codes), len(codes)) + }) + + t.Run("case=current", func(t *testing.T) { + ctx := confighelpers.WithConfigValue(ctx, config.ViperKeyCodeShortLegacyCode, false) + codes := make([]string, 250) + for k := range codes { + codes[k] = code.GenerateCode(ctx, reg) + assert.Regexp(t, "^[a-zA-Z0-9]{8}$", codes[k]) + } + + assert.Len(t, stringslice.Unique(codes), len(codes)) + }) } func TestMaskAddress(t *testing.T) { diff --git a/selfservice/strategy/code/strategy_verification.go b/selfservice/strategy/code/strategy_verification.go index 9cfb6eb74535..5f1d735e18da 100644 --- a/selfservice/strategy/code/strategy_verification.go +++ b/selfservice/strategy/code/strategy_verification.go @@ -367,7 +367,7 @@ func (s *Strategy) retryVerificationFlowWithError(ctx context.Context, w http.Re } func (s *Strategy) SendVerificationEmail(ctx context.Context, f *verification.Flow, i *identity.Identity, a *identity.VerifiableAddress) (err error) { - rawCode := GenerateCode() + rawCode := GenerateCode(ctx, s.deps) code, err := s.deps.VerificationCodePersister().CreateVerificationCode(ctx, &CreateVerificationCodeParams{ RawCode: rawCode,