Browse Source

feat(signup): almost done with new sign up flow, #2353

Torkel Ödegaard 10 years ago
parent
commit
d19e101e6b

+ 3 - 0
conf/defaults.ini

@@ -134,6 +134,9 @@ auto_assign_org = true
 # Default role new users will be automatically assigned (if auto_assign_org above is set to true)
 # Default role new users will be automatically assigned (if auto_assign_org above is set to true)
 auto_assign_org_role = Viewer
 auto_assign_org_role = Viewer
 
 
+# Require email validation before sign up completes
+verify_email_enabled = false
+
 #################################### Anonymous Auth ##########################
 #################################### Anonymous Auth ##########################
 [auth.anonymous]
 [auth.anonymous]
 # enable anonymous access
 # enable anonymous access

BIN
grafana


+ 1 - 0
pkg/api/api.go

@@ -43,6 +43,7 @@ func Register(r *macaron.Macaron) {
 
 
 	// sign up
 	// sign up
 	r.Get("/signup", Index)
 	r.Get("/signup", Index)
+	r.Get("/api/user/signup/options", wrap(GetSignUpOptions))
 	r.Post("/api/user/signup", bind(dtos.SignUpForm{}), wrap(SignUp))
 	r.Post("/api/user/signup", bind(dtos.SignUpForm{}), wrap(SignUp))
 	r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), wrap(SignUpStep2))
 	r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), wrap(SignUpStep2))
 
 

+ 43 - 32
pkg/api/signup.go

@@ -11,6 +11,14 @@ import (
 	"github.com/grafana/grafana/pkg/util"
 	"github.com/grafana/grafana/pkg/util"
 )
 )
 
 
