Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User Schema #6433

Open
3 of 21 tasks
hifabienne opened this issue Aug 25, 2023 · 48 comments
Open
3 of 21 tasks

User Schema #6433

hifabienne opened this issue Aug 25, 2023 · 48 comments
Labels

Comments

@hifabienne
Copy link
Member

hifabienne commented Aug 25, 2023

As an administrator I want to be able to define how my users look like. Also I want to be able to define which fields can be edited by administrators and which by the user itself. I want to be able to define which authenticators can be used by which user.

Todos

Additional Information

The migration path looks as follow:

  • We will have a new V3 API/Service which includes the new user schemas, etc
  • The feature is disabled till we are finished
  • Old API can be used till a point where you migrate to new
  • Migration possibility to upgrade to V3
  • After upgrade old User APIs can't be used
  • You can not go back to old User Versions
@muhlemmer
Copy link
Contributor

If we use proto defined schema, we could build a validation engine like so:

message SetHumanProfile {
  string given_name = 1  [
    (validate.rules).string = {min_len: 1, max_len: 200},
    (google.api.field_behavior) = REQUIRED,
    (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
      min_length: 1;
      max_length: 200;
      example: "\"Minnie\"";
    }
  ];
  string family_name = 2  [
    (validate.rules).string = {min_len: 1, max_len: 200},
    (google.api.field_behavior) = REQUIRED,
    (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
      min_length: 1;
      max_length: 200;
      example: "\"Mouse\"";
    }
  ];
  optional string nick_name = 3 [
    (validate.rules).string = {max_len: 200},
    (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
      max_length: 200;
      example: "\"Mini\"";
    }
  ];
  optional string display_name = 4 [
    (validate.rules).string = {max_len: 200},
    (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
      max_length: 200;
      example: "\"Minnie Mouse\"";
    }
  ];
  optional string preferred_language = 5 [
    (validate.rules).string = {max_len: 10},
    (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
      max_length: 10;
      example: "\"en\"";
    }
  ];
  optional zitadel.user.v2alpha.Gender gender = 6 [
    (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
      example: "\"GENDER_FEMALE\"";
    }
  ];
}

enum ProfileRequiredID {
  ProfileRequiredFields_UNDEFINED = 0;
  ProfileRequiredFields_GIVEN_NAME = 1;
  ProfileRequiredFields_FAMILY_NAME = 2;
  ProfileRequiredFields_NICK_NAME = 3;
  ProfileRequiredFields_DISPLAY_NAME = 4;
  ProfileRequiredFields_PREFERRED_LANGUAGE = 5;
  ProfileRequiredFields_GENDER = 6;
}

message ProfileRequirements {
  message Field {
    ProfileRequiredID field_id = 1;
    optional int32 min_length = 2;
    optional string regex = 3;
  }
  repeated Field requirements = 1;
}


@hifabienne
Copy link
Member Author

@fforootd @adlerhurst We just collected all the questions that came to our mind about the user schema discussion. If we missed something or you have some ideas, please drop them here

@adlerhurst
Copy link
Member

Just some thoughts and ideas.

  • I would use a DDL many people are familiar with. My guess is that most people are familiar with json.
  • There are other projects which allow schema definitions, i would have a look at those.
  • I would prefer to use an existing schema definition language (e.g. json schema, xsd)

@hifabienne
Copy link
Member Author

hifabienne commented Aug 30, 2023

One more thought, should it be possible to define uniqueness of a field? (e.g Email)
Maybe also over multiple fields (e.g Username, or Username + Org ID) Either the user is unique globally or only in the organization

@adlerhurst
Copy link
Member

One more thought, should it be possible to define uniqueness of a field? (e.g Email)

I think this would be a great feature 👍

@fforootd
Copy link
Member

fforootd commented Aug 31, 2023

Just some thoughts and ideas.

  • I would use a DDL many people are familiar with. My guess is that most people are familiar with json.
  • There are other projects which allow schema definitions, i would have a look at those.
  • I would prefer to use an existing schema definition language (e.g. json schema, xsd)

I agree with this, we should choose something ubiquitous to people. Not sure though what fits into this description, I will try to do some research on this subject.

For reference I also asked this in discord

@pr0gr8mm3r
Copy link
Contributor

pr0gr8mm3r commented Sep 11, 2023

Just a thought on email and phone validation: I guess it'd be neat to treat them like any other field, with the addition of being marked as "verifiable". This validation logic (sending a code one way or another that the user then has to enter) could be extendable enough, so that you could implement use cases like:

  • verifying a physical address (via a code sent to you by mail. Can be automated)
  • validating a phone number that's on a specific messenger, like Whatsapp (phone number), matrix (matrix ID), Telegram (Username) by sending them a code on that platform through a bot/buisness account

This validation could be done through actions or the like and would also allow for bringing a custom SMS provider.
I'm not in need of any of those usecases, but just thought this should be considered when rethinking the user schema.

@hifabienne hifabienne mentioned this issue Sep 11, 2023
2 tasks
@adlerhurst
Copy link
Member

Another use case that came to my mind during having a look at zendesks api and i'm unsure if it's already covered:

My user should only be able to log in using an IDP.

During the registration the flow looks more or less straight forward to me. The users uses the IDP to register and the information is mapped to the user. But how would we handle if there are multiple IDP's and the user must use at least one of them?

It gets even more complex if I am an administrator and want to create the users up front because I'm not able to link the users to an IDP then. How would we handle that?

@thepaperpilot
Copy link
Contributor

I think using json schema would make a lot of sense. I think, considering how many people are looking to remove first/last name from users, it'd be prudent to get something we can use to make an MVP but designed so we can extend it later with the other desired features.

JSON schema already has support for defining validations on properties, including making a list of required fields and setting regex patterns the field values must match.

JSON schema allows properties to be defined as strings, numbers, objects, and arrays. I think we could feasibly "extend" JSON schema by defining any additional types we'll need, such as for auth methods. For example, password: { type: "password" } could add a password property, and auth: { type: "externalAuth" } could add an array of all their external accounts. Note you can then use JSON schema's "oneOf" property to say accounts need to have either a password or at least one external account.

I think we'd also want to "extend" JSON schema by allowing any property to define a "readableBy" and "writableBy", each of which takes an array of strings. Values could be "self", or "admin" to start, but I'd also appreciate the ability to say "role:" or "group:". For example, if I add writableBy: ["role:Moderator"] then any user with the "Moderator" role could write to that property.

The trickiest part of all this, imo, is having the default login flow respect the user schema, which I think should be included in some form in the MVP. By making sure there's at most 1 password field and at most 1 externalAuth field, I think the actual login process should be fairly unchanged - it just needs to look up where the fields are within the user object now. I'm not sure how best to handle the other fields, but my initial idea is to have a "ui" array at the top level, and it takes an array of objects, each of which represents a row in the login form. It will take a path to the property that field will be for (e.g. path: "email" or path: "person.age"), and can have other properties for field title, placeholder text, etc. I'm not sure how best to handle translations or rows with multiple fields. This part would be the best part to have a GUI editor for.

