Add support for user creation
This commit is contained in:
@@ -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 |
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) })
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user