feat: passwordless registration (#2103)

* begin pw less registration

* create pwless one time codes

* send pwless link

* separate send and add passwordless link

* separate send and add passwordless link events

* custom message text for passwordless registration

* begin custom login texts for passwordless

* i18n

* i18n message

* i18n message

* custom message text

* custom login text

* org design and texts

* create link in human import process

* fix import human tests

* begin passwordless init required step

* passwordless init

* passwordless init

* do not return link in mgmt api

* prompt

* passwordless init only (no additional prompt)

* cleanup

* cleanup

* add passwordless prompt to custom login text

* increase init code complexity

* fix grpc

* cleanup

* fix and add some cases for nextStep tests

* fix tests

* Update internal/notification/static/i18n/en.yaml

* Update internal/notification/static/i18n/de.yaml

* Update proto/zitadel/management.proto

* Update internal/ui/login/static/i18n/de.yaml

* Update internal/ui/login/static/i18n/de.yaml

* Update internal/ui/login/static/i18n/de.yaml

Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
Livio Amstutz
2021-08-02 15:24:58 +02:00
committed by GitHub
parent 9b5cb38d62
commit 00220e9532
60 changed files with 2916 additions and 350 deletions

View File

@@ -1692,12 +1692,49 @@ service AdminService {
}
//Sets the default custom text for domain claimed phone message
// it impacts all organisations without customized verify phone message text
// it impacts all organisations without customized domain claimed message text
// The Following Variables can be used:
// {{.Domain}} {{.TempUsername}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
rpc SetDefaultDomainClaimedMessageText(SetDefaultDomainClaimedMessageTextRequest) returns (SetDefaultDomainClaimedMessageTextResponse) {
option (google.api.http) = {
put: "/text/message/verifyphone/{language}";
put: "/text/message/domainclaimed/{language}";
body: "*";
};
option (zitadel.v1.auth_option) = {
permission: "iam.policy.write";
};
}
//Returns the default text for passwordless registration message (translation file)
rpc GetDefaultPasswordlessRegistrationMessageText(GetDefaultPasswordlessRegistrationMessageTextRequest) returns (GetDefaultPasswordlessRegistrationMessageTextResponse) {
option (google.api.http) = {
get: "/text/default/message/passwordless_registration/{language}";
};
option (zitadel.v1.auth_option) = {
permission: "iam.policy.read";
};
}
//Returns the custom text for passwordless registration message (overwritten in eventstore)
rpc GetCustomPasswordlessRegistrationMessageText(GetCustomPasswordlessRegistrationMessageTextRequest) returns (GetCustomPasswordlessRegistrationMessageTextResponse) {
option (google.api.http) = {
get: "/text/message/passwordless_registration/{language}";
};
option (zitadel.v1.auth_option) = {
permission: "iam.policy.read";
};
}
//Sets the default custom text for passwordless registration message
// it impacts all organisations without customized passwordless registration message text
// The Following Variables can be used:
// {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
rpc SetDefaultPasswordlessRegistrationMessageText(SetDefaultPasswordlessRegistrationMessageTextRequest) returns (SetDefaultPasswordlessRegistrationMessageTextResponse) {
option (google.api.http) = {
put: "/text/message/passwordless_registration/{language}";
body: "*";
};
@@ -3267,6 +3304,42 @@ message SetDefaultDomainClaimedMessageTextResponse {
zitadel.v1.ObjectDetails details = 1;
}
message GetDefaultPasswordlessRegistrationMessageTextRequest {
string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message GetDefaultPasswordlessRegistrationMessageTextResponse {
zitadel.text.v1.MessageCustomText custom_text = 1;
}
message GetCustomPasswordlessRegistrationMessageTextRequest {
string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message GetCustomPasswordlessRegistrationMessageTextResponse {
zitadel.text.v1.MessageCustomText custom_text = 1;
}
message SetDefaultPasswordlessRegistrationMessageTextRequest {
string language = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"de\""
}
];
string title = 2 [(validate.rules).string = {max_len: 200}];
string pre_header = 3 [(validate.rules).string = {max_len: 200}];
string subject = 4 [(validate.rules).string = {max_len: 200}];
string greeting = 5 [(validate.rules).string = {max_len: 200}];
string text = 6 [(validate.rules).string = {max_len: 800}];
string button_text = 7 [(validate.rules).string = {max_len: 200}];
string footer_text = 8 [(validate.rules).string = {max_len: 200}];
}
message SetDefaultPasswordlessRegistrationMessageTextResponse {
zitadel.v1.ObjectDetails details = 1;
}
message GetDefaultLoginTextsRequest {
string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
@@ -3320,6 +3393,9 @@ message SetCustomLoginTextsRequest {
zitadel.text.v1.SuccessLoginScreenText success_login_text = 29;
zitadel.text.v1.LogoutDoneScreenText logout_text = 30;
zitadel.text.v1.FooterText footer_text = 31;
zitadel.text.v1.PasswordlessPromptScreenText passwordless_prompt_text = 32;
zitadel.text.v1.PasswordlessRegistrationScreenText passwordless_registration_text = 33;
zitadel.text.v1.PasswordlessRegistrationDoneScreenText passwordless_registration_done_text = 34;
}
message SetCustomLoginTextsResponse {

View File

@@ -9,6 +9,7 @@ import "zitadel/policy.proto";
import "zitadel/idp.proto";
import "validate/validate.proto";
import "google/api/annotations.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
@@ -76,7 +77,7 @@ service AuthService {
option (google.api.http) = {
post: "/users/me/changes/_search"
};
option (zitadel.v1.auth_option) = {
permission: "authenticated"
};
@@ -132,7 +133,7 @@ service AuthService {
put: "/users/me/username"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "authenticated"
};
@@ -407,7 +408,7 @@ service AuthService {
};
}
// Returns all configured passwordless authentications of the authorized user
// Returns all configured passwordless authenticators of the authorized user
rpc ListMyPasswordless(ListMyPasswordlessRequest) returns (ListMyPasswordlessResponse) {
option (google.api.http) = {
post: "/users/me/passwordless/_search"
@@ -417,7 +418,7 @@ service AuthService {
};
}
// Adds a new passwordless authentications to the authorized user
// Adds a new passwordless authenticator to the authorized user
// Multiple passwordless authentications can be configured
rpc AddMyPasswordless(AddMyPasswordlessRequest) returns (AddMyPasswordlessResponse) {
option (google.api.http) = {
@@ -429,6 +430,32 @@ service AuthService {
};
}
// Adds a new passwordless authenticator link to the authorized user and returns it directly
// This link enables the user to register a new device if current passwordless devices are all platform authenticators
// e.g. User has already registered Windows Hello and wants to register FaceID on the iPhone
rpc AddMyPasswordlessLink(AddMyPasswordlessLinkRequest) returns (AddMyPasswordlessLinkResponse) {
option (google.api.http) = {
post: "/users/me/passwordless/_link"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "authenticated"
};
}
// Adds a new passwordless authenticator link to the authorized user and sends it to the registered email address
// This link enables the user to register a new device if current passwordless devices are all platform authenticators
// e.g. User has already registered Windows Hello and wants to register FaceID on the iPhone
rpc SendMyPasswordlessLink(SendMyPasswordlessLinkRequest) returns (SendMyPasswordlessLinkResponse) {
option (google.api.http) = {
post: "/users/me/passwordless/_send_link"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "authenticated"
};
}
// Verifies the last added passwordless configuration
rpc VerifyMyPasswordless(VerifyMyPasswordlessRequest) returns (VerifyMyPasswordlessResponse) {
option (google.api.http) = {
@@ -775,7 +802,7 @@ message RemoveMyAuthFactorU2FResponse {
message ListMyPasswordlessRequest {}
message ListMyPasswordlessResponse {
repeated zitadel.user.v1.WebAuthNToken result = 1;
repeated zitadel.user.v1.WebAuthNToken result = 1;
}
//This is an empty request
@@ -786,6 +813,22 @@ message AddMyPasswordlessResponse {
zitadel.v1.ObjectDetails details = 2;
}
//This is an empty request
message AddMyPasswordlessLinkRequest {}
message AddMyPasswordlessLinkResponse {
zitadel.v1.ObjectDetails details = 1;
string link = 2;
google.protobuf.Duration expiration = 3;
}
//This is an empty request
message SendMyPasswordlessLinkRequest {}
message SendMyPasswordlessLinkResponse {
zitadel.v1.ObjectDetails details = 1;
}
message VerifyMyPasswordlessRequest {
zitadel.user.v1.WebAuthNVerification verification = 1 [(validate.rules).message.required = true];
}

View File

@@ -485,7 +485,7 @@ service ManagementService {
};
}
// Returns all configured passwordless authentications
// Returns all configured passwordless authenticators
rpc ListHumanPasswordless(ListHumanPasswordlessRequest) returns (ListHumanPasswordlessResponse) {
option (google.api.http) = {
post: "/users/{user_id}/passwordless/_search"
@@ -496,7 +496,20 @@ service ManagementService {
};
}
// Removed a configured passwordless authentication
// Adds a new passwordless authenticator link to the user and sends it to the registered email address
// This link enables the user to register a new device if current passwordless devices are all platform authenticators
// e.g. User has already registered Windows Hello and wants to register FaceID on the iPhone
rpc SendPasswordlessRegistration(SendPasswordlessRegistrationRequest) returns (SendPasswordlessRegistrationResponse) {
option (google.api.http) = {
post: "/users/{user_id}/passwordless/_send_link"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "authenticated"
};
}
// Removed a configured passwordless authenticator
rpc RemoveHumanPasswordless(RemoveHumanPasswordlessRequest) returns (RemoveHumanPasswordlessResponse) {
option (google.api.http) = {
delete: "/users/{user_id}/passwordless/{token_id}"
@@ -2175,8 +2188,7 @@ service ManagementService {
};
}
//Sets the default custom text for initial message
// it impacts all organisations without customized initial message text
// Sets the custom text for initial message
// The Following Variables can be used:
// {{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
rpc SetCustomInitMessageText(SetCustomInitMessageTextRequest) returns (SetCustomInitMessageTextResponse) {
@@ -2224,8 +2236,7 @@ service ManagementService {
};
}
//Sets the default custom text for password reset message
// it impacts all organisations without customized password reset message text
// Sets the custom text for password reset message
// The Following Variables can be used:
// {{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
rpc SetCustomPasswordResetMessageText(SetCustomPasswordResetMessageTextRequest) returns (SetCustomPasswordResetMessageTextResponse) {
@@ -2274,8 +2285,7 @@ service ManagementService {
};
}
//Sets the default custom text for verify email message
// it impacts all organisations without customized verify email message text
// Sets the custom text for verify email message
// The Following Variables can be used:
// {{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
rpc SetCustomVerifyEmailMessageText(SetCustomVerifyEmailMessageTextRequest) returns (SetCustomVerifyEmailMessageTextResponse) {
@@ -2324,8 +2334,7 @@ service ManagementService {
};
}
//Sets the default custom text for verify email message
// it impacts all organisations without customized verify email message text
// Sets the default custom text for verify email message
// The Following Variables can be used:
// {{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
rpc SetCustomVerifyPhoneMessageText(SetCustomVerifyPhoneMessageTextRequest) returns (SetCustomVerifyPhoneMessageTextResponse) {
@@ -2374,8 +2383,7 @@ service ManagementService {
};
}
// Sets the default custom text for domain claimed message
// it impacts all organisations without customized domain claimed message text
// Sets the custom text for domain claimed message
// The Following Variables can be used:
// {{.Domain}} {{.TempUsername}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
rpc SetCustomDomainClaimedMessageCustomText(SetCustomDomainClaimedMessageTextRequest) returns (SetCustomDomainClaimedMessageTextResponse) {
@@ -2390,7 +2398,7 @@ service ManagementService {
};
}
// Removes the custom init message text of the organisation
// Removes the custom domain claimed message text of the organisation
// The default text of the IAM will trigger after
rpc ResetCustomDomainClaimedMessageTextToDefault(ResetCustomDomainClaimedMessageTextToDefaultRequest) returns (ResetCustomDomainClaimedMessageTextToDefaultResponse) {
option (google.api.http) = {
@@ -2402,6 +2410,55 @@ service ManagementService {
};
}
//Returns the custom text for passwordless link message
rpc GetCustomPasswordlessRegistrationMessageText(GetCustomPasswordlessRegistrationMessageTextRequest) returns (GetCustomPasswordlessRegistrationMessageTextResponse) {
option (google.api.http) = {
get: "/text/message/passwordless_registration/{language}";
};
option (zitadel.v1.auth_option) = {
permission: "policy.read";
};
}
//Returns the custom text for passwordless link message
rpc GetDefaultPasswordlessRegistrationMessageText(GetDefaultPasswordlessRegistrationMessageTextRequest) returns (GetDefaultPasswordlessRegistrationMessageTextResponse) {
option (google.api.http) = {
get: "/text/default/message/passwordless_registration/{language}";
};
option (zitadel.v1.auth_option) = {
permission: "policy.read";
};
}
// Sets the custom text for passwordless link message
// The Following Variables can be used:
// {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
rpc SetCustomPasswordlessRegistrationMessageCustomText(SetCustomPasswordlessRegistrationMessageTextRequest) returns (SetCustomPasswordlessRegistrationMessageTextResponse) {
option (google.api.http) = {
put: "/text/message/passwordless_registration/{language}";
body: "*";
};
option (zitadel.v1.auth_option) = {
permission: "policy.write";
feature: "custom_text"
};
}
// Removes the custom passwordless link message text of the organisation
// The default text of the IAM will trigger after
rpc ResetCustomPasswordlessRegistrationMessageTextToDefault(ResetCustomPasswordlessRegistrationMessageTextToDefaultRequest) returns (ResetCustomPasswordlessRegistrationMessageTextToDefaultResponse) {
option (google.api.http) = {
delete: "/text/message/passwordless_registration/{language}"
};
option (zitadel.v1.auth_option) = {
permission: "policy.delete"
};
}
//Returns the custom texts for login ui
rpc GetCustomLoginTexts(GetCustomLoginTextsRequest) returns (GetCustomLoginTextsResponse) {
option (google.api.http) = {
@@ -2695,11 +2752,18 @@ message ImportHumanUserRequest {
Phone phone = 4;
string password = 5;
bool password_change_required = 6;
bool request_passwordless_registration = 7;
}
message ImportHumanUserResponse {
message PasswordlessRegistration {
string link = 1;
google.protobuf.Duration lifetime = 2;
}
string user_id = 1;
zitadel.v1.ObjectDetails details = 2;
PasswordlessRegistration passwordless_registration = 3;
}
message AddMachineUserRequest {
@@ -2934,6 +2998,14 @@ message ListHumanPasswordlessResponse {
repeated zitadel.user.v1.WebAuthNToken result = 1;
}
message SendPasswordlessRegistrationRequest {
string user_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message SendPasswordlessRegistrationResponse {
zitadel.v1.ObjectDetails details = 1;
}
message RemoveHumanPasswordlessRequest {
string user_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string token_id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
@@ -4354,6 +4426,9 @@ message SetCustomLoginTextsRequest {
zitadel.text.v1.SuccessLoginScreenText success_login_text = 29;
zitadel.text.v1.LogoutDoneScreenText logout_text = 30;
zitadel.text.v1.FooterText footer_text = 31;
zitadel.text.v1.PasswordlessPromptScreenText passwordless_prompt_text = 32;
zitadel.text.v1.PasswordlessRegistrationScreenText passwordless_registration_text = 33;
zitadel.text.v1.PasswordlessRegistrationDoneScreenText passwordless_registration_done_text = 34;
}
message SetCustomLoginTextsResponse {
@@ -4544,6 +4619,50 @@ message ResetCustomDomainClaimedMessageTextToDefaultResponse {
zitadel.v1.ObjectDetails details = 1;
}
message GetCustomPasswordlessRegistrationMessageTextRequest {
string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message GetCustomPasswordlessRegistrationMessageTextResponse {
zitadel.text.v1.MessageCustomText custom_text = 1;
}
message GetDefaultPasswordlessRegistrationMessageTextRequest {
string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message GetDefaultPasswordlessRegistrationMessageTextResponse {
zitadel.text.v1.MessageCustomText custom_text = 1;
}
message SetCustomPasswordlessRegistrationMessageTextRequest {
string language = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"de\""
}
];
string title = 2 [(validate.rules).string = {max_len: 200}];
string pre_header = 3 [(validate.rules).string = {max_len: 200}];
string subject = 4 [(validate.rules).string = {max_len: 200}];
string greeting = 5 [(validate.rules).string = {max_len: 200}];
string text = 6 [(validate.rules).string = {max_len: 800}];
string button_text = 7 [(validate.rules).string = {max_len: 200}];
string footer_text = 8 [(validate.rules).string = {max_len: 200}];
}
message SetCustomPasswordlessRegistrationMessageTextResponse {
zitadel.v1.ObjectDetails details = 1;
}
message ResetCustomPasswordlessRegistrationMessageTextToDefaultRequest {
string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message ResetCustomPasswordlessRegistrationMessageTextToDefaultResponse {
zitadel.v1.ObjectDetails details = 1;
}
message GetOrgIDPByIDRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}

View File

@@ -79,6 +79,9 @@ message LoginCustomText {
SuccessLoginScreenText success_login_text = 29;
LogoutDoneScreenText logout_text = 30;
FooterText footer_text = 31;
PasswordlessPromptScreenText passwordless_prompt_text = 32;
PasswordlessRegistrationScreenText passwordless_registration_text = 33;
PasswordlessRegistrationDoneScreenText passwordless_registration_done_text = 34;
}
message SelectAccountScreenText {
@@ -358,4 +361,28 @@ message FooterText {
string privacy_policy = 3 [(validate.rules).string = {max_len: 200}];
string help = 5 [(validate.rules).string = {max_len: 200}];
string help_link = 6 [(validate.rules).string = {max_len: 500}];
}
message PasswordlessPromptScreenText {
string title = 1 [(validate.rules).string = {max_len: 200}];
string description = 2 [(validate.rules).string = {max_len: 500}];
string description_init = 3 [(validate.rules).string = {max_len: 500}];
string passwordless_button_text = 4 [(validate.rules).string = {max_len: 100}];
string next_button_text = 5 [(validate.rules).string = {max_len: 100}];
string skip_button_text = 6 [(validate.rules).string = {max_len: 100}];
}
message PasswordlessRegistrationScreenText {
string title = 1 [(validate.rules).string = {max_len: 200}];
string description = 2 [(validate.rules).string = {max_len: 500}];
string token_name_label = 3 [(validate.rules).string = {max_len: 200}];
string not_supported = 4 [(validate.rules).string = {max_len: 500}];
string register_token_button_text = 5 [(validate.rules).string = {max_len: 100}];
string error_retry = 6 [(validate.rules).string = {max_len: 500}];
}
message PasswordlessRegistrationDoneScreenText {
string title = 1 [(validate.rules).string = {max_len: 200}];
string description = 2 [(validate.rules).string = {max_len: 500}];
string next_button_text = 3 [(validate.rules).string = {max_len: 100}];
}