The other tricky part is migrations. I think for MVP we might be able to get away with only migrating password and externalAuth, which should be feasible if there's at most one of each of those properties in the schema. Any other new fields will just always start empty, and we'll have a big old warning before committing any schema changes that remove fields. For the other fields I think it should wait until we have a GUI editor, at which point we can ask for each field if it should copy the value from a field in the current schema (or even have a string template, but that might be overkill), although we'll also need some sort of conflict resolution for users who have previous fields that don't validate under the new schema. With this design in place, I don't think we'll need to version the user schemas since they'll always be at the latest.

Also, we would certainly need a default user schema, which I think could just reproduce the current behavior, thus allowing admins to update Zitadel without anything breaking. The migration should probably get explicit tests though (that is, migrating from the version before this change, to whatever version the test is being ran on).

Finally, for MVP I think this could all be done by asking the owner to paste in their schema code. It'll then be ran through a "meta-schema" that'll ensure everything is formatted correctly, and that there's always an email field and some form of auth, only 1 "password" field, only 1 "externalAuth" field, etc. Later on there can be a GUI for all this, but until then I think it can just be hidden away in settings behind a disclaimer not to mess with it unless you know what you're doing and really need to change it.

@thepaperpilot
Copy link
Contributor

thepaperpilot commented Sep 26, 2023

So here's an example of what the default user schema might look like. I also added a primaryKey: "email", which could in theory be a way to define unique users by ensuring that property (or array of properties) is exclusive. For MVP I think it should just be assumed email is the one and only exclusive property.

https://gist.github.com/thepaperpilot/04ab18242cc2c434788aa9b7b0825ba3

And for easier access, here's a list of all the things I think would still need to be resolved before this proposed schema design is ready:

  • Split up ui for different flows (e.g. registering via password, external account, or linking).
  • Translations for ui
  • Default values for optional fields, using a template (e.g. display name being $firstName $lastName). What if the property is a number and we want its default value to be the value of another property?

@adlerhurst
Copy link
Member

Hi @thepaperpilot
Thanks for your great feedback. ☺️ You triggered some thoughts which i have to figure out.
Maybe it makes sense to define a minimal set of fields on zitadel side e.g. unique identifier (what you used email for), password and identity provider. More or less all the fields which might change the behavior of logins. All the remaining data could be stored in a identity.payload field for example. I question myself if we need a schema for it. I will get back with more details as soon as i got my head around it

@thepaperpilot
Copy link
Contributor

Oh, that's a great idea. I was thinking admins might want to remove one of the password or externalAuth fields if they know they'll never use them, but that was adding a lot of quirkiness to the proposal and it's just as easy for the admin to not have any external IDPs or disable registration via password. Removing those fields from the admin configurable part of the schema simplifies a lot of things.

@adlerhurst
Copy link
Member

hi @thepaperpilot sorry i had no headspace left the last two week, i hope it gets better next week.

@Lan-Phan
Copy link

Some technical fields are needed (email, phone)

