feat: choose preferred WebAuthN platform for passwordless registration (#2469)

* feat: request preferred platform type for passwordless registration when using link

* add text in console
This commit is contained in:
Livio Amstutz 2021-10-04 16:19:21 +02:00 committed by GitHub
parent 415d0c7ab2
commit 52c1494fe9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 273 additions and 130 deletions

View File

@ -333,6 +333,7 @@ export function mapRequestValues(map: Partial<Map>, req: Req): Req {
const r31 = new PasswordlessRegistrationDoneScreenText();
r31.setDescription(map.passwordlessRegistrationDoneText?.description ?? '');
r31.setDescriptionClose(map.passwordlessRegistrationDoneText?.descriptionClose ?? '');
r31.setNextButtonText(map.passwordlessRegistrationDoneText?.nextButtonText ?? '');
r31.setTitle(map.passwordlessRegistrationDoneText?.title ?? '');
r31.setNextButtonText(map.passwordlessRegistrationDoneText?.cancelButtonText ?? '');

View File

@ -438,6 +438,7 @@ title: zitadel/text.proto
| description | string | - | string.max_len: 500<br /> |
| next_button_text | string | - | string.max_len: 100<br /> |
| cancel_button_text | string | - | string.max_len: 100<br /> |
| description_close | string | - | string.max_len: 100<br /> |

View File

@ -8,6 +8,7 @@ import (
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/object"
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
"github.com/caos/zitadel/internal/domain"
auth_pb "github.com/caos/zitadel/pkg/grpc/auth"
user_pb "github.com/caos/zitadel/pkg/grpc/user"
)
@ -24,7 +25,7 @@ func (s *Server) ListMyPasswordless(ctx context.Context, _ *auth_pb.ListMyPasswo
func (s *Server) AddMyPasswordless(ctx context.Context, _ *auth_pb.AddMyPasswordlessRequest) (*auth_pb.AddMyPasswordlessResponse, error) {
ctxData := authz.GetCtxData(ctx)
token, err := s.command.HumanAddPasswordlessSetup(ctx, ctxData.UserID, ctxData.ResourceOwner, false)
token, err := s.command.HumanAddPasswordlessSetup(ctx, ctxData.UserID, ctxData.ResourceOwner, false, domain.AuthenticatorAttachmentUnspecified)
if err != nil {
return nil, err
}

View File

@ -302,6 +302,7 @@ func PasswordlessRegistrationDoneScreenTextToPb(text domain.PasswordlessRegistra
return &text_pb.PasswordlessRegistrationDoneScreenText{
Title: text.Title,
Description: text.Description,
DescriptionClose: text.DescriptionClose,
NextButtonText: text.NextButtonText,
CancelButtonText: text.CancelButtonText,
}
@ -754,9 +755,10 @@ func PasswordlessRegistrationDoneScreenTextPbToDomain(text *text_pb.Passwordless
return domain.PasswordlessRegistrationDoneScreenText{}
}
return domain.PasswordlessRegistrationDoneScreenText{
Title: text.Title,
Description: text.Description,
NextButtonText: text.NextButtonText,
Title: text.Title,
Description: text.Description,
DescriptionClose: text.DescriptionClose,
NextButtonText: text.NextButtonText,
}
}

View File

@ -24,9 +24,9 @@ type AuthRequestRepository interface {
VerifyMFAOTP(ctx context.Context, authRequestID, userID, resourceOwner, code, userAgentID string, info *domain.BrowserInfo) error
BeginMFAU2FLogin(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) (*domain.WebAuthNLogin, error)
VerifyMFAU2F(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string, credentialData []byte, info *domain.BrowserInfo) error
BeginPasswordlessSetup(ctx context.Context, userID, resourceOwner string) (login *domain.WebAuthNToken, err error)
BeginPasswordlessSetup(ctx context.Context, userID, resourceOwner string, preferredPlatformType domain.AuthenticatorAttachment) (login *domain.WebAuthNToken, err error)
VerifyPasswordlessSetup(ctx context.Context, userID, resourceOwner, userAgentID, tokenName string, credentialData []byte) (err error)
BeginPasswordlessInitCodeSetup(ctx context.Context, userID, resourceOwner, codeID, verificationCode string) (login *domain.WebAuthNToken, err error)
BeginPasswordlessInitCodeSetup(ctx context.Context, userID, resourceOwner, codeID, verificationCode string, preferredPlatformType domain.AuthenticatorAttachment) (login *domain.WebAuthNToken, err error)
VerifyPasswordlessInitCodeSetup(ctx context.Context, userID, resourceOwner, userAgentID, tokenName, codeID, verificationCode string, credentialData []byte) (err error)
BeginPasswordlessLogin(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) (*domain.WebAuthNLogin, error)
VerifyPasswordless(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string, credentialData []byte, info *domain.BrowserInfo) error

View File

@ -5,6 +5,7 @@ import (
"time"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/auth_request/model"
@ -327,10 +328,10 @@ func (repo *AuthRequestRepo) VerifyMFAU2F(ctx context.Context, userID, resourceO
return repo.Command.HumanFinishU2FLogin(ctx, userID, resourceOwner, credentialData, request, true)
}
func (repo *AuthRequestRepo) BeginPasswordlessSetup(ctx context.Context, userID, resourceOwner string) (login *domain.WebAuthNToken, err error) {
func (repo *AuthRequestRepo) BeginPasswordlessSetup(ctx context.Context, userID, resourceOwner string, authenticatorPlatform domain.AuthenticatorAttachment) (login *domain.WebAuthNToken, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
return repo.Command.HumanAddPasswordlessSetup(ctx, userID, resourceOwner, true)
return repo.Command.HumanAddPasswordlessSetup(ctx, userID, resourceOwner, true, authenticatorPlatform)
}
func (repo *AuthRequestRepo) VerifyPasswordlessSetup(ctx context.Context, userID, resourceOwner, userAgentID, tokenName string, credentialData []byte) (err error) {
@ -340,10 +341,10 @@ func (repo *AuthRequestRepo) VerifyPasswordlessSetup(ctx context.Context, userID
return err
}
func (repo *AuthRequestRepo) BeginPasswordlessInitCodeSetup(ctx context.Context, userID, resourceOwner, codeID, verificationCode string) (login *domain.WebAuthNToken, err error) {
func (repo *AuthRequestRepo) BeginPasswordlessInitCodeSetup(ctx context.Context, userID, resourceOwner, codeID, verificationCode string, preferredPlatformType domain.AuthenticatorAttachment) (login *domain.WebAuthNToken, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
return repo.Command.HumanAddPasswordlessSetupInitCode(ctx, userID, resourceOwner, codeID, verificationCode)
return repo.Command.HumanAddPasswordlessSetupInitCode(ctx, userID, resourceOwner, codeID, verificationCode, preferredPlatformType)
}
func (repo *AuthRequestRepo) VerifyPasswordlessInitCodeSetup(ctx context.Context, userID, resourceOwner, userAgentID, tokenName, codeID, verificationCode string, credentialData []byte) (err error) {

View File

@ -661,6 +661,10 @@ func (c *Commands) createPasswordlessRegistrationDoneEvents(ctx context.Context,
if event != nil {
events = append(events, event)
}
event = c.createCustomLoginTextEvent(ctx, agg, domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose, existingText.PasswordlessRegistrationDoneDescriptionClose, text.PasswordlessRegistrationDone.DescriptionClose, text.Language, defaultText)
if event != nil {
events = append(events, event)
}
event = c.createCustomLoginTextEvent(ctx, agg, domain.LoginKeyPasswordlessRegistrationDoneNextButtonText, existingText.PasswordlessRegistrationDoneNextButtonText, text.PasswordlessRegistrationDone.NextButtonText, text.Language, defaultText)
if event != nil {
events = append(events, event)

View File

@ -182,6 +182,7 @@ type CustomLoginTextReadModel struct {
PasswordlessRegistrationDoneTitle string
PasswordlessRegistrationDoneDescription string
PasswordlessRegistrationDoneDescriptionClose string
PasswordlessRegistrationDoneNextButtonText string
PasswordlessRegistrationDoneCancelButtonText string
@ -1703,6 +1704,10 @@ func (wm *CustomLoginTextReadModel) handlePasswordlessRegistrationDoneScreenSetE
wm.PasswordlessRegistrationDoneDescription = e.Text
return
}
if e.Key == domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose {
wm.PasswordlessRegistrationDoneDescriptionClose = e.Text
return
}
if e.Key == domain.LoginKeyPasswordlessRegistrationDoneNextButtonText {
wm.PasswordlessRegistrationDoneNextButtonText = e.Text
return
@ -1722,6 +1727,10 @@ func (wm *CustomLoginTextReadModel) handlePasswordlessRegistrationDoneScreenRemo
wm.PasswordlessRegistrationDoneDescription = ""
return
}
if e.Key == domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose {
wm.PasswordlessRegistrationDoneDescriptionClose = ""
return
}
if e.Key == domain.LoginKeyPasswordlessRegistrationDoneNextButtonText {
wm.PasswordlessRegistrationDoneNextButtonText = ""
return

View File

@ -684,6 +684,11 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) {
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescription, "Description", language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose, "DescriptionClose", language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneNextButtonText, "NextButtonText", language.English,
@ -1320,6 +1325,7 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) {
PasswordlessRegistrationDone: domain.PasswordlessRegistrationDoneScreenText{
Title: "Title",
Description: "Description",
DescriptionClose: "DescriptionClose",
NextButtonText: "NextButtonText",
CancelButtonText: "CancelButtonText",
},
@ -2081,6 +2087,11 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) {
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescription, "Description", language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose, "DescriptionClose", language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneNextButtonText, "NextButtonText", language.English,
@ -3174,6 +3185,11 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) {
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescription, language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose, language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneNextButtonText, language.English,
@ -3641,41 +3657,41 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) {
args: args{
ctx: context.Background(),
config: &domain.CustomLoginText{
Language: language.English,
SelectAccount: domain.SelectAccountScreenText{},
Login: domain.LoginScreenText{},
Password: domain.PasswordScreenText{},
UsernameChange: domain.UsernameChangeScreenText{},
UsernameChangeDone: domain.UsernameChangeDoneScreenText{},
InitPassword: domain.InitPasswordScreenText{},
InitPasswordDone: domain.InitPasswordDoneScreenText{},
EmailVerification: domain.EmailVerificationScreenText{},
EmailVerificationDone: domain.EmailVerificationDoneScreenText{},
InitUser: domain.InitializeUserScreenText{},
InitUserDone: domain.InitializeUserDoneScreenText{},
InitMFAPrompt: domain.InitMFAPromptScreenText{},
InitMFAOTP: domain.InitMFAOTPScreenText{},
InitMFAU2F: domain.InitMFAU2FScreenText{},
InitMFADone: domain.InitMFADoneScreenText{},
MFAProvider: domain.MFAProvidersText{},
VerifyMFAOTP: domain.VerifyMFAOTPScreenText{},
VerifyMFAU2F: domain.VerifyMFAU2FScreenText{},
Passwordless: domain.PasswordlessScreenText{},
PasswordlessPrompt: domain.PasswordlessPromptScreenText{},
PasswordlessRegistration: domain.PasswordlessRegistrationScreenText{},
PasswordlessRegistrationDone: domain.PasswordlessRegistrationDoneScreenText{},
PasswordChange: domain.PasswordChangeScreenText{},
PasswordChangeDone: domain.PasswordChangeDoneScreenText{},
PasswordResetDone: domain.PasswordResetDoneScreenText{},
RegisterOption: domain.RegistrationOptionScreenText{},
RegistrationUser: domain.RegistrationUserScreenText{},
Language: language.English,
SelectAccount: domain.SelectAccountScreenText{},
Login: domain.LoginScreenText{},
Password: domain.PasswordScreenText{},
UsernameChange: domain.UsernameChangeScreenText{},
UsernameChangeDone: domain.UsernameChangeDoneScreenText{},
InitPassword: domain.InitPasswordScreenText{},
InitPasswordDone: domain.InitPasswordDoneScreenText{},
EmailVerification: domain.EmailVerificationScreenText{},
EmailVerificationDone: domain.EmailVerificationDoneScreenText{},
InitUser: domain.InitializeUserScreenText{},
InitUserDone: domain.InitializeUserDoneScreenText{},
InitMFAPrompt: domain.InitMFAPromptScreenText{},
InitMFAOTP: domain.InitMFAOTPScreenText{},
InitMFAU2F: domain.InitMFAU2FScreenText{},
InitMFADone: domain.InitMFADoneScreenText{},
MFAProvider: domain.MFAProvidersText{},
VerifyMFAOTP: domain.VerifyMFAOTPScreenText{},
VerifyMFAU2F: domain.VerifyMFAU2FScreenText{},
Passwordless: domain.PasswordlessScreenText{},
PasswordlessPrompt: domain.PasswordlessPromptScreenText{},
PasswordlessRegistration: domain.PasswordlessRegistrationScreenText{},
PasswordlessRegistrationDone: domain.PasswordlessRegistrationDoneScreenText{},
PasswordChange: domain.PasswordChangeScreenText{},
PasswordChangeDone: domain.PasswordChangeDoneScreenText{},
PasswordResetDone: domain.PasswordResetDoneScreenText{},
RegisterOption: domain.RegistrationOptionScreenText{},
RegistrationUser: domain.RegistrationUserScreenText{},
ExternalRegistrationUserOverview: domain.ExternalRegistrationUserOverviewScreenText{},
RegistrationOrg: domain.RegistrationOrgScreenText{},
LinkingUsersDone: domain.LinkingUserDoneScreenText{},
ExternalNotFoundOption: domain.ExternalUserNotFoundScreenText{},
LoginSuccess: domain.SuccessLoginScreenText{},
LogoutDone: domain.LogoutDoneScreenText{},
Footer: domain.FooterText{},
RegistrationOrg: domain.RegistrationOrgScreenText{},
LinkingUsersDone: domain.LinkingUserDoneScreenText{},
ExternalNotFoundOption: domain.ExternalUserNotFoundScreenText{},
LoginSuccess: domain.SuccessLoginScreenText{},
LogoutDone: domain.LogoutDoneScreenText{},
Footer: domain.FooterText{},
},
},
res: res{
@ -4320,6 +4336,11 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) {
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescription, "Description", language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose, "DescriptionClose", language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneNextButtonText, "NextButtonText", language.English,
@ -5411,6 +5432,11 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) {
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescription, language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose, language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneNextButtonText, language.English,
@ -6504,6 +6530,11 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) {
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescription, "Description", language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose, "DescriptionClose", language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneNextButtonText, "NextButtonText", language.English,
@ -7141,6 +7172,7 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) {
PasswordlessRegistrationDone: domain.PasswordlessRegistrationDoneScreenText{
Title: "Title",
Description: "Description",
DescriptionClose: "DescriptionClose",
NextButtonText: "NextButtonText",
CancelButtonText: "CancelButtonText",
},

View File

@ -701,6 +701,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) {
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescription, "Description", language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose, "DescriptionClose", language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneNextButtonText, "NextButtonText", language.English,
@ -1339,6 +1344,7 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) {
PasswordlessRegistrationDone: domain.PasswordlessRegistrationDoneScreenText{
Title: "Title",
Description: "Description",
DescriptionClose: "DescriptionClose",
NextButtonText: "NextButtonText",
CancelButtonText: "CancelButtonText",
},
@ -2100,6 +2106,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) {
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescription, "Description", language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose, "DescriptionClose", language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneNextButtonText, "NextButtonText", language.English,
@ -3193,6 +3204,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) {
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescription, language.English,
),
),
eventFromEventPusher(
org.NewCustomTextRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose, language.English,
),
),
eventFromEventPusher(
org.NewCustomTextRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneNextButtonText, language.English,
@ -3661,41 +3677,41 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) {
ctx: context.Background(),
resourceOwner: "org1",
config: &domain.CustomLoginText{
Language: language.English,
SelectAccount: domain.SelectAccountScreenText{},
Login: domain.LoginScreenText{},
Password: domain.PasswordScreenText{},
UsernameChange: domain.UsernameChangeScreenText{},
UsernameChangeDone: domain.UsernameChangeDoneScreenText{},
InitPassword: domain.InitPasswordScreenText{},
InitPasswordDone: domain.InitPasswordDoneScreenText{},
EmailVerification: domain.EmailVerificationScreenText{},
EmailVerificationDone: domain.EmailVerificationDoneScreenText{},
InitUser: domain.InitializeUserScreenText{},
InitUserDone: domain.InitializeUserDoneScreenText{},
InitMFAPrompt: domain.InitMFAPromptScreenText{},
InitMFAOTP: domain.InitMFAOTPScreenText{},
InitMFAU2F: domain.InitMFAU2FScreenText{},
InitMFADone: domain.InitMFADoneScreenText{},
MFAProvider: domain.MFAProvidersText{},
VerifyMFAOTP: domain.VerifyMFAOTPScreenText{},
VerifyMFAU2F: domain.VerifyMFAU2FScreenText{},
Passwordless: domain.PasswordlessScreenText{},
PasswordlessPrompt: domain.PasswordlessPromptScreenText{},
PasswordlessRegistration: domain.PasswordlessRegistrationScreenText{},
PasswordlessRegistrationDone: domain.PasswordlessRegistrationDoneScreenText{},
PasswordChange: domain.PasswordChangeScreenText{},
PasswordChangeDone: domain.PasswordChangeDoneScreenText{},
PasswordResetDone: domain.PasswordResetDoneScreenText{},
RegisterOption: domain.RegistrationOptionScreenText{},
Language: language.English,
SelectAccount: domain.SelectAccountScreenText{},
Login: domain.LoginScreenText{},
Password: domain.PasswordScreenText{},
UsernameChange: domain.UsernameChangeScreenText{},
UsernameChangeDone: domain.UsernameChangeDoneScreenText{},
InitPassword: domain.InitPasswordScreenText{},
InitPasswordDone: domain.InitPasswordDoneScreenText{},
EmailVerification: domain.EmailVerificationScreenText{},
EmailVerificationDone: domain.EmailVerificationDoneScreenText{},
InitUser: domain.InitializeUserScreenText{},
InitUserDone: domain.InitializeUserDoneScreenText{},
InitMFAPrompt: domain.InitMFAPromptScreenText{},
InitMFAOTP: domain.InitMFAOTPScreenText{},
InitMFAU2F: domain.InitMFAU2FScreenText{},
InitMFADone: domain.InitMFADoneScreenText{},
MFAProvider: domain.MFAProvidersText{},
VerifyMFAOTP: domain.VerifyMFAOTPScreenText{},
VerifyMFAU2F: domain.VerifyMFAU2FScreenText{},
Passwordless: domain.PasswordlessScreenText{},
PasswordlessPrompt: domain.PasswordlessPromptScreenText{},
PasswordlessRegistration: domain.PasswordlessRegistrationScreenText{},
PasswordlessRegistrationDone: domain.PasswordlessRegistrationDoneScreenText{},
PasswordChange: domain.PasswordChangeScreenText{},
PasswordChangeDone: domain.PasswordChangeDoneScreenText{},
PasswordResetDone: domain.PasswordResetDoneScreenText{},
RegisterOption: domain.RegistrationOptionScreenText{},
ExternalRegistrationUserOverview: domain.ExternalRegistrationUserOverviewScreenText{},
RegistrationUser: domain.RegistrationUserScreenText{},
RegistrationOrg: domain.RegistrationOrgScreenText{},
LinkingUsersDone: domain.LinkingUserDoneScreenText{},
ExternalNotFoundOption: domain.ExternalUserNotFoundScreenText{},
LoginSuccess: domain.SuccessLoginScreenText{},
LogoutDone: domain.LogoutDoneScreenText{},
Footer: domain.FooterText{},
RegistrationUser: domain.RegistrationUserScreenText{},
RegistrationOrg: domain.RegistrationOrgScreenText{},
LinkingUsersDone: domain.LinkingUserDoneScreenText{},
ExternalNotFoundOption: domain.ExternalUserNotFoundScreenText{},
LoginSuccess: domain.SuccessLoginScreenText{},
LogoutDone: domain.LogoutDoneScreenText{},
Footer: domain.FooterText{},
},
},
res: res{
@ -4340,6 +4356,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) {
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescription, "Description", language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose, "DescriptionClose", language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneNextButtonText, "NextButtonText", language.English,
@ -5430,6 +5451,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) {
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescription, language.English,
),
),
eventFromEventPusher(
org.NewCustomTextRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose, language.English,
),
),
eventFromEventPusher(
org.NewCustomTextRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneNextButtonText, language.English,
@ -6523,6 +6549,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) {
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescription, "Description", language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose, "DescriptionClose", language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate, domain.LoginCustomText, domain.LoginKeyPasswordlessRegistrationDoneNextButtonText, "NextButtonText", language.English,
@ -7161,6 +7192,7 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) {
PasswordlessRegistrationDone: domain.PasswordlessRegistrationDoneScreenText{
Title: "Title",
Description: "Description",
DescriptionClose: "DescriptionClose",
NextButtonText: "NextButtonText",
CancelButtonText: "CancelButtonText",
},

View File

@ -82,7 +82,7 @@ func (c *Commands) HumanAddU2FSetup(ctx context.Context, userID, resourceowner s
if err != nil {
return nil, err
}
addWebAuthN, userAgg, webAuthN, err := c.addHumanWebAuthN(ctx, userID, resourceowner, isLoginUI, u2fTokens)
addWebAuthN, userAgg, webAuthN, err := c.addHumanWebAuthN(ctx, userID, resourceowner, isLoginUI, u2fTokens, domain.AuthenticatorAttachmentUnspecified)
if err != nil {
return nil, err
}
@ -103,12 +103,12 @@ func (c *Commands) HumanAddU2FSetup(ctx context.Context, userID, resourceowner s
return createdWebAuthN, nil
}
func (c *Commands) HumanAddPasswordlessSetup(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) {
func (c *Commands) HumanAddPasswordlessSetup(ctx context.Context, userID, resourceowner string, isLoginUI bool, authenticatorPlatform domain.AuthenticatorAttachment) (*domain.WebAuthNToken, error) {
passwordlessTokens, err := c.getHumanPasswordlessTokens(ctx, userID, resourceowner)
if err != nil {
return nil, err
}
addWebAuthN, userAgg, webAuthN, err := c.addHumanWebAuthN(ctx, userID, resourceowner, isLoginUI, passwordlessTokens)
addWebAuthN, userAgg, webAuthN, err := c.addHumanWebAuthN(ctx, userID, resourceowner, isLoginUI, passwordlessTokens, authenticatorPlatform)
if err != nil {
return nil, err
}
@ -129,15 +129,15 @@ func (c *Commands) HumanAddPasswordlessSetup(ctx context.Context, userID, resour
return createdWebAuthN, nil
}
func (c *Commands) HumanAddPasswordlessSetupInitCode(ctx context.Context, userID, resourceowner, codeID, verificationCode string) (*domain.WebAuthNToken, error) {
func (c *Commands) HumanAddPasswordlessSetupInitCode(ctx context.Context, userID, resourceowner, codeID, verificationCode string, preferredPlatformType domain.AuthenticatorAttachment) (*domain.WebAuthNToken, error) {
err := c.humanVerifyPasswordlessInitCode(ctx, userID, resourceowner, codeID, verificationCode)
if err != nil {
return nil, err
}
return c.HumanAddPasswordlessSetup(ctx, userID, resourceowner, true)
return c.HumanAddPasswordlessSetup(ctx, userID, resourceowner, true, preferredPlatformType)
}
func (c *Commands) addHumanWebAuthN(ctx context.Context, userID, resourceowner string, isLoginUI bool, tokens []*domain.WebAuthNToken) (*HumanWebAuthNWriteModel, *eventstore.Aggregate, *domain.WebAuthNToken, error) {
func (c *Commands) addHumanWebAuthN(ctx context.Context, userID, resourceowner string, isLoginUI bool, tokens []*domain.WebAuthNToken, authenticatorPlatform domain.AuthenticatorAttachment) (*HumanWebAuthNWriteModel, *eventstore.Aggregate, *domain.WebAuthNToken, error) {
if userID == "" {
return nil, nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0od", "Errors.IDMissing")
}
@ -157,7 +157,7 @@ func (c *Commands) addHumanWebAuthN(ctx context.Context, userID, resourceowner s
if accountName == "" {
accountName = user.EmailAddress
}
webAuthN, err := c.webauthn.BeginRegistration(user, accountName, domain.AuthenticatorAttachmentUnspecified, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
webAuthN, err := c.webauthn.BeginRegistration(user, accountName, authenticatorPlatform, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
if err != nil {
return nil, nil, nil, err
}
@ -498,6 +498,9 @@ func (c *Commands) HumanAddPasswordlessInitCode(ctx context.Context, userID, res
func (c *Commands) HumanSendPasswordlessInitCode(ctx context.Context, userID, resourceOwner string) (*domain.PasswordlessInitCode, error) {
codeEvent, initCode, code, err := c.humanAddPasswordlessInitCode(ctx, userID, resourceOwner, false)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx, codeEvent)
if err != nil {
return nil, err

View File

@ -178,6 +178,7 @@ const (
LoginKeyPasswordlessRegistrationDone = "PasswordlessRegistrationDone."
LoginKeyPasswordlessRegistrationDoneTitle = LoginKeyPasswordlessRegistrationDone + "Title"
LoginKeyPasswordlessRegistrationDoneDescription = LoginKeyPasswordlessRegistrationDone + "Description"
LoginKeyPasswordlessRegistrationDoneDescriptionClose = LoginKeyPasswordlessRegistrationDone + "DescriptionClose"
LoginKeyPasswordlessRegistrationDoneNextButtonText = LoginKeyPasswordlessRegistrationDone + "NextButtonText"
LoginKeyPasswordlessRegistrationDoneCancelButtonText = LoginKeyPasswordlessRegistrationDone + "CancelButtonText"
@ -659,6 +660,7 @@ type PasswordlessRegistrationScreenText struct {
type PasswordlessRegistrationDoneScreenText struct {
Title string
Description string
DescriptionClose string
NextButtonText string
CancelButtonText string
}

View File

@ -697,6 +697,9 @@ func passwordlessRegistrationDoneKeyToDomain(text *CustomTextView, result *domai
if text.Key == domain.LoginKeyPasswordlessRegistrationDoneDescription {
result.PasswordlessRegistrationDone.Description = text.Text
}
if text.Key == domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose {
result.PasswordlessRegistrationDone.DescriptionClose = text.Text
}
if text.Key == domain.LoginKeyPasswordlessRegistrationDoneNextButtonText {
result.PasswordlessRegistrationDone.NextButtonText = text.Text
}

View File

@ -23,7 +23,7 @@ func (l *Login) handlePasswordlessPrompt(w http.ResponseWriter, r *http.Request)
l.renderError(w, r, authReq, err)
return
}
l.renderPasswordlessRegistration(w, r, authReq, "", "", "", "", nil)
l.renderPasswordlessRegistration(w, r, authReq, "", "", "", "", 0, nil)
}
func (l *Login) renderPasswordlessPrompt(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) {

View File

@ -4,46 +4,78 @@ import (
"encoding/base64"
"net/http"
"github.com/caos/logging"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/domain"
)
const (
tmplPasswordlessRegistration = "passwordlessregistration"
tmplPasswordlessRegistrationDone = "passwordlessregistrationdone"
queryPasswordlessRegistrationCode = "code"
queryPasswordlessRegistrationCodeID = "codeID"
queryPasswordlessRegistrationUserID = "userID"
queryPasswordlessRegistrationOrgID = "orgID"
tmplPasswordlessRegistration = "passwordlessregistration"
tmplPasswordlessRegistrationDone = "passwordlessregistrationdone"
)
type passwordlessRegistrationData struct {
webAuthNData
Code string
CodeID string
UserID string
OrgID string
Disabled bool
Code string
CodeID string
UserID string
OrgID string
RequestPlatformType authPlatform
Disabled bool
}
type passwordlessRegistrationDoneDate struct {
userData
HideNextButton bool
}
type passwordlessRegistrationFormData struct {
webAuthNFormData
Code string `schema:"code"`
CodeID string `schema:"codeID"`
UserID string `schema:"userID"`
OrgID string `schema:"orgID"`
passwordlessRegistrationQueries
TokenName string `schema:"name"`
}
func (l *Login) handlePasswordlessRegistration(w http.ResponseWriter, r *http.Request) {
userID := r.FormValue(queryPasswordlessRegistrationUserID)
orgID := r.FormValue(queryPasswordlessRegistrationOrgID)
codeID := r.FormValue(queryPasswordlessRegistrationCodeID)
code := r.FormValue(queryPasswordlessRegistrationCode)
l.renderPasswordlessRegistration(w, r, nil, userID, orgID, codeID, code, nil)
type passwordlessRegistrationQueries struct {
Code string `schema:"code"`
CodeID string `schema:"codeID"`
UserID string `schema:"userID"`
OrgID string `schema:"orgID"`
RequestPlatformType authPlatform `schema:"requestPlatformType"`
}
func (l *Login) renderPasswordlessRegistration(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, orgID, codeID, code string, err error) {
type authPlatform domain.AuthenticatorAttachment
func (a authPlatform) MarshalText() (text []byte, err error) {
switch domain.AuthenticatorAttachment(a) {
case domain.AuthenticatorAttachmentPlattform:
return []byte("platform"), nil
case domain.AuthenticatorAttachmentCrossPlattform:
return []byte("crossPlatform"), nil
default:
return []byte("unspecified"), nil
}
}
func (a *authPlatform) UnmarshalText(text []byte) (err error) {
switch string(text) {
case "platform",
"1":
*a = authPlatform(domain.AuthenticatorAttachmentPlattform)
case "crossPlatform",
"2":
*a = authPlatform(domain.AuthenticatorAttachmentCrossPlattform)
}
return nil
}
func (l *Login) handlePasswordlessRegistration(w http.ResponseWriter, r *http.Request) {
queries := new(passwordlessRegistrationQueries)
err := l.parser.Parse(r, queries)
l.renderPasswordlessRegistration(w, r, nil, queries.UserID, queries.OrgID, queries.CodeID, queries.Code, queries.RequestPlatformType, err)
}
func (l *Login) renderPasswordlessRegistration(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, orgID, codeID, code string, requestedPlatformType authPlatform, err error) {
var errID, errMessage, credentialData string
var disabled bool
if authReq != nil {
@ -53,9 +85,9 @@ func (l *Login) renderPasswordlessRegistration(w http.ResponseWriter, r *http.Re
var webAuthNToken *domain.WebAuthNToken
if err == nil {
if authReq != nil {
webAuthNToken, err = l.authRepo.BeginPasswordlessSetup(setContext(r.Context(), authReq.UserOrgID), userID, authReq.UserOrgID)
webAuthNToken, err = l.authRepo.BeginPasswordlessSetup(setContext(r.Context(), authReq.UserOrgID), userID, authReq.UserOrgID, domain.AuthenticatorAttachment(requestedPlatformType))
} else {
webAuthNToken, err = l.authRepo.BeginPasswordlessInitCodeSetup(setContext(r.Context(), orgID), userID, orgID, codeID, code)
webAuthNToken, err = l.authRepo.BeginPasswordlessInitCodeSetup(setContext(r.Context(), orgID), userID, orgID, codeID, code, domain.AuthenticatorAttachment(requestedPlatformType))
}
}
if err != nil {
@ -74,21 +106,21 @@ func (l *Login) renderPasswordlessRegistration(w http.ResponseWriter, r *http.Re
codeID,
userID,
orgID,
requestedPlatformType,
disabled,
}
translator := l.getTranslator(authReq)
if authReq == nil {
policy, err := l.authRepo.GetLabelPolicy(r.Context(), orgID)
if err != nil {
}
logging.Log("LOGIN-afgr2").OnError(err).Warn("could not get label policy")
data.LabelPolicy = policy
texts, err := l.authRepo.GetLoginText(r.Context(), orgID)
if err != nil {
translator, err = l.renderer.NewTranslator()
if err == nil {
texts, err := l.authRepo.GetLoginText(r.Context(), orgID)
logging.Log("LOGIN-HJK4t").OnError(err).Warn("could not get custom texts")
l.addLoginTranslations(translator, texts)
}
translator, _ = l.renderer.NewTranslator()
l.addLoginTranslations(translator, texts)
}
l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplPasswordlessRegistration], data, nil)
}
@ -100,13 +132,13 @@ func (l *Login) handlePasswordlessRegistrationCheck(w http.ResponseWriter, r *ht
l.renderError(w, r, authReq, err)
return
}
l.checkPasswordlessRegistration(w, r, authReq, formData, nil)
l.checkPasswordlessRegistration(w, r, authReq, formData)
}
func (l *Login) checkPasswordlessRegistration(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, formData *passwordlessRegistrationFormData, err error) {
func (l *Login) checkPasswordlessRegistration(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, formData *passwordlessRegistrationFormData) {
credData, err := base64.URLEncoding.DecodeString(formData.CredentialData)
if err != nil {
l.renderPasswordlessRegistration(w, r, authReq, formData.UserID, formData.OrgID, formData.CodeID, formData.Code, err)
l.renderPasswordlessRegistration(w, r, authReq, formData.UserID, formData.OrgID, formData.CodeID, formData.Code, formData.RequestPlatformType, err)
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
@ -116,7 +148,7 @@ func (l *Login) checkPasswordlessRegistration(w http.ResponseWriter, r *http.Req
err = l.authRepo.VerifyPasswordlessInitCodeSetup(setContext(r.Context(), formData.OrgID), formData.UserID, formData.OrgID, userAgentID, formData.TokenName, formData.CodeID, formData.Code, credData)
}
if err != nil {
l.renderPasswordlessRegistration(w, r, authReq, formData.UserID, formData.OrgID, formData.CodeID, formData.Code, err)
l.renderPasswordlessRegistration(w, r, authReq, formData.UserID, formData.OrgID, formData.CodeID, formData.Code, formData.RequestPlatformType, err)
return
}
l.renderPasswordlessRegistrationDone(w, r, authReq, nil)
@ -127,6 +159,9 @@ func (l *Login) renderPasswordlessRegistrationDone(w http.ResponseWriter, r *htt
if err != nil {
errID, errMessage = l.getErrorMessage(r, err)
}
data := l.getUserData(r, authReq, "Passwordless Registration Done", errID, errMessage)
data := passwordlessRegistrationDoneDate{
userData: l.getUserData(r, authReq, "Passwordless Registration Done", errID, errMessage),
HideNextButton: authReq == nil,
}
l.renderer.RenderTemplate(w, r, l.getTranslator(authReq), l.renderer.Templates[tmplPasswordlessRegistrationDone], data, nil)
}

View File

@ -153,6 +153,7 @@ PasswordlessRegistration:
PasswordlessRegistrationDone:
Title: Passwortloser Login erstellt
Description: Token für passwortlosen Login erfolgreich hinzugefügt.
DescriptionClose: Du kannst das Fenster nun schliessen.
NextButtonText: weiter
CancelButtonText: abbrechen

View File

@ -153,6 +153,7 @@ PasswordlessRegistration:
PasswordlessRegistrationDone:
Title: Passwordless set up
Description: Token for passwordless successfully added.
DescriptionClose: You can now close this window.
NextButtonText: next
CancelButtonText: cancel

View File

@ -6,4 +6,8 @@
margin-right: .5rem;
font-size: 1.5rem;
}
}
}
#wa-error {
flex-direction: column;
}

View File

@ -537,6 +537,10 @@ a.block {
font-size: 1.5rem;
}
#wa-error {
flex-direction: column;
}
.lgn-qrcode {
display: block;
margin: auto;
@ -1426,6 +1430,10 @@ a.block {
font-size: 1.5rem;
}
#wa-error {
flex-direction: column;
}
.lgn-qrcode {
display: block;
margin: auto;

File diff suppressed because one or more lines are too long

View File

@ -17,6 +17,7 @@
<input type="hidden" name="orgID" value="{{ .OrgID }}" />
<input type="hidden" name="codeID" value="{{ .CodeID }}" />
<input type="hidden" name="code" value="{{ .Code }}" />
<input type="hidden" name="requestPlatformType" value="{{ .RequestPlatformType }}" />
<input type="hidden" name="credentialCreationData" value="{{ .CredentialCreationData }}" />
<input type="hidden" name="credentialData" />

View File

@ -6,6 +6,9 @@
{{ template "user-profile" . }}
<p>{{t "PasswordlessRegistrationDone.Description"}}</p>
{{if .HideNextButton }}
<p>{{t "PasswordlessRegistrationDone.DescriptionClose"}}</p>
{{end}}
</div>
<form action="{{ loginUrl }}" method="POST">
@ -15,13 +18,11 @@
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<div class="lgn-actions">
<a class="lgn-stroked-button lgn-primary" href="{{ loginUrl }}">
{{t "PasswordlessRegistrationDone.CancelButtonText"}}
</a>
<span class="fill-space"></span>
{{if not .HideNextButton }}
<button class="lgn-raised-button lgn-primary" type="submit">{{t "PasswordlessRegistrationDone.NextButtonText"}}</button>
{{end}}
</div>
</form>
{{template "main-bottom" .}}
{{template "main-bottom" .}}

View File

@ -411,4 +411,5 @@ message PasswordlessRegistrationDoneScreenText {
string description = 2 [(validate.rules).string = {max_len: 500}];
string next_button_text = 3 [(validate.rules).string = {max_len: 100}];
string cancel_button_text = 4 [(validate.rules).string = {max_len: 100}];
}
string description_close = 5 [(validate.rules).string = {max_len: 100}];
}