From 80815e89cff9222f98ba9cecc11374c412704816 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Wed, 17 May 2023 09:06:11 +0200 Subject: [PATCH] chore(api): proto definition of passkeys endpoints (user service) (#5864) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: first proto definition of passkeys endpoints * improve passkeys requests * fix: some renaming of passkey endpoint attributes * change to post methods * improve passkeys requests * add code id and make codes optional * fix: some documentation for passkeys endpoints * drop code from VerifyPasskeyRegistrationRequest not needed, as disccussed * put code_id and code in a nested object * add passkey_id to RegisterPasskeyResponse * improve description --------- Co-authored-by: Livio Spring Co-authored-by: Tim Möhlmann --- proto/zitadel/user/v2alpha/auth.proto | 50 +++++ proto/zitadel/user/v2alpha/email.proto | 1 - proto/zitadel/user/v2alpha/user_service.proto | 176 ++++++++++++++++++ 3 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 proto/zitadel/user/v2alpha/auth.proto diff --git a/proto/zitadel/user/v2alpha/auth.proto b/proto/zitadel/user/v2alpha/auth.proto new file mode 100644 index 0000000000..754877fd4e --- /dev/null +++ b/proto/zitadel/user/v2alpha/auth.proto @@ -0,0 +1,50 @@ +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"; + +enum PasskeyAuthenticator { + PASSKEY_AUTHENTICATOR_UNSPECIFIED = 0; + PASSKEY_AUTHENTICATOR_PLATFORM = 1; + PASSKEY_AUTHENTICATOR_CROSS_PLATFORM = 2; +} + +message SendPasskeyRegistrationLink { + optional string url_template = 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: "\"https://example.com/passkey/register?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}\""; + description: "\"Optionally set a url_template, which will be used in the mail sent by ZITADEL to guide the user to your passkey registration page. If no template is set, the default ZITADEL url will be used.\"" + } + ]; +} + +message ReturnPasskeyRegistrationCode {} + +message PasskeyRegistrationCode { + string id = 1 [ + (validate.rules).string = {min_len: 1, max_len: 200}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "\"id to the one time code generated by ZITADEL\""; + example: "\"e2a48d6a-362b-4db6-a1fb-34feab84dc62\""; + max_length: 200; + } + ]; + string code = 2 [ + (validate.rules).string = {min_len: 1, max_len: 200}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "\"one time code generated by ZITADEL\""; + example: "\"SomeSpecialCode\""; + max_length: 200; + } + ]; +} diff --git a/proto/zitadel/user/v2alpha/email.proto b/proto/zitadel/user/v2alpha/email.proto index 151348b55a..3022ffc2a6 100644 --- a/proto/zitadel/user/v2alpha/email.proto +++ b/proto/zitadel/user/v2alpha/email.proto @@ -30,7 +30,6 @@ message SetHumanEmail { message SendEmailVerificationCode { optional string url_template = 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; diff --git a/proto/zitadel/user/v2alpha/user_service.proto b/proto/zitadel/user/v2alpha/user_service.proto index f899d91f7a..66e1bc4516 100644 --- a/proto/zitadel/user/v2alpha/user_service.proto +++ b/proto/zitadel/user/v2alpha/user_service.proto @@ -4,11 +4,13 @@ package zitadel.user.v2alpha; import "zitadel/object/v2alpha/object.proto"; import "zitadel/protoc_gen_zitadel/v2/options.proto"; +import "zitadel/user/v2alpha/auth.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 "google/protobuf/duration.proto"; import "protoc-gen-openapiv2/options/annotations.proto"; import "validate/validate.proto"; @@ -153,6 +155,73 @@ service UserService { }; }; } + + rpc RegisterPasskey (RegisterPasskeyRequest) returns (RegisterPasskeyResponse) { + option (google.api.http) = { + post: "/users/{user_id}/passkeys" + body: "*" + }; + + option (zitadel.protoc_gen_zitadel.v2.options) = { + auth_option: { + permission: "authenticated" + } + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Start the registration of passkey for a user"; + description: "Start the registration of a passkey for a user, as a response the public key credential creation options are returned, which are used to verify the passkey." + responses: { + key: "200" + value: { + description: "OK"; + } + }; + }; + } + rpc VerifyPasskeyRegistration (VerifyPasskeyRegistrationRequest) returns (VerifyPasskeyRegistrationResponse) { + option (google.api.http) = { + post: "/users/{user_id}/passkeys/{passkey_id}" + body: "*" + }; + + option (zitadel.protoc_gen_zitadel.v2.options) = { + auth_option: { + permission: "authenticated" + } + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Verify a passkey for a user"; + description: "Verify the passkey registration with the public key credential." + responses: { + key: "200" + value: { + description: "OK"; + } + }; + }; + } + rpc CreatePasskeyRegistrationLink (CreatePasskeyRegistrationLinkRequest) returns (CreatePasskeyRegistrationLinkResponse) { + option (google.api.http) = { + post: "/users/{user_id}/passkeys/registration_link" + body: "*" + }; + + option (zitadel.protoc_gen_zitadel.v2.options) = { + auth_option: { + permission: "user.passkey.write" + } + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Create a passkey registration link for a user"; + description: "Create a passkey registration link which includes a code and either return it or send it to the user." + responses: { + key: "200" + value: { + description: "OK"; + } + }; + }; + } } message AddHumanUserRequest{ @@ -254,3 +323,110 @@ message VerifyEmailRequest{ message VerifyEmailResponse{ zitadel.object.v2alpha.Details details = 1; } + +message RegisterPasskeyRequest{ + string user_id = 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: "\"d654e6ba-70a3-48ef-a95d-37c8d8a7901a\""; + } + ]; + optional PasskeyRegistrationCode code = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "\"one time code generated by ZITADEL; required to start the passkey registration without user authentication\""; + } + ]; + PasskeyAuthenticator authenticator = 3 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "\"Optionally specify the authenticator type of the passkey device (platform or cross-platform). If none is provided, both values are allowed.\""; + } + ]; +} + +message RegisterPasskeyResponse{ + zitadel.object.v2alpha.Details details = 1; + string passkey_id = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"fabde5c8-c13f-481d-a90b-5e59a001a076\"" + } + ]; + bytes public_key_credential_creation_options = 3 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "json representation of public key credential creation options used by the passkey client" + example: "\"eyJwdWJsaWNLZXkiOnsiY2hhbGxlbmdlIoplfZm4vM21qSzBPdjltN2x6VWhnclYyejFJSlVzZnpLd0Z1TytWTWtzRW1Icz0iLCJycCI6eyJuYW1lIjoiWklUQURFTCIsImlkIjoiYWNtZS1nem9lNHgueml0YWRlbC5jbG91ZCJ9LCJ1c2VyIjp7Im5hbWUiOiJ0ZXN0dXNlcjU1QGFjbWUueml0YWRlbC5jbG91ZCIsImRpc3BsYXlOYW1lIjoiVGVzdCBUZXN0IiwiaWQiOiJNVGd5TVRVMk1qWTBNakk1TXpBMk5qSTEifSwicHViS2V5Q3JlZFBhcmFtcyI6W3sidHlwZSI6InB1YmxpYy1rZXkiLCJhbGciOi03fSx7InR5cGUiOiJwdWJsaWMta2V5IiwiYWxnIjotMzV9LHsidHlwZSI6InB1YmxpYy1rZXkiLCJhbGciOi0zNn0seyJ0eXBlIjoicHVibGljLWtleSIsImFsZyI6LTI1N30seyJ0eXBlIjoicHVibGljLWtleSIsImFsZyI6LTI1OH0seyJ0eXBlIjoicHVibGljLWtleSIsImFsZyI6LTI1OX0seyJ0eXBlIjoicHVibGljLWtleSIsImFsZyI6LTM3fSx7InR5cGUiOiJwdWJsaWMta2V5IiwiYWxnIjotMzh9LHsidHlwZSI6InB1YmxpYy1rZXkiLCJhbGciOi0zOX0seyJ0eXBlIjoicHVibGljLWtleSIsImFsZyI6LTh9XSwiYXV0aGVudGljYXRvclNlbGVjdGlvbiI6eyJ1c2VyVmVyaWZpY2F0aW9uIjoiZGlzY291cmFnZWQifn2ilGltZW91dCI6NjAwMDAsImF0dGVzdGF0aW9uIjoibm9uZSJ9fQ==\"" + } + ]; +} + +message VerifyPasskeyRegistrationRequest{ + string user_id = 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: "\"d654e6ba-70a3-48ef-a95d-37c8d8a7901a\""; + } + ]; + string passkey_id = 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: "\"fabde5c8-c13f-481d-a90b-5e59a001a076\""; + } + ]; + bytes public_key_credential = 3 [ + (validate.rules).bytes = {min_len: 55, max_len: 1048576}, + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "JSON representation of public key credential issued by the passkey client"; + min_length: 55; + max_length: 1048576; //1 MB + } + ]; + string passkey_name = 4 [ + (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: "\"fido key\"" + } + ]; +} + +message VerifyPasskeyRegistrationResponse{ + zitadel.object.v2alpha.Details details = 1; +} + +message CreatePasskeyRegistrationLinkRequest{ + string user_id = 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: "\"d654e6ba-70a3-48ef-a95d-37c8d8a7901a\""; + } + ]; + // if no medium is specified, an email is sent with the default url + oneof medium { + SendPasskeyRegistrationLink send_link = 2; + ReturnPasskeyRegistrationCode return_code = 3; + } +} + +message CreatePasskeyRegistrationLinkResponse{ + zitadel.object.v2alpha.Details details = 1; + // in case the medium was set to return_code, the code will be returned + optional PasskeyRegistrationCode code = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "\"one time code generated by ZITADEL; required to start the passkey registration without user authentication\""; + } + ]; +}