Is it possible to re-consider that email must be a mandatory field? A lot of online businesses now uses phone number only, and some web3 applications use self-sovereign identity instead (self-sovereign identity = user wallet's address)

I suggest:

  • we have flexible rule to define 1 arbitrary mandatory field (phone OR email OR self-sovereign identity)
  • verification action can be assigned to that mandatory field (email: send email to verify, phone: send sms|whats app ... message to verify, self-sovereign identity: request signature from user)

@adlerhurst
Copy link
Member

adlerhurst commented Oct 24, 2023

Instead of adding DDLs to ZITADEL I propose an approach which heavily relies on the use of actions and divides data required for authN and additional data.

I'm unsure if it is possible to solve all the problems of users using a DDL. My questions are:

  • Is there a possibility to concatenate fields like we currently do it with the display name?
  • What happens if the validation of fields has to be dynamic or even need additional data?
  • Do devs or admins of ZITADEL really want to store the schema definition inside ZITADEL?
  • Do people want to define their own user identifiers (aka login_names)
  • How would a DDL work with OIDC for example? how does ZITADEL find out how to fill which fields?
  • ...

Let's consider the following:

  • Actions on API requests are implemented.
  • Actions can be defined on instance level
  • Action templates are implemented
  • needed context information are provided to actions
  • admin has the possibility to test actions

Idea:

  • Information ZITADEL to authenticate are defined by ZITADEL. As these fields are private credentials apps should not have any interest in these data anyways
  • These information can include contact data phone and email of the user to make sure that all the authN factors work properly and to send email to the end-user.
  • Data not relevant for authN are stored in a field called "profile".
    • The "profile" type consists of 2 fields which are used to store information visible for different actors.
    • The fields are defined as a protobuf Any
    • Permissions are defined by the the sub-field of "profile"
    • ZITADEL is setup with a default "profiles" definition to fulfil standard use cases
    • The default definition includes several actions like:
      • Field validation on AddUser
      • Fill token information (e.g. set first name, ...)
      • ...
    • If users want to customize one of the "profiles" they can change the @type-subfield and define whichever fields they want in the "profile"
      • This disables default behaviors of ZITADEL as soon as the @type-subfield is changed. UI components of Console might not show data afterwards or an action must map the data to the default definition of ZITADEL when Console queries the user.
      • Examples of actions:
        • Set user identifiers on user manipulations
        • Add a suffix to each user identifier
        • Set contact data always to verified
        • Set suffix to user identifier during login
        • User is not allowed to change contact data
        • ...
  • The differentiation between human and machine isn't required anymore because authentication methods can be defined on the user itself.
  • Deletion of PII data is implemented by truncating all the fields besides the "user_id"-field

Definition

message User {
  // as is, snowflake id
  string user_id = 1;
  // Information for zitadel to contact the user, possibly related to  user_identifiers. 
  // and if defined to use in authenticators
  // Used for emails/sms sent to the user
  optional Contact contact = 2;
  // multi factors
  repeated Authenticator authenticators = 3;
  // Data not relevant for authN
  optional Profile profile = 4;
}

// stored information inside zitadel
message Authenticator {
  oneof authenticator {
    // password stored information inside zitadel
    Password password = 1;
    // passkeys from other devices and universal second factors (u2f)
    WebAuthNKey web_auth_n_key = 2;
    // one time password
    // SMS and email
    OneTimePassword otp = 3;
    // time based one time password
    // authenticator app
    TimeBasedOneTimePassword totp = 4;
    // keys currently used for machine users
    JWT_Profile_Key jwt_profile_key = 5;
    // like google login, sign in with apple, ...
    IdentityProvider identity_providers = 6;
    // if people forget their passwords
    string recovery_code = 7;
    // can be defined via API calls, there are no computed login_names anymore.
    // ZITADEL ensures that each login_name is unique on an instance.
    string login_name = 8;
  }
}

// DISCUSS: when should be defined which hashing algorithm and config must be used
message Password {
  string password = 1;
  Time change_date = 2;
}

message OneTimePassword {
  oneof provider {
    SMSOneTimePassword sms = 1;
    EmailOneTimePassword email = 2;
  }
}

message SMSOneTimePassword{
  oneof provider {
    // uses the phone defined in user.contact.phone
    bool use_contact = 1;
    // analog https://github.com/zitadel/zitadel/blob/ab79855cf059af55e27506b29dfec580751ee2aa/proto/zitadel/user/v2beta/phone.proto#L12
    // if this phone is used it's only used for otp 
    Phone phone = 2;
  }
}

message EmailOneTimePassword{
    oneof provider {
      // uses the email defined in user.contact.email
      bool use_contact = 1;
      // analog https://github.com/zitadel/zitadel/blob/ab79855cf059af55e27506b29dfec580751ee2aa/proto/zitadel/user/v2beta/email.proto#L12
      // if this email is used it's only used for otp 
      Email email = 2;
    }
}

// Information about totp not relevant for the discussion
message TimeBasedOneTimePassword {}

// Information about jwt profile key not relevant for the discussion
message JWT_Profile_Key {}

// Information about web authn key not relevant for the discussion
message WebAuthNKey {}

message Contact {
  // email is unchanged as it can be part of the login process, but it is optional now as it's not always required
  // It's used to contact the user, e.g. for init or password changed notification
  // analog https://github.com/zitadel/zitadel/blob/ab79855cf059af55e27506b29dfec580751ee2aa/proto/zitadel/user/v2beta/email.proto#L12
  optional Email email = 1;
  // phone is unchanged as it can be used as part of the login process, but it is optional now as it's not always required
  // It's used to contact the user, e.g. for init or password changed notification
  // analog https://github.com/zitadel/zitadel/blob/ab79855cf059af55e27506b29dfec580751ee2aa/proto/zitadel/user/v2beta/phone.proto#L12
  optional Phone phone = 2;
}

message Profile {
  // analog the type of protobuf any
  string type = 1;
  // Data the end user user is allowed to read and manipulate (e.g. first name, last name), it can be seen by org and instance admins
  // the user is not allowed to add/remove fields
  optional Any self_managed = 2;
  // Data can be read by the user, data can be manipulated and read by user manager and read by admins
  optional Any managed = 3;
  // Data can NOT be read by the user, data can be manipulated and read by user manager and read by admins
  optional Any internal = 4;
}
/*
examples of profiles:
{
  "type": "zitadel.com/api/user.human.v1",
  "selfManaged": {
    "@type": "zitadel.com/api/user.human.v1",
    "firstName": "adler"
    "lastName": "hurst"
  }
  "managed": {
    "@type": "org.com/api/employee.external.v1"
    "employeeId": "123"
  }
},
{
  "type": "zitadel.com/api/user.machine.v1",
  "selfManaged": {}
  "managed": {
    "@type": "zitadel.com/api/user.machine.v1"
    "name": "machine",
    "description": 
  }
}
*/

// Is a separate sub resource of users analog sessions. Long lived session in the name of a user.
message PersonalAccessToken {
  string id = 1;
  string user_id = 2;
}

Pro's

  • Fully customizable
  • Only one way to customize the behavior of ZITADEL
  • Definition of user identifiers are clear to admins
  • Contact data are not required
  • metadata of user are not used anymore

Con's

  • Setup of actions must be smooth
  • User must have an easy possibility to add custom actions

Edits

  • Added internal-field in Profile-message
  • Added DISCUSS-comments to bind_*-fields of UserIdentifier-message
  • Added recovery_code Authenticator option
  • Changed the definition of Contact-message, ZITADEL uses these information to contact the user, e.g. to send init or password has changed notifications
  • Definition of OneTimePassword-message got relevant for the discussion. Current proposal allows to add additional phones/email to the otp authenticator which are then not used to contact the user (despite of verification of the phone/email)
  • remove user identifier, only strings are allowed and are renamed to login_name
  • login_names are aligned to UserFactor of Sessions and are therefore no longer a separate field on the user, it is moved to Authenticator-message

@adlerhurst
Copy link
Member

@thepaperpilot @pr0gr8mm3r @Lan-Phan if you have some time would be cool to get your feedback or questions on my proposal above :)

@thepaperpilot
Copy link
Contributor

That idea looks good to me! It sounds easier to implement, solves the issues I left to be worked out, and would already have an interface in the console.

@yordis
Copy link
Contributor

yordis commented Oct 24, 2023

I would strongly recommend to add internal since it will be required at some point, it is a matter of time:

  • self managed: user can manage
  • managed: user can view
  • internal: user can not view

I wouldn't do anything related to validations or structure data, let application level code (actions included) to add such concern, Zitadel itself should treat the information as a bag of traits.
Evolving the schemas, breaking changes between them, among other concerns isn't worth it to push to Zitadel.

@yordis
Copy link
Contributor

yordis commented Oct 24, 2023

About UserIdentifiers, I would not allow to bind information to the Profile, far too often programmers want to reuse data at the cost of future management, and in IAM, at the cost of a simple mistake causing a security problem.

Identity is a extremely tricky situation, the anti-pattern I see is that Contact info is (maybe) required by the Authenticator such as "Password" where you may want a username, email, phone as "Identity".
Same with "Verified Emails" or "Verified X" most of the time those are required and used instead of profile (In this context) because they are part of authentication workflows, or recovery mechanism, but the bottom line is, they are managed traits for very specific reasons.

Make such fields part of the password authenticator or whoever wants that data to ensure "identity". And an "identity" for which workflow are we talking about?

Really tricky if you intertwine Contact information with Authentication information that "becomes" identity to for that reason:

  • John Doe Contact Phone: 555-555-5555
  • John Doe Contact Kiddo: 555-555-5555 🤷🏻

Totally valid! Contact Info != Identity


Just copy info at the app-level, and avoid intertwining such data

:2cents:

@adlerhurst
Copy link
Member

adlerhurst commented Oct 26, 2023

thanks for the feedbacks :) It looks like we are not too far off with this proposal

I think internal is definitely worth thinking about. I add it to the proposal.

the contact infos are used on different authenticators that's why i split it.

The use cases I thought of were the following (there are definitely more to consider):

  • init user by sending them an init code via mail or sms
  • password:
    • forgot password, send reset via mail
    • inform user about password change
  • otp require the information that they work

Because there are workflows which are not related to an authenticator like the init I think it would be misleading if we set them on the authenticators.

