ory / kratos

The most scalable and customizable identity server on the market. Replace your Homegrown, Auth0, Okta, Firebase with better UX and DX. Has all the tablestakes: Passkeys, Social Sign In, Multi-Factor Auth, SMS, SAML, TOTP, and more. Written in Go, cloud native, headless, API-first. Available as a service on Ory Network and for self-hosters.
https://www.ory.sh/?utm_source=github&utm_medium=banner&utm_campaign=kratos
Apache License 2.0
11.29k stars 963 forks source link

Not possible to create account when identity schema contains array #2360

Open PetrSilar opened 2 years ago

PetrSilar commented 2 years ago

Preflight checklist

Describe the bug

It is not possible to create new account (using GitHub in this case} when there is array property in used identity schema using version 0.9.0-alpha.3.

The identity schema looks like this (for example):

{
  "$id": "https://example.com/fruits.schema.json",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Person",
  "type": "object",
  "properties": {
    "traits": {
      "type": "object",
      "properties": {
        "email": {
          "type": "string",
          "format": "email",
          "title": "E-Mail",
          "minLength": 3,
          "ory.sh/kratos": {
            "credentials": {
              "password": {
                "identifier": true
              }
            },
            "verification": {
              "via": "email"
            },
            "recovery": {
              "via": "email"
            }
          }
        },
        "fruits": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "name": {
                "description": "Fruit's name",
                "type": "string"
              },
              "weight": {
                "description": "Fruit's weight",
                "type": "integer"
              },
              "color": {
                "description": "Fruit's color",
                "type": "string",
                "default": "green"
              }
            },
            "required": [ "name", "weight" ]
          },
          "default": []
        }
      },
      "required": [
        "email"
      ],
      "additionalProperties": false
    }
  }
}

Please note fruits property. It's array, its items have to be an object containing at least name and weight properties. GitHub's jsonnet is like this:

local claims = {
  email_verified: true # lets set it to 'true' for now
} + std.extVar('claims');

{
  identity: {
    traits: {
      [if "email" in claims && claims.email_verified then "email" else null]: claims.email
    },
  },
}

