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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
114 changed files with 3898 additions and 293 deletions

View File

@ -712,6 +712,13 @@ DefaultInstance:
IncludeUpperLetters: false # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_OTPEMAIL_INCLUDEUPPERLETTERS IncludeUpperLetters: false # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_OTPEMAIL_INCLUDEUPPERLETTERS
IncludeDigits: true # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_OTPEMAIL_INCLUDEDIGITS IncludeDigits: true # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_OTPEMAIL_INCLUDEDIGITS
IncludeSymbols: false # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_OTPEMAIL_INCLUDESYMBOLS IncludeSymbols: false # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_OTPEMAIL_INCLUDESYMBOLS
InviteCode:
Length: 6 # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_INITIALIZEUSERCODE_LENGTH
Expiry: "72h" # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_INITIALIZEUSERCODE_EXPIRY
IncludeLowerLetters: false # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_INITIALIZEUSERCODE_INCLUDELOWERLETTERS
IncludeUpperLetters: true # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_INITIALIZEUSERCODE_INCLUDEUPPERLETTERS
IncludeDigits: true # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_INITIALIZEUSERCODE_INCLUDEDIGITS
IncludeSymbols: false # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_INITIALIZEUSERCODE_INCLUDESYMBOLS
PasswordComplexityPolicy: PasswordComplexityPolicy:
MinLength: 8 # ZITADEL_DEFAULTINSTANCE_PASSWORDCOMPLEXITYPOLICY_MINLENGTH MinLength: 8 # ZITADEL_DEFAULTINSTANCE_PASSWORDCOMPLEXITYPOLICY_MINLENGTH
HasLowercase: true # ZITADEL_DEFAULTINSTANCE_PASSWORDCOMPLEXITYPOLICY_HASLOWERCASE HasLowercase: true # ZITADEL_DEFAULTINSTANCE_PASSWORDCOMPLEXITYPOLICY_HASLOWERCASE

View File

@ -7,6 +7,7 @@ import {
GetDefaultInitMessageTextRequest as AdminGetDefaultInitMessageTextRequest, GetDefaultInitMessageTextRequest as AdminGetDefaultInitMessageTextRequest,
GetDefaultPasswordChangeMessageTextRequest as AdminGetDefaultPasswordChangeMessageTextRequest, GetDefaultPasswordChangeMessageTextRequest as AdminGetDefaultPasswordChangeMessageTextRequest,
GetDefaultPasswordlessRegistrationMessageTextRequest as AdminGetDefaultPasswordlessRegistrationMessageTextRequest, GetDefaultPasswordlessRegistrationMessageTextRequest as AdminGetDefaultPasswordlessRegistrationMessageTextRequest,
GetDefaultInviteUserMessageTextRequest as AdminGetDefaultInviteUserMessageTextRequest,
GetDefaultPasswordResetMessageTextRequest as AdminGetDefaultPasswordResetMessageTextRequest, GetDefaultPasswordResetMessageTextRequest as AdminGetDefaultPasswordResetMessageTextRequest,
GetDefaultVerifyEmailMessageTextRequest as AdminGetDefaultVerifyEmailMessageTextRequest, GetDefaultVerifyEmailMessageTextRequest as AdminGetDefaultVerifyEmailMessageTextRequest,
GetDefaultVerifyEmailOTPMessageTextRequest as AdminGetDefaultVerifyEmailOTPMessageTextRequest, GetDefaultVerifyEmailOTPMessageTextRequest as AdminGetDefaultVerifyEmailOTPMessageTextRequest,
@ -16,6 +17,7 @@ import {
SetDefaultInitMessageTextRequest, SetDefaultInitMessageTextRequest,
SetDefaultPasswordChangeMessageTextRequest, SetDefaultPasswordChangeMessageTextRequest,
SetDefaultPasswordlessRegistrationMessageTextRequest, SetDefaultPasswordlessRegistrationMessageTextRequest,
SetDefaultInviteUserMessageTextRequest,
SetDefaultPasswordResetMessageTextRequest, SetDefaultPasswordResetMessageTextRequest,
SetDefaultVerifyEmailMessageTextRequest, SetDefaultVerifyEmailMessageTextRequest,
SetDefaultVerifyEmailOTPMessageTextRequest, SetDefaultVerifyEmailOTPMessageTextRequest,
@ -27,6 +29,7 @@ import {
GetCustomInitMessageTextRequest, GetCustomInitMessageTextRequest,
GetCustomPasswordChangeMessageTextRequest, GetCustomPasswordChangeMessageTextRequest,
GetCustomPasswordlessRegistrationMessageTextRequest, GetCustomPasswordlessRegistrationMessageTextRequest,
GetCustomInviteUserMessageTextRequest,
GetCustomPasswordResetMessageTextRequest, GetCustomPasswordResetMessageTextRequest,
GetCustomVerifyEmailMessageTextRequest, GetCustomVerifyEmailMessageTextRequest,
GetCustomVerifyEmailOTPMessageTextRequest, GetCustomVerifyEmailOTPMessageTextRequest,
@ -36,6 +39,7 @@ import {
GetDefaultInitMessageTextRequest, GetDefaultInitMessageTextRequest,
GetDefaultPasswordChangeMessageTextRequest, GetDefaultPasswordChangeMessageTextRequest,
GetDefaultPasswordlessRegistrationMessageTextRequest, GetDefaultPasswordlessRegistrationMessageTextRequest,
GetDefaultInviteUserMessageTextRequest,
GetDefaultPasswordResetMessageTextRequest, GetDefaultPasswordResetMessageTextRequest,
GetDefaultVerifyEmailMessageTextRequest, GetDefaultVerifyEmailMessageTextRequest,
GetDefaultVerifyEmailOTPMessageTextRequest, GetDefaultVerifyEmailOTPMessageTextRequest,
@ -45,6 +49,7 @@ import {
SetCustomInitMessageTextRequest, SetCustomInitMessageTextRequest,
SetCustomPasswordChangeMessageTextRequest, SetCustomPasswordChangeMessageTextRequest,
SetCustomPasswordlessRegistrationMessageTextRequest, SetCustomPasswordlessRegistrationMessageTextRequest,
SetCustomInviteUserMessageTextRequest,
SetCustomPasswordResetMessageTextRequest, SetCustomPasswordResetMessageTextRequest,
SetCustomVerifyEmailMessageTextRequest, SetCustomVerifyEmailMessageTextRequest,
SetCustomVerifyEmailOTPMessageTextRequest, SetCustomVerifyEmailOTPMessageTextRequest,
@ -73,6 +78,7 @@ enum MESSAGETYPES {
PASSWORDCHANGE = 'PC', PASSWORDCHANGE = 'PC',
VERIFYSMSOTP = 'VSO', VERIFYSMSOTP = 'VSO',
VERIFYEMAILOTP = 'VEO', VERIFYEMAILOTP = 'VEO',
INVITEUSER = 'IU',
} }
const REQUESTMAP = { const REQUESTMAP = {
@ -226,6 +232,23 @@ const REQUESTMAP = {
req.setText(map.text ?? ''); req.setText(map.text ?? '');
req.setTitle(map.title ?? ''); req.setTitle(map.title ?? '');
return req;
},
},
[MESSAGETYPES.INVITEUSER]: {
get: new GetCustomInviteUserMessageTextRequest(),
set: new SetCustomInviteUserMessageTextRequest(),
getDefault: new GetDefaultInviteUserMessageTextRequest(),
setFcn: (map: Partial<SetCustomInviteUserMessageTextRequest.AsObject>): SetCustomInviteUserMessageTextRequest => {
const req = new SetCustomInviteUserMessageTextRequest();
req.setButtonText(map.buttonText ?? '');
req.setFooterText(map.footerText ?? '');
req.setGreeting(map.greeting ?? '');
req.setPreHeader(map.preHeader ?? '');
req.setSubject(map.subject ?? '');
req.setText(map.text ?? '');
req.setTitle(map.title ?? '');
return req; return req;
}, },
}, },
@ -371,6 +394,22 @@ const REQUESTMAP = {
req.setText(map.text ?? ''); req.setText(map.text ?? '');
req.setTitle(map.title ?? ''); req.setTitle(map.title ?? '');
return req;
},
},
[MESSAGETYPES.INVITEUSER]: {
get: new AdminGetDefaultInviteUserMessageTextRequest(),
set: new SetDefaultInviteUserMessageTextRequest(),
setFcn: (map: Partial<SetDefaultInviteUserMessageTextRequest.AsObject>): SetDefaultInviteUserMessageTextRequest => {
const req = new SetDefaultInviteUserMessageTextRequest();
req.setButtonText(map.buttonText ?? '');
req.setFooterText(map.footerText ?? '');
req.setGreeting(map.greeting ?? '');
req.setPreHeader(map.preHeader ?? '');
req.setSubject(map.subject ?? '');
req.setText(map.text ?? '');
req.setTitle(map.title ?? '');
return req; return req;
}, },
}, },
@ -540,6 +579,21 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.loginnames', value: '{{.LoginNames}}' }, { key: 'POLICY.MESSAGE_TEXTS.CHIPS.loginnames', value: '{{.LoginNames}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.changedate', value: '{{.ChangeDate}}' }, { key: 'POLICY.MESSAGE_TEXTS.CHIPS.changedate', value: '{{.ChangeDate}}' },
], ],
[MESSAGETYPES.INVITEUSER]: [
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.preferredLoginName', value: '{{.PreferredLoginName}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.username', value: '{{.UserName}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.firstname', value: '{{.FirstName}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.lastname', value: '{{.LastName}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.nickName', value: '{{.NickName}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.displayName', value: '{{.DisplayName}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.lastEmail', value: '{{.LastEmail}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.verifiedEmail', value: '{{.VerifiedEmail}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.lastPhone', value: '{{.LastPhone}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.verifiedPhone', value: '{{.VerifiedPhone}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.loginnames', value: '{{.LoginNames}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.changedate', value: '{{.ChangeDate}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.applicationName', value: '{{.ApplicationName}}' },
],
}; };
public language: string = 'en'; public language: string = 'en';
@ -599,6 +653,8 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
return this.stripEmail(this.service.getDefaultPasswordlessRegistrationMessageText(req)); return this.stripEmail(this.service.getDefaultPasswordlessRegistrationMessageText(req));
case MESSAGETYPES.PASSWORDCHANGE: case MESSAGETYPES.PASSWORDCHANGE:
return this.stripEmail(this.service.getDefaultPasswordChangeMessageText(req)); return this.stripEmail(this.service.getDefaultPasswordChangeMessageText(req));
case MESSAGETYPES.INVITEUSER:
return this.stripEmail(this.service.getDefaultInviteUserMessageText(req));
} }
} }
@ -622,6 +678,8 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
return this.stripEmail(this.service.getCustomPasswordlessRegistrationMessageText(req)); return this.stripEmail(this.service.getCustomPasswordlessRegistrationMessageText(req));
case MESSAGETYPES.PASSWORDCHANGE: case MESSAGETYPES.PASSWORDCHANGE:
return this.stripEmail(this.service.getCustomPasswordChangeMessageText(req)); return this.stripEmail(this.service.getCustomPasswordChangeMessageText(req));
case MESSAGETYPES.INVITEUSER:
return this.stripEmail(this.service.getCustomInviteUserMessageText(req));
default: default:
return undefined; return undefined;
} }
@ -690,6 +748,8 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
); );
case MESSAGETYPES.PASSWORDCHANGE: case MESSAGETYPES.PASSWORDCHANGE:
return handler((this.service as ManagementService).setCustomPasswordChangeMessageText(this.updateRequest)); return handler((this.service as ManagementService).setCustomPasswordChangeMessageText(this.updateRequest));
case MESSAGETYPES.INVITEUSER:
return handler((this.service as ManagementService).setCustomInviteUserMessageText(this.updateRequest));
} }
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) { } else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
switch (this.currentType) { switch (this.currentType) {
@ -711,6 +771,8 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
return handler((this.service as AdminService).setDefaultPasswordlessRegistrationMessageText(this.updateRequest)); return handler((this.service as AdminService).setDefaultPasswordlessRegistrationMessageText(this.updateRequest));
case MESSAGETYPES.PASSWORDCHANGE: case MESSAGETYPES.PASSWORDCHANGE:
return handler((this.service as AdminService).setDefaultPasswordChangeMessageText(this.updateRequest)); return handler((this.service as AdminService).setDefaultPasswordChangeMessageText(this.updateRequest));
case MESSAGETYPES.INVITEUSER:
return handler((this.service as AdminService).setDefaultInviteUserMessageText(this.updateRequest));
} }
} }
} }
@ -763,6 +825,8 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
return handler(this.service.resetCustomPasswordlessRegistrationMessageTextToDefault(this.language)); return handler(this.service.resetCustomPasswordlessRegistrationMessageTextToDefault(this.language));
case MESSAGETYPES.PASSWORDCHANGE: case MESSAGETYPES.PASSWORDCHANGE:
return handler(this.service.resetCustomPasswordChangeMessageTextToDefault(this.language)); return handler(this.service.resetCustomPasswordChangeMessageTextToDefault(this.language));
case MESSAGETYPES.INVITEUSER:
return handler(this.service.resetCustomInviteUserMessageTextToDefault(this.language));
default: default:
return Promise.reject(); return Promise.reject();
} }

View File