I agree shared contact information would break uniqueness. I add a comment to the bind's and will discuss it internally tomorrow and add an additional comment.

@muhlemmer
Copy link
Contributor

To overall idea looks great, I just have a single comment:

The fields are defined as a protobuf Any

My experience is that Any is quite hard to work with. Instead I would propose one of the following alternatives:

  1. If we want to define which fields / keys the profile will carry, use the Value well-known type for the values.
  2. If we want to go for free-from profile information, as in just a json blob, use the Struct well-known type.

Value uses a oneof for all possible types, which includes number, string, bool or even a Struct. A Struct is the equivalent of a JSON object and Un-marshals to a map[string]*Value. Both types have generated utility functions for back and front-end languages. We currently use Struct for webAuthN data in the API already.

@Lan-Phan
Copy link

Lan-Phan commented Oct 27, 2023

@adlerhurst , your idea looks great.

I have only 1 concern relating to the number of emails and phones which user have maybe more than 1, so that I suggest to change a little bit:

  • Contact have a list of phones and list of emails
  • For identifier, we can use index of email or phone as a value for bind_email and bind_phone

Changing 1 email / phone to a list of emails / phones will make Zitadel more complex, but I think it's worth for us to do that because normally 1 online user have multiple contacts for now.

@adlerhurst
Copy link
Member

adlerhurst commented Oct 27, 2023

@muhlemmer I prefer Any because then the clients have the possibility to check the type of the field. for example if you add a new version of the value the client is able to type switch, which is more difficult with other well known types. Or am I missing something? We should consider a new role which is allowed to change the type.

@Lan-Phan That sounds like a good idea. What would you expect to happen if ZITADEL sends a notification? should it be sent to all emails/phones or only to one? Im asking because if it should be sent only to one then ZITADEL must know the preferred one.
Additionally i gues your idea includes that the address/number is set in the authenticators as well then so that ZITADEL knows where to send the code?

@andar1an
Copy link

andar1an commented Dec 4, 2023

This is a long thread, and hard to follow. But I have 1 question that has arisen from the previous comment.

Is the UserID not currently a unique and crptographically secure randomly generated value?

It is my opinion that PII should never be used as an identifer as a user may change those things.

@adlerhurst
Copy link
Member

hi @stephenandary

Thanks for the feedback, I totally agree that it's hard to follow, that's why i tried another way for the actions discussion ;-)

I think there is a differentiation between the technical ID and "user facing" ID. The technical ID will never change and is generated by zitadel. The id @buehler is talking about is the identifier the user enters during login, currently called login name.

@andar1an
Copy link

andar1an commented Dec 4, 2023

hi @stephenandary

Thanks for the feedback, I totally agree that it's hard to follow, that's why i tried another way for the actions discussion ;-)

I think there is a differentiation between the technical ID and "user facing" ID. The technical ID will never change and is generated by zitadel. The id @buehler is talking about is the identifier the user enters during login, currently called login name.

Understood, thanks for the clarification. The actions conversation is also a little unwieldy lol. Maybe a future attempt at this could use a top level issue with nested sub-issue links.

@adlerhurst
Copy link
Member

Understood, thanks for the clarification. The actions conversation is also a little unwieldy lol. Maybe a future attempt at this could use a top level issue with nested sub-issue links.

Thanks for the input i keep it in mind for the next bigger discussion :)

@adlerhurst
Copy link
Member

adlerhurst commented Dec 22, 2023

User schema

Currently users are not customizable, if a field is not needed but required it must be filled. Additionally it's currently not possible to define which fields are allowed to be managed by a user and which by an admin.

Because of these limitations we propose the following changes to the user schema. And secondly we need to adjust the user to the session API to improve developer experience.

Requirements

Functional

  • the fields of the user must be customizable
  • the must be a differenciation between self management fields and fields not visible for the user
  • users must be able to sign in
  • ZITADEL must start without any further confiugration
  • ZITADEL must provide a default which fulfils first time interaction
  • Dynamic login names are no longer used
  • A user must contain multiple human readable identifiers which are unique inside an instance

Nonfunctional

  • p99 of validation is < 2ms

Use cases

  • create anonymous users
  • create users with defined fields
  • define self managed fields
  • define fields not visible to the user
  • I want to set a language on the user
  • I want to upload a profile picture
  • GDPR save delete

Proposal

The goal is to provide a minimal user object which is easily extendable.

User object

  • id: unchangeble, unique identifier of the system
  • schema:
    • type: schema type of the user (e.g. human/machine)
    • revision: read only, revision of the schema the user was stored the last time (e.g. 4)
  • authenticators: list of possibilites how a user can authenticate itself. there can be multiple authenticators of the same type
  • contact: email and phone of the user if provided
  • state: one of
    • Initial: User has not yet verified some code, TODO: (adlerhurst) maybe valid state for the first day?
    • Active: User is verified
    • Inactive: TODO: (adlerhurst) what's this state used for?
    • Locked: User got locked by an admin
    • Deleted: User got deleted
  • trait: custom fields of a user based on the type

Authenticators field

Everything that authenticates a user.

Authenticators can be manipulated by the users themselves.

  • username: human readable identifier of the user
  • password
  • webAuthNKey
  • one time password
    • email
    • phone
  • time based one time password
  • jwt profile key
  • identity provider
  • FUTURE: recovery code

Traits field

The type of the user must match an existing schema. The schema is used validate the provided fields and also allows validation based on JSON Schema Validation.

User methods

  • ListUsers: lists users
  • UserByID: shows details of a specific user
  • CreateUser: adds a new user
  • UpdateUser: changes an existing user. The type of a user cannot be changed
    • If the schema changed since the last time the user was stored the user will be validated by the latest schema. This might require additional changes
  • DeleteUser: Removes an exisiting user.
  • AddAuthenticator: Adds an authenticator
  • RemoveAuthenticator: Removes an existing atuehticator
  • LockUser: changes the state of the user to locked
  • UnlockUser: changes the state of the user to active after it was locked
  • DeactivateUser: changes the state of the user to inactive
  • ReactivateUser: changes the state of the user to active after it was locked

User schema

The user schema is based on JSON schema.

ZITADEL adds additional annotations to instances:

permission

Following permissions are allowed r (4), w (2), rw (6)

  • admin: defines the permissions of the admin TODO: (adlerhurst) admin might be missleading, generally there should be a permission which allows a role to administrate (read/write) the traits of a user.
  • user: defines the user of the user

oidcClaim

Allows to map claims if zitadel is the provider.

IDP specific configuration can be handeled on IDP configuration.

specifies which OIDC claims the field is mapped to.

The claims are validated during write of user schema to verify that types of standart claims can be casted correctly. If casting still fails the claim remains empty if types cannot be casted during token creation.

Non standard claims are allowed, type casting is not possible without actions.