+// GET /api/user/signup/options
+func GetSignUpOptions(c *middleware.Context) Response {
+	return Json(200, util.DynMap{
+		"verifyEmailEnabled": setting.VerifyEmailEnabled,
+		"autoAssignOrg":      setting.AutoAssignOrg,
+	})
+}
+
 // POST /api/user/signup
 // POST /api/user/signup
 func SignUp(c *middleware.Context, form dtos.SignUpForm) Response {
 func SignUp(c *middleware.Context, form dtos.SignUpForm) Response {
 	if !setting.AllowUserSignUp {
 	if !setting.AllowUserSignUp {
@@ -19,7 +27,7 @@ func SignUp(c *middleware.Context, form dtos.SignUpForm) Response {
 
 
 	existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email}
 	existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email}
 	if err := bus.Dispatch(&existing); err == nil {
 	if err := bus.Dispatch(&existing); err == nil {
-		return ApiError(401, "User with same email address already exists", nil)
+		return ApiError(422, "User with same email address already exists", nil)
 	}
 	}
 
 
 	cmd := m.CreateTempUserCommand{}
 	cmd := m.CreateTempUserCommand{}
@@ -49,64 +57,49 @@ func SignUpStep2(c *middleware.Context, form dtos.SignUpStep2Form) Response {
 		return ApiError(401, "User signup is disabled", nil)
 		return ApiError(401, "User signup is disabled", nil)
 	}
 	}
 
 
-	query := m.GetTempUserByCodeQuery{Code: form.Code}
-
-	if err := bus.Dispatch(&query); err != nil {
-		if err == m.ErrTempUserNotFound {
-			return ApiError(404, "Invalid email verification code", nil)
-		}
-		return ApiError(500, "Failed to read temp user", err)
+	createUserCmd := m.CreateUserCommand{
+		Email:    form.Email,
+		Login:    form.Username,
+		Name:     form.Name,
+		Password: form.Password,
+		OrgName:  form.OrgName,
 	}
 	}
 
 
-	tempUser := query.Result
-	if tempUser.Email != form.Email {
-		return ApiError(404, "Email verification code does not match email", nil)
+	if setting.VerifyEmailEnabled {
+		if ok, rsp := verifyUserSignUpEmail(form.Email, form.Code); !ok {
+			return rsp
+		}
+		createUserCmd.EmailVerified = true
 	}
 	}
 
 
-	existing := m.GetUserByLoginQuery{LoginOrEmail: tempUser.Email}
+	existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email}
 	if err := bus.Dispatch(&existing); err == nil {
 	if err := bus.Dispatch(&existing); err == nil {
 		return ApiError(401, "User with same email address already exists", nil)
 		return ApiError(401, "User with same email address already exists", nil)
 	}
 	}
 
 
-	// create user
-	createUserCmd := m.CreateUserCommand{
-		Email:    tempUser.Email,
-		Login:    form.Username,
-		Name:     form.Name,
-		Password: form.Password,
-		OrgName:  form.OrgName,
-	}
-
 	if err := bus.Dispatch(&createUserCmd); err != nil {
 	if err := bus.Dispatch(&createUserCmd); err != nil {
 		return ApiError(500, "Failed to create user", err)
 		return ApiError(500, "Failed to create user", err)
 	}
 	}
 
 
 	// publish signup event
 	// publish signup event
 	user := &createUserCmd.Result
 	user := &createUserCmd.Result
-
 	bus.Publish(&events.SignUpCompleted{
 	bus.Publish(&events.SignUpCompleted{
 		Email: user.Email,
 		Email: user.Email,
 		Name:  user.NameOrFallback(),
 		Name:  user.NameOrFallback(),
 	})
 	})
 
 
-	// update tempuser
-	updateTempUserCmd := m.UpdateTempUserStatusCommand{
-		Code:   tempUser.Code,
-		Status: m.TmpUserCompleted,
-	}
-
-	if err := bus.Dispatch(&updateTempUserCmd); err != nil {
-		return ApiError(500, "Failed to update temp user", err)
+	// mark temp user as completed
+	if ok, rsp := updateTempUserStatus(form.Code, m.TmpUserCompleted); !ok {
+		return rsp
 	}
 	}
 
 
 	// check for pending invites
 	// check for pending invites
-	invitesQuery := m.GetTempUsersQuery{Email: tempUser.Email, Status: m.TmpUserInvitePending}
+	invitesQuery := m.GetTempUsersQuery{Email: form.Email, Status: m.TmpUserInvitePending}
 	if err := bus.Dispatch(&invitesQuery); err != nil {
 	if err := bus.Dispatch(&invitesQuery); err != nil {
 		return ApiError(500, "Failed to query database for invites", err)
 		return ApiError(500, "Failed to query database for invites", err)
 	}
 	}
 
 
 	apiResponse := util.DynMap{"message": "User sign up completed succesfully", "code": "redirect-to-landing-page"}
 	apiResponse := util.DynMap{"message": "User sign up completed succesfully", "code": "redirect-to-landing-page"}
-
 	for _, invite := range invitesQuery.Result {
 	for _, invite := range invitesQuery.Result {
 		if ok, rsp := applyUserInvite(user, invite, false); !ok {
 		if ok, rsp := applyUserInvite(user, invite, false); !ok {
 			return rsp
 			return rsp
@@ -119,3 +112,21 @@ func SignUpStep2(c *middleware.Context, form dtos.SignUpStep2Form) Response {
 
 
 	return Json(200, apiResponse)
 	return Json(200, apiResponse)
 }
 }
+
+func verifyUserSignUpEmail(email string, code string) (bool, Response) {
+	query := m.GetTempUserByCodeQuery{Code: code}
+
+	if err := bus.Dispatch(&query); err != nil {
+		if err == m.ErrTempUserNotFound {
+			return false, ApiError(404, "Invalid email verification code", nil)
+		}
+		return false, ApiError(500, "Failed to read temp user", err)
+	}
+
+	tempUser := query.Result
+	if tempUser.Email != email {
+		return false, ApiError(404, "Email verification code does not match email", nil)
+	}
+
+	return true, nil
+}

+ 10 - 9
pkg/models/user.go

@@ -44,15 +44,16 @@ func (u *User) NameOrFallback() string {
 // COMMANDS
 // COMMANDS
 
 
 type CreateUserCommand struct {
 type CreateUserCommand struct {
-	Email    string `json:"email" binding:"Required"`
-	Login    string `json:"login"`
-	Name     string `json:"name"`
-	Company  string `json:"compay"`
-	OrgName  string `json:"orgName"`
-	Password string `json:"password" binding:"Required"`
-	IsAdmin  bool   `json:"-"`
-
-	Result User `json:"-"`
+	Email         string
+	Login         string
+	Name          string
+	Company       string
+	OrgName       string
+	Password      string
+	EmailVerified bool
+	IsAdmin       bool
+
+	Result User
 }
 }
 
 
 type UpdateUserCommand struct {
 type UpdateUserCommand struct {

+ 4 - 0
pkg/services/notifications/notifications.go

@@ -123,6 +123,10 @@ func validateResetPasswordCode(query *m.ValidateResetPasswordCodeQuery) error {
 }
 }
 
 
 func signUpStartedHandler(evt *events.SignUpStarted) error {
 func signUpStartedHandler(evt *events.SignUpStarted) error {
+	if !setting.VerifyEmailEnabled {
+		return nil
+	}
+
 	log.Info("User signup started: %s", evt.Email)
 	log.Info("User signup started: %s", evt.Email)
 
 
 	if evt.Email == "" {
 	if evt.Email == "" {

+ 9 - 8
pkg/services/sqlstore/user.go

@@ -80,14 +80,15 @@ func CreateUser(cmd *m.CreateUserCommand) error {
 
 
 		// create user
 		// create user
 		user := m.User{
 		user := m.User{
-			Email:   cmd.Email,
-			Name:    cmd.Name,
-			Login:   cmd.Login,
-			Company: cmd.Company,
-			IsAdmin: cmd.IsAdmin,
-			OrgId:   orgId,
-			Created: time.Now(),
-			Updated: time.Now(),
+			Email:         cmd.Email,
+			Name:          cmd.Name,
+			Login:         cmd.Login,
+			Company:       cmd.Company,
+			IsAdmin:       cmd.IsAdmin,
+			OrgId:         orgId,
+			EmailVerified: cmd.EmailVerified,
+			Created:       time.Now(),
+			Updated:       time.Now(),
 		}
 		}
 
 
 		if len(cmd.Password) > 0 {
 		if len(cmd.Password) > 0 {

+ 10 - 5
pkg/setting/setting.go

@@ -75,11 +75,11 @@ var (
 	EmailCodeValidMinutes int
 	EmailCodeValidMinutes int
 
 
 	// User settings
 	// User settings
-	AllowUserSignUp        bool
-	AllowUserOrgCreate     bool
-	AutoAssignOrg          bool
-	AutoAssignOrgRole      string
-	RequireEmailValidation bool
+	AllowUserSignUp    bool
+	AllowUserOrgCreate bool
+	AutoAssignOrg      bool
+	AutoAssignOrgRole  string
+	VerifyEmailEnabled bool
 
 
 	// Http auth
 	// Http auth
 	AdminUser     string
 	AdminUser     string
@@ -394,6 +394,7 @@ func NewConfigContext(args *CommandLineArgs) {
 	AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true)
 	AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true)
 	AutoAssignOrg = users.Key("auto_assign_org").MustBool(true)
 	AutoAssignOrg = users.Key("auto_assign_org").MustBool(true)
 	AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"})
 	AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"})
+	VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
 
 
 	// anonymous access
 	// anonymous access
 	AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
 	AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
@@ -425,6 +426,10 @@ func NewConfigContext(args *CommandLineArgs) {
 
 
 	readSessionConfig()
 	readSessionConfig()
 	readSmtpSettings()
 	readSmtpSettings()
+
+	if VerifyEmailEnabled && !Smtp.Enabled {
+		log.Warn("require_email_validation is enabled but smpt is disabled")
+	}
 }
 }
 
 
 func readSessionConfig() {
 func readSessionConfig() {

+ 9 - 1
public/app/controllers/signupCtrl.js

@@ -19,10 +19,18 @@ function (angular, config) {
       $scope.formModel.email = params.email;
       $scope.formModel.email = params.email;
       $scope.formModel.username = params.email;
       $scope.formModel.username = params.email;
       $scope.formModel.code = params.code;
       $scope.formModel.code = params.code;
+
+      $scope.verifyEmailEnabled = false;
+      $scope.autoAssignOrg = false;
+
+      backendSrv.get('/api/user/signup/options').then(function(options) {
+        $scope.verifyEmailEnabled = options.verifyEmailEnabled;
+        $scope.autoAssignOrg = options.autoAssignOrg;
+      });
     };
     };
 
 
     $scope.submit = function() {
     $scope.submit = function() {
-      if (!$scope.signupForm.$valid) {
+      if (!$scope.signUpForm.$valid) {
         return;
         return;
       }
       }
 
 

+ 3 - 3
public/app/partials/signup_step2.html

@@ -27,9 +27,9 @@
 
 
 			<br>
 			<br>
 
 
-			<form name="signupForm" class="login-form">
+			<form name="signUpForm" class="login-form">
 
 
-				<div style="display: inline-block; margin-bottom: 25px; width: 300px">
+				<div style="display: inline-block; margin-bottom: 25px; width: 300px" ng-if="verifyEmailEnabled">
 					<div class="editor-option">
 					<div class="editor-option">
 						<label class="small">Email verification code: (sent to your email)</label>
 						<label class="small">Email verification code: (sent to your email)</label>
 						<input type="text" class="input input-xlarge text-center" ng-model="formModel.code" required></input>
 						<input type="text" class="input input-xlarge text-center" ng-model="formModel.code" required></input>
@@ -37,7 +37,7 @@
 				</div>
 				</div>
 
 
 				<div class="tight-from-container">
 				<div class="tight-from-container">
-					<div class="tight-form">
+					<div class="tight-form" ng-if="!autoAssignOrg">
 						<ul class="tight-form-list">
 						<ul class="tight-form-list">
 							<li class="tight-form-item" style="width: 128px">
 							<li class="tight-form-item" style="width: 128px">
 								Organization name
 								Organization name

+ 5 - 6
public/app/services/backendSrv.js

@@ -37,17 +37,16 @@ function (angular, _, config) {
           return;
           return;
         }
         }
 
 
-        if (err.status === 422) {
-          alertSrv.set("Validation failed", "", "warning", 4000);
-          throw err.data;
-        }
-
         var data = err.data || { message: 'Unexpected error' };
         var data = err.data || { message: 'Unexpected error' };
-
         if (_.isString(data)) {
         if (_.isString(data)) {
           data = { message: data };
           data = { message: data };
         }
         }
 
 
+        if (err.status === 422) {
+          alertSrv.set("Validation failed", data.message, "warning", 4000);
+          throw data;
+        }
+
         data.severity = 'error';
         data.severity = 'error';
 
 
         if (err.status < 500) {
         if (err.status < 500) {