feat(api): add user creation to user service (#5745)

* chore(proto): update versions

* change protoc plugin

* some cleanups

* define api for setting emails in new api

* implement user.SetEmail

* move SetEmail buisiness logic into command

* resuse newCryptoCode

* command: add ChangeEmail unit tests

Not complete, was not able to mock the generator.

* Revert "resuse newCryptoCode"

This reverts commit c89e90ae35.

* undo change to crypto code generators

* command: use a generator so we can test properly

* command: reorganise ChangeEmail

improve test coverage

* implement VerifyEmail

including unit tests

* add URL template tests

* begin user creation

* change protos

* implement metadata and move context

* merge commands

* proto: change context to object

* remove old auth option

* remove old auth option

* fix linting errors

run gci on modified files

* add permission checks and fix some errors

* comments

* comments

* update email requests

* rename proto requests

* cleanup and docs

* simplify

* simplify

* fix setup

* remove unused proto messages / fields

---------

Co-authored-by: adlerhurst <silvan.reusser@gmail.com>
Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>
This commit is contained in:
Livio Spring
2023-04-26 07:47:57 +02:00
committed by GitHub
parent 19f2f83b61
commit e4a4b7cfbe
24 changed files with 1175 additions and 226 deletions

View File

@@ -6,11 +6,11 @@ option go_package = "github.com/zitadel/zitadel/pkg/grpc/object/v2alpha;object";
import "google/protobuf/timestamp.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
message OrgContext {
oneof ctx {
message Organisation {
oneof org {
string org_id = 1;
string org_domain = 2;
}
}

View File

@@ -9,6 +9,23 @@ import "google/api/field_behavior.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
message SetHumanEmail {
string email = 1 [
(validate.rules).string = {min_len: 1, max_len: 200, email: true},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "\"mini@mouse.com\"";
}
];
// if no verification is specified, an email is sent with the default url
oneof verification {
SendEmailVerificationCode send_code = 2;
ReturnEmailVerificationCode return_code = 3;
bool is_verified = 4 [(validate.rules).bool.const = true];
}
}
message SendEmailVerificationCode {
optional string url_template = 1 [

View File

@@ -0,0 +1,53 @@
syntax = "proto3";
package zitadel.user.v2alpha;
option go_package = "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha;user";
import "google/api/field_behavior.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
message SetUserPassword {
oneof type {
Password password = 1;
HashedPassword hashed_password = 2;
}
}
message Password {
string password = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"Secr3tP4ssw0rd!\"";
min_length: 1,
max_length: 200;
}
];
bool change_required = 2;
}
message HashedPassword {
string hash = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"$2a$12$lJ08fqVr8bFJilRVnDT9QeULI7YW.nT3iwUv6dyg0aCrfm3UY8XR2\"";
description: "\"hashed password\"";
min_length: 1,
max_length: 200;
}
];
string algorithm = 2 [
(validate.rules).string = {min_len: 1, max_len: 200, const: "bcrypt"},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"bcrypt\"";
description: "\"algorithm used for the hash. currently only bcrypt is supported\"";
min_length: 1,
max_length: 200;
}
];
bool change_required = 3;
}

View File

@@ -4,6 +4,87 @@ package zitadel.user.v2alpha;
option go_package = "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha;user";
import "google/api/field_behavior.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
message User {
string id = 1;
}
enum Gender {
GENDER_UNSPECIFIED = 0;
GENDER_FEMALE = 1;
GENDER_MALE = 2;
GENDER_DIVERSE = 3;
}
message SetHumanProfile {
string first_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 last_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\"";
}
];
}
message SetMetadataEntry {
string key = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"my-key\"";
min_length: 1,
max_length: 200;
}
];
bytes value = 2 [
(validate.rules).bytes = {min_len: 1, max_len: 500000},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "The value has to be base64 encoded.";
example: "\"VGhpcyBpcyBteSB0ZXN0IHZhbHVl\"";
min_length: 1,
max_length: 500000;
}
];
}

View File

