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
 | ||||
| 	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
 | ||||
| 	r.Get("/api/user/invite/:code", wrap(GetInviteInfoByCode)) | ||||
|  |  | |||
|  | @ -4,6 +4,14 @@ type SignUpForm struct { | |||
| 	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 { | ||||
| 	Email    string `json:"email"` | ||||
| 	Login    string `json:"login"` | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ func SignUp(c *middleware.Context, form dtos.SignUpForm) Response { | |||
| 	cmd.Email = form.Email | ||||
| 	cmd.Status = m.TmpUserSignUpStarted | ||||
| 	cmd.InvitedByUserId = c.UserId | ||||
| 	cmd.Code = util.GetRandomString(10) | ||||
| 	cmd.Code = util.GetRandomString(20) | ||||
| 	cmd.RemoteAddr = c.Req.RemoteAddr | ||||
| 
 | ||||
| 	if err := bus.Dispatch(&cmd); err != nil { | ||||
|  | @ -36,13 +36,41 @@ func SignUp(c *middleware.Context, form dtos.SignUpForm) Response { | |||
| 
 | ||||
| 	// user := cmd.Resu
 | ||||
| 
 | ||||
| 	bus.Publish(&events.UserSignedUp{Email: form.Email}) | ||||
| 	bus.Publish(&events.SignUpStarted{ | ||||
| 		Email: form.Email, | ||||
| 		Code:  cmd.Code, | ||||
| 	}) | ||||
| 
 | ||||
| 	//
 | ||||
| 	// loginUserWithUser(&user, c)
 | ||||
| 	//
 | ||||
| 	//
 | ||||
| 
 | ||||
| 	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"` | ||||
| } | ||||
| 
 | ||||
| type UserSignedUp struct { | ||||
| type SignUpStarted struct { | ||||
| 	Timestamp time.Time `json:"timestamp"` | ||||
| 	Id        int64     `json:"id"` | ||||
| 	Name      string    `json:"name"` | ||||
| 	Login     string    `json:"login"` | ||||
| 	Email     string    `json:"email"` | ||||
| 	Code      string    `json:"code"` | ||||
| } | ||||
| 
 | ||||
| type SignUpCompleted struct { | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ func Init() error { | |||
| 	bus.AddHandler("email", validateResetPasswordCode) | ||||
| 	bus.AddHandler("email", sendEmailCommandHandler) | ||||
| 
 | ||||
| 	bus.AddEventListener(userSignedUpHandler) | ||||
| 	bus.AddEventListener(signUpStartedHandler) | ||||
| 
 | ||||
| 	mailTemplates = template.New("name") | ||||
| 	mailTemplates.Funcs(template.FuncMap{ | ||||
|  | @ -120,7 +120,7 @@ func validateResetPasswordCode(query *m.ValidateResetPasswordCodeQuery) error { | |||
| 	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) | ||||
| 
 | ||||
| 	if evt.Email == "" || !setting.Smtp.SendWelcomeEmailOnSignUp { | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ define([ | |||
|   './jsonEditorCtrl', | ||||
|   './loginCtrl', | ||||
|   './invitedCtrl', | ||||
|   './signupCtrl', | ||||
|   './resetPasswordCtrl', | ||||
|   './sidemenuCtrl', | ||||
|   './errorCtrl', | ||||
|  |  | |||
|  | @ -58,8 +58,12 @@ function (angular, config) { | |||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       backendSrv.post('/api/user/signup', $scope.formModel).then(function() { | ||||
|       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 + '/'; | ||||
|         } | ||||
|       }); | ||||
|     }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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', | ||||
|         controller : 'InvitedCtrl', | ||||
|       }) | ||||
|       .when('/signup', { | ||||
|         templateUrl: 'app/partials/signup_step2.html', | ||||
|         controller : 'SignUpCtrl', | ||||
|       }) | ||||
|       .when('/user/password/send-reset-email', { | ||||
|         templateUrl: 'app/partials/reset_password.html', | ||||
|         controller : 'ResetPasswordCtrl', | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
|   float: left; | ||||
|   margin-left: 25%; | ||||
|   margin-right: 25%; | ||||
|   padding-top: 50px; | ||||
|   padding-top: 25px; | ||||
| } | ||||
| 
 | ||||
| .login-box { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue