3
0

Add support for user creation

This commit is contained in:
Denis Arh
2018-08-05 23:23:47 +02:00
parent baae815899
commit cd85f1b608
10 changed files with 143 additions and 23 deletions

View File

@@ -599,4 +599,21 @@ The following event types may be sent with a message event:
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| username | string | POST | Username or email | N/A | YES |
| password | string | POST | Password for user | N/A | YES |
| password | string | POST | Password for user | N/A | YES |
## Create new user
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/auth/create` | HTTP/S | POST | |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| name | string | POST | Display name | N/A | YES |
| email | string | POST | Email | N/A | YES |
| username | string | POST | Username | N/A | YES |
| password | string | POST | Password | N/A | YES |

View File

@@ -642,6 +642,20 @@
{ "type": "string", "name": "password", "required": true, "title": "Password for user" }
]
}
},
{
"name": "create",
"path": "/create",
"method": "POST",
"title": "Create new user",
"parameters": {
"post": [
{ "type": "string", "name": "name", "required": true, "title": "Display name" },
{ "type": "string", "name": "email", "required": true, "title": "Email" },
{ "type": "string", "name": "username", "required": true, "title": "Username" },
{ "type": "string", "name": "password", "required": true, "title": "Password" }
]
}
}
]
}

View File

@@ -29,6 +29,40 @@
}
]
}
},
{
"Name": "create",
"Method": "POST",
"Title": "Create new user",
"Path": "/create",
"Parameters": {
"post": [
{
"name": "name",
"required": true,
"title": "Display name",
"type": "string"
},
{
"name": "email",
"required": true,
"title": "Email",
"type": "string"
},
{
"name": "username",
"required": true,
"title": "Username",
"type": "string"
},
{
"name": "password",
"required": true,
"title": "Password",
"type": "string"
}
]
}
}
]
}

View File

@@ -13,13 +13,14 @@ var _ = errors.Wrap
type (
Auth struct {
svc struct {
user authCredentialsValidator
user authUserBasics
token authTokenEncoder
}
}
authCredentialsValidator interface {
authUserBasics interface {
ValidateCredentials(ctx context.Context, username, password string) (*types.User, error)
Create(ctx context.Context, input *types.User) (user *types.User, err error)
}
authTokenEncoder interface {
@@ -27,7 +28,7 @@ type (
}
)
func (Auth) New(credValidator authCredentialsValidator, tknEncoder authTokenEncoder) *Auth {
func (Auth) New(credValidator authUserBasics, tknEncoder authTokenEncoder) *Auth {
auth := &Auth{}
auth.svc.user = credValidator
auth.svc.token = tknEncoder
@@ -36,7 +37,18 @@ func (Auth) New(credValidator authCredentialsValidator, tknEncoder authTokenEnco
}
func (ctrl *Auth) Login(ctx context.Context, r *server.AuthLoginRequest) (interface{}, error) {
user, err := ctrl.svc.user.ValidateCredentials(ctx, r.Username, r.Password)
return ctrl.tokenize(ctrl.svc.user.ValidateCredentials(ctx, r.Username, r.Password))
}
func (ctrl *Auth) Create(ctx context.Context, r *server.AuthCreateRequest) (interface{}, error) {
user := &types.User{Username: r.Username}
user.GeneratePassword(r.Password)
return ctrl.tokenize(ctrl.svc.user.Create(ctx, user))
}
// Wraps user return value and appends JWT
func (ctrl *Auth) tokenize(user *types.User, err error) (interface{}, error) {
if err != nil {
return nil, err
}

View File

@@ -28,9 +28,11 @@ type AuthHandlers struct {
// Internal API interface
type AuthAPI interface {
Login(context.Context, *AuthLoginRequest) (interface{}, error)
Create(context.Context, *AuthCreateRequest) (interface{}, error)
}
// HTTP API interface
type AuthHandlersAPI interface {
Login(http.ResponseWriter, *http.Request)
Create(http.ResponseWriter, *http.Request)
}

View File

@@ -25,3 +25,7 @@ func (ah *AuthHandlers) Login(w http.ResponseWriter, r *http.Request) {
params := AuthLoginRequest{}.new()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) { return ah.Auth.Login(r.Context(), params) })
}
func (ah *AuthHandlers) Create(w http.ResponseWriter, r *http.Request) {
params := AuthCreateRequest{}.new()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) { return ah.Auth.Create(r.Context(), params) })
}

View File

@@ -52,3 +52,40 @@ func (a *AuthLoginRequest) Fill(r *http.Request) error {
}
var _ RequestFiller = AuthLoginRequest{}.new()
// Auth create request parameters
type AuthCreateRequest struct {
Name string
Email string
Username string
Password string
}
func (AuthCreateRequest) new() *AuthCreateRequest {
return &AuthCreateRequest{}
}
func (a *AuthCreateRequest) Fill(r *http.Request) error {
r.ParseForm()
get := map[string]string{}
post := map[string]string{}
urlQuery := r.URL.Query()
for name, param := range urlQuery {
get[name] = string(param[0])
}
postVars := r.Form
for name, param := range postVars {
post[name] = string(param[0])
}
a.Name = post["name"]
a.Email = post["email"]
a.Username = post["username"]
a.Password = post["password"]
return nil
}
var _ RequestFiller = AuthCreateRequest{}.new()

View File

@@ -25,6 +25,7 @@ func (ah *AuthHandlers) MountRoutes(r chi.Router, middlewares ...func(http.Handl
r.Use(middlewares...)
r.Route("/auth", func(r chi.Router) {
r.Post("/login", ah.Login)
r.Post("/create", ah.Create)
})
})
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"github.com/crusttech/crust/sam/repository"
"github.com/crusttech/crust/sam/types"
"golang.org/x/crypto/bcrypt"
)
const (
@@ -34,7 +33,7 @@ func (svc user) ValidateCredentials(ctx context.Context, username, password stri
return nil, err
}
if !svc.validatePassword(user, password) {
if !user.ValidatePassword(password) {
return nil, ErrUserInvalidCredentials
}
@@ -54,8 +53,8 @@ func (svc user) Find(ctx context.Context, filter *types.UserFilter) ([]*types.Us
}
func (svc user) Create(ctx context.Context, input *types.User) (user *types.User, err error) {
// no real need for tx here, just presenting the capabilities
return user, svc.rpo.BeginWith(ctx, func(r repository.Interfaces) error {
// Encrypt user password
if user, err = r.CreateUser(input); err != nil {
return err
}
@@ -68,21 +67,6 @@ func (svc user) Update(ctx context.Context, mod *types.User) (*types.User, error
return svc.rpo.UpdateUser(mod)
}
func (svc user) validatePassword(user *types.User, password string) bool {
return user != nil &&
bcrypt.CompareHashAndPassword(user.Password, []byte(password)) == nil
}
func (svc user) generatePassword(user *types.User, password string) error {
pwd, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
user.Password = pwd
return nil
}
func (svc user) canLogin(u *types.User) bool {
return u != nil && u.ID > 0 && u.SuspendedAt == nil && u.DeletedAt == nil
}

View File

@@ -2,6 +2,7 @@ package types
import (
"encoding/json"
"golang.org/x/crypto/bcrypt"
"time"
)
@@ -31,3 +32,17 @@ func (u *User) Valid() bool {
func (u *User) Identity() uint64 {
return u.ID
}
func (u *User) ValidatePassword(password string) bool {
return bcrypt.CompareHashAndPassword(u.Password, []byte(password)) == nil
}
func (u *User) GeneratePassword(password string) error {
pwd, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
u.Password = pwd
return nil
}