fixed verification code
This commit is contained in:
@@ -33,7 +33,7 @@ func (pr *PublicRouter) logUserIn(ctx context.Context, _ *http.Request, req *sre
|
||||
return response.Internal(pr.logger, pr.service, err)
|
||||
}
|
||||
|
||||
if account.VerifyToken != "" {
|
||||
if !account.IsActive() {
|
||||
return response.Forbidden(pr.logger, pr.service, "account_not_verified", "Account verification required")
|
||||
}
|
||||
|
||||
|
||||
@@ -43,12 +43,8 @@ func (a *AccountAPI) getProfile(_ *http.Request, u *model.Account, token *srespo
|
||||
return sresponse.Account(a.logger, u, token)
|
||||
}
|
||||
|
||||
func (a *AccountAPI) reportTokenNotFound() http.HandlerFunc {
|
||||
return response.NotFound(a.logger, a.Name(), "No account found associated with given verifcation token")
|
||||
}
|
||||
|
||||
func (a *AccountAPI) sendWelcomeEmail(account *model.Account) error {
|
||||
if err := a.producer.SendMessage(an.AccountCreated(a.Name(), *account.GetID())); err != nil {
|
||||
func (a *AccountAPI) sendWelcomeEmail(account *model.Account, token string) error {
|
||||
if err := a.producer.SendMessage(an.AccountCreated(a.Name(), *account.GetID(), token)); err != nil {
|
||||
a.logger.Warn("Failed to send account creation notification", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
@@ -63,27 +59,23 @@ func (a *AccountAPI) sendVerificationMail(r *http.Request, paramGetter func(ctx
|
||||
return response.Internal(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
// Get the account
|
||||
// accnt, err := a.db.GetByEmail(ctx, paramGetter(u))
|
||||
// if err != nil || accnt == nil {
|
||||
// a.logger.Warn("Failed to ger user from db with", zap.Error(err), mzap.StorableRef(u))
|
||||
// return response.Internal(a.logger, a.Name(), err)
|
||||
// }
|
||||
accnt, err := paramGetter(r.Context(), a.db, u)
|
||||
ctx := r.Context()
|
||||
accnt, err := paramGetter(ctx, a.db, u)
|
||||
if err != nil || accnt == nil {
|
||||
a.logger.Warn("Failed to ger user from db with", zap.Error(err), mzap.StorableRef(u))
|
||||
return response.Internal(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
if accnt.VerifyToken == "" {
|
||||
a.logger.Debug("Verification token is empty", zap.Error(err), mzap.StorableRef(u))
|
||||
return a.reportTokenNotFound()
|
||||
token, err := a.accService.VerifyAccount(ctx, accnt)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to create verification token for account", zap.Error(err), mzap.StorableRef(accnt))
|
||||
return response.Internal(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
// Send welcome email
|
||||
if err = a.sendWelcomeEmail(accnt); err != nil {
|
||||
if err = a.sendWelcomeEmail(accnt, token); err != nil {
|
||||
a.logger.Warn("Failed to send verification email",
|
||||
zap.Error(err), mzap.StorableRef(u), zap.String("email", accnt.Login))
|
||||
zap.Error(err), mzap.StorableRef(accnt), zap.String("email", accnt.Login))
|
||||
return response.Internal(a.logger, a.Name(), err)
|
||||
}
|
||||
return response.Success(a.logger)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package accountapiimp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
@@ -108,8 +109,14 @@ func (a *AccountAPI) deleteAll(r *http.Request, account *model.Account, token *s
|
||||
}
|
||||
|
||||
// Delete everything (organization + account)
|
||||
if err := a.accService.DeleteAll(ctx, &org, account.ID); err != nil {
|
||||
a.logger.Error("Failed to delete all data", zap.Error(err), mzap.StorableRef(&org), mzap.StorableRef(account))
|
||||
if _, err := a.tf.CreateTransaction().Execute(ctx, func(c context.Context) (any, error) {
|
||||
if err := a.accService.DeleteAll(c, &org, account.ID); err != nil {
|
||||
a.logger.Warn("Failed to delete all data", zap.Error(err), mzap.StorableRef(&org), mzap.StorableRef(account))
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}); err != nil {
|
||||
a.logger.Warn("Failed to execute delete transaction", zap.Error(err), mzap.StorableRef(&org), mzap.StorableRef(account))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
mutil "github.com/tech/sendico/server/internal/mutil/param"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -15,19 +16,30 @@ func (a *AccountAPI) verify(r *http.Request) http.HandlerFunc {
|
||||
token := mutil.GetToken(r)
|
||||
// Get user
|
||||
ctx := r.Context()
|
||||
user, err := a.db.GetByToken(ctx, token)
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
a.logger.Debug("Verification token not found", zap.Error(err))
|
||||
return a.reportTokenNotFound()
|
||||
}
|
||||
// Delete verification token to confirm account
|
||||
t, err := a.vdb.Consume(ctx, token)
|
||||
if err != nil {
|
||||
a.logger.Debug("Failed to consume verification token", zap.Error(err))
|
||||
return a.mapTokenErrorToResponse(err)
|
||||
}
|
||||
|
||||
if t.Purpose != model.PurposeAccountActivation {
|
||||
a.logger.Warn("Invalid token purpose", zap.String("expected", string(model.PurposeAccountActivation)), zap.String("actual", string(t.Purpose)))
|
||||
return response.DataConflict(a.logger, a.Name(), "Invalid token purpose")
|
||||
}
|
||||
|
||||
var user model.Account
|
||||
if err := a.db.Get(ctx, t.AccountRef, &user); err != nil {
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
a.logger.Debug("Verified user not found", zap.Error(err))
|
||||
return response.NotFound(a.logger, a.Name(), "User not found")
|
||||
}
|
||||
a.logger.Warn("Failed to fetch account", zap.Error(err))
|
||||
return response.Internal(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
// Delete verification token to confirm account
|
||||
user.VerifyToken = ""
|
||||
if err = a.db.Update(ctx, user); err != nil {
|
||||
user.Status = model.AccountActive
|
||||
if err = a.db.Update(ctx, &user); err != nil {
|
||||
a.logger.Warn("Failed to save account while verifying account", zap.Error(err))
|
||||
return response.Internal(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
@@ -55,7 +55,8 @@ func (a *AccountAPI) updateEmployee(r *http.Request, account *model.Account, tok
|
||||
}
|
||||
if acc.Login != u.Login {
|
||||
// Change email address
|
||||
if err := a.accService.UpdateLogin(ctx, &acc, u.Login); err != nil {
|
||||
verificationToken, err := a.accService.UpdateLogin(ctx, &acc, u.Login)
|
||||
if err != nil {
|
||||
if errors.Is(err, merrors.ErrDataConflict) {
|
||||
a.logger.Debug("Duplicate login, denying change...", zap.Error(err), mzap.ObjRef("employee_ref", u.ID))
|
||||
return a.reportDuplicateEmail()
|
||||
@@ -65,7 +66,7 @@ func (a *AccountAPI) updateEmployee(r *http.Request, account *model.Account, tok
|
||||
}
|
||||
|
||||
// Send verification email
|
||||
if err = a.sendWelcomeEmail(&acc); err != nil {
|
||||
if err = a.sendWelcomeEmail(&acc, verificationToken); err != nil {
|
||||
a.logger.Warn("Failed to send verification email", zap.Error(err), mzap.StorableRef(&acc))
|
||||
return response.Internal(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
@@ -84,13 +84,14 @@ func (a *AccountAPI) forgotPassword(r *http.Request) http.HandlerFunc {
|
||||
}
|
||||
|
||||
// Generate reset password token
|
||||
if err := a.accService.ResetPassword(ctx, user); err != nil {
|
||||
verificationToken, err := a.accService.ResetPassword(ctx, user)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to generate reset password token", zap.Error(err), mzap.StorableRef(user))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
// Send reset password email
|
||||
if err = a.sendPasswordResetEmail(user, user.ResetPasswordToken); err != nil {
|
||||
if err = a.sendPasswordResetEmail(user, verificationToken); err != nil {
|
||||
a.logger.Warn("Failed to send reset password email", zap.Error(err), mzap.StorableRef(user))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
@@ -127,15 +128,20 @@ func (a *AccountAPI) resetPassword(r *http.Request) http.HandlerFunc {
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
// Validate reset token
|
||||
if user.ResetPasswordToken == "" {
|
||||
a.logger.Debug("No reset token found for user", mzap.StorableRef(&user))
|
||||
return response.BadRequest(a.logger, a.Name(), "no_reset_token", "No password reset token found for this user")
|
||||
t, err := a.vdb.Consume(ctx, token)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to consume password reset token", zap.Error(err), zap.String("token", token))
|
||||
return a.mapTokenErrorToResponse(err)
|
||||
}
|
||||
|
||||
if user.ResetPasswordToken != token {
|
||||
a.logger.Debug("Reset token mismatch", mzap.StorableRef(&user))
|
||||
return response.BadRequest(a.logger, a.Name(), "invalid_token", "Invalid or expired reset token")
|
||||
if t.Purpose != model.PurposePasswordReset {
|
||||
a.logger.Warn("Invalid token purpose for password reset", zap.String("expected", string(model.PurposePasswordReset)), zap.String("actual", string(t.Purpose)))
|
||||
return response.DataConflict(a.logger, a.Name(), "Invalid token purpose")
|
||||
}
|
||||
|
||||
if t.AccountRef != accountRef {
|
||||
a.logger.Warn("Token account reference does not match request account reference", zap.String("token_account_ref", t.AccountRef.Hex()), zap.String("request_account_ref", accountRef.Hex()))
|
||||
return response.DataConflict(a.logger, a.Name(), "Token does not match account")
|
||||
}
|
||||
|
||||
// Parse new password from request body
|
||||
@@ -145,11 +151,6 @@ func (a *AccountAPI) resetPassword(r *http.Request) http.HandlerFunc {
|
||||
return response.BadPayload(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
if req.Password == "" {
|
||||
a.logger.Debug("New password is empty")
|
||||
return response.BadRequest(a.logger, a.Name(), "empty_password", "New password cannot be empty")
|
||||
}
|
||||
|
||||
// Validate new password
|
||||
if err := a.accService.ValidatePassword(req.Password, nil); err != nil {
|
||||
a.logger.Debug("Password validation failed", zap.Error(err), mzap.StorableRef(&user))
|
||||
@@ -172,7 +173,6 @@ func (a *AccountAPI) resetPassword(r *http.Request) http.HandlerFunc {
|
||||
func (a *AccountAPI) resetPasswordTransactionBody(ctx context.Context, user *model.Account, newPassword string) (any, error) {
|
||||
// Update user with new password and clear reset token
|
||||
user.Password = newPassword
|
||||
user.ResetPasswordToken = "" // Clear the token after use
|
||||
|
||||
// Hash the new password
|
||||
if err := user.HashPassword(); err != nil {
|
||||
|
||||
@@ -4,92 +4,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
// TestPasswordResetTokenGeneration tests the token generation logic
|
||||
func TestPasswordResetTokenGeneration(t *testing.T) {
|
||||
// Test that ResetPassword service method generates a token
|
||||
account := &model.Account{
|
||||
AccountPublic: model.AccountPublic{
|
||||
AccountBase: model.AccountBase{
|
||||
Describable: model.Describable{
|
||||
Name: "Test User",
|
||||
},
|
||||
},
|
||||
UserDataBase: model.UserDataBase{
|
||||
Login: "test@example.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Initially no reset token
|
||||
assert.Empty(t, account.ResetPasswordToken, "Account should not have reset token initially")
|
||||
|
||||
// Simulate what ResetPassword service method does
|
||||
account.ResetPasswordToken = "generated-token-123"
|
||||
assert.NotEmpty(t, account.ResetPasswordToken, "Reset token should be generated")
|
||||
assert.Equal(t, "generated-token-123", account.ResetPasswordToken, "Reset token should match generated value")
|
||||
}
|
||||
|
||||
// TestPasswordResetTokenValidation tests token validation logic
|
||||
func TestPasswordResetTokenValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
storedToken string
|
||||
providedToken string
|
||||
shouldBeValid bool
|
||||
}{
|
||||
{
|
||||
name: "ValidToken_ShouldMatch",
|
||||
storedToken: "valid-token-123",
|
||||
providedToken: "valid-token-123",
|
||||
shouldBeValid: true,
|
||||
},
|
||||
{
|
||||
name: "InvalidToken_ShouldNotMatch",
|
||||
storedToken: "valid-token-123",
|
||||
providedToken: "invalid-token-456",
|
||||
shouldBeValid: false,
|
||||
},
|
||||
{
|
||||
name: "EmptyStoredToken_ShouldBeInvalid",
|
||||
storedToken: "",
|
||||
providedToken: "any-token",
|
||||
shouldBeValid: false,
|
||||
},
|
||||
{
|
||||
name: "EmptyProvidedToken_ShouldBeInvalid",
|
||||
storedToken: "valid-token-123",
|
||||
providedToken: "",
|
||||
shouldBeValid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
account := &model.Account{
|
||||
AccountPublic: model.AccountPublic{
|
||||
AccountBase: model.AccountBase{
|
||||
Describable: model.Describable{
|
||||
Name: "Test User",
|
||||
},
|
||||
},
|
||||
UserDataBase: model.UserDataBase{
|
||||
Login: "test@example.com",
|
||||
},
|
||||
},
|
||||
ResetPasswordToken: tt.storedToken,
|
||||
}
|
||||
|
||||
// Test token validation logic (what the resetPassword handler does)
|
||||
isValid := account.ResetPasswordToken != "" && account.ResetPasswordToken == tt.providedToken
|
||||
assert.Equal(t, tt.shouldBeValid, isValid, "Token validation should match expected result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestPasswordResetFlowLogic tests the logical flow without database dependencies
|
||||
func TestPasswordResetFlowLogic(t *testing.T) {
|
||||
t.Run("CompleteFlow", func(t *testing.T) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/tech/sendico/pkg/db/policy"
|
||||
"github.com/tech/sendico/pkg/db/refreshtokens"
|
||||
"github.com/tech/sendico/pkg/db/transaction"
|
||||
"github.com/tech/sendico/pkg/db/verification"
|
||||
"github.com/tech/sendico/pkg/domainprovider"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/messaging"
|
||||
@@ -36,6 +37,7 @@ type AccountAPI struct {
|
||||
tf transaction.Factory
|
||||
rtdb refreshtokens.DB
|
||||
plcdb policy.DB
|
||||
vdb verification.DB
|
||||
domain domainprovider.DomainProvider
|
||||
avatars mservice.MicroService
|
||||
producer messaging.Producer
|
||||
@@ -91,6 +93,10 @@ func CreateAPI(a eapi.API) (*AccountAPI, error) {
|
||||
p.logger.Error("Failed to create policies database", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
if p.vdb, err = a.DBFactory().NewVerificationsDB(); err != nil {
|
||||
p.logger.Error("Failed to create verification database", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.domain = a.DomainProvider()
|
||||
p.producer = a.Register().Messaging().Producer()
|
||||
|
||||
@@ -91,7 +91,8 @@ func (a *AccountAPI) signup(r *http.Request) http.HandlerFunc {
|
||||
return response.BadPayload(a.logger, a.Name(), res)
|
||||
}
|
||||
|
||||
if err := a.executeSignupTransaction(r.Context(), &sr, newAccount); err != nil {
|
||||
verificationToken, err := a.executeSignupTransaction(r.Context(), &sr, newAccount)
|
||||
if err != nil {
|
||||
if errors.Is(err, merrors.ErrDataConflict) {
|
||||
a.logger.Warn("Failed to register account", zap.Error(err), zap.String("login", newAccount.Login))
|
||||
return response.DataConflict(a.logger, "user_already_registered", "User has already been registered")
|
||||
@@ -100,7 +101,7 @@ func (a *AccountAPI) signup(r *http.Request) http.HandlerFunc {
|
||||
return response.Internal(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
if err := a.sendWelcomeEmail(newAccount); err != nil {
|
||||
if err := a.sendWelcomeEmail(newAccount, verificationToken); err != nil {
|
||||
a.logger.Warn("Failed to send welcome email", zap.Error(err), mzap.StorableRef(newAccount))
|
||||
}
|
||||
|
||||
@@ -127,11 +128,14 @@ func (a *AccountAPI) signupAvailability(r *http.Request) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AccountAPI) executeSignupTransaction(ctxt context.Context, sr *srequest.Signup, newAccount *model.Account) error {
|
||||
_, err := a.tf.CreateTransaction().Execute(ctxt, func(ctx context.Context) (any, error) {
|
||||
func (a *AccountAPI) executeSignupTransaction(ctxt context.Context, sr *srequest.Signup, newAccount *model.Account) (string, error) {
|
||||
res, err := a.tf.CreateTransaction().Execute(ctxt, func(ctx context.Context) (any, error) {
|
||||
return a.signupTransactionBody(ctx, sr, newAccount)
|
||||
})
|
||||
return err
|
||||
if token, ok := res.(string); token != "" || ok {
|
||||
return token, err
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
func (a *AccountAPI) signupTransactionBody(ctx context.Context, sr *srequest.Signup, newAccount *model.Account) (any, error) {
|
||||
@@ -165,13 +169,14 @@ func (a *AccountAPI) signupTransactionBody(ctx context.Context, sr *srequest.Sig
|
||||
}
|
||||
a.logger.Info("Organization owner role permissions granted", mzap.StorableRef(org), zap.String("account", sr.Account.Login))
|
||||
|
||||
if err := a.accService.CreateAccount(ctx, org, newAccount, roleDescription.ID); err != nil {
|
||||
token, err := a.accService.CreateAccount(ctx, org, newAccount, roleDescription.ID)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to create account", zap.Error(err), zap.String("login", newAccount.Login))
|
||||
return nil, err
|
||||
}
|
||||
a.logger.Info("Organization owner account registered", mzap.StorableRef(org), zap.String("account", sr.Account.Login))
|
||||
|
||||
return nil, nil
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (a *AccountAPI) grantAllPermissions(ctx context.Context, organizationRef bson.ObjectID, roleID bson.ObjectID, newAccount *model.Account) error {
|
||||
|
||||
31
api/server/internal/server/accountapiimp/token.go
Normal file
31
api/server/internal/server/accountapiimp/token.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package accountapiimp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/db/verification"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (a *AccountAPI) mapTokenErrorToResponse(err error) http.HandlerFunc {
|
||||
if errors.Is(err, verification.ErrTokenNotFound) {
|
||||
a.logger.Debug("Verification token not found during consume", zap.Error(err))
|
||||
return response.NotFound(a.logger, a.Name(), "No account found associated with given verifcation token")
|
||||
}
|
||||
if errors.Is(err, verification.ErrTokenExpired) {
|
||||
a.logger.Debug("Verification token expired during consume", zap.Error(err))
|
||||
return response.Gone(a.logger, a.Name(), "token_expired", "verification token has expired")
|
||||
}
|
||||
if errors.Is(err, verification.ErrTokenAlreadyUsed) {
|
||||
a.logger.Debug("Verification token already used during consume", zap.Error(err))
|
||||
return response.DataConflict(a.logger, a.Name(), "verification token has already been used")
|
||||
}
|
||||
if err != nil {
|
||||
a.logger.Warn("Uenxpected error during token verification", zap.Error(err))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
a.logger.Debug("No token verification error found")
|
||||
return response.Success(a.logger)
|
||||
}
|
||||
@@ -31,7 +31,8 @@ func (a *AccountAPI) updateProfile(r *http.Request, account *model.Account, toke
|
||||
|
||||
if account.Login != u.Login {
|
||||
// Change email address
|
||||
if err := a.accService.UpdateLogin(ctx, account, u.Login); err != nil {
|
||||
verificationToken, err := a.accService.UpdateLogin(ctx, account, u.Login)
|
||||
if err != nil {
|
||||
if errors.Is(err, merrors.ErrDataConflict) {
|
||||
a.logger.Debug("Duplicate login, denying change...", zap.Error(err), mzap.StorableRef(u))
|
||||
return a.reportDuplicateEmail()
|
||||
@@ -41,7 +42,7 @@ func (a *AccountAPI) updateProfile(r *http.Request, account *model.Account, toke
|
||||
}
|
||||
|
||||
// Send verification email
|
||||
if err = a.sendWelcomeEmail(account); err != nil {
|
||||
if err = a.sendWelcomeEmail(account, verificationToken); err != nil {
|
||||
a.logger.Warn("Failed to send verification email", zap.Error(err), mzap.StorableRef(account))
|
||||
return response.Internal(a.logger, a.Name(), err)
|
||||
}
|
||||
@@ -49,8 +50,7 @@ func (a *AccountAPI) updateProfile(r *http.Request, account *model.Account, toke
|
||||
} else {
|
||||
// Save the user
|
||||
u.Password = account.Password
|
||||
u.ResetPasswordToken = account.ResetPasswordToken
|
||||
u.VerifyToken = account.VerifyToken
|
||||
u.Status = account.Status
|
||||
if err = a.db.Update(ctx, u); err != nil {
|
||||
a.logger.Warn("Failed to save account", zap.Error(err), mzap.StorableRef(u))
|
||||
return response.Internal(a.logger, a.Name(), err)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
an "github.com/tech/sendico/pkg/messaging/notifications/account"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mutil/mzap"
|
||||
"github.com/tech/sendico/server/interface/api/srequest"
|
||||
@@ -38,6 +39,14 @@ func (a *InvitationAPI) doAccept(ctx context.Context, invitationRef bson.ObjectI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *InvitationAPI) sendWelcomeEmail(account *model.Account, token string) error {
|
||||
if err := a.producer.SendMessage(an.AccountCreated(a.Name(), *account.GetID(), token)); err != nil {
|
||||
a.Logger.Warn("Failed to send account creation notification", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *InvitationAPI) getPendingInvitation(ctx context.Context, invitationRef bson.ObjectID) (*model.Invitation, error) {
|
||||
a.Logger.Debug("Fetching invitation", mzap.ObjRef("invitation_ref", invitationRef))
|
||||
var inv model.Invitation
|
||||
@@ -79,10 +88,17 @@ func (a *InvitationAPI) fetchOrCreateAccount(ctx context.Context, org *model.Org
|
||||
return nil, err
|
||||
}
|
||||
// creates account and joins organization
|
||||
if err := a.accService.CreateAccount(ctx, org, account, inv.RoleRef); err != nil {
|
||||
token, err := a.accService.CreateAccount(ctx, org, account, inv.RoleRef)
|
||||
if err != nil {
|
||||
a.Logger.Warn("Failed to create account", zap.Error(err), zap.String("email", inv.Content.Email))
|
||||
return nil, err
|
||||
}
|
||||
// Send welcome email
|
||||
if err = a.sendWelcomeEmail(account, token); err != nil {
|
||||
a.Logger.Warn("Failed to send welcome email for new account created via invitation",
|
||||
zap.Error(err), zap.String("email", inv.Content.Email))
|
||||
return nil, err
|
||||
}
|
||||
return account, nil
|
||||
} else if err != nil {
|
||||
a.Logger.Warn("Failed to fetch account by email", zap.Error(err), zap.String("email", inv.Content.Email))
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/tech/sendico/pkg/db/invitation"
|
||||
"github.com/tech/sendico/pkg/db/organization"
|
||||
"github.com/tech/sendico/pkg/db/transaction"
|
||||
"github.com/tech/sendico/pkg/messaging"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
"github.com/tech/sendico/server/interface/accountservice"
|
||||
@@ -25,6 +26,7 @@ type InvitationAPI struct {
|
||||
adb account.DB
|
||||
odb organization.DB
|
||||
accService accountservice.AccountService
|
||||
producer messaging.Producer
|
||||
}
|
||||
|
||||
func (a *InvitationAPI) Name() mservice.Type {
|
||||
@@ -41,8 +43,9 @@ func CreateAPI(a eapi.API) (*InvitationAPI, error) {
|
||||
}
|
||||
|
||||
res := &InvitationAPI{
|
||||
irh: mutil.CreatePH("invitation"),
|
||||
tf: a.DBFactory().TransactionFactory(),
|
||||
irh: mutil.CreatePH("invitation"),
|
||||
tf: a.DBFactory().TransactionFactory(),
|
||||
producer: a.Register().Messaging().Producer(),
|
||||
}
|
||||
|
||||
p, err := papitemplate.CreateAPI(a, dbFactory, mservice.Organizations, mservice.Invitations)
|
||||
|
||||
Reference in New Issue
Block a user