@ -72,6 +72,8 @@ import {
GetCustomPasswordChangeMessageTextResponse, GetCustomPasswordChangeMessageTextResponse,
GetCustomPasswordlessRegistrationMessageTextRequest, GetCustomPasswordlessRegistrationMessageTextRequest,
GetCustomPasswordlessRegistrationMessageTextResponse, GetCustomPasswordlessRegistrationMessageTextResponse,
GetCustomInviteUserMessageTextRequest,
GetCustomInviteUserMessageTextResponse,
GetCustomPasswordResetMessageTextRequest, GetCustomPasswordResetMessageTextRequest,
GetCustomPasswordResetMessageTextResponse, GetCustomPasswordResetMessageTextResponse,
GetCustomVerifyEmailMessageTextRequest, GetCustomVerifyEmailMessageTextRequest,
@ -96,6 +98,8 @@ import {
GetDefaultPasswordChangeMessageTextResponse, GetDefaultPasswordChangeMessageTextResponse,
GetDefaultPasswordlessRegistrationMessageTextRequest, GetDefaultPasswordlessRegistrationMessageTextRequest,
GetDefaultPasswordlessRegistrationMessageTextResponse, GetDefaultPasswordlessRegistrationMessageTextResponse,
GetDefaultInviteUserMessageTextRequest,
GetDefaultInviteUserMessageTextResponse,
GetDefaultPasswordResetMessageTextRequest, GetDefaultPasswordResetMessageTextRequest,
GetDefaultPasswordResetMessageTextResponse, GetDefaultPasswordResetMessageTextResponse,
GetDefaultVerifyEmailMessageTextRequest, GetDefaultVerifyEmailMessageTextRequest,
@ -224,6 +228,8 @@ import {
SetDefaultPasswordChangeMessageTextResponse, SetDefaultPasswordChangeMessageTextResponse,
SetDefaultPasswordlessRegistrationMessageTextRequest, SetDefaultPasswordlessRegistrationMessageTextRequest,
SetDefaultPasswordlessRegistrationMessageTextResponse, SetDefaultPasswordlessRegistrationMessageTextResponse,
SetDefaultInviteUserMessageTextRequest,
SetDefaultInviteUserMessageTextResponse,
SetDefaultPasswordResetMessageTextRequest, SetDefaultPasswordResetMessageTextRequest,
SetDefaultPasswordResetMessageTextResponse, SetDefaultPasswordResetMessageTextResponse,
SetDefaultVerifyEmailMessageTextRequest, SetDefaultVerifyEmailMessageTextRequest,
@ -311,6 +317,8 @@ import {
ResetCustomPasswordChangeMessageTextToDefaultResponse, ResetCustomPasswordChangeMessageTextToDefaultResponse,
ResetCustomPasswordlessRegistrationMessageTextToDefaultRequest, ResetCustomPasswordlessRegistrationMessageTextToDefaultRequest,
ResetCustomPasswordlessRegistrationMessageTextToDefaultResponse, ResetCustomPasswordlessRegistrationMessageTextToDefaultResponse,
ResetCustomInviteUserMessageTextToDefaultRequest,
ResetCustomInviteUserMessageTextToDefaultResponse,
ResetCustomPasswordResetMessageTextToDefaultRequest, ResetCustomPasswordResetMessageTextToDefaultRequest,
ResetCustomPasswordResetMessageTextToDefaultResponse, ResetCustomPasswordResetMessageTextToDefaultResponse,
ResetCustomVerifyEmailMessageTextToDefaultRequest, ResetCustomVerifyEmailMessageTextToDefaultRequest,
@ -722,6 +730,32 @@ export class AdminService {
return this.grpcService.admin.resetCustomPasswordChangeMessageTextToDefault(req, null).then((resp) => resp.toObject()); return this.grpcService.admin.resetCustomPasswordChangeMessageTextToDefault(req, null).then((resp) => resp.toObject());
} }
public getDefaultInviteUserMessageText(
req: GetDefaultInviteUserMessageTextRequest,
): Promise<GetDefaultInviteUserMessageTextResponse.AsObject> {
return this.grpcService.admin.getDefaultInviteUserMessageText(req, null).then((resp) => resp.toObject());
}
public getCustomInviteUserMessageText(
req: GetCustomInviteUserMessageTextRequest,
): Promise<GetCustomInviteUserMessageTextResponse.AsObject> {
return this.grpcService.admin.getCustomInviteUserMessageText(req, null).then((resp) => resp.toObject());
}
public setDefaultInviteUserMessageText(
req: SetDefaultInviteUserMessageTextRequest,
): Promise<SetDefaultInviteUserMessageTextResponse.AsObject> {
return this.grpcService.admin.setDefaultInviteUserMessageText(req, null).then((resp) => resp.toObject());
}
public resetCustomInviteUserMessageTextToDefault(
lang: string,
): Promise<ResetCustomInviteUserMessageTextToDefaultResponse.AsObject> {
const req = new ResetCustomInviteUserMessageTextToDefaultRequest();
req.setLanguage(lang);
return this.grpcService.admin.resetCustomInviteUserMessageTextToDefault(req, null).then((resp) => resp.toObject());
}
public SetUpOrg(org: SetUpOrgRequest.Org, human: SetUpOrgRequest.Human): Promise<SetUpOrgResponse.AsObject> { public SetUpOrg(org: SetUpOrgRequest.Org, human: SetUpOrgRequest.Human): Promise<SetUpOrgResponse.AsObject> {
const req = new SetUpOrgRequest(); const req = new SetUpOrgRequest();

View File

@ -133,6 +133,8 @@ import {
GetCustomLoginTextsResponse, GetCustomLoginTextsResponse,
GetCustomPasswordChangeMessageTextRequest, GetCustomPasswordChangeMessageTextRequest,
GetCustomPasswordChangeMessageTextResponse, GetCustomPasswordChangeMessageTextResponse,
GetCustomInviteUserMessageTextRequest,
GetCustomInviteUserMessageTextResponse,
GetCustomPasswordlessRegistrationMessageTextRequest, GetCustomPasswordlessRegistrationMessageTextRequest,
GetCustomPasswordlessRegistrationMessageTextResponse, GetCustomPasswordlessRegistrationMessageTextResponse,
GetCustomPasswordResetMessageTextRequest, GetCustomPasswordResetMessageTextRequest,
@ -155,6 +157,8 @@ import {
GetDefaultLoginTextsResponse, GetDefaultLoginTextsResponse,
GetDefaultPasswordChangeMessageTextRequest, GetDefaultPasswordChangeMessageTextRequest,
GetDefaultPasswordChangeMessageTextResponse, GetDefaultPasswordChangeMessageTextResponse,
GetDefaultInviteUserMessageTextRequest,
GetDefaultInviteUserMessageTextResponse,
GetDefaultPasswordComplexityPolicyRequest, GetDefaultPasswordComplexityPolicyRequest,
GetDefaultPasswordComplexityPolicyResponse, GetDefaultPasswordComplexityPolicyResponse,
GetDefaultPasswordlessRegistrationMessageTextRequest, GetDefaultPasswordlessRegistrationMessageTextRequest,
@ -386,6 +390,8 @@ import {
ResetCustomLoginTextsToDefaultResponse, ResetCustomLoginTextsToDefaultResponse,
ResetCustomPasswordChangeMessageTextToDefaultRequest, ResetCustomPasswordChangeMessageTextToDefaultRequest,
ResetCustomPasswordChangeMessageTextToDefaultResponse, ResetCustomPasswordChangeMessageTextToDefaultResponse,
ResetCustomInviteUserMessageTextToDefaultRequest,
ResetCustomInviteUserMessageTextToDefaultResponse,
ResetCustomPasswordlessRegistrationMessageTextToDefaultRequest, ResetCustomPasswordlessRegistrationMessageTextToDefaultRequest,
ResetCustomPasswordlessRegistrationMessageTextToDefaultResponse, ResetCustomPasswordlessRegistrationMessageTextToDefaultResponse,
ResetCustomPasswordResetMessageTextToDefaultRequest, ResetCustomPasswordResetMessageTextToDefaultRequest,
@ -423,6 +429,8 @@ import {
SetCustomLoginTextsResponse, SetCustomLoginTextsResponse,
SetCustomPasswordChangeMessageTextRequest, SetCustomPasswordChangeMessageTextRequest,
SetCustomPasswordChangeMessageTextResponse, SetCustomPasswordChangeMessageTextResponse,
SetCustomInviteUserMessageTextRequest,
SetCustomInviteUserMessageTextResponse,
SetCustomPasswordlessRegistrationMessageTextRequest, SetCustomPasswordlessRegistrationMessageTextRequest,
SetCustomPasswordlessRegistrationMessageTextResponse, SetCustomPasswordlessRegistrationMessageTextResponse,
SetCustomPasswordResetMessageTextRequest, SetCustomPasswordResetMessageTextRequest,
@ -804,6 +812,32 @@ export class ManagementService {
return this.grpcService.mgmt.resetCustomPasswordChangeMessageTextToDefault(req, null).then((resp) => resp.toObject()); return this.grpcService.mgmt.resetCustomPasswordChangeMessageTextToDefault(req, null).then((resp) => resp.toObject());
} }
public getDefaultInviteUserMessageText(
req: GetDefaultInviteUserMessageTextRequest,
): Promise<GetDefaultInviteUserMessageTextResponse.AsObject> {
return this.grpcService.mgmt.getDefaultInviteUserMessageText(req, null).then((resp) => resp.toObject());
}
public getCustomInviteUserMessageText(
req: GetCustomInviteUserMessageTextRequest,
): Promise<GetCustomInviteUserMessageTextResponse.AsObject> {
return this.grpcService.mgmt.getCustomInviteUserMessageText(req, null).then((resp) => resp.toObject());
}
public setCustomInviteUserMessageText(
req: SetCustomInviteUserMessageTextRequest,
): Promise<SetCustomInviteUserMessageTextResponse.AsObject> {
return this.grpcService.mgmt.setCustomInviteUserMessageCustomText(req, null).then((resp) => resp.toObject());
}
public resetCustomInviteUserMessageTextToDefault(
lang: string,
): Promise<ResetCustomInviteUserMessageTextToDefaultResponse.AsObject> {
const req = new ResetCustomInviteUserMessageTextToDefaultRequest();
req.setLanguage(lang);
return this.grpcService.mgmt.resetCustomInviteUserMessageTextToDefault(req, null).then((resp) => resp.toObject());
}
public updateUserName(userId: string, username: string): Promise<UpdateUserNameResponse.AsObject> { public updateUserName(userId: string, username: string): Promise<UpdateUserNameResponse.AsObject> {
const req = new UpdateUserNameRequest(); const req = new UpdateUserNameRequest();
req.setUserId(userId); req.setUserId(userId);

View File

@ -197,7 +197,8 @@
"VE": "Когато потребител промени своя имейл адрес, той ще получи имейл с връзка за верифициране на новия адрес.", "VE": "Когато потребител промени своя имейл адрес, той ще получи имейл с връзка за верифициране на новия адрес.",
"VP": "Когато потребител промени своя телефонен номер, той ще получи SMS с код за верификация на новия номер.", "VP": "Когато потребител промени своя телефонен номер, той ще получи SMS с код за верификация на новия номер.",
"VEO": "Когато потребител добави метод за One-Time Password чрез имейл, трябва да го активира, като въведе код, изпратен на неговия имейл адрес.", "VEO": "Когато потребител добави метод за One-Time Password чрез имейл, трябва да го активира, като въведе код, изпратен на неговия имейл адрес.",
"VSO": "Когато потребител добави метод за One-Time Password чрез SMS, трябва да го активира, като въведе код, изпратен на неговия телефонен номер." "VSO": "Когато потребител добави метод за One-Time Password чрез SMS, трябва да го активира, като въведе код, изпратен на неговия телефонен номер.",
"IU": "Когато се създаде покана за потребител, те ще получат имейл с връзка за задаване на своя метод за удостоверяване."
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1666,7 +1667,8 @@
"PR": "Нулиране на парола", "PR": "Нулиране на парола",
"DC": "Заявка за домейн", "DC": "Заявка за домейн",
"PL": "Без парола", "PL": "Без парола",
"PC": "Промяна на паролата" "PC": "Промяна на паролата",
"IU": "Покана за потребител"
}, },
"CHIPS": { "CHIPS": {
"firstname": "Първо име", "firstname": "Първо име",
@ -1686,7 +1688,8 @@
"tempUsername": "Временно потребителско име", "tempUsername": "Временно потребителско име",
"otp": "Еднократна парола", "otp": "Еднократна парола",
"verifyUrl": "URL за потвърждаване на еднократна парола", "verifyUrl": "URL за потвърждаване на еднократна парола",
"expiry": "Изтичане" "expiry": "Изтичане",
"applicationName": "Името на приложението"
}, },
"TOAST": { "TOAST": {
"UPDATED": "Персонализираните текстове са запазени." "UPDATED": "Персонализираните текстове са запазени."

View File

@ -197,7 +197,8 @@
"VE": "Když uživatel změní svou e-mailovou adresu, obdrží e-mail s odkazem na ověření nové adresy.", "VE": "Když uživatel změní svou e-mailovou adresu, obdrží e-mail s odkazem na ověření nové adresy.",
"VP": "Když uživatel změní své telefonní číslo, obdrží SMS s kódem pro ověření nového čísla.", "VP": "Když uživatel změní své telefonní číslo, obdrží SMS s kódem pro ověření nového čísla.",
"VEO": "Když uživatel přidá metodu jednorázového hesla přes e-mail, musí ji aktivovat zadáním kódu poslaného na jeho e-mailovou adresu.", "VEO": "Když uživatel přidá metodu jednorázového hesla přes e-mail, musí ji aktivovat zadáním kódu poslaného na jeho e-mailovou adresu.",
"VSO": "Když uživatel přidá metodu jednorázového hesla přes SMS, musí ji aktivovat zadáním kódu poslaného na jeho telefonní číslo." "VSO": "Když uživatel přidá metodu jednorázového hesla přes SMS, musí ji aktivovat zadáním kódu poslaného na jeho telefonní číslo.",
"IU": "Když se vytvoří pozvánka pro uživatele, obdrží e-mail s odkazem na nastavení své metody ověřování."
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1667,7 +1668,8 @@
"PR": "Reset hesla", "PR": "Reset hesla",
"DC": "Nárok na doménu", "DC": "Nárok na doménu",
"PL": "Bezheslový", "PL": "Bezheslový",
"PC": "Změna hesla" "PC": "Změna hesla",
"IU": "Pozvat uživatele"
}, },
"CHIPS": { "CHIPS": {
"firstname": "Křestní jméno", "firstname": "Křestní jméno",
@ -1687,7 +1689,8 @@
"tempUsername": "Dočasné uživatelské jméno", "tempUsername": "Dočasné uživatelské jméno",
"otp": "Jednorázové heslo", "otp": "Jednorázové heslo",
"verifyUrl": "Ověřovací URL jednorázového hesla", "verifyUrl": "Ověřovací URL jednorázového hesla",
"expiry": "Expirace" "expiry": "Expirace",
"applicationName": "Název aplikace"
}, },
"TOAST": { "TOAST": {
"UPDATED": "Vlastní texty uloženy." "UPDATED": "Vlastní texty uloženy."

View File

@ -197,7 +197,8 @@
"VE": "Wenn ein Benutzer seine E-Mail-Adresse ändert, erhält er eine E-Mail mit einem Link zur Verifizierung der neuen Adresse.", "VE": "Wenn ein Benutzer seine E-Mail-Adresse ändert, erhält er eine E-Mail mit einem Link zur Verifizierung der neuen Adresse.",
"VP": "Wenn ein Benutzer seine Telefonnummer ändert, erhält er eine SMS mit einem Code zur Verifizierung der neuen Nummer.", "VP": "Wenn ein Benutzer seine Telefonnummer ändert, erhält er eine SMS mit einem Code zur Verifizierung der neuen Nummer.",
"VEO": "Wenn ein Benutzer eine Einmalpasswort-Methode per E-Mail hinzufügt, muss er sie aktivieren, indem er einen Code eingibt, der an seine E-Mail-Adresse gesendet wurde.", "VEO": "Wenn ein Benutzer eine Einmalpasswort-Methode per E-Mail hinzufügt, muss er sie aktivieren, indem er einen Code eingibt, der an seine E-Mail-Adresse gesendet wurde.",
"VSO": "Wenn ein Benutzer eine Einmalpasswort-Methode per SMS hinzufügt, muss er sie aktivieren, indem er einen Code eingibt, der an seine Telefonnummer gesendet wurde." "VSO": "Wenn ein Benutzer eine Einmalpasswort-Methode per SMS hinzufügt, muss er sie aktivieren, indem er einen Code eingibt, der an seine Telefonnummer gesendet wurde.",
"IU": "Wenn ein Benutzer-Einladungscode erstellt wird, erhält er eine E-Mail mit einem Link zur Einstellung seiner Authentifizierungsmethode."
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1667,7 +1668,8 @@
"PR": "Passwort Wiederherstellung", "PR": "Passwort Wiederherstellung",
"DC": "Domainbeanspruchung", "DC": "Domainbeanspruchung",
"PL": "Passwortlos", "PL": "Passwortlos",
"PC": "Passwordwechsel" "PC": "Passwordwechsel",
"IU": "Benutzer einladen"
}, },
"CHIPS": { "CHIPS": {
"firstname": "Vorname", "firstname": "Vorname",
@ -1687,7 +1689,8 @@
"tempUsername": "Temp. Username", "tempUsername": "Temp. Username",
"otp": "Einmalpasswort", "otp": "Einmalpasswort",
"verifyUrl": "URL zur Überprüfung des Einmalpassworts", "verifyUrl": "URL zur Überprüfung des Einmalpassworts",
"expiry": "Ablauf" "expiry": "Ablauf",
"applicationName": "Anwendungsname"
}, },
"TOAST": { "TOAST": {
"UPDATED": "Benutzerdefinierte Texte gespeichert." "UPDATED": "Benutzerdefinierte Texte gespeichert."

View File

@ -197,7 +197,8 @@
"VE": "When a user changes their email address, they will receive an email with a link to verify the new address.", "VE": "When a user changes their email address, they will receive an email with a link to verify the new address.",
"VP": "When a user changes their phone number, they will receive an SMS with a code to verify the new number.", "VP": "When a user changes their phone number, they will receive an SMS with a code to verify the new number.",
"VEO": "When a user adds a One-Time Password via email method, they need to activate it by entering a code sent to their email address.", "VEO": "When a user adds a One-Time Password via email method, they need to activate it by entering a code sent to their email address.",
"VSO": "When a user adds a One-Time Password via SMS method, they need to activate it by entering a code sent to their phone number." "VSO": "When a user adds a One-Time Password via SMS method, they need to activate it by entering a code sent to their phone number.",
"IU": "When a user invite code is created, they will receive an email with a link to set their authentication method."
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1667,7 +1668,8 @@
"PR": "Password Reset", "PR": "Password Reset",
"DC": "Domain Claim", "DC": "Domain Claim",
"PL": "Passwordless", "PL": "Passwordless",
"PC": "Password Change" "PC": "Password Change",
"IU": "Invite User"
}, },
"CHIPS": { "CHIPS": {
"firstname": "Given name", "firstname": "Given name",
@ -1687,7 +1689,8 @@
"tempUsername": "Temp username", "tempUsername": "Temp username",
"otp": "One-time password", "otp": "One-time password",
"verifyUrl": "Verify One-time-password URL", "verifyUrl": "Verify One-time-password URL",
"expiry": "Expiry" "expiry": "Expiry",
"applicationName": "Application name"
}, },
"TOAST": { "TOAST": {
"UPDATED": "Custom Texts saved." "UPDATED": "Custom Texts saved."

View File

@ -197,7 +197,8 @@
"VE": "Cuando un usuario cambia su dirección de correo electrónico, recibirá un correo electrónico con un enlace para verificar la nueva dirección.", "VE": "Cuando un usuario cambia su dirección de correo electrónico, recibirá un correo electrónico con un enlace para verificar la nueva dirección.",
"VP": "Cuando un usuario cambia su número de teléfono, recibirá un SMS con un código para verificar el nuevo número.", "VP": "Cuando un usuario cambia su número de teléfono, recibirá un SMS con un código para verificar el nuevo número.",
"VEO": "Cuando un usuario agrega una Contraseña de Un Solo Uso mediante correo electrónico, necesita activarla ingresando un código enviado a su dirección de correo electrónico.", "VEO": "Cuando un usuario agrega una Contraseña de Un Solo Uso mediante correo electrónico, necesita activarla ingresando un código enviado a su dirección de correo electrónico.",
"VSO": "Cuando un usuario agrega una Contraseña de Un Solo Uso mediante SMS, necesita activarla ingresando un código enviado a su número de teléfono." "VSO": "Cuando un usuario agrega una Contraseña de Un Solo Uso mediante SMS, necesita activarla ingresando un código enviado a su número de teléfono.",
"IU": "Cuando se crea un código de invitación de usuario, recibirán un correo electrónico con un enlace para configurar su método de autenticación."
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1668,7 +1669,8 @@
"PR": "Restablecimiento de contraseña", "PR": "Restablecimiento de contraseña",
"DC": "Reclamar un dominio", "DC": "Reclamar un dominio",
"PL": "Acceso sin contraseña", "PL": "Acceso sin contraseña",
"PC": "Cambio de contraseña" "PC": "Cambio de contraseña",
"IU": "Invitar usuario"
}, },
"CHIPS": { "CHIPS": {
"firstname": "Nombre", "firstname": "Nombre",
@ -1688,7 +1690,8 @@
"tempUsername": "Nombre de usuario temporal", "tempUsername": "Nombre de usuario temporal",
"otp": "Contraseña de un solo uso", "otp": "Contraseña de un solo uso",
"verifyUrl": "URL para verificar la contraseña de un solo uso", "verifyUrl": "URL para verificar la contraseña de un solo uso",
"expiry": "Expiración" "expiry": "Expiración",
"applicationName": "Nombre de la aplicación"
}, },
"TOAST": { "TOAST": {
"UPDATED": "Textos personalizados guardados." "UPDATED": "Textos personalizados guardados."

View File

@ -197,7 +197,8 @@
"VE": "Lorsqu'un utilisateur change son adresse e-mail, il recevra un e-mail avec un lien pour vérifier la nouvelle adresse.", "VE": "Lorsqu'un utilisateur change son adresse e-mail, il recevra un e-mail avec un lien pour vérifier la nouvelle adresse.",
"VP": "Lorsqu'un utilisateur change son numéro de téléphone, il recevra un SMS avec un code pour vérifier le nouveau numéro.", "VP": "Lorsqu'un utilisateur change son numéro de téléphone, il recevra un SMS avec un code pour vérifier le nouveau numéro.",
"VEO": "Lorsqu'un utilisateur ajoute un Mot de Passe à Usage Unique via e-mail, il doit l'activer en entrant un code envoyé à son adresse e-mail.", "VEO": "Lorsqu'un utilisateur ajoute un Mot de Passe à Usage Unique via e-mail, il doit l'activer en entrant un code envoyé à son adresse e-mail.",
"VSO": "Lorsqu'un utilisateur ajoute un Mot de Passe à Usage Unique via SMS, il doit l'activer en entrant un code envoyé à son numéro de téléphone." "VSO": "Lorsqu'un utilisateur ajoute un Mot de Passe à Usage Unique via SMS, il doit l'activer en entrant un code envoyé à son numéro de téléphone.",
"IU": "Lorsqu'un code d'invitation d'utilisateur est créé, il recevra un e-mail avec un lien pour configurer sa méthode d'authentification."
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1667,7 +1668,8 @@
"PR": "Réinitialisation du mot de passe", "PR": "Réinitialisation du mot de passe",
"DC": "Réclamation de domaine", "DC": "Réclamation de domaine",
"PL": "Sans mot de passe", "PL": "Sans mot de passe",
"PC": "Modification du mot de passe" "PC": "Modification du mot de passe",
"IU": "Inviter un utilisateur"
}, },
"CHIPS": { "CHIPS": {
"firstname": "Prénom", "firstname": "Prénom",
@ -1687,7 +1689,8 @@
"tempUsername": "Nom d'utilisateur temporaire", "tempUsername": "Nom d'utilisateur temporaire",
"otp": "Mot de passe à usage unique", "otp": "Mot de passe à usage unique",
"verifyUrl": "URL pour vérifier le mot de passe à usage unique", "verifyUrl": "URL pour vérifier le mot de passe à usage unique",
"expiry": "Expiration" "expiry": "Expiration",
"applicationName": "Nom de l'application"
}, },
"TOAST": { "TOAST": {
"UPDATED": "Textes personnalisés enregistrés." "UPDATED": "Textes personnalisés enregistrés."

View File

@ -185,7 +185,8 @@
"VE": "Saat pengguna mengubah alamat emailnya, mereka akan menerima email berisi tautan untuk memverifikasi alamat baru.", "VE": "Saat pengguna mengubah alamat emailnya, mereka akan menerima email berisi tautan untuk memverifikasi alamat baru.",
"VP": "Saat pengguna mengganti nomor teleponnya, mereka akan menerima SMS berisi kode untuk memverifikasi nomor baru.", "VP": "Saat pengguna mengganti nomor teleponnya, mereka akan menerima SMS berisi kode untuk memverifikasi nomor baru.",
"VEO": "Ketika pengguna menambahkan Kata Sandi Sekali Pakai melalui metode email, mereka perlu mengaktifkannya dengan memasukkan kode yang dikirimkan ke alamat email mereka.", "VEO": "Ketika pengguna menambahkan Kata Sandi Sekali Pakai melalui metode email, mereka perlu mengaktifkannya dengan memasukkan kode yang dikirimkan ke alamat email mereka.",
"VSO": "Ketika pengguna menambahkan One-Time Password melalui metode SMS, mereka perlu mengaktifkannya dengan memasukkan kode yang dikirimkan ke nomor telepon mereka." "VSO": "Ketika pengguna menambahkan One-Time Password melalui metode SMS, mereka perlu mengaktifkannya dengan memasukkan kode yang dikirimkan ke nomor telepon mereka.",
"IU": "Ketika kode undangan pengguna dibuat, mereka akan menerima email dengan tautan untuk mengatur metode otentikasi mereka."
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1533,7 +1534,8 @@
"PR": "Reset Kata Sandi", "PR": "Reset Kata Sandi",
"DC": "Klaim Domain", "DC": "Klaim Domain",
"PL": "Tanpa kata sandi", "PL": "Tanpa kata sandi",
"PC": "Perubahan Kata Sandi" "PC": "Perubahan Kata Sandi",
"IU": "Mengundang Pengguna"
}, },
"CHIPS": { "CHIPS": {
"firstname": "Nama yang diberikan", "firstname": "Nama yang diberikan",
@ -1553,7 +1555,8 @@
"tempUsername": "Nama pengguna sementara", "tempUsername": "Nama pengguna sementara",
"otp": "Kata sandi satu kali", "otp": "Kata sandi satu kali",
"verifyUrl": "Verifikasi URL kata sandi satu kali", "verifyUrl": "Verifikasi URL kata sandi satu kali",
"expiry": "Kedaluwarsa" "expiry": "Kedaluwarsa",
"applicationName": "Nama aplikasi"
}, },
"TOAST": { "UPDATED": "Teks Khusus disimpan." } "TOAST": { "UPDATED": "Teks Khusus disimpan." }
}, },

View File

@ -197,7 +197,8 @@
"VE": "Quando un utente cambia il suo indirizzo email, riceverà un'email con un link per verificare il nuovo indirizzo.", "VE": "Quando un utente cambia il suo indirizzo email, riceverà un'email con un link per verificare il nuovo indirizzo.",
"VP": "Quando un utente cambia il suo numero di telefono, riceverà un SMS con un codice per verificare il nuovo numero.", "VP": "Quando un utente cambia il suo numero di telefono, riceverà un SMS con un codice per verificare il nuovo numero.",
"VEO": "Quando un utente aggiunge una Password Monouso tramite metodo email, deve attivarla inserendo un codice inviato al suo indirizzo email.", "VEO": "Quando un utente aggiunge una Password Monouso tramite metodo email, deve attivarla inserendo un codice inviato al suo indirizzo email.",
"VSO": "Quando un utente aggiunge una Password Monouso tramite metodo SMS, deve attivarla inserendo un codice inviato al suo numero di telefono." "VSO": "Quando un utente aggiunge una Password Monouso tramite metodo SMS, deve attivarla inserendo un codice inviato al suo numero di telefono.",
"IU": "Quando viene creato un codice di invito per un utente, riceverà un'e-mail con un collegamento per impostare il suo metodo di autenticazione."
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1667,7 +1668,8 @@
"PR": "Ripristino della password", "PR": "Ripristino della password",
"DC": "Rivendicazione del dominio", "DC": "Rivendicazione del dominio",
"PL": "Autenticazione Passwordless", "PL": "Autenticazione Passwordless",
"PC": "Cambiamento della password" "PC": "Cambiamento della password",
"IU": "Invita utente"
}, },
"CHIPS": { "CHIPS": {
"firstname": "Nome", "firstname": "Nome",
@ -1687,7 +1689,8 @@
"tempUsername": "Nome utente temporaneo", "tempUsername": "Nome utente temporaneo",
"otp": "Password monouso", "otp": "Password monouso",
"verifyUrl": "URL per verificare la password monouso", "verifyUrl": "URL per verificare la password monouso",
"expiry": "Scadenza" "expiry": "Scadenza",
"applicationName": "Nome dell'applicazione"
}, },
"TOAST": { "TOAST": {
"UPDATED": "Testi personalizzati salvati." "UPDATED": "Testi personalizzati salvati."

View File

@ -197,7 +197,8 @@
"VE": "ユーザーがメールアドレスを変更すると、新しいアドレスを検証するリンクが記載されたメールを受け取ります。", "VE": "ユーザーがメールアドレスを変更すると、新しいアドレスを検証するリンクが記載されたメールを受け取ります。",
"VP": "ユーザーが電話番号を変更すると、新しい番号を検証するコードが記載されたSMSを受け取ります。", "VP": "ユーザーが電話番号を変更すると、新しい番号を検証するコードが記載されたSMSを受け取ります。",
"VEO": "ユーザーがメール経由でワンタイムパスワードの方法を追加すると、それをアクティブにするためにメールに送信されたコードを入力する必要があります。", "VEO": "ユーザーがメール経由でワンタイムパスワードの方法を追加すると、それをアクティブにするためにメールに送信されたコードを入力する必要があります。",
"VSO": "ユーザーがSMS経由でワンタイムパスワードの方法を追加すると、それをアクティブにするために電話番号に送信されたコードを入力する必要があります。" "VSO": "ユーザーがSMS経由でワンタイムパスワードの方法を追加すると、それをアクティブにするために電話番号に送信されたコードを入力する必要があります。",
"IU": "ユーザー招待コードが作成されると、認証方法を設定するためのリンクを含むメールが送信されます。"
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1683,7 +1684,8 @@
"tempUsername": "一時ユーザー名", "tempUsername": "一時ユーザー名",
"otp": "ワンタイムパスワード", "otp": "ワンタイムパスワード",
"verifyUrl": "ワンタイムパスワードを確認するURL", "verifyUrl": "ワンタイムパスワードを確認するURL",
"expiry": "有効期限" "expiry": "有効期限",
"applicationName": "アプリケーション名"
}, },
"TOAST": { "TOAST": {
"UPDATED": "カスタムテキストが保存されました。" "UPDATED": "カスタムテキストが保存されました。"

View File

@ -197,7 +197,8 @@
"VE": "Кога корисник ја менува својата е-маил адреса, тој ќе добие е-маил со врска за верификација на новата адреса.", "VE": "Кога корисник ја менува својата е-маил адреса, тој ќе добие е-маил со врска за верификација на новата адреса.",
"VP": "Кога корисник ја менува својата телефонска бројка, тој ќе добие SMS со код за верификација на новиот број.", "VP": "Кога корисник ја менува својата телефонска бројка, тој ќе добие SMS со код за верификација на новиот број.",
"VEO": "Кога корисник додава метод за еднократна лозинка преку е-маил, потребно е да го активира со внесување на кодот испратен на нивната е-маил адреса.", "VEO": "Кога корисник додава метод за еднократна лозинка преку е-маил, потребно е да го активира со внесување на кодот испратен на нивната е-маил адреса.",
"VSO": "Кога корисник додава метод за еднократна лозинка преку SMS, потребно е да го активира со внесување на кодот испратен на нивниот телефонски број." "VSO": "Кога корисник додава метод за еднократна лозинка преку SMS, потребно е да го активира со внесување на кодот испратен на нивниот телефонски број.",
"IU": "Кога се создаде покана за корисникот, тие ќе добијат имејл со врска за поставување на нивниот метод за автентикација."
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1668,7 +1669,8 @@
"PR": "Ресетирање на лозинка", "PR": "Ресетирање на лозинка",
"DC": "Зафатница на домен", "DC": "Зафатница на домен",
"PL": "Лозинка без лозинка", "PL": "Лозинка без лозинка",
"PC": "Промена на лозинка" "PC": "Промена на лозинка",
"IU": "Покана за корисникот"
}, },
"CHIPS": { "CHIPS": {
"firstname": "Име", "firstname": "Име",
@ -1688,7 +1690,8 @@
"tempUsername": "Привремено корисничко име", "tempUsername": "Привремено корисничко име",
"otp": "Еднократна лозинка", "otp": "Еднократна лозинка",
"verifyUrl": "URL за потврдување на еднократна лозинка", "verifyUrl": "URL за потврдување на еднократна лозинка",
"expiry": "Истекување" "expiry": "Истекување",
"applicationName": "Името на апликацијата"
}, },
"TOAST": { "TOAST": {
"UPDATED": "Прилагодените текстови се зачувани." "UPDATED": "Прилагодените текстови се зачувани."

View File

@ -197,7 +197,8 @@
"VE": "Wanneer een gebruiker zijn e-mailadres wijzigt, ontvangt hij een e-mail met een link om het nieuwe adres te verifiëren.", "VE": "Wanneer een gebruiker zijn e-mailadres wijzigt, ontvangt hij een e-mail met een link om het nieuwe adres te verifiëren.",
"VP": "Wanneer een gebruiker zijn telefoonnummer wijzigt, ontvangt hij een SMS met een code om het nieuwe nummer te verifiëren.", "VP": "Wanneer een gebruiker zijn telefoonnummer wijzigt, ontvangt hij een SMS met een code om het nieuwe nummer te verifiëren.",
"VEO": "Wanneer een gebruiker een eenmalig wachtwoord via e-mailmethode toevoegt, moeten ze dit activeren door een code in te voeren die naar hun e-mailadres is verzonden.", "VEO": "Wanneer een gebruiker een eenmalig wachtwoord via e-mailmethode toevoegt, moeten ze dit activeren door een code in te voeren die naar hun e-mailadres is verzonden.",
"VSO": "Wanneer een gebruiker een eenmalig wachtwoord via SMS-methode toevoegt, moeten ze dit activeren door een code in te voeren die naar hun telefoonnummer is verzonden." "VSO": "Wanneer een gebruiker een eenmalig wachtwoord via SMS-methode toevoegt, moeten ze dit activeren door een code in te voeren die naar hun telefoonnummer is verzonden.",
"IU": "Wanneer een uitnodigingscode voor gebruikers wordt gemaakt, ontvangt de gebruiker een e-mail met een link om zijn verificatiemethode in te stellen."
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1666,7 +1667,8 @@
"PR": "Wachtwoord Reset", "PR": "Wachtwoord Reset",
"DC": "Domein Claim", "DC": "Domein Claim",
"PL": "Wachtwoordloos", "PL": "Wachtwoordloos",
"PC": "Wachtwoord Verandering" "PC": "Wachtwoord Verandering",
"IU": "Gebruiker uitnodigen"
}, },
"CHIPS": { "CHIPS": {
"firstname": "Voornaam", "firstname": "Voornaam",
@ -1686,7 +1688,8 @@
"tempUsername": "Tijdelijke gebruikersnaam", "tempUsername": "Tijdelijke gebruikersnaam",
"otp": "Eenmalig wachtwoord", "otp": "Eenmalig wachtwoord",
"verifyUrl": "Verifieer Eenmalig-wachtwoord URL", "verifyUrl": "Verifieer Eenmalig-wachtwoord URL",
"expiry": "Vervaldatum" "expiry": "Vervaldatum",
"applicationName": "Toepassingsnaam"
}, },
"TOAST": { "TOAST": {
"UPDATED": "Aangepaste Teksten opgeslagen." "UPDATED": "Aangepaste Teksten opgeslagen."

View File

@ -197,7 +197,8 @@
"VE": "Gdy użytkownik zmieni swój adres e-mail, otrzyma e-mail z linkiem do weryfikacji nowego adresu.", "VE": "Gdy użytkownik zmieni swój adres e-mail, otrzyma e-mail z linkiem do weryfikacji nowego adresu.",
"VP": "Gdy użytkownik zmieni swój numer telefonu, otrzyma SMS z kodem do weryfikacji nowego numeru.", "VP": "Gdy użytkownik zmieni swój numer telefonu, otrzyma SMS z kodem do weryfikacji nowego numeru.",
"VEO": "Gdy użytkownik doda metodę jednorazowego hasła przez e-mail, musi ją aktywować, wprowadzając kod wysłany na jego adres e-mail.", "VEO": "Gdy użytkownik doda metodę jednorazowego hasła przez e-mail, musi ją aktywować, wprowadzając kod wysłany na jego adres e-mail.",
"VSO": "Gdy użytkownik doda metodę jednorazowego hasła przez SMS, musi ją aktywować, wprowadzając kod wysłany na jego numer telefonu." "VSO": "Gdy użytkownik doda metodę jednorazowego hasła przez SMS, musi ją aktywować, wprowadzając kod wysłany na jego numer telefonu.",
"IU": "Kiedy zostanie utworzony kod zaproszenia użytkownika, otrzyma on e-mail z linkiem do ustawienia swojej metody uwierzytelniania."
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1666,7 +1667,8 @@
"PR": "Resetowanie hasła", "PR": "Resetowanie hasła",
"DC": "Rejestracja domeny", "DC": "Rejestracja domeny",
"PL": "Bez hasła", "PL": "Bez hasła",
"PC": "Zmiana hasła" "PC": "Zmiana hasła",
"IU": "Zaproś użytkownika"
}, },
"CHIPS": { "CHIPS": {
"firstname": "Imię", "firstname": "Imię",
@ -1686,7 +1688,8 @@
"tempUsername": "Tymczasowa nazwa użytkownika", "tempUsername": "Tymczasowa nazwa użytkownika",
"otp": "Hasło jednorazowe", "otp": "Hasło jednorazowe",
"verifyUrl": "URL do weryfikacji hasła jednorazowego", "verifyUrl": "URL do weryfikacji hasła jednorazowego",
"expiry": "Wygaśnięcie" "expiry": "Wygaśnięcie",
"applicationName": "Nazwa aplikacji"
}, },
"TOAST": { "TOAST": {
"UPDATED": "Teksty niestandardowe zapisane." "UPDATED": "Teksty niestandardowe zapisane."

View File

@ -197,7 +197,8 @@
"VE": "Quando um usuário muda seu endereço de e-mail, ele receberá um e-mail com um link para verificar o novo endereço.", "VE": "Quando um usuário muda seu endereço de e-mail, ele receberá um e-mail com um link para verificar o novo endereço.",
"VP": "Quando um usuário muda seu número de telefone, ele receberá um SMS com um código para verificar o novo número.", "VP": "Quando um usuário muda seu número de telefone, ele receberá um SMS com um código para verificar o novo número.",
"VEO": "Quando um usuário adiciona um método de Senha Única via e-mail, ele precisa ativá-lo inserindo um código enviado para seu endereço de e-mail.", "VEO": "Quando um usuário adiciona um método de Senha Única via e-mail, ele precisa ativá-lo inserindo um código enviado para seu endereço de e-mail.",
"VSO": "Quando um usuário adiciona um método de Senha Única via SMS, ele precisa ativá-lo inserindo um código enviado para seu número de telefone." "VSO": "Quando um usuário adiciona um método de Senha Única via SMS, ele precisa ativá-lo inserindo um código enviado para seu número de telefone.",
"IU": "Quando um código de convite de usuário é criado, eles receberão um e-mail com um link para configurar seu método de autenticação."
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1668,7 +1669,8 @@
"PR": "Redefinição de Senha", "PR": "Redefinição de Senha",
"DC": "Reivindicação de Domínio", "DC": "Reivindicação de Domínio",
"PL": "Sem senha", "PL": "Sem senha",
"PC": "Alteração de Senha" "PC": "Alteração de Senha",
"IU": "Convidar usuário"
}, },
"CHIPS": { "CHIPS": {
"firstname": "Nome próprio", "firstname": "Nome próprio",
@ -1688,7 +1690,8 @@
"tempUsername": "Nome de usuário temporário", "tempUsername": "Nome de usuário temporário",
"otp": "Senha de uso único", "otp": "Senha de uso único",
"verifyUrl": "URL para verificar a senha de uso único", "verifyUrl": "URL para verificar a senha de uso único",
"expiry": "Data de expiração" "expiry": "Data de expiração",
"applicationName": "Nome do aplicativo"
}, },
"TOAST": { "TOAST": {
"UPDATED": "Textos personalizados salvos." "UPDATED": "Textos personalizados salvos."

View File

@ -197,7 +197,8 @@
"VE": "Когда пользователь меняет свой адрес электронной почты, он получает электронное письмо со ссылкой для подтверждения нового адреса.", "VE": "Когда пользователь меняет свой адрес электронной почты, он получает электронное письмо со ссылкой для подтверждения нового адреса.",
"VP": "Когда пользователь меняет свой телефонный номер, он получает SMS с кодом для подтверждения нового номера.", "VP": "Когда пользователь меняет свой телефонный номер, он получает SMS с кодом для подтверждения нового номера.",
"VEO": "Когда пользователь добавляет метод одноразового пароля по электронной почте, ему необходимо активировать его, введя код, отправленный на его адрес электронной почты.", "VEO": "Когда пользователь добавляет метод одноразового пароля по электронной почте, ему необходимо активировать его, введя код, отправленный на его адрес электронной почты.",
"VSO": "Когда пользователь добавляет метод одноразового пароля по SMS, ему необходимо активировать его, введя код, отправленный на его телефонный номер." "VSO": "Когда пользователь добавляет метод одноразового пароля по SMS, ему необходимо активировать его, введя код, отправленный на его телефонный номер.",
"IU": "Когда создается код приглашения пользователя, он получит электронное письмо со ссылкой для настройки своего метода аутентификации."
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1735,7 +1736,8 @@
"PR": "Восстановление пароля", "PR": "Восстановление пароля",
"DC": "Утверждение домена", "DC": "Утверждение домена",
"PL": "Без пароля", "PL": "Без пароля",
"PC": "Изменение пароля" "PC": "Изменение пароля",
"IU": "Пригласить пользователя"
}, },
"CHIPS": { "CHIPS": {
"firstname": "Имя", "firstname": "Имя",
@ -1755,7 +1757,8 @@
"tempUsername": "Временное имя пользователя", "tempUsername": "Временное имя пользователя",
"otp": "Одноразовый пароль", "otp": "Одноразовый пароль",
"verifyUrl": "Проверка URL-адреса с одноразовым паролем", "verifyUrl": "Проверка URL-адреса с одноразовым паролем",
"expiry": "Срок действия" "expiry": "Срок действия",
"applicationName": "Имя приложения"
}, },
"TOAST": { "TOAST": {
"UPDATED": "Тексты сохранены." "UPDATED": "Тексты сохранены."

View File

@ -197,7 +197,8 @@
"VE": "När en användare ändrar sin e-postadress, kommer de att få ett mail med en länk för att verifiera den nya adressen.", "VE": "När en användare ändrar sin e-postadress, kommer de att få ett mail med en länk för att verifiera den nya adressen.",
"VP": "När en användare ändrar sitt telefonnummer, kommer de att få ett SMS med en kod för att verifiera det nya numret.", "VP": "När en användare ändrar sitt telefonnummer, kommer de att få ett SMS med en kod för att verifiera det nya numret.",
"VEO": "När en användare lägger till en engångslösenord via e-postmetod, måste de aktivera den genom att ange en kod som skickas till deras e-postadress.", "VEO": "När en användare lägger till en engångslösenord via e-postmetod, måste de aktivera den genom att ange en kod som skickas till deras e-postadress.",
"VSO": "När en användare lägger till en engångslösenord via SMS-metod, måste de aktivera den genom att ange en kod som skickas till deras telefonnummer." "VSO": "När en användare lägger till en engångslösenord via SMS-metod, måste de aktivera den genom att ange en kod som skickas till deras telefonnummer.",
"IU": "När en inbjudningskod för användare skapas, får de ett e-mail med en länk för att ställa in sin autentiseringsmetod."
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1671,7 +1672,8 @@
"PR": "Återställ Lösenord", "PR": "Återställ Lösenord",
"DC": "Domänkrav", "DC": "Domänkrav",
"PL": "lösenordsfri", "PL": "lösenordsfri",
"PC": "Lösenordsändring" "PC": "Lösenordsändring",
"IU": "Bjud in användare"
}, },
"CHIPS": { "CHIPS": {
"firstname": "Förnamn", "firstname": "Förnamn",
@ -1691,7 +1693,8 @@
"tempUsername": "Tillfälligt användarnamn", "tempUsername": "Tillfälligt användarnamn",
"otp": "Engångslösenord", "otp": "Engångslösenord",
"verifyUrl": "Verifiera Engångslösenord URL", "verifyUrl": "Verifiera Engångslösenord URL",
"expiry": "Utgångsdatum" "expiry": "Utgångsdatum",
"applicationName": "Applikationsnamn"
}, },
"TOAST": { "TOAST": {
"UPDATED": "Anpassade Texter sparade." "UPDATED": "Anpassade Texter sparade."

View File

@ -197,7 +197,8 @@
"VE": "当用户更改其电子邮件地址时,他们将收到一封带有验证新地址链接的电子邮件。", "VE": "当用户更改其电子邮件地址时,他们将收到一封带有验证新地址链接的电子邮件。",
"VP": "当用户更改其电话号码时,他们将收到一条带有验证新号码的代码的短信。", "VP": "当用户更改其电话号码时,他们将收到一条带有验证新号码的代码的短信。",
"VEO": "当用户通过电子邮件方式添加一次性密码时,他们需要通过输入发送到其电子邮件地址的代码来激活它。", "VEO": "当用户通过电子邮件方式添加一次性密码时,他们需要通过输入发送到其电子邮件地址的代码来激活它。",
"VSO": "当用户通过短信方式添加一次性密码时,他们需要通过输入发送到其电话号码的代码来激活它。" "VSO": "当用户通过短信方式添加一次性密码时,他们需要通过输入发送到其电话号码的代码来激活它。",
"IU": "当创建用户邀请代码时,他们将收到一封包含设置其身份验证方法的链接的电子邮件。"
} }
}, },
"LOGIN_TEXTS": { "LOGIN_TEXTS": {
@ -1666,7 +1667,8 @@
"PR": "重置密码", "PR": "重置密码",
"DC": "域名声明", "DC": "域名声明",
"PL": "无密码身份验证", "PL": "无密码身份验证",
"PC": "修改密码" "PC": "修改密码",
"IU": "邀请用户"
}, },
"CHIPS": { "CHIPS": {
"firstname": "名", "firstname": "名",
@ -1686,7 +1688,8 @@
"tempUsername": "临时用户名", "tempUsername": "临时用户名",
"otp": "一次性密码", "otp": "一次性密码",
"verifyUrl": "验证一次性密码的URL", "verifyUrl": "验证一次性密码的URL",
"expiry": "过期时间" "expiry": "过期时间",
"applicationName": "应用程序名称"
}, },
"TOAST": { "TOAST": {
"UPDATED": "自定义文本已保存。" "UPDATED": "自定义文本已保存。"

View File

@ -396,6 +396,54 @@ func (s *Server) ResetCustomPasswordChangeMessageTextToDefault(ctx context.Conte
}, nil }, nil
} }
func (s *Server) GetDefaultInviteUserMessageText(ctx context.Context, req *admin_pb.GetDefaultInviteUserMessageTextRequest) (*admin_pb.GetDefaultInviteUserMessageTextResponse, error) {
msg, err := s.query.DefaultMessageTextByTypeAndLanguageFromFileSystem(ctx, domain.InviteUserMessageType, req.Language)
if err != nil {
return nil, err
}
return &admin_pb.GetDefaultInviteUserMessageTextResponse{
CustomText: text_grpc.ModelCustomMessageTextToPb(msg),
}, nil
}
func (s *Server) GetCustomInviteUserMessageText(ctx context.Context, req *admin_pb.GetCustomInviteUserMessageTextRequest) (*admin_pb.GetCustomInviteUserMessageTextResponse, error) {
msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, authz.GetInstance(ctx).InstanceID(), domain.InviteUserMessageType, req.Language, false)
if err != nil {
return nil, err
}
return &admin_pb.GetCustomInviteUserMessageTextResponse{
CustomText: text_grpc.ModelCustomMessageTextToPb(msg),
}, nil
}
func (s *Server) SetDefaultInviteUserMessageText(ctx context.Context, req *admin_pb.SetDefaultInviteUserMessageTextRequest) (*admin_pb.SetDefaultInviteUserMessageTextResponse, error) {
result, err := s.command.SetDefaultMessageText(ctx, authz.GetInstance(ctx).InstanceID(), SetInviteUserCustomTextToDomain(req))
if err != nil {
return nil, err
}
return &admin_pb.SetDefaultInviteUserMessageTextResponse{
Details: object.ChangeToDetailsPb(
result.Sequence,
result.EventDate,
result.ResourceOwner,
),
}, nil
}
func (s *Server) ResetCustomInviteUserMessageTextToDefault(ctx context.Context, req *admin_pb.ResetCustomInviteUserMessageTextToDefaultRequest) (*admin_pb.ResetCustomInviteUserMessageTextToDefaultResponse, error) {
result, err := s.command.RemoveInstanceMessageTexts(ctx, domain.InviteUserMessageType, language.Make(req.Language))
if err != nil {
return nil, err
}
return &admin_pb.ResetCustomInviteUserMessageTextToDefaultResponse{
Details: object.ChangeToDetailsPb(
result.Sequence,
result.EventDate,
result.ResourceOwner,
),
}, nil
}
func (s *Server) GetDefaultPasswordlessRegistrationMessageText(ctx context.Context, req *admin_pb.GetDefaultPasswordlessRegistrationMessageTextRequest) (*admin_pb.GetDefaultPasswordlessRegistrationMessageTextResponse, error) { func (s *Server) GetDefaultPasswordlessRegistrationMessageText(ctx context.Context, req *admin_pb.GetDefaultPasswordlessRegistrationMessageTextRequest) (*admin_pb.GetDefaultPasswordlessRegistrationMessageTextResponse, error) {
msg, err := s.query.DefaultMessageTextByTypeAndLanguageFromFileSystem(ctx, domain.PasswordlessRegistrationMessageType, req.Language) msg, err := s.query.DefaultMessageTextByTypeAndLanguageFromFileSystem(ctx, domain.PasswordlessRegistrationMessageType, req.Language)
if err != nil { if err != nil {

View File

@ -122,6 +122,21 @@ func SetPasswordChangeCustomTextToDomain(msg *admin_pb.SetDefaultPasswordChangeM
} }
} }
func SetInviteUserCustomTextToDomain(msg *admin_pb.SetDefaultInviteUserMessageTextRequest) *domain.CustomMessageText {
langTag := language.Make(msg.Language)
return &domain.CustomMessageText{
MessageTextType: domain.InviteUserMessageType,
Language: langTag,
Title: msg.Title,
PreHeader: msg.PreHeader,
Subject: msg.Subject,
Greeting: msg.Greeting,
Text: msg.Text,
ButtonText: msg.ButtonText,
FooterText: msg.FooterText,
}
}
func SetPasswordlessRegistrationCustomTextToDomain(msg *admin_pb.SetDefaultPasswordlessRegistrationMessageTextRequest) *domain.CustomMessageText { func SetPasswordlessRegistrationCustomTextToDomain(msg *admin_pb.SetDefaultPasswordlessRegistrationMessageTextRequest) *domain.CustomMessageText {
langTag := language.Make(msg.Language) langTag := language.Make(msg.Language)
return &domain.CustomMessageText{ return &domain.CustomMessageText{

View File

@ -793,6 +793,7 @@ func importResources(ctx context.Context, s *Server, errors *[]*admin_pb.ImportD
importVerifyPhoneMessageTexts(ctx, s, errors, org) importVerifyPhoneMessageTexts(ctx, s, errors, org)
importDomainClaimedMessageTexts(ctx, s, errors, org) importDomainClaimedMessageTexts(ctx, s, errors, org)
importPasswordlessRegistrationMessageTexts(ctx, s, errors, org) importPasswordlessRegistrationMessageTexts(ctx, s, errors, org)
importInviteUserMessageTexts(ctx, s, errors, org)
if err := importHumanUsers(ctx, s, errors, successOrg, org, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode); err != nil { if err := importHumanUsers(ctx, s, errors, successOrg, org, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode); err != nil {
return err return err
} }
@ -975,6 +976,21 @@ func importPasswordlessRegistrationMessageTexts(ctx context.Context, s *Server,
} }
} }
func importInviteUserMessageTexts(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, org *admin_pb.DataOrg) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.End() }()
if org.PasswordlessRegistrationMessages == nil {
return
}
for _, message := range org.GetInviteUserMessages() {
_, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, management.SetInviteUserCustomTextToDomain(message))
if err != nil {
*errors = append(*errors, &admin_pb.ImportDataError{Type: "invite_user_messages", Id: org.GetOrgId() + "_" + message.Language, Message: err.Error()})
}
}
}
func importOrg2(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, success *admin_pb.ImportDataSuccess, count *counts, org *admin_pb.DataOrg) (err error) { func importOrg2(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, success *admin_pb.ImportDataSuccess, count *counts, org *admin_pb.DataOrg) (err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
@ -1236,6 +1252,7 @@ func (s *Server) dataOrgsV1ToDataOrgs(ctx context.Context, dataOrgs *v1_pb.Impor
VerifyPhoneMessages: orgV1.GetVerifyPhoneMessages(), VerifyPhoneMessages: orgV1.GetVerifyPhoneMessages(),
DomainClaimedMessages: orgV1.GetDomainClaimedMessages(), DomainClaimedMessages: orgV1.GetDomainClaimedMessages(),
PasswordlessRegistrationMessages: orgV1.GetPasswordlessRegistrationMessages(), PasswordlessRegistrationMessages: orgV1.GetPasswordlessRegistrationMessages(),
InviteUserMessages: orgV1.GetInviteUserMessages(),
OidcIdps: orgV1.GetOidcIdps(), OidcIdps: orgV1.GetOidcIdps(),
JwtIdps: orgV1.GetJwtIdps(), JwtIdps: orgV1.GetJwtIdps(),
UserLinks: orgV1.GetUserLinks(), UserLinks: orgV1.GetUserLinks(),

View File

@ -396,6 +396,54 @@ func (s *Server) ResetCustomPasswordChangeMessageTextToDefault(ctx context.Conte
}, nil }, nil
} }
func (s *Server) GetCustomInviteUserMessageText(ctx context.Context, req *mgmt_pb.GetCustomInviteUserMessageTextRequest) (*mgmt_pb.GetCustomInviteUserMessageTextResponse, error) {
msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, authz.GetCtxData(ctx).OrgID, domain.InviteUserMessageType, req.Language, false)
if err != nil {
return nil, err
}
return &mgmt_pb.GetCustomInviteUserMessageTextResponse{
CustomText: text_grpc.ModelCustomMessageTextToPb(msg),
}, nil
}
func (s *Server) GetDefaultInviteUserMessageText(ctx context.Context, req *mgmt_pb.GetDefaultInviteUserMessageTextRequest) (*mgmt_pb.GetDefaultInviteUserMessageTextResponse, error) {
msg, err := s.query.IAMMessageTextByTypeAndLanguage(ctx, domain.InviteUserMessageType, req.Language)
if err != nil {
return nil, err
}
return &mgmt_pb.GetDefaultInviteUserMessageTextResponse{
CustomText: text_grpc.ModelCustomMessageTextToPb(msg),
}, nil
}
func (s *Server) SetCustomInviteUserMessageCustomText(ctx context.Context, req *mgmt_pb.SetCustomInviteUserMessageTextRequest) (*mgmt_pb.SetCustomInviteUserMessageTextResponse, error) {
result, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, SetInviteUserCustomTextToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.SetCustomInviteUserMessageTextResponse{
Details: object.ChangeToDetailsPb(
result.Sequence,
result.EventDate,
result.ResourceOwner,
),
}, nil
}
func (s *Server) ResetCustomInviteUserMessageTextToDefault(ctx context.Context, req *mgmt_pb.ResetCustomInviteUserMessageTextToDefaultRequest) (*mgmt_pb.ResetCustomInviteUserMessageTextToDefaultResponse, error) {
result, err := s.command.RemoveOrgMessageTexts(ctx, authz.GetCtxData(ctx).OrgID, domain.InviteUserMessageType, language.Make(req.Language))
if err != nil {
return nil, err
}
return &mgmt_pb.ResetCustomInviteUserMessageTextToDefaultResponse{
Details: object.ChangeToDetailsPb(
result.Sequence,
result.EventDate,
result.ResourceOwner,
),
}, nil
}
func (s *Server) GetCustomPasswordlessRegistrationMessageText(ctx context.Context, req *mgmt_pb.GetCustomPasswordlessRegistrationMessageTextRequest) (*mgmt_pb.GetCustomPasswordlessRegistrationMessageTextResponse, error) { func (s *Server) GetCustomPasswordlessRegistrationMessageText(ctx context.Context, req *mgmt_pb.GetCustomPasswordlessRegistrationMessageTextRequest) (*mgmt_pb.GetCustomPasswordlessRegistrationMessageTextResponse, error) {
msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, authz.GetCtxData(ctx).OrgID, domain.PasswordlessRegistrationMessageType, req.Language, false) msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, authz.GetCtxData(ctx).OrgID, domain.PasswordlessRegistrationMessageType, req.Language, false)
if err != nil { if err != nil {

View File

@ -122,6 +122,21 @@ func SetPasswordChangeCustomTextToDomain(msg *mgmt_pb.SetCustomPasswordChangeMes
} }
} }
func SetInviteUserCustomTextToDomain(msg *mgmt_pb.SetCustomInviteUserMessageTextRequest) *domain.CustomMessageText {
langTag := language.Make(msg.Language)
return &domain.CustomMessageText{
MessageTextType: domain.InviteUserMessageType,
Language: langTag,
Title: msg.Title,
PreHeader: msg.PreHeader,
Subject: msg.Subject,
Greeting: msg.Greeting,
Text: msg.Text,
ButtonText: msg.ButtonText,
FooterText: msg.FooterText,
}
}
func SetPasswordlessRegistrationCustomTextToDomain(msg *mgmt_pb.SetCustomPasswordlessRegistrationMessageTextRequest) *domain.CustomMessageText { func SetPasswordlessRegistrationCustomTextToDomain(msg *mgmt_pb.SetCustomPasswordlessRegistrationMessageTextRequest) *domain.CustomMessageText {
langTag := language.Make(msg.Language) langTag := language.Make(msg.Language)
return &domain.CustomMessageText{ return &domain.CustomMessageText{

View File

@ -2437,3 +2437,310 @@ func TestServer_ListAuthenticationMethodTypes(t *testing.T) {
}) })
} }
} }
func TestServer_CreateInviteCode(t *testing.T) {
type args struct {
ctx context.Context
req *user.CreateInviteCodeRequest
prepare func(request *user.CreateInviteCodeRequest) error
}
tests := []struct {
name string
args args
want *user.CreateInviteCodeResponse
wantErr bool
}{
{
name: "create, not existing",
args: args{
CTX,
&user.CreateInviteCodeRequest{
UserId: "notexisting",
},
func(request *user.CreateInviteCodeRequest) error { return nil },
},
wantErr: true,
},
{
name: "create, ok",
args: args{
ctx: CTX,
req: &user.CreateInviteCodeRequest{},
prepare: func(request *user.CreateInviteCodeRequest) error {
resp := Instance.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
return nil
},
},
want: &user.CreateInviteCodeResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.DefaultOrg.Id,
},
},
},
{
name: "create, invalid template",
args: args{
ctx: CTX,
req: &user.CreateInviteCodeRequest{
Verification: &user.CreateInviteCodeRequest_SendCode{
SendCode: &user.SendInviteCode{
UrlTemplate: gu.Ptr("{{"),
},
},
},
prepare: func(request *user.CreateInviteCodeRequest) error {
resp := Instance.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
return nil
},
},
wantErr: true,
},
{
name: "create, valid template",
args: args{
ctx: CTX,
req: &user.CreateInviteCodeRequest{
Verification: &user.CreateInviteCodeRequest_SendCode{
SendCode: &user.SendInviteCode{
UrlTemplate: gu.Ptr("https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}"),
ApplicationName: gu.Ptr("TestApp"),
},
},
},
prepare: func(request *user.CreateInviteCodeRequest) error {
resp := Instance.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
return nil
},
},
want: &user.CreateInviteCodeResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.DefaultOrg.Id,
},
},
},
{
name: "create, return code, ok",
args: args{
ctx: CTX,
req: &user.CreateInviteCodeRequest{
Verification: &user.CreateInviteCodeRequest_ReturnCode{
ReturnCode: &user.ReturnInviteCode{},
},
},
prepare: func(request *user.CreateInviteCodeRequest) error {
resp := Instance.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
return nil
},
},
want: &user.CreateInviteCodeResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.DefaultOrg.Id,
},
InviteCode: gu.Ptr("something"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.CreateInviteCode(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
integration.AssertDetails(t, tt.want, got)
if tt.want.GetInviteCode() != "" {
assert.NotEmpty(t, got.GetInviteCode())
} else {
assert.Empty(t, got.GetInviteCode())
}
})
}
}
func TestServer_ResendInviteCode(t *testing.T) {
type args struct {
ctx context.Context
req *user.ResendInviteCodeRequest
prepare func(request *user.ResendInviteCodeRequest) error
}
tests := []struct {
name string
args args
want *user.ResendInviteCodeResponse
wantErr bool
}{
{
name: "user not existing",
args: args{
CTX,
&user.ResendInviteCodeRequest{
UserId: "notexisting",
},
func(request *user.ResendInviteCodeRequest) error { return nil },
},
wantErr: true,
},
{
name: "code not existing",
args: args{
ctx: CTX,
req: &user.ResendInviteCodeRequest{},
prepare: func(request *user.ResendInviteCodeRequest) error {
resp := Instance.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
return nil
},
},
wantErr: true,
},
{
name: "code not sent before",
args: args{
ctx: CTX,
req: &user.ResendInviteCodeRequest{},
prepare: func(request *user.ResendInviteCodeRequest) error {
userResp := Instance.CreateHumanUser(CTX)
request.UserId = userResp.GetUserId()
Instance.CreateInviteCode(CTX, userResp.GetUserId())
return nil
},
},
wantErr: true,
},
{
name: "resend, ok",
args: args{
ctx: CTX,
req: &user.ResendInviteCodeRequest{},
prepare: func(request *user.ResendInviteCodeRequest) error {
resp := Instance.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
_, err := Instance.Client.UserV2.CreateInviteCode(CTX, &user.CreateInviteCodeRequest{
UserId: resp.GetUserId(),
})
return err
},
},
want: &user.ResendInviteCodeResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.DefaultOrg.Id,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.ResendInviteCode(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_VerifyInviteCode(t *testing.T) {
type args struct {
ctx context.Context
req *user.VerifyInviteCodeRequest
prepare func(request *user.VerifyInviteCodeRequest) error
}
tests := []struct {
name string
args args
want *user.VerifyInviteCodeResponse
wantErr bool
}{
{
name: "user not existing",
args: args{
CTX,
&user.VerifyInviteCodeRequest{
UserId: "notexisting",
},
func(request *user.VerifyInviteCodeRequest) error { return nil },
},
wantErr: true,
},
{
name: "code not existing",
args: args{
ctx: CTX,
req: &user.VerifyInviteCodeRequest{},
prepare: func(request *user.VerifyInviteCodeRequest) error {
resp := Instance.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
return nil
},
},
wantErr: true,
},
{
name: "invalid code",
args: args{
ctx: CTX,
req: &user.VerifyInviteCodeRequest{
VerificationCode: "invalid",
},
prepare: func(request *user.VerifyInviteCodeRequest) error {
userResp := Instance.CreateHumanUser(CTX)
request.UserId = userResp.GetUserId()
Instance.CreateInviteCode(CTX, userResp.GetUserId())
return nil
},
},
wantErr: true,
},
{
name: "verify, ok",
args: args{
ctx: CTX,
req: &user.VerifyInviteCodeRequest{},
prepare: func(request *user.VerifyInviteCodeRequest) error {
userResp := Instance.CreateHumanUser(CTX)
request.UserId = userResp.GetUserId()
codeResp := Instance.CreateInviteCode(CTX, userResp.GetUserId())
request.VerificationCode = codeResp.GetInviteCode()
return nil
},
},
want: &user.VerifyInviteCodeResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.DefaultOrg.Id,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.VerifyInviteCode(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
integration.AssertDetails(t, tt.want, got)
})
}
}

View File

@ -45,14 +45,6 @@ func AddUserRequestToAddHuman(req *user.AddHumanUserRequest) (*command.AddHuman,
if username == "" { if username == "" {
username = req.GetEmail().GetEmail() username = req.GetEmail().GetEmail()
} }
var urlTemplate string
if req.GetEmail().GetSendCode() != nil {
urlTemplate = req.GetEmail().GetSendCode().GetUrlTemplate()
// test the template execution so the async notification will not fail because of it and the user won't realize
if err := domain.RenderConfirmURLTemplate(io.Discard, urlTemplate, req.GetUserId(), "code", "orgID"); err != nil {
return nil, err
}
}
passwordChangeRequired := req.GetPassword().GetChangeRequired() || req.GetHashedPassword().GetChangeRequired() passwordChangeRequired := req.GetPassword().GetChangeRequired() || req.GetHashedPassword().GetChangeRequired()
metadata := make([]*command.AddMetadataEntry, len(req.Metadata)) metadata := make([]*command.AddMetadataEntry, len(req.Metadata))
for i, metadataEntry := range req.Metadata { for i, metadataEntry := range req.Metadata {
@ -69,6 +61,10 @@ func AddUserRequestToAddHuman(req *user.AddHumanUserRequest) (*command.AddHuman,
DisplayName: link.GetUserName(), DisplayName: link.GetUserName(),
} }
} }
email, err := addUserRequestEmailToCommand(req.GetEmail())
if err != nil {
return nil, err
}
return &command.AddHuman{ return &command.AddHuman{
ID: req.GetUserId(), ID: req.GetUserId(),
Username: username, Username: username,
@ -76,12 +72,7 @@ func AddUserRequestToAddHuman(req *user.AddHumanUserRequest) (*command.AddHuman,
LastName: req.GetProfile().GetFamilyName(), LastName: req.GetProfile().GetFamilyName(),
NickName: req.GetProfile().GetNickName(), NickName: req.GetProfile().GetNickName(),
DisplayName: req.GetProfile().GetDisplayName(), DisplayName: req.GetProfile().GetDisplayName(),
Email: command.Email{ Email: email,
Address: domain.EmailAddress(req.GetEmail().GetEmail()),
Verified: req.GetEmail().GetIsVerified(),
ReturnCode: req.GetEmail().GetReturnCode() != nil,
URLTemplate: urlTemplate,
},
Phone: command.Phone{ Phone: command.Phone{
Number: domain.PhoneNumber(req.GetPhone().GetPhone()), Number: domain.PhoneNumber(req.GetPhone().GetPhone()),
Verified: req.GetPhone().GetIsVerified(), Verified: req.GetPhone().GetIsVerified(),
@ -100,6 +91,25 @@ func AddUserRequestToAddHuman(req *user.AddHumanUserRequest) (*command.AddHuman,
}, nil }, nil
} }
func addUserRequestEmailToCommand(email *user.SetHumanEmail) (command.Email, error) {
address := domain.EmailAddress(email.GetEmail())
switch v := email.GetVerification().(type) {
case *user.SetHumanEmail_ReturnCode:
return command.Email{Address: address, ReturnCode: true}, nil
case *user.SetHumanEmail_SendCode:
urlTemplate := v.SendCode.GetUrlTemplate()
// test the template execution so the async notification will not fail because of it and the user won't realize
if err := domain.RenderConfirmURLTemplate(io.Discard, urlTemplate, "userID", "code", "orgID"); err != nil {
return command.Email{}, err
}
return command.Email{Address: address, URLTemplate: urlTemplate}, nil
case *user.SetHumanEmail_IsVerified:
return command.Email{Address: address, Verified: v.IsVerified, NoEmailVerification: true}, nil
default:
return command.Email{Address: address}, nil
}
}
func genderToDomain(gender user.Gender) domain.Gender { func genderToDomain(gender user.Gender) domain.Gender {
switch gender { switch gender {
case user.Gender_GENDER_UNSPECIFIED: case user.Gender_GENDER_UNSPECIFIED:
@ -617,3 +627,54 @@ func authMethodTypeToPb(methodType domain.UserAuthMethodType) user.Authenticatio
return user.AuthenticationMethodType_AUTHENTICATION_METHOD_TYPE_UNSPECIFIED return user.AuthenticationMethodType_AUTHENTICATION_METHOD_TYPE_UNSPECIFIED
} }
} }
func (s *Server) CreateInviteCode(ctx context.Context, req *user.CreateInviteCodeRequest) (*user.CreateInviteCodeResponse, error) {
invite, err := createInviteCodeRequestToCommand(req)
if err != nil {
return nil, err
}
details, code, err := s.command.CreateInviteCode(ctx, invite)
if err != nil {
return nil, err
}
return &user.CreateInviteCodeResponse{
Details: object.DomainToDetailsPb(details),
InviteCode: code,
}, nil
}
func (s *Server) ResendInviteCode(ctx context.Context, req *user.ResendInviteCodeRequest) (*user.ResendInviteCodeResponse, error) {
details, err := s.command.ResendInviteCode(ctx, req.GetUserId(), "", "")
if err != nil {
return nil, err
}
return &user.ResendInviteCodeResponse{
Details: object.DomainToDetailsPb(details),
}, nil
}
func (s *Server) VerifyInviteCode(ctx context.Context, req *user.VerifyInviteCodeRequest) (*user.VerifyInviteCodeResponse, error) {
details, err := s.command.VerifyInviteCode(ctx, req.GetUserId(), req.GetVerificationCode())
if err != nil {
return nil, err
}
return &user.VerifyInviteCodeResponse{
Details: object.DomainToDetailsPb(details),
}, nil
}
func createInviteCodeRequestToCommand(req *user.CreateInviteCodeRequest) (*command.CreateUserInvite, error) {
switch v := req.GetVerification().(type) {
case *user.CreateInviteCodeRequest_SendCode:
urlTemplate := v.SendCode.GetUrlTemplate()
// test the template execution so the async notification will not fail because of it and the user won't realize
if err := domain.RenderConfirmURLTemplate(io.Discard, urlTemplate, req.GetUserId(), "code", "orgID"); err != nil {
return nil, err
}
return &command.CreateUserInvite{UserID: req.GetUserId(), URLTemplate: urlTemplate, ApplicationName: v.SendCode.GetApplicationName()}, nil
case *user.CreateInviteCodeRequest_ReturnCode:
return &command.CreateUserInvite{UserID: req.GetUserId(), ReturnCode: true}, nil
default:
return &command.CreateUserInvite{UserID: req.GetUserId()}, nil
}
}

View File

@ -0,0 +1,154 @@
package login
import (
"net/http"
"net/url"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/zerrors"
)
const (
queryInviteUserCode = "code"
queryInviteUserUserID = "userID"
queryInviteUserLoginName = "loginname"
tmplInviteUser = "inviteuser"
)
type inviteUserFormData struct {
Code string `schema:"code"`
LoginName string `schema:"loginname"`
Password string `schema:"password"`
PasswordConfirm string `schema:"passwordconfirm"`
UserID string `schema:"userID"`
OrgID string `schema:"orgID"`
Resend bool `schema:"resend"`
}
type inviteUserData struct {
baseData
profileData
Code string
LoginName string
UserID string
MinLength uint64
HasUppercase string
HasLowercase string
HasNumber string
HasSymbol string
}
func InviteUserLink(origin, userID, loginName, code, orgID string, authRequestID string) string {
v := url.Values{}
v.Set(queryInviteUserUserID, userID)
v.Set(queryInviteUserLoginName, loginName)
v.Set(queryInviteUserCode, code)
v.Set(queryOrgID, orgID)
v.Set(QueryAuthRequestID, authRequestID)
return externalLink(origin) + EndpointInviteUser + "?" + v.Encode()
}
func (l *Login) handleInviteUser(w http.ResponseWriter, r *http.Request) {
authReq := l.checkOptionalAuthRequestOfEmailLinks(r)
userID := r.FormValue(queryInviteUserUserID)
orgID := r.FormValue(queryOrgID)
code := r.FormValue(queryInviteUserCode)
loginName := r.FormValue(queryInviteUserLoginName)
l.renderInviteUser(w, r, authReq, userID, orgID, loginName, code, nil)
}
func (l *Login) handleInviteUserCheck(w http.ResponseWriter, r *http.Request) {
data := new(inviteUserFormData)
authReq, err := l.getAuthRequestAndParseData(r, data)
if err != nil {
l.renderError(w, r, nil, err)
return
}
if data.Resend {
l.resendUserInvite(w, r, authReq, data.UserID, data.OrgID, data.LoginName)
return
}
l.checkUserInviteCode(w, r, authReq, data)
}
func (l *Login) checkUserInviteCode(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, data *inviteUserFormData) {
if data.Password != data.PasswordConfirm {
err := zerrors.ThrowInvalidArgument(nil, "VIEW-KJS3h", "Errors.User.Password.ConfirmationWrong")
l.renderInviteUser(w, r, authReq, data.UserID, data.OrgID, data.LoginName, data.Code, err)
return
}
userOrgID := ""
if authReq != nil {
userOrgID = authReq.UserOrgID
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
_, err := l.command.VerifyInviteCodeSetPassword(setUserContext(r.Context(), data.UserID, userOrgID), data.UserID, data.Code, data.Password, userAgentID)
if err != nil {
l.renderInviteUser(w, r, authReq, data.UserID, data.OrgID, data.LoginName, "", err)
return
}
if authReq == nil {
l.defaultRedirect(w, r)
return
}
l.renderNextStep(w, r, authReq)
}
func (l *Login) resendUserInvite(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, orgID, loginName string) {
var userOrgID, authRequestID string
if authReq != nil {
userOrgID = authReq.UserOrgID
authRequestID = authReq.ID
}
_, err := l.command.ResendInviteCode(setUserContext(r.Context(), userID, userOrgID), userID, userOrgID, authRequestID)
l.renderInviteUser(w, r, authReq, userID, orgID, loginName, "", err)
}
func (l *Login) renderInviteUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, userID, orgID, loginName string, code string, err error) {
var errID, errMessage string
if err != nil {
errID, errMessage = l.getErrorMessage(r, err)
}
if authReq != nil {
userID = authReq.UserID
orgID = authReq.UserOrgID
}
translator := l.getTranslator(r.Context(), authReq)
data := inviteUserData{
baseData: l.getBaseData(r, authReq, translator, "InviteUser.Title", "InviteUser.Description", errID, errMessage),
profileData: l.getProfileData(authReq),
UserID: userID,
Code: code,
}
// if the user clicked on the link in the mail, we need to make sure the loginName is rendered
if authReq == nil {
data.LoginName = loginName
data.UserName = loginName
}
policy := l.getPasswordComplexityPolicyByUserID(r, userID)
if policy != nil {
data.MinLength = policy.MinLength
if policy.HasUppercase {
data.HasUppercase = UpperCaseRegex
}
if policy.HasLowercase {
data.HasLowercase = LowerCaseRegex
}
if policy.HasSymbol {
data.HasSymbol = SymbolRegex
}
if policy.HasNumber {
data.HasNumber = NumberRegex
}
}
if authReq == nil {
if err == nil {
l.customTexts(r.Context(), translator, orgID)
}
}
l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplInviteUser], data, nil)
}

View File

@ -68,6 +68,7 @@ func CreateRenderer(pathPrefix string, staticStorage static.Storage, cookieName
tmplInitPasswordDone: "init_password_done.html", tmplInitPasswordDone: "init_password_done.html",
tmplInitUser: "init_user.html", tmplInitUser: "init_user.html",
tmplInitUserDone: "init_user_done.html", tmplInitUserDone: "init_user_done.html",
tmplInviteUser: "invite_user.html",
tmplPasswordResetDone: "password_reset_done.html", tmplPasswordResetDone: "password_reset_done.html",
tmplChangePassword: "change_password.html", tmplChangePassword: "change_password.html",
tmplChangePasswordDone: "change_password_done.html", tmplChangePasswordDone: "change_password_done.html",
@ -193,6 +194,9 @@ func CreateRenderer(pathPrefix string, staticStorage static.Storage, cookieName
"initUserUrl": func() string { "initUserUrl": func() string {
return path.Join(r.pathPrefix, EndpointInitUser) return path.Join(r.pathPrefix, EndpointInitUser)
}, },
"inviteUserUrl": func() string {
return path.Join(r.pathPrefix, EndpointInviteUser)
},
"changePasswordUrl": func() string { "changePasswordUrl": func() string {
return path.Join(r.pathPrefix, EndpointChangePassword) return path.Join(r.pathPrefix, EndpointChangePassword)
}, },
@ -329,6 +333,8 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq *
l.renderInternalError(w, r, authReq, zerrors.ThrowPreconditionFailed(nil, "APP-asb43", "Errors.User.GrantRequired")) l.renderInternalError(w, r, authReq, zerrors.ThrowPreconditionFailed(nil, "APP-asb43", "Errors.User.GrantRequired"))
case *domain.ProjectRequiredStep: case *domain.ProjectRequiredStep:
l.renderInternalError(w, r, authReq, zerrors.ThrowPreconditionFailed(nil, "APP-m92d", "Errors.User.ProjectRequired")) l.renderInternalError(w, r, authReq, zerrors.ThrowPreconditionFailed(nil, "APP-m92d", "Errors.User.ProjectRequired"))
case *domain.VerifyInviteStep:
l.renderInviteUser(w, r, authReq, "", "", "", "", nil)
default: default:
l.renderInternalError(w, r, authReq, zerrors.ThrowInternal(nil, "APP-ds3QF", "step no possible")) l.renderInternalError(w, r, authReq, zerrors.ThrowInternal(nil, "APP-ds3QF", "step no possible"))
} }

View File

@ -30,6 +30,7 @@ const (
EndpointChangePassword = "/password/change" EndpointChangePassword = "/password/change"
EndpointPasswordReset = "/password/reset" EndpointPasswordReset = "/password/reset"
EndpointInitUser = "/user/init" EndpointInitUser = "/user/init"
EndpointInviteUser = "/user/invite"
EndpointMFAVerify = "/mfa/verify" EndpointMFAVerify = "/mfa/verify"
EndpointMFAPrompt = "/mfa/prompt" EndpointMFAPrompt = "/mfa/prompt"
EndpointMFAInitVerify = "/mfa/init/verify" EndpointMFAInitVerify = "/mfa/init/verify"
@ -94,6 +95,8 @@ func CreateRouter(login *Login, interceptors ...mux.MiddlewareFunc) *mux.Router
router.HandleFunc(EndpointPasswordReset, login.handlePasswordReset).Methods(http.MethodGet) router.HandleFunc(EndpointPasswordReset, login.handlePasswordReset).Methods(http.MethodGet)
router.HandleFunc(EndpointInitUser, login.handleInitUser).Methods(http.MethodGet) router.HandleFunc(EndpointInitUser, login.handleInitUser).Methods(http.MethodGet)
router.HandleFunc(EndpointInitUser, login.handleInitUserCheck).Methods(http.MethodPost) router.HandleFunc(EndpointInitUser, login.handleInitUserCheck).Methods(http.MethodPost)
router.HandleFunc(EndpointInviteUser, login.handleInviteUser).Methods(http.MethodGet)
router.HandleFunc(EndpointInviteUser, login.handleInviteUserCheck).Methods(http.MethodPost)
router.HandleFunc(EndpointMFAVerify, login.handleMFAVerify).Methods(http.MethodPost) router.HandleFunc(EndpointMFAVerify, login.handleMFAVerify).Methods(http.MethodPost)
router.HandleFunc(EndpointMFAPrompt, login.handleMFAPromptSelection).Methods(http.MethodGet) router.HandleFunc(EndpointMFAPrompt, login.handleMFAPromptSelection).Methods(http.MethodGet)
router.HandleFunc(EndpointMFAPrompt, login.handleMFAPrompt).Methods(http.MethodPost) router.HandleFunc(EndpointMFAPrompt, login.handleMFAPrompt).Methods(http.MethodPost)

View File

@ -81,6 +81,14 @@ InitUserDone:
Description: Имейлът е потвърден и паролата е успешно зададена Description: Имейлът е потвърден и паролата е успешно зададена
NextButtonText: следващия NextButtonText: следващия
CancelButtonText: анулиране CancelButtonText: анулиране
InviteUser:
Title: Активиране на потребителя
Description: Проверете своя имейл с кода по-долу и задайте паролата си.
CodeLabel: Код
NewPasswordLabel: Нова парола
NewPasswordConfirm: Потвърди парола
NextButtonText: Напред
ResendButtonText: Изпрати отново код
InitMFAPrompt: InitMFAPrompt:
Title: 2-факторна настройка Title: 2-факторна настройка
Description: >- Description: >-

View File

@ -86,6 +86,15 @@ InitUserDone:
NextButtonText: Další NextButtonText: Další
CancelButtonText: Zrušit CancelButtonText: Zrušit
InviteUser:
Title: Aktivace uživatele
Description: Ověřte svůj e-mail pomocí níže uvedeného kódu a nastavte si heslo.
CodeLabel: Kód
NewPasswordLabel: Nové heslo
NewPasswordConfirm: Potvrďte heslo
NextButtonText: Další
ResendButtonText: Odeslat kód znovu
InitMFAPrompt: InitMFAPrompt:
Title: Nastavení 2-faktorové autentizace Title: Nastavení 2-faktorové autentizace
Description: 2-faktorová autentizace vám poskytuje další zabezpečení pro váš uživatelský účet. Tím je zajištěno, že k vašemu účtu máte přístup pouze vy. Description: 2-faktorová autentizace vám poskytuje další zabezpečení pro váš uživatelský účet. Tím je zajištěno, že k vašemu účtu máte přístup pouze vy.

View File

@ -86,6 +86,15 @@ InitUserDone:
NextButtonText: Weiter NextButtonText: Weiter
CancelButtonText: Abbrechen CancelButtonText: Abbrechen
InviteUser:
Title: Benutzer aktivieren
Description: Bestätige deine E-Mail-Adresse mit dem unten stehenden Code und lege dein Passwort fest.
CodeLabel: Code
NewPasswordLabel: Neues Passwort
NewPasswordConfirm: Passwort bestätigen
NextButtonText: Weiter
ResendButtonText: Code erneut senden
InitMFAPrompt: InitMFAPrompt:
Title: Zweitfaktor hinzufügen Title: Zweitfaktor hinzufügen
Description: Die Zwei-Faktor-Authentifizierung gibt dir eine zusätzliche Sicherheit für dein Benutzerkonto. Damit stellst du sicher, dass nur du Zugriff auf dein Konto hast. Description: Die Zwei-Faktor-Authentifizierung gibt dir eine zusätzliche Sicherheit für dein Benutzerkonto. Damit stellst du sicher, dass nur du Zugriff auf dein Konto hast.

View File

@ -86,6 +86,15 @@ InitUserDone:
NextButtonText: Next NextButtonText: Next
CancelButtonText: Cancel CancelButtonText: Cancel
InviteUser:
Title: Activate User
Description: Verify your e-mail with the code below and set your password.
CodeLabel: Code
NewPasswordLabel: New Password
NewPasswordConfirm: Confirm Password
NextButtonText: Next
ResendButtonText: Resend Code
InitMFAPrompt: InitMFAPrompt:
Title: 2-Factor Setup Title: 2-Factor Setup
Description: 2-factor authentication gives you an additional security for your user account. This ensures that only you have access to your account. Description: 2-factor authentication gives you an additional security for your user account. This ensures that only you have access to your account.

View File

@ -86,6 +86,15 @@ InitUserDone:
NextButtonText: siguiente NextButtonText: siguiente
CancelButtonText: cancelar CancelButtonText: cancelar
InviteUser:
Title: Activar usuario
Description: Verifica tu email con el siguiente código y establece tu contraseña.
CodeLabel: Código
NewPasswordLabel: Nueva contraseña
NewPasswordConfirm: Confirmar contraseña
NextButtonText: siguiente
ResendButtonText: reenviar código
InitMFAPrompt: InitMFAPrompt:
Title: Configuración de doble factor Title: Configuración de doble factor
Description: La autenticación de doble factor te proporciona seguridad adicional para tu cuenta de usuario. Ésta asegura que solo tú tienes acceso a tu cuenta. Description: La autenticación de doble factor te proporciona seguridad adicional para tu cuenta de usuario. Ésta asegura que solo tú tienes acceso a tu cuenta.

View File

@ -86,6 +86,15 @@ InitUserDone:
NextButtonText: Suivant NextButtonText: Suivant
CancelButtonText: Annuler CancelButtonText: Annuler
InviteUser:
Title: Activer l'utilisateur
Description: Vérifiez votre e-mail avec le code ci-dessous et définissez votre mot de passe.
CodeLabel: Code
NewPasswordLabel: Nouveau mot de passe
NewPasswordConfirm: Confirmer le mot de passe
NextButtonText: Suivant
ResendButtonText: Renvoyer le code
InitMFAPrompt: InitMFAPrompt:
Title: Configuration authentification à 2 facteurs Title: Configuration authentification à 2 facteurs
Description: L'authentification authentification à 2 facteurs vous offre une sécurité supplémentaire pour votre compte d'utilisateur. Vous êtes ainsi assuré d'être le seul à avoir accès à votre compte. Description: L'authentification authentification à 2 facteurs vous offre une sécurité supplémentaire pour votre compte d'utilisateur. Vous êtes ainsi assuré d'être le seul à avoir accès à votre compte.

View File

@ -76,6 +76,14 @@ InitUserDone:
Description: Email terverifikasi dan Kata Sandi berhasil ditetapkan Description: Email terverifikasi dan Kata Sandi berhasil ditetapkan
NextButtonText: Berikutnya NextButtonText: Berikutnya
CancelButtonText: Membatalkan CancelButtonText: Membatalkan
InviteUser:
Title: Aktifkan Pengguna
Description: Verifikasi email Anda dengan kode di bawah ini dan atur kata sandi Anda.
CodeLabel: Kode
NewPasswordLabel: Kata Sandi Baru
NewPasswordConfirm: Konfirmasi Kata Sandi
NextButtonText: Selanjutnya
ResendButtonText: Kirim Ulang Kode
InitMFAPrompt: InitMFAPrompt:
Title: Pengaturan 2 Faktor Title: Pengaturan 2 Faktor
Description: Otentikasi 2 faktor memberi Anda keamanan tambahan untuk akun pengguna Anda. Description: Otentikasi 2 faktor memberi Anda keamanan tambahan untuk akun pengguna Anda.

View File

@ -86,6 +86,15 @@ InitUserDone:
NextButtonText: Avanti NextButtonText: Avanti
CancelButtonText: annulla CancelButtonText: annulla
InviteUser:
Title: Attiva utente
Description: Verifica la tua email con il codice seguente e imposta la tua password.
CodeLabel: Codice
NewPasswordLabel: Nuova password
NewPasswordConfirm: Conferma password
NextButtonText: Avanti
ResendButtonText: Reinvia codice
InitMFAPrompt: InitMFAPrompt:
Title: Impostazione a 2 fattori Title: Impostazione a 2 fattori
Description: L'autenticazione a due fattori offre un'ulteriore sicurezza al vostro account utente. Questo garantisce che solo voi possiate accedere al vostro account. Description: L'autenticazione a due fattori offre un'ulteriore sicurezza al vostro account utente. Questo garantisce che solo voi possiate accedere al vostro account.

View File

@ -79,6 +79,15 @@ InitUserDone:
NextButtonText: 次へ NextButtonText: 次へ
CancelButtonText: キャンセル CancelButtonText: キャンセル
InviteUser:
Title: ユーザーの有効化
Description: 下のコードでメールアドレスを確認し、パスワードを設定してください。
CodeLabel: コード
NewPasswordLabel: 新しいパスワード
NewPasswordConfirm: パスワードの確認
NextButtonText: 次へ
ResendButtonText: コードを再送信
InitMFAPrompt: InitMFAPrompt:
Title: 二要素認証のセットアップ Title: 二要素認証のセットアップ
Description: 二要素認証でアカウントのセキュリティを強化します。 Description: 二要素認証でアカウントのセキュリティを強化します。

View File

@ -86,6 +86,15 @@ InitUserDone:
NextButtonText: следно NextButtonText: следно
CancelButtonText: откажи CancelButtonText: откажи
InviteUser:
Title: Активирање на корисникот
Description: Проверете го вашиот имејл со кодот подолу и поставете ја вашата лозинка.
CodeLabel: Код
NewPasswordLabel: Нова лозинка
NewPasswordConfirm: Потврди лозинка
NextButtonText: Следно
ResendButtonText: Повторно испрати код
InitMFAPrompt: InitMFAPrompt:
Title: Подесување на 2-факторска автентикација Title: Подесување на 2-факторска автентикација
Description: 2-факторската автентикација ви дава дополнителна безбедност за вашата корисничка сметка. Ова обезбедува само вие да имате пристап до вашата сметка. Description: 2-факторската автентикација ви дава дополнителна безбедност за вашата корисничка сметка. Ова обезбедува само вие да имате пристап до вашата сметка.

View File

@ -86,6 +86,15 @@ InitUserDone:
NextButtonText: Volgende NextButtonText: Volgende
CancelButtonText: Annuleren CancelButtonText: Annuleren
InviteUser:
Title: Gebruiker activeren
Description: Verifieer uw e-mail met de onderstaande code en stel uw wachtwoord in.
CodeLabel: Code
NewPasswordLabel: Nieuw wachtwoord
NewPasswordConfirm: Wachtwoord bevestigen
NextButtonText: Volgende
ResendButtonText: Code opnieuw verzenden
InitMFAPrompt: InitMFAPrompt:
Title: 2-Factor Setup Title: 2-Factor Setup
Description: 2-factor authenticatie geeft u extra beveiliging voor uw gebruikersaccount. Hierdoor bent u de enige die toegang heeft tot uw account. Description: 2-factor authenticatie geeft u extra beveiliging voor uw gebruikersaccount. Hierdoor bent u de enige die toegang heeft tot uw account.

View File

@ -86,6 +86,15 @@ InitUserDone:
NextButtonText: dalej NextButtonText: dalej
CancelButtonText: anuluj CancelButtonText: anuluj
InviteUser:
Title: Aktywuj użytkownika
Description: Zweryfikuj swój adres e-mail za pomocą poniższego kodu i ustaw swoje hasło.
CodeLabel: Kod
NewPasswordLabel: Nowe hasło
NewPasswordConfirm: Potwierdź hasło
NextButtonText: Dalej
ResendButtonText: Wyślij ponownie kod
InitMFAPrompt: InitMFAPrompt:
Title: Konfiguracja 2-etapowego uwierzytelniania Title: Konfiguracja 2-etapowego uwierzytelniania
Description: 2-etapowe uwierzytelnianie daje Ci dodatkową ochronę dla Twojego konta użytkownika. Dzięki temu masz pewność, że tylko Ty masz dostęp do swojego konta. Description: 2-etapowe uwierzytelnianie daje Ci dodatkową ochronę dla Twojego konta użytkownika. Dzięki temu masz pewność, że tylko Ty masz dostęp do swojego konta.

View File

@ -86,6 +86,15 @@ InitUserDone:
NextButtonText: próximo NextButtonText: próximo
CancelButtonText: cancelar CancelButtonText: cancelar
InviteUser:
Title: Ativar usuário
Description: Verifique seu e-mail com o código abaixo e defina sua senha.
CodeLabel: Código
NewPasswordLabel: Nova senha
NewPasswordConfirm: Confirmar senha
NextButtonText: Próximo
ResendButtonText: Reenviar código
InitMFAPrompt: InitMFAPrompt:
Title: Configuração de 2 fatores Title: Configuração de 2 fatores
Description: A autenticação de 2 fatores fornece uma segurança adicional para sua conta de usuário. Isso garante que apenas você tenha acesso à sua conta. Description: A autenticação de 2 fatores fornece uma segurança adicional para sua conta de usuário. Isso garante que apenas você tenha acesso à sua conta.

View File

@ -85,6 +85,15 @@ InitUserDone:
NextButtonText: далее NextButtonText: далее
CancelButtonText: отмена CancelButtonText: отмена
InviteUser:
Title: Активировать пользователя
Description: Проверьте свой адрес электронной почты с помощью кода ниже и установите свой пароль.
CodeLabel: Код
NewPasswordLabel: Новый пароль
NewPasswordConfirm: Подтвердить пароль
NextButtonText: Далее
ResendButtonText: Отправить код повторно
InitMFAPrompt: InitMFAPrompt:
Title: Установка двухфакторной аутентификации Title: Установка двухфакторной аутентификации
Description: Двухфакторная аутентификация обеспечивает дополнительную защиту вашей учётной записи. Description: Двухфакторная аутентификация обеспечивает дополнительную защиту вашей учётной записи.

View File

@ -86,6 +86,15 @@ InitUserDone:
NextButtonText: Fortsätt NextButtonText: Fortsätt
CancelButtonText: Avbryt CancelButtonText: Avbryt
InviteUser:
Title: Aktivera användare
Description: Verifiera din e-post med koden nedan och sätt ditt lösenord.
CodeLabel: Kod
NewPasswordLabel: Nytt lösenord
NewPasswordConfirm: Bekräfta lösenord
NextButtonText: Nästa
ResendButtonText: Skicka koden igen
InitMFAPrompt: InitMFAPrompt:
Title: tvåfaktorinställningar Title: tvåfaktorinställningar
Description: 2-factor-identifiering ökar säkerheten för ditt konto. Enbart du som har tillgång till enheten kan logga in. Description: 2-factor-identifiering ökar säkerheten för ditt konto. Enbart du som har tillgång till enheten kan logga in.

View File

@ -86,6 +86,15 @@ InitUserDone:
NextButtonText: 继续 NextButtonText: 继续
CancelButtonText: 取消 CancelButtonText: 取消
InviteUser:
Title: 激活用户
Description: 使用以下代码验证您的电子邮件并设置您的密码。
CodeLabel: 代码
NewPasswordLabel: 新密码
NewPasswordConfirm: 确认密码
NextButtonText: 下一步
ResendButtonText: 重新发送代码
InitMFAPrompt: InitMFAPrompt:
Title: 两步验证设置 Title: 两步验证设置
Description: 两步验证为您的账户提供了额外的安全保障。这确保只有你能访问你的账户。 Description: 两步验证为您的账户提供了额外的安全保障。这确保只有你能访问你的账户。

View File

@ -0,0 +1,63 @@
{{template "main-top" .}}
<div class="lgn-head">
<h1>{{t "InviteUser.Title"}}</h1>
{{ template "user-profile" . }}
<p>{{t "InviteUser.Description"}}</p>
</div>
<form action="{{ inviteUserUrl }}" method="POST">
{{ .CSRF }}
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="userID" value="{{ .UserID }}" />
<input type="hidden" name="orgID" value="{{ .OrgID }}" />
<input type="text" name="loginName" value="{{if .DisplayLoginNameSuffix}}{{.LoginName}}{{else}}{{.UserName}}{{end}}" autocomplete="username" class="hidden" />
<div class="fields">
<div class="field">
<label class="lgn-label" for="code">{{t "InviteUser.CodeLabel"}}</label>
<input class="lgn-input" {{if .ErrMessage}}shake {{end}} type="text" id="code" name="code" value="{{.Code}}" autocomplete="one-time-code" autofocus
required>
</div>
<div class="field">
<label class="lgn-label" for="password">{{t "InviteUser.NewPasswordLabel"}}</label>
<input data-minlength="{{ .MinLength }}" data-has-uppercase="{{ .HasUppercase }}"
data-has-lowercase="{{ .HasLowercase }}" data-has-number="{{ .HasNumber }}"
data-has-symbol="{{ .HasSymbol }}" class="lgn-input" type="password" id="password" name="password"
autocomplete="new-password" autofocus required>
</div>
<div class="field">
<label class="lgn-label" for="passwordconfirm">{{t "InviteUser.NewPasswordConfirm"}}</label>
<input class="lgn-input" type="password" id="passwordconfirm" name="passwordconfirm"
autocomplete="new-password" autofocus required>
{{ template "password-complexity-policy-description" . }}
</div>
</div>
{{ template "error-message" .}}
<div class="lgn-actions lgn-reverse-order">
<!-- position element in header -->
<a class="lgn-icon-button lgn-left-action" href="{{ loginUrl }}">
<i class="lgn-icon-arrow-left-solid"></i>
</a>
<button type="submit" id="init-button" name="resend" value="false"
class="lgn-primary lgn-raised-button">{{t "InviteUser.NextButtonText"}}</button>
<span class="fill-space"></span>
<button type="submit" name="resend" value="true" class="lgn-stroked-button" formnovalidate>{{t "InviteUser.ResendButtonText"}}</button>
</div>
</form>
<script src="{{ resourceUrl "scripts/form_submit.js" }}"></script>
<script src="{{ resourceUrl "scripts/password_policy_check.js" }}"></script>
<script src="{{ resourceUrl "scripts/init_password_check.js" }}"></script>
{{template "main-bottom" .}}

View File

@ -106,6 +106,7 @@ type idpUserLinksProvider interface {
type userEventProvider interface { type userEventProvider interface {
UserEventsByID(ctx context.Context, id string, changeDate time.Time, eventTypes []eventstore.EventType) ([]eventstore.Event, error) UserEventsByID(ctx context.Context, id string, changeDate time.Time, eventTypes []eventstore.EventType) ([]eventstore.Event, error)
PasswordCodeExists(ctx context.Context, userID string) (exists bool, err error) PasswordCodeExists(ctx context.Context, userID string) (exists bool, err error)
InviteCodeExists(ctx context.Context, userID string) (exists bool, err error)
} }
type userCommandProvider interface { type userCommandProvider interface {
@ -1254,8 +1255,18 @@ func (repo *AuthRequestRepo) firstFactorChecked(ctx context.Context, request *do
if user.PasswordInitRequired { if user.PasswordInitRequired {
if !user.IsEmailVerified { if !user.IsEmailVerified {
// If the user was created through the user resource API,
// they can either have an invite code...
exists, err := repo.UserEventProvider.InviteCodeExists(ctx, user.ID)
logging.WithFields("userID", user.ID).OnError(err).Error("unable to check if invite code exists")
if err == nil && exists {
return &domain.VerifyInviteStep{}
}
// or were created with an explicit email verification mail
return &domain.VerifyEMailStep{InitPassword: true} return &domain.VerifyEMailStep{InitPassword: true}
} }
// If they were created with a verified mail, they might have never received mail to set their password,
// e.g. when created through a user resource API. In this case we'll just create and send one now.
exists, err := repo.UserEventProvider.PasswordCodeExists(ctx, user.ID) exists, err := repo.UserEventProvider.PasswordCodeExists(ctx, user.ID)
logging.WithFields("userID", user.ID).OnError(err).Error("unable to check if password code exists") logging.WithFields("userID", user.ID).OnError(err).Error("unable to check if password code exists")
if err == nil && !exists { if err == nil && !exists {

View File

@ -111,7 +111,8 @@ func (m *mockViewNoUser) UserByID(context.Context, string, string) (*user_view_m
type mockEventUser struct { type mockEventUser struct {
Events []eventstore.Event Events []eventstore.Event
CodeExists bool PwCodeExists bool
InvitationCodeExists bool
} }
func (m *mockEventUser) UserEventsByID(ctx context.Context, id string, changeDate time.Time, types []eventstore.EventType) ([]eventstore.Event, error) { func (m *mockEventUser) UserEventsByID(ctx context.Context, id string, changeDate time.Time, types []eventstore.EventType) ([]eventstore.Event, error) {
@ -119,7 +120,11 @@ func (m *mockEventUser) UserEventsByID(ctx context.Context, id string, changeDat
} }
func (m *mockEventUser) PasswordCodeExists(ctx context.Context, userID string) (bool, error) { func (m *mockEventUser) PasswordCodeExists(ctx context.Context, userID string) (bool, error) {
return m.CodeExists, nil return m.PwCodeExists, nil
}
func (m *mockEventUser) InviteCodeExists(ctx context.Context, userID string) (bool, error) {
return m.InvitationCodeExists, nil
} }
func (m *mockEventUser) GetLatestUserSessionSequence(ctx context.Context, instanceID string) (*query.CurrentState, error) { func (m *mockEventUser) GetLatestUserSessionSequence(ctx context.Context, instanceID string) (*query.CurrentState, error) {
@ -140,6 +145,10 @@ func (m *mockEventErrUser) PasswordCodeExists(ctx context.Context, userID string
return false, zerrors.ThrowInternal(nil, "id", "internal error") return false, zerrors.ThrowInternal(nil, "id", "internal error")
} }
func (m *mockEventErrUser) InviteCodeExists(ctx context.Context, userID string) (bool, error) {
return false, zerrors.ThrowInternal(nil, "id", "internal error")
}
type mockViewUser struct { type mockViewUser struct {
InitRequired bool InitRequired bool
PasswordInitRequired bool PasswordInitRequired bool
@ -1019,6 +1028,36 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
[]domain.NextStep{&domain.VerifyEMailStep{}}, []domain.NextStep{&domain.VerifyEMailStep{}},
nil, nil,
}, },
{
"password not set (email not verified), invite code exists, invite step",
fields{
userSessionViewProvider: &mockViewUserSession{},
userViewProvider: &mockViewUser{
PasswordInitRequired: true,
},
userEventProvider: &mockEventUser{
InvitationCodeExists: true,
},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &query.LockoutPolicy{
ShowFailures: true,
},
},
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
idpUserLinksProvider: &mockIDPUserLinks{},
},
args{
&domain.AuthRequest{
UserID: "UserID",
LoginPolicy: &domain.LoginPolicy{
AllowUsernamePassword: true,
},
},
false,
},
[]domain.NextStep{&domain.VerifyInviteStep{}},
nil,
},
{ {
"password not set (email not verified), init password step", "password not set (email not verified), init password step",
fields{ fields{
@ -1056,7 +1095,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true, IsEmailVerified: true,
}, },
userEventProvider: &mockEventUser{ userEventProvider: &mockEventUser{
CodeExists: true, PwCodeExists: true,
}, },
lockoutPolicyProvider: &mockLockoutPolicy{ lockoutPolicyProvider: &mockLockoutPolicy{
policy: &query.LockoutPolicy{ policy: &query.LockoutPolicy{
@ -1088,7 +1127,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true, IsEmailVerified: true,
}, },
userEventProvider: &mockEventUser{ userEventProvider: &mockEventUser{
CodeExists: false, PwCodeExists: false,
}, },
lockoutPolicyProvider: &mockLockoutPolicy{ lockoutPolicyProvider: &mockLockoutPolicy{
policy: &query.LockoutPolicy{ policy: &query.LockoutPolicy{

View File

@ -93,3 +93,41 @@ func (repo *UserRepo) PasswordCodeExists(ctx context.Context, userID string) (ex
} }
return model.exists, nil return model.exists, nil
} }
type inviteCodeCheck struct {
userID string
exists bool
events int
}
func (p *inviteCodeCheck) Reduce() error {
p.exists = p.events > 0
return nil
}
func (p *inviteCodeCheck) AppendEvents(events ...eventstore.Event) {
p.events += len(events)
}
func (p *inviteCodeCheck) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery().
AggregateTypes(user.AggregateType).
AggregateIDs(p.userID).
EventTypes(
user.HumanInviteCodeAddedType,
user.HumanInviteCodeSentType).
Builder()
}
func (repo *UserRepo) InviteCodeExists(ctx context.Context, userID string) (exists bool, err error) {
model := &inviteCodeCheck{
userID: userID,
}
err = repo.Eventstore.FilterToQueryReducer(ctx, model)
if err != nil {
return false, zerrors.ThrowPermissionDenied(err, "EVENT-GJ2os", "Errors.Internal")
}
return model.exists, nil
}

View File

@ -41,14 +41,6 @@ func newEncryptedCodeWithDefaultConfig(ctx context.Context, filter preparation.F
}, nil }, nil
} }
func verifyEncryptedCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, creation time.Time, expiry time.Duration, crypted *crypto.CryptoValue, plain string) error {
gen, _, err := encryptedCodeGenerator(ctx, filter, typ, alg, emptyConfig)
if err != nil {
return err
}
return crypto.VerifyCode(creation, expiry, crypted, plain, gen.Alg())
}
func encryptedCodeGenerator(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (crypto.Generator, *crypto.GeneratorConfig, error) { func encryptedCodeGenerator(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (crypto.Generator, *crypto.GeneratorConfig, error) {
config, err := cryptoGeneratorConfigWithDefault(ctx, filter, typ, defaultConfig) config, err := cryptoGeneratorConfigWithDefault(ctx, filter, typ, defaultConfig)
if err != nil { if err != nil {

View File

@ -123,78 +123,6 @@ func Test_newCryptoCode(t *testing.T) {
} }
} }
func Test_verifyCryptoCode(t *testing.T) {
es := eventstoreExpect(t, expectFilter(
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
))
code, err := newEncryptedCode(context.Background(), es.Filter, domain.SecretGeneratorTypeVerifyEmailCode, crypto.CreateMockEncryptionAlg(gomock.NewController(t))) //nolint:staticcheck
require.NoError(t, err)
type args struct {
typ domain.SecretGeneratorType
alg crypto.EncryptionAlgorithm
expiry time.Duration
crypted *crypto.CryptoValue
plain string
}
tests := []struct {
name string
eventsore *eventstore.Eventstore
args args
wantErr bool
}{
{
name: "filter config error",
eventsore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)),
args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
expiry: code.Expiry,
crypted: code.Crypted,
plain: code.Plain,
},
wantErr: true,
},
{
name: "success",
eventsore: eventstoreExpect(t, expectFilter(
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
)),
args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
expiry: code.Expiry,
crypted: code.Crypted,
plain: code.Plain,
},
},
{
name: "wrong plain",
eventsore: eventstoreExpect(t, expectFilter(
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
)),
args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
expiry: code.Expiry,
crypted: code.Crypted,
plain: "wrong",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := verifyEncryptedCode(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, time.Now(), tt.args.expiry, tt.args.crypted, tt.args.plain) //nolint:staticcheck
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
})
}
}
func Test_cryptoCodeGenerator(t *testing.T) { func Test_cryptoCodeGenerator(t *testing.T) {
type args struct { type args struct {
typ domain.SecretGeneratorType typ domain.SecretGeneratorType

View File

@ -12,6 +12,9 @@ type Email struct {
Address domain.EmailAddress Address domain.EmailAddress
Verified bool Verified bool
// NoEmailVerification is used Verified field is false
NoEmailVerification bool
// ReturnCode is used if the Verified field is false // ReturnCode is used if the Verified field is false
ReturnCode bool ReturnCode bool

View File

@ -145,6 +145,7 @@ type SecretGenerators struct {
DomainVerification *crypto.GeneratorConfig DomainVerification *crypto.GeneratorConfig
OTPSMS *crypto.GeneratorConfig OTPSMS *crypto.GeneratorConfig
OTPEmail *crypto.GeneratorConfig OTPEmail *crypto.GeneratorConfig
InviteCode *crypto.GeneratorConfig
} }
type ZitadelConfig struct { type ZitadelConfig struct {

View File

@ -388,6 +388,10 @@ func (c *Commands) newUserInitCode(ctx context.Context, filter preparation.Filte
return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypeInitCode, alg) return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypeInitCode, alg)
} }
func (c *Commands) newUserInviteCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) {
return c.newEncryptedCodeWithDefault(ctx, filter, domain.SecretGeneratorTypeInviteCode, alg, c.defaultSecretGenerators.InviteCode)
}
func userWriteModelByID(ctx context.Context, filter preparation.FilterToQueryReducer, userID, resourceOwner string) (*UserWriteModel, error) { func userWriteModelByID(ctx context.Context, filter preparation.FilterToQueryReducer, userID, resourceOwner string) (*UserWriteModel, error) {
user := NewUserWriteModel(userID, resourceOwner) user := NewUserWriteModel(userID, resourceOwner)
events, err := filter(ctx, user.Query()) events, err := filter(ctx, user.Query())

View File

@ -284,6 +284,9 @@ func (c *Commands) addHumanCommandEmail(ctx context.Context, filter preparation.
} }
return append(cmds, user.NewHumanInitialCodeAddedEvent(ctx, &a.Aggregate, initCode.Crypted, initCode.Expiry, human.AuthRequestID)), nil return append(cmds, user.NewHumanInitialCodeAddedEvent(ctx, &a.Aggregate, initCode.Crypted, initCode.Expiry, human.AuthRequestID)), nil
} }
if human.Email.NoEmailVerification {
return cmds, nil
}
if !human.Email.Verified { if !human.Email.Verified {
emailCode, err := c.newEmailCode(ctx, filter, codeAlg) emailCode, err := c.newEmailCode(ctx, filter, codeAlg)
if err != nil { if err != nil {

View File

@ -626,6 +626,67 @@ func TestCommandSide_AddHuman(t *testing.T) {
wantEmailCode: "emailCode", wantEmailCode: "emailCode",
}, },
}, },
{
name: "add human (with password and unverified email), ok (no email code)",
fields: fields{
eventstore: expectEventstore(
expectFilter(),
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
true,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
newAddHumanEvent("$plain$x$password", false, true, "", AllowedLanguage),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockEncryptedCode("emailCode", time.Hour),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &AddHuman{
Username: "username",
Password: "password",
FirstName: "firstname",
LastName: "lastname",
Email: Email{
Address: "email@test.ch",
Verified: false,
NoEmailVerification: true,
},
PreferredLanguage: AllowedLanguage,
},
secretGenerator: GetMockSecretGenerator(t),
allowInitMail: false,
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
wantID: "user1",
},
},
{ {
name: "add human email verified, ok", name: "add human email verified, ok",
fields: fields{ fields: fields{

View File

@ -1,6 +1,8 @@
package command package command
import ( import (
"context"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/user" "github.com/zitadel/zitadel/internal/repository/user"
@ -122,6 +124,10 @@ func UserAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregat
return eventstore.AggregateFromWriteModel(wm, user.AggregateType, user.AggregateVersion) return eventstore.AggregateFromWriteModel(wm, user.AggregateType, user.AggregateVersion)
} }
func UserAggregateFromWriteModelCtx(ctx context.Context, wm *eventstore.WriteModel) *eventstore.Aggregate {
return eventstore.AggregateFromWriteModelCtx(ctx, wm, user.AggregateType, user.AggregateVersion)
}
func isUserStateExists(state domain.UserState) bool { func isUserStateExists(state domain.UserState) bool {
return !hasUserState(state, domain.UserStateDeleted, domain.UserStateUnspecified) return !hasUserState(state, domain.UserStateDeleted, domain.UserStateUnspecified)
} }

View File

@ -0,0 +1,193 @@
package command
import (
"context"
"strings"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
type CreateUserInvite struct {
UserID string
URLTemplate string
ReturnCode bool
ApplicationName string
}
func (c *Commands) CreateInviteCode(ctx context.Context, invite *CreateUserInvite) (details *domain.ObjectDetails, returnCode *string, err error) {
invite.UserID = strings.TrimSpace(invite.UserID)
if invite.UserID == "" {
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4jio3", "Errors.User.UserIDMissing")
}
wm, err := c.userInviteCodeWriteModel(ctx, invite.UserID, "")
if err != nil {
return nil, nil, err
}
if err := c.checkPermission(ctx, domain.PermissionUserWrite, wm.ResourceOwner, wm.AggregateID); err != nil {
return nil, nil, err
}
if !wm.UserState.Exists() {
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Wgvn4", "Errors.User.NotFound")
}
if !wm.CreationAllowed() {
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-EF34g", "Errors.User.AlreadyInitialised")
}
code, err := c.newUserInviteCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint
if err != nil {
return nil, nil, err
}
err = c.pushAppendAndReduce(ctx, wm, user.NewHumanInviteCodeAddedEvent(
ctx,
UserAggregateFromWriteModelCtx(ctx, &wm.WriteModel),
code.Crypted,
code.Expiry,
invite.URLTemplate,
invite.ReturnCode,
invite.ApplicationName,
"",
))
if err != nil {
return nil, nil, err
}
if invite.ReturnCode {
returnCode = &code.Plain
}
return writeModelToObjectDetails(&wm.WriteModel), returnCode, nil
}
// ResendInviteCode resends the invite mail with a new code and an optional authRequestID.
// It will reuse the applicationName from the previous code.
func (c *Commands) ResendInviteCode(ctx context.Context, userID, resourceOwner, authRequestID string) (objectDetails *domain.ObjectDetails, err error) {
if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-2n8vs", "Errors.User.UserIDMissing")
}
existingCode, err := c.userInviteCodeWriteModel(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
if authz.GetCtxData(ctx).UserID != userID {
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingCode.ResourceOwner, userID); err != nil {
return nil, err
}
}
if !existingCode.UserState.Exists() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-H3b2a", "Errors.User.NotFound")
}
if !existingCode.CreationAllowed() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Gg42s", "Errors.User.AlreadyInitialised")
}
if existingCode.InviteCode == nil || existingCode.CodeReturned {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Wr3gq", "Errors.User.Code.NotFound")
}
code, err := c.newUserInviteCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint
if err != nil {
return nil, err
}
if authRequestID == "" {
authRequestID = existingCode.AuthRequestID
}
err = c.pushAppendAndReduce(ctx, existingCode,
user.NewHumanInviteCodeAddedEvent(
ctx,
UserAggregateFromWriteModelCtx(ctx, &existingCode.WriteModel),
code.Crypted,
code.Expiry,
existingCode.URLTemplate,
false,
existingCode.ApplicationName,
authRequestID,
))
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingCode.WriteModel), nil
}
func (c *Commands) InviteCodeSent(ctx context.Context, userID, orgID string) (err error) {
if userID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-Sgf31", "Errors.User.UserIDMissing")
}
existingCode, err := c.userInviteCodeWriteModel(ctx, userID, orgID)
if err != nil {
return err
}
if !existingCode.UserState.Exists() {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-HN34a", "Errors.User.NotFound")
}
if existingCode.InviteCode == nil || existingCode.CodeReturned {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-Wr3gq", "Errors.User.Code.NotFound")
}
userAgg := UserAggregateFromWriteModelCtx(ctx, &existingCode.WriteModel)
_, err = c.eventstore.Push(ctx, user.NewHumanInviteCodeSentEvent(ctx, userAgg))
return err
}
func (c *Commands) VerifyInviteCode(ctx context.Context, userID, code string) (details *domain.ObjectDetails, err error) {
return c.VerifyInviteCodeSetPassword(ctx, userID, code, "", "")
}
func (c *Commands) VerifyInviteCodeSetPassword(ctx context.Context, userID, code, password, userAgentID string) (details *domain.ObjectDetails, err error) {
if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Gk3f2", "Errors.User.UserIDMissing")
}
wm, err := c.userInviteCodeWriteModel(ctx, userID, "")
if err != nil {
return nil, err
}
if !wm.UserState.Exists() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-F5g2h", "Errors.User.NotFound")
}
userAgg := UserAggregateFromWriteModelCtx(ctx, &wm.WriteModel)
err = crypto.VerifyCode(wm.InviteCodeCreationDate, wm.InviteCodeExpiry, wm.InviteCode, code, c.userEncryption)
if err != nil {
_, err = c.eventstore.Push(ctx, user.NewHumanInviteCheckFailedEvent(ctx, userAgg))
logging.WithFields("userID", userAgg.ID).OnError(err).Error("NewHumanInviteCheckFailedEvent push failed")
return nil, zerrors.ThrowInvalidArgument(err, "COMMAND-Wgn4q", "Errors.User.Code.Invalid")
}
commands := []eventstore.Command{
user.NewHumanInviteCheckSucceededEvent(ctx, userAgg),
user.NewHumanEmailVerifiedEvent(ctx, userAgg),
}
if password != "" {
passwordCommand, err := c.setPasswordCommand(
ctx,
userAgg,
wm.UserState,
password,
"",
userAgentID,
false,
nil,
)
if err != nil {
return nil, err
}
commands = append(commands, passwordCommand)
}
err = c.pushAppendAndReduce(ctx, wm, commands...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&wm.WriteModel), nil
}
func (c *Commands) userInviteCodeWriteModel(ctx context.Context, userID, orgID string) (writeModel *UserV2InviteWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel = newUserV2InviteWriteModel(userID, orgID)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@ -0,0 +1,141 @@
package command
import (
"time"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/user"
)
type UserV2InviteWriteModel struct {
eventstore.WriteModel
InviteCode *crypto.CryptoValue
InviteCodeCreationDate time.Time
InviteCodeExpiry time.Duration
InviteCheckFailureCount uint8
ApplicationName string
AuthRequestID string
URLTemplate string
CodeReturned bool
EmailVerified bool
AuthMethodSet bool
UserState domain.UserState
}
func (wm *UserV2InviteWriteModel) CreationAllowed() bool {
return !wm.EmailVerified && !wm.AuthMethodSet
}
func newUserV2InviteWriteModel(userID, orgID string) *UserV2InviteWriteModel {
return &UserV2InviteWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: userID,
ResourceOwner: orgID,
},
}
}
func (wm *UserV2InviteWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *user.HumanAddedEvent:
wm.UserState = domain.UserStateActive
wm.AuthMethodSet = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""
wm.EmptyInviteCode()
wm.ApplicationName = ""
wm.AuthRequestID = ""
case *user.HumanRegisteredEvent:
wm.UserState = domain.UserStateActive
wm.AuthMethodSet = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""
wm.EmptyInviteCode()
wm.ApplicationName = ""
wm.AuthRequestID = ""
case *user.HumanInviteCodeAddedEvent:
wm.SetInviteCode(e.Code, e.Expiry, e.CreationDate())
wm.URLTemplate = e.URLTemplate
wm.CodeReturned = e.CodeReturned
wm.ApplicationName = e.ApplicationName
wm.AuthRequestID = e.AuthRequestID
case *user.HumanInviteCheckSucceededEvent:
wm.EmptyInviteCode()
case *user.HumanInviteCheckFailedEvent:
wm.InviteCheckFailureCount++
if wm.InviteCheckFailureCount >= 3 { //TODO: config?
wm.UserState = domain.UserStateDeleted
}
case *user.HumanEmailVerifiedEvent:
wm.EmailVerified = true
wm.EmptyInviteCode()
case *user.UserLockedEvent:
wm.UserState = domain.UserStateLocked
case *user.UserUnlockedEvent:
wm.UserState = domain.UserStateActive
case *user.UserDeactivatedEvent:
wm.UserState = domain.UserStateInactive
case *user.UserReactivatedEvent:
wm.UserState = domain.UserStateActive
case *user.UserRemovedEvent:
wm.UserState = domain.UserStateDeleted
case *user.HumanPasswordChangedEvent:
wm.AuthMethodSet = true
case *user.UserIDPLinkAddedEvent:
wm.AuthMethodSet = true
case *user.HumanPasswordlessVerifiedEvent:
wm.AuthMethodSet = true
}
}
return wm.WriteModel.Reduce()
}
func (wm *UserV2InviteWriteModel) SetInviteCode(code *crypto.CryptoValue, expiry time.Duration, creationDate time.Time) {
wm.InviteCode = code
wm.InviteCodeExpiry = expiry
wm.InviteCodeCreationDate = creationDate
wm.InviteCheckFailureCount = 0
}
func (wm *UserV2InviteWriteModel) EmptyInviteCode() {
wm.InviteCode = nil
wm.InviteCodeExpiry = 0
wm.InviteCodeCreationDate = time.Time{}
wm.InviteCheckFailureCount = 0
}
func (wm *UserV2InviteWriteModel) Query() *eventstore.SearchQueryBuilder {
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery().
AggregateTypes(user.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
user.UserV1AddedType,
user.HumanAddedType,
user.UserV1RegisteredType,
user.HumanRegisteredType,
user.HumanInviteCodeAddedType,
user.HumanInviteCheckSucceededType,
user.HumanInviteCheckFailedType,
user.UserV1EmailVerifiedType,
user.HumanEmailVerifiedType,
user.UserLockedType,
user.UserUnlockedType,
user.UserDeactivatedType,
user.UserReactivatedType,
user.UserRemovedType,
user.HumanPasswordChangedType,
user.UserV1PasswordChangedType,
user.UserIDPLinkAddedType,
user.HumanPasswordlessTokenVerifiedType,
).Builder()
if wm.ResourceOwner != "" {
query.ResourceOwner(wm.ResourceOwner)
}
return query
}
func (wm *UserV2InviteWriteModel) Aggregate() *user.Aggregate {
return user.NewAggregate(wm.AggregateID, wm.ResourceOwner)
}

File diff suppressed because it is too large Load Diff

View File

@ -48,7 +48,7 @@ func (c *Commands) verifyUserPasskeyCode(ctx context.Context, userID, resourceOw
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = verifyEncryptedCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg, wm.ChangeDate, wm.Expiration, wm.CryptoCode, code) //nolint:staticcheck err = crypto.VerifyCode(wm.ChangeDate, wm.Expiration, wm.CryptoCode, code, alg)
if err != nil || wm.State != domain.PasswordlessInitCodeStateActive { if err != nil || wm.State != domain.PasswordlessInitCodeStateActive {
c.verifyUserPasskeyCodeFailed(ctx, wm) c.verifyUserPasskeyCodeFailed(ctx, wm)
return nil, zerrors.ThrowInvalidArgument(err, "COMMAND-Eeb2a", "Errors.User.Code.Invalid") return nil, zerrors.ThrowInvalidArgument(err, "COMMAND-Eeb2a", "Errors.User.Code.Invalid")

View File

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

View File

@ -17,6 +17,7 @@ const (
DomainClaimedMessageType = "DomainClaimed" DomainClaimedMessageType = "DomainClaimed"
PasswordlessRegistrationMessageType = "PasswordlessRegistration" PasswordlessRegistrationMessageType = "PasswordlessRegistration"
PasswordChangeMessageType = "PasswordChange" PasswordChangeMessageType = "PasswordChange"
InviteUserMessageType = "InviteUser"
MessageTitle = "Title" MessageTitle = "Title"
MessagePreHeader = "PreHeader" MessagePreHeader = "PreHeader"
MessageSubject = "Subject" MessageSubject = "Subject"
@ -26,16 +27,6 @@ const (
MessageFooterText = "Footer" MessageFooterText = "Footer"
) )
type MessageTexts struct {
InitCode CustomMessageText
PasswordReset CustomMessageText
VerifyEmail CustomMessageText
VerifyPhone CustomMessageText
DomainClaimed CustomMessageText
PasswordlessRegistration CustomMessageText
PasswordChange CustomMessageText
}
type CustomMessageText struct { type CustomMessageText struct {
models.ObjectRoot models.ObjectRoot
@ -71,5 +62,6 @@ func IsMessageTextType(textType string) bool {
textType == VerifyEmailOTPMessageType || textType == VerifyEmailOTPMessageType ||
textType == DomainClaimedMessageType || textType == DomainClaimedMessageType ||
textType == PasswordlessRegistrationMessageType || textType == PasswordlessRegistrationMessageType ||
textType == PasswordChangeMessageType textType == PasswordChangeMessageType ||
textType == InviteUserMessageType
} }

View File

@ -29,6 +29,7 @@ const (
NextStepProjectRequired NextStepProjectRequired
NextStepRedirectToExternalIDP NextStepRedirectToExternalIDP
NextStepLoginSucceeded NextStepLoginSucceeded
NextStepVerifyInvite
) )
type LoginStep struct{} type LoginStep struct{}
@ -191,3 +192,9 @@ type LoginSucceededStep struct{}
func (s *LoginSucceededStep) Type() NextStepType { func (s *LoginSucceededStep) Type() NextStepType {
return NextStepLoginSucceeded return NextStepLoginSucceeded
} }
type VerifyInviteStep struct{}
func (s *VerifyInviteStep) Type() NextStepType {
return NextStepVerifyInvite
}

View File

@ -14,6 +14,7 @@ const (
SecretGeneratorTypeAppSecret SecretGeneratorTypeAppSecret
SecretGeneratorTypeOTPSMS SecretGeneratorTypeOTPSMS
SecretGeneratorTypeOTPEmail SecretGeneratorTypeOTPEmail
SecretGeneratorTypeInviteCode
secretGeneratorTypeCount secretGeneratorTypeCount
) )

View File

@ -4,14 +4,11 @@ package domain
import ( import (
"fmt" "fmt"
"strings"
) )
const _SecretGeneratorTypeName = "unspecifiedinit_codeverify_email_codeverify_phone_codeverify_domainpassword_reset_codepasswordless_init_codeapp_secretotpsmsotp_emailsecret_generator_type_count" const _SecretGeneratorTypeName = "unspecifiedinit_codeverify_email_codeverify_phone_codeverify_domainpassword_reset_codepasswordless_init_codeapp_secretotpsmsotp_emailinvite_codesecret_generator_type_count"
var _SecretGeneratorTypeIndex = [...]uint8{0, 11, 20, 37, 54, 67, 86, 108, 118, 124, 133, 160} var _SecretGeneratorTypeIndex = [...]uint8{0, 11, 20, 37, 54, 67, 86, 108, 118, 124, 133, 144, 171}
const _SecretGeneratorTypeLowerName = "unspecifiedinit_codeverify_email_codeverify_phone_codeverify_domainpassword_reset_codepasswordless_init_codeapp_secretotpsmsotp_emailsecret_generator_type_count"
func (i SecretGeneratorType) String() string { func (i SecretGeneratorType) String() string {
if i < 0 || i >= SecretGeneratorType(len(_SecretGeneratorTypeIndex)-1) { if i < 0 || i >= SecretGeneratorType(len(_SecretGeneratorTypeIndex)-1) {
@ -20,62 +17,21 @@ func (i SecretGeneratorType) String() string {
return _SecretGeneratorTypeName[_SecretGeneratorTypeIndex[i]:_SecretGeneratorTypeIndex[i+1]] return _SecretGeneratorTypeName[_SecretGeneratorTypeIndex[i]:_SecretGeneratorTypeIndex[i+1]]
} }
// An "invalid array index" compiler error signifies that the constant values have changed. var _SecretGeneratorTypeValues = []SecretGeneratorType{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
// Re-run the stringer command to generate them again.
func _SecretGeneratorTypeNoOp() {
var x [1]struct{}
_ = x[SecretGeneratorTypeUnspecified-(0)]
_ = x[SecretGeneratorTypeInitCode-(1)]
_ = x[SecretGeneratorTypeVerifyEmailCode-(2)]
_ = x[SecretGeneratorTypeVerifyPhoneCode-(3)]
_ = x[SecretGeneratorTypeVerifyDomain-(4)]
_ = x[SecretGeneratorTypePasswordResetCode-(5)]
_ = x[SecretGeneratorTypePasswordlessInitCode-(6)]
_ = x[SecretGeneratorTypeAppSecret-(7)]
_ = x[SecretGeneratorTypeOTPSMS-(8)]
_ = x[SecretGeneratorTypeOTPEmail-(9)]
_ = x[secretGeneratorTypeCount-(10)]
}
var _SecretGeneratorTypeValues = []SecretGeneratorType{SecretGeneratorTypeUnspecified, SecretGeneratorTypeInitCode, SecretGeneratorTypeVerifyEmailCode, SecretGeneratorTypeVerifyPhoneCode, SecretGeneratorTypeVerifyDomain, SecretGeneratorTypePasswordResetCode, SecretGeneratorTypePasswordlessInitCode, SecretGeneratorTypeAppSecret, SecretGeneratorTypeOTPSMS, SecretGeneratorTypeOTPEmail, secretGeneratorTypeCount}
var _SecretGeneratorTypeNameToValueMap = map[string]SecretGeneratorType{ var _SecretGeneratorTypeNameToValueMap = map[string]SecretGeneratorType{
_SecretGeneratorTypeName[0:11]: SecretGeneratorTypeUnspecified, _SecretGeneratorTypeName[0:11]: 0,
_SecretGeneratorTypeLowerName[0:11]: SecretGeneratorTypeUnspecified, _SecretGeneratorTypeName[11:20]: 1,
_SecretGeneratorTypeName[11:20]: SecretGeneratorTypeInitCode, _SecretGeneratorTypeName[20:37]: 2,
_SecretGeneratorTypeLowerName[11:20]: SecretGeneratorTypeInitCode, _SecretGeneratorTypeName[37:54]: 3,
_SecretGeneratorTypeName[20:37]: SecretGeneratorTypeVerifyEmailCode, _SecretGeneratorTypeName[54:67]: 4,
_SecretGeneratorTypeLowerName[20:37]: SecretGeneratorTypeVerifyEmailCode, _SecretGeneratorTypeName[67:86]: 5,
_SecretGeneratorTypeName[37:54]: SecretGeneratorTypeVerifyPhoneCode, _SecretGeneratorTypeName[86:108]: 6,
_SecretGeneratorTypeLowerName[37:54]: SecretGeneratorTypeVerifyPhoneCode, _SecretGeneratorTypeName[108:118]: 7,
_SecretGeneratorTypeName[54:67]: SecretGeneratorTypeVerifyDomain, _SecretGeneratorTypeName[118:124]: 8,
_SecretGeneratorTypeLowerName[54:67]: SecretGeneratorTypeVerifyDomain, _SecretGeneratorTypeName[124:133]: 9,
_SecretGeneratorTypeName[67:86]: SecretGeneratorTypePasswordResetCode, _SecretGeneratorTypeName[133:144]: 10,
_SecretGeneratorTypeLowerName[67:86]: SecretGeneratorTypePasswordResetCode, _SecretGeneratorTypeName[144:171]: 11,
_SecretGeneratorTypeName[86:108]: SecretGeneratorTypePasswordlessInitCode,
_SecretGeneratorTypeLowerName[86:108]: SecretGeneratorTypePasswordlessInitCode,
_SecretGeneratorTypeName[108:118]: SecretGeneratorTypeAppSecret,
_SecretGeneratorTypeLowerName[108:118]: SecretGeneratorTypeAppSecret,
_SecretGeneratorTypeName[118:124]: SecretGeneratorTypeOTPSMS,
_SecretGeneratorTypeLowerName[118:124]: SecretGeneratorTypeOTPSMS,
_SecretGeneratorTypeName[124:133]: SecretGeneratorTypeOTPEmail,
_SecretGeneratorTypeLowerName[124:133]: SecretGeneratorTypeOTPEmail,
_SecretGeneratorTypeName[133:160]: secretGeneratorTypeCount,
_SecretGeneratorTypeLowerName[133:160]: secretGeneratorTypeCount,
}
var _SecretGeneratorTypeNames = []string{
_SecretGeneratorTypeName[0:11],
_SecretGeneratorTypeName[11:20],
_SecretGeneratorTypeName[20:37],
_SecretGeneratorTypeName[37:54],
_SecretGeneratorTypeName[54:67],
_SecretGeneratorTypeName[67:86],
_SecretGeneratorTypeName[86:108],
_SecretGeneratorTypeName[108:118],
_SecretGeneratorTypeName[118:124],
_SecretGeneratorTypeName[124:133],
_SecretGeneratorTypeName[133:160],
} }
// SecretGeneratorTypeString retrieves an enum value from the enum constants string name. // SecretGeneratorTypeString retrieves an enum value from the enum constants string name.
@ -84,10 +40,6 @@ func SecretGeneratorTypeString(s string) (SecretGeneratorType, error) {
if val, ok := _SecretGeneratorTypeNameToValueMap[s]; ok { if val, ok := _SecretGeneratorTypeNameToValueMap[s]; ok {
return val, nil return val, nil
} }
if val, ok := _SecretGeneratorTypeNameToValueMap[strings.ToLower(s)]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to SecretGeneratorType values", s) return 0, fmt.Errorf("%s does not belong to SecretGeneratorType values", s)
} }
@ -96,13 +48,6 @@ func SecretGeneratorTypeValues() []SecretGeneratorType {
return _SecretGeneratorTypeValues return _SecretGeneratorTypeValues
} }
// SecretGeneratorTypeStrings returns a slice of all String values of the enum
func SecretGeneratorTypeStrings() []string {
strs := make([]string, len(_SecretGeneratorTypeNames))
copy(strs, _SecretGeneratorTypeNames)
return strs
}
// IsASecretGeneratorType returns "true" if the value is listed in the enum definition. "false" otherwise // IsASecretGeneratorType returns "true" if the value is listed in the enum definition. "false" otherwise
func (i SecretGeneratorType) IsASecretGeneratorType() bool { func (i SecretGeneratorType) IsASecretGeneratorType() bool {
for _, v := range _SecretGeneratorTypeValues { for _, v := range _SecretGeneratorTypeValues {

View File

@ -775,3 +775,12 @@ func (i *Instance) CreateSchemaUser(ctx context.Context, orgID string, schemaID
logging.OnError(err).Fatal("create user") logging.OnError(err).Fatal("create user")
return user return user
} }
func (i *Instance) CreateInviteCode(ctx context.Context, userID string) *user_v2.CreateInviteCodeResponse {
user, err := i.Client.UserV2.CreateInviteCode(ctx, &user_v2.CreateInviteCodeRequest{
UserId: userID,
Verification: &user_v2.CreateInviteCodeRequest_ReturnCode{ReturnCode: &user_v2.ReturnInviteCode{}},
})
logging.OnError(err).Fatal("create invite code")
return user
}

View File

@ -19,6 +19,7 @@ type Commands interface {
HumanPasswordlessInitCodeSent(ctx context.Context, userID, resourceOwner, codeID string) error HumanPasswordlessInitCodeSent(ctx context.Context, userID, resourceOwner, codeID string) error
PasswordChangeSent(ctx context.Context, orgID, userID string) error PasswordChangeSent(ctx context.Context, orgID, userID string) error
HumanPhoneVerificationCodeSent(ctx context.Context, orgID, userID string) error HumanPhoneVerificationCodeSent(ctx context.Context, orgID, userID string) error
InviteCodeSent(ctx context.Context, orgID, userID string) error
UsageNotificationSent(ctx context.Context, dueEvent *quota.NotificationDueEvent) error UsageNotificationSent(ctx context.Context, dueEvent *quota.NotificationDueEvent) error
MilestonePushed(ctx context.Context, msType milestone.Type, endpoints []string, primaryDomain string) error MilestonePushed(ctx context.Context, msType milestone.Type, endpoints []string, primaryDomain string) error
} }

View File

@ -18,30 +18,30 @@ import (
gomock "go.uber.org/mock/gomock" gomock "go.uber.org/mock/gomock"
) )
// MockCommands is a mock of Commands interface. // MockCommands is a mock of Commands interface
type MockCommands struct { type MockCommands struct {
ctrl *gomock.Controller ctrl *gomock.Controller
recorder *MockCommandsMockRecorder recorder *MockCommandsMockRecorder
} }
// MockCommandsMockRecorder is the mock recorder for MockCommands. // MockCommandsMockRecorder is the mock recorder for MockCommands
type MockCommandsMockRecorder struct { type MockCommandsMockRecorder struct {
mock *MockCommands mock *MockCommands
} }
// NewMockCommands creates a new mock instance. // NewMockCommands creates a new mock instance
func NewMockCommands(ctrl *gomock.Controller) *MockCommands { func NewMockCommands(ctrl *gomock.Controller) *MockCommands {
mock := &MockCommands{ctrl: ctrl} mock := &MockCommands{ctrl: ctrl}
mock.recorder = &MockCommandsMockRecorder{mock} mock.recorder = &MockCommandsMockRecorder{mock}
return mock return mock
} }
// EXPECT returns an object that allows the caller to indicate expected use. // EXPECT returns an object that allows the caller to indicate expected use
func (m *MockCommands) EXPECT() *MockCommandsMockRecorder { func (m *MockCommands) EXPECT() *MockCommandsMockRecorder {
return m.recorder return m.recorder
} }
// HumanEmailVerificationCodeSent mocks base method. // HumanEmailVerificationCodeSent mocks base method
func (m *MockCommands) HumanEmailVerificationCodeSent(arg0 context.Context, arg1, arg2 string) error { func (m *MockCommands) HumanEmailVerificationCodeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HumanEmailVerificationCodeSent", arg0, arg1, arg2) ret := m.ctrl.Call(m, "HumanEmailVerificationCodeSent", arg0, arg1, arg2)
@ -49,13 +49,13 @@ func (m *MockCommands) HumanEmailVerificationCodeSent(arg0 context.Context, arg1
return ret0 return ret0
} }
// HumanEmailVerificationCodeSent indicates an expected call of HumanEmailVerificationCodeSent. // HumanEmailVerificationCodeSent indicates an expected call of HumanEmailVerificationCodeSent
func (mr *MockCommandsMockRecorder) HumanEmailVerificationCodeSent(arg0, arg1, arg2 any) *gomock.Call { func (mr *MockCommandsMockRecorder) HumanEmailVerificationCodeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanEmailVerificationCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanEmailVerificationCodeSent), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanEmailVerificationCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanEmailVerificationCodeSent), arg0, arg1, arg2)
} }
// HumanInitCodeSent mocks base method. // HumanInitCodeSent mocks base method
func (m *MockCommands) HumanInitCodeSent(arg0 context.Context, arg1, arg2 string) error { func (m *MockCommands) HumanInitCodeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HumanInitCodeSent", arg0, arg1, arg2) ret := m.ctrl.Call(m, "HumanInitCodeSent", arg0, arg1, arg2)
@ -63,13 +63,13 @@ func (m *MockCommands) HumanInitCodeSent(arg0 context.Context, arg1, arg2 string
return ret0 return ret0
} }
// HumanInitCodeSent indicates an expected call of HumanInitCodeSent. // HumanInitCodeSent indicates an expected call of HumanInitCodeSent
func (mr *MockCommandsMockRecorder) HumanInitCodeSent(arg0, arg1, arg2 any) *gomock.Call { func (mr *MockCommandsMockRecorder) HumanInitCodeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanInitCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanInitCodeSent), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanInitCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanInitCodeSent), arg0, arg1, arg2)
} }
// HumanOTPEmailCodeSent mocks base method. // HumanOTPEmailCodeSent mocks base method
func (m *MockCommands) HumanOTPEmailCodeSent(arg0 context.Context, arg1, arg2 string) error { func (m *MockCommands) HumanOTPEmailCodeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HumanOTPEmailCodeSent", arg0, arg1, arg2) ret := m.ctrl.Call(m, "HumanOTPEmailCodeSent", arg0, arg1, arg2)
@ -77,13 +77,13 @@ func (m *MockCommands) HumanOTPEmailCodeSent(arg0 context.Context, arg1, arg2 st
return ret0 return ret0
} }
// HumanOTPEmailCodeSent indicates an expected call of HumanOTPEmailCodeSent. // HumanOTPEmailCodeSent indicates an expected call of HumanOTPEmailCodeSent
func (mr *MockCommandsMockRecorder) HumanOTPEmailCodeSent(arg0, arg1, arg2 any) *gomock.Call { func (mr *MockCommandsMockRecorder) HumanOTPEmailCodeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanOTPEmailCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanOTPEmailCodeSent), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanOTPEmailCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanOTPEmailCodeSent), arg0, arg1, arg2)
} }
// HumanOTPSMSCodeSent mocks base method. // HumanOTPSMSCodeSent mocks base method
func (m *MockCommands) HumanOTPSMSCodeSent(arg0 context.Context, arg1, arg2 string) error { func (m *MockCommands) HumanOTPSMSCodeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HumanOTPSMSCodeSent", arg0, arg1, arg2) ret := m.ctrl.Call(m, "HumanOTPSMSCodeSent", arg0, arg1, arg2)
@ -91,13 +91,13 @@ func (m *MockCommands) HumanOTPSMSCodeSent(arg0 context.Context, arg1, arg2 stri
return ret0 return ret0
} }
// HumanOTPSMSCodeSent indicates an expected call of HumanOTPSMSCodeSent. // HumanOTPSMSCodeSent indicates an expected call of HumanOTPSMSCodeSent
func (mr *MockCommandsMockRecorder) HumanOTPSMSCodeSent(arg0, arg1, arg2 any) *gomock.Call { func (mr *MockCommandsMockRecorder) HumanOTPSMSCodeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanOTPSMSCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanOTPSMSCodeSent), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanOTPSMSCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanOTPSMSCodeSent), arg0, arg1, arg2)
} }
// HumanPasswordlessInitCodeSent mocks base method. // HumanPasswordlessInitCodeSent mocks base method
func (m *MockCommands) HumanPasswordlessInitCodeSent(arg0 context.Context, arg1, arg2, arg3 string) error { func (m *MockCommands) HumanPasswordlessInitCodeSent(arg0 context.Context, arg1, arg2, arg3 string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HumanPasswordlessInitCodeSent", arg0, arg1, arg2, arg3) ret := m.ctrl.Call(m, "HumanPasswordlessInitCodeSent", arg0, arg1, arg2, arg3)
@ -105,13 +105,13 @@ func (m *MockCommands) HumanPasswordlessInitCodeSent(arg0 context.Context, arg1,
return ret0 return ret0
} }
// HumanPasswordlessInitCodeSent indicates an expected call of HumanPasswordlessInitCodeSent. // HumanPasswordlessInitCodeSent indicates an expected call of HumanPasswordlessInitCodeSent
func (mr *MockCommandsMockRecorder) HumanPasswordlessInitCodeSent(arg0, arg1, arg2, arg3 any) *gomock.Call { func (mr *MockCommandsMockRecorder) HumanPasswordlessInitCodeSent(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanPasswordlessInitCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanPasswordlessInitCodeSent), arg0, arg1, arg2, arg3) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanPasswordlessInitCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanPasswordlessInitCodeSent), arg0, arg1, arg2, arg3)
} }
// HumanPhoneVerificationCodeSent mocks base method. // HumanPhoneVerificationCodeSent mocks base method
func (m *MockCommands) HumanPhoneVerificationCodeSent(arg0 context.Context, arg1, arg2 string) error { func (m *MockCommands) HumanPhoneVerificationCodeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HumanPhoneVerificationCodeSent", arg0, arg1, arg2) ret := m.ctrl.Call(m, "HumanPhoneVerificationCodeSent", arg0, arg1, arg2)
@ -119,13 +119,27 @@ func (m *MockCommands) HumanPhoneVerificationCodeSent(arg0 context.Context, arg1
return ret0 return ret0
} }
// HumanPhoneVerificationCodeSent indicates an expected call of HumanPhoneVerificationCodeSent. // HumanPhoneVerificationCodeSent indicates an expected call of HumanPhoneVerificationCodeSent
func (mr *MockCommandsMockRecorder) HumanPhoneVerificationCodeSent(arg0, arg1, arg2 any) *gomock.Call { func (mr *MockCommandsMockRecorder) HumanPhoneVerificationCodeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanPhoneVerificationCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanPhoneVerificationCodeSent), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HumanPhoneVerificationCodeSent", reflect.TypeOf((*MockCommands)(nil).HumanPhoneVerificationCodeSent), arg0, arg1, arg2)
} }
// MilestonePushed mocks base method. // InviteCodeSent mocks base method
func (m *MockCommands) InviteCodeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InviteCodeSent", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// InviteCodeSent indicates an expected call of InviteCodeSent
func (mr *MockCommandsMockRecorder) InviteCodeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InviteCodeSent", reflect.TypeOf((*MockCommands)(nil).InviteCodeSent), arg0, arg1, arg2)
}
// MilestonePushed mocks base method
func (m *MockCommands) MilestonePushed(arg0 context.Context, arg1 milestone.Type, arg2 []string, arg3 string) error { func (m *MockCommands) MilestonePushed(arg0 context.Context, arg1 milestone.Type, arg2 []string, arg3 string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MilestonePushed", arg0, arg1, arg2, arg3) ret := m.ctrl.Call(m, "MilestonePushed", arg0, arg1, arg2, arg3)
@ -133,13 +147,13 @@ func (m *MockCommands) MilestonePushed(arg0 context.Context, arg1 milestone.Type
return ret0 return ret0
} }
// MilestonePushed indicates an expected call of MilestonePushed. // MilestonePushed indicates an expected call of MilestonePushed
func (mr *MockCommandsMockRecorder) MilestonePushed(arg0, arg1, arg2, arg3 any) *gomock.Call { func (mr *MockCommandsMockRecorder) MilestonePushed(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MilestonePushed", reflect.TypeOf((*MockCommands)(nil).MilestonePushed), arg0, arg1, arg2, arg3) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MilestonePushed", reflect.TypeOf((*MockCommands)(nil).MilestonePushed), arg0, arg1, arg2, arg3)
} }
// OTPEmailSent mocks base method. // OTPEmailSent mocks base method
func (m *MockCommands) OTPEmailSent(arg0 context.Context, arg1, arg2 string) error { func (m *MockCommands) OTPEmailSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OTPEmailSent", arg0, arg1, arg2) ret := m.ctrl.Call(m, "OTPEmailSent", arg0, arg1, arg2)
@ -147,13 +161,13 @@ func (m *MockCommands) OTPEmailSent(arg0 context.Context, arg1, arg2 string) err
return ret0 return ret0
} }
// OTPEmailSent indicates an expected call of OTPEmailSent. // OTPEmailSent indicates an expected call of OTPEmailSent
func (mr *MockCommandsMockRecorder) OTPEmailSent(arg0, arg1, arg2 any) *gomock.Call { func (mr *MockCommandsMockRecorder) OTPEmailSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OTPEmailSent", reflect.TypeOf((*MockCommands)(nil).OTPEmailSent), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OTPEmailSent", reflect.TypeOf((*MockCommands)(nil).OTPEmailSent), arg0, arg1, arg2)
} }
// OTPSMSSent mocks base method. // OTPSMSSent mocks base method
func (m *MockCommands) OTPSMSSent(arg0 context.Context, arg1, arg2 string) error { func (m *MockCommands) OTPSMSSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OTPSMSSent", arg0, arg1, arg2) ret := m.ctrl.Call(m, "OTPSMSSent", arg0, arg1, arg2)
@ -161,13 +175,13 @@ func (m *MockCommands) OTPSMSSent(arg0 context.Context, arg1, arg2 string) error
return ret0 return ret0
} }
// OTPSMSSent indicates an expected call of OTPSMSSent. // OTPSMSSent indicates an expected call of OTPSMSSent
func (mr *MockCommandsMockRecorder) OTPSMSSent(arg0, arg1, arg2 any) *gomock.Call { func (mr *MockCommandsMockRecorder) OTPSMSSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OTPSMSSent", reflect.TypeOf((*MockCommands)(nil).OTPSMSSent), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OTPSMSSent", reflect.TypeOf((*MockCommands)(nil).OTPSMSSent), arg0, arg1, arg2)
} }
// PasswordChangeSent mocks base method. // PasswordChangeSent mocks base method
func (m *MockCommands) PasswordChangeSent(arg0 context.Context, arg1, arg2 string) error { func (m *MockCommands) PasswordChangeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PasswordChangeSent", arg0, arg1, arg2) ret := m.ctrl.Call(m, "PasswordChangeSent", arg0, arg1, arg2)
@ -175,13 +189,13 @@ func (m *MockCommands) PasswordChangeSent(arg0 context.Context, arg1, arg2 strin
return ret0 return ret0
} }
// PasswordChangeSent indicates an expected call of PasswordChangeSent. // PasswordChangeSent indicates an expected call of PasswordChangeSent
func (mr *MockCommandsMockRecorder) PasswordChangeSent(arg0, arg1, arg2 any) *gomock.Call { func (mr *MockCommandsMockRecorder) PasswordChangeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordChangeSent", reflect.TypeOf((*MockCommands)(nil).PasswordChangeSent), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordChangeSent", reflect.TypeOf((*MockCommands)(nil).PasswordChangeSent), arg0, arg1, arg2)
} }
// PasswordCodeSent mocks base method. // PasswordCodeSent mocks base method
func (m *MockCommands) PasswordCodeSent(arg0 context.Context, arg1, arg2 string) error { func (m *MockCommands) PasswordCodeSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PasswordCodeSent", arg0, arg1, arg2) ret := m.ctrl.Call(m, "PasswordCodeSent", arg0, arg1, arg2)
@ -189,13 +203,13 @@ func (m *MockCommands) PasswordCodeSent(arg0 context.Context, arg1, arg2 string)
return ret0 return ret0
} }
// PasswordCodeSent indicates an expected call of PasswordCodeSent. // PasswordCodeSent indicates an expected call of PasswordCodeSent
func (mr *MockCommandsMockRecorder) PasswordCodeSent(arg0, arg1, arg2 any) *gomock.Call { func (mr *MockCommandsMockRecorder) PasswordCodeSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordCodeSent", reflect.TypeOf((*MockCommands)(nil).PasswordCodeSent), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordCodeSent", reflect.TypeOf((*MockCommands)(nil).PasswordCodeSent), arg0, arg1, arg2)
} }
// UsageNotificationSent mocks base method. // UsageNotificationSent mocks base method
func (m *MockCommands) UsageNotificationSent(arg0 context.Context, arg1 *quota.NotificationDueEvent) error { func (m *MockCommands) UsageNotificationSent(arg0 context.Context, arg1 *quota.NotificationDueEvent) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UsageNotificationSent", arg0, arg1) ret := m.ctrl.Call(m, "UsageNotificationSent", arg0, arg1)
@ -203,13 +217,13 @@ func (m *MockCommands) UsageNotificationSent(arg0 context.Context, arg1 *quota.N
return ret0 return ret0
} }
// UsageNotificationSent indicates an expected call of UsageNotificationSent. // UsageNotificationSent indicates an expected call of UsageNotificationSent
func (mr *MockCommandsMockRecorder) UsageNotificationSent(arg0, arg1 any) *gomock.Call { func (mr *MockCommandsMockRecorder) UsageNotificationSent(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UsageNotificationSent", reflect.TypeOf((*MockCommands)(nil).UsageNotificationSent), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UsageNotificationSent", reflect.TypeOf((*MockCommands)(nil).UsageNotificationSent), arg0, arg1)
} }
// UserDomainClaimedSent mocks base method. // UserDomainClaimedSent mocks base method
func (m *MockCommands) UserDomainClaimedSent(arg0 context.Context, arg1, arg2 string) error { func (m *MockCommands) UserDomainClaimedSent(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UserDomainClaimedSent", arg0, arg1, arg2) ret := m.ctrl.Call(m, "UserDomainClaimedSent", arg0, arg1, arg2)
@ -217,8 +231,8 @@ func (m *MockCommands) UserDomainClaimedSent(arg0 context.Context, arg1, arg2 st
return ret0 return ret0
} }
// UserDomainClaimedSent indicates an expected call of UserDomainClaimedSent. // UserDomainClaimedSent indicates an expected call of UserDomainClaimedSent
func (mr *MockCommandsMockRecorder) UserDomainClaimedSent(arg0, arg1, arg2 any) *gomock.Call { func (mr *MockCommandsMockRecorder) UserDomainClaimedSent(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserDomainClaimedSent", reflect.TypeOf((*MockCommands)(nil).UserDomainClaimedSent), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserDomainClaimedSent", reflect.TypeOf((*MockCommands)(nil).UserDomainClaimedSent), arg0, arg1, arg2)
} }