To complement the token with more complex claims caluculation than set, actions are used.

It's possible to map all fields to the token, permissions are ignored. An action can be implemented to fulfill this use case.

The annotation is a simple string field, the value works analog to json path.

example:

"firstName": {
  "type": "string",
  "oidcClaim": "profile.first_name"
},

The above schema would create the following token:

{
  "sub": "...",
  "profile": {
    "first_name": "provided first name"
  },
  "iat": 1516239022
}

samlAttribute

Allows to map attributes if zitadel is the provider.

IDP specific configuration can be handeled on IDP configuration.

Specifies which SAML attributes the field is mapped to.

The attributes are validated during write of user schema to verify that types of standart attributes can be casted correctly. If casting still fails the attributes remains empty if types cannot be casted during token creation.

Non standard attributes are allowed, type casting is not possible without actions.

To complement the token with more complex attributes caluculation than set, actions are used.

It's possible to map all fields to the token, permissions are ignored. An action can be implemented to fulfill this use case.

The annotation describes a [SAML Attribute] as described in chapter 8 SAML Attribute Profiles of the SAML specification

The annotation is an object containing the following fields:

  • name: Object identifier
  • nameFormat: Describes the format of the attribute
  • friendlyName: Human readable name of the field

example:

"lastName": {
  "type": "string",
  "samlAttribute": {
    "name": "urn:oid:2.5.4.42",
    "nameFormat": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
    "friendlyName": "givenName"
  }
},

The above schema would create the following attribute:

<saml:Attribute 
  xmlns:x500="urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500"
  NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
  Name="urn:oid:2.5.4.42" FriendlyName="givenName">
</saml:Attribute>

additional formats

  • phone: possibility to add declare phone numbers
  • language: language tag analog to ietf bcp 47

fields of user schema

  • id: read only unique identifier
  • state: state of the schema
  • type: single word which describes the schema
  • revision: read only version of the schema, each update increases the revision
  • schema: describes the user
  • possibleAuthenticators: defines the possible types of authenticators. this allows creating different user types like human/machine without usage of actions to validate possible authenticators. Removal of an authenticator does not remove the authenticator on a user. For some specific fields there are additional configuration possibilities:
    • username:
      • isOrgSpecific: if set to true, login with this authenticator is only possible if org is defined
    • identity provider:
      • isOrgSpecific: if set to true, login with this authenticator is only possible if org is defined

User schemas can be managed on instance and org level. For the beginning we start on instance level and later add schemas to org level (seperate issue for org level).

In the future it will be possible to configure which schema is allowed on organizations. (add issue)

Every time a schema is updated the revision of the user schema is increased.

User schema methods

  • ListUserSchemas: lists latests states schemas
  • UserSchemaByID: shows details of a specific schema
  • CreateUserSchema: adds a new schema
  • UpdateUserSchema: changes an existing schema.
    • If an existing field gets removed users are no longer able to retrieve the field
    • If a type of a field get changed (e.g. from string to int): if mapping is allowed field is filled (e.g. int to string) if it's not possible to map the error is ignored and the field is returned empty
    • If a field gets redeclared (e.g. hodor string gets deleted, after two revisions hodor number is added) the
    • If a field is set to required the field is required after the update, previously safed users are not affected until they are updated
    • If an allowed authenticator is removed users which previously added the authenticator are still able to use it.
  • DeactivateUserSchema: moves the schema to a read only state. Users of this schema cannot be updated anymore. Users are still able to log in
  • ReactivateUserSchema: moves the schema state to active
  • DeleteUserSchema: Removes an exisiting schema. This operation is only allowed if there are no associated users
  • ValidateUser: Verifies if the user satisfies a schema

Additional information

Following use cases will work with action:

  • unique username per organisation:
    • Add an action on request to CreateUser, UpdateUser which adds a suffix to the username
    • Add an action on response ListUsers, GetUserByID which removes the suffix so that it is not visible to the user
  • allow schema usage only for specific users:
    • Add an action on request to CreateUser which checks if the user is allowed to create a user with the provided type.
  • don't allow user schema changes on org level
    • Add action on request to CreateUserSchema, UpdateUserSchema, DeleteUserSchema which always returns an error if the request is made for an org
  • send user added, password changed notification (multiple possibilities)
    • Add an action on event user.added which sends an email
    • Add an action on response CreateUser which sends an email
    • Don't use an action and send the mail from the client after successful response

Permissions vs. predefined fields

predefined fields

  • pro: permission is enforced by design
  • con: migration of permission breaks schema
  • con: same field can be set on each trait (e.g. public.firstname, private.firstname, …)
  • con: gathering of all fields of an object could require multiple traits
  • con: data model is based on permission

annotations

  • pro: no breaking change for permission change
  • pro: annotations feel natural in json schema as all properties contain annotations like “type”, validations, …
  • pro: analog unix file system permissions which is widely adopted and understood
  • con: inheritance might confuses people never worked with file systems

following behaviour is expected:

  • inheritance is allowed (analog unix file system)
  • allowed permissions: Read, Write
  • memberships (allow more in future):
    • user
    • admin

additionalProperties annotation

“additionalProperties” is set to false by default, if set to true all sent fields are stored, setting the field to true disables partial updates

API call order of execution

The schema is validated on each manipulation of the user object. Actions are not allowed to overrule schema validations, so the schema validation is executed after the action.

Token creation order of execution

The definition of claims/metadata mapping is executed before the action is executed.

Uploading avatars

It's possible to store byte arrays on a user, but it's not recommended to store picutes on the user object, only the URLs of assets should be stored on the user.

If a user wants to upload an avatar, the avatar fist has to be stored and afterwards the manipulation on the user with the updated URL.

Email/phone validation in schema

ZITADEL will not provide the logic to send out verification notifications at the beginning.

Additional changes

DefaultInstance

The user schemas will be added to the default instance configuration.

IDP

TODO: (adlerhurst) unsure about the outcome of the discussion, do we need an attribute/claim mapping on idp?

Allow custom attribute / claim mapping per idp.

Validation with current behaviour

TODO

Migration

TODO

Projection example

events

1 user.schema.added 1 {hodor: string}
2 user.added 1 {hodor: "asdf", schema_revision: 1}
3 user.schema.changed 2 {}
4 user.schema.changed 3 {hodor: number}
5 user.changed 2 {hodor: 123, schema_revision: 3}
6 user.schema.changed 4 {hodor: number, frodo: string}

user projection

1 build schema
2 fill fields hodor: asdf => {hodor: asdf}
3 remove field hodor => {}
4 change schema => {}
5 fill fields hodor: 123 => {hodor: 123}

Human schema example

