diff --git a/client/web/admin/src/components/Settings/System/CSystemEditorAuth.vue b/client/web/admin/src/components/Settings/System/CSystemEditorAuth.vue
index 347b12e00..e385ede2b 100644
--- a/client/web/admin/src/components/Settings/System/CSystemEditorAuth.vue
+++ b/client/web/admin/src/components/Settings/System/CSystemEditorAuth.vue
@@ -240,6 +240,38 @@
Hello,
Enter this code into your login form: {{.Code}}
Hello,
+To get started, please follow this link to accept the invitation and set up your password
+Once you accept the invitation, you will be able to logged in to your account.
+ {{template "email_general_footer" .}} diff --git a/server/system/commands/commands.go b/server/system/commands/commands.go index 6eaebd5d9..0a140eb1c 100644 --- a/server/system/commands/commands.go +++ b/server/system/commands/commands.go @@ -10,6 +10,7 @@ import ( type ( serviceInitializer interface { InitServices(ctx context.Context) error + Activate(ctx context.Context) error } ) @@ -18,3 +19,9 @@ func commandPreRunInitService(app serviceInitializer) func(*cobra.Command, []str return app.InitServices(cli.Context()) } } + +func commandPreRunInitActivate(app serviceInitializer) func(*cobra.Command, []string) error { + return func(_ *cobra.Command, _ []string) error { + return app.Activate(cli.Context()) + } +} diff --git a/server/system/commands/users.go b/server/system/commands/users.go index 4c1ff72ef..410f861d5 100644 --- a/server/system/commands/users.go +++ b/server/system/commands/users.go @@ -24,6 +24,7 @@ func Users(ctx context.Context, app serviceInitializer) *cobra.Command { flagPassword string flagMakePasswordLink bool flagRoles []string + flagSendEmail bool ) // User management commands. @@ -93,7 +94,7 @@ func Users(ctx context.Context, app serviceInitializer) *cobra.Command { Short: "Add new user", Args: cobra.MinimumNArgs(1), - PreRunE: commandPreRunInitService(app), + PreRunE: commandPreRunInitActivate(app), Run: func(cmd *cobra.Command, args []string) { ctx = auth.SetIdentityToContext(ctx, auth.ServiceUser()) @@ -148,6 +149,12 @@ func Users(ctx context.Context, app serviceInitializer) *cobra.Command { } } + if flagSendEmail { + if err = authSvc.SendInviteEmail(ctx, user.Email); err != nil { + cli.HandleError(err) + } + } + fmt.Fprintf(cmd.OutOrStdout(), "User created [%d].\n", user.ID) if flagMakePasswordLink && len(url) > 0 { @@ -195,6 +202,12 @@ func Users(ctx context.Context, app serviceInitializer) *cobra.Command { nil, "Add user to roles (use ID or handle, repeat for multiple roles)") + addCmd.Flags().BoolVar( + &flagSendEmail, + "send-invite", + false, + "Send invite email to user with accept invite link") + pwdCmd := &cobra.Command{ Use: "password [email]", Short: "Change password for user", diff --git a/server/system/service/auth_actions.gen.go b/server/system/service/auth_actions.gen.go index d842baf40..1ce58d94e 100644 --- a/server/system/service/auth_actions.gen.go +++ b/server/system/service/auth_actions.gen.go @@ -56,7 +56,6 @@ var ( // setEmail updates authActionProps's email // // This function is auto-generated. -// func (p *authActionProps) setEmail(email string) *authActionProps { p.email = email return p @@ -65,7 +64,6 @@ func (p *authActionProps) setEmail(email string) *authActionProps { // setProvider updates authActionProps's provider // // This function is auto-generated. -// func (p *authActionProps) setProvider(provider string) *authActionProps { p.provider = provider return p @@ -74,7 +72,6 @@ func (p *authActionProps) setProvider(provider string) *authActionProps { // setCredentials updates authActionProps's credentials // // This function is auto-generated. -// func (p *authActionProps) setCredentials(credentials *types.Credential) *authActionProps { p.credentials = credentials return p @@ -83,7 +80,6 @@ func (p *authActionProps) setCredentials(credentials *types.Credential) *authAct // setRole updates authActionProps's role // // This function is auto-generated. -// func (p *authActionProps) setRole(role *types.Role) *authActionProps { p.role = role return p @@ -92,7 +88,6 @@ func (p *authActionProps) setRole(role *types.Role) *authActionProps { // setUser updates authActionProps's user // // This function is auto-generated. -// func (p *authActionProps) setUser(user *types.User) *authActionProps { p.user = user return p @@ -101,7 +96,6 @@ func (p *authActionProps) setUser(user *types.User) *authActionProps { // Serialize converts authActionProps to actionlog.Meta // // This function is auto-generated. -// func (p authActionProps) Serialize() actionlog.Meta { var ( m = make(actionlog.Meta) @@ -134,7 +128,6 @@ func (p authActionProps) Serialize() actionlog.Meta { // tr translates string and replaces meta value placeholder with values // // This function is auto-generated. -// func (p authActionProps) Format(in string, err error) string { var ( pairs = []string{"{{err}}"} @@ -221,7 +214,6 @@ func (p authActionProps) Format(in string, err error) string { // String returns loggable description as string // // This function is auto-generated. -// func (a *authAction) String() string { var props = &authActionProps{} @@ -249,7 +241,6 @@ func (e *authAction) ToAction() *actionlog.Action { // AuthActionAuthenticate returns "system:auth.authenticate" action // // This function is auto-generated. -// func AuthActionAuthenticate(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -269,7 +260,6 @@ func AuthActionAuthenticate(props ...*authActionProps) *authAction { // AuthActionIssueToken returns "system:auth.issueToken" action // // This function is auto-generated. -// func AuthActionIssueToken(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -289,7 +279,6 @@ func AuthActionIssueToken(props ...*authActionProps) *authAction { // AuthActionValidateToken returns "system:auth.validateToken" action // // This function is auto-generated. -// func AuthActionValidateToken(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -309,7 +298,6 @@ func AuthActionValidateToken(props ...*authActionProps) *authAction { // AuthActionChangePassword returns "system:auth.changePassword" action // // This function is auto-generated. -// func AuthActionChangePassword(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -329,7 +317,6 @@ func AuthActionChangePassword(props ...*authActionProps) *authAction { // AuthActionInternalSignup returns "system:auth.internalSignup" action // // This function is auto-generated. -// func AuthActionInternalSignup(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -349,7 +336,6 @@ func AuthActionInternalSignup(props ...*authActionProps) *authAction { // AuthActionConfirmEmail returns "system:auth.confirmEmail" action // // This function is auto-generated. -// func AuthActionConfirmEmail(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -369,7 +355,6 @@ func AuthActionConfirmEmail(props ...*authActionProps) *authAction { // AuthActionExternalSignup returns "system:auth.externalSignup" action // // This function is auto-generated. -// func AuthActionExternalSignup(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -389,7 +374,6 @@ func AuthActionExternalSignup(props ...*authActionProps) *authAction { // AuthActionSendEmailConfirmationToken returns "system:auth.sendEmailConfirmationToken" action // // This function is auto-generated. -// func AuthActionSendEmailConfirmationToken(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -409,7 +393,6 @@ func AuthActionSendEmailConfirmationToken(props ...*authActionProps) *authAction // AuthActionSendPasswordResetToken returns "system:auth.sendPasswordResetToken" action // // This function is auto-generated. -// func AuthActionSendPasswordResetToken(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -429,7 +412,6 @@ func AuthActionSendPasswordResetToken(props ...*authActionProps) *authAction { // AuthActionExchangePasswordResetToken returns "system:auth.exchangePasswordResetToken" action // // This function is auto-generated. -// func AuthActionExchangePasswordResetToken(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -449,7 +431,6 @@ func AuthActionExchangePasswordResetToken(props ...*authActionProps) *authAction // AuthActionGeneratePasswordCreateToken returns "system:auth.generatePasswordCreateToken" action // // This function is auto-generated. -// func AuthActionGeneratePasswordCreateToken(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -469,7 +450,6 @@ func AuthActionGeneratePasswordCreateToken(props ...*authActionProps) *authActio // AuthActionAutoPromote returns "system:auth.autoPromote" action // // This function is auto-generated. -// func AuthActionAutoPromote(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -489,7 +469,6 @@ func AuthActionAutoPromote(props ...*authActionProps) *authAction { // AuthActionUpdateCredentials returns "system:auth.updateCredentials" action // // This function is auto-generated. -// func AuthActionUpdateCredentials(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -509,7 +488,6 @@ func AuthActionUpdateCredentials(props ...*authActionProps) *authAction { // AuthActionCreateCredentials returns "system:auth.createCredentials" action // // This function is auto-generated. -// func AuthActionCreateCredentials(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -529,7 +507,6 @@ func AuthActionCreateCredentials(props ...*authActionProps) *authAction { // AuthActionImpersonate returns "system:auth.impersonate" action // // This function is auto-generated. -// func AuthActionImpersonate(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -549,7 +526,6 @@ func AuthActionImpersonate(props ...*authActionProps) *authAction { // AuthActionTotpConfigure returns "system:auth.totpConfigure" action // // This function is auto-generated. -// func AuthActionTotpConfigure(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -569,7 +545,6 @@ func AuthActionTotpConfigure(props ...*authActionProps) *authAction { // AuthActionTotpRemove returns "system:auth.totpRemove" action // // This function is auto-generated. -// func AuthActionTotpRemove(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -589,7 +564,6 @@ func AuthActionTotpRemove(props ...*authActionProps) *authAction { // AuthActionTotpValidate returns "system:auth.totpValidate" action // // This function is auto-generated. -// func AuthActionTotpValidate(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -609,7 +583,6 @@ func AuthActionTotpValidate(props ...*authActionProps) *authAction { // AuthActionEmailOtpVerify returns "system:auth.emailOtpVerify" action // // This function is auto-generated. -// func AuthActionEmailOtpVerify(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -629,7 +602,6 @@ func AuthActionEmailOtpVerify(props ...*authActionProps) *authAction { // AuthActionAccessTokensRemoved returns "system:auth.accessTokensRemoved" action // // This function is auto-generated. -// func AuthActionAccessTokensRemoved(props ...*authActionProps) *authAction { a := &authAction{ timestamp: time.Now(), @@ -646,15 +618,32 @@ func AuthActionAccessTokensRemoved(props ...*authActionProps) *authAction { return a } +// AuthActionSendInviteEMail returns "system:auth.sendInviteEMail" action +// +// This function is auto-generated. +func AuthActionSendInviteEMail(props ...*authActionProps) *authAction { + a := &authAction{ + timestamp: time.Now(), + resource: "system:auth", + action: "sendInviteEMail", + log: "invite email sent to {{email}}", + severity: actionlog.Notice, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + // ********************************************************************************************************************* // ********************************************************************************************************************* // Error constructors // AuthErrGeneric returns "system:auth.generic" as *errors.Error // -// // This function is auto-generated. -// func AuthErrGeneric(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -688,9 +677,7 @@ func AuthErrGeneric(mm ...*authActionProps) *errors.Error { // AuthErrInvalidCredentials returns "system:auth.invalidCredentials" as *errors.Error // -// // This function is auto-generated. -// func AuthErrInvalidCredentials(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -724,9 +711,7 @@ func AuthErrInvalidCredentials(mm ...*authActionProps) *errors.Error { // AuthErrInvalidEmailFormat returns "system:auth.invalidEmailFormat" as *errors.Error // -// // This function is auto-generated. -// func AuthErrInvalidEmailFormat(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -758,9 +743,7 @@ func AuthErrInvalidEmailFormat(mm ...*authActionProps) *errors.Error { // AuthErrInvalidHandle returns "system:auth.invalidHandle" as *errors.Error // -// // This function is auto-generated. -// func AuthErrInvalidHandle(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -795,7 +778,6 @@ func AuthErrInvalidHandle(mm ...*authActionProps) *errors.Error { // Note: This error will be wrapped with safe (system:auth.invalidCredentials) error! // // This function is auto-generated. -// func AuthErrFailedForUnknownUser(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -835,7 +817,6 @@ func AuthErrFailedForUnknownUser(mm ...*authActionProps) *errors.Error { // Note: This error will be wrapped with safe (system:auth.invalidCredentials) error! // // This function is auto-generated. -// func AuthErrFailedForDeletedUser(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -875,7 +856,6 @@ func AuthErrFailedForDeletedUser(mm ...*authActionProps) *errors.Error { // Note: This error will be wrapped with safe (system:auth.invalidCredentials) error! // // This function is auto-generated. -// func AuthErrFailedForSuspendedUser(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -915,7 +895,6 @@ func AuthErrFailedForSuspendedUser(mm ...*authActionProps) *errors.Error { // Note: This error will be wrapped with safe (system:auth.invalidCredentials) error! // // This function is auto-generated. -// func AuthErrFailedForSystemUser(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -952,9 +931,7 @@ func AuthErrFailedForSystemUser(mm ...*authActionProps) *errors.Error { // AuthErrFailedUnconfirmedEmail returns "system:auth.failedUnconfirmedEmail" as *errors.Error // -// // This function is auto-generated. -// func AuthErrFailedUnconfirmedEmail(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -988,9 +965,7 @@ func AuthErrFailedUnconfirmedEmail(mm ...*authActionProps) *errors.Error { // AuthErrInternalLoginDisabledByConfig returns "system:auth.internalLoginDisabledByConfig" as *errors.Error // -// // This function is auto-generated. -// func AuthErrInternalLoginDisabledByConfig(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1022,9 +997,7 @@ func AuthErrInternalLoginDisabledByConfig(mm ...*authActionProps) *errors.Error // AuthErrInternalSignupDisabledByConfig returns "system:auth.internalSignupDisabledByConfig" as *errors.Error // -// // This function is auto-generated. -// func AuthErrInternalSignupDisabledByConfig(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1056,9 +1029,7 @@ func AuthErrInternalSignupDisabledByConfig(mm ...*authActionProps) *errors.Error // AuthErrPasswordChangeFailedForUnknownUser returns "system:auth.passwordChangeFailedForUnknownUser" as *errors.Error // -// // This function is auto-generated. -// func AuthErrPasswordChangeFailedForUnknownUser(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1090,9 +1061,7 @@ func AuthErrPasswordChangeFailedForUnknownUser(mm ...*authActionProps) *errors.E // AuthErrPasswordResetFailedOldPasswordCheckFailed returns "system:auth.passwordResetFailedOldPasswordCheckFailed" as *errors.Error // -// // This function is auto-generated. -// func AuthErrPasswordResetFailedOldPasswordCheckFailed(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1124,9 +1093,7 @@ func AuthErrPasswordResetFailedOldPasswordCheckFailed(mm ...*authActionProps) *e // AuthErrPasswordSetFailedReusedPasswordCheckFailed returns "system:auth.passwordSetFailedReusedPasswordCheckFailed" as *errors.Error // -// // This function is auto-generated. -// func AuthErrPasswordSetFailedReusedPasswordCheckFailed(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1158,9 +1125,7 @@ func AuthErrPasswordSetFailedReusedPasswordCheckFailed(mm ...*authActionProps) * // AuthErrPasswordCreateFailedForUnknownUser returns "system:auth.passwordCreateFailedForUnknownUser" as *errors.Error // -// // This function is auto-generated. -// func AuthErrPasswordCreateFailedForUnknownUser(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1192,9 +1157,7 @@ func AuthErrPasswordCreateFailedForUnknownUser(mm ...*authActionProps) *errors.E // AuthErrPasswordResetDisabledByConfig returns "system:auth.passwordResetDisabledByConfig" as *errors.Error // -// // This function is auto-generated. -// func AuthErrPasswordResetDisabledByConfig(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1226,9 +1189,7 @@ func AuthErrPasswordResetDisabledByConfig(mm ...*authActionProps) *errors.Error // AuthErrPasswordCreateDisabledByConfig returns "system:auth.passwordCreateDisabledByConfig" as *errors.Error // -// // This function is auto-generated. -// func AuthErrPasswordCreateDisabledByConfig(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1260,9 +1221,7 @@ func AuthErrPasswordCreateDisabledByConfig(mm ...*authActionProps) *errors.Error // AuthErrPasswordNotSecure returns "system:auth.passwordNotSecure" as *errors.Error // -// // This function is auto-generated. -// func AuthErrPasswordNotSecure(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1294,9 +1253,7 @@ func AuthErrPasswordNotSecure(mm ...*authActionProps) *errors.Error { // AuthErrExternalDisabledByConfig returns "system:auth.externalDisabledByConfig" as *errors.Error // -// // This function is auto-generated. -// func AuthErrExternalDisabledByConfig(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1330,9 +1287,7 @@ func AuthErrExternalDisabledByConfig(mm ...*authActionProps) *errors.Error { // AuthErrProfileWithoutValidEmail returns "system:auth.profileWithoutValidEmail" as *errors.Error // -// // This function is auto-generated. -// func AuthErrProfileWithoutValidEmail(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1366,9 +1321,7 @@ func AuthErrProfileWithoutValidEmail(mm ...*authActionProps) *errors.Error { // AuthErrCredentialsLinkedToInvalidUser returns "system:auth.credentialsLinkedToInvalidUser" as *errors.Error // -// // This function is auto-generated. -// func AuthErrCredentialsLinkedToInvalidUser(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1400,9 +1353,7 @@ func AuthErrCredentialsLinkedToInvalidUser(mm ...*authActionProps) *errors.Error // AuthErrInvalidToken returns "system:auth.invalidToken" as *errors.Error // -// // This function is auto-generated. -// func AuthErrInvalidToken(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1434,9 +1385,7 @@ func AuthErrInvalidToken(mm ...*authActionProps) *errors.Error { // AuthErrNotAllowedToImpersonate returns "system:auth.notAllowedToImpersonate" as *errors.Error // -// // This function is auto-generated. -// func AuthErrNotAllowedToImpersonate(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1468,9 +1417,7 @@ func AuthErrNotAllowedToImpersonate(mm ...*authActionProps) *errors.Error { // AuthErrNotAllowedToRemoveTOTP returns "system:auth.notAllowedToRemoveTOTP" as *errors.Error // -// // This function is auto-generated. -// func AuthErrNotAllowedToRemoveTOTP(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1502,9 +1449,7 @@ func AuthErrNotAllowedToRemoveTOTP(mm ...*authActionProps) *errors.Error { // AuthErrUnconfiguredTOTP returns "system:auth.unconfiguredTOTP" as *errors.Error // -// // This function is auto-generated. -// func AuthErrUnconfiguredTOTP(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1536,9 +1481,7 @@ func AuthErrUnconfiguredTOTP(mm ...*authActionProps) *errors.Error { // AuthErrNotAllowedToConfigureTOTP returns "system:auth.notAllowedToConfigureTOTP" as *errors.Error // -// // This function is auto-generated. -// func AuthErrNotAllowedToConfigureTOTP(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1570,9 +1513,7 @@ func AuthErrNotAllowedToConfigureTOTP(mm ...*authActionProps) *errors.Error { // AuthErrEnforcedMFAWithTOTP returns "system:auth.enforcedMFAWithTOTP" as *errors.Error // -// // This function is auto-generated. -// func AuthErrEnforcedMFAWithTOTP(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1604,9 +1545,7 @@ func AuthErrEnforcedMFAWithTOTP(mm ...*authActionProps) *errors.Error { // AuthErrInvalidTOTP returns "system:auth.invalidTOTP" as *errors.Error // -// // This function is auto-generated. -// func AuthErrInvalidTOTP(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1638,9 +1577,7 @@ func AuthErrInvalidTOTP(mm ...*authActionProps) *errors.Error { // AuthErrDisabledMFAWithTOTP returns "system:auth.disabledMFAWithTOTP" as *errors.Error // -// // This function is auto-generated. -// func AuthErrDisabledMFAWithTOTP(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1672,9 +1609,7 @@ func AuthErrDisabledMFAWithTOTP(mm ...*authActionProps) *errors.Error { // AuthErrDisabledMFAWithEmailOTP returns "system:auth.disabledMFAWithEmailOTP" as *errors.Error // -// // This function is auto-generated. -// func AuthErrDisabledMFAWithEmailOTP(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1706,9 +1641,7 @@ func AuthErrDisabledMFAWithEmailOTP(mm ...*authActionProps) *errors.Error { // AuthErrEnforcedMFAWithEmailOTP returns "system:auth.enforcedMFAWithEmailOTP" as *errors.Error // -// // This function is auto-generated. -// func AuthErrEnforcedMFAWithEmailOTP(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1740,9 +1673,7 @@ func AuthErrEnforcedMFAWithEmailOTP(mm ...*authActionProps) *errors.Error { // AuthErrInvalidEmailOTP returns "system:auth.invalidEmailOTP" as *errors.Error // -// // This function is auto-generated. -// func AuthErrInvalidEmailOTP(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1774,9 +1705,7 @@ func AuthErrInvalidEmailOTP(mm ...*authActionProps) *errors.Error { // AuthErrRateLimitExceeded returns "system:auth.rateLimitExceeded" as *errors.Error // -// // This function is auto-generated. -// func AuthErrRateLimitExceeded(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1810,9 +1739,7 @@ func AuthErrRateLimitExceeded(mm ...*authActionProps) *errors.Error { // AuthErrMaxUserLimitReached returns "system:auth.maxUserLimitReached" as *errors.Error // -// // This function is auto-generated. -// func AuthErrMaxUserLimitReached(mm ...*authActionProps) *errors.Error { var p = &authActionProps{} if len(mm) > 0 { @@ -1842,6 +1769,38 @@ func AuthErrMaxUserLimitReached(mm ...*authActionProps) *errors.Error { return e } +// AuthErrDisabledSendUserInviteEmail returns "system:auth.disabledSendUserInviteEmail" as *errors.Error +// +// This function is auto-generated. +func AuthErrDisabledSendUserInviteEmail(mm ...*authActionProps) *errors.Error { + var p = &authActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("sending user invite email is disabled", nil), + + errors.Meta("type", "disabledSendUserInviteEmail"), + errors.Meta("resource", "system:auth"), + + errors.Meta(authPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "auth.errors.disabledSendUserInviteEmail"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + // ********************************************************************************************************************* // ********************************************************************************************************************* @@ -1850,7 +1809,6 @@ func AuthErrMaxUserLimitReached(mm ...*authActionProps) *errors.Error { // It will wrap unrecognized/internal errors with generic errors. // // This function is auto-generated. -// func (svc auth) recordAction(ctx context.Context, props *authActionProps, actionFn func(...*authActionProps) *authAction, err error) error { if svc.actionlog == nil || actionFn == nil { // action log disabled or no action fn passed, return error as-is diff --git a/server/system/service/auth_actions.yaml b/server/system/service/auth_actions.yaml index f5ef0aaf3..0af499489 100644 --- a/server/system/service/auth_actions.yaml +++ b/server/system/service/auth_actions.yaml @@ -85,6 +85,9 @@ actions: - action: accessTokensRemoved log: "access tokens for {{user}} removed" + + - action: sendInviteEMail + log: "invite email sent to {{email}}" errors: - error: invalidCredentials message: "invalid username and password combination" @@ -213,3 +216,7 @@ errors: - error: maxUserLimitReached message: "you have reached your user limit, contact your Corteza administrator" severity: warning + + - error: disabledSendUserInviteEmail + message: "sending user invite email is disabled" + severity: warning diff --git a/server/system/service/auth_credentials.go b/server/system/service/auth_credentials.go index d72b9bcaf..0100d6958 100644 --- a/server/system/service/auth_credentials.go +++ b/server/system/service/auth_credentials.go @@ -32,6 +32,7 @@ const ( credentialsTypeCreatePasswordToken = "password-create-token" credentialsTypeMfaTotpSecret = "mfa-totp-secret" credentialsTypeMFAEmailOTP = "mfa-email-otp" + credentialsTypeInviteEmailToken = "invite-email-token" credentialsTokenLength = 32 @@ -40,6 +41,10 @@ const ( passwordMinLength = 8 passwordMaxLength = 256 + + passwordTokenValidity = 24 + emailOTPValidity = 60 + inviteTokenValidity = 72 ) var ( @@ -211,7 +216,7 @@ func (svc *auth) createUserToken(ctx context.Context, u *types.User, kind string case credentialsTypeMFAEmailOTP: expSec := svc.settings.Auth.MultiFactor.EmailOTP.Expires if expSec == 0 { - expSec = 60 + expSec = emailOTPValidity } expiresAt = now().Add(time.Second * time.Duration(expSec)) @@ -221,13 +226,22 @@ func (svc *auth) createUserToken(ctx context.Context, u *types.User, kind string case credentialsTypeCreatePasswordToken: expSec := svc.settings.Auth.Internal.PasswordCreate.Expires if expSec == 0 { - expSec = 24 + expSec = passwordTokenValidity } expiresAt = now().Add(time.Hour * time.Duration(expSec)) // random password string, "3i[g0|)z" token = fmt.Sprintf("%s", rand.Password(credentialsTokenLength)) + case credentialsTypeInviteEmailToken: + expSec := svc.settings.Auth.Internal.SendUserInviteEmail.Expires + if expSec == 0 { + expSec = inviteTokenValidity + } + + // set the expiration time for all invited user tokens sent via email to 72 hours. + expiresAt = now().Add(time.Hour * time.Duration(expSec)) + token = string(rand.Bytes(credentialsTokenLength)) default: // 1h expiration for all tokens send via email expiresAt = now().Add(time.Minute * 60) @@ -423,6 +437,10 @@ func (svc *auth) ValidatePasswordCreateToken(ctx context.Context, token string) return svc.loadFromTokenAndConfirmEmail(ctx, token, credentialsTypeCreatePasswordToken) } +func (svc *auth) ValidateInviteEmailToken(ctx context.Context, token string) (user *types.User, err error) { + return svc.loadFromTokenAndConfirmEmail(ctx, token, credentialsTypeInviteEmailToken) +} + // ExchangePasswordResetToken exchanges reset password token for a new one and returns it with user info func (svc *auth) ExchangePasswordResetToken(ctx context.Context, token string) (u *types.User, t string, err error) { var ( @@ -1184,3 +1202,40 @@ func checkPasswordStrength(password string, pc types.PasswordConstraints) bool { return true } + +func (svc *auth) SendInviteEmail(ctx context.Context, email string) (err error) { + var ( + u *types.User + + aam = &authActionProps{ + user: u, + email: email, + } + ) + + err = func() error { + if !svc.settings.Auth.Internal.SendUserInviteEmail.Enabled { + return AuthErrDisabledSendUserInviteEmail(aam) + } + + if u, err = store.LookupUserByEmail(ctx, svc.store, email); err != nil { + return err + } + + ctx = internalAuth.SetIdentityToContext(ctx, u) + + token, err := svc.createUserToken(ctx, u, credentialsTypeInviteEmailToken) + if err != nil { + return err + } + + err = svc.notifications.InviteEmail(ctx, u.Email, token) + if err != nil { + return err + } + + return nil + }() + + return svc.recordAction(ctx, aam, AuthActionSendInviteEMail, err) +} diff --git a/server/system/service/auth_notification.go b/server/system/service/auth_notification.go index bd496ec1b..1f9674af6 100644 --- a/server/system/service/auth_notification.go +++ b/server/system/service/auth_notification.go @@ -30,6 +30,7 @@ type ( EmailConfirmation(ctx context.Context, emailAddress string, url string) error PasswordReset(ctx context.Context, emailAddress string, url string) error PasswordCreate(url string) (string, error) + InviteEmail(ctx context.Context, emailAddress string, token string) error } ) @@ -68,6 +69,12 @@ func (svc authNotification) PasswordCreate(token string) (string, error) { return fmt.Sprintf("%s/create-password?token=%s", svc.opt.BaseURL, url.QueryEscape(token)), nil } +func (svc authNotification) InviteEmail(ctx context.Context, emailAddress string, token string) error { + return svc.send(ctx, "auth_email_user_invite", emailAddress, map[string]interface{}{ + "URL": fmt.Sprintf("%s/accept-invite?token=%s", svc.opt.BaseURL, url.QueryEscape(token)), + }) +} + func (svc authNotification) newMail() *gomail.Message { var ( m = mail.New() diff --git a/server/system/types/app_settings.go b/server/system/types/app_settings.go index fc244a14f..ee091afb5 100644 --- a/server/system/types/app_settings.go +++ b/server/system/types/app_settings.go @@ -58,6 +58,12 @@ type ( // Can users reset their passwords PasswordReset struct{ Enabled bool } `json:"-" kv:"password-reset"` + // When enabled, users added via CLI will receive an email with a link to reset their password. + SendUserInviteEmail struct { + Enabled bool + Expires uint + } `kv:"send-user-invite-email" json:"sendUserInviteEmail"` + // PasswordCreate setting for create password for user via generated link with token // If user has no password then link redirects to create password page // Otherwise it redirects to profile page of that user