View File

@ -106,6 +106,10 @@ func (u *userNotifier) Reducers() []handler.AggregateReducer {
Event: user.HumanOTPEmailCodeAddedType, Event: user.HumanOTPEmailCodeAddedType,
Reduce: u.reduceOTPEmailCodeAdded, Reduce: u.reduceOTPEmailCodeAdded,
}, },
{
Event: user.HumanInviteCodeAddedType,
Reduce: u.reduceInviteCodeAdded,
},
}, },
}, },
{ {
@ -718,6 +722,61 @@ func (u *userNotifier) reducePhoneCodeAdded(event eventstore.Event) (*handler.St
}), nil }), nil
} }
func (u *userNotifier) reduceInviteCodeAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*user.HumanInviteCodeAddedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Eeg3s", "reduce.wrong.event.type %s", user.HumanInviteCodeAddedType)
}
if e.CodeReturned {
return handler.NewNoOpStatement(e), nil
}
return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
ctx := HandlerContext(event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.HumanInviteCodeAddedType, user.HumanInviteCodeSentType)
if err != nil {
return err
}
if alreadyHandled {
return nil
}
code, err := crypto.DecryptString(e.Code, u.queries.UserDataCrypto)
if err != nil {
return err
}
colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false)
if err != nil {
return err
}
template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false)
if err != nil {
return err
}
notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID)
if err != nil {
return err
}
translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.InviteUserMessageType)
if err != nil {
return err
}
ctx, err = u.queries.Origin(ctx, e)
if err != nil {
return err
}
notify := types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e)
err = notify.SendInviteCode(ctx, notifyUser, code, e.ApplicationName, e.URLTemplate, e.AuthRequestID)
if err != nil {
return err
}
return u.commands.InviteCodeSent(ctx, e.Aggregate().ID, e.Aggregate().ResourceOwner)
}), nil
}
func (u *userNotifier) checkIfCodeAlreadyHandledOrExpired(ctx context.Context, event eventstore.Event, expiry time.Duration, data map[string]interface{}, eventTypes ...eventstore.EventType) (bool, error) { func (u *userNotifier) checkIfCodeAlreadyHandledOrExpired(ctx context.Context, event eventstore.Event, expiry time.Duration, data map[string]interface{}, eventTypes ...eventstore.EventType) (bool, error) {
if event.CreatedAt().Add(expiry).Before(time.Now().UTC()) { if event.CreatedAt().Add(expiry).Before(time.Now().UTC()) {
return true, nil return true, nil

View File

@ -69,3 +69,10 @@ PasswordChange:
Паролата на вашия потребител е променена, ако тази промяна не е направена от Паролата на вашия потребител е променена, ако тази промяна не е направена от
вас, моля, незабавно нулирайте паролата си. вас, моля, незабавно нулирайте паролата си.
ButtonText: Влизам ButtonText: Влизам
InviteUser:
Title: Покана за {{.ApplicationName}}
PreHeader: Покана за {{.ApplicationName}}
Subject: Покана за {{.ApplicationName}}
Greeting: 'Здравейте {{.DisplayName}},'
Text: Вашият потребител е бил поканен за {{.ApplicationName}}. Моля, кликнете върху бутона по-долу, за да завършите процеса на покана. Ако не сте поискали този имейл, моля, игнорирайте го.
ButtonText: Приеми поканата

View File

@ -59,3 +59,10 @@ PasswordChange:
Greeting: Dobrý den, {{.DisplayName}}, Greeting: Dobrý den, {{.DisplayName}},
Text: Heslo vašeho uživatele bylo změněno. Pokud tato změna nebyla provedena Vámi pak doporučujeme okamžitě resetovat/změnit vaše heslo. Text: Heslo vašeho uživatele bylo změněno. Pokud tato změna nebyla provedena Vámi pak doporučujeme okamžitě resetovat/změnit vaše heslo.
ButtonText: Přihlásit se ButtonText: Přihlásit se
InviteUser:
Title: Pozvánka do {{.ApplicationName}}
PreHeader: Pozvánka do {{.ApplicationName}}
Subject: Pozvánka do {{.ApplicationName}}
Greeting: Dobrý den, {{.DisplayName}},
Text: Váš uživatel byl pozván do {{.ApplicationName}}. Klikněte prosím na tlačítko níže, abyste dokončili proces pozvání. Pokud jste o tento e-mail nepožádali, prosím, ignorujte ho.
ButtonText: Přijmout pozvání

View File

@ -59,3 +59,10 @@ PasswordChange:
Greeting: Hallo {{.DisplayName}}, Greeting: Hallo {{.DisplayName}},
Text: Dein Passwort wurde geändert. Wenn diese Änderung nicht von dir gemacht wurde, empfehlen wir das sofortige Zurücksetzen deines Passworts. Text: Dein Passwort wurde geändert. Wenn diese Änderung nicht von dir gemacht wurde, empfehlen wir das sofortige Zurücksetzen deines Passworts.
ButtonText: Login ButtonText: Login
InviteUser:
Title: Einladung zu {{.ApplicationName}}
PreHeader: Einladung zu {{.ApplicationName}}
Subject: Einladung zu {{.ApplicationName}}
Greeting: Hallo {{.DisplayName}},
Text: Ihr Benutzer wurde zu {{.ApplicationName}} eingeladen. Bitte klicken Sie auf die Schaltfläche unten, um den Einladungsprozess abzuschließen. Wenn Sie diese E-Mail nicht angefordert haben, ignorieren Sie sie bitte.
ButtonText: Einladung annehmen

View File

@ -59,3 +59,10 @@ PasswordChange:
Greeting: Hello {{.DisplayName}}, Greeting: Hello {{.DisplayName}},
Text: The password of your user has changed. If this change was not done by you, please be advised to immediately reset your password. Text: The password of your user has changed. If this change was not done by you, please be advised to immediately reset your password.
ButtonText: Login ButtonText: Login
InviteUser:
Title: Invitation to {{.ApplicationName}}
PreHeader: Invitation to {{.ApplicationName}}
Subject: Invitation to {{.ApplicationName}}
Greeting: Hello {{.DisplayName}},
Text: Your user has been invited to {{.ApplicationName}}. Please click the button below to finish the invite process. If you didn't ask for this mail, please ignore it.
ButtonText: Accept invite

View File

@ -59,3 +59,10 @@ PasswordChange:
Greeting: Hola {{.DisplayName}}, Greeting: Hola {{.DisplayName}},
Text: La contraseña de tu usuario ha sido cambiada, si este cambio no fue hecho por ti, por favor proceder a restablecer inmediatamente tu contraseña. Text: La contraseña de tu usuario ha sido cambiada, si este cambio no fue hecho por ti, por favor proceder a restablecer inmediatamente tu contraseña.
ButtonText: Iniciar sesión ButtonText: Iniciar sesión
InviteUser:
Title: Invitación a {{.ApplicationName}}
PreHeader: Invitación a {{.ApplicationName}}
Subject: Invitación a {{.ApplicationName}}
Greeting: Hola {{.DisplayName}},
Text: Tu usuario ha sido invitado a {{.ApplicationName}}. Haz clic en el botón de abajo para finalizar el proceso de invitación. Si no solicitaste este correo electrónico, por favor ignóralo.
ButtonText: Aceptar invitación

View File

@ -59,3 +59,10 @@ PasswordChange:
Greeting: Bonjour {{.DisplayName}}, Greeting: Bonjour {{.DisplayName}},
Text: Le mot de passe de votre utilisateur a changé, si ce changement n'a pas été fait par vous, nous vous conseillons de réinitialiser immédiatement votre mot de passe. Text: Le mot de passe de votre utilisateur a changé, si ce changement n'a pas été fait par vous, nous vous conseillons de réinitialiser immédiatement votre mot de passe.
ButtonText: Login ButtonText: Login
InviteUser:
Title: Invitation à {{.ApplicationName}}
PreHeader: Invitation à {{.ApplicationName}}
Subject: Invitation à {{.ApplicationName}}
Greeting: Bonjour {{.DisplayName}},
Text: Votre utilisateur a été invité à {{.ApplicationName}}. Veuillez cliquer sur le bouton ci-dessous pour terminer le processus d'invitation. Si vous n'avez pas demandé cet e-mail, veuillez l'ignorer.
ButtonText: Accepter l'invitation

View File

@ -59,3 +59,10 @@ PasswordChange:
Greeting: 'Halo {{.DisplayName}},' Greeting: 'Halo {{.DisplayName}},'
Text: 'Kata sandi pengguna Anda telah berubah. ' Text: 'Kata sandi pengguna Anda telah berubah. '
ButtonText: Login ButtonText: Login
InviteUser:
Title: Undangan ke {{.ApplicationName}}
PreHeader: Undangan ke {{.ApplicationName}}
Subject: Undangan ke {{.ApplicationName}}
Greeting: 'Halo {{.DisplayName}},'
Text: Pengguna Anda telah diundang ke {{.ApplicationName}}. Silakan klik tombol di bawah ini untuk menyelesaikan proses undangan. Jika Anda tidak meminta email ini, harap abaikan.
ButtonText: Terima undangan

View File

@ -59,3 +59,10 @@ PasswordChange:
Greeting: Ciao {{.DisplayName}}, Greeting: Ciao {{.DisplayName}},
Text: La password del vostro utente è cambiata; se questa modifica non è stata fatta da voi, vi consigliamo di reimpostare immediatamente la vostra password. Text: La password del vostro utente è cambiata; se questa modifica non è stata fatta da voi, vi consigliamo di reimpostare immediatamente la vostra password.
ButtonText: Login ButtonText: Login
InviteUser:
Title: Invito a {{.ApplicationName}}
PreHeader: Invito a {{.ApplicationName}}
Subject: Invito a {{.ApplicationName}}
Greeting: 'Ciao {{.DisplayName}},'
Text: Il tuo utente è stato invitato a {{.ApplicationName}}. Clicca sul pulsante qui sotto per completare il processo di invito. Se non hai richiesto questa email, ignorala.
ButtonText: Accetta invito

View File

@ -59,3 +59,10 @@ PasswordChange:
Greeting: こんにちは {{.DisplayName}} さん、 Greeting: こんにちは {{.DisplayName}} さん、
Text: ユーザーのパスワードが変更されました。この変更があなたによって行われなかった場合は、すぐにパスワードをリセットすることをお勧めします。 Text: ユーザーのパスワードが変更されました。この変更があなたによって行われなかった場合は、すぐにパスワードをリセットすることをお勧めします。
ButtonText: ログイン ButtonText: ログイン
InviteUser:
Title: '{{.ApplicationName}}への招待'
PreHeader: '{{.ApplicationName}}への招待'
Subject: '{{.ApplicationName}}への招待'
Greeting: こんにちは {{.DisplayName}} さん、
Text: あなたのユーザーは{{.ApplicationName}}に招待されました。下のボタンをクリックして、招待プロセスを完了してください。このメールをリクエストしていない場合は、無視してください。
ButtonText: 招待を受け入れる

View File

@ -59,3 +59,10 @@ PasswordChange:
Greeting: Здраво {{.DisplayName}}, Greeting: Здраво {{.DisplayName}},
Text: Лозинката на вашиот корисник е променета. Ако оваа промена не е извршена од вас, ве молиме веднаш ресетирајте ја вашата лозинка. Text: Лозинката на вашиот корисник е променета. Ако оваа промена не е извршена од вас, ве молиме веднаш ресетирајте ја вашата лозинка.
ButtonText: Најава ButtonText: Најава
InviteUser:
Title: Покана за {{.ApplicationName}}
PreHeader: Покана за {{.ApplicationName}}
Subject: Покана за {{.ApplicationName}}
Greeting: Здраво {{.DisplayName}},
Text: Вашиот корисник е бил поканет за {{.ApplicationName}}. Ве молиме кликнете на копчето подолу за да го завршите процесот на покана. Ако не сте побарале овој мејл, ве молиме игнорирајте го.
ButtonText: Прифати покана

View File

@ -59,3 +59,10 @@ PasswordChange:
Greeting: Hallo {{.DisplayName}}, Greeting: Hallo {{.DisplayName}},
Text: Het wachtwoord van uw gebruiker is veranderd. Als deze wijziging niet door u is gedaan, wordt u geadviseerd om direct uw wachtwoord te resetten. Text: Het wachtwoord van uw gebruiker is veranderd. Als deze wijziging niet door u is gedaan, wordt u geadviseerd om direct uw wachtwoord te resetten.
ButtonText: Inloggen ButtonText: Inloggen
InviteUser:
Title: Uitnodiging voor {{.ApplicationName}}
PreHeader: Uitnodiging voor {{.ApplicationName}}
Subject: Uitnodiging voor {{.ApplicationName}}
Greeting: Hallo {{.DisplayName}},
Text: Uw gebruiker is uitgenodigd voor {{.ApplicationName}}. Klik op de onderstaande knop om het uitnodigingsproces te voltooien. Als u deze e-mail niet hebt aangevraagd, negeer deze dan.
ButtonText: Uitnodiging accepteren

View File

@ -59,3 +59,10 @@ PasswordChange:
Greeting: Witaj {{.DisplayName}}, Greeting: Witaj {{.DisplayName}},
Text: Hasło Twojego użytkownika zostało zmienione, jeśli ta zmiana nie została dokonana przez Ciebie, zalecamy natychmiastowe zresetowanie hasła. Text: Hasło Twojego użytkownika zostało zmienione, jeśli ta zmiana nie została dokonana przez Ciebie, zalecamy natychmiastowe zresetowanie hasła.
ButtonText: Zaloguj się ButtonText: Zaloguj się
InviteUser:
Title: Zaproszenie do {{.ApplicationName}}
PreHeader: Zaproszenie do {{.ApplicationName}}
Subject: Zaproszenie do {{.ApplicationName}}
Greeting: Witaj {{.DisplayName}},
Text: Twój użytkownik został zaproszony do {{.ApplicationName}}. Kliknij poniższy przycisk, aby zakończyć proces zaproszenia. Jeśli nie zażądałeś tego e-maila, zignoruj go.
ButtonText: Akceptuj zaproszenie

View File

@ -59,3 +59,10 @@ PasswordChange:
Greeting: Olá {{.DisplayName}}, Greeting: Olá {{.DisplayName}},
Text: A senha do seu usuário foi alterada. Se esta alteração não foi feita por você, recomendamos que você redefina sua senha imediatamente. Text: A senha do seu usuário foi alterada. Se esta alteração não foi feita por você, recomendamos que você redefina sua senha imediatamente.
ButtonText: Fazer login ButtonText: Fazer login
InviteUser:
Title: Convite para {{.ApplicationName}}
PreHeader: Convite para {{.ApplicationName}}
Subject: Convite para {{.ApplicationName}}
Greeting: Olá {{.DisplayName}},
Text: Seu usuário foi convidado para {{.ApplicationName}}. Clique no botão abaixo para concluir o processo de convite. Se você não solicitou este e-mail, por favor, ignore-o.
ButtonText: Aceitar convite

View File

@ -2,28 +2,28 @@ InitCode:
Title: Регистрация пользователя Title: Регистрация пользователя
PreHeader: Регистрация пользователя PreHeader: Регистрация пользователя
Subject: Регистрация пользователя Subject: Регистрация пользователя
Greeting: Здравствуйте {{.FirstName}} {{.LastName}}, Greeting: Здравствуйте {{.DisplayName}},
Text: Используйте логин {{.PreferredLoginName}} для входа. Пожалуйста, нажмите кнопку ниже для завершения процесса регистрации. (Код {{.Code}}) Если вы не запрашивали это письмо, пожалуйста, проигнорируйте его. Text: Используйте логин {{.PreferredLoginName}} для входа. Пожалуйста, нажмите кнопку ниже для завершения процесса регистрации. (Код {{.Code}}) Если вы не запрашивали это письмо, пожалуйста, проигнорируйте его.
ButtonText: Завершить регистрацию ButtonText: Завершить регистрацию
PasswordReset: PasswordReset:
Title: Сброс пароля Title: Сброс пароля
PreHeader: Сброс пароля PreHeader: Сброс пароля
Subject: Сброс пароля Subject: Сброс пароля
Greeting: Здравствуйте {{.FirstName}} {{.LastName}}, Greeting: Здравствуйте {{.DisplayName}},
Text: Мы получили запрос на сброс пароля. Пожалуйста, нажмите кнопку ниже для сброса вашего пароля. (Код {{.Code}}) Если вы не запрашивали это письмо, пожалуйста, проигнорируйте его. Text: Мы получили запрос на сброс пароля. Пожалуйста, нажмите кнопку ниже для сброса вашего пароля. (Код {{.Code}}) Если вы не запрашивали это письмо, пожалуйста, проигнорируйте его.
ButtonText: Сбросить пароль ButtonText: Сбросить пароль
VerifyEmail: VerifyEmail:
Title: Подтверждение email Title: Подтверждение email
PreHeader: Подтверждение email PreHeader: Подтверждение email
Subject: Подтверждение email Subject: Подтверждение email
Greeting: Здравствуйте {{.FirstName}} {{.LastName}}, Greeting: Здравствуйте {{.DisplayName}},
Text: Добавлен новый email. Пожалуйста, нажмите кнопку ниже для подтверждения вашего email. (Код {{.Code}}) Если вы не запрашивали это письмо, пожалуйста, проигнорируйте его. Text: Добавлен новый email. Пожалуйста, нажмите кнопку ниже для подтверждения вашего email. (Код {{.Code}}) Если вы не запрашивали это письмо, пожалуйста, проигнорируйте его.
ButtonText: Подтвердить email ButtonText: Подтвердить email
VerifyPhone: VerifyPhone:
Title: Подтверждение телефона Title: Подтверждение телефона
PreHeader: Подтверждение телефона PreHeader: Подтверждение телефона
Subject: Подтверждение телефона Subject: Подтверждение телефона
Greeting: Здравствуйте {{.FirstName}} {{.LastName}}, Greeting: Здравствуйте {{.DisplayName}},
Text: Добавлен новый номер телефона. Пожалуйста, используйте следующий код, чтобы подтвердить его. Код {{.Code}} Text: Добавлен новый номер телефона. Пожалуйста, используйте следующий код, чтобы подтвердить его. Код {{.Code}}
ButtonText: Подтвердить телефон ButtonText: Подтвердить телефон
VerifyEmailOTP: VerifyEmailOTP:
@ -42,20 +42,27 @@ DomainClaimed:
Title: Утверждение домена Title: Утверждение домена
PreHeader: Изменение email / логина PreHeader: Изменение email / логина
Subject: Домен был утвержден Subject: Домен был утвержден
Greeting: Здравствуйте {{.FirstName}} {{.LastName}}, Greeting: Здравствуйте {{.DisplayName}},
Text: Домен {{.Domain}} был утвержден организацией. Ваш текущий пользователь {{.Username}} не является частью этой организации. Вам необходимо изменить свой email при входе в систему. Мы создали временный логин ({{.TempUsername}}) для входа. Text: Домен {{.Domain}} был утвержден организацией. Ваш текущий пользователь {{.Username}} не является частью этой организации. Вам необходимо изменить свой email при входе в систему. Мы создали временный логин ({{.TempUsername}}) для входа.
ButtonText: Вход ButtonText: Вход
PasswordlessRegistration: PasswordlessRegistration:
Title: Добавление входа без пароля Title: Добавление входа без пароля
PreHeader: Добавление входа без пароля PreHeader: Добавление входа без пароля
Subject: Добавление входа без пароля Subject: Добавление входа без пароля
Greeting: Здравствуйте {{.FirstName}} {{.LastName}}, Greeting: Здравствуйте {{.DisplayName}},
Text: Мы получили запрос на добавление токена для входа без пароля. Пожалуйста, используйте кнопку ниже, чтобы добавить свой токен или устройство для входа без пароля. Text: Мы получили запрос на добавление токена для входа без пароля. Пожалуйста, используйте кнопку ниже, чтобы добавить свой токен или устройство для входа без пароля.
ButtonText: Добавить вход без пароля ButtonText: Добавить вход без пароля
PasswordChange: PasswordChange:
Title: Смена пароля пользователя Title: Смена пароля пользователя
PreHeader: Смена пароля PreHeader: Смена пароля
Subject: Пароль пользователя изменен Subject: Пароль пользователя изменен
Greeting: Здравствуйте {{.FirstName}} {{.LastName}}, Greeting: Здравствуйте {{.DisplayName}},
Text: Пароль пользователя был изменен. Если это изменение сделано не вами, советуем немедленно сбросить пароль. Text: Пароль пользователя был изменен. Если это изменение сделано не вами, советуем немедленно сбросить пароль.
ButtonText: Вход ButtonText: Вход
InviteUser:
Title: Приглашение в {{.ApplicationName}}
PreHeader: Приглашение в {{.ApplicationName}}
Subject: Приглашение в {{.ApplicationName}}
Greeting: Здравствуйте, {{.DisplayName}},
Text: Ваш пользователь был приглашен в {{.ApplicationName}}. Пожалуйста, нажмите кнопку ниже, чтобы завершить процесс приглашения. Если вы не запрашивали это письмо, пожалуйста, игнорируйте его.
ButtonText: Принять приглашение

View File

@ -59,3 +59,10 @@ PasswordChange:
Greeting: Hej {{.DisplayName}}, Greeting: Hej {{.DisplayName}},
Text: Lösenordet för din användare har ändrats. Om denna ändring inte gjordes av dig, vänligen återställ ditt lösenord omedelbart. Text: Lösenordet för din användare har ändrats. Om denna ändring inte gjordes av dig, vänligen återställ ditt lösenord omedelbart.
ButtonText: Logga in ButtonText: Logga in
InviteUser:
Title: Inbjudan till {{.ApplicationName}}
PreHeader: Inbjudan till {{.ApplicationName}}
Subject: Inbjudan till {{.ApplicationName}}
Greeting: Hej {{.DisplayName}},
Text: Din användare har blivit inbjuden till {{.ApplicationName}}. Klicka på knappen nedan för att slutföra inbjudansprocessen. Om du inte har begärt detta e-postmeddelande, ignorera det.
ButtonText: Acceptera inbjudan

View File

@ -59,3 +59,10 @@ PasswordChange:
Greeting: 你好 {{.DisplayName}}, Greeting: 你好 {{.DisplayName}},
Text: 您的用户的密码已经改变,如果这个改变不是由您做的,请注意立即重新设置您的密码。 Text: 您的用户的密码已经改变,如果这个改变不是由您做的,请注意立即重新设置您的密码。
ButtonText: 登录 ButtonText: 登录
InviteUser:
Title: '{{.ApplicationName}}邀请'
PreHeader: '{{.ApplicationName}}邀请'
Subject: '{{.ApplicationName}}邀请'
Greeting: 您好,{{.DisplayName}},
Text: 您的用户已被邀请加入{{.ApplicationName}}。请点击下面的按钮完成邀请过程。如果您没有请求此邮件,请忽略它。
ButtonText: 接受邀请

View File

@ -0,0 +1,31 @@
package types
import (
"context"
"strings"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
)
func (notify Notify) SendInviteCode(ctx context.Context, user *query.NotifyUser, code, applicationName, urlTmpl, authRequestID string) error {
var url string
if applicationName == "" {
applicationName = "ZITADEL"
}
if urlTmpl == "" {
url = login.InviteUserLink(http_utils.DomainContext(ctx).Origin(), user.ID, user.PreferredLoginName, code, user.ResourceOwner, authRequestID)
} else {
var buf strings.Builder
if err := domain.RenderConfirmURLTemplate(&buf, urlTmpl, user.ID, code, user.ResourceOwner); err != nil {
return err
}
url = buf.String()
}
args := make(map[string]interface{})
args["Code"] = code
args["ApplicationName"] = applicationName
return notify(url, args, domain.InviteUserMessageType, true)
}

View File

@ -33,6 +33,7 @@ type MessageTexts struct {
DomainClaimed MessageText DomainClaimed MessageText
PasswordlessRegistration MessageText PasswordlessRegistration MessageText
PasswordChange MessageText PasswordChange MessageText
InviteUser MessageText
} }
type MessageText struct { type MessageText struct {
@ -346,6 +347,8 @@ func (m *MessageTexts) GetMessageTextByType(msgType string) *MessageText {
return &m.PasswordlessRegistration return &m.PasswordlessRegistration
case domain.PasswordChangeMessageType: case domain.PasswordChangeMessageType:
return &m.PasswordChange return &m.PasswordChange
case domain.InviteUserMessageType:
return &m.InviteUser
} }
return nil return nil
} }

View File

@ -272,7 +272,8 @@ func isMessageTemplate(template string) bool {
template == domain.VerifyEmailOTPMessageType || template == domain.VerifyEmailOTPMessageType ||
template == domain.DomainClaimedMessageType || template == domain.DomainClaimedMessageType ||
template == domain.PasswordlessRegistrationMessageType || template == domain.PasswordlessRegistrationMessageType ||
template == domain.PasswordChangeMessageType template == domain.PasswordChangeMessageType ||
template == domain.InviteUserMessageType
} }
func isTitle(key string) bool { func isTitle(key string) bool {
return key == domain.MessageTitle return key == domain.MessageTitle

View File

@ -137,4 +137,8 @@ func init() {
eventstore.RegisterFilterEventMapper(AggregateType, MachineSecretCheckSucceededType, MachineSecretCheckSucceededEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, MachineSecretCheckSucceededType, MachineSecretCheckSucceededEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, MachineSecretCheckFailedType, MachineSecretCheckFailedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, MachineSecretCheckFailedType, MachineSecretCheckFailedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, MachineSecretHashUpdatedType, eventstore.GenericEventMapper[MachineSecretHashUpdatedEvent]) eventstore.RegisterFilterEventMapper(AggregateType, MachineSecretHashUpdatedType, eventstore.GenericEventMapper[MachineSecretHashUpdatedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, HumanInviteCodeAddedType, eventstore.GenericEventMapper[HumanInviteCodeAddedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, HumanInviteCodeSentType, eventstore.GenericEventMapper[HumanInviteCodeSentEvent])
eventstore.RegisterFilterEventMapper(AggregateType, HumanInviteCheckSucceededType, eventstore.GenericEventMapper[HumanInviteCheckSucceededEvent])
eventstore.RegisterFilterEventMapper(AggregateType, HumanInviteCheckFailedType, eventstore.GenericEventMapper[HumanInviteCheckFailedEvent])
} }

View File

@ -21,6 +21,10 @@ const (
HumanInitialCodeSentType = humanEventPrefix + "initialization.code.sent" HumanInitialCodeSentType = humanEventPrefix + "initialization.code.sent"
HumanInitializedCheckSucceededType = humanEventPrefix + "initialization.check.succeeded" HumanInitializedCheckSucceededType = humanEventPrefix + "initialization.check.succeeded"
HumanInitializedCheckFailedType = humanEventPrefix + "initialization.check.failed" HumanInitializedCheckFailedType = humanEventPrefix + "initialization.check.failed"
HumanInviteCodeAddedType = humanEventPrefix + "invite.code.added"
HumanInviteCodeSentType = humanEventPrefix + "invite.code.sent"
HumanInviteCheckSucceededType = humanEventPrefix + "invite.check.succeeded"
HumanInviteCheckFailedType = humanEventPrefix + "invite.check.failed"
HumanSignedOutType = humanEventPrefix + "signed.out" HumanSignedOutType = humanEventPrefix + "signed.out"
) )
@ -379,6 +383,137 @@ func HumanInitializedCheckFailedEventMapper(event eventstore.Event) (eventstore.
}, nil }, nil
} }
type HumanInviteCodeAddedEvent struct {
*eventstore.BaseEvent `json:"-"`
Code *crypto.CryptoValue `json:"code,omitempty"`
Expiry time.Duration `json:"expiry,omitempty"`
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
URLTemplate string `json:"urlTemplate,omitempty"`
CodeReturned bool `json:"codeReturned,omitempty"`
ApplicationName string `json:"applicationName,omitempty"`
AuthRequestID string `json:"authRequestID,omitempty"`
}
func (e *HumanInviteCodeAddedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
e.BaseEvent = b
}
func (e *HumanInviteCodeAddedEvent) Payload() interface{} {
return e
}
func (e *HumanInviteCodeAddedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return nil
}
func (e *HumanInviteCodeAddedEvent) TriggerOrigin() string {
return e.TriggeredAtOrigin
}
func NewHumanInviteCodeAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
code *crypto.CryptoValue,
expiry time.Duration,
urlTemplate string,
codeReturned bool,
applicationName string,
authRequestID string,
) *HumanInviteCodeAddedEvent {
return &HumanInviteCodeAddedEvent{
BaseEvent: eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanInviteCodeAddedType,
),
Code: code,
Expiry: expiry,
TriggeredAtOrigin: http.DomainContext(ctx).Origin(),
URLTemplate: urlTemplate,
CodeReturned: codeReturned,
ApplicationName: applicationName,
AuthRequestID: authRequestID,
}
}
type HumanInviteCodeSentEvent struct {
*eventstore.BaseEvent `json:"-"`
}
func (e *HumanInviteCodeSentEvent) SetBaseEvent(b *eventstore.BaseEvent) {
e.BaseEvent = b
}
func (e *HumanInviteCodeSentEvent) Payload() interface{} {
return nil
}
func (e *HumanInviteCodeSentEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return nil
}
func NewHumanInviteCodeSentEvent(ctx context.Context, aggregate *eventstore.Aggregate) *HumanInviteCodeSentEvent {
return &HumanInviteCodeSentEvent{
BaseEvent: eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanInviteCodeSentType,
),
}
}
type HumanInviteCheckSucceededEvent struct {
*eventstore.BaseEvent `json:"-"`
}
func (e *HumanInviteCheckSucceededEvent) SetBaseEvent(b *eventstore.BaseEvent) {
e.BaseEvent = b
}
func (e *HumanInviteCheckSucceededEvent) Payload() interface{} {
return nil
}
func (e *HumanInviteCheckSucceededEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return nil
}
func NewHumanInviteCheckSucceededEvent(ctx context.Context, aggregate *eventstore.Aggregate) *HumanInviteCheckSucceededEvent {
return &HumanInviteCheckSucceededEvent{
BaseEvent: eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanInviteCheckSucceededType,
),
}
}
type HumanInviteCheckFailedEvent struct {
*eventstore.BaseEvent `json:"-"`
}
func (e *HumanInviteCheckFailedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
e.BaseEvent = b
}
func (e *HumanInviteCheckFailedEvent) Payload() interface{} {
return nil
}
func (e *HumanInviteCheckFailedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return nil
}
func NewHumanInviteCheckFailedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *HumanInviteCheckFailedEvent {
return &HumanInviteCheckFailedEvent{
BaseEvent: eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanInviteCheckFailedType,
),
}
}
type HumanSignedOutEvent struct { type HumanSignedOutEvent struct {
eventstore.BaseEvent `json:"-"` eventstore.BaseEvent `json:"-"`

View File

@ -704,6 +704,13 @@ EventTypes:
check: check:
succeeded: Проверката за инициализация е успешна succeeded: Проверката за инициализация е успешна
failed: Проверката на инициализацията е неуспешна failed: Проверката на инициализацията е неуспешна
invite:
code:
added: Генериран е код за покана
sent: Изпратен е код за покана
check:
succeeded: Проверката на поканата е успешна
failed: Проверката на поканата е неуспешна
username: username:
reserved: Потребителското име е запазено reserved: Потребителското име е запазено
released: Потребителското име е освободено released: Потребителското име е освободено

View File

@ -685,6 +685,13 @@ EventTypes:
check: check:
succeeded: Kontrola inicializace byla úspěšná succeeded: Kontrola inicializace byla úspěšná
failed: Kontrola inicializace selhala failed: Kontrola inicializace selhala
invite:
code:
added: Vygenerován pozvánkový kód
sent: Pozvánkový kód byl odeslán
check:
succeeded: Kontrola pozvánky byla úspěšná
failed: Kontrola pozvánky selhala
username: username:
reserved: Uživatelské jméno rezervováno reserved: Uživatelské jméno rezervováno
released: Uživatelské jméno uvolněno released: Uživatelské jméno uvolněno

View File

@ -687,6 +687,13 @@ EventTypes:
check: check:
succeeded: Benutzerinitialisierung erfolgreich succeeded: Benutzerinitialisierung erfolgreich
failed: Benutzerinitialisierung fehlgeschlagen failed: Benutzerinitialisierung fehlgeschlagen
invite:
code:
added: Einladungscode generiert
sent: Einladungscode gesendet
check:
succeeded: Einladungsprüfung erfolgreich
failed: Einladungsprüfung fehlgeschlagen
username: username:
reserved: Benutzername reserviert reserved: Benutzername reserviert
released: Benutzername freigegeben released: Benutzername freigegeben

View File

@ -687,6 +687,13 @@ EventTypes:
check: check:
succeeded: Initialization check succeeded succeeded: Initialization check succeeded
failed: Initialization check failed failed: Initialization check failed
invite:
code:
added: Invitation code generated
sent: Invitation code sent
check:
succeeded: Invitation check succeeded
failed: Invitation check failed
username: username:
reserved: Username reserved reserved: Username reserved
released: Username released released: Username released

View File

@ -687,6 +687,13 @@ EventTypes:
check: check:
succeeded: Comprobación exitosa de la inicialización succeeded: Comprobación exitosa de la inicialización
failed: Fallo en la comprobación de la inicialización failed: Fallo en la comprobación de la inicialización
invite:
code:
added: Código de invitación generado
sent: Código de invitación enviado
check:
succeeded: Comprobación de invitación correcta
failed: Comprobación de invitación fallida
username: username:
reserved: Nombre de usuario reservado reserved: Nombre de usuario reservado
released: Nombre de usuario liberado released: Nombre de usuario liberado

View File

@ -685,6 +685,13 @@ EventTypes:
check: check:
succeeded: Vérification de l'initialisation réussie succeeded: Vérification de l'initialisation réussie
failed: La vérification de l'initialisation a échoué failed: La vérification de l'initialisation a échoué
invite:
code:
added: Code d'invitation généré
sent: Code d'invitation envoyé
check:
succeeded: Vérification de l'invitation réussie
failed: Vérification de l'invitation échouée
username: username:
reserved: Nom d'utilisateur réservé reserved: Nom d'utilisateur réservé
released: Nom d'utilisateur libéré released: Nom d'utilisateur libéré

View File

@ -680,6 +680,13 @@ EventTypes:
check: check:
succeeded: Pemeriksaan inisialisasi berhasil succeeded: Pemeriksaan inisialisasi berhasil
failed: Pemeriksaan inisialisasi gagal failed: Pemeriksaan inisialisasi gagal
invite:
code:
added: Kode undangan dihasilkan
sent: Kode undangan dikirim
check:
succeeded: Pemeriksaan undangan berhasil
failed: Pemeriksaan undangan gagal
username: username:
reserved: Nama pengguna dicadangkan reserved: Nama pengguna dicadangkan
released: Nama pengguna dirilis released: Nama pengguna dirilis

View File

@ -686,6 +686,13 @@ EventTypes:
check: check:
succeeded: Controllo dell'inizializzazione riuscito succeeded: Controllo dell'inizializzazione riuscito
failed: Controllo dell'inizializzazione fallito failed: Controllo dell'inizializzazione fallito
invite:
code:
added: Codice invito generato
sent: Codice invito inviato
check:
succeeded: Controllo invito riuscito
failed: Controllo invito fallito
username: username:
reserved: Nome utente riservato reserved: Nome utente riservato
released: Nome utente rilasciato released: Nome utente rilasciato

Some files were not shown because too many files have changed in this diff Show More