diff --git a/.docker/Dockerfile-build b/.docker/Dockerfile-build index bd619930f0a9..687d8834012f 100644 --- a/.docker/Dockerfile-build +++ b/.docker/Dockerfile-build @@ -1,5 +1,5 @@ # syntax = docker/dockerfile:1-experimental -FROM golang:1.22-bullseye AS builder +FROM golang:1.23-bullseye AS builder RUN apt-get update && apt-get upgrade -y &&\ mkdir -p /var/lib/sqlite diff --git a/.docker/Dockerfile-debug b/.docker/Dockerfile-debug index a309b5ad92bb..97a0e2b72525 100644 --- a/.docker/Dockerfile-debug +++ b/.docker/Dockerfile-debug @@ -1,4 +1,4 @@ -FROM golang:1.22-bullseye +FROM golang:1.23-bullseye ENV CGO_ENABLED 1 RUN apt-get update && apt-get install -y --no-install-recommends inotify-tools psmisc diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e7c488f0d496..9fc74bd6397f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -79,7 +79,7 @@ jobs: fetch-depth: 2 - uses: actions/setup-go@v4 with: - go-version: "1.22" + go-version: "1.23" - run: go list -json > go.list - name: Run nancy uses: sonatype-nexus-community/nancy-github-action@v1.0.2 @@ -93,7 +93,7 @@ jobs: GOGC: 100 with: args: --timeout 10m0s - version: v1.59.1 + version: v1.61.0 - name: Build Kratos run: make install - name: Run go-acc (tests) @@ -169,7 +169,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v4 with: - go-version: "1.22" + go-version: "1.23" - name: Install selfservice-ui-react-native uses: actions/checkout@v3 @@ -273,7 +273,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v4 with: - go-version: "1.22" + go-version: "1.23" - run: go build -tags sqlite,json1 . - name: Install selfservice-ui-react-native diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 7e243923b8ca..bb107819d849 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: "1.22" + go-version: "1.23" - run: make format - name: Indicate formatting issues run: git diff HEAD --exit-code --color diff --git a/.github/workflows/licenses.yml b/.github/workflows/licenses.yml index 8a86486031de..9d1589506da2 100644 --- a/.github/workflows/licenses.yml +++ b/.github/workflows/licenses.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: "1.22" + go-version: "1.23" - uses: actions/setup-node@v2 with: node-version: "18" diff --git a/.golangci.yml b/.golangci.yml index e83dd5a56a2e..81b4a23960df 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -29,3 +29,4 @@ issues: - "Set is deprecated: use context-based WithConfigValue instead" - "SetDefaultIdentitySchemaFromRaw is deprecated: Use context-based WithDefaultIdentitySchemaFromRaw instead" - "SetDefaultIdentitySchema is deprecated: Use context-based WithDefaultIdentitySchema instead" + - "G115" diff --git a/.schema/openapi/patches/selfservice.yaml b/.schema/openapi/patches/selfservice.yaml index 3fe24b62fb5a..39a329bf5928 100644 --- a/.schema/openapi/patches/selfservice.yaml +++ b/.schema/openapi/patches/selfservice.yaml @@ -32,11 +32,15 @@ passkey: "#/components/schemas/updateRegistrationFlowWithPasskeyMethod" profile: "#/components/schemas/updateRegistrationFlowWithProfileMethod" - op: add - path: /components/schemas/registrationFlowState/enum + path: /components/schemas/registrationFlowState value: - - choose_method - - sent_email - - passed_challenge + title: Registration flow state (experimental) + description: The experimental state represents the state of a registration flow. This field is EXPERIMENTAL and subject to change! + type: string + enum: + - choose_method + - sent_email + - passed_challenge # end # All modifications for the login flow @@ -67,11 +71,15 @@ passkey: "#/components/schemas/updateLoginFlowWithPasskeyMethod" identifier_first: "#/components/schemas/updateLoginFlowWithIdentifierFirstMethod" - op: add - path: /components/schemas/loginFlowState/enum + path: /components/schemas/loginFlowState value: - - choose_method - - sent_email - - passed_challenge + title: Login flow state (experimental) + description: The experimental state represents the state of a login flow. This field is EXPERIMENTAL and subject to change! + type: string + enum: + - choose_method + - sent_email + - passed_challenge # end # All modifications for the recovery flow @@ -90,11 +98,15 @@ link: "#/components/schemas/updateRecoveryFlowWithLinkMethod" code: "#/components/schemas/updateRecoveryFlowWithCodeMethod" - op: add - path: /components/schemas/recoveryFlowState/enum + path: /components/schemas/recoveryFlowState + type: string value: - - choose_method - - sent_email - - passed_challenge + title: Recovery flow state (experimental) + description: The experimental state represents the state of a recovery flow. This field is EXPERIMENTAL and subject to change! + enum: + - choose_method + - sent_email + - passed_challenge # End # All modifications for the verification flow @@ -113,11 +125,15 @@ link: "#/components/schemas/updateVerificationFlowWithLinkMethod" code: "#/components/schemas/updateVerificationFlowWithCodeMethod" - op: add - path: /components/schemas/verificationFlowState/enum + path: /components/schemas/verificationFlowState + type: string value: - - choose_method - - sent_email - - passed_challenge + title: Verification flow state (experimental) + description: The experimental state represents the state of a verification flow. This field is EXPERIMENTAL and subject to change! + enum: + - choose_method + - sent_email + - passed_challenge # End # All modifications for the settings flow @@ -146,10 +162,14 @@ passkey: "#/components/schemas/updateSettingsFlowWithPasskeyMethod" lookup_secret: "#/components/schemas/updateSettingsFlowWithLookupMethod" - op: add - path: /components/schemas/settingsFlowState/enum + path: /components/schemas/settingsFlowState value: - - show_form - - success + title: Settings flow state (experimental) + description: The experimental state represents the state of a settings flow. This field is EXPERIMENTAL and subject to change! + type: string + enum: + - show_form + - success # end # Some issues with AdditionalProperties diff --git a/cmd/clidoc/main.go b/cmd/clidoc/main.go index a2b1850a87fa..c0bcda5d320e 100644 --- a/cmd/clidoc/main.go +++ b/cmd/clidoc/main.go @@ -309,6 +309,8 @@ func validateAllMessages(path string) error { info := &types.Info{ Defs: make(map[*ast.Ident]types.Object), } + + //nolint:staticcheck var pack *ast.Package for _, p := range packs { if p.Name == "text" { diff --git a/driver/config/config.go b/driver/config/config.go index dd7a6a42f00d..169e4363b38d 100644 --- a/driver/config/config.go +++ b/driver/config/config.go @@ -1511,6 +1511,7 @@ func (p *Config) PasskeyConfig(ctx context.Context) *webauthn.Config { AuthenticatorSelection: protocol.AuthenticatorSelection{ AuthenticatorAttachment: "platform", RequireResidentKey: pointerx.Ptr(true), + ResidentKey: protocol.ResidentKeyRequirementRequired, UserVerification: protocol.VerificationPreferred, }, EncodeUserIDAsString: false, diff --git a/embedx/config.schema.json b/embedx/config.schema.json index abe5a4e7ac5b..4317d11579f9 100644 --- a/embedx/config.schema.json +++ b/embedx/config.schema.json @@ -1893,7 +1893,6 @@ "description": "A list of explicit RP origins. If left empty, this defaults to either `origin` or `id`, prepended with the current protocol schema (HTTP or HTTPS).", "items": { "type": "string", - "format": "uri", "examples": [ "https://www.ory.sh", "https://auth.ory.sh" diff --git a/go.mod b/go.mod index ac36ab25892a..f7aae5c1da3e 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,12 @@ module github.com/ory/kratos -go 1.22 +go 1.23 + +toolchain go1.23.2 replace ( + github.com/go-swagger/go-swagger => github.com/aeneasr/go-swagger v0.19.1-0.20241013070044-bccef3a12e26 // See https://github.com/go-swagger/go-swagger/issues/3131 + // github.com/go-swagger/go-swagger => ../../go-swagger/go-swagger // https://github.com/gobuffalo/pop/pull/833 github.com/gobuffalo/pop/v6 => github.com/ory/pop/v6 v6.2.0 @@ -34,7 +38,7 @@ require ( github.com/go-openapi/strfmt v0.23.0 github.com/go-playground/validator/v10 v10.22.0 github.com/go-swagger/go-swagger v0.31.0 - github.com/go-webauthn/webauthn v0.10.2 // DO NOT UPGRADE TO 0.11.0 WITHOUT ADDRESSING ory/kratos#4034 + github.com/go-webauthn/webauthn v0.11.2 github.com/gobuffalo/httptest v1.5.2 github.com/gobuffalo/pop/v6 v6.1.2-0.20230318123913-c85387acc9a0 github.com/gofrs/uuid v4.4.0+incompatible @@ -91,12 +95,12 @@ require ( go.opentelemetry.io/otel v1.28.0 go.opentelemetry.io/otel/sdk v1.28.0 go.opentelemetry.io/otel/trace v1.28.0 - golang.org/x/crypto v0.25.0 + golang.org/x/crypto v0.26.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/net v0.27.0 golang.org/x/oauth2 v0.21.0 - golang.org/x/sync v0.7.0 - golang.org/x/text v0.16.0 + golang.org/x/sync v0.8.0 + golang.org/x/text v0.17.0 google.golang.org/grpc v1.65.0 ) @@ -111,7 +115,7 @@ require ( github.com/cortesi/termlog v0.0.0-20210222042314-a1eec763abec // indirect github.com/jackc/pgx/v5 v5.6.0 // indirect github.com/rjeczalik/notify v0.9.3 // indirect - golang.org/x/term v0.22.0 // indirect + golang.org/x/term v0.23.0 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect mvdan.cc/sh/v3 v3.6.0 // indirect ) @@ -164,7 +168,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect - github.com/go-webauthn/x v0.1.12 // indirect + github.com/go-webauthn/x v0.1.14 // indirect github.com/gobuffalo/envy v1.10.2 // indirect github.com/gobuffalo/fizz v1.14.4 // indirect github.com/gobuffalo/flect v1.0.2 // indirect @@ -307,7 +311,7 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.19.0 // indirect - golang.org/x/sys v0.22.0 // indirect + golang.org/x/sys v0.23.0 // indirect golang.org/x/tools v0.23.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect diff --git a/go.sum b/go.sum index e33a12499ae0..daa9d3c4bc9e 100644 --- a/go.sum +++ b/go.sum @@ -53,6 +53,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/aeneasr/go-swagger v0.19.1-0.20241013070044-bccef3a12e26 h1:rwCKVbnpzxQ0F/AhO9FkXnrKqRmqej4epjhe1CpNkB0= +github.com/aeneasr/go-swagger v0.19.1-0.20241013070044-bccef3a12e26/go.mod h1:WSigRRWEig8zV6t6Sm8Y+EmUjlzA/HoaZJ5edupq7po= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -232,14 +234,12 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-swagger/go-swagger v0.31.0 h1:H8eOYQnY2u7vNKWDNykv2xJP3pBhRG/R+SOCAmKrLlc= -github.com/go-swagger/go-swagger v0.31.0/go.mod h1:WSigRRWEig8zV6t6Sm8Y+EmUjlzA/HoaZJ5edupq7po= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/go-webauthn/webauthn v0.10.2 h1:OG7B+DyuTytrEPFmTX503K77fqs3HDK/0Iv+z8UYbq4= -github.com/go-webauthn/webauthn v0.10.2/go.mod h1:Gd1IDsGAybuvK1NkwUTLbGmeksxuRJjVN2PE/xsPxHs= -github.com/go-webauthn/x v0.1.12 h1:RjQ5cvApzyU/xLCiP+rub0PE4HBZsLggbxGR5ZpUf/A= -github.com/go-webauthn/x v0.1.12/go.mod h1:XlRcGkNH8PT45TfeJYc6gqpOtiOendHhVmnOxh+5yHs= +github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc= +github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0= +github.com/go-webauthn/x v0.1.14 h1:1wrB8jzXAofojJPAaRxnZhRgagvLGnLjhCAwg3kTpT0= +github.com/go-webauthn/x v0.1.14/go.mod h1:UuVvFZ8/NbOnkDz3y1NaxtUN87pmtpC1PQ+/5BBQRdc= github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4= github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8= github.com/gobuffalo/fizz v1.14.4 h1:8uume7joF6niTNWN582IQ2jhGTUoa9g1fiV/tIoGdBs= @@ -871,8 +871,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4 golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -986,8 +986,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1053,8 +1053,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1066,8 +1066,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1080,8 +1080,8 @@ golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/hash/hash_comparator.go b/hash/hash_comparator.go index 524eb9bfba14..d57ec64ea616 100644 --- a/hash/hash_comparator.go +++ b/hash/hash_comparator.go @@ -9,8 +9,8 @@ import ( "crypto/aes" "crypto/cipher" "crypto/hmac" - "crypto/md5" //#nosec G501 -- compatibility for imported passwords - "crypto/sha1" //#nosec G505 -- compatibility for imported passwords + "crypto/md5" //nolint:all // System compatibility for imported passwords + "crypto/sha1" //nolint:all // System compatibility for imported passwords "crypto/sha256" "crypto/sha512" "crypto/subtle" @@ -21,22 +21,18 @@ import ( "regexp" "strings" + "github.com/go-crypt/crypt" + "github.com/go-crypt/crypt/algorithm/md5crypt" + "github.com/go-crypt/crypt/algorithm/shacrypt" "github.com/pkg/errors" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "golang.org/x/crypto/argon2" "golang.org/x/crypto/bcrypt" - - //nolint:staticcheck - //lint:ignore SA1019 compatibility for imported passwords - "golang.org/x/crypto/md4" //#nosec G501 -- compatibility for imported passwords + "golang.org/x/crypto/md4" //nolint:all // System compatibility for imported passwords "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/scrypt" - "github.com/go-crypt/crypt" - "github.com/go-crypt/crypt/algorithm/md5crypt" - "github.com/go-crypt/crypt/algorithm/shacrypt" - "github.com/ory/kratos/driver/config" ) diff --git a/identity/credentials_webauthn.go b/identity/credentials_webauthn.go index 0816046d23e4..ae6b2e34cb77 100644 --- a/identity/credentials_webauthn.go +++ b/identity/credentials_webauthn.go @@ -6,6 +6,8 @@ package identity import ( "time" + "github.com/go-webauthn/webauthn/protocol" + "github.com/go-webauthn/webauthn/webauthn" "github.com/ory/kratos/x/webauthnx/aaguid" @@ -27,11 +29,17 @@ func CredentialFromWebAuthn(credential *webauthn.Credential, isPasswordless bool IsPasswordless: isPasswordless, AttestationType: credential.AttestationType, AddedAt: time.Now().UTC().Round(time.Second), - Authenticator: AuthenticatorWebAuthn{ + Authenticator: &AuthenticatorWebAuthn{ AAGUID: credential.Authenticator.AAGUID, SignCount: credential.Authenticator.SignCount, CloneWarning: credential.Authenticator.CloneWarning, }, + Flags: &CredentialWebAuthnFlags{ + UserPresent: credential.Flags.UserPresent, + UserVerified: credential.Flags.UserVerified, + BackupEligible: credential.Flags.BackupEligible, + BackupState: credential.Flags.BackupState, + }, } id := aaguid.Lookup(credential.Authenticator.AAGUID) if id != nil { @@ -49,8 +57,16 @@ func (c CredentialsWebAuthn) ToWebAuthn() (result []webauthn.Credential) { } // PasswordlessOnly returns only passwordless credentials. -func (c CredentialsWebAuthn) PasswordlessOnly() (result []webauthn.Credential) { +func (c CredentialsWebAuthn) PasswordlessOnly(authenticatorResponseFlags *protocol.AuthenticatorFlags) (result []webauthn.Credential) { for k, cc := range c { + // Upgrade path for legacy webauthn credentials. Only possible if we are handling a response from an authenticator. + if c[k].Flags == nil && authenticatorResponseFlags != nil { + c[k].Flags = &CredentialWebAuthnFlags{ + BackupEligible: authenticatorResponseFlags.HasBackupEligible(), + BackupState: authenticatorResponseFlags.HasBackupState(), + } + } + if cc.IsPasswordless { result = append(result, *c[k].ToWebAuthn()) } @@ -61,38 +77,91 @@ func (c CredentialsWebAuthn) PasswordlessOnly() (result []webauthn.Credential) { // ToWebAuthnFiltered returns only the appropriate credentials for the requested // AAL. For AAL1, only passwordless credentials are returned, for AAL2, only // non-passwordless credentials are returned. -func (c CredentialsWebAuthn) ToWebAuthnFiltered(aal AuthenticatorAssuranceLevel) (result []webauthn.Credential) { +// +// authenticatorResponseFlags should be passed if the response is from an authenticator. It will be used to +// upgrade legacy webauthn credentials' BackupEligible and BackupState flags. +func (c CredentialsWebAuthn) ToWebAuthnFiltered(aal AuthenticatorAssuranceLevel, authenticatorResponseFlags *protocol.AuthenticatorFlags) (result []webauthn.Credential) { for k, cc := range c { + // Upgrade path for legacy webauthn credentials. Only possible if we are handling a response from an authenticator. + if c[k].Flags == nil && authenticatorResponseFlags != nil { + c[k].Flags = &CredentialWebAuthnFlags{ + BackupEligible: authenticatorResponseFlags.HasBackupEligible(), + BackupState: authenticatorResponseFlags.HasBackupState(), + } + } + if (aal == AuthenticatorAssuranceLevel1 && cc.IsPasswordless) || (aal == AuthenticatorAssuranceLevel2 && !cc.IsPasswordless) { result = append(result, *c[k].ToWebAuthn()) } - } return result } func (c *CredentialWebAuthn) ToWebAuthn() *webauthn.Credential { - return &webauthn.Credential{ + wc := &webauthn.Credential{ ID: c.ID, PublicKey: c.PublicKey, AttestationType: c.AttestationType, - Authenticator: webauthn.Authenticator{ + Transport: c.Transport, + } + + if c.Authenticator != nil { + wc.Authenticator = webauthn.Authenticator{ AAGUID: c.Authenticator.AAGUID, SignCount: c.Authenticator.SignCount, CloneWarning: c.Authenticator.CloneWarning, - }, + } } + + if c.Flags != nil { + wc.Flags = webauthn.CredentialFlags{ + UserPresent: c.Flags.UserPresent, + UserVerified: c.Flags.UserVerified, + BackupEligible: c.Flags.BackupEligible, + BackupState: c.Flags.BackupState, + } + } + + if c.Attestation != nil { + wc.Attestation = webauthn.CredentialAttestation{ + ClientDataJSON: c.Attestation.ClientDataJSON, + ClientDataHash: c.Attestation.ClientDataHash, + AuthenticatorData: c.Attestation.AuthenticatorData, + PublicKeyAlgorithm: c.Attestation.PublicKeyAlgorithm, + Object: c.Attestation.Object, + } + } + + return wc } type CredentialWebAuthn struct { - ID []byte `json:"id"` - PublicKey []byte `json:"public_key"` - AttestationType string `json:"attestation_type"` - Authenticator AuthenticatorWebAuthn `json:"authenticator"` - DisplayName string `json:"display_name"` - AddedAt time.Time `json:"added_at"` - IsPasswordless bool `json:"is_passwordless"` + ID []byte `json:"id"` + PublicKey []byte `json:"public_key"` + AttestationType string `json:"attestation_type"` + Authenticator *AuthenticatorWebAuthn `json:"authenticator,omitempty"` + DisplayName string `json:"display_name"` + AddedAt time.Time `json:"added_at"` + IsPasswordless bool `json:"is_passwordless"` + Flags *CredentialWebAuthnFlags `json:"flags,omitempty"` + Transport []protocol.AuthenticatorTransport `json:"transport,omitempty"` + Attestation *CredentialWebAuthnAttestation `json:"attestation,omitempty"` +} + +type CredentialWebAuthnFlags struct { + UserPresent bool `json:"user_present"` + UserVerified bool `json:"user_verified"` + BackupEligible bool `json:"backup_eligible"` + BackupState bool `json:"backup_state"` +} + +type CredentialWebAuthnAttestation struct { + ClientDataJSON []byte `json:"client_dataJSON"` + ClientDataHash []byte `json:"client_data_hash"` + AuthenticatorData []byte `json:"authenticator_data"` + PublicKeyAlgorithm int64 `json:"public_key_algorithm"` + Object []byte `json:"object"` } type AuthenticatorWebAuthn struct { diff --git a/identity/credentials_webauthn_test.go b/identity/credentials_webauthn_test.go index ed3dc9689a7b..8918898e71be 100644 --- a/identity/credentials_webauthn_test.go +++ b/identity/credentials_webauthn_test.go @@ -28,16 +28,16 @@ func TestCredentialConversion(t *testing.T) { actual := CredentialFromWebAuthn(expected, false).ToWebAuthn() assert.Equal(t, expected, actual) - actualList := CredentialsWebAuthn{*CredentialFromWebAuthn(expected, false)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel2) + actualList := CredentialsWebAuthn{*CredentialFromWebAuthn(expected, false)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel2, nil) assert.Equal(t, []webauthn.Credential{*expected}, actualList) - actualList = CredentialsWebAuthn{*CredentialFromWebAuthn(expected, true)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel1) + actualList = CredentialsWebAuthn{*CredentialFromWebAuthn(expected, true)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel1, nil) assert.Equal(t, []webauthn.Credential{*expected}, actualList) - actualList = CredentialsWebAuthn{*CredentialFromWebAuthn(expected, true)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel2) + actualList = CredentialsWebAuthn{*CredentialFromWebAuthn(expected, true)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel2, nil) assert.Len(t, actualList, 0) - actualList = CredentialsWebAuthn{*CredentialFromWebAuthn(expected, false)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel1) + actualList = CredentialsWebAuthn{*CredentialFromWebAuthn(expected, false)}.ToWebAuthnFiltered(AuthenticatorAssuranceLevel1, nil) assert.Len(t, actualList, 0) fromWebAuthn := CredentialFromWebAuthn(expected, true) @@ -58,7 +58,7 @@ func TestPasswordlessOnly(t *testing.T) { e := *CredentialFromWebAuthn(&webauthn.Credential{ID: []byte("e")}, true) expected := CredentialsWebAuthn{a, b, c, d, e} - actual := expected.PasswordlessOnly() + actual := expected.PasswordlessOnly(nil) require.Len(t, actual, 2) assert.Equal(t, []webauthn.Credential{*c.ToWebAuthn(), *e.ToWebAuthn()}, actual) } diff --git a/identity/handler_test.go b/identity/handler_test.go index 12bbde6cfa80..2133304a99a4 100644 --- a/identity/handler_test.go +++ b/identity/handler_test.go @@ -1644,7 +1644,7 @@ func TestHandler(t *testing.T) { AddedAt: time.Date(2022, 12, 16, 14, 11, 55, 0, time.UTC), PublicKey: []byte("pQECAyYgASFYIMJLQhJxQRzhnKPTcPCUODOmxYDYo2obrm9bhp5lvSZ3IlggXjhZvJaPUqF9PXqZqTdWYPR7R+b2n/Wi+IxKKXsS4rU="), DisplayName: "test", - Authenticator: identity.AuthenticatorWebAuthn{ + Authenticator: &identity.AuthenticatorWebAuthn{ AAGUID: []byte("rc4AAjW8xgpkiwsl8fBVAw=="), SignCount: 0, CloneWarning: false, @@ -1657,7 +1657,7 @@ func TestHandler(t *testing.T) { AddedAt: time.Date(2022, 12, 16, 14, 11, 55, 0, time.UTC), PublicKey: []byte("pQECAyYgASFYIMJLQhJxQRzhnKPTcPCUODOmxYDYo2obrm9bhp5lvSZ3IlggXjhZvJaPUqF9PXqZqTdWYPR7R+b2n/Wi+IxKKXsS4rU="), DisplayName: "test", - Authenticator: identity.AuthenticatorWebAuthn{ + Authenticator: &identity.AuthenticatorWebAuthn{ AAGUID: []byte("rc4AAjW8xgpkiwsl8fBVAw=="), SignCount: 0, CloneWarning: false, @@ -1670,7 +1670,7 @@ func TestHandler(t *testing.T) { AddedAt: time.Date(2022, 12, 16, 14, 11, 55, 0, time.UTC), PublicKey: []byte("pQECAyYgASFYIMJLQhJxQRzhnKPTcPCUODOmxYDYo2obrm9bhp5lvSZ3IlggXjhZvJaPUqF9PXqZqTdWYPR7R+b2n/Wi+IxKKXsS4rU="), DisplayName: "test", - Authenticator: identity.AuthenticatorWebAuthn{ + Authenticator: &identity.AuthenticatorWebAuthn{ AAGUID: []byte("rc4AAjW8xgpkiwsl8fBVAw=="), SignCount: 0, CloneWarning: false, @@ -1683,7 +1683,7 @@ func TestHandler(t *testing.T) { AddedAt: time.Date(2022, 12, 16, 14, 11, 55, 0, time.UTC), PublicKey: []byte("pQECAyYgASFYIMJLQhJxQRzhnKPTcPCUODOmxYDYo2obrm9bhp5lvSZ3IlggXjhZvJaPUqF9PXqZqTdWYPR7R+b2n/Wi+IxKKXsS4rU="), DisplayName: "test", - Authenticator: identity.AuthenticatorWebAuthn{ + Authenticator: &identity.AuthenticatorWebAuthn{ AAGUID: []byte("rc4AAjW8xgpkiwsl8fBVAw=="), SignCount: 0, CloneWarning: false, diff --git a/identity/manager.go b/identity/manager.go index 3bc5b08e0158..89c0259e6658 100644 --- a/identity/manager.go +++ b/identity/manager.go @@ -370,7 +370,7 @@ func (e *CreateIdentitiesError) Find(ident *Identity) *FailedIdentity { return nil } func (e *CreateIdentitiesError) ErrOrNil() error { - if e.failedIdentities == nil || len(e.failedIdentities) == 0 { + if len(e.failedIdentities) == 0 { return nil } return e diff --git a/internal/client-go/go.sum b/internal/client-go/go.sum index c966c8ddfd0d..6cc3f5911d11 100644 --- a/internal/client-go/go.sum +++ b/internal/client-go/go.sum @@ -4,6 +4,7 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/client-go/model_login_flow_state.go b/internal/client-go/model_login_flow_state.go index ce5570b79032..58af057c612f 100644 --- a/internal/client-go/model_login_flow_state.go +++ b/internal/client-go/model_login_flow_state.go @@ -16,7 +16,7 @@ import ( "fmt" ) -// LoginFlowState The state represents the state of the login flow. choose_method: ask the user to choose a method (e.g. login account via email) sent_email: the email has been sent to the user passed_challenge: the request was successful and the login challenge was passed. +// LoginFlowState The experimental state represents the state of a login flow. This field is EXPERIMENTAL and subject to change! type LoginFlowState string // List of loginFlowState diff --git a/internal/client-go/model_recovery_flow_state.go b/internal/client-go/model_recovery_flow_state.go index 1c660ba043b9..d1fa3618882a 100644 --- a/internal/client-go/model_recovery_flow_state.go +++ b/internal/client-go/model_recovery_flow_state.go @@ -16,7 +16,7 @@ import ( "fmt" ) -// RecoveryFlowState The state represents the state of the recovery flow. choose_method: ask the user to choose a method (e.g. recover account via email) sent_email: the email has been sent to the user passed_challenge: the request was successful and the recovery challenge was passed. +// RecoveryFlowState The experimental state represents the state of a recovery flow. This field is EXPERIMENTAL and subject to change! type RecoveryFlowState string // List of recoveryFlowState diff --git a/internal/client-go/model_registration_flow_state.go b/internal/client-go/model_registration_flow_state.go index 86f3fd38cff0..15fd9f532d4b 100644 --- a/internal/client-go/model_registration_flow_state.go +++ b/internal/client-go/model_registration_flow_state.go @@ -16,7 +16,7 @@ import ( "fmt" ) -// RegistrationFlowState choose_method: ask the user to choose a method (e.g. registration with email) sent_email: the email has been sent to the user passed_challenge: the request was successful and the registration challenge was passed. +// RegistrationFlowState The experimental state represents the state of a registration flow. This field is EXPERIMENTAL and subject to change! type RegistrationFlowState string // List of registrationFlowState diff --git a/internal/client-go/model_settings_flow_state.go b/internal/client-go/model_settings_flow_state.go index f994c786a2d8..70093c9c4a03 100644 --- a/internal/client-go/model_settings_flow_state.go +++ b/internal/client-go/model_settings_flow_state.go @@ -16,7 +16,7 @@ import ( "fmt" ) -// SettingsFlowState show_form: No user data has been collected, or it is invalid, and thus the form should be shown. success: Indicates that the settings flow has been updated successfully with the provided data. Done will stay true when repeatedly checking. If set to true, done will revert back to false only when a flow with invalid (e.g. \"please use a valid phone number\") data was sent. +// SettingsFlowState The experimental state represents the state of a settings flow. This field is EXPERIMENTAL and subject to change! type SettingsFlowState string // List of settingsFlowState diff --git a/internal/client-go/model_verification_flow_state.go b/internal/client-go/model_verification_flow_state.go index bea74568c94d..56b65e0c0a5b 100644 --- a/internal/client-go/model_verification_flow_state.go +++ b/internal/client-go/model_verification_flow_state.go @@ -16,7 +16,7 @@ import ( "fmt" ) -// VerificationFlowState The state represents the state of the verification flow. choose_method: ask the user to choose a method (e.g. recover account via email) sent_email: the email has been sent to the user passed_challenge: the request was successful and the recovery challenge was passed. +// VerificationFlowState The experimental state represents the state of a verification flow. This field is EXPERIMENTAL and subject to change! type VerificationFlowState string // List of verificationFlowState diff --git a/internal/httpclient/model_login_flow_state.go b/internal/httpclient/model_login_flow_state.go index ce5570b79032..58af057c612f 100644 --- a/internal/httpclient/model_login_flow_state.go +++ b/internal/httpclient/model_login_flow_state.go @@ -16,7 +16,7 @@ import ( "fmt" ) -// LoginFlowState The state represents the state of the login flow. choose_method: ask the user to choose a method (e.g. login account via email) sent_email: the email has been sent to the user passed_challenge: the request was successful and the login challenge was passed. +// LoginFlowState The experimental state represents the state of a login flow. This field is EXPERIMENTAL and subject to change! type LoginFlowState string // List of loginFlowState diff --git a/internal/httpclient/model_recovery_flow_state.go b/internal/httpclient/model_recovery_flow_state.go index 1c660ba043b9..d1fa3618882a 100644 --- a/internal/httpclient/model_recovery_flow_state.go +++ b/internal/httpclient/model_recovery_flow_state.go @@ -16,7 +16,7 @@ import ( "fmt" ) -// RecoveryFlowState The state represents the state of the recovery flow. choose_method: ask the user to choose a method (e.g. recover account via email) sent_email: the email has been sent to the user passed_challenge: the request was successful and the recovery challenge was passed. +// RecoveryFlowState The experimental state represents the state of a recovery flow. This field is EXPERIMENTAL and subject to change! type RecoveryFlowState string // List of recoveryFlowState diff --git a/internal/httpclient/model_registration_flow_state.go b/internal/httpclient/model_registration_flow_state.go index 86f3fd38cff0..15fd9f532d4b 100644 --- a/internal/httpclient/model_registration_flow_state.go +++ b/internal/httpclient/model_registration_flow_state.go @@ -16,7 +16,7 @@ import ( "fmt" ) -// RegistrationFlowState choose_method: ask the user to choose a method (e.g. registration with email) sent_email: the email has been sent to the user passed_challenge: the request was successful and the registration challenge was passed. +// RegistrationFlowState The experimental state represents the state of a registration flow. This field is EXPERIMENTAL and subject to change! type RegistrationFlowState string // List of registrationFlowState diff --git a/internal/httpclient/model_settings_flow_state.go b/internal/httpclient/model_settings_flow_state.go index f994c786a2d8..70093c9c4a03 100644 --- a/internal/httpclient/model_settings_flow_state.go +++ b/internal/httpclient/model_settings_flow_state.go @@ -16,7 +16,7 @@ import ( "fmt" ) -// SettingsFlowState show_form: No user data has been collected, or it is invalid, and thus the form should be shown. success: Indicates that the settings flow has been updated successfully with the provided data. Done will stay true when repeatedly checking. If set to true, done will revert back to false only when a flow with invalid (e.g. \"please use a valid phone number\") data was sent. +// SettingsFlowState The experimental state represents the state of a settings flow. This field is EXPERIMENTAL and subject to change! type SettingsFlowState string // List of settingsFlowState diff --git a/internal/httpclient/model_verification_flow_state.go b/internal/httpclient/model_verification_flow_state.go index bea74568c94d..56b65e0c0a5b 100644 --- a/internal/httpclient/model_verification_flow_state.go +++ b/internal/httpclient/model_verification_flow_state.go @@ -16,7 +16,7 @@ import ( "fmt" ) -// VerificationFlowState The state represents the state of the verification flow. choose_method: ask the user to choose a method (e.g. recover account via email) sent_email: the email has been sent to the user passed_challenge: the request was successful and the recovery challenge was passed. +// VerificationFlowState The experimental state represents the state of a verification flow. This field is EXPERIMENTAL and subject to change! type VerificationFlowState string // List of verificationFlowState diff --git a/persistence/sql/identity/persister_identity.go b/persistence/sql/identity/persister_identity.go index 3e982730398d..c14686d85092 100644 --- a/persistence/sql/identity/persister_identity.go +++ b/persistence/sql/identity/persister_identity.go @@ -972,7 +972,7 @@ func (p *IdentityPersister) ListIdentities(ctx context.Context, params identity. ) } - if params.IdsFilter != nil && len(params.IdsFilter) != 0 { + if len(params.IdsFilter) != 0 { wheres += ` AND identities.id in (?) ` diff --git a/schema/handler.go b/schema/handler.go index fe2842b14a56..acf6a0dc786a 100644 --- a/schema/handler.go +++ b/schema/handler.go @@ -69,7 +69,16 @@ func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) { // //nolint:deadcode,unused //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions -type identitySchema = json.RawMessage +type identitySchema json.RawMessage + +func (m identitySchema) MarshalJSON() ([]byte, error) { + return json.RawMessage(m).MarshalJSON() +} + +func (m *identitySchema) UnmarshalJSON(data []byte) error { + mm := json.RawMessage(*m) + return mm.UnmarshalJSON(data) +} // Get Identity JSON Schema Response // @@ -151,7 +160,7 @@ type identitySchemaContainer struct { // The ID of the Identity JSON Schema ID string `json:"id"` // The actual Identity JSON Schema - Schema identitySchema `json:"schema"` + Schema json.RawMessage `json:"schema"` } // List Identity JSON Schemas Response diff --git a/schema/handler_test.go b/schema/handler_test.go index 615d8092269d..36aeb3aea75d 100644 --- a/schema/handler_test.go +++ b/schema/handler_test.go @@ -189,7 +189,7 @@ func TestHandler(t *testing.T) { body := getFromTSPaginated(t, 0, 2, http.StatusOK) var result []client.IdentitySchemaContainer - require.NoError(t, json.Unmarshal(body, &result)) + require.NoError(t, json.Unmarshal(body, &result), "%s", body) ids_orig := []string{} for _, s := range schemas { diff --git a/selfservice/flow/error.go b/selfservice/flow/error.go index d666822fa5c0..8449ec58ad3e 100644 --- a/selfservice/flow/error.go +++ b/selfservice/flow/error.go @@ -73,7 +73,7 @@ func NewFlowReplacedError(message *text.Message) *ReplacedError { return &ReplacedError{ DefaultError: x.ErrGone.WithID(text.ErrIDSelfServiceFlowReplaced). WithError("self-service flow replaced"). - WithReasonf(message.Text), + WithReason(message.Text), } } diff --git a/selfservice/strategy/passkey/fixtures/registration/success/android/internal_context.json b/selfservice/strategy/passkey/fixtures/registration/success/android/internal_context.json new file mode 100644 index 000000000000..cd9949ce0093 --- /dev/null +++ b/selfservice/strategy/passkey/fixtures/registration/success/android/internal_context.json @@ -0,0 +1,7 @@ +{ + "passkey_session_data": { + "challenge": "mFtAwmtDDdwcO6200I2H6oWjzOiF21lZhQVlrC4tdaU", + "user_id": "d29OeDNJVjdYR2NRa09RVHhNVG1ZbHE1ejBDYzM1dGV3UWxFT25yaUJKcTUyb0VOR0pUMk5PeXExRXp3Z2M2dg", + "userVerification": "" + } +} diff --git a/selfservice/strategy/passkey/fixtures/registration/success/android/response.json b/selfservice/strategy/passkey/fixtures/registration/success/android/response.json new file mode 100644 index 000000000000..8b480b356790 --- /dev/null +++ b/selfservice/strategy/passkey/fixtures/registration/success/android/response.json @@ -0,0 +1,9 @@ +{ + "id": "mK2RV0b2NUGDsj8QqH0XtQ", + "rawId": "mK2RV0b2NUGDsj8QqH0XtQ", + "response": { + "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUJYVxRmHaAcJuz7n2X5FJILFPwxIhVpoURyBRglMxnFpdAAAAAOqbjWZNAR0hPOS2tIy1ddQAEJitkVdG9jVBg7I_EKh9F7WlAQIDJiABIVggjEkfDDjIm8yAYfth4u0EV7ApX4kclQONhpK5BLc7W6wiWCCHiHhRNqf8Qhc7bjoIFTqw4lafiC7yrXvojU_WMNcutA", + "clientDataJson": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibUZ0QXdtdEREZHdjTzYyMDBJMkg2b1dqek9pRjIxbFpoUVZsckM0dGRhVSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOlMyUmZOWWdKbVFpS2dkNi1zZGJqVzdwaGNMX09UUDR2R0U4TDUxUTJHQjAiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20udHJwLmFuZC5wZXJzb25hbC5xbCJ9" + }, + "type": "public-key" +} diff --git a/selfservice/strategy/passkey/fixtures/registration/success/identity.json b/selfservice/strategy/passkey/fixtures/registration/success/browser/identity.json similarity index 100% rename from selfservice/strategy/passkey/fixtures/registration/success/identity.json rename to selfservice/strategy/passkey/fixtures/registration/success/browser/identity.json diff --git a/selfservice/strategy/passkey/fixtures/registration/success/internal_context.json b/selfservice/strategy/passkey/fixtures/registration/success/browser/internal_context.json similarity index 100% rename from selfservice/strategy/passkey/fixtures/registration/success/internal_context.json rename to selfservice/strategy/passkey/fixtures/registration/success/browser/internal_context.json diff --git a/selfservice/strategy/passkey/fixtures/registration/success/response.json b/selfservice/strategy/passkey/fixtures/registration/success/browser/response.json similarity index 100% rename from selfservice/strategy/passkey/fixtures/registration/success/response.json rename to selfservice/strategy/passkey/fixtures/registration/success/browser/response.json diff --git a/selfservice/strategy/passkey/passkey_login.go b/selfservice/strategy/passkey/passkey_login.go index b7957a85ad74..5fffcdaac3c3 100644 --- a/selfservice/strategy/passkey/passkey_login.go +++ b/selfservice/strategy/passkey/passkey_login.go @@ -266,8 +266,7 @@ func (s *Strategy) loginAuthenticate(ctx context.Context, r *http.Request, f *lo WithWrap(err))) } - webAuthCreds := o.Credentials.PasswordlessOnly() - + webAuthCreds := o.Credentials.PasswordlessOnly(&webAuthnResponse.Response.AuthenticatorData.Flags) _, err = web.ValidateDiscoverableLogin( func(rawID, userHandle []byte) (user webauthn.User, err error) { return webauthnx.NewUser(userHandle, webAuthCreds, web.Config), nil diff --git a/selfservice/strategy/passkey/passkey_registration.go b/selfservice/strategy/passkey/passkey_registration.go index cad0e02fda5d..fe3ec305e8b1 100644 --- a/selfservice/strategy/passkey/passkey_registration.go +++ b/selfservice/strategy/passkey/passkey_registration.go @@ -147,7 +147,7 @@ func (s *Strategy) Register(w http.ResponseWriter, r *http.Request, regFlow *reg herodot.ErrInternalServerError.WithReasonf("Expected WebAuthN in internal context to be an object but got: %s", err))) } - if webAuthnSess.UserID == nil || len(webAuthnSess.UserID) == 0 { + if len(webAuthnSess.UserID) == 0 { return s.handleRegistrationError(w, r, regFlow, params, errors.WithStack( herodot.ErrInternalServerError.WithReasonf("Expected WebAuthN session data to contain a user ID"))) } diff --git a/selfservice/strategy/passkey/passkey_registration_test.go b/selfservice/strategy/passkey/passkey_registration_test.go index 86a7a7992e68..28d971ef869e 100644 --- a/selfservice/strategy/passkey/passkey_registration_test.go +++ b/selfservice/strategy/passkey/passkey_registration_test.go @@ -5,6 +5,7 @@ package passkey_test import ( _ "embed" + "github.com/ory/x/assertx" "net/url" "testing" @@ -28,12 +29,21 @@ import ( var ( flows = []string{"spa", "browser"} - //go:embed fixtures/registration/success/response.json + //go:embed fixtures/registration/success/browser/response.json registrationFixtureSuccessResponse []byte - //go:embed fixtures/registration/success/internal_context.json - registrationFixtureSuccessInternalContext []byte + + //go:embed fixtures/registration/success/browser/internal_context.json + registrationFixtureSuccessBrowserInternalContext []byte + + //go:embed fixtures/registration/success/android/response.json + registrationFixtureSuccessAndroidResponse []byte + + //go:embed fixtures/registration/success/android/internal_context.json + registrationFixtureSuccessAndroidInternalContext []byte + //go:embed fixtures/registration/failure/internal_context_missing_user_id.json registrationFixtureFailureInternalContextMissingUserID []byte + //go:embed fixtures/registration/failure/internal_context_wrong_user_id.json registrationFixtureFailureInternalContextWrongUserID []byte ) @@ -180,7 +190,7 @@ func TestRegistration(t *testing.T) { for _, f := range flows { t.Run("type="+f, func(t *testing.T) { - actual, _, _ := fix.submitPasskeyRegistration(t, f, testhelpers.NewClientWithCookies(t), values) + actual, _, _ := fix.submitPasskeyBrowserRegistration(t, f, testhelpers.NewClientWithCookies(t), values) assert.NotEmpty(t, gjson.Get(actual, "id").String(), "%s", actual) assert.Contains(t, gjson.Get(actual, "ui.action").String(), fix.publicTS.URL+registration.RouteSubmitFlow, "%s", actual) registrationhelpers.CheckFormContent(t, []byte(actual), node.PasskeyRegister, "csrf_token", "traits.username", "traits.foobar") @@ -220,7 +230,7 @@ func TestRegistration(t *testing.T) { for _, f := range flows { t.Run("type="+f, func(t *testing.T) { - actual, _, _ := fix.submitPasskeyRegistration(t, f, testhelpers.NewClientWithCookies(t), values, + actual, _, _ := fix.submitPasskeyBrowserRegistration(t, f, testhelpers.NewClientWithCookies(t), values, withInternalContext(sqlxx.JSONRawMessage(tc.internalContext))) if flowIsSPA(f) { assert.Equal(t, "Internal Server Error", gjson.Get(actual, "error.status").String(), "%s", actual) @@ -415,5 +425,49 @@ func TestRegistration(t *testing.T) { }) } }) + + t.Run("case=should create the identity when using android", func(t *testing.T) { + fix.useRedirNoSessionTS() + t.Cleanup(fix.useRedirTS) + fix.disableSessionAfterRegistration() + + prevRPID := fix.conf.GetProvider(fix.ctx).String(config.ViperKeyPasskeyRPID) + prevOrigins := fix.conf.GetProvider(fix.ctx).String(config.ViperKeyPasskeyRPOrigins) + + fix.conf.MustSet(fix.ctx, config.ViperKeyPasskeyRPID, "www.troweprice.com") + fix.conf.MustSet(fix.ctx, config.ViperKeyPasskeyRPOrigins, []string{"android:apk-key-hash:S2RfNYgJmQiKgd6-sdbjW7phcL_OTP4vGE8L51Q2GB0"}) + t.Cleanup(func() { + fix.conf.MustSet(fix.ctx, config.ViperKeyPasskeyRPID, prevRPID) + fix.conf.MustSet(fix.ctx, config.ViperKeyPasskeyRPOrigins, prevOrigins) + }) + + for _, f := range flows { + t.Run("type="+f, func(t *testing.T) { + email := f + "-" + testhelpers.RandomEmail() + userID := f + "-user-" + randx.MustString(8, randx.AlphaNum) + + expectReturnTo := fix.redirNoSessionTS.URL + "/registration-return-ts" + actual, res, _ := fix.submitPasskeyAndroidRegistration(t, f, testhelpers.NewClientWithCookies(t), func(v url.Values) { + values(email)(v) + v.Set(node.PasskeyRegister, string(registrationFixtureSuccessAndroidResponse)) + }, withUserID(userID)) + + if f == "spa" { + expectReturnTo = fix.publicTS.URL + 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) + } + + assert.Contains(t, res.Request.URL.String(), expectReturnTo, "%+v\n\t%s", res.Request, assertx.PrettifyJSONPayload(t, actual)) + + i, _, err := fix.reg.PrivilegedIdentityPool().FindByCredentialsIdentifier(fix.ctx, identity.CredentialsTypePasskey, userID) + require.NoError(t, err) + assert.Equal(t, "aal1", i.InternalAvailableAAL.String) + assert.Equal(t, email, gjson.GetBytes(i.Traits, "username").String(), "%s", actual) + }) + } + }) }) } diff --git a/selfservice/strategy/passkey/testfixture_test.go b/selfservice/strategy/passkey/testfixture_test.go index 1f3090177341..3f0fadfd2387 100644 --- a/selfservice/strategy/passkey/testfixture_test.go +++ b/selfservice/strategy/passkey/testfixture_test.go @@ -241,12 +241,6 @@ type submitPasskeyOpt struct { internalContext sqlxx.JSONRawMessage } -func newSubmitPasskeyOpt() *submitPasskeyOpt { - return &submitPasskeyOpt{ - internalContext: registrationFixtureSuccessInternalContext, - } -} - type submitPasskeyOption func(o *submitPasskeyOpt) func withUserID(id string) submitPasskeyOption { @@ -261,6 +255,29 @@ func withInternalContext(ic sqlxx.JSONRawMessage) submitPasskeyOption { } } +func (fix *fixture) submitPasskeyBrowserRegistration( + t *testing.T, + flowType string, + client *http.Client, + cb func(values url.Values), + opts ...submitPasskeyOption, +) (string, *http.Response, *kratos.RegistrationFlow) { + return fix.submitPasskeyRegistration(t, flowType, client, cb, append([]submitPasskeyOption{withInternalContext(registrationFixtureSuccessBrowserInternalContext)}, opts...)...) +} + +func (fix *fixture) submitPasskeyAndroidRegistration( + t *testing.T, + flowType string, + client *http.Client, + cb func(values url.Values), + opts ...submitPasskeyOption, +) (string, *http.Response, *kratos.RegistrationFlow) { + return fix.submitPasskeyRegistration(t, flowType, client, cb, + append([]submitPasskeyOption{withInternalContext( + registrationFixtureSuccessAndroidInternalContext, + )}, opts...)...) +} + func (fix *fixture) submitPasskeyRegistration( t *testing.T, flowType string, @@ -268,7 +285,7 @@ func (fix *fixture) submitPasskeyRegistration( cb func(values url.Values), opts ...submitPasskeyOption, ) (string, *http.Response, *kratos.RegistrationFlow) { - o := newSubmitPasskeyOpt() + o := &submitPasskeyOpt{} for _, fn := range opts { fn(o) } @@ -302,7 +319,7 @@ func (fix *fixture) submitPasskeyRegistration( } func (fix *fixture) makeRegistration(t *testing.T, flowType string, values func(v url.Values), opts ...submitPasskeyOption) (actual string, res *http.Response, fetchedFlow *registration.Flow) { - actual, res, actualFlow := fix.submitPasskeyRegistration(t, flowType, testhelpers.NewClientWithCookies(t), values, opts...) + actual, res, actualFlow := fix.submitPasskeyBrowserRegistration(t, flowType, testhelpers.NewClientWithCookies(t), values, opts...) fetchedFlow, err := fix.reg.RegistrationFlowPersister().GetRegistrationFlow(fix.ctx, uuid.FromStringOrNil(actualFlow.Id)) require.NoError(t, err) diff --git a/selfservice/strategy/webauthn/login.go b/selfservice/strategy/webauthn/login.go index 4279ed48bc96..97fdd1190ab2 100644 --- a/selfservice/strategy/webauthn/login.go +++ b/selfservice/strategy/webauthn/login.go @@ -71,7 +71,7 @@ func (s *Strategy) populateLoginMethod(r *http.Request, sr *login.Flow, i *ident return errors.WithStack(err) } - webAuthCreds := conf.Credentials.ToWebAuthnFiltered(aal) + webAuthCreds := conf.Credentials.ToWebAuthnFiltered(aal, nil) if len(webAuthCreds) == 0 { // Identity has no webauthn return webauthnx.ErrNoCredentials @@ -283,7 +283,7 @@ func (s *Strategy) loginAuthenticate(ctx context.Context, r *http.Request, f *lo 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) + webAuthCreds := o.Credentials.ToWebAuthnFiltered(aal, &webAuthnResponse.Response.AuthenticatorData.Flags) if f.IsRefresh() { webAuthCreds = o.Credentials.ToWebAuthn() } diff --git a/spec/api.json b/spec/api.json index 2363d0632564..a01107f04f9e 100644 --- a/spec/api.json +++ b/spec/api.json @@ -1425,13 +1425,13 @@ "type": "object" }, "loginFlowState": { - "description": "The state represents the state of the login flow.\n\nchoose_method: ask the user to choose a method (e.g. login account via email)\nsent_email: the email has been sent to the user\npassed_challenge: the request was successful and the login challenge was passed.", + "description": "The experimental state represents the state of a login flow. This field is EXPERIMENTAL and subject to change!", "enum": [ "choose_method", "sent_email", "passed_challenge" ], - "title": "Login Flow State", + "title": "Login flow state (experimental)", "type": "string" }, "logoutFlow": { @@ -1723,14 +1723,13 @@ "type": "object" }, "recoveryFlowState": { - "description": "The state represents the state of the recovery flow.\n\nchoose_method: ask the user to choose a method (e.g. recover account via email)\nsent_email: the email has been sent to the user\npassed_challenge: the request was successful and the recovery challenge was passed.", + "description": "The experimental state represents the state of a recovery flow. This field is EXPERIMENTAL and subject to change!", "enum": [ "choose_method", "sent_email", "passed_challenge" ], - "title": "Recovery Flow State", - "type": "string" + "title": "Recovery flow state (experimental)" }, "recoveryIdentityAddress": { "properties": { @@ -1863,13 +1862,13 @@ "type": "object" }, "registrationFlowState": { - "description": "choose_method: ask the user to choose a method (e.g. registration with email)\nsent_email: the email has been sent to the user\npassed_challenge: the request was successful and the registration challenge was passed.", + "description": "The experimental state represents the state of a registration flow. This field is EXPERIMENTAL and subject to change!", "enum": [ "choose_method", "sent_email", "passed_challenge" ], - "title": "State represents the state of this request:", + "title": "Registration flow state (experimental)", "type": "string" }, "selfServiceFlowExpiredError": { @@ -2092,12 +2091,12 @@ "type": "object" }, "settingsFlowState": { - "description": "show_form: No user data has been collected, or it is invalid, and thus the form should be shown.\nsuccess: Indicates that the settings flow has been updated successfully with the provided data.\nDone will stay true when repeatedly checking. If set to true, done will revert back to false only\nwhen a flow with invalid (e.g. \"please use a valid phone number\") data was sent.", + "description": "The experimental state represents the state of a settings flow. This field is EXPERIMENTAL and subject to change!", "enum": [ "show_form", "success" ], - "title": "State represents the state of this flow. It knows two states:", + "title": "Settings flow state (experimental)", "type": "string" }, "successfulCodeExchangeResponse": { @@ -3729,14 +3728,13 @@ "type": "object" }, "verificationFlowState": { - "description": "The state represents the state of the verification flow.\n\nchoose_method: ask the user to choose a method (e.g. recover account via email)\nsent_email: the email has been sent to the user\npassed_challenge: the request was successful and the recovery challenge was passed.", + "description": "The experimental state represents the state of a verification flow. This field is EXPERIMENTAL and subject to change!", "enum": [ "choose_method", "sent_email", "passed_challenge" ], - "title": "Verification Flow State", - "type": "string" + "title": "Verification flow state (experimental)" }, "version": { "properties": { diff --git a/spec/swagger.json b/spec/swagger.json index 3cbbf82b340b..dd5268c66200 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -4556,11 +4556,6 @@ } } }, - "loginFlowState": { - "description": "The state represents the state of the login flow.\n\nchoose_method: ask the user to choose a method (e.g. login account via email)\nsent_email: the email has been sent to the user\npassed_challenge: the request was successful and the login challenge was passed.", - "type": "string", - "title": "Login Flow State" - }, "logoutFlow": { "description": "Logout Flow", "type": "object", @@ -4840,11 +4835,6 @@ } } }, - "recoveryFlowState": { - "description": "The state represents the state of the recovery flow.\n\nchoose_method: ask the user to choose a method (e.g. recover account via email)\nsent_email: the email has been sent to the user\npassed_challenge: the request was successful and the recovery challenge was passed.", - "type": "string", - "title": "Recovery Flow State" - }, "recoveryIdentityAddress": { "type": "object", "required": [ @@ -4975,11 +4965,6 @@ } } }, - "registrationFlowState": { - "description": "choose_method: ask the user to choose a method (e.g. registration with email)\nsent_email: the email has been sent to the user\npassed_challenge: the request was successful and the registration challenge was passed.", - "type": "string", - "title": "State represents the state of this request:" - }, "selfServiceFlowExpiredError": { "description": "Is sent when a flow is expired", "type": "object", @@ -5201,11 +5186,6 @@ } } }, - "settingsFlowState": { - "description": "show_form: No user data has been collected, or it is invalid, and thus the form should be shown.\nsuccess: Indicates that the settings flow has been updated successfully with the provided data.\nDone will stay true when repeatedly checking. If set to true, done will revert back to false only\nwhen a flow with invalid (e.g. \"please use a valid phone number\") data was sent.", - "type": "string", - "title": "State represents the state of this flow. It knows two states:" - }, "successfulCodeExchangeResponse": { "description": "The Response for Registration Flows via API", "type": "object", @@ -6679,11 +6659,6 @@ } } }, - "verificationFlowState": { - "description": "The state represents the state of the verification flow.\n\nchoose_method: ask the user to choose a method (e.g. recover account via email)\nsent_email: the email has been sent to the user\npassed_challenge: the request was successful and the recovery challenge was passed.", - "type": "string", - "title": "Verification Flow State" - }, "version": { "type": "object", "properties": { diff --git a/test/e2e/mock/httptarget/go.mod b/test/e2e/mock/httptarget/go.mod index 2d66a9ff4f48..a82d636fb196 100644 --- a/test/e2e/mock/httptarget/go.mod +++ b/test/e2e/mock/httptarget/go.mod @@ -1,6 +1,6 @@ module github.com/ory/mock -go 1.22.1 +go 1.23.1 require ( github.com/julienschmidt/httprouter v1.3.0