Using e.g. quickstart example with modified configuration to use this schema and jsonnet, the creation of new account fails with expected array, but got object error. Please note this is regression since it works for me with version 0.6.3 (I didn't test this with other versions).

Reproducing the bug

Steps to reproduce:

  1. Modify configuration of quickstart example to use the identity schema and provider's jsonnet shown above.
  2. Using selfservice node's UI try create a new account using your GitHub identity.

Expected result: New user is created in Kratos Actual result: User is not created and error message expected array, but got object is shown.

Relevant log output

{
  "audience": "application",
  "file": "/project/selfservice/strategy/oidc/strategy_registration.go:220",
  "func": "github.com/ory/kratos/selfservice/strategy/oidc.(*Strategy).processRegistration",
  "http_request": {
  },
  "level": "debug",
  "mapper_jsonnet_output": "{\n   \"identity\": {\n      \"traits\": {\n         \"email\": \"my.email@example.com\"\n      }\n   }\n}\n",
  "mapper_jsonnet_url": "file:///etc/config/kratos/oidc/oidc.github.jsonnet",
  "msg": "OpenID Connect Jsonnet mapper completed.",
  "oidc_claims": {
    "iss": "https://github.com/login/oauth/access_token",
    "sub": "12345678",
    "nickname": "Nickname",
    "profile": "https://github.com/Nickname",
    "picture": "https://avatars.githubusercontent.com/u/12345678?v=4",
    "email": "my.email@example.com",
    "email_verified": true,
    "updated_at": 1617017523
  },
  "oidc_provider": "github",
  "service_name": "Ory Kratos",
  "service_version": "v0.9.0-alpha.3",
  "time": "2022-04-04T11:11:58Z"
}
{
  "audience": "application",
  "file": "/project/selfservice/strategy/oidc/strategy_registration.go:233",
  "func": "github.com/ory/kratos/selfservice/strategy/oidc.(*Strategy).processRegistration",
  "http_request": {
  },
  "identity_traits": {
    "email": "my.email@example.com",
    "fruits": {}
  },
  "level": "debug",
  "mapper_jsonnet_output": "{\n   \"identity\": {\n      \"traits\": {\n         \"email\": \"my.email@example.com\"\n      }\n   }\n}\n",
  "mapper_jsonnet_url": "file:///etc/config/kratos/oidc/oidc.github.jsonnet",
  "msg": "Merged form values and OpenID Connect Jsonnet output.",
  "oidc_provider": "github",
  "service_name": "Ory Kratos",
  "service_version": "v0.9.0-alpha.3",
  "time": "2022-04-04T11:11:58Z"
}
{
  "audience": "audit",
  "error": {
    "message": "I[#/traits/fruits] S[#/properties/traits/properties/fruits/type] expected array, but got object",
    "stack_trace": "\ngithub.com/ory/kratos/schema.(*Validator).Validate\n\t/project/schema/validator.go:69\ngithub.com/ory/kratos/identity.(*Validator).ValidateWithRunner\n\t/project/identity/validator.go:51\ngithub.com/ory/kratos/identity.(*Validator).Validate\n\t/project/identity/validator.go:55\ngithub.com/ory/kratos/selfservice/strategy/oidc.(*Strategy).processRegistration\n\t/project/selfservice/strategy/oidc/strategy_registration.go:236\ngithub.com/ory/kratos/selfservice/strategy/oidc.(*Strategy).handleCallback\n\t/project/selfservice/strategy/oidc/strategy.go:347\ngithub.com/ory/kratos/selfservice/strategy.disabledWriter\n\t/project/selfservice/strategy/handler.go:25\ngithub.com/ory/kratos/selfservice/strategy.IsDisabled.func1\n\t/project/selfservice/strategy/handler.go:30\ngithub.com/ory/kratos/x.NoCacheHandle.func1\n\t/project/x/nocache.go:18\ngithub.com/ory/kratos/x.NoCacheHandle.func1\n\t/project/x/nocache.go:18\ngithub.com/julienschmidt/httprouter.(*Router).ServeHTTP\n\t/go/pkg/mod/github.com/julienschmidt/httprouter@v1.3.0/router.go:387\ngithub.com/ory/nosurf.(*CSRFHandler).handleSuccess\n\t/go/pkg/mod/github.com/ory/nosurf@v1.2.7/handler.go:234\ngithub.com/ory/nosurf.(*CSRFHandler).ServeHTTP\n\t/go/pkg/mod/github.com/ory/nosurf@v1.2.7/handler.go:191\ngithub.com/urfave/negroni.Wrap.func1\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:46\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/kratos/x.glob..func1\n\t/project/x/clean_url.go:12\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2047\ngithub.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerResponseSize.func1\n\t/go/pkg/mod/github.com/prometheus/client_golang@v1.11.0/prometheus/promhttp/instrument_server.go:198\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2047\ngithub.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerCounter.func1\n\t/go/pkg/mod/github.com/prometheus/client_golang@v1.11.0/prometheus/promhttp/instrument_server.go:101\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2047\ngithub.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerDuration.func1\n\t/go/pkg/mod/github.com/prometheus/client_golang@v1.11.0/prometheus/promhttp/instrument_server.go:68\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2047\ngithub.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerDuration.func2\n\t/go/pkg/mod/github.com/prometheus/client_golang@v1.11.0/prometheus/promhttp/instrument_server.go:76\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2047\ngithub.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerRequestSize.func1\n\t/go/pkg/mod/github.com/prometheus/client_golang@v1.11.0/prometheus/promhttp/instrument_server.go:165\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2047\ngithub.com/ory/x/prometheusx.Metrics.instrumentHandlerStatusBucket.func1\n\t/go/pkg/mod/github.com/ory/x@v0.0.358/prometheusx/metrics.go:108\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2047\ngithub.com/ory/x/prometheusx.(*MetricsManager).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.358/prometheusx/middleware.go:30"
  },
  "file": "/project/selfservice/flow/registration/error.go:76",
  "func": "github.com/ory/kratos/selfservice/flow/registration.(*ErrorHandler).WriteFlowError",
  "http_request": {
  },
  "level": "info",
  "msg": "Encountered self-service flow error.",
  "registration_flow": {
    "type": "browser"
  },
  "service_name": "Ory Kratos",
  "service_version": "v0.9.0-alpha.3",
  "time": "2022-04-04T11:11:58Z"
}

Relevant configuration

No response

Version

0.9.0-alpha.3

On which operating system are you observing this issue?

Linux

In which environment are you deploying?

Kubernetes

Additional Context

No response

aeneasr commented 2 years ago

Could you expand when the error happens exactly? Is it after the user re-submits the registration form with the missing info, or immediately when the user returns from GitHub? It’s also possible that the UI is sending incorrect data here

PetrSilar commented 2 years ago

The error appears immediately when the user returns from GitHub: Screenshot 2022-04-06 at 08-56-09 Create account Please see the second entry of the attached log (strategy_registration.go:233), there are identity_traits with fruits as an object already.

aeneasr commented 2 years ago

Thank you! Could you show the payload of the initial POST request which triggers the OAuth2 flow please? So the request rhat is happening when you click the „sign in with“ button. That would be very helpful.

Also, which UI are you using?

PetrSilar commented 2 years ago

Thank you! Could you show the payload of the initial POST request which triggers the OAuth2 flow please? So the request rhat is happening when you click the „sign in with“ button. That would be very helpful.

Of course. If you mean the request initiated from browser: POST http://my.local/identities/self-service/registration?flow=80e7d332-e3bf-4251-9255-573da41d3ac6 Payload: csrf_token=<some token>&provider=github&traits.email=&password=

Also, which UI are you using?

kratos-selfservice-ui-node

aeneasr commented 2 years ago

Thank you! It appears as if our type guessing gets it wrong then and instead of an array initializes it with an empty object. This will require a bit of investigation and we always appreciate contributions :)

PetrSilar commented 2 years ago

Thank you for analysis and confirmation! Unfortunately, Go is not language of mine.