mirror of https://github.com/grafana/grafana.git
				
				
				
			feat(signup): progress on new sign up and email verification flow, #2353
This commit is contained in:
		
							parent
							
								
									d25624a8ad
								
							
						
					
					
						commit
						24dfa55465
					
				|  | @ -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)) | ||||||
|  |  | ||||||
|  | @ -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"` | ||||||
|  |  | ||||||
|  | @ -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"}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ define([ | ||||||
|   './jsonEditorCtrl', |   './jsonEditorCtrl', | ||||||
|   './loginCtrl', |   './loginCtrl', | ||||||
|   './invitedCtrl', |   './invitedCtrl', | ||||||
|  |   './signupCtrl', | ||||||
|   './resetPasswordCtrl', |   './resetPasswordCtrl', | ||||||
|   './sidemenuCtrl', |   './sidemenuCtrl', | ||||||
|   './errorCtrl', |   './errorCtrl', | ||||||
|  |  | ||||||
|  | @ -58,8 +58,12 @@ function (angular, config) { | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       backendSrv.post('/api/user/signup', $scope.formModel).then(function() { |       backendSrv.post('/api/user/signup', $scope.formModel).then(function(result) { | ||||||
|         window.location.href = config.appSubUrl + '/'; |         if (result.status === 'SignUpCreated') { | ||||||
|  |           $location.path('/signup').search({email: $scope.formModel.email}); | ||||||
|  |         } else { | ||||||
|  |           window.location.href = config.appSubUrl + '/'; | ||||||
|  |         } | ||||||
|       }); |       }); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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(); | ||||||
|  | 
 | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -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> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @ -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', | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue