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
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:
54
selfservice/strategy/webauthn/.schema/login.schema.json
Normal file
54
selfservice/strategy/webauthn/.schema/login.schema.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"$id": "https://schemas.ory.sh/kratos/selfservice/strategy/webauthn/login.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"csrf_token": {
|
||||
"type": "string"
|
||||
},
|
||||
"webauthn_login": {
|
||||
"type": "string"
|
||||
},
|
||||
"method": {
|
||||
"type": "string"
|
||||
},
|
||||
"identifier": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"transient_payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"if": {
|
||||
"anyOf": [
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"const": "webauthn"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"webauthn_login": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"webauthn_login"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"identifier"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"$id": "https://schemas.ory.sh/kratos/selfservice/strategy/password/registration.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"csrf_token": {
|
||||
"type": "string"
|
||||
},
|
||||
"traits": {
|
||||
"description": "This field will be overwritten in registration.go's decoder() method. Do not add anything to this field as it has no effect."
|
||||
},
|
||||
"method": {
|
||||
"type": "string"
|
||||
},
|
||||
"webauthn_register": {
|
||||
"type": "string"
|
||||
},
|
||||
"webauthn_register_displayname": {
|
||||
"type": "string"
|
||||
},
|
||||
"transient_payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
{
|
||||
"required": [
|
||||
"webauthn_register"
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": {
|
||||
"const": "webauthn"
|
||||
},
|
||||
"required": [
|
||||
"identifier",
|
||||
"method"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
47
selfservice/strategy/webauthn/.schema/settings.schema.json
Normal file
47
selfservice/strategy/webauthn/.schema/settings.schema.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"$id": "https://schemas.ory.sh/kratos/selfservice/strategy/webauthn/settings.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"csrf_token": {
|
||||
"type": "string"
|
||||
},
|
||||
"method": {
|
||||
"type": "string"
|
||||
},
|
||||
"webauthn_register": {
|
||||
"type": "string"
|
||||
},
|
||||
"webauthn_register_displayname": {
|
||||
"type": "string"
|
||||
},
|
||||
"webauthn_remove": {
|
||||
"type": "string"
|
||||
},
|
||||
"transient_payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"if": {
|
||||
"properties": {
|
||||
"webauthn_register": {
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"webauthn_register"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"webauthn_register_displayname": {
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"webauthn_register_displayname"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"id": 4000043,
|
||||
"text": "The provided web authn login is invalid, please try again.",
|
||||
"type": "error"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,74 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "identifier",
|
||||
"node_type": "input",
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_login",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"crossorigin": "anonymous",
|
||||
"id": "webauthn_script",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"node_type": "script",
|
||||
"referrerpolicy": "no-referrer",
|
||||
"type": "text/javascript"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "script"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_login_trigger",
|
||||
"node_type": "input",
|
||||
"onclickTrigger": "oryWebAuthnLogin",
|
||||
"type": "button"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"organization_id": null,
|
||||
"type": "browser",
|
||||
"ui": {
|
||||
"method": "POST",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "identifier",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "script",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"referrerpolicy": "no-referrer",
|
||||
"crossorigin": "anonymous",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"type": "text/javascript",
|
||||
"node_type": "script"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_login_trigger",
|
||||
"type": "button",
|
||||
"disabled": false,
|
||||
"onclickTrigger": "oryWebAuthnLogin",
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"text": "Continue",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_login",
|
||||
"type": "hidden",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
}
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"text": "Prepare your WebAuthn device (e.g. security key, biometrics scanner, ...) and press continue.",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
},
|
||||
"refresh": false,
|
||||
"requested_aal": "aal1",
|
||||
"state": "choose_method"
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"organization_id": null,
|
||||
"type": "browser",
|
||||
"ui": {
|
||||
"method": "POST",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "identifier",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "script",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"referrerpolicy": "no-referrer",
|
||||
"crossorigin": "anonymous",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"type": "text/javascript",
|
||||
"node_type": "script"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_login_trigger",
|
||||
"type": "button",
|
||||
"disabled": false,
|
||||
"onclickTrigger": "oryWebAuthnLogin",
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"text": "Continue",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_login",
|
||||
"type": "hidden",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
}
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"text": "Prepare your WebAuthn device (e.g. security key, biometrics scanner, ...) and press continue.",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
},
|
||||
"refresh": false,
|
||||
"requested_aal": "aal1",
|
||||
"state": "choose_method"
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"autocomplete": "username webauthn",
|
||||
"disabled": false,
|
||||
"name": "identifier",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "text",
|
||||
"value": ""
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1070004,
|
||||
"text": "ID",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "method",
|
||||
"node_type": "input",
|
||||
"type": "submit",
|
||||
"value": "webauthn"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,75 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "identifier",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": "foo@bar.com"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_login",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"crossorigin": "anonymous",
|
||||
"id": "webauthn_script",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"node_type": "script",
|
||||
"referrerpolicy": "no-referrer",
|
||||
"type": "text/javascript"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "script"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_login_trigger",
|
||||
"node_type": "input",
|
||||
"onclickTrigger": "oryWebAuthnLogin",
|
||||
"type": "button"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,75 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "identifier",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": "foo@bar.com"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_login",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"crossorigin": "anonymous",
|
||||
"id": "webauthn_script",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"node_type": "script",
|
||||
"referrerpolicy": "no-referrer",
|
||||
"type": "text/javascript"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "script"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_login_trigger",
|
||||
"node_type": "input",
|
||||
"onclickTrigger": "oryWebAuthnLogin",
|
||||
"type": "button"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,75 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "identifier",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": "foo@bar.com"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_login",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"crossorigin": "anonymous",
|
||||
"id": "webauthn_script",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"node_type": "script",
|
||||
"referrerpolicy": "no-referrer",
|
||||
"type": "text/javascript"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "script"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_login_trigger",
|
||||
"node_type": "input",
|
||||
"onclickTrigger": "oryWebAuthnLogin",
|
||||
"type": "button"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,75 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "identifier",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": "foo@bar.com"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_login",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"crossorigin": "anonymous",
|
||||
"id": "webauthn_script",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"node_type": "script",
|
||||
"referrerpolicy": "no-referrer",
|
||||
"type": "text/javascript"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "script"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_login_trigger",
|
||||
"node_type": "input",
|
||||
"onclickTrigger": "oryWebAuthnLogin",
|
||||
"type": "button"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,75 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "identifier",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": "foo@bar.com"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_login",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"crossorigin": "anonymous",
|
||||
"id": "webauthn_script",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"node_type": "script",
|
||||
"referrerpolicy": "no-referrer",
|
||||
"type": "text/javascript"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "script"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_login_trigger",
|
||||
"node_type": "input",
|
||||
"onclickTrigger": "oryWebAuthnLogin",
|
||||
"type": "button"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,75 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "identifier",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": "foo@bar.com"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_login",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"crossorigin": "anonymous",
|
||||
"id": "webauthn_script",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"node_type": "script",
|
||||
"referrerpolicy": "no-referrer",
|
||||
"type": "text/javascript"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "script"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_login_trigger",
|
||||
"node_type": "input",
|
||||
"onclickTrigger": "oryWebAuthnLogin",
|
||||
"type": "button"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,129 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_remove",
|
||||
"node_type": "input",
|
||||
"type": "submit",
|
||||
"value": "626172626172"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"context": {
|
||||
"added_at": "0001-01-01T00:00:00Z",
|
||||
"added_at_unix": -62135596800,
|
||||
"display_name": "bar"
|
||||
},
|
||||
"id": 1050018,
|
||||
"text": "Remove security key \"bar\"",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_remove",
|
||||
"node_type": "input",
|
||||
"type": "submit",
|
||||
"value": "666f6f666f6f"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"context": {
|
||||
"added_at": "0001-01-01T00:00:00Z",
|
||||
"added_at_unix": -62135596800,
|
||||
"display_name": "foo"
|
||||
},
|
||||
"id": 1050018,
|
||||
"text": "Remove security key \"foo\"",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_register_displayname",
|
||||
"node_type": "input",
|
||||
"type": "text",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1050013,
|
||||
"text": "Name of the security key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_register",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_register_trigger",
|
||||
"node_type": "input",
|
||||
"onclickTrigger": "oryWebAuthnRegistration",
|
||||
"type": "button"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1050012,
|
||||
"text": "Add security key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"crossorigin": "anonymous",
|
||||
"id": "webauthn_script",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"node_type": "script",
|
||||
"referrerpolicy": "no-referrer",
|
||||
"type": "text/javascript"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "script"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_remove",
|
||||
"type": "submit",
|
||||
"value": "666f6f666f6f",
|
||||
"disabled": true,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [
|
||||
{
|
||||
"id": 4000001,
|
||||
"text": "unable to remove this security key because it would lock you out of your account",
|
||||
"type": "error",
|
||||
"context": {
|
||||
"reason": "unable to remove this security key because it would lock you out of your account"
|
||||
}
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1050018,
|
||||
"text": "Remove security key \"foo\"",
|
||||
"type": "info",
|
||||
"context": {
|
||||
"added_at": "0001-01-01T00:00:00Z",
|
||||
"added_at_unix": -62135596800,
|
||||
"display_name": "foo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"webauthn_register": [
|
||||
""
|
||||
],
|
||||
"webauthn_register_displayname": [
|
||||
""
|
||||
],
|
||||
"webauthn_remove": [
|
||||
"666f6f666f6f"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_remove",
|
||||
"type": "submit",
|
||||
"value": "666f6f666f6f",
|
||||
"disabled": true,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [
|
||||
{
|
||||
"id": 4000001,
|
||||
"text": "unable to remove this security key because it would lock you out of your account",
|
||||
"type": "error",
|
||||
"context": {
|
||||
"reason": "unable to remove this security key because it would lock you out of your account"
|
||||
}
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1050018,
|
||||
"text": "Remove security key \"foo\"",
|
||||
"type": "info",
|
||||
"context": {
|
||||
"added_at": "0001-01-01T00:00:00Z",
|
||||
"added_at_unix": -62135596800,
|
||||
"display_name": "foo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"webauthn_register": [
|
||||
""
|
||||
],
|
||||
"webauthn_register_displayname": [
|
||||
""
|
||||
],
|
||||
"webauthn_remove": [
|
||||
"666f6f666f6f"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_register_displayname",
|
||||
"node_type": "input",
|
||||
"type": "text",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1050013,
|
||||
"text": "Name of the security key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_register",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_register_trigger",
|
||||
"node_type": "input",
|
||||
"onclickTrigger": "oryWebAuthnRegistration",
|
||||
"type": "button"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1050012,
|
||||
"text": "Add security key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"crossorigin": "anonymous",
|
||||
"id": "webauthn_script",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"node_type": "script",
|
||||
"referrerpolicy": "no-referrer",
|
||||
"type": "text/javascript"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "script"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"webauthn_register": [
|
||||
""
|
||||
],
|
||||
"webauthn_register_displayname": [
|
||||
""
|
||||
],
|
||||
"webauthn_remove": [
|
||||
"666f6f666f6f"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"webauthn_register": [
|
||||
""
|
||||
],
|
||||
"webauthn_register_displayname": [
|
||||
""
|
||||
],
|
||||
"webauthn_remove": [
|
||||
"666f6f666f6f"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,54 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "identifier",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"autocomplete": "username webauthn",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1070004,
|
||||
"text": "ID",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "method",
|
||||
"type": "submit",
|
||||
"value": "webauthn",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,54 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "identifier",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"autocomplete": "username webauthn",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1070004,
|
||||
"text": "ID",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "method",
|
||||
"type": "submit",
|
||||
"value": "webauthn",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,34 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "method",
|
||||
"type": "submit",
|
||||
"value": "webauthn",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,34 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "method",
|
||||
"type": "submit",
|
||||
"value": "webauthn",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,34 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "method",
|
||||
"type": "submit",
|
||||
"value": "webauthn",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,34 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "method",
|
||||
"type": "submit",
|
||||
"value": "webauthn",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,74 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "identifier",
|
||||
"type": "hidden",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "script",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"referrerpolicy": "no-referrer",
|
||||
"crossorigin": "anonymous",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"type": "text/javascript",
|
||||
"id": "webauthn_script",
|
||||
"node_type": "script"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_login_trigger",
|
||||
"type": "button",
|
||||
"disabled": false,
|
||||
"onclickTrigger": "oryWebAuthnLogin",
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_login",
|
||||
"type": "hidden",
|
||||
"value": "",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,74 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "identifier",
|
||||
"type": "hidden",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "script",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"referrerpolicy": "no-referrer",
|
||||
"crossorigin": "anonymous",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"type": "text/javascript",
|
||||
"id": "webauthn_script",
|
||||
"node_type": "script"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_login_trigger",
|
||||
"type": "button",
|
||||
"disabled": false,
|
||||
"onclickTrigger": "oryWebAuthnLogin",
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_login",
|
||||
"type": "hidden",
|
||||
"value": "",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,74 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "identifier",
|
||||
"type": "hidden",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "script",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"referrerpolicy": "no-referrer",
|
||||
"crossorigin": "anonymous",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"type": "text/javascript",
|
||||
"id": "webauthn_script",
|
||||
"node_type": "script"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_login_trigger",
|
||||
"type": "button",
|
||||
"disabled": false,
|
||||
"onclickTrigger": "oryWebAuthnLogin",
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1010008,
|
||||
"text": "Sign in with a hardware key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_login",
|
||||
"type": "hidden",
|
||||
"value": "",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,81 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_register_displayname",
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1050013,
|
||||
"text": "Name of the security key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_register_trigger",
|
||||
"type": "button",
|
||||
"disabled": false,
|
||||
"onclickTrigger": "oryWebAuthnRegistration",
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1040004,
|
||||
"text": "Sign up with security key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "script",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"referrerpolicy": "no-referrer",
|
||||
"crossorigin": "anonymous",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"type": "text/javascript",
|
||||
"id": "webauthn_script",
|
||||
"node_type": "script"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_register",
|
||||
"type": "hidden",
|
||||
"value": "",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,81 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_register_displayname",
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1050013,
|
||||
"text": "Name of the security key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_register_trigger",
|
||||
"type": "button",
|
||||
"disabled": false,
|
||||
"onclickTrigger": "oryWebAuthnRegistration",
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1040004,
|
||||
"text": "Sign up with security key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "script",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"referrerpolicy": "no-referrer",
|
||||
"crossorigin": "anonymous",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"type": "text/javascript",
|
||||
"id": "webauthn_script",
|
||||
"node_type": "script"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_register",
|
||||
"type": "hidden",
|
||||
"value": "",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,81 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_register_displayname",
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1050013,
|
||||
"text": "Name of the security key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_register_trigger",
|
||||
"type": "button",
|
||||
"disabled": false,
|
||||
"onclickTrigger": "oryWebAuthnRegistration",
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1040004,
|
||||
"text": "Sign up with security key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "script",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"referrerpolicy": "no-referrer",
|
||||
"crossorigin": "anonymous",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"type": "text/javascript",
|
||||
"id": "webauthn_script",
|
||||
"node_type": "script"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_register",
|
||||
"type": "hidden",
|
||||
"value": "",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,81 @@
|
||||
[
|
||||
{
|
||||
"type": "input",
|
||||
"group": "default",
|
||||
"attributes": {
|
||||
"name": "csrf_token",
|
||||
"type": "hidden",
|
||||
"required": true,
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_register_displayname",
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1050013,
|
||||
"text": "Name of the security key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_register_trigger",
|
||||
"type": "button",
|
||||
"disabled": false,
|
||||
"onclickTrigger": "oryWebAuthnRegistration",
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1040004,
|
||||
"text": "Sign up with security key",
|
||||
"type": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "script",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"referrerpolicy": "no-referrer",
|
||||
"crossorigin": "anonymous",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"type": "text/javascript",
|
||||
"id": "webauthn_script",
|
||||
"node_type": "script"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"group": "webauthn",
|
||||
"attributes": {
|
||||
"name": "webauthn_register",
|
||||
"type": "hidden",
|
||||
"value": "",
|
||||
"disabled": false,
|
||||
"node_type": "input"
|
||||
},
|
||||
"messages": [],
|
||||
"meta": {}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,80 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "traits.foobar",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "text"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"autocomplete": "new-password",
|
||||
"disabled": false,
|
||||
"name": "password",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "password"
|
||||
},
|
||||
"group": "password",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1070001,
|
||||
"text": "Password",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "traits.username",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "text"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "method",
|
||||
"node_type": "input",
|
||||
"type": "submit",
|
||||
"value": "password"
|
||||
},
|
||||
"group": "password",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1040001,
|
||||
"text": "Sign up",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,80 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "traits.foobar",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "text"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"autocomplete": "new-password",
|
||||
"disabled": false,
|
||||
"name": "password",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "password"
|
||||
},
|
||||
"group": "password",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1070001,
|
||||
"text": "Password",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "traits.username",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "text"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "method",
|
||||
"node_type": "input",
|
||||
"type": "submit",
|
||||
"value": "password"
|
||||
},
|
||||
"group": "password",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1040001,
|
||||
"text": "Sign up",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,146 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "traits.foobar",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "text"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "traits.username",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "text"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_register_displayname",
|
||||
"node_type": "input",
|
||||
"type": "text",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1050013,
|
||||
"text": "Name of the security key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_register",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_register_trigger",
|
||||
"node_type": "input",
|
||||
"onclickTrigger": "oryWebAuthnRegistration",
|
||||
"type": "button"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1040004,
|
||||
"text": "Sign up with security key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"crossorigin": "anonymous",
|
||||
"id": "webauthn_script",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"node_type": "script",
|
||||
"referrerpolicy": "no-referrer",
|
||||
"type": "text/javascript"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "script"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"autocomplete": "new-password",
|
||||
"disabled": false,
|
||||
"name": "password",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "password"
|
||||
},
|
||||
"group": "password",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1070001,
|
||||
"text": "Password",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "method",
|
||||
"node_type": "input",
|
||||
"type": "submit",
|
||||
"value": "password"
|
||||
},
|
||||
"group": "password",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1040001,
|
||||
"text": "Sign up",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,146 @@
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "csrf_token",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "hidden"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "traits.foobar",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "text"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "traits.username",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "text"
|
||||
},
|
||||
"group": "default",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_register_displayname",
|
||||
"node_type": "input",
|
||||
"type": "text",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1050013,
|
||||
"text": "Name of the security key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_register",
|
||||
"node_type": "input",
|
||||
"type": "hidden",
|
||||
"value": ""
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "webauthn_register_trigger",
|
||||
"node_type": "input",
|
||||
"onclickTrigger": "oryWebAuthnRegistration",
|
||||
"type": "button"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1040004,
|
||||
"text": "Sign up with security key",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"async": true,
|
||||
"crossorigin": "anonymous",
|
||||
"id": "webauthn_script",
|
||||
"integrity": "sha512-9Qlvd9zudkdfprW3Tf8FsDI2bVA667UGRVUBcima8+YLHQenHVyPaAiSTLzQ3V6MrW60xS7NpWod+UTdVa6QmQ==",
|
||||
"node_type": "script",
|
||||
"referrerpolicy": "no-referrer",
|
||||
"type": "text/javascript"
|
||||
},
|
||||
"group": "webauthn",
|
||||
"messages": [],
|
||||
"meta": {},
|
||||
"type": "script"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"autocomplete": "new-password",
|
||||
"disabled": false,
|
||||
"name": "password",
|
||||
"node_type": "input",
|
||||
"required": true,
|
||||
"type": "password"
|
||||
},
|
||||
"group": "password",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1070001,
|
||||
"text": "Password",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"disabled": false,
|
||||
"name": "method",
|
||||
"node_type": "input",
|
||||
"type": "submit",
|
||||
"value": "password"
|
||||
},
|
||||
"group": "password",
|
||||
"messages": [],
|
||||
"meta": {
|
||||
"label": {
|
||||
"id": 1040001,
|
||||
"text": "Sign up",
|
||||
"type": "info"
|
||||
}
|
||||
},
|
||||
"type": "input"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "f5d1b6a3-a4bb-44f7-9161-f4f877efe9ad",
|
||||
"schema_id": "default",
|
||||
"schema_url": "http://localhost:4455/schemas/default",
|
||||
"state": "active",
|
||||
"state_changed_at": "2021-08-17T12:18:01.690425+02:00",
|
||||
"traits": {
|
||||
"email": "foo@bar.com",
|
||||
"website": "https://www.ory.sh"
|
||||
},
|
||||
"created_at": "2021-08-17T12:18:01.690741+02:00",
|
||||
"updated_at": "2021-08-17T12:18:01.690741+02:00"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"id": "OE7fnoAeqaydiBM4-fQsbYMiO-EObq97WYb_c1HuX8Crbsb2777xs-upv7muXE8hOLkm6lQHC1ahegnzw-aIsQ",
|
||||
"rawId": "OE7fnoAeqaydiBM4-fQsbYMiO-EObq97WYb_c1HuX8Crbsb2777xs-upv7muXE8hOLkm6lQHC1ahegnzw-aIsQ",
|
||||
"type": "public-key",
|
||||
"response": {
|
||||
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAACg",
|
||||
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiV3paQ1dVTG1hcTV4VEFsQTBZWUhscW91YnFBaGUxQVdkTFJaQ0lCQU1jTSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDQ1NSIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
|
||||
"signature": "MEQCIHtRzzmLJrTPucNIRpPkstxR8oGJEzrm558LFe2jHTesAiAy2SGuBMDkdVdMJU4WJR2qFSpbAHQUvwG--Gv3vK8vDA",
|
||||
"userHandle": "AAaaaaaaTD6lazfHs42XPg"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"credentials": [
|
||||
{
|
||||
"id": "OE7fnoAeqaydiBM4+fQsbYMiO+EObq97WYb/c1HuX8Crbsb2777xs+upv7muXE8hOLkm6lQHC1ahegnzw+aIsQ==",
|
||||
"public_key": "pQECAyYgASFYIPW2FsD6d/Lc7SU33hMhJUxafOA3JWpsLka8eKO+OPRkIlggkkPt8ocrupQOuvy+8HbQLSLiu899EdchJlWdMPE1tiw=",
|
||||
"attestation_type": "none",
|
||||
"authenticator": {
|
||||
"aaguid": "AAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"sign_count": 3,
|
||||
"clone_warning": false
|
||||
},
|
||||
"display_name": "some-key",
|
||||
"added_at": "2021-08-17T10:18:55Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"webauthn_session_data": {
|
||||
"challenge": "WzZCWULmaq5xTAlA0YYHlqoubqAhe1AWdLRZCIBAMcM",
|
||||
"user_id": "9dG2o6S7RPeRYfT4d+/prQ==",
|
||||
"allowed_credentials": [
|
||||
"OE7fnoAeqaydiBM4+fQsbYMiO+EObq97WYb/c1HuX8Crbsb2777xs+upv7muXE8hOLkm6lQHC1ahegnzw+aIsQ=="
|
||||
],
|
||||
"userVerification": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"id": "OE7fnoAeqaydiBM4-fQsbYMiO-EObq97WYb_c1HuX8Crbsb2777xs-upv7muXE8hOLkm6lQHC1ahegnzw-aIsQ",
|
||||
"rawId": "OE7fnoAeqaydiBM4-fQsbYMiO-EObq97WYb_c1HuX8Crbsb2777xs-upv7muXE8hOLkm6lQHC1ahegnzw-aIsQ",
|
||||
"type": "public-key",
|
||||
"response": {
|
||||
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAACg",
|
||||
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiV3paQ1dVTG1hcTV4VEFsQTBZWUhscW91YnFBaGUxQVdkTFJaQ0lCQU1jTSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDQ1NSIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
|
||||
"signature": "MEQCIHtRzzmLJrTPucNIRpPkstxR8oGJEzrm558LFe2jHTesAiAy2SGuBMDkdVdMJU4WJR2qFSpbAHQUvwG--Gv3vK8vDA",
|
||||
"userHandle": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"credentials": [
|
||||
{
|
||||
"id": "OE7fnoAeqaydiBM4+fQsbYMiO+EObq97WYb/c1HuX8Crbsb2777xs+upv7muXE8hOLkm6lQHC1ahegnzw+aIsQ==",
|
||||
"public_key": "pQECAyYgASFYIPW2FsD6d/Lc7SU33hMhJUxafOA3JWpsLka8eKO+OPRkIlggkkPt8ocrupQOuvy+8HbQLSLiu899EdchJlWdMPE1tiw=",
|
||||
"attestation_type": "none",
|
||||
"authenticator": {
|
||||
"aaguid": "AAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"sign_count": 3,
|
||||
"clone_warning": false
|
||||
},
|
||||
"display_name": "some-key",
|
||||
"added_at": "2021-08-17T10:18:55Z",
|
||||
"is_passwordless": false
|
||||
}
|
||||
],
|
||||
"user_handle": "9dG2o6S7RPeRYfT4d+/prQ=="
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"webauthn_session_data": {
|
||||
"challenge": "WzZCWULmaq5xTAlA0YYHlqoubqAhe1AWdLRZCIBAMcM",
|
||||
"user_id": "9dG2o6S7RPeRYfT4d+/prQ==",
|
||||
"allowed_credentials": [
|
||||
"OE7fnoAeqaydiBM4+fQsbYMiO+EObq97WYb/c1HuX8Crbsb2777xs+upv7muXE8hOLkm6lQHC1ahegnzw+aIsQ=="
|
||||
],
|
||||
"userVerification": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"id": "OE7fnoAeqaydiBM4-fQsbYMiO-EObq97WYb_c1HuX8Crbsb2777xs-upv7muXE8hOLkm6lQHC1ahegnzw-aIsQ",
|
||||
"rawId": "OE7fnoAeqaydiBM4-fQsbYMiO-EObq97WYb_c1HuX8Crbsb2777xs-upv7muXE8hOLkm6lQHC1ahegnzw-aIsQ",
|
||||
"type": "public-key",
|
||||
"response": {
|
||||
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAACg",
|
||||
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiV3paQ1dVTG1hcTV4VEFsQTBZWUhscW91YnFBaGUxQVdkTFJaQ0lCQU1jTSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDQ1NSIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
|
||||
"signature": "MEQCIHtRzzmLJrTPucNIRpPkstxR8oGJEzrm558LFe2jHTesAiAy2SGuBMDkdVdMJU4WJR2qFSpbAHQUvwG--Gv3vK8vDA",
|
||||
"userHandle": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"credentials": [
|
||||
{
|
||||
"id": "OE7fnoAeqaydiBM4+fQsbYMiO+EObq97WYb/c1HuX8Crbsb2777xs+upv7muXE8hOLkm6lQHC1ahegnzw+aIsQ==",
|
||||
"public_key": "pQECAyYgASFYIPW2FsD6d/Lc7SU33hMhJUxafOA3JWpsLka8eKO+OPRkIlggkkPt8ocrupQOuvy+8HbQLSLiu899EdchJlWdMPE1tiw=",
|
||||
"attestation_type": "none",
|
||||
"authenticator": {
|
||||
"aaguid": "AAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"sign_count": 3,
|
||||
"clone_warning": false
|
||||
},
|
||||
"display_name": "some-key",
|
||||
"added_at": "2021-08-17T10:18:55Z",
|
||||
"is_passwordless": false
|
||||
}
|
||||
],
|
||||
"user_handle": "RPwiyauuTD6lazfHs42XPg=="
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"webauthn_session_data": {
|
||||
"challenge": "WzZCWULmaq5xTAlA0YYHlqoubqAhe1AWdLRZCIBAMcM",
|
||||
"user_id": "RPwiyauuTD6lazfHs42XPg==",
|
||||
"allowed_credentials": [
|
||||
"OE7fnoAeqaydiBM4+fQsbYMiO+EObq97WYb/c1HuX8Crbsb2777xs+upv7muXE8hOLkm6lQHC1ahegnzw+aIsQ=="
|
||||
],
|
||||
"userVerification": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"id": "OE7fnoAeqaydiBM4-fQsbYMiO-EObq97WYb_c1HuX8Crbsb2777xs-upv7muXE8hOLkm6lQHC1ahegnzw-aIsQ",
|
||||
"rawId": "OE7fnoAeqaydiBM4-fQsbYMiO-EObq97WYb_c1HuX8Crbsb2777xs-upv7muXE8hOLkm6lQHC1ahegnzw-aIsQ",
|
||||
"type": "public-key",
|
||||
"response": {
|
||||
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAACg",
|
||||
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiV3paQ1dVTG1hcTV4VEFsQTBZWUhscW91YnFBaGUxQVdkTFJaQ0lCQU1jTSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDQ1NSIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
|
||||
"signature": "MEQCIHtRzzmLJrTPucNIRpPkstxR8oGJEzrm558LFe2jHTesAiAy2SGuBMDkdVdMJU4WJR2qFSpbAHQUvwG--Gv3vK8vDA",
|
||||
"userHandle": "RPwiyauuTD6lazfHs42XPg"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"credentials": [
|
||||
{
|
||||
"id": "OE7fnoAeqaydiBM4+fQsbYMiO+EObq97WYb/c1HuX8Crbsb2777xs+upv7muXE8hOLkm6lQHC1ahegnzw+aIsQ==",
|
||||
"public_key": "pQECAyYgASFYIPW2FsD6d/Lc7SU33hMhJUxafOA3JWpsLka8eKO+OPRkIlggkkPt8ocrupQOuvy+8HbQLSLiu899EdchJlWdMPE1tiw=",
|
||||
"attestation_type": "none",
|
||||
"authenticator": {
|
||||
"aaguid": "AAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"sign_count": 3,
|
||||
"clone_warning": false
|
||||
},
|
||||
"display_name": "some-key",
|
||||
"added_at": "2021-08-17T10:18:55Z",
|
||||
"is_passwordless": true
|
||||
}
|
||||
],
|
||||
"user_handle": "9dG2o6S7RPeRYfT4d+/prQ=="
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"webauthn_session_data": {
|
||||
"challenge": "WzZCWULmaq5xTAlA0YYHlqoubqAhe1AWdLRZCIBAMcM",
|
||||
"user_id": "9dG2o6S7RPeRYfT4d+/prQ==",
|
||||
"allowed_credentials": [
|
||||
"OE7fnoAeqaydiBM4+fQsbYMiO+EObq97WYb/c1HuX8Crbsb2777xs+upv7muXE8hOLkm6lQHC1ahegnzw+aIsQ=="
|
||||
],
|
||||
"userVerification": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"id": "OE7fnoAeqaydiBM4-fQsbYMiO-EObq97WYb_c1HuX8Crbsb2777xs-upv7muXE8hOLkm6lQHC1ahegnzw-aIsQ",
|
||||
"rawId": "OE7fnoAeqaydiBM4-fQsbYMiO-EObq97WYb_c1HuX8Crbsb2777xs-upv7muXE8hOLkm6lQHC1ahegnzw-aIsQ",
|
||||
"type": "public-key",
|
||||
"response": {
|
||||
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAACg",
|
||||
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiV3paQ1dVTG1hcTV4VEFsQTBZWUhscW91YnFBaGUxQVdkTFJaQ0lCQU1jTSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDQ1NSIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
|
||||
"signature": "MEQCIHtRzzmLJrTPucNIRpPkstxR8oGJEzrm558LFe2jHTesAiAy2SGuBMDkdVdMJU4WJR2qFSpbAHQUvwG--Gv3vK8vDA",
|
||||
"userHandle": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"webauthn_session_data": {
|
||||
"challenge": "UlxHSTkuMvtVDoV9y5lhu9OyNUP8P7MP0RYAT6Im_rY",
|
||||
"user_id": "",
|
||||
"userVerification": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"webauthn_session_data": {
|
||||
"challenge": "UlxHSTkuMvtVDoV9y5lhu9OyNUP8P7MP0RYAT6Im_rY",
|
||||
"user_id": "wrong",
|
||||
"userVerification": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"id": "6e11a9a7-62fd-4c88-871a-097f18f0306f",
|
||||
"schema_id": "default",
|
||||
"schema_url": "http://localhost:4455/schemas/default",
|
||||
"state": "active",
|
||||
"state_changed_at": "2021-08-17T11:15:59.232051+02:00",
|
||||
"traits": {
|
||||
"email": "foo@bar.com",
|
||||
"website": "https://www.ory.sh"
|
||||
},
|
||||
"created_at": "2021-08-17T11:15:59.232288+02:00",
|
||||
"updated_at": "2021-08-17T11:15:59.232288+02:00"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"totp_url": "otpauth://totp/issuer.ory.sh:6e11a9a7-62fd-4c88-871a-097f18f0306f?algorithm=SHA1&digits=6&issuer=issuer.ory.sh&period=30&secret=2F43HRJNMUW67EDMRR7AKQYRZP3AI6IG",
|
||||
"webauthn_session_data": {
|
||||
"challenge": "UlxHSTkuMvtVDoV9y5lhu9OyNUP8P7MP0RYAT6Im_rY",
|
||||
"user_id": "bhGpp2L9TIiHGgl/GPAwbw==",
|
||||
"userVerification": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"id": "L1yOrxHy5Lq72lAPahaWdl0q9gsXRzV2BJ4xJmkTVH_8uuVKU-FVbJlVRwYGzPNc1IjCWUYAK0H0YSpd5hz-Pg",
|
||||
"rawId": "L1yOrxHy5Lq72lAPahaWdl0q9gsXRzV2BJ4xJmkTVH_8uuVKU-FVbJlVRwYGzPNc1IjCWUYAK0H0YSpd5hz-Pg",
|
||||
"type": "public-key",
|
||||
"response": {
|
||||
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjESZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NBAAAABAAAAAAAAAAAAAAAAAAAAAAAQC9cjq8R8uS6u9pQD2oWlnZdKvYLF0c1dgSeMSZpE1R__LrlSlPhVWyZVUcGBszzXNSIwllGACtB9GEqXeYc_j6lAQIDJiABIVggFFzdor6hBMgrpYLCds8Uu2JtPaaaxKU6LEAUT6QRZ5UiWCA24TI4vED6rrTUjykchoAln67u5GT1nwmzjvrk79HhlQ",
|
||||
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiVWx4SFNUa3VNdnRWRG9WOXk1bGh1OU95TlVQOFA3TVAwUllBVDZJbV9yWSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDQ1NSIsImNyb3NzT3JpZ2luIjpmYWxzZX0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"id": "6e11a9a7-62fd-4c88-871a-097f18f0306f",
|
||||
"schema_id": "default",
|
||||
"schema_url": "http://localhost:4455/schemas/default",
|
||||
"state": "active",
|
||||
"state_changed_at": "2021-08-17T11:15:59.232051+02:00",
|
||||
"traits": {
|
||||
"email": "foo@bar.com",
|
||||
"website": "https://www.ory.sh"
|
||||
},
|
||||
"created_at": "2021-08-17T11:15:59.232288+02:00",
|
||||
"updated_at": "2021-08-17T11:15:59.232288+02:00"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"totp_url": "otpauth://totp/issuer.ory.sh:6e11a9a7-62fd-4c88-871a-097f18f0306f?algorithm=SHA1&digits=6&issuer=issuer.ory.sh&period=30&secret=2F43HRJNMUW67EDMRR7AKQYRZP3AI6IG",
|
||||
"webauthn_session_data": {
|
||||
"challenge": "UlxHSTkuMvtVDoV9y5lhu9OyNUP8P7MP0RYAT6Im_rY",
|
||||
"user_id": "bhGpp2L9TIiHGgl/GPAwbw==",
|
||||
"userVerification": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"id": "L1yOrxHy5Lq72lAPahaWdl0q9gsXRzV2BJ4xJmkTVH_8uuVKU-FVbJlVRwYGzPNc1IjCWUYAK0H0YSpd5hz-Pg",
|
||||
"rawId": "L1yOrxHy5Lq72lAPahaWdl0q9gsXRzV2BJ4xJmkTVH_8uuVKU-FVbJlVRwYGzPNc1IjCWUYAK0H0YSpd5hz-Pg",
|
||||
"type": "public-key",
|
||||
"response": {
|
||||
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjESZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NBAAAABAAAAAAAAAAAAAAAAAAAAAAAQC9cjq8R8uS6u9pQD2oWlnZdKvYLF0c1dgSeMSZpE1R__LrlSlPhVWyZVUcGBszzXNSIwllGACtB9GEqXeYc_j6lAQIDJiABIVggFFzdor6hBMgrpYLCds8Uu2JtPaaaxKU6LEAUT6QRZ5UiWCA24TI4vED6rrTUjykchoAln67u5GT1nwmzjvrk79HhlQ",
|
||||
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiVWx4SFNUa3VNdnRWRG9WOXk1bGh1OU95TlVQOFA3TVAwUllBVDZJbV9yWSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDQ1NSIsImNyb3NzT3JpZ2luIjpmYWxzZX0"
|
||||
}
|
||||
}
|
||||
444
selfservice/strategy/webauthn/login.go
Normal file
444
selfservice/strategy/webauthn/login.go
Normal file
@@ -0,0 +1,444 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package webauthn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/ory/x/otelx"
|
||||
|
||||
"github.com/ory/kratos/selfservice/strategy/idfirst"
|
||||
|
||||
"github.com/ory/kratos/selfservice/flowhelpers"
|
||||
"github.com/ory/kratos/session"
|
||||
"github.com/ory/kratos/x/webauthnx"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
|
||||
"github.com/ory/kratos/text"
|
||||
"github.com/ory/kratos/ui/node"
|
||||
"github.com/ory/kratos/x"
|
||||
|
||||
"github.com/go-webauthn/webauthn/protocol"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ory/herodot"
|
||||
"github.com/ory/kratos/identity"
|
||||
"github.com/ory/kratos/schema"
|
||||
"github.com/ory/kratos/selfservice/flow"
|
||||
"github.com/ory/kratos/selfservice/flow/login"
|
||||
"github.com/ory/x/decoderx"
|
||||
)
|
||||
|
||||
var (
|
||||
_ login.AAL1FormHydrator = new(Strategy)
|
||||
_ login.AAL2FormHydrator = new(Strategy)
|
||||
)
|
||||
|
||||
func (s *Strategy) populateLoginMethodForPasswordless(r *http.Request, sr *login.Flow) error {
|
||||
sr.UI.SetCSRF(s.d.GenerateCSRFToken(r))
|
||||
sr.UI.GetNodes().Append(node.NewInputField("method", "webauthn", node.WebAuthnGroup, node.InputAttributeTypeSubmit).WithMetaLabel(text.NewInfoSelfServiceLoginWebAuthn()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) populateLoginMethod(r *http.Request, sr *login.Flow, i *identity.Identity, label *text.Message, aal identity.AuthenticatorAssuranceLevel) error {
|
||||
id, err := s.d.PrivilegedIdentityPool().GetIdentityConfidential(r.Context(), i.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cred, ok := id.GetCredentials(s.ID())
|
||||
if !ok {
|
||||
// Identity has no webauth
|
||||
return nil
|
||||
}
|
||||
|
||||
var conf identity.CredentialsWebAuthnConfig
|
||||
if err := json.Unmarshal(cred.Config, &conf); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
webAuthCreds := conf.Credentials.ToWebAuthnFiltered(aal, nil)
|
||||
if len(webAuthCreds) == 0 {
|
||||
// Identity has no webauthn
|
||||
return webauthnx.ErrNoCredentials
|
||||
}
|
||||
|
||||
web, err := webauthn.New(s.d.Config().WebAuthnConfig(r.Context()))
|
||||
if err != nil {
|
||||
return errors.WithStack(herodot.ErrInternalServerError().WithReasonf("Unable to initiate WebAuth.").WithDebug(err.Error()))
|
||||
}
|
||||
|
||||
options, sessionData, err := web.BeginLogin(webauthnx.NewUser(conf.UserHandle, webAuthCreds, web.Config))
|
||||
if err != nil {
|
||||
return errors.WithStack(herodot.ErrInternalServerError().WithReasonf("Unable to initiate WebAuth login.").WithDebug(err.Error()))
|
||||
}
|
||||
|
||||
// Remove the WebAuthn URL from the internal context now that it is set!
|
||||
sr.InternalContext, err = sjson.SetBytes(sr.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeySessionData), sessionData)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
injectWebAuthnOptions, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if len(cred.Identifiers) > 0 {
|
||||
sr.UI.SetNode(node.NewInputField("identifier", cred.Identifiers[0], node.DefaultGroup, node.InputAttributeTypeHidden))
|
||||
}
|
||||
|
||||
sr.UI.SetCSRF(s.d.GenerateCSRFToken(r))
|
||||
sr.UI.Nodes.Upsert(webauthnx.NewWebAuthnScript(s.d.Config().SelfPublicURL(r.Context())))
|
||||
sr.UI.SetNode(webauthnx.NewWebAuthnLoginTrigger(string(injectWebAuthnOptions)).
|
||||
WithMetaLabel(label))
|
||||
sr.UI.Nodes.Upsert(webauthnx.NewWebAuthnLoginInput())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) handleLoginError(r *http.Request, f *login.Flow, err error) error {
|
||||
if f != nil {
|
||||
f.UI.Nodes.ResetNodes("webauthn_login")
|
||||
if f.Type == flow.TypeBrowser {
|
||||
f.UI.SetCSRF(s.d.GenerateCSRFToken(r))
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Update Login Flow with WebAuthn Method
|
||||
//
|
||||
// swagger:model updateLoginFlowWithWebAuthnMethod
|
||||
type updateLoginFlowWithWebAuthnMethod struct {
|
||||
// Identifier is the email or username of the user trying to log in.
|
||||
//
|
||||
// required: true
|
||||
Identifier string `json:"identifier"`
|
||||
|
||||
// Method should be set to "webAuthn" when logging in using the WebAuthn strategy.
|
||||
//
|
||||
// required: true
|
||||
Method string `json:"method"`
|
||||
|
||||
// Sending the anti-csrf token is only required for browser login flows.
|
||||
CSRFToken string `json:"csrf_token"`
|
||||
|
||||
// Login a WebAuthn Security Key
|
||||
//
|
||||
// This must contain the ID of the WebAuthN connection.
|
||||
Login string `json:"webauthn_login"`
|
||||
|
||||
// Transient data to pass along to any webhooks
|
||||
//
|
||||
// required: false
|
||||
TransientPayload json.RawMessage `json:"transient_payload,omitempty" form:"transient_payload"`
|
||||
}
|
||||
|
||||
func (s *Strategy) Login(w http.ResponseWriter, r *http.Request, f *login.Flow, sess *session.Session) (i *identity.Identity, err error) {
|
||||
ctx, span := s.d.Tracer(r.Context()).Tracer().Start(r.Context(), "selfservice.strategy.webauthn.Strategy.Login")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
if f.Type != flow.TypeBrowser {
|
||||
span.SetAttributes(attribute.String("not_responsible_reason", "flow type is not browser"))
|
||||
return nil, flow.ErrStrategyNotResponsible
|
||||
}
|
||||
|
||||
var p updateLoginFlowWithWebAuthnMethod
|
||||
if err := decoderx.Decode(r, &p,
|
||||
decoderx.HTTPKeepRequestBody(true),
|
||||
decoderx.HTTPDecoderSetValidatePayloads(true),
|
||||
decoderx.MustHTTPRawJSONSchemaCompiler(loginSchema),
|
||||
decoderx.HTTPDecoderJSONFollowsFormFormat()); err != nil {
|
||||
return nil, s.handleLoginError(r, f, err)
|
||||
}
|
||||
f.TransientPayload = p.TransientPayload
|
||||
|
||||
if len(p.Login) > 0 || p.Method == s.SettingsStrategyID() {
|
||||
// This method has only two submit buttons
|
||||
p.Method = s.SettingsStrategyID()
|
||||
} else {
|
||||
span.SetAttributes(attribute.String("not_responsible_reason", "login is not provided and method is not webauthn"))
|
||||
return nil, flow.ErrStrategyNotResponsible
|
||||
}
|
||||
|
||||
if err := flow.MethodEnabledAndAllowed(ctx, f.GetFlowName(), s.SettingsStrategyID(), p.Method, s.d); err != nil {
|
||||
return nil, s.handleLoginError(r, f, err)
|
||||
}
|
||||
|
||||
if err := flow.EnsureCSRF(s.d, r, f.Type, s.d.Config().DisableAPIFlowEnforcement(ctx), s.d.GenerateCSRFToken, p.CSRFToken); err != nil {
|
||||
return nil, s.handleLoginError(r, f, err)
|
||||
}
|
||||
|
||||
if s.d.Config().WebAuthnForPasswordless(ctx) || f.IsRefresh() && f.RequestedAAL == identity.AuthenticatorAssuranceLevel1 {
|
||||
return s.loginPasswordless(ctx, w, r, f, &p)
|
||||
}
|
||||
|
||||
return s.loginMultiFactor(ctx, r, f, sess.IdentityID, &p)
|
||||
}
|
||||
|
||||
func (s *Strategy) loginPasswordless(ctx context.Context, w http.ResponseWriter, r *http.Request, f *login.Flow, p *updateLoginFlowWithWebAuthnMethod) (i *identity.Identity, err error) {
|
||||
ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.webauthn.Strategy.loginPasswordless")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
if err := login.CheckAAL(f, identity.AuthenticatorAssuranceLevel1); err != nil {
|
||||
span.SetAttributes(attribute.String("not_responsible_reason", "requested AAL is not AAL1"))
|
||||
return nil, s.handleLoginError(r, f, err)
|
||||
}
|
||||
|
||||
if err := flow.EnsureCSRF(s.d, r, f.Type, s.d.Config().DisableAPIFlowEnforcement(ctx), s.d.GenerateCSRFToken, p.CSRFToken); err != nil {
|
||||
return nil, s.handleLoginError(r, f, err)
|
||||
}
|
||||
|
||||
if p.Identifier == "" {
|
||||
return nil, s.handleLoginError(r, f, errors.WithStack(herodot.ErrBadRequest().WithReason("identifier is required")))
|
||||
}
|
||||
|
||||
i, _, err = s.d.PrivilegedIdentityPool().FindByCredentialsIdentifier(ctx, s.ID(), p.Identifier)
|
||||
if err != nil {
|
||||
time.Sleep(x.RandomDelay(s.d.Config().HasherArgon2(ctx).ExpectedDuration, s.d.Config().HasherArgon2(ctx).ExpectedDeviation))
|
||||
return nil, s.handleLoginError(r, f, errors.WithStack(schema.NewNoWebAuthnCredentials()))
|
||||
}
|
||||
|
||||
if len(p.Login) == 0 {
|
||||
// Reset all nodes to not confuse users.
|
||||
// This is kinda hacky and will probably need to be updated at some point.
|
||||
previousNodes := f.UI.Nodes
|
||||
f.UI.Nodes = node.Nodes{}
|
||||
|
||||
if err := s.populateLoginMethod(r, f, i, text.NewInfoSelfServiceLoginContinue(), identity.AuthenticatorAssuranceLevel1); errors.Is(err, webauthnx.ErrNoCredentials) {
|
||||
f.UI.Nodes = previousNodes
|
||||
return nil, s.handleLoginError(r, f, schema.NewNoWebAuthnCredentials())
|
||||
} else if err != nil {
|
||||
return nil, s.handleLoginError(r, f, err)
|
||||
}
|
||||
|
||||
// Adds the "Continue" button
|
||||
f.UI.SetCSRF(s.d.GenerateCSRFToken(r))
|
||||
f.UI.Messages.Add(text.NewInfoLoginWebAuthnPasswordless())
|
||||
f.UI.SetNode(node.NewInputField("identifier", p.Identifier, node.DefaultGroup, node.InputAttributeTypeHidden, node.WithRequiredInputAttribute))
|
||||
if err := s.d.LoginFlowPersister().UpdateLoginFlow(ctx, f); err != nil {
|
||||
return nil, s.handleLoginError(r, f, err)
|
||||
}
|
||||
|
||||
redirectTo := f.AppendTo(s.d.Config().SelfServiceFlowLoginUI(ctx)).String()
|
||||
if x.IsJSONRequest(r) {
|
||||
s.d.Writer().WriteError(w, r, flow.NewBrowserLocationChangeRequiredError(redirectTo))
|
||||
} else {
|
||||
http.Redirect(w, r, f.AppendTo(s.d.Config().SelfServiceFlowLoginUI(ctx)).String(), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
return nil, errors.WithStack(flow.ErrCompletedByStrategy)
|
||||
}
|
||||
|
||||
return s.loginAuthenticate(ctx, r, f, i.ID, p, identity.AuthenticatorAssuranceLevel1)
|
||||
}
|
||||
|
||||
func (s *Strategy) loginAuthenticate(ctx context.Context, r *http.Request, f *login.Flow, identityID uuid.UUID, p *updateLoginFlowWithWebAuthnMethod, aal identity.AuthenticatorAssuranceLevel) (_ *identity.Identity, err error) {
|
||||
ctx, span := s.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.webauthn.Strategy.loginAuthenticate")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
i, err := s.d.PrivilegedIdentityPool().GetIdentityConfidential(ctx, identityID)
|
||||
if err != nil {
|
||||
return nil, s.handleLoginError(r, f, errors.WithStack(schema.NewNoWebAuthnRegistered()))
|
||||
}
|
||||
|
||||
c, ok := i.GetCredentials(s.ID())
|
||||
if !ok {
|
||||
return nil, s.handleLoginError(r, f, errors.WithStack(schema.NewNoWebAuthnRegistered()))
|
||||
}
|
||||
|
||||
var o identity.CredentialsWebAuthnConfig
|
||||
if err := json.Unmarshal(c.Config, &o); err != nil {
|
||||
return nil, s.handleLoginError(r, f, errors.WithStack(herodot.ErrInternalServerError().WithReason("The WebAuthn credentials could not be decoded properly").WithDebug(err.Error()).WithWrap(err)))
|
||||
}
|
||||
|
||||
web, err := webauthn.New(s.d.Config().WebAuthnConfig(ctx))
|
||||
if err != nil {
|
||||
return nil, s.handleLoginError(r, f, errors.WithStack(herodot.ErrInternalServerError().WithReasonf("Unable to get webAuthn config.").WithDebug(err.Error())))
|
||||
}
|
||||
|
||||
webAuthnResponse, err := protocol.ParseCredentialRequestResponseBody(strings.NewReader(p.Login))
|
||||
if err != nil {
|
||||
return nil, s.handleLoginError(r, f, errors.WithStack(herodot.ErrBadRequest().WithReasonf("Unable to parse WebAuthn response.").WithDebug(err.Error())))
|
||||
}
|
||||
|
||||
var webAuthnSess webauthn.SessionData
|
||||
if err := json.Unmarshal([]byte(gjson.GetBytes(f.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeySessionData)).Raw), &webAuthnSess); err != nil {
|
||||
return nil, s.handleLoginError(r, f, errors.WithStack(herodot.ErrInternalServerError().WithReasonf("Expected WebAuthN in internal context to be an object but got: %s", err)))
|
||||
}
|
||||
|
||||
webAuthCreds := o.Credentials.ToWebAuthnFiltered(aal, &webAuthnResponse.Response.AuthenticatorData.Flags)
|
||||
if f.IsRefresh() {
|
||||
webAuthCreds = o.Credentials.ToWebAuthn()
|
||||
}
|
||||
|
||||
validatedCredential, err := web.ValidateLogin(webauthnx.NewUser(o.UserHandle, webAuthCreds, web.Config), webAuthnSess, webAuthnResponse)
|
||||
if err != nil {
|
||||
return nil, s.handleLoginError(r, f, errors.WithStack(schema.NewWebAuthnVerifierWrongError("#/")))
|
||||
}
|
||||
|
||||
// Persist the updated signature counter and clone warning for W3C WebAuthn
|
||||
// clone detection. This must not block the login: the user already proved
|
||||
// possession of the authenticator.
|
||||
webauthnx.UpdateSignCountAfterLogin(ctx, s.d, i, identity.CredentialsTypeWebAuthn, validatedCredential)
|
||||
|
||||
// Remove the WebAuthn URL from the internal context now that it is set!
|
||||
f.InternalContext, err = sjson.DeleteBytes(f.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeySessionData))
|
||||
if err != nil {
|
||||
return nil, s.handleLoginError(r, f, errors.WithStack(err))
|
||||
}
|
||||
|
||||
f.Active = s.ID()
|
||||
if err = s.d.LoginFlowPersister().UpdateLoginFlow(ctx, f); err != nil {
|
||||
return nil, s.handleLoginError(r, f, errors.WithStack(herodot.ErrInternalServerError().WithReason("Could not update flow").WithDebug(err.Error())))
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (s *Strategy) loginMultiFactor(ctx context.Context, r *http.Request, f *login.Flow, identityID uuid.UUID, p *updateLoginFlowWithWebAuthnMethod) (*identity.Identity, error) {
|
||||
if err := login.CheckAAL(f, identity.AuthenticatorAssuranceLevel2); err != nil {
|
||||
trace.SpanFromContext(ctx).SetAttributes(attribute.String("not_responsible_reason", "requested AAL is not AAL2"))
|
||||
return nil, err
|
||||
}
|
||||
return s.loginAuthenticate(ctx, r, f, identityID, p, identity.AuthenticatorAssuranceLevel2)
|
||||
}
|
||||
|
||||
func (s *Strategy) populateLoginMethodRefresh(r *http.Request, sr *login.Flow) error {
|
||||
if sr.Type != flow.TypeBrowser {
|
||||
return nil
|
||||
}
|
||||
|
||||
identifier, id, _ := flowhelpers.GuessForcedLoginIdentifier(r, s.d, sr, s.ID())
|
||||
if identifier == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := s.populateLoginMethod(r, sr, id, text.NewInfoSelfServiceLoginWebAuthn(), sr.RequestedAAL); errors.Is(err, webauthnx.ErrNoCredentials) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sr.UI.SetCSRF(s.d.GenerateCSRFToken(r))
|
||||
sr.UI.SetNode(node.NewInputField("identifier", identifier, node.DefaultGroup, node.InputAttributeTypeHidden))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) PopulateLoginMethodFirstFactorRefresh(r *http.Request, sr *login.Flow, _ *session.Session) error {
|
||||
return s.populateLoginMethodRefresh(r, sr)
|
||||
}
|
||||
|
||||
func (s *Strategy) PopulateLoginMethodSecondFactorRefresh(r *http.Request, sr *login.Flow) error {
|
||||
return s.populateLoginMethodRefresh(r, sr)
|
||||
}
|
||||
|
||||
func (s *Strategy) PopulateLoginMethodFirstFactor(r *http.Request, sr *login.Flow) error {
|
||||
if sr.Type != flow.TypeBrowser || !s.d.Config().WebAuthnForPasswordless(r.Context()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ds, err := sr.IdentitySchema.URL(r.Context(), s.d.Config())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
identifierLabel, err := login.GetIdentifierLabelFromSchema(r.Context(), ds.String(), s.d.Config().SecurityDisallowRefInIdentitySchemas(r.Context()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sr.UI.SetNode(node.NewInputField(
|
||||
"identifier",
|
||||
"",
|
||||
node.DefaultGroup,
|
||||
node.InputAttributeTypeText,
|
||||
node.WithRequiredInputAttribute,
|
||||
func(attributes *node.InputAttributes) {
|
||||
attributes.Autocomplete = node.InputAttributeAutocompleteUsernameWebauthn
|
||||
},
|
||||
).WithMetaLabel(identifierLabel))
|
||||
|
||||
if err := s.populateLoginMethodForPasswordless(r, sr); errors.Is(err, webauthnx.ErrNoCredentials) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) PopulateLoginMethodSecondFactor(r *http.Request, sr *login.Flow) error {
|
||||
if sr.Type != flow.TypeBrowser || s.d.Config().WebAuthnForPasswordless(r.Context()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We have done proper validation before so this should never error.
|
||||
// The full expansion is required: the form hydrator reads the identity's credentials.
|
||||
sess, err := s.d.SessionManager().FetchFromRequest(r.Context(), r, session.ExpandEverything, identity.ExpandEverything)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.populateLoginMethod(r, sr, sess.Identity, text.NewInfoSelfServiceLoginWebAuthn(), identity.AuthenticatorAssuranceLevel2); errors.Is(err, webauthnx.ErrNoCredentials) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) PopulateLoginMethodIdentifierFirstCredentials(r *http.Request, sr *login.Flow, opts ...login.FormHydratorModifier) error {
|
||||
if sr.Type != flow.TypeBrowser || !s.d.Config().WebAuthnForPasswordless(r.Context()) {
|
||||
return errors.WithStack(idfirst.ErrNoCredentialsFound)
|
||||
}
|
||||
|
||||
o := login.NewFormHydratorOptions(opts)
|
||||
|
||||
var count int
|
||||
if o.IdentityHint != nil {
|
||||
var err error
|
||||
// If we have an identity hint we can perform identity credentials discovery and
|
||||
// hide this credential if it should not be included.
|
||||
if count, err = s.CountActiveFirstFactorCredentials(r.Context(), o.IdentityHint.Credentials); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if count > 0 || s.d.Config().SecurityAccountEnumerationMitigate(r.Context()) {
|
||||
if err := s.populateLoginMethodForPasswordless(r, sr); errors.Is(err, webauthnx.ErrNoCredentials) {
|
||||
if !s.d.Config().SecurityAccountEnumerationMitigate(r.Context()) {
|
||||
return errors.WithStack(idfirst.ErrNoCredentialsFound)
|
||||
}
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return errors.WithStack(idfirst.ErrNoCredentialsFound)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) PopulateLoginMethodIdentifierFirstIdentification(r *http.Request, sr *login.Flow) error {
|
||||
return nil
|
||||
}
|
||||
137
selfservice/strategy/webauthn/login_signcount_test.go
Normal file
137
selfservice/strategy/webauthn/login_signcount_test.go
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright © 2025 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package webauthn_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/ory/x/configx"
|
||||
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/kratos/identity"
|
||||
"github.com/ory/kratos/pkg"
|
||||
kratos "github.com/ory/kratos/pkg/httpclient"
|
||||
"github.com/ory/kratos/pkg/testhelpers"
|
||||
"github.com/ory/kratos/ui/node"
|
||||
)
|
||||
|
||||
// TestLoginUpdatesSignCount is a regression test for HackerOne #3780149.
|
||||
//
|
||||
// The go-webauthn library returns a *webauthn.Credential from ValidateLogin
|
||||
// whose Authenticator.SignCount reflects the signature counter reported by the
|
||||
// authenticator during the assertion. W3C WebAuthn clone detection relies on
|
||||
// the relying party persisting this incremented counter so that a replayed or
|
||||
// cloned authenticator (which reports a stale counter) can be detected.
|
||||
//
|
||||
// Before the fix, Kratos discarded the returned credential, so the stored
|
||||
// SignCount stayed at its registration value forever and clone detection was
|
||||
// inert.
|
||||
//
|
||||
// The MFA v1 fixtures store sign_count = 3 and the assertion response reports
|
||||
// counter = 10, so a correct implementation persists SignCount = 10.
|
||||
func TestLoginUpdatesSignCount(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conf, reg := pkg.NewFastRegistryWithMocks(t,
|
||||
configx.WithValue(config.ViperKeySelfServiceStrategyConfig+"."+string(identity.CredentialsTypePassword)+".enabled", false),
|
||||
enabledWebauthn,
|
||||
configx.WithValues(testhelpers.DefaultIdentitySchemaConfig("file://./stub/login.schema.json")),
|
||||
)
|
||||
|
||||
publicTS, _ := testhelpers.NewKratosServer(t, reg)
|
||||
_ = testhelpers.NewErrorTestServer(t, reg)
|
||||
_ = testhelpers.NewLoginUIFlowEchoServer(t, reg)
|
||||
_ = testhelpers.NewRedirSessionEchoTS(t, reg)
|
||||
|
||||
createIdentityWithWebAuthn := func(t *testing.T, c identity.Credentials) *identity.Identity {
|
||||
var id identity.Identity
|
||||
require.NoError(t, json.Unmarshal(loginFixtureSuccessIdentity, &id))
|
||||
|
||||
id.SetCredentials(identity.CredentialsTypeWebAuthn, identity.Credentials{
|
||||
Identifiers: []string{loginFixtureSuccessEmail},
|
||||
Config: c.Config,
|
||||
Type: identity.CredentialsTypeWebAuthn,
|
||||
Version: c.Version,
|
||||
})
|
||||
|
||||
_ = reg.PrivilegedIdentityPool().DeleteIdentity(t.Context(), id.ID)
|
||||
require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(t.Context(), &id))
|
||||
return &id
|
||||
}
|
||||
|
||||
submitWebAuthnLogin := func(t *testing.T, isSPA bool, id *identity.Identity, contextFixture []byte, cb func(values url.Values), opts ...testhelpers.InitFlowWithOption) (string, *http.Response, *kratos.LoginFlow) {
|
||||
client := testhelpers.NewHTTPClientWithIdentitySessionCookie(t.Context(), t, reg, id)
|
||||
f := testhelpers.InitializeLoginFlowViaBrowser(t, client, publicTS, false, isSPA, false, false, opts...)
|
||||
|
||||
interim, err := reg.LoginFlowPersister().GetLoginFlow(t.Context(), uuid.FromStringOrNil(f.Id))
|
||||
require.NoError(t, err)
|
||||
interim.InternalContext = contextFixture
|
||||
require.NoError(t, reg.LoginFlowPersister().UpdateLoginFlow(t.Context(), interim))
|
||||
|
||||
values := testhelpers.SDKFormFieldsToURLValues(f.Ui.Nodes)
|
||||
cb(values)
|
||||
body, res := testhelpers.LoginMakeRequest(t, false, isSPA, f, client, values.Encode())
|
||||
return body, res, f
|
||||
}
|
||||
|
||||
// Reads the stored first webauthn credential: the outer row (for id and
|
||||
// created_at) and the decoded authenticator state.
|
||||
storedCredential := func(t *testing.T, idID uuid.UUID) (*identity.Credentials, *identity.AuthenticatorWebAuthn) {
|
||||
stored, err := reg.PrivilegedIdentityPool().GetIdentityConfidential(t.Context(), idID)
|
||||
require.NoError(t, err)
|
||||
c, ok := stored.GetCredentials(identity.CredentialsTypeWebAuthn)
|
||||
require.True(t, ok)
|
||||
var conf identity.CredentialsWebAuthnConfig
|
||||
require.NoError(t, json.Unmarshal(c.Config, &conf))
|
||||
require.NotEmpty(t, conf.Credentials)
|
||||
require.NotNil(t, conf.Credentials[0].Authenticator)
|
||||
return c, conf.Credentials[0].Authenticator
|
||||
}
|
||||
|
||||
conf.MustSet(t.Context(), config.ViperKeySessionWhoAmIAAL, "aal1")
|
||||
|
||||
id := createIdentityWithWebAuthn(t, identity.Credentials{
|
||||
Config: loginFixtureSuccessV1Credentials,
|
||||
Version: 1,
|
||||
})
|
||||
|
||||
// Sanity: the registered credential starts at the fixture's sign_count = 3.
|
||||
credBefore, authBefore := storedCredential(t, id.ID)
|
||||
require.EqualValues(t, 3, authBefore.SignCount)
|
||||
|
||||
body, _, _ := submitWebAuthnLogin(t, true, id, loginFixtureSuccessV1Context, func(values url.Values) {
|
||||
values.Set("identifier", loginFixtureSuccessEmail)
|
||||
values.Set(node.WebAuthnLogin, string(loginFixtureSuccessV1Response))
|
||||
}, testhelpers.InitFlowWithAAL(identity.AuthenticatorAssuranceLevel2))
|
||||
|
||||
// The login must succeed.
|
||||
require.True(t, gjson.Get(body, "session.active").Bool(), "%s", body)
|
||||
|
||||
// The stored SignCount must be updated to the value reported by the
|
||||
// authenticator assertion (10), not left at the registration value (3).
|
||||
credAfter, auth := storedCredential(t, id.ID)
|
||||
assert.EqualValues(t, 10, auth.SignCount,
|
||||
"stored SignCount must be updated from the login assertion for clone detection to work")
|
||||
|
||||
// The assertion counter advanced (3 -> 10), so this is not a clone: the
|
||||
// persisted clone warning must round-trip as false rather than being set
|
||||
// spuriously on a normal login.
|
||||
assert.False(t, auth.CloneWarning,
|
||||
"a normally advancing counter must not set the clone warning")
|
||||
|
||||
// The credential row must be updated in place, not deleted and recreated:
|
||||
// its id and created_at must be stable across the login.
|
||||
assert.Equal(t, credBefore.ID, credAfter.ID,
|
||||
"the credential row must be updated in place (stable id), not recreated")
|
||||
assert.Equal(t, credBefore.CreatedAt, credAfter.CreatedAt,
|
||||
"persisting the sign counter must not reset the credential's created_at")
|
||||
}
|
||||
1029
selfservice/strategy/webauthn/login_test.go
Normal file
1029
selfservice/strategy/webauthn/login_test.go
Normal file
File diff suppressed because it is too large
Load Diff
17
selfservice/strategy/webauthn/nodes.go
Normal file
17
selfservice/strategy/webauthn/nodes.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright © 2025 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package webauthn
|
||||
|
||||
import (
|
||||
"github.com/ory/kratos/text"
|
||||
"github.com/ory/kratos/ui/node"
|
||||
"github.com/ory/kratos/x/webauthnx"
|
||||
)
|
||||
|
||||
func nodeWebauthnRegistrationOptions(opts []byte) *node.Node {
|
||||
return webauthnx.NewWebAuthnConnectionTrigger(string(opts)).WithMetaLabel(text.NewInfoSelfServiceRegistrationRegisterWebAuthn())
|
||||
}
|
||||
|
||||
func nodeDisplayName() *node.Node { return webauthnx.NewWebAuthnConnectionName() }
|
||||
func nodeConnectionInput() *node.Node { return webauthnx.NewWebAuthnConnectionInput() }
|
||||
292
selfservice/strategy/webauthn/registration.go
Normal file
292
selfservice/strategy/webauthn/registration.go
Normal file
@@ -0,0 +1,292 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package webauthn
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
|
||||
"github.com/ory/x/otelx"
|
||||
|
||||
"github.com/go-webauthn/webauthn/protocol"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
|
||||
"github.com/ory/herodot"
|
||||
"github.com/ory/kratos/identity"
|
||||
"github.com/ory/kratos/selfservice/flow"
|
||||
"github.com/ory/kratos/selfservice/flow/registration"
|
||||
"github.com/ory/kratos/ui/container"
|
||||
"github.com/ory/kratos/ui/node"
|
||||
"github.com/ory/kratos/x"
|
||||
"github.com/ory/kratos/x/webauthnx"
|
||||
)
|
||||
|
||||
var _ registration.FormHydrator = new(Strategy)
|
||||
|
||||
// Update Registration Flow with WebAuthn Method
|
||||
//
|
||||
// swagger:model updateRegistrationFlowWithWebAuthnMethod
|
||||
type updateRegistrationFlowWithWebAuthnMethod struct {
|
||||
// Register a WebAuthn Security Key
|
||||
//
|
||||
// It is expected that the JSON returned by the WebAuthn registration process
|
||||
// is included here.
|
||||
Register string `json:"webauthn_register"`
|
||||
|
||||
// Name of the WebAuthn Security Key to be Added
|
||||
//
|
||||
// A human-readable name for the security key which will be added.
|
||||
RegisterDisplayName string `json:"webauthn_register_displayname"`
|
||||
|
||||
// CSRFToken is the anti-CSRF token
|
||||
CSRFToken string `json:"csrf_token"`
|
||||
|
||||
// The identity's traits
|
||||
//
|
||||
// required: true
|
||||
Traits json.RawMessage `json:"traits"`
|
||||
|
||||
// Method
|
||||
//
|
||||
// Should be set to "webauthn" when trying to add, update, or remove a webAuthn pairing.
|
||||
//
|
||||
// required: true
|
||||
Method string `json:"method"`
|
||||
|
||||
// Flow is flow ID.
|
||||
//
|
||||
// swagger:ignore
|
||||
Flow string `json:"flow"`
|
||||
|
||||
// Transient data to pass along to any webhooks
|
||||
//
|
||||
// required: false
|
||||
TransientPayload json.RawMessage `json:"transient_payload,omitempty" form:"transient_payload"`
|
||||
}
|
||||
|
||||
func (s *Strategy) handleRegistrationError(r *http.Request, f *registration.Flow, p updateRegistrationFlowWithWebAuthnMethod, err error) error {
|
||||
if f != nil {
|
||||
for _, n := range container.NewFromJSON("", node.DefaultGroup, p.Traits, "traits").Nodes {
|
||||
// we only set the value and not the whole field because we want to keep types from the initial form generation
|
||||
f.UI.Nodes.SetValueAttribute(n.ID(), n.Attributes.GetValue())
|
||||
}
|
||||
|
||||
f.UI.Nodes.SetValueAttribute(node.WebAuthnRegisterDisplayName, p.RegisterDisplayName)
|
||||
|
||||
if f.Type == flow.TypeBrowser {
|
||||
f.UI.SetCSRF(s.d.GenerateCSRFToken(r))
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Strategy) decode(p *updateRegistrationFlowWithWebAuthnMethod, r *http.Request, ds *url.URL) error {
|
||||
return registration.DecodeBody(p, r, registrationSchema, ds)
|
||||
}
|
||||
|
||||
func (s *Strategy) Register(_ http.ResponseWriter, r *http.Request, regFlow *registration.Flow, i *identity.Identity) (err error) {
|
||||
ctx, span := s.d.Tracer(r.Context()).Tracer().Start(r.Context(), "selfservice.strategy.webauthn.Strategy.Register")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
if regFlow.Type != flow.TypeBrowser || !s.d.Config().WebAuthnForPasswordless(ctx) {
|
||||
span.SetAttributes(attribute.String("not_responsible_reason", "registration flow is not a browser flow or WebAuthn is not enabled"))
|
||||
return flow.ErrStrategyNotResponsible
|
||||
}
|
||||
|
||||
ds, err := regFlow.IdentitySchema.URL(ctx, s.d.Config())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var p updateRegistrationFlowWithWebAuthnMethod
|
||||
if err := s.decode(&p, r, ds); err != nil {
|
||||
return s.handleRegistrationError(r, regFlow, p, err)
|
||||
}
|
||||
|
||||
regFlow.TransientPayload = p.TransientPayload
|
||||
|
||||
if err := flow.EnsureCSRF(s.d, r, regFlow.Type, s.d.Config().DisableAPIFlowEnforcement(ctx), s.d.GenerateCSRFToken, p.CSRFToken); err != nil {
|
||||
return s.handleRegistrationError(r, regFlow, p, err)
|
||||
}
|
||||
|
||||
if len(p.Register) == 0 {
|
||||
span.SetAttributes(attribute.String("not_responsible_reason", "register field is empty"))
|
||||
return flow.ErrStrategyNotResponsible
|
||||
}
|
||||
|
||||
p.Method = s.SettingsStrategyID()
|
||||
if err := flow.MethodEnabledAndAllowed(ctx, regFlow.GetFlowName(), s.SettingsStrategyID(), p.Method, s.d); err != nil {
|
||||
return s.handleRegistrationError(r, regFlow, p, err)
|
||||
}
|
||||
|
||||
if len(p.Traits) == 0 {
|
||||
p.Traits = json.RawMessage("{}")
|
||||
}
|
||||
i.Traits = identity.Traits(p.Traits)
|
||||
|
||||
webAuthnSession := gjson.GetBytes(regFlow.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeySessionData))
|
||||
if !webAuthnSession.IsObject() {
|
||||
return s.handleRegistrationError(r, regFlow, p, errors.WithStack(
|
||||
herodot.ErrInternalServerError().WithReasonf("Expected WebAuthN in internal context to be an object.")))
|
||||
}
|
||||
|
||||
var webAuthnSess webauthn.SessionData
|
||||
if err := json.Unmarshal([]byte(webAuthnSession.Raw), &webAuthnSess); err != nil {
|
||||
return s.handleRegistrationError(r, regFlow, p, errors.WithStack(
|
||||
herodot.ErrInternalServerError().WithReasonf("Expected WebAuthN in internal context to be an object but got: %s", err)))
|
||||
}
|
||||
|
||||
webAuthnResponse, err := protocol.ParseCredentialCreationResponseBody(strings.NewReader(p.Register))
|
||||
if err != nil {
|
||||
return s.handleRegistrationError(r, regFlow, p, errors.WithStack(
|
||||
herodot.ErrBadRequest().WithReasonf("Unable to parse WebAuthn response: %s", err)))
|
||||
}
|
||||
|
||||
web, err := webauthn.New(s.d.Config().WebAuthnConfig(ctx))
|
||||
if err != nil {
|
||||
return s.handleRegistrationError(r, regFlow, p, errors.WithStack(
|
||||
herodot.ErrInternalServerError().WithReasonf("Unable to get webAuthn config.").WithDebug(err.Error())))
|
||||
}
|
||||
|
||||
credential, err := web.CreateCredential(webauthnx.NewUser(webAuthnSess.UserID, nil, web.Config), webAuthnSess, webAuthnResponse)
|
||||
if err != nil {
|
||||
if devErr := new(protocol.Error); errors.As(err, &devErr) {
|
||||
s.d.Logger().WithError(err).WithField("error_devinfo", devErr.DevInfo).Error("Failed to create WebAuthn credential")
|
||||
}
|
||||
return s.handleRegistrationError(r, regFlow, p, errors.WithStack(
|
||||
herodot.ErrInternalServerError().WithReasonf("Unable to create WebAuthn credential: %s", err)))
|
||||
}
|
||||
|
||||
credentialWebAuthn := identity.CredentialFromWebAuthn(credential, true)
|
||||
credentialWebAuthn.DisplayName = p.RegisterDisplayName
|
||||
credentialWebAuthnConfig, err := json.Marshal(identity.CredentialsWebAuthnConfig{
|
||||
Credentials: identity.CredentialsWebAuthn{*credentialWebAuthn},
|
||||
UserHandle: webAuthnSess.UserID,
|
||||
})
|
||||
if err != nil {
|
||||
return s.handleRegistrationError(r, regFlow, p, errors.WithStack(
|
||||
herodot.ErrInternalServerError().WithReasonf("Unable to encode identity credentials.").WithDebug(err.Error())))
|
||||
}
|
||||
|
||||
i.UpsertCredentialsConfig(s.ID(), credentialWebAuthnConfig, 1)
|
||||
if err := s.validateCredentials(ctx, i); err != nil {
|
||||
return s.handleRegistrationError(r, regFlow, p, err)
|
||||
}
|
||||
|
||||
// Remove the WebAuthn URL from the internal context now that it is set!
|
||||
regFlow.InternalContext, err = sjson.DeleteBytes(regFlow.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeySessionData))
|
||||
if err != nil {
|
||||
return s.handleRegistrationError(r, regFlow, p, err)
|
||||
}
|
||||
|
||||
if err := s.d.RegistrationFlowPersister().UpdateRegistrationFlow(ctx, regFlow); err != nil {
|
||||
return s.handleRegistrationError(r, regFlow, p, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) injectWebauthnRegistrationOptions(r *http.Request, f *registration.Flow) ([]byte, error) {
|
||||
ctx := r.Context()
|
||||
if options := gjson.GetBytes(f.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeyWebauthnOptions)); options.IsObject() {
|
||||
return []byte(options.Raw), nil
|
||||
}
|
||||
|
||||
web, err := webauthn.New(s.d.Config().WebAuthnConfig(ctx))
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
webauthID := x.NewUUID()
|
||||
user := webauthnx.NewUser(webauthID[:], nil, s.d.Config().WebAuthnConfig(ctx))
|
||||
option, sessionData, err := web.BeginRegistration(user)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
injectWebAuthnOptions, err := json.Marshal(option)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
f.InternalContext, err = sjson.SetBytes(f.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeySessionData), sessionData)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
f.InternalContext, err = sjson.SetRawBytes(f.InternalContext, flow.PrefixInternalContextKey(s.ID(), InternalContextKeyWebauthnOptions), injectWebAuthnOptions)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return injectWebAuthnOptions, nil
|
||||
}
|
||||
|
||||
func (s *Strategy) PopulateRegistrationMethod(r *http.Request, f *registration.Flow) error {
|
||||
ctx := r.Context()
|
||||
if f.Type != flow.TypeBrowser || !s.d.Config().WebAuthnForPasswordless(ctx) {
|
||||
return nil
|
||||
}
|
||||
|
||||
f.UI.SetCSRF(s.d.GenerateCSRFToken(r))
|
||||
opts, err := s.injectWebauthnRegistrationOptions(r, f)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
f.UI.Nodes.Upsert(nodeDisplayName())
|
||||
f.UI.Nodes.Upsert(nodeWebauthnRegistrationOptions(opts))
|
||||
|
||||
f.UI.Nodes.Upsert(webauthnx.NewWebAuthnScript(s.d.Config().SelfPublicURL(ctx)))
|
||||
f.UI.Nodes.Upsert(nodeConnectionInput())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) PopulateRegistrationMethodProfile(r *http.Request, f *registration.Flow, options ...registration.FormHydratorModifier) error {
|
||||
ctx := r.Context()
|
||||
if f.Type != flow.TypeBrowser || !s.d.Config().WebAuthnForPasswordless(ctx) {
|
||||
return nil
|
||||
}
|
||||
|
||||
f.UI.SetCSRF(s.d.GenerateCSRFToken(r))
|
||||
opts, err := s.injectWebauthnRegistrationOptions(r, f)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
f.UI.Nodes.RemoveMatching(nodeDisplayName())
|
||||
f.UI.Nodes.RemoveMatching(nodeWebauthnRegistrationOptions(opts))
|
||||
|
||||
f.UI.Nodes.RemoveMatching(webauthnx.NewWebAuthnScript(s.d.Config().SelfPublicURL(ctx)))
|
||||
f.UI.Nodes.RemoveMatching(nodeConnectionInput())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) PopulateRegistrationMethodCredentials(r *http.Request, f *registration.Flow, options ...registration.FormHydratorModifier) error {
|
||||
ctx := r.Context()
|
||||
if f.Type != flow.TypeBrowser || !s.d.Config().WebAuthnForPasswordless(ctx) {
|
||||
return nil
|
||||
}
|
||||
|
||||
f.UI.SetCSRF(s.d.GenerateCSRFToken(r))
|
||||
opts, err := s.injectWebauthnRegistrationOptions(r, f)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
f.UI.Nodes.Upsert(nodeDisplayName())
|
||||
f.UI.Nodes.Upsert(nodeWebauthnRegistrationOptions(opts))
|
||||
|
||||
f.UI.Nodes.Upsert(webauthnx.NewWebAuthnScript(s.d.Config().SelfPublicURL(ctx)))
|
||||
f.UI.Nodes.Upsert(nodeConnectionInput())
|
||||
return nil
|
||||
}
|
||||
628
selfservice/strategy/webauthn/registration_test.go
Normal file
628
selfservice/strategy/webauthn/registration_test.go
Normal file
@@ -0,0 +1,628 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package webauthn_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/ory/x/configx"
|
||||
|
||||
"github.com/ory/kratos/driver"
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/kratos/identity"
|
||||
"github.com/ory/kratos/pkg"
|
||||
kratos "github.com/ory/kratos/pkg/httpclient"
|
||||
"github.com/ory/kratos/pkg/registrationhelpers"
|
||||
"github.com/ory/kratos/pkg/testhelpers"
|
||||
"github.com/ory/kratos/selfservice/flow"
|
||||
"github.com/ory/kratos/selfservice/flow/registration"
|
||||
"github.com/ory/kratos/selfservice/strategy/webauthn"
|
||||
"github.com/ory/kratos/text"
|
||||
"github.com/ory/kratos/ui/node"
|
||||
"github.com/ory/x/assertx"
|
||||
"github.com/ory/x/contextx"
|
||||
"github.com/ory/x/snapshotx"
|
||||
)
|
||||
|
||||
var (
|
||||
flows = []string{"spa", "browser"}
|
||||
//go:embed fixtures/registration/success/response.json
|
||||
registrationFixtureSuccessResponse []byte
|
||||
//go:embed fixtures/registration/success/internal_context.json
|
||||
registrationFixtureSuccessInternalContext []byte
|
||||
//go:embed fixtures/registration/failure/internal_context_wrong_user_id.json
|
||||
registrationFixtureFailureInternalContextWrongUserID []byte
|
||||
)
|
||||
|
||||
func flowToIsSPA(flow string) bool {
|
||||
return flow == "spa"
|
||||
}
|
||||
|
||||
func newRegistrationRegistry(t *testing.T, cfgOpts ...configx.OptionModifier) *driver.RegistryDefault {
|
||||
_, reg := pkg.NewFastRegistryWithMocks(t, append([]configx.OptionModifier{
|
||||
configx.WithValues(map[string]any{
|
||||
config.ViperKeySelfServiceStrategyConfig + "." + string(identity.CredentialsTypePassword) + ".enabled": true,
|
||||
config.ViperKeyWebAuthnPasswordless: true,
|
||||
config.ViperKeySelfServiceRegistrationLoginHints: true,
|
||||
config.ViperKeySelfServiceRegistrationEnableLegacyOneStep: true,
|
||||
}),
|
||||
enabledWebauthn,
|
||||
}, cfgOpts...)...)
|
||||
return reg
|
||||
}
|
||||
|
||||
func TestRegistration(t *testing.T) {
|
||||
reg := newRegistrationRegistry(t, configx.WithValues(testhelpers.DefaultIdentitySchemaConfig("file://./stub/registration.schema.json")))
|
||||
conf := reg.Config()
|
||||
|
||||
publicTS, _ := testhelpers.NewKratosServer(t, reg)
|
||||
|
||||
_ = testhelpers.NewErrorTestServer(t, reg)
|
||||
_ = testhelpers.NewRegistrationUIFlowEchoServer(t, reg)
|
||||
_ = testhelpers.NewRedirSessionEchoTS(t, reg)
|
||||
|
||||
redirTS := testhelpers.NewRedirSessionEchoTS(t, reg)
|
||||
redirNoSessionTS := testhelpers.NewRedirNoSessionTS(t, reg)
|
||||
|
||||
// set the "return to" server, which will assert the session state
|
||||
// (redirTS: enforce that a session exists, redirNoSessionTS: enforce that no session exists)
|
||||
useReturnToFromTS := func(ts *httptest.Server) {
|
||||
conf.MustSet(t.Context(), config.ViperKeySelfServiceBrowserDefaultReturnTo, ts.URL+"/default-return-to")
|
||||
conf.MustSet(t.Context(), config.ViperKeySelfServiceRegistrationAfter+"."+config.DefaultBrowserReturnURL, ts.URL+"/registration-return-ts")
|
||||
}
|
||||
useReturnToFromTS(redirTS)
|
||||
|
||||
//checkURL := func(t *testing.T, shouldRedirect bool, res *http.Response) {
|
||||
// if shouldRedirect {
|
||||
// assert.Contains(t, res.Request.URL.String(), uiTS.URL+"/registration-ts")
|
||||
// } else {
|
||||
// assert.Contains(t, res.Request.URL.String(), publicTS.URL+registration.RouteSubmitFlow)
|
||||
// }
|
||||
//}
|
||||
|
||||
t.Run("AssertCommonErrorCases", func(t *testing.T) {
|
||||
registrationhelpers.AssertCommonErrorCases(t, flows)
|
||||
})
|
||||
|
||||
t.Run("AssertRegistrationRespectsValidation", func(t *testing.T) {
|
||||
reg := newRegistrationRegistry(t)
|
||||
registrationhelpers.AssertRegistrationRespectsValidation(t, reg, flows, func(v url.Values) {
|
||||
v.Del("traits.foobar")
|
||||
v.Set(node.WebAuthnRegister, "{}")
|
||||
v.Del("method")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("AssertCSRFFailures", func(t *testing.T) {
|
||||
reg := newRegistrationRegistry(t)
|
||||
registrationhelpers.AssertCSRFFailures(t, reg, flows, func(v url.Values) {
|
||||
v.Set(node.WebAuthnRegister, "{}")
|
||||
v.Del("method")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("AssertSchemaDoesNotExist", func(t *testing.T) {
|
||||
reg := newRegistrationRegistry(t)
|
||||
registrationhelpers.AssertSchemaDoesNotExist(t, reg, flows, func(v url.Values) {
|
||||
v.Set(node.WebAuthnRegister, "{}")
|
||||
v.Del("method")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("case=webauthn button does not exist when passwordless is disabled", func(t *testing.T) {
|
||||
conf.MustSet(t.Context(), config.ViperKeyWebAuthnPasswordless, false)
|
||||
t.Cleanup(func() {
|
||||
conf.MustSet(t.Context(), config.ViperKeyWebAuthnPasswordless, true)
|
||||
})
|
||||
for _, f := range flows {
|
||||
t.Run(f, func(t *testing.T) {
|
||||
client := testhelpers.NewClientWithCookies(t)
|
||||
f := testhelpers.InitializeRegistrationFlowViaBrowser(t, client, publicTS, flowToIsSPA(f), false, false)
|
||||
testhelpers.SnapshotTExcept(t, f.Ui.Nodes, []string{
|
||||
"0.attributes.value",
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("case=webauthn button exists", func(t *testing.T) {
|
||||
for _, f := range flows {
|
||||
t.Run(f, func(t *testing.T) {
|
||||
client := testhelpers.NewClientWithCookies(t)
|
||||
f := testhelpers.InitializeRegistrationFlowViaBrowser(t, client, publicTS, flowToIsSPA(f), false, false)
|
||||
testhelpers.SnapshotTExcept(t, f.Ui.Nodes, []string{
|
||||
"0.attributes.value",
|
||||
"5.attributes.onclick",
|
||||
"5.attributes.value",
|
||||
"6.attributes.nonce",
|
||||
"6.attributes.src",
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("case=should return an error because not passing validation", func(t *testing.T) {
|
||||
email := testhelpers.RandomEmail()
|
||||
|
||||
values := func(v url.Values) {
|
||||
v.Set("traits.username", email)
|
||||
v.Del("traits.foobar")
|
||||
v.Set(node.WebAuthnRegister, "{}")
|
||||
v.Del("method")
|
||||
}
|
||||
|
||||
for _, f := range flows {
|
||||
t.Run("type="+f, func(t *testing.T) {
|
||||
actual := registrationhelpers.ExpectValidationError(t.Context(), t, publicTS, conf, f, values)
|
||||
|
||||
assert.NotEmpty(t, gjson.Get(actual, "id").String(), "%s", actual)
|
||||
assert.Contains(t, gjson.Get(actual, "ui.action").String(), publicTS.URL+registration.RouteSubmitFlow, "%s", actual)
|
||||
registrationhelpers.CheckFormContent(t, []byte(actual), node.WebAuthnRegisterTrigger, "csrf_token", "traits.username", "traits.foobar")
|
||||
assert.Contains(t, gjson.Get(actual, "ui.nodes.#(attributes.name==traits.foobar).messages.0").String(), `Property foobar is missing`, "%s", actual)
|
||||
assert.Equal(t, email, gjson.Get(actual, "ui.nodes.#(attributes.name==traits.username).attributes.value").String(), "%s", actual)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("case=should reject invalid transient payload", func(t *testing.T) {
|
||||
email := testhelpers.RandomEmail()
|
||||
|
||||
values := func(v url.Values) {
|
||||
v.Set("traits.username", email)
|
||||
v.Set("traits.foobar", "bar")
|
||||
v.Set("transient_payload", "42")
|
||||
v.Set(node.WebAuthnRegister, "{}")
|
||||
v.Del("method")
|
||||
}
|
||||
|
||||
for _, f := range flows {
|
||||
t.Run("type="+f, func(t *testing.T) {
|
||||
actual := registrationhelpers.ExpectValidationError(t.Context(), t, publicTS, conf, f, values)
|
||||
|
||||
assert.NotEmpty(t, gjson.Get(actual, "id").String(), "%s", actual)
|
||||
assert.Contains(t, gjson.Get(actual, "ui.action").String(), publicTS.URL+registration.RouteSubmitFlow, "%s", actual)
|
||||
registrationhelpers.CheckFormContent(t, []byte(actual), node.WebAuthnRegisterTrigger, "csrf_token", "traits.username", "traits.foobar")
|
||||
assert.Equal(t, "bar", gjson.Get(actual, "ui.nodes.#(attributes.name==traits.foobar).attributes.value").String(), "%s", actual)
|
||||
assert.Equal(t, email, gjson.Get(actual, "ui.nodes.#(attributes.name==traits.username).attributes.value").String(), "%s", actual)
|
||||
assert.Equal(t, int64(4000026), gjson.Get(actual, "ui.nodes.#(attributes.name==transient_payload).messages.0.id").Int(), "%s", actual)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("case=should return an error because webauthn response is invalid", func(t *testing.T) {
|
||||
email := testhelpers.RandomEmail()
|
||||
values := func(v url.Values) {
|
||||
v.Set("traits.username", email)
|
||||
v.Set("traits.foobar", "bazbar")
|
||||
v.Set(node.WebAuthnRegister, "{}")
|
||||
v.Del("method")
|
||||
}
|
||||
|
||||
for _, f := range flows {
|
||||
t.Run("type="+f, func(t *testing.T) {
|
||||
actual := registrationhelpers.ExpectValidationError(t.Context(), t, publicTS, conf, f, values)
|
||||
assert.NotEmpty(t, gjson.Get(actual, "id").String(), "%s", actual)
|
||||
assert.Contains(t, gjson.Get(actual, "ui.action").String(), publicTS.URL+registration.RouteSubmitFlow, "%s", actual)
|
||||
registrationhelpers.CheckFormContent(t, []byte(actual), node.WebAuthnRegisterTrigger, "csrf_token", "traits.username", "traits.foobar")
|
||||
assert.Equal(t, "bazbar", gjson.Get(actual, "ui.nodes.#(attributes.name==traits.foobar).attributes.value").String(), "%s", actual)
|
||||
assert.Equal(t, email, gjson.Get(actual, "ui.nodes.#(attributes.name==traits.username).attributes.value").String(), "%s", actual)
|
||||
assert.Contains(t, gjson.Get(actual, "ui.messages.0").String(), `Unable to parse WebAuthn response: Parse error for Registration`, "%s", actual)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
submitWebAuthnRegistrationWithClient := func(t *testing.T, flow string, contextFixture []byte, client *http.Client, cb func(values url.Values), opts ...testhelpers.InitFlowWithOption) (string, *http.Response, *kratos.RegistrationFlow) {
|
||||
isSPA := flow == "spa"
|
||||
f := testhelpers.InitializeRegistrationFlowViaBrowser(t, client, publicTS, isSPA, false, false, opts...)
|
||||
|
||||
// We inject the session to replay
|
||||
interim, err := reg.RegistrationFlowPersister().GetRegistrationFlow(context.Background(), uuid.FromStringOrNil(f.Id))
|
||||
require.NoError(t, err)
|
||||
interim.InternalContext = contextFixture
|
||||
require.NoError(t, reg.RegistrationFlowPersister().UpdateRegistrationFlow(context.Background(), interim))
|
||||
|
||||
values := testhelpers.SDKFormFieldsToURLValues(f.Ui.Nodes)
|
||||
cb(values)
|
||||
|
||||
// We use the response replay
|
||||
body, res := testhelpers.RegistrationMakeRequest(t, false, isSPA, f, client, values.Encode())
|
||||
return body, res, f
|
||||
}
|
||||
|
||||
t.Run("case=should return an error because internal context is invalid", func(t *testing.T) {
|
||||
email := testhelpers.RandomEmail()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
internalContext string
|
||||
}{{
|
||||
name: "invalid json",
|
||||
internalContext: "invalid",
|
||||
}, {
|
||||
name: "wrong user ID",
|
||||
internalContext: string(registrationFixtureFailureInternalContextWrongUserID),
|
||||
}} {
|
||||
tc := tc
|
||||
t.Run("context="+tc.name, func(t *testing.T) {
|
||||
values := func(v url.Values) {
|
||||
v.Set("traits.username", email)
|
||||
v.Set("traits.foobar", "bazbar")
|
||||
v.Set(node.WebAuthnRegister, string(registrationFixtureSuccessResponse))
|
||||
v.Del("method")
|
||||
}
|
||||
|
||||
for _, f := range flows {
|
||||
t.Run("type="+f, func(t *testing.T) {
|
||||
actual, _, _ := submitWebAuthnRegistrationWithClient(t, f,
|
||||
[]byte(tc.internalContext),
|
||||
testhelpers.NewClientWithCookies(t),
|
||||
values,
|
||||
)
|
||||
|
||||
if f == "spa" {
|
||||
assert.Equal(t, "Internal Server Error", gjson.Get(actual, "error.status").String(), "%s", actual)
|
||||
} else {
|
||||
assert.Equal(t, "Internal Server Error", gjson.Get(actual, "status").String(), "%s", actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("case=should fail to create identity if schema is missing the identifier", func(t *testing.T) {
|
||||
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/noid.schema.json")
|
||||
t.Cleanup(func() {
|
||||
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/registration.schema.json")
|
||||
})
|
||||
|
||||
email := testhelpers.RandomEmail()
|
||||
|
||||
values := func(v url.Values) {
|
||||
v.Set("traits.email", email)
|
||||
v.Set(node.WebAuthnRegister, string(registrationFixtureSuccessResponse))
|
||||
v.Del("method")
|
||||
}
|
||||
|
||||
for _, f := range flows {
|
||||
t.Run("type="+f, func(t *testing.T) {
|
||||
actual, _, _ := submitWebAuthnRegistrationWithClient(t, f, registrationFixtureSuccessInternalContext, testhelpers.NewClientWithCookies(t), values)
|
||||
|
||||
assert.Contains(t, gjson.Get(actual, "ui.action").String(), publicTS.URL+registration.RouteSubmitFlow, "%s", actual)
|
||||
registrationhelpers.CheckFormContent(t, []byte(actual), node.WebAuthnRegisterTrigger, "csrf_token", "traits.email")
|
||||
assert.Equal(t, text.NewErrorValidationIdentifierMissing().Text, gjson.Get(actual, "ui.messages.0.text").String(), "%s", actual)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
makeRegistration := func(t *testing.T, f string, values func(v url.Values), opts ...testhelpers.InitFlowWithOption) (actual string, res *http.Response, fetchedFlow *registration.Flow) {
|
||||
actual, res, actualFlow := submitWebAuthnRegistrationWithClient(t, f, registrationFixtureSuccessInternalContext, testhelpers.NewClientWithCookies(t), values, opts...)
|
||||
fetchedFlow, err := reg.RegistrationFlowPersister().GetRegistrationFlow(context.Background(), uuid.FromStringOrNil(actualFlow.Id))
|
||||
require.NoError(t, err)
|
||||
|
||||
return actual, res, fetchedFlow
|
||||
}
|
||||
|
||||
makeSuccessfulRegistration := func(t *testing.T, f string, expectReturnTo string, values func(v url.Values), opts ...testhelpers.InitFlowWithOption) (actual string) {
|
||||
actual, res, fetchedFlow := makeRegistration(t, f, values, opts...)
|
||||
assert.Empty(t, gjson.GetBytes(fetchedFlow.InternalContext, flow.PrefixInternalContextKey(identity.CredentialsTypeWebAuthn, webauthn.InternalContextKeySessionData)), "has cleaned up the internal context after success")
|
||||
if f == "spa" {
|
||||
expectReturnTo = publicTS.URL
|
||||
}
|
||||
assert.Contains(t, res.Request.URL.String(), expectReturnTo, "%+v\n\t%s", res.Request, assertx.PrettifyJSONPayload(t, actual))
|
||||
return actual
|
||||
}
|
||||
|
||||
getPrefix := func(f string) (prefix string) {
|
||||
if f == "spa" {
|
||||
prefix = "session."
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
t.Run("successful registration", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypeWebAuthn.String()), nil)
|
||||
})
|
||||
|
||||
values := func(email string) func(v url.Values) {
|
||||
return func(v url.Values) {
|
||||
v.Set("traits.username", email)
|
||||
v.Set("traits.foobar", "bazbar")
|
||||
v.Set(node.WebAuthnRegister, string(registrationFixtureSuccessResponse))
|
||||
v.Del("method")
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("case=should create the identity but not a session", func(t *testing.T) {
|
||||
useReturnToFromTS(redirNoSessionTS)
|
||||
t.Cleanup(func() {
|
||||
useReturnToFromTS(redirTS)
|
||||
})
|
||||
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), nil)
|
||||
|
||||
for _, f := range flows {
|
||||
t.Run("type="+f, func(t *testing.T) {
|
||||
email := testhelpers.RandomEmail()
|
||||
actual := makeSuccessfulRegistration(t, f, redirNoSessionTS.URL+"/registration-return-ts", values(email))
|
||||
|
||||
if f == "spa" {
|
||||
assert.Equal(t, email, gjson.Get(actual, "identity.traits.username").String(), "%s", actual)
|
||||
assert.False(t, gjson.Get(actual, "session").Exists(), "because the registration yielded no session, the user is not expected to be signed in: %s", actual)
|
||||
} else {
|
||||
assert.Equal(t, "null\n", actual, "because the registration yielded no session, the user is not expected to be signed in: %s", actual)
|
||||
}
|
||||
|
||||
i, _, err := reg.PrivilegedIdentityPool().FindByCredentialsIdentifier(context.Background(), identity.CredentialsTypeWebAuthn, email)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, email, gjson.GetBytes(i.Traits, "username").String(), "%s", actual)
|
||||
|
||||
if f == "spa" {
|
||||
assert.EqualValues(t, flow.ContinueWithActionRedirectBrowserToString, gjson.Get(actual, "continue_with.0.action").String(), "%s", actual)
|
||||
assert.Contains(t, gjson.Get(actual, "continue_with.0.redirect_browser_to").String(), redirNoSessionTS.URL+"/registration-return-ts", "%s", actual)
|
||||
} else {
|
||||
assert.Empty(t, gjson.Get(actual, "continue_with").Array(), "%s", actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("case=should accept valid transient payload", func(t *testing.T) {
|
||||
useReturnToFromTS(redirNoSessionTS)
|
||||
t.Cleanup(func() {
|
||||
useReturnToFromTS(redirTS)
|
||||
})
|
||||
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypePassword.String()), nil)
|
||||
|
||||
for _, f := range flows {
|
||||
t.Run("type="+f, func(t *testing.T) {
|
||||
email := testhelpers.RandomEmail()
|
||||
actual := makeSuccessfulRegistration(t, f, redirNoSessionTS.URL+"/registration-return-ts", func(v url.Values) {
|
||||
values(email)(v)
|
||||
v.Set("transient_payload.stuff", "42")
|
||||
})
|
||||
|
||||
if f == "spa" {
|
||||
assert.Equal(t, email, gjson.Get(actual, "identity.traits.username").String(), "%s", actual)
|
||||
assert.False(t, gjson.Get(actual, "session").Exists(), "because the registration yielded no session, the user is not expected to be signed in: %s", actual)
|
||||
} else {
|
||||
assert.Equal(t, "null\n", actual, "because the registration yielded no session, the user is not expected to be signed in: %s", actual)
|
||||
}
|
||||
|
||||
i, _, err := reg.PrivilegedIdentityPool().FindByCredentialsIdentifier(context.Background(), identity.CredentialsTypeWebAuthn, email)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, email, gjson.GetBytes(i.Traits, "username").String(), "%s", actual)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("case=should create the identity and a session and use the correct schema", func(t *testing.T) {
|
||||
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypeWebAuthn.String()), []config.SelfServiceHook{{Name: "session"}})
|
||||
conf.MustSet(t.Context(), config.ViperKeyDefaultIdentitySchemaID, "advanced-user")
|
||||
conf.MustSet(t.Context(), config.ViperKeyIdentitySchemas, config.Schemas{
|
||||
{ID: "does-not-exist", URL: "file://./stub/profile.schema.json"},
|
||||
{ID: "advanced-user", URL: "file://./stub/registration.schema.json"},
|
||||
})
|
||||
|
||||
for _, f := range flows {
|
||||
t.Run("type="+f, func(t *testing.T) {
|
||||
email := testhelpers.RandomEmail()
|
||||
actual := makeSuccessfulRegistration(t, f, redirTS.URL+"/registration-return-ts", values(email))
|
||||
|
||||
prefix := getPrefix(f)
|
||||
|
||||
assert.Equal(t, email, gjson.Get(actual, prefix+"identity.traits.username").String(), "%s", actual)
|
||||
assert.True(t, gjson.Get(actual, prefix+"active").Bool(), "%s", actual)
|
||||
|
||||
i, _, err := reg.PrivilegedIdentityPool().FindByCredentialsIdentifier(context.Background(), identity.CredentialsTypeWebAuthn, email)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, email, gjson.GetBytes(i.Traits, "username").String(), "%s", actual)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("case=not able to create the same account twice", func(t *testing.T) {
|
||||
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypeWebAuthn.String()), []config.SelfServiceHook{{Name: "session"}})
|
||||
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/registration.schema.json")
|
||||
|
||||
for _, f := range flows {
|
||||
t.Run("type="+f, func(t *testing.T) {
|
||||
email := testhelpers.RandomEmail()
|
||||
actual := makeSuccessfulRegistration(t, f, redirTS.URL+"/registration-return-ts", values(email))
|
||||
assert.True(t, gjson.Get(actual, getPrefix(f)+"active").Bool(), "%s", actual)
|
||||
|
||||
actual, _, _ = makeRegistration(t, f, values(email))
|
||||
assert.Contains(t, gjson.Get(actual, "ui.action").String(), publicTS.URL+registration.RouteSubmitFlow, "%s", actual)
|
||||
registrationhelpers.CheckFormContent(t, []byte(actual), node.WebAuthnRegisterTrigger, "csrf_token", "traits.username")
|
||||
assert.Equal(t, "You tried signing in with "+email+" which is already in use by another account. You can sign in using your passkey or a security key.", gjson.Get(actual, "ui.messages.0.text").String(), "%s", actual)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("case=reset previous form errors", func(t *testing.T) {
|
||||
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypeWebAuthn.String()), []config.SelfServiceHook{{Name: "session"}})
|
||||
testhelpers.SetDefaultIdentitySchema(conf, "file://./stub/registration.schema.json")
|
||||
|
||||
for _, f := range flows {
|
||||
t.Run("type="+f, func(t *testing.T) {
|
||||
email := testhelpers.RandomEmail()
|
||||
actual, _, _ := makeRegistration(t, f, func(v url.Values) {
|
||||
v.Del("traits.username")
|
||||
v.Set("traits.foobar", "bazbar")
|
||||
v.Set(node.WebAuthnRegister, string(registrationFixtureSuccessResponse))
|
||||
v.Del("method")
|
||||
})
|
||||
registrationhelpers.CheckFormContent(t, []byte(actual), node.WebAuthnRegisterTrigger, "csrf_token", "traits.username", "traits.foobar")
|
||||
assert.Contains(t, gjson.Get(actual, "ui.nodes.#(attributes.name==traits.username).messages.0").String(), `Property username is missing`, "%s", actual)
|
||||
|
||||
actual, _, _ = makeRegistration(t, f, func(v url.Values) {
|
||||
v.Set("traits.username", email)
|
||||
v.Del("traits.foobar")
|
||||
v.Set(node.WebAuthnRegister, string(registrationFixtureSuccessResponse))
|
||||
v.Del("method")
|
||||
})
|
||||
registrationhelpers.CheckFormContent(t, []byte(actual), node.WebAuthnRegisterTrigger, "csrf_token", "traits.username", "traits.foobar")
|
||||
assert.Contains(t, gjson.Get(actual, "ui.nodes.#(attributes.name==traits.foobar).messages.0").String(), `Property foobar is missing`, "%s", actual)
|
||||
assert.Empty(t, gjson.Get(actual, "ui.nodes.#(attributes.name==traits.username).messages").Array())
|
||||
assert.Empty(t, gjson.Get(actual, "ui.nodes.messages").Array())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("case=multi-schema should create the identity and a session and use the correct schema", func(t *testing.T) {
|
||||
conf.MustSet(t.Context(), config.HookStrategyKey(config.ViperKeySelfServiceRegistrationAfter, identity.CredentialsTypeWebAuthn.String()), []config.SelfServiceHook{{Name: "session"}})
|
||||
conf.MustSet(t.Context(), config.ViperKeyDefaultIdentitySchemaID, "does-not-exist")
|
||||
conf.MustSet(t.Context(), config.ViperKeyIdentitySchemas, config.Schemas{
|
||||
{ID: "does-not-exist", URL: "file://./stub/profile.schema.json"},
|
||||
{ID: "advanced-user", URL: "file://./stub/registration.schema.json", SelfserviceSelectable: true},
|
||||
})
|
||||
|
||||
for _, f := range flows {
|
||||
t.Run("type="+f+" registration success", func(t *testing.T) {
|
||||
email := testhelpers.RandomEmail()
|
||||
actual := makeSuccessfulRegistration(t, f, redirTS.URL+"/registration-return-ts", values(email), testhelpers.InitFlowWithIdentitySchema("advanced-user"))
|
||||
|
||||
prefix := getPrefix(f)
|
||||
|
||||
assert.Equal(t, email, gjson.Get(actual, prefix+"identity.traits.username").String(), "%s", actual)
|
||||
assert.True(t, gjson.Get(actual, prefix+"active").Bool(), "%s", actual)
|
||||
|
||||
i, _, err := reg.PrivilegedIdentityPool().FindByCredentialsIdentifier(context.Background(), identity.CredentialsTypeWebAuthn, email)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, email, gjson.GetBytes(i.Traits, "username").String(), "%s", actual)
|
||||
})
|
||||
|
||||
t.Run("type="+f+" registration failure due to invalid form data", func(t *testing.T) {
|
||||
invalidValues := func(v url.Values) {
|
||||
v.Set("traits.username", testhelpers.RandomEmail())
|
||||
v.Set("traits.foobar", "b")
|
||||
v.Set(node.WebAuthnRegister, string(registrationFixtureSuccessResponse))
|
||||
v.Del("method")
|
||||
}
|
||||
|
||||
actual, res, _ := submitWebAuthnRegistrationWithClient(t, f, registrationFixtureSuccessInternalContext, testhelpers.NewClientWithCookies(t), invalidValues, testhelpers.InitFlowWithIdentitySchema("advanced-user"))
|
||||
|
||||
if f == "browser" {
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode, "%s", actual)
|
||||
} else {
|
||||
assert.Equal(t, http.StatusBadRequest, res.StatusCode, "%s", actual)
|
||||
}
|
||||
assert.Equal(t, int64(4000003), gjson.Get(actual, "ui.nodes.#(attributes.name==traits.foobar).messages.0.id").Int(), "%s", actual)
|
||||
assert.Equal(t, "length must be \u003e= 2, but got 1", gjson.Get(actual, "ui.nodes.#(attributes.name==traits.foobar).messages.0.text").String(), "%s", actual)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("case=should fail if no identifier was set in the schema", func(t *testing.T) {
|
||||
testhelpers.SetDefaultIdentitySchema(conf, "file://stub/missing-identifier.schema.json")
|
||||
|
||||
for _, f := range []string{"spa", "api", "browser"} {
|
||||
t.Run("type="+f, func(t *testing.T) {
|
||||
actual, _, _ := makeRegistration(t, f, func(v url.Values) {
|
||||
v.Set("traits.email", testhelpers.RandomEmail())
|
||||
v.Set(node.WebAuthnRegister, string(registrationFixtureSuccessResponse))
|
||||
v.Del("method")
|
||||
})
|
||||
assert.Equal(t, text.NewErrorValidationIdentifierMissing().Text, gjson.Get(actual, "ui.messages.0.text").String(), "%s", actual)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPopulateRegistrationMethod(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
_, reg := pkg.NewFastRegistryWithMocks(t)
|
||||
|
||||
ctx = testhelpers.WithDefaultIdentitySchema(ctx, "file://stub/registration.schema.json")
|
||||
ctx = contextx.WithConfigValue(ctx, config.ViperKeyWebAuthnRPID, "localhost")
|
||||
ctx = contextx.WithConfigValue(ctx, config.ViperKeyWebAuthnRPDisplayName, "localhost")
|
||||
ctx = contextx.WithConfigValue(ctx, config.ViperKeyWebAuthnPasswordless, true)
|
||||
|
||||
s, err := reg.AllRegistrationStrategies().Strategy(identity.CredentialsTypeWebAuthn)
|
||||
require.NoError(t, err)
|
||||
|
||||
fh, ok := s.(registration.FormHydrator)
|
||||
require.True(t, ok)
|
||||
|
||||
toSnapshot := func(t *testing.T, f node.Nodes, except ...snapshotx.Opt) {
|
||||
t.Helper()
|
||||
// The CSRF token has a unique value that messes with the snapshot - ignore it.
|
||||
f.ResetNodes("csrf_token")
|
||||
snapshotx.SnapshotT(t, f, append(except, snapshotx.ExceptNestedKeys("nonce", "src", "onclick"))...)
|
||||
}
|
||||
|
||||
newFlow := func(ctx context.Context, t *testing.T) (*http.Request, *registration.Flow) {
|
||||
r := httptest.NewRequest("GET", "/self-service/registration/browser", nil)
|
||||
r = r.WithContext(ctx)
|
||||
t.Helper()
|
||||
f, err := registration.NewFlow(reg, r, flow.TypeBrowser)
|
||||
f.UI.Nodes = make(node.Nodes, 0)
|
||||
require.NoError(t, err)
|
||||
return r, f
|
||||
}
|
||||
|
||||
t.Run("method=PopulateRegistrationMethod", func(t *testing.T) {
|
||||
r, f := newFlow(ctx, t)
|
||||
require.NoError(t, fh.PopulateRegistrationMethod(r, f))
|
||||
toSnapshot(t, f.UI.Nodes, snapshotx.ExceptPaths("2.attributes.value"))
|
||||
})
|
||||
|
||||
t.Run("method=PopulateRegistrationMethodProfile", func(t *testing.T) {
|
||||
r, f := newFlow(ctx, t)
|
||||
require.NoError(t, fh.PopulateRegistrationMethodProfile(r, f))
|
||||
toSnapshot(t, f.UI.Nodes)
|
||||
})
|
||||
|
||||
t.Run("method=PopulateRegistrationMethodCredentials", func(t *testing.T) {
|
||||
r, f := newFlow(ctx, t)
|
||||
require.NoError(t, fh.PopulateRegistrationMethodCredentials(r, f))
|
||||
toSnapshot(t, f.UI.Nodes, snapshotx.ExceptPaths("2.attributes.value"))
|
||||
})
|
||||
|
||||
t.Run("method=idempotency", func(t *testing.T) {
|
||||
r, f := newFlow(ctx, t)
|
||||
|
||||
var snapshots []node.Nodes
|
||||
|
||||
t.Run("case=1", func(t *testing.T) {
|
||||
require.NoError(t, fh.PopulateRegistrationMethodProfile(r, f))
|
||||
snapshots = append(snapshots, f.UI.Nodes)
|
||||
toSnapshot(t, f.UI.Nodes)
|
||||
})
|
||||
|
||||
t.Run("case=2", func(t *testing.T) {
|
||||
require.NoError(t, fh.PopulateRegistrationMethodCredentials(r, f))
|
||||
snapshots = append(snapshots, f.UI.Nodes)
|
||||
toSnapshot(t, f.UI.Nodes, snapshotx.ExceptPaths("2.attributes.value"))
|
||||
})
|
||||
|
||||
t.Run("case=3", func(t *testing.T) {
|
||||
require.NoError(t, fh.PopulateRegistrationMethodProfile(r, f))
|
||||
snapshots = append(snapshots, f.UI.Nodes)
|
||||
toSnapshot(t, f.UI.Nodes)
|
||||
})
|
||||
|
||||
t.Run("case=4", func(t *testing.T) {
|
||||
require.NoError(t, fh.PopulateRegistrationMethodCredentials(r, f))
|
||||
snapshots = append(snapshots, f.UI.Nodes)
|
||||
toSnapshot(t, f.UI.Nodes, snapshotx.ExceptPaths("2.attributes.value"))
|
||||
})
|
||||
|
||||
t.Run("case=evaluate", func(t *testing.T) {
|
||||
assertx.EqualAsJSON(t, snapshots[0], snapshots[2])
|
||||
assertx.EqualAsJSONExcept(t, snapshots[1], snapshots[3], []string{"3.attributes.nonce"})
|
||||
})
|
||||
})
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user