+signup +email availability check
Some checks failed
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
Some checks failed
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
This commit is contained in:
@@ -6,13 +6,16 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
"github.com/tech/sendico/pkg/mutil/mzap"
|
||||
gatewayv1 "github.com/tech/sendico/pkg/proto/chain/gateway/v1"
|
||||
"github.com/tech/sendico/server/interface/api/srequest"
|
||||
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
@@ -41,6 +44,10 @@ func (a *AccountAPI) createAnonymousAccount(ctx context.Context, org *model.Orga
|
||||
}
|
||||
|
||||
func (a *AccountAPI) createOrg(ctx context.Context, sr *srequest.Signup, permissionRef primitive.ObjectID) (*model.Organization, error) {
|
||||
name := strings.TrimSpace(sr.Organization.Name)
|
||||
if name == "" {
|
||||
return nil, merrors.InvalidArgument("organization name must not be empty")
|
||||
}
|
||||
if _, err := time.LoadLocation(sr.OrganizationTimeZone); err != nil {
|
||||
return nil, merrors.DataConflict(fmt.Sprintf("invalid time zone '%s' provided, error %s", sr.OrganizationTimeZone, err.Error()))
|
||||
}
|
||||
@@ -51,7 +58,8 @@ func (a *AccountAPI) createOrg(ctx context.Context, sr *srequest.Signup, permiss
|
||||
PermissionRef: permissionRef,
|
||||
},
|
||||
Describable: model.Describable{
|
||||
Name: sr.OrganizationName,
|
||||
Name: name,
|
||||
Description: sr.Organization.Description,
|
||||
},
|
||||
TimeZone: sr.OrganizationTimeZone,
|
||||
},
|
||||
@@ -74,6 +82,18 @@ func (a *AccountAPI) signup(r *http.Request) http.HandlerFunc {
|
||||
return response.BadRequest(a.logger, a.Name(), "", err.Error())
|
||||
}
|
||||
|
||||
sr.Account.Login = strings.ToLower(strings.TrimSpace(sr.Account.Login))
|
||||
if err := a.ensureLoginAvailable(r.Context(), sr.Account.Login); err != nil {
|
||||
if errors.Is(err, merrors.ErrDataConflict) {
|
||||
return response.DataConflict(a.logger, "user_already_registered", "User has already been registered")
|
||||
}
|
||||
if errors.Is(err, merrors.ErrInvalidArg) {
|
||||
return response.BadPayload(a.logger, a.Name(), err)
|
||||
}
|
||||
a.logger.Warn("Failed to validate login availability", zap.Error(err), zap.String("login", sr.Account.Login))
|
||||
return response.Internal(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
newAccount := sr.Account.ToAccount()
|
||||
if res := a.accService.ValidateAccount(newAccount); res != nil {
|
||||
a.logger.Warn("Invalid signup account received", zap.Error(res), zap.String("account", newAccount.Login))
|
||||
@@ -96,6 +116,26 @@ func (a *AccountAPI) signup(r *http.Request) http.HandlerFunc {
|
||||
return sresponse.SignUp(a.logger, newAccount)
|
||||
}
|
||||
|
||||
func (a *AccountAPI) signupAvailability(r *http.Request) http.HandlerFunc {
|
||||
login := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("login")))
|
||||
if login == "" {
|
||||
return response.BadRequest(a.logger, a.Name(), "missing_login", "login query parameter is required")
|
||||
}
|
||||
|
||||
err := a.ensureLoginAvailable(r.Context(), login)
|
||||
switch {
|
||||
case err == nil:
|
||||
return sresponse.SignUpAvailability(a.logger, login, true)
|
||||
case errors.Is(err, merrors.ErrDataConflict):
|
||||
return sresponse.SignUpAvailability(a.logger, login, false)
|
||||
case errors.Is(err, merrors.ErrInvalidArg):
|
||||
return response.BadPayload(a.logger, a.Name(), err)
|
||||
default:
|
||||
a.logger.Warn("Failed to check login availability", zap.Error(err), zap.String("login", login))
|
||||
return response.Internal(a.logger, a.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return a.signupTransactionBody(ctx, sr, newAccount)
|
||||
@@ -116,6 +156,10 @@ func (a *AccountAPI) signupTransactionBody(ctx context.Context, sr *srequest.Sig
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := a.openOrgWallet(ctx, org, sr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roleDescription, err := a.pmanager.Role().Create(ctx, org.ID, &sr.OwnerRole)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to create owner role", zap.Error(err), zap.String("login", newAccount.Login))
|
||||
@@ -146,8 +190,21 @@ func (a *AccountAPI) grantAllPermissions(ctx context.Context, organizationRef pr
|
||||
return err
|
||||
}
|
||||
|
||||
required := map[mservice.Type]bool{
|
||||
mservice.Organizations: false,
|
||||
mservice.Accounts: false,
|
||||
mservice.LedgerAccounts: false,
|
||||
}
|
||||
|
||||
actions := []model.Action{model.ActionCreate, model.ActionRead, model.ActionUpdate, model.ActionDelete}
|
||||
for _, policy := range policies {
|
||||
if policy.ResourceTypes != nil {
|
||||
for _, resource := range *policy.ResourceTypes {
|
||||
if _, ok := required[resource]; ok {
|
||||
required[resource] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, action := range actions {
|
||||
a.logger.Debug("Adding permission", mzap.StorableRef(&policy), zap.String("action", string(action)),
|
||||
mzap.ObjRef("role_ref", roleID), mzap.ObjRef("policy_ref", policy.ID), mzap.ObjRef("organization_ref", organizationRef))
|
||||
@@ -172,5 +229,55 @@ func (a *AccountAPI) grantAllPermissions(ctx context.Context, organizationRef pr
|
||||
return err
|
||||
}
|
||||
|
||||
for resource, granted := range required {
|
||||
if !granted {
|
||||
a.logger.Warn("Required policy description not found for signup permissions", zap.String("resource", string(resource)))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AccountAPI) ensureLoginAvailable(ctx context.Context, login string) error {
|
||||
if strings.TrimSpace(login) == "" {
|
||||
return merrors.InvalidArgument("login must not be empty")
|
||||
}
|
||||
if _, err := a.db.GetByEmail(ctx, login); err != nil {
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
return nil
|
||||
}
|
||||
a.logger.Warn("Failed to lookup account by login", zap.Error(err), zap.String("login", login))
|
||||
return err
|
||||
}
|
||||
return merrors.DataConflict("account already exists")
|
||||
}
|
||||
|
||||
func (a *AccountAPI) openOrgWallet(ctx context.Context, org *model.Organization, sr *srequest.Signup) error {
|
||||
if a.chainGateway == nil || a.chainAsset == nil {
|
||||
a.logger.Warn("Chain gateway client not configured, skipping wallet creation", mzap.StorableRef(org))
|
||||
return merrors.Internal("chain gateway client is not configured")
|
||||
}
|
||||
asset := *a.chainAsset
|
||||
req := &gatewayv1.CreateManagedWalletRequest{
|
||||
IdempotencyKey: uuid.NewString(),
|
||||
OrganizationRef: org.ID.Hex(),
|
||||
OwnerRef: org.ID.Hex(),
|
||||
Asset: &asset,
|
||||
Metadata: map[string]string{
|
||||
"source": "signup",
|
||||
"login": sr.Account.Login,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := a.chainGateway.CreateManagedWallet(ctx, req)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to create managed wallet for organization", zap.Error(err), mzap.StorableRef(org))
|
||||
return err
|
||||
}
|
||||
if resp == nil || resp.Wallet == nil || strings.TrimSpace(resp.Wallet.WalletRef) == "" {
|
||||
return merrors.Internal("chain gateway returned empty wallet reference")
|
||||
}
|
||||
|
||||
a.logger.Info("Managed wallet created for organization", mzap.StorableRef(org), zap.String("wallet_ref", resp.Wallet.WalletRef))
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user