fixed verification code

This commit is contained in:
Stephan D
2026-02-09 16:40:52 +01:00
parent f8a3bef2e6
commit eda6b75f74
78 changed files with 1118 additions and 487 deletions

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"slices"
"time"
"unicode"
"github.com/tech/sendico/pkg/auth"
@@ -13,13 +14,12 @@ import (
"github.com/tech/sendico/pkg/db/account"
"github.com/tech/sendico/pkg/db/organization"
"github.com/tech/sendico/pkg/db/policy"
"github.com/tech/sendico/pkg/db/transaction"
"github.com/tech/sendico/pkg/db/verification"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mutil/mzap"
"github.com/tech/sendico/server/interface/middleware"
"github.com/tech/sendico/server/internal/mutil/flrstring"
"go.mongodb.org/mongo-driver/v2/bson"
"go.uber.org/zap"
)
@@ -31,9 +31,9 @@ type service struct {
enforcer auth.Enforcer
roleManager management.Role
config *middleware.PasswordConfig
tf transaction.Factory
policyDB policy.DB
vdb verification.DB
}
func validateUserRequest(u *model.Account) error {
@@ -112,7 +112,6 @@ func (s *service) ValidateAccount(acct *model.Account) error {
return err
}
acct.VerifyToken = flrstring.CreateRandString(s.config.TokenLength)
return nil
}
@@ -121,32 +120,50 @@ func (s *service) CreateAccount(
org *model.Organization,
acct *model.Account,
roleDescID bson.ObjectID,
) error {
) (string, error) {
if org == nil {
return merrors.InvalidArgument("Organization must not be nil")
return "", merrors.InvalidArgument("Organization must not be nil")
}
if acct == nil || len(acct.Login) == 0 {
return merrors.InvalidArgument("Account must have a non-empty login")
return "", merrors.InvalidArgument("Account must have a non-empty login")
}
if roleDescID == bson.NilObjectID {
return merrors.InvalidArgument("Role description must be provided")
return "", merrors.InvalidArgument("Role description must be provided")
}
// 1) Create the account
acct.Status = model.AccountPendingVerification
if err := s.accountDB.Create(ctx, acct); err != nil {
if errors.Is(err, merrors.ErrDataConflict) {
s.logger.Info("Username is already taken", zap.String("login", acct.Login))
} else {
s.logger.Warn("Failed to signup a user", zap.Error(err), zap.String("login", acct.Login))
}
return err
return "", err
}
// 2) Add to organization
if err := s.JoinOrganization(ctx, org, acct, roleDescID); err != nil {
s.logger.Warn("Failed to register new organization member", zap.Error(err), mzap.StorableRef(acct))
return err
return "", err
}
return nil
// 3) Issue verification token
return s.VerifyAccount(ctx, acct)
}
func (s *service) VerifyAccount(
ctx context.Context,
acct *model.Account,
) (verificationToken string, err error) {
verificationToken, err = s.vdb.Create(ctx, *acct.GetID(), model.PurposeAccountActivation, "", time.Duration(time.Hour*24))
if err != nil {
s.logger.Warn("Failed to create verification token for new account", zap.Error(err), mzap.StorableRef(acct))
return "", err
}
return verificationToken, nil
}
func (s *service) DeleteAccount(
@@ -213,20 +230,16 @@ func (s *service) RemoveAccountFromOrganization(
func (s *service) ResetPassword(
ctx context.Context,
acct *model.Account,
) error {
acct.ResetPasswordToken = flrstring.CreateRandString(s.config.TokenLength)
return s.accountDB.Update(ctx, acct)
) (string, error) {
return s.vdb.Create(ctx, *acct.GetID(), model.PurposePasswordReset, "", time.Duration(time.Hour*1))
}
func (s *service) UpdateLogin(
ctx context.Context,
acct *model.Account,
newLogin string,
) error {
acct.EmailBackup = acct.Login
acct.Login = newLogin
acct.VerifyToken = flrstring.CreateRandString(s.config.TokenLength)
return s.accountDB.Update(ctx, acct)
) (string, error) {
return s.vdb.Create(ctx, *acct.GetID(), model.PurposeEmailChange, newLogin, time.Duration(time.Hour*1))
}
func (s *service) JoinOrganization(
@@ -311,27 +324,19 @@ func (s *service) DeleteOrganization(
s.logger.Info("Starting organization deletion", mzap.StorableRef(org))
// Use transaction to ensure atomicity
_, err := s.tf.CreateTransaction().Execute(ctx, func(ctx context.Context) (any, error) {
// 8. Delete all roles and role descriptions in the organization
if err := s.deleteOrganizationRoles(ctx, org.ID); err != nil {
return nil, err
}
// 8. Delete all roles and role descriptions in the organization
if err := s.deleteOrganizationRoles(ctx, org.ID); err != nil {
return err
}
// 9. Delete all policies in the organization
if err := s.deleteOrganizationPolicies(ctx, org.ID); err != nil {
return nil, err
}
// 9. Delete all policies in the organization
if err := s.deleteOrganizationPolicies(ctx, org.ID); err != nil {
return err
}
// 10. Finally, delete the organization itself
if err := s.orgDB.Delete(ctx, bson.NilObjectID, org.ID); err != nil {
s.logger.Warn("Failed to delete organization", zap.Error(err), mzap.StorableRef(org))
return nil, err
}
return nil, nil
})
if err != nil {
s.logger.Error("Failed to delete organization", zap.Error(err), mzap.StorableRef(org))
// 10. Finally, delete the organization itself
if err := s.orgDB.Delete(ctx, bson.NilObjectID, org.ID); err != nil {
s.logger.Warn("Failed to delete organization", zap.Error(err), mzap.StorableRef(org))
return err
}
@@ -347,23 +352,14 @@ func (s *service) DeleteAll(
s.logger.Info("Starting complete deletion (organization + account)",
mzap.StorableRef(org), mzap.ObjRef("account_ref", accountRef))
// Use transaction to ensure atomicity
_, err := s.tf.CreateTransaction().Execute(ctx, func(ctx context.Context) (any, error) {
// 1. First delete the organization and all its data
if err := s.DeleteOrganization(ctx, org); err != nil {
return nil, err
}
// 1. First delete the organization and all its data
if err := s.DeleteOrganization(ctx, org); err != nil {
return err
}
// 2. Then delete the account
if err := s.accountDB.Delete(ctx, accountRef); err != nil {
s.logger.Warn("Failed to delete account", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
return nil, err
}
return nil, nil
})
if err != nil {
s.logger.Error("Failed to delete all data", zap.Error(err), mzap.StorableRef(org), mzap.ObjRef("account_ref", accountRef))
// 2. Then delete the account
if err := s.accountDB.Delete(ctx, accountRef); err != nil {
s.logger.Warn("Failed to delete account", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
return err
}
@@ -390,7 +386,6 @@ func NewAccountService(
enforcer: enforcer,
roleManager: ra,
config: config,
tf: dbf.TransactionFactory(),
}
var err error
if res.accountDB, err = dbf.NewAccountDB(); err != nil {
@@ -407,6 +402,9 @@ func NewAccountService(
logger.Warn("Failed to create policies database", zap.Error(err))
return nil, err
}
if res.vdb, err = dbf.NewVerificationsDB(); err != nil {
logger.Warn("Failed to create verification database", zap.Error(err))
return nil, err
}
return res, nil
}