feat: invite user link (#8578)

# Which Problems Are Solved

As an administrator I want to be able to invite users to my application
with the API V2, some user data I will already prefil, the user should
add the authentication method themself (password, passkey, sso).

# How the Problems Are Solved

- A user can now be created with a email explicitly set to false.
- If a user has no verified email and no authentication method, an
`InviteCode` can be created through the User V2 API.
  - the code can be returned or sent through email
- additionally `URLTemplate` and an `ApplicatioName` can provided for
the email
- The code can be resent and verified through the User V2 API
- The V1 login allows users to verify and resend the code and set a
password (analog user initialization)
- The message text for the user invitation can be customized

# Additional Changes

- `verifyUserPasskeyCode` directly uses `crypto.VerifyCode` (instead of
`verifyEncryptedCode`)
- `verifyEncryptedCode` is removed (unnecessarily queried for the code
generator)

# Additional Context

- closes #8310
- TODO: login V2 will have to implement invite flow:
https://github.com/zitadel/typescript/issues/166
This commit is contained in:
Livio Spring
2024-09-11 12:53:55 +02:00
committed by GitHub
parent 02c78a19c6
commit a07b2f4677
114 changed files with 3898 additions and 293 deletions

View File

@@ -143,7 +143,7 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
require.NoError(t, err)
userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
idGenerator id.Generator
}
type args struct {
@@ -163,7 +163,7 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
{
name: "code verification error",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusherWithCreationDateNow(
user.NewHumanPasswordlessInitCodeRequestedEvent(context.Background(),
@@ -174,7 +174,6 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
user.NewHumanPasswordlessInitCodeSentEvent(ctx, userAgg, "123"),
),
),
expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))),
expectPush(
user.NewHumanPasswordlessInitCodeCheckFailedEvent(ctx, userAgg, "123"),
),
@@ -192,7 +191,7 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
{
name: "code verification ok, get human passwordless error",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusherWithCreationDateNow(
user.NewHumanPasswordlessInitCodeRequestedEvent(context.Background(),
@@ -203,7 +202,6 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
user.NewHumanPasswordlessInitCodeSentEvent(ctx, userAgg, "123"),
),
),
expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))),
expectFilterError(io.ErrClosedPipe),
),
},
@@ -220,7 +218,7 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
webauthnConfig: webauthnConfig,
}
@@ -242,7 +240,7 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
userID string
@@ -260,7 +258,7 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
{
name: "filter error",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilterError(io.ErrClosedPipe),
),
},
@@ -274,7 +272,7 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
{
name: "code verification error",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusherWithCreationDateNow(
user.NewHumanPasswordlessInitCodeRequestedEvent(context.Background(),
@@ -285,7 +283,6 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
user.NewHumanPasswordlessInitCodeSentEvent(ctx, userAgg, "123"),
),
),
expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))),
expectPush(
user.NewHumanPasswordlessInitCodeCheckFailedEvent(ctx, userAgg, "123"),
),
@@ -302,7 +299,7 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
{
name: "success",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusherWithCreationDateNow(
user.NewHumanPasswordlessInitCodeRequestedEvent(context.Background(),
@@ -313,7 +310,6 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
user.NewHumanPasswordlessInitCodeSentEvent(ctx, userAgg, "123"),
),
),
expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))),
),
},
args: args{
@@ -328,7 +324,7 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
got, err := c.verifyUserPasskeyCode(ctx, tt.args.userID, tt.args.resourceOwner, tt.args.codeID, tt.args.code, alg)
require.ErrorIs(t, err, tt.wantErr)