package handlers import ( "net/http" "github.com/creativenoz/aurganize-v62/backend/internal/config" "github.com/creativenoz/aurganize-v62/backend/internal/models" "github.com/creativenoz/aurganize-v62/backend/internal/services" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" ) type UserRegisterHander struct { config *config.Config authService *services.AuthService userService *services.UserService tenantService *services.TenantService } func NewUserRegisterHandler(config *config.Config, authService *services.AuthService, userService *services.UserService, tenantService *services.TenantService) *UserRegisterHander { log.Info(). Str("handler", "user_register"). Str("component", "handler_init"). Bool("has_auth_service", authService != nil). Bool("has_user_service", userService != nil). Bool("has_tenant_service", tenantService != nil). Msg("user registration handler initialized") return &UserRegisterHander{ config: config, authService: authService, userService: userService, tenantService: tenantService, } } type RegisterUserRequest struct { Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required,min=8"` FirstName *string `json:"first_name"` LastName *string `json:"last_name"` TenantName string `json:"tenant_name" validate:"required"` } type RegisterUserResponse struct { User *models.UserResponse `json:"user"` Tenant interface{} `json:"tenant"` AccessToken string `json:"access_token"` RefershToken string `json:"refresh_token"` ExpiresIn int `json:"expires_in"` } func (h *UserRegisterHander) Register(c echo.Context) error { log.Info(). Str("handler", "user_register"). Str("action", "registration_attempt"). Str("ip", c.RealIP()). Str("user_agent", c.Request().UserAgent()). Msg("new user registration attempt started") var req RegisterUserRequest if err := c.Bind(&req); err != nil { log.Warn(). Str("handler", "user_register"). Str("action", "registration_bind_failed"). Str("ip", c.RealIP()). Err(err). Msg("failed to bind registration request - malformed json or content-type") return echo.NewHTTPError(http.StatusBadRequest, "invalid request") } if err := c.Validate(&req); err != nil { log.Warn(). Str("handler", "user_register"). Str("action", "registration_validation_failed"). Str("email", req.Email). Str("tenant_name", req.TenantName). Str("validation_error", err.Error()). Bool("has_first_name", req.FirstName != nil). Bool("has_last_name", req.LastName != nil). Str("ip", c.RealIP()). Msg("registration validation failed") return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } log.Info(). Str("handler", "user_register"). Str("action", "checking_tenant_availability"). Str("tenant_name", req.TenantName). Str("email", req.Email). Msg("validated registration request, checking tenant name availability") ctx := c.Request().Context() tenantExists, err := h.tenantService.SlugExists(ctx, req.TenantName) if err != nil { log.Error(). Str("handler", "user_register"). Str("action", "tenant_check_failed"). Str("tenant_name", req.TenantName). Str("email", req.Email). Err(err). Msg("failed to check tenant name availability - database or service error") return echo.NewHTTPError(http.StatusInternalServerError, "failed to check tenant") } if tenantExists { log.Warn(). Str("handler", "user_register"). Str("action", "tenant_name_conflict"). Str("requested_tenant_name", req.TenantName). Str("email", req.Email). Str("ip", c.RealIP()). Msg("registration failed - organization name already taken") return echo.NewHTTPError(http.StatusConflict, "organization name already taken") } log.Info(). Str("handler", "user_register"). Str("action", "creating_tenant_and_user"). Str("tenant_name", req.TenantName). Str("email", req.Email). Bool("has_first_name", req.FirstName != nil). Bool("has_last_name", req.LastName != nil). Msg("tenant available, creating organization and user account") tenant, user, err := h.tenantService.CreateWithUser(ctx, &models.CreateTenantWithUserInput{ TenantName: req.TenantName, Email: &req.Email, Password: &req.Password, FirstName: req.FirstName, LastName: req.LastName, }) if err != nil { if err == services.ErrEmailAlreadyExists { log.Warn(). Str("handler", "user_register"). Str("action", "email_already_exists"). Str("email", req.Email). Str("tenant_name", req.TenantName). Str("ip", c.RealIP()). Msg("registration failed - email already registered") return echo.NewHTTPError(http.StatusConflict, "email already registered") } if err == services.ErrWeakPassword { log.Warn(). Str("handler", "user_register"). Str("action", "weak_password_rejected"). Str("email", req.Email). Str("tenant_name", req.TenantName). Int("password_length", len(req.Password)). Msg("registration failed - password too weak") return echo.NewHTTPError(http.StatusBadRequest, "password is too weak") } log.Error(). Str("handler", "user_register"). Str("action", "registration_failed_unexpected"). Str("email", req.Email). Str("tenant_name", req.TenantName). Err(err). Msg("registration failed - unexpected error during account creation") return echo.NewHTTPError(http.StatusInternalServerError, "registration failed") } log.Info(). Str("handler", "user_register"). Str("action", "account_created_successfully"). Str("user_id", user.ID.String()). Str("tenant_id", user.TenantID.String()). Str("tenant_name", tenant.Name). Str("email", user.Email). Str("user_role", user.Role). Msg("account created successfully, generating authentication tokens") // Generate Tokens accessToken, err := h.authService.GenerateAccessToken(user) if err != nil { log.Error(). Str("handler", "user_register"). Str("action", "access_token_generation_failed"). Str("user_id", user.ID.String()). Str("tenant_id", user.TenantID.String()). Str("email", user.Email). Err(err). Msg("CRITICAL: account created but failed to generate access token") return echo.NewHTTPError(http.StatusInternalServerError, "failed to generate access token") } userAgent := c.Request().UserAgent() ipAddress := c.RealIP() log.Debug(). Str("handler", "user_register"). Str("action", "generating_refresh_token"). Str("user_id", user.ID.String()). Str("ip", ipAddress). Str("user_agent", userAgent). Msg("generating refresh token and creating first session") refreshToken, _, err := h.authService.GenerateRefreshToken(ctx, user, &userAgent, &ipAddress) if err != nil { log.Error(). Str("handler", "user_register"). Str("action", "refresh_token_generation_failed"). Str("user_id", user.ID.String()). Str("tenant_id", user.TenantID.String()). Str("email", user.Email). Err(err). Msg("CRITICAL: account created but failed to generate refresh token") return echo.NewHTTPError(http.StatusInternalServerError, "failed to generate refresh token") } log.Debug(). Str("handler", "user_register"). Str("action", "setting_auth_cookies"). Str("user_id", user.ID.String()). Str("cookie_domain", h.config.Cookie.CookieDomain). Bool("cookie_secure", h.config.Cookie.CookieSecure). Msg("setting access and refresh token cookies") h.setAccessTokenCookie(c, accessToken) h.setRefreshTokenCookie(c, refreshToken) log.Info(). Str("handler", "user_register"). Str("action", "registration_success"). Str("user_id", user.ID.String()). Str("tenant_id", user.TenantID.String()). Str("email", user.Email). Str("tenant_name", tenant.Name). Str("user_role", user.Role). Str("ip", ipAddress). Str("user_agent", userAgent). Bool("has_full_name", req.FirstName != nil && req.LastName != nil). Msg("user registration completed successfully") return c.JSON( http.StatusCreated, RegisterUserResponse{ User: user.ToResponse(), Tenant: tenant.ToResponse(), AccessToken: accessToken, RefershToken: refreshToken, ExpiresIn: int(h.config.JWT.AccessExpiry.Seconds()), }, ) } func (h *UserRegisterHander) setAccessTokenCookie(c echo.Context, token string) { cookie := &http.Cookie{ Name: "access_token", Value: token, Path: "/", // Available to all paths Domain: h.config.Cookie.CookieDomain, MaxAge: int(h.config.JWT.AccessExpiry.Seconds()), // Browser deletes after this time Secure: h.config.Cookie.CookieSecure, // HTTPS only in production HttpOnly: true, // JavaScript cannot access (XSS protection) SameSite: h.parseSameSite(h.config.Cookie.CookieSameSite), // CSRF protection } c.SetCookie(cookie) } func (h *UserRegisterHander) setRefreshTokenCookie(c echo.Context, token string) { cookie := &http.Cookie{ Name: "refresh_token", Value: token, Path: "/", // Available to all paths Domain: h.config.Cookie.CookieDomain, MaxAge: int(h.config.JWT.RefreshExpiry.Seconds()), // Much longer than access token Secure: h.config.Cookie.CookieSecure, // HTTPS only in production HttpOnly: true, // JavaScript cannot access (XSS protection) SameSite: h.parseSameSite(h.config.Cookie.CookieSameSite), // CSRF protection } c.SetCookie(cookie) } func (h *UserRegisterHander) parseSameSite(s string) http.SameSite { switch s { case "strict": return http.SameSiteStrictMode // Never send cookie cross-site case "lax": return http.SameSiteLaxMode // Send on top-level navigation only case "none": return http.SameSiteNoneMode // Always send (requires Secure=true) default: return http.SameSiteDefaultMode // Let browser decide } }