@@ -5,6 +5,8 @@ package zitadel.user.v2alpha;
import "zitadel/options.proto";
import "zitadel/object/v2alpha/object.proto";
import "zitadel/user/v2alpha/email.proto";
import "zitadel/user/v2alpha/password.proto";
import "zitadel/user/v2alpha/user.proto";
import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
@@ -12,8 +14,91 @@ import "validate/validate.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha;user";
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
info: {
title: "User Service";
version: "2.0-alpha";
description: "This API is intended to manage users in a ZITADEL instance. This project is in alpha state. It can AND will continue breaking until the services provide the same functionality as the current login.";
contact:{
name: "ZITADEL"
url: "https://zitadel.com"
email: "hi@zitadel.com"
}
license: {
name: "Apache 2.0",
url: "https://github.com/zitadel/zitadel/blob/main/LICENSE";
};
};
schemes: HTTPS;
schemes: HTTP;
consumes: "application/json";
consumes: "application/grpc";
produces: "application/json";
produces: "application/grpc";
consumes: "application/grpc-web+proto";
produces: "application/grpc-web+proto";
host: "$ZITADEL_DOMAIN";
base_path: "/";
external_docs: {
description: "Detailed information about ZITADEL",
url: "https://zitadel.com/docs"
}
responses: {
key: "403";
value: {
description: "Returned when the user does not have permission to access the resource.";
schema: {
json_schema: {
ref: "#/definitions/rpcStatus";
}
}
}
}
responses: {
key: "404";
value: {
description: "Returned when the resource does not exist.";
schema: {
json_schema: {
ref: "#/definitions/rpcStatus";
}
}
}
}
};
service UserService {
// Create a new human user
rpc AddHumanUser (AddHumanUserRequest) returns (AddHumanUserResponse) {
option (google.api.http) = {
post: "/v2alpha/users/human"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "user.write"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Create a user (Human)";
description: "Create/import a new user with the type human. The newly created user will get a verification email if either the email address is not marked as verified and you did not request the verification to be returned."
responses: {
key: "200"
value: {
description: "OK";
}
};
};
}
// Change the email of a user
rpc SetEmail (SetEmailRequest) returns (SetEmailResponse) {
option (google.api.http) = {
post: "/v2alpha/users/{user_id}/email"
@@ -23,8 +108,20 @@ service UserService {
option (zitadel.v1.auth_option) = {
permission: "authenticated"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Change the user email";
description: "Change the email address of a user. If the state is set to not verified, a verification code will be generated, which can be either returned or sent to the user by email."
responses: {
key: "200"
value: {
description: "OK";
}
};
};
}
// Verify the email with the provided code
rpc VerifyEmail (VerifyEmailRequest) returns (VerifyEmailResponse) {
option (google.api.http) = {
post: "/v2alpha/users/{user_id}/email/_verify"
@@ -34,9 +131,61 @@ service UserService {
option (zitadel.v1.auth_option) = {
permission: "authenticated"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Verify the email";
description: "Verify the email with the generated code."
responses: {
key: "200"
value: {
description: "OK";
}
};
};
}
}
message AddHumanUserRequest{
// optionally set your own id unique for the user
optional string user_id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "\"d654e6ba-70a3-48ef-a95d-37c8d8a7901a\"";
}
];
// optionally set a unique username, if none is provided the email will be used
optional string username = 2 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "\"minnie-mouse\"";
}
];
zitadel.object.v2alpha.Organisation organisation = 3;
SetHumanProfile profile = 4 [
(validate.rules).message.required = true,
(google.api.field_behavior) = REQUIRED
];
SetHumanEmail email = 5 [
(validate.rules).message.required = true,
(google.api.field_behavior) = REQUIRED
];
repeated SetMetadataEntry metadata = 6;
oneof password_type {
Password password = 7;
HashedPassword hashed_password = 8;
}
}
message AddHumanUserResponse {
string user_id = 1;
zitadel.object.v2alpha.Details details = 2;
optional string email_code = 3;
}
message SetEmailRequest{
string user_id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},