{
    "$schema": "https://zitadel.com/user/human",
    "type": "object",
    "properties": {
        "profile": {
            "type": "object",
            "required": true,
            "properties": {
                "givenName": {
                    "type": "string",
                    "required": true,
                    "maxLength": 200
                },
                "familyName": {
                    "type": "string",
                    "required": true,
                    "maxLength": 200
                },
                "nickName": {
                    "type": "string",
                    "maxLength": 200
                },
                "displayName": {
                    "type": "string",
                    "maxLength": 200
                },
                "preferredLanguage": {
                    "type": "language"
                },
                "gender": {
                    "type": "string",
                    "enum": ["female", "male", "diverse"]
                },
                "avatarUrl": {
                    "type": "string",
                    "format": "uri"
                }
            }
        },
        "email": {
            "type": "object",
            "required": true,
            "properties": {
                "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 200,
                    "required": true
                },
                "isVerified": {
                    "type": "boolean"
                }
            }
        },
        "phone": {
            "type": "object",
            "required": true,
            "properties": {
                "phone": {
                    "type": "string",
                    "format": "phone",
                    "maxLength": 200,
                    "required": true
                },
                "isVerified": {
                    "type": "boolean"
                }
            }
        },
        "metadata": {
            "type": "array",
            "items": {
                "key": {
                    "type": "string",
                    "maxLength": 200
                },
                "value": {
                    "type": "string",
                    "maxLength": 500000
                }
            },
            "required": [
                "key",
                "value"
            ]
        }
    }
}

Machine schema example

{
    "$schema": "https://zitadel.com/user/machine",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "required": true
        },
        "description": {
            "type": "string"
        }
    }
}

Edits

  • Change authenticators to allow org specific identifiers
  • Users are able to manipulate their identifiers
  • Replace userVisibility by permission annotation
  • Add oidcClaim annotation
  • Add samlAttribute annotation
  • Add additional info of order of execution
  • define path on how to upload avatars
  • describe user states
  • moved isOrgSpecific-configuration of username and idp to possibleAuthenticators-field of user schema
  • rephrase additions of user schema fields
  • rename samlMapping to samlAttribute
  • further describe oidcClaim and samlAttribute annotations
  • add additional changes for idp for claim/attribute mapping
  • extended examples

@yordis
Copy link
Contributor

yordis commented Dec 22, 2023

I strongly recommend reconsidering the decision to emulate Okta's approach.

From an IT perspective, this strategy introduces significant complexities and potential risks. The overlap of developer and IT responsibilities in managing user data, coupled with the necessity to meticulously prefix fields, elevates the risk of inadvertently exposing sensitive information.

That approach also imposes the responsibility of a Schema Registry onto your team. Ideally, application developers should manage this complexity with dedicated Schema Registry tools. Additionally, emulating Okta might necessitate the development of Data Migration Tools, adding to the burden. Overall, this path seems to involve considerable challenges and potential pitfalls that could be avoided with a different approach.

Ory did an amazing job by moving into traits and metadata as an improvement to Okta. Clerk recently added Metadata as well that would be closer to what I would suggest to do:

type Traits map[string]interface{}

type User struct {
  Type          string
  PublicTraits  Traits // or Traits
  PrivateTraits Traits // or ManagedTraits
  UnsafeTraits  Traits // or InternalTraits
}

Having a type field or a similar mechanism can greatly enhance the overall identity management process. It enables efficient handling of various user types, and simple enough as a type field 👍🏻

@adlerhurst
Copy link
Member

Hi @yordis thanks for your feedback :) i have some additional questions.

From an IT perspective, this strategy introduces significant complexities and potential risks. The overlap of developer and IT responsibilities in managing user data, coupled with the necessity to meticulously prefix fields, elevates the risk of inadvertently exposing sensitive information.

I see your concerns. The idea with the visibility field was that the schema is self describing. If we predefine fields you need to know about the behavior of the fields.

That approach also imposes the responsibility of a Schema Registry onto your team. Ideally, application developers should manage this complexity with dedicated Schema Registry tools.

So you would not provide a schema at all?
If zitadel doesn't know the schema should it not validate input?
And showing data gets quite complex without the knowledge of the schema in console for example

Additionally, emulating Okta might necessitate the development of Data Migration Tools, adding to the burden. Overall, this path seems to involve considerable challenges and potential pitfalls that could be avoided with a different approach.

If organizations migrate from okta to zitadel we need to provide a migration path anyways do i understand something wrong?

You basically describe two concerns the complexity of the product by adding schemas to it and user errors based on the visibility field did i miss something?

Ory did an amazing job by moving into traits and metadata as an improvement to Okta. Clerk recently added Metadata as well that would be closer to what I would suggest to do:

type Traits map[string]interface{}



type User struct {

  Type          string

  PublicTraits  Traits // or Traits

  PrivateTraits Traits // or ManagedTraits

  UnsafeTraits  Traits // or InternalTraits

}

Having a type field or a similar mechanism can greatly enhance the overall identity management process. It enables efficient handling of various user types, and simple enough as a type field 👍🏻

Thanks for the example. You would also allow different user types but predefine the fields for accessibility. We will consider this approach.
I try to reconsider the schemas even if i still think that there huge benefits for the users.

Zitadel can use schemas for its default models, unified validation of input and zitadel has the possibility to render user fields dynamically

@yordis
Copy link
Contributor

yordis commented Dec 23, 2023

I see your concerns. The idea with the visibility field was that the schema is self-describing. If we predefine fields, you need to know about the behavior of the fields.

Not so simple, "self-describing" is extremely difficult. Linguistics requires context to make sense of data:

  • "That is the doctor's name", sitting at home with a dog, well, is it the veterinarian or the human doctor?

You could even argue that some cultures would never call Veterinarian doctors 🤷🏻

  • "This is the doctor's name", at a veterinary

It's probably extremely clear, as long we weren't talking about my health while we were at the vet either...

Tricky,

IAM systems like Zitadel will sit at such a high and out-of-context scope that such simple decision-making becomes difficult. Combine it with high risk, and you can comprehend why I am pushing you back.

So you would not provide a schema at all?
If Zitadel doesn't know the schema should it not validate input?
Showing data gets quite complex without the knowledge of the schema in the console for example.

Precisely,

  1. You already provide the Action; if people want to leverage JSON Schema, so be it, or if you wish to use Confluent Schema Registry, no problem!
  2. The type dimension is the one that matters the most. The schema and the type are strongly coupled to each other. In programming languages without types, we only care about the "Data," not the Types/Schemas. We look at the type and make assumptions about what was true at X point in time and hopefully that is true, which leads to:
  3. On the Command side, if you are most likely concerned with the data invariants, I can see the usefulness of it, but again, you have Actions for invariants, including structural invariants. That is also assuming I want to do that at the Zitadel level instead of at some other proxy/app level that communicates with Zitadel to Register the Users.
  4. On the Query side, if something gets into your system, you must handle it (garbage in, garbage out).

