Browse Source

feat(signup): progress on new sign up and email verification flow, #2353

Torkel Ödegaard 10 years ago
parent
commit
24dfa55465

+ 2 - 1
pkg/api/api.go

@@ -43,7 +43,8 @@ func Register(r *macaron.Macaron) {
 
 
 	// sign up
 	// sign up
 	r.Get("/signup", Index)
 	r.Get("/signup", Index)
-	r.Post("/api/user/signup", bind(m.CreateUserCommand{}), wrap(SignUp))
+	r.Post("/api/user/signup", bind(dtos.SignUpForm{}), wrap(SignUp))
+	r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), wrap(SignUpStep2))
 
 
 	// invited
 	// invited
 	r.Get("/api/user/invite/:code", wrap(GetInviteInfoByCode))
 	r.Get("/api/user/invite/:code", wrap(GetInviteInfoByCode))

+ 8 - 0
pkg/api/dtos/user.go

@@ -4,6 +4,14 @@ type SignUpForm struct {
 	Email string `json:"email" binding:"Required"`
 	Email string `json:"email" binding:"Required"`
 }
 }
 
 
+type SignUpStep2Form struct {
+	Email    string `json:"email"`
+	Name     string `json:"name"`
+	Username string `json:"username"`
+	Code     string `json:"code"`
+	OrgName  string `json:"orgName"`
+}
+
 type AdminCreateUserForm struct {
 type AdminCreateUserForm struct {
 	Email    string `json:"email"`
 	Email    string `json:"email"`
 	Login    string `json:"login"`
 	Login    string `json:"login"`

+ 34 - 6
pkg/api/signup.go

@@ -27,7 +27,7 @@ func SignUp(c *middleware.Context, form dtos.SignUpForm) Response {
 	cmd.Email = form.Email
 	cmd.Email = form.Email
 	cmd.Status = m.TmpUserSignUpStarted
 	cmd.Status = m.TmpUserSignUpStarted
 	cmd.InvitedByUserId = c.UserId
 	cmd.InvitedByUserId = c.UserId
-	cmd.Code = util.GetRandomString(10)
+	cmd.Code = util.GetRandomString(20)
 	cmd.RemoteAddr = c.Req.RemoteAddr
 	cmd.RemoteAddr = c.Req.RemoteAddr
 
 
 	if err := bus.Dispatch(&cmd); err != nil {
 	if err := bus.Dispatch(&cmd); err != nil {
@@ -36,13 +36,41 @@ func SignUp(c *middleware.Context, form dtos.SignUpForm) Response {
 
 
 	// user := cmd.Resu
 	// user := cmd.Resu
 
 
-	bus.Publish(&events.UserSignedUp{Email: form.Email})
+	bus.Publish(&events.SignUpStarted{
+		Email: form.Email,
+		Code:  cmd.Code,
+	})
 
 
-	//
 	// loginUserWithUser(&user, c)
 	// loginUserWithUser(&user, c)
-	//
-	//
 
 
 	metrics.M_Api_User_SignUpStarted.Inc(1)
 	metrics.M_Api_User_SignUpStarted.Inc(1)
-	return ApiSuccess("User created and logged in")
+
+	return Json(200, util.DynMap{"status": "SignUpCreated"})
+}
+
+func SignUpStep2(c *middleware.Context, form dtos.SignUpStep2Form) Response {
+	if !setting.AllowUserSignUp {
+		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)
+	}
+
+	tempUser := query.Result
+	if tempUser.Email != form.Email {
+		return ApiError(404, "Email verification code does not match email", nil)
+	}
+
+	existing := m.GetUserByLoginQuery{LoginOrEmail: tempUser.Email}
+	if err := bus.Dispatch(&existing); err == nil {
+		return ApiError(401, "User with same email address already exists", nil)
+	}
+
+	return Json(200, util.DynMap{"status": "SignUpCreated"})
 }
 }

+ 2 - 4
pkg/events/events.go

@@ -70,12 +70,10 @@ type UserCreated struct {
 	Email     string    `json:"email"`
 	Email     string    `json:"email"`
 }
 }
 
 
-type UserSignedUp struct {
+type SignUpStarted struct {
 	Timestamp time.Time `json:"timestamp"`
 	Timestamp time.Time `json:"timestamp"`
-	Id        int64     `json:"id"`
-	Name      string    `json:"name"`
-	Login     string    `json:"login"`
 	Email     string    `json:"email"`
 	Email     string    `json:"email"`
+	Code      string    `json:"code"`
 }
 }
 
 
 type SignUpCompleted struct {
 type SignUpCompleted struct {

+ 2 - 2
pkg/services/notifications/notifications.go

@@ -25,7 +25,7 @@ func Init() error {
 	bus.AddHandler("email", validateResetPasswordCode)
 	bus.AddHandler("email", validateResetPasswordCode)
 	bus.AddHandler("email", sendEmailCommandHandler)
 	bus.AddHandler("email", sendEmailCommandHandler)
 
 
-	bus.AddEventListener(userSignedUpHandler)
+	bus.AddEventListener(signUpStartedHandler)
 
 
 	mailTemplates = template.New("name")
 	mailTemplates = template.New("name")
 	mailTemplates.Funcs(template.FuncMap{
 	mailTemplates.Funcs(template.FuncMap{
@@ -120,7 +120,7 @@ func validateResetPasswordCode(query *m.ValidateResetPasswordCodeQuery) error {
 	return nil
 	return nil
 }
 }
 
 
-func userSignedUpHandler(evt *events.UserSignedUp) error {
+func signUpStartedHandler(evt *events.SignUpStarted) error {
 	log.Info("User signed up: %s, send_option: %s", evt.Email, setting.Smtp.SendWelcomeEmailOnSignUp)
 	log.Info("User signed up: %s, send_option: %s", evt.Email, setting.Smtp.SendWelcomeEmailOnSignUp)
 
 
 	if evt.Email == "" || !setting.Smtp.SendWelcomeEmailOnSignUp {
 	if evt.Email == "" || !setting.Smtp.SendWelcomeEmailOnSignUp {

+ 1 - 0
public/app/controllers/all.js

@@ -7,6 +7,7 @@ define([
   './jsonEditorCtrl',
   './jsonEditorCtrl',
   './loginCtrl',
   './loginCtrl',
   './invitedCtrl',
   './invitedCtrl',
+  './signupCtrl',
   './resetPasswordCtrl',
   './resetPasswordCtrl',
   './sidemenuCtrl',
   './sidemenuCtrl',
   './errorCtrl',
   './errorCtrl',

+ 6 - 2
public/app/controllers/loginCtrl.js

@@ -58,8 +58,12 @@ function (angular, config) {
         return;
         return;
       }
       }
 
 
-      backendSrv.post('/api/user/signup', $scope.formModel).then(function() {
-        window.location.href = config.appSubUrl + '/';
+      backendSrv.post('/api/user/signup', $scope.formModel).then(function(result) {
+        if (result.status === 'SignUpCreated') {
+          $location.path('/signup').search({email: $scope.formModel.email});
+        } else {
+          window.location.href = config.appSubUrl + '/';
+        }
       });
       });
     };
     };
 
 

+ 36 - 0
public/app/controllers/signupCtrl.js

@@ -0,0 +1,36 @@
+define([
+  'angular',
+  'config',
+],
+function (angular, config) {
+  'use strict';
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('SignUpCtrl', function($scope, $location, contextSrv, backendSrv) {
+
+    contextSrv.sidemenu = false;
+
+    $scope.formModel = {};
+
+    $scope.init = function() {
+      var email = $location.search().email;
+      $scope.formModel.orgName = email;
+      $scope.formModel.email = email;
+      $scope.formModel.username = email;
+    };
+
+    $scope.submit = function() {
+      if (!$scope.signupForm.$valid) {
+        return;
+      }
+
+      backendSrv.post('/api/user/signup/step2', $scope.formModel).then(function() {
+        window.location.href = config.appSubUrl + '/';
+      });
+    };
+
+    $scope.init();
+
+  });
+});

+ 113 - 0
public/app/partials/signup_step2.html

@@ -0,0 +1,113 @@
+<div class="container">
+
+	<div class="signup-page-background">
+	</div>
+
+	<div class="login-box">
+
+		<div class="login-box-logo">
+			<img src="img/logo_transparent_200x75.png">
+		</div>
+
+    <div class="invite-box">
+			<h3>
+				You're almost there.
+			</h3>
+
+			<div class="modal-tagline">
+				We just need a couple of more bits of<br> information to finish creating your account.
+			</div>
+
+			<div style="display: inline-block; margin-top: 25px; width: 300px">
+					<div class="editor-option">
+						<label class="small">Your email:</label>
+						<span class="large">{{formModel.email}}</span>
+					</div>
+			</div>
+
+			<br>
+
+			<form name="signupForm" class="login-form">
+
+				<div style="display: inline-block; margin-bottom: 25px; width: 300px">
+					<div class="editor-option">
+						<label class="small">Email verification code: <em>Sent to your email just now</em></label>
+						<input type="text" class="input input-xlarge" ng-model="formModel.code" required></input>
+					</div>
+				</div>
+
+				<div class="tight-from-container">
+					<div class="tight-form">
+						<ul class="tight-form-list">
+							<li class="tight-form-item" style="width: 128px">
+								Organization
+							</li>
+							<li>
+								<input type="text" name="orgName" class="tight-form-input last" ng-model='formModel.orgName' placeholder="Name your organization" style="width: 253px">
+							</li>
+						</ul>
+						<div class="clearfix"></div>
+					</div>
+
+					<div class="tight-form">
+						<ul class="tight-form-list">
+							<li class="tight-form-item" style="width: 128px">
+								Name
+							</li>
+							<li>
+								<input type="text" name="name" class="tight-form-input last" ng-model='formModel.name' placeholder="Name (optional)" style="width: 253px">
+							</li>
+						</ul>
+						<div class="clearfix"></div>
+					</div>
+					<div class="tight-form">
+						<ul class="tight-form-list">
+							<li class="tight-form-item" style="width: 128px">
+								Username
+							</li>
+							<li>
+								<input type="text" class="tight-form-input last" required ng-model='formModel.username' placeholder="Username" style="width: 253px" autocomplete="off">
+							</li>
+						</ul>
+						<div class="clearfix"></div>
+					</div>
+
+					<div class="tight-form">
+						<ul class="tight-form-list">
+							<li class="tight-form-item" style="width: 128px">
+								Password
+							</li>
+							<li>
+								<input type="password" class="tight-form-input last" required ng-model="formModel.password" id="inputPassword" style="width: 253px" placeholder="password" autocomplete="off">
+							</li>
+						</ul>
+						<div class="clearfix"></div>
+					</div>
+				</div>
+
+				<div style="margin-left: 147px; width: 254px;">
+					<password-strength password="formModel.password"></password-strength>
+				</div>
+
+				<div class="login-submit-button-row">
+					<button type="submit" class="btn" ng-click="submit();" ng-class="{'btn-inverse': !signUpForm.$valid, 'btn-primary': signUpForm.$valid}">
+						Continue
+					</button>
+				</div>
+			</form>
+
+			<div class="clearfix"></div>
+		</div>
+
+		<div class="row" style="margin-top: 50px">
+			<div class="version-footer text-center small">
+				Grafana version: {{buildInfo.version}}, commit: {{buildInfo.commit}},
+				build date: {{buildInfo.buildstamp | date: 'yyyy-MM-dd HH:mm:ss' }}
+			</div>
+		</div>
+
+	</div>
+
+</div>
+
+

+ 4 - 0
public/app/routes/all.js

@@ -106,6 +106,10 @@ define([
         templateUrl: 'app/partials/signup_invited.html',
         templateUrl: 'app/partials/signup_invited.html',
         controller : 'InvitedCtrl',
         controller : 'InvitedCtrl',
       })
       })
+      .when('/signup', {
+        templateUrl: 'app/partials/signup_step2.html',
+        controller : 'SignUpCtrl',
+      })
       .when('/user/password/send-reset-email', {
       .when('/user/password/send-reset-email', {
         templateUrl: 'app/partials/reset_password.html',
         templateUrl: 'app/partials/reset_password.html',
         controller : 'ResetPasswordCtrl',
         controller : 'ResetPasswordCtrl',

+ 1 - 1
public/css/less/login.less

@@ -4,7 +4,7 @@
   float: left;
   float: left;
   margin-left: 25%;
   margin-left: 25%;
   margin-right: 25%;
   margin-right: 25%;
-  padding-top: 50px;
+  padding-top: 25px;
 }
 }
 
 
 .login-box {
 .login-box {