mirror of
https://github.com/grafana/grafana.git
synced 2025-09-27 06:43:56 +08:00
feat(signup): progress on new sign up and email verification flow, #2353
This commit is contained in:
@ -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 + '/';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
36
public/app/controllers/signupCtrl.js
Normal file
36
public/app/controllers/signupCtrl.js
Normal file
@ -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
public/app/partials/signup_step2.html
Normal file
113
public/app/partials/signup_step2.html
Normal file
@ -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 {
|
||||||
|
Reference in New Issue
Block a user