And showing data gets quite complex without the knowledge of the schema in the console for example

That is also why it is crucial to pick the battles carefully.

I am not saying the Okta route will not work, but why do you need fancy schema GUI components when a simple JSON blob shown to the user in a text field will do it just fine? Look at Auth0 and user_metadata or app_metadata, same concept.

The Console is extremely valuable, but we barely live in it. We build IT automation tools around it (use Rippling, for example) or Products that have more control over the UX (accounts.google.com), and so on ... So, what do you want to get out of the Console precisely?

If organizations migrate from Okta to Zitadel, we need to provide a migration path anyways. Do I understand something wrong?

I was talking about the fact that Okta (and Ory to some extent) have to build migration tools inside them to deal with breaking changes between schemas, among many unknown unknowns.

You basically describe two concerns the complexity of the product by adding schemas to it, and user errors based on the visibility field. Did I miss something?

Two major concerns,

  1. Visibility Field vs. Specialized Trait Fields
  2. Zitadel Schemas vs. Action/App-Level/"Unstructured" Data

@yordis
Copy link
Contributor

yordis commented Dec 23, 2023

Side note, Actions could map the traits to OpenID Connect Standard Claims; you do not need to create specialized schemas such as Contact/Profile.

This is where I still do not know how I feel about it, my gut feeling says "do not do anything special", my product gut feeling says "add a profile field that is OpenID Connect Claims specific"

I can not make up my mind about it. Actions should be able to map these claims regardless, so the tricky bit is simplify the 80/20 rule.

@adlerhurst
Copy link
Member

hi @yordis

sorry for the late reply and the patience with me 😄

I will give feedback and update the concept on friday :)

@adlerhurst
Copy link
Member

adlerhurst commented Jan 26, 2024

hi @yordis

sorry for the late reply, we had a lot of internal discussions about the permission model and i think i changed my opinion more often than there are days in between my last answer and now 😅

I want to address your main concerns:

Visibility Field vs. Specialized Trait Fields

The biggest pro we can see on specialized trait fields is permission enforcement by design. And our main concerns are the following:

  • migration of visibility breaks schema
  • same field can be set on each trait (e.g. public.firstname, private.firstname, …)
  • gathering of all fields of an object could require multiple traits
  • data model is based on permission

The current concept describes visibility fields which are not logical thats why we made a step back and thought about how this is solved in IT in general and we came up with UNIX file system permissions. It uses inheritance, does not impact folder structures and still allows complex permission models and it's widely understood in IT.

These are the main pro's in it from our perspective:

  • no breaking change for visibility change
  • annotations feel natural in json schema as all properties contain annotations like “type”, validations, …
  • analog unix file system permissions which is widely adopted and understood

As we don't use executable (1) and don't have group access at the moment we propose the following:

permission annotation which consists of two fields at the beginning (admin, user) and the following values can be set on these fields: write, read, readwrite.

This annotation is easy to validate that each field has a defined permission either from the field spec or inheritance from the parent fields.

a schema could look the following:

{
    "$schema": "https://zitadel.com/user/permission-example",
    "type": "object",
     // allows the admin to read and write on each field by default
     // user cannot see any fields by default
     "permission": {
         "admin": "rw"
     },
    "properties": {
        "profile": {
            "type": "object",
            // admin is only allowd to read the field but not update
            // user is allowed to manipulate and read the fields
            "permission": {
                "admin": "r",
                "user": "rw"
            },
            "properties": {
                "firstName": {
                    "type": "string"
                },
                "lastName": {
                    "type": "string"
                },
                "nickName": {
                    "type": "string"
                },
                "DisplayName": {
                    "type": "string"
                },
                "preferredLanguage": {
                    "type": "language"
                },
                "gender": {
                    "type": "string"
                },
                "avatarUrl": {
                    "type": "string"
                },
            }
        },
        "password": {
            // admin is only allowed to set the password
            // user is allowed to read and write the password
            "permission": {
                "admin": "w",
                "user": "rw"
            },
            "type": "password"
        }
    }
}

Zitadel Schemas vs. Action/App-Level/"Unstructured" Data

We will allow the "additionalProperties"-annotation which allows you to define a schema where only defined fields are validated.

The following example allows as many fields as you want and verifies that the username field is set

{
    "$schema": "https://zitadel.com/user/additional-props-example",
    "type": "object",
    "additionalProperties": true,
    "properties": {
        "username": {
            "type": "string",
            "required": true
        }
    }
}

We are happy to discuss if you have any concerns :)

@yordis
Copy link
Contributor

yordis commented Jan 26, 2024

Taking Unix as a reference is not good, and it is easy to misguide yourself.

I said that because the access pattern in a file system (API) is drastically different from Zitadel or IAM Systems.
For example, the file system is about the resource, not attributes, based on the payload's content. The payload is controlled and belongs to external systems, which the only way you can control is by self-imposing schema registries upon yourself.

Think about the Roles of your system, their access patterns, and how the permissions are supposed to look around the use cases for the given Roles. That is critical!

Are the permission's roles static or dynamic?

How will the whole management of the roles and the schema work if they are dynamic? Is your target audience enterprise customers? If so, could you tell me the precise use cases you see from them?

If they are static, well:

The whole permission situation is required (making assumptions here) because you want an untrusted role (end-user) to read and write its data, and you want APIs that avoid making a mistake around such data, where you do not accidentally expose internal data or let the user update managed data.

For example, https://clerk.com/docs/users/metadata#user-metadata (my apologies that I keep mentioning your competitors; I am honestly trying to help here and make sure you succeed)

Notice how they phrase things in the docs; I like to believe it is not accidental:

Metadata | Frontend API | Backend API
-- | -- | --
Private | No read or write access | Read & write access
Public | Read access | Read & write access
Unsafe | Read & write access | Read & write access

The keywords API and Frontend vs. Backend**, as I mentioned before, focus on the use cases and the API for the use cases.

Technically, this situation wouldn't be if and only if; we always put proxies before the IAM system and bring that complexity to our app layer, so they are just convenience capabilities. I am not arguing against it, but what drives the need for the API is critical here!

There is no right or wrong here, just trade-offs and complexity. As with anything in software, It Depends ™️, what are roles, and use cases per role.

I would finally say that Enterprise will always complicate things, which is fine; give them the ability to do so, but isolate the problems to them at the very least. Please keep it simple for the rest of us!

@adlerhurst
Copy link
Member

First of all, providing examples is nothing you have to apologize for, we are thankful for that, it helps through the design process to analyze existing solutions ;-)

Taking Unix as a reference is not good, and it is easy to misguide yourself.

I said that because the access pattern in a file system (API) is drastically different from Zitadel or IAM Systems. For example, the file system is about the resource, not attributes, based on the payload's content. The payload is controlled and belongs to external systems, which the only way you can control is by self-imposing schema registries upon yourself.

