feat(signup): almost done with new sign up flow, #2353
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|
||||||
|
|||||||
+44
-33
@@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create user
|
|
||||||
createUserCmd := m.CreateUserCommand{
|
createUserCmd := m.CreateUserCommand{
|
||||||
Email: tempUser.Email,
|
Email: form.Email,
|
||||||
Login: form.Username,
|
Login: form.Username,
|
||||||
Name: form.Name,
|
Name: form.Name,
|
||||||
Password: form.Password,
|
Password: form.Password,
|
||||||
OrgName: form.OrgName,
|
OrgName: form.OrgName,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if setting.VerifyEmailEnabled {
|
||||||
|
if ok, rsp := verifyUserSignUpEmail(form.Email, form.Code); !ok {
|
||||||
|
return rsp
|
||||||
|
}
|
||||||
|
createUserCmd.EmailVerified = true
|
||||||
|
}
|
||||||
|
|
||||||
|
existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email}
|
||||||
|
if err := bus.Dispatch(&existing); err == nil {
|
||||||
|
return ApiError(401, "User with same email address already exists", nil)
|
||||||
|
}
|
||||||
|
|
||||||
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
|
// mark temp user as completed
|
||||||
updateTempUserCmd := m.UpdateTempUserStatusCommand{
|
if ok, rsp := updateTempUserStatus(form.Code, m.TmpUserCompleted); !ok {
|
||||||
Code: tempUser.Code,
|
return rsp
|
||||||
Status: m.TmpUserCompleted,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := bus.Dispatch(&updateTempUserCmd); err != nil {
|
|
||||||
return ApiError(500, "Failed to update temp user", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
}
|
||||||
|
|||||||
+9
-8
@@ -44,15 +44,16 @@ func (u *User) NameOrFallback() string {
|
|||||||
// COMMANDS
|
// COMMANDS
|
||||||
|
|
||||||
type CreateUserCommand struct {
|
type CreateUserCommand struct {
|
||||||
Email string `json:"email" binding:"Required"`
|
Email string
|
||||||
Login string `json:"login"`
|
Login string
|
||||||
Name string `json:"name"`
|
Name string
|
||||||
Company string `json:"compay"`
|
Company string
|
||||||
OrgName string `json:"orgName"`
|
OrgName string
|
||||||
Password string `json:"password" binding:"Required"`
|
Password string
|
||||||
IsAdmin bool `json:"-"`
|
EmailVerified bool
|
||||||
|
IsAdmin bool
|
||||||
|
|
||||||
Result User `json:"-"`
|
Result User
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateUserCommand struct {
|
type UpdateUserCommand struct {
|
||||||
|
|||||||
@@ -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 == "" {
|
||||||
|
|||||||
@@ -80,14 +80,15 @@ func CreateUser(cmd *m.CreateUserCommand) error {
|
|||||||
|
|
||||||
// create user
|
// create user
|
||||||
user := m.User{
|
user := m.User{
|
||||||
Email: cmd.Email,
|
Email: cmd.Email,
|
||||||
Name: cmd.Name,
|
Name: cmd.Name,
|
||||||
Login: cmd.Login,
|
Login: cmd.Login,
|
||||||
Company: cmd.Company,
|
Company: cmd.Company,
|
||||||
IsAdmin: cmd.IsAdmin,
|
IsAdmin: cmd.IsAdmin,
|
||||||
OrgId: orgId,
|
OrgId: orgId,
|
||||||
Created: time.Now(),
|
EmailVerified: cmd.EmailVerified,
|
||||||
Updated: time.Now(),
|
Created: time.Now(),
|
||||||
|
Updated: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cmd.Password) > 0 {
|
if len(cmd.Password) > 0 {
|
||||||
|
|||||||
+10
-5
@@ -75,11 +75,11 @@ var (
|
|||||||
EmailCodeValidMinutes int
|
EmailCodeValidMinutes int
|
||||||
|
|
||||||
// User settings
|
// User settings
|
||||||
AllowUserSignUp bool
|
AllowUserSignUp bool
|
||||||
AllowUserOrgCreate bool
|
AllowUserOrgCreate bool
|
||||||
AutoAssignOrg bool
|
AutoAssignOrg bool
|
||||||
AutoAssignOrgRole string
|
AutoAssignOrgRole string
|
||||||
RequireEmailValidation bool
|
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() {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user