Maybe it makes sense to describe my mental model first: folder == object, file == field

For me it's not that different. Both are a rooted trees, don't care about the payload of the file/field. And the schema is enforced on the file system by the folder structure.

Think about the Roles of your system, their access patterns, and how the permissions are supposed to look around the use cases for the given Roles. That is critical!

Are the permission's roles static or dynamic?

For me it would make sense to simplify the permission model to a single user permission which can be assigned to multiple roles.

There are still use cases like manipulating authenticators which still requires a permission model for zitadel.

How will the whole management of the roles and the schema work if they are dynamic? Is your target audience enterprise customers? If so, could you tell me the precise use cases you see from them?

How ZITADEL will handle permissions is currently discussed here.

There are multiple target audiences. You can find use cases below.

If they are static, well:

The whole permission situation is required (making assumptions here) because you want an untrusted role (end-user) to read and write its data, and you want APIs that avoid making a mistake around such data, where you do not accidentally expose internal data or let the user update managed data.

I totally agree on that and I also agree that you can enforce that by predefined fields, but from a customers perspective I wouldn't understand why I have to break my user model into predefined fields which break my model.

For example, https://clerk.com/docs/users/metadata#user-metadata (my apologies that I keep mentioning your competitors; I am honestly trying to help here and make sure you succeed)

Notice how they phrase things in the docs; I like to believe it is not accidental:

yes me too.

Metadata | Frontend API | Backend API
-- | -- | --
Private | No read or write access | Read & write access
Public | Read access | Read & write access
Unsafe | Read & write access | Read & write access

The keywords API and Frontend vs. Backend**, as I mentioned before, focus on the use cases and the API for the use cases.

The use case is what you (your app) define, and your use case should not be influenced by the IAM. At least the model of your use case is hardly influenced by the IAM if you take the example above.

Technically, this situation wouldn't be if and only if; we always put proxies before the IAM system and bring that complexity to our app layer, so they are just convenience capabilities. I am not arguing against it, but what drives the need for the API is critical here!

I agree to put a proxy before the IAM. If I would implement a proxy this would have to manually map the data to the specific fields, but bugs in the proxy do not prevent access to sensitive data.

There is no right or wrong here, just trade-offs and complexity. As with anything in software, It Depends ™️, what are roles, and use cases per role.

I would finally say that Enterprise will always complicate things, which is fine; give them the ability to do so, but isolate the problems to them at the very least. Please keep it simple for the rest of us!

For me the enterprise use case is more enforced by the predefined field approach. If you define a schema like following ZITADEL won't enforce any rules and you can use actions or your proxy to load the schema of your joyce to enforce your model:

{
    "$schema": "https://zitadel.com/user/additional-props-example",
    "type": "object",
    "additionalProperties": true,
    "permissions": {
        "admin": "rw",
        "user": "rw"
    }
}

Use cases

First time user who wants to get an overview

There is no coding involved.

I want to know which fields a user does consist of?

My users must provide an address during registration.

What happens when I create a user?

Changing the behavior of the default register form

There is no coding involved.

First name is an optional field and I don't want to show it in the registration form.

Changing the behavior of the login

There is no coding involved.

I want to enforce login by an identity provider.

Use the API

This involves docs and user schema.

Which fields do I need to provide?

How do I define a custom user schema? What possibility do I have.

How can I map my user model to ZITADEL?

How can I map my permission model to ZITADEL?

ZITADEL admin (operations) provides service to development

Clarify borders what's provided.

Define the schema both parties can work with.

@adlerhurst
Copy link
Member

adlerhurst commented Jan 31, 2024

following changes were applied to the concept

  • Change authenticators to allow org specific identifiers
  • Users are able to manipulate their identifiers
  • Replace userVisibility by permission annotation
  • Add oidcClaim annotation
  • Add samlAttribute annotation
  • Add additional info of order of execution
  • define path on how to upload avatars
  • describe user states
  • moved isOrgSpecific-configuration of username and idp to possibleAuthenticators-field of user schema
  • rephrase additions of user schema fields
  • rename samlMapping to samlAttribute
  • further describe oidcClaim and samlAttribute annotations
  • add additional changes for idp for claim/attribute mapping
  • extended examples

@livio-a
Copy link
Member

livio-a commented Feb 12, 2024

There's an additional input for the OIDC claim mapping. Since it will be possible to store the user's address, we need to also be able to add annotations for the address claims.

see https://discord.com/channels/927474939156643850/1205475933847429140

@Lan-Phan
Copy link

Lan-Phan commented Mar 7, 2024

Hello @adlerhurst ,

What do you think to allow an array of profiles in User, so that User can store as many profiles as they want?
I think Profile is somehow similar with "Who I am" in the real world or a vocative of people, such as "Dad in home, engineer level 1 in company A, founder in company B, Master in school ...". In case of an array of profiles, default (what will be displayed to user) will be the first one (index 0).

Your schema:

message User {
  // as is, snowflake id
  string user_id = 1;
  // Information for zitadel to contact the user, possibly related to  user_identifiers. 
  // and if defined to use in authenticators
  // Used for emails/sms sent to the user
  optional Contact contact = 2;
  // multi factors
  repeated Authenticator authenticators = 3;
  // Data not relevant for authN
  optional Profile profile = 4;
}

My suggestion:

message User {
  // as is, snowflake id
  string user_id = 1;
  // Information for zitadel to contact the user, possibly related to  user_identifiers. 
  // and if defined to use in authenticators
  // Used for emails/sms sent to the user
  optional Contact contact = 2;
  // multi factors
  repeated Authenticator authenticators = 3;
  // Data not relevant for authN
  optional repeated Profile profiles = 4;
}

@Yberion
Copy link

Yberion commented Mar 21, 2024

One more thought, should it be possible to define uniqueness of a field? (e.g Email) Maybe also over multiple fields (e.g Username, or Username + Org ID) Either the user is unique globally or only in the organization

Hello @hifabienne, sorry if this is not the best place to ask for news about this ^^'

Is there any news regarding the uniqueness of the email field ?

This would be really much appreciated as even if we prevent the Console management access (https://zitadel.com/docs/guides/solution-scenarios/restrict-console), they could use their token to use the API directly with the proper scope.

For instance, Keycloak allow for email uniqueness and I know that on my last company this was a mandatory requirement that Zitadel does not fulfill.

We currently don't have a back-end for user management yet, so we currently use the urn:zitadel:iam:org:project:id:XXXXX:aud scope so we can call API with the token.

@muhlemmer
Copy link
Contributor

@Yberion All that needs to be done including progress is in the original post above. Once User schema is fully implemented, you can define you own uniqueness.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: 📝 Prioritized Product Backlog
Status: No status
Development

No branches or pull requests