prepare user system
This commit is contained in:
13 files changed
+470
-18
No files matched your search
+44
-2
@@ -1,14 +1,20 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/SowinskiBraeden/DeviceBookingAPI/api"
|
||||
"github.com/SowinskiBraeden/DeviceBookingAPI/models"
|
||||
"github.com/SowinskiBraeden/DeviceBookingAPI/util"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gorilla/mux"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/SowinskiBraeden/DeviceBookingAPI/config"
|
||||
@@ -27,9 +33,14 @@ type App struct {
|
||||
|
||||
// New creates a new mux router and all the routes
|
||||
func (a *App) New() *mux.Router {
|
||||
|
||||
// Detect if system is new and needs default admin
|
||||
a.newSystem()
|
||||
|
||||
r := mux.NewRouter()
|
||||
cow := Cow{DB: databases.NewCowDatabase(a.dbHelper)}
|
||||
device := Device{DB: databases.NewDeviceDatabase(a.dbHelper)}
|
||||
// user := User{DB: databases.NewUserDatabase(a.dbHelper)}
|
||||
|
||||
// healthcheck
|
||||
r.HandleFunc("/health", healthCheckHandler)
|
||||
@@ -37,7 +48,7 @@ func (a *App) New() *mux.Router {
|
||||
apiCreate := r.PathPrefix("/api/v1").Subrouter()
|
||||
|
||||
// Data handlers, create, delete, update etc.
|
||||
apiCreate.Handle("/cow/{cow_id}", api.Middleware(http.HandlerFunc(cow.CowByIDHandler))).Methods("GET") // By Object ID not Cow Name
|
||||
apiCreate.Handle("/cow/{cow_id}", api.Middleware(http.HandlerFunc(cow.CowByObjectIDHandler))).Methods("GET") // By Object ID not Cow Name
|
||||
apiCreate.Handle("/cows", api.Middleware(http.HandlerFunc(cow.CowHandler))).Methods("GET") // Returns all cows
|
||||
apiCreate.Handle("/cows", api.Middleware(http.HandlerFunc(cow.CowHandlerQuery))).Methods("POST") // Returns list of cows based of name query
|
||||
apiCreate.Handle("/cows/new", api.Middleware(http.HandlerFunc(cow.NewCowHandler))).Methods("POST") // Create new cow
|
||||
@@ -46,7 +57,7 @@ func (a *App) New() *mux.Router {
|
||||
apiCreate.Handle("/cows/get_devices/{cow_id}", api.Middleware(http.HandlerFunc(device.GetChildDevices))).Methods("POST") // Returns a list of devices from a given Cow obj
|
||||
apiCreate.Handle("cows/bookings/{cow_id}", api.Middleware(http.HandlerFunc(cow.GetBookingsHandler))).Methods("GET") // Returns all bookings for a given cow
|
||||
|
||||
apiCreate.Handle("/device/{device_id}", api.Middleware(http.HandlerFunc(device.DeviceByIDHandler))).Methods("GET") // By Object ID not Device Name
|
||||
apiCreate.Handle("/device/{device_id}", api.Middleware(http.HandlerFunc(device.DeviceByObjectIDHandler))).Methods("GET") // By Object ID not Device Name
|
||||
apiCreate.Handle("/devices", api.Middleware(http.HandlerFunc(device.DeviceHandler))).Methods("GET") // Returns all devices
|
||||
apiCreate.Handle("/devices", api.Middleware(http.HandlerFunc(device.DeviceHandlerQuery))).Methods("POST") // Returns list of devices based of name query
|
||||
apiCreate.Handle("/devices/new", api.Middleware(http.HandlerFunc(device.NewDeviceHandler))).Methods("POST") // create new device
|
||||
@@ -93,3 +104,34 @@ func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
_, _ = io.WriteString(w, string(b))
|
||||
}
|
||||
|
||||
func (a *App) newSystem() {
|
||||
var DB databases.UserDatabase = databases.NewUserDatabase(a.dbHelper)
|
||||
dbResp, err := DB.Find(context.TODO(), bson.M{})
|
||||
if err != nil {
|
||||
zap.S().With(err).Error("Unable to detect new system: failed to get users")
|
||||
}
|
||||
|
||||
if len(dbResp) == 0 {
|
||||
fmt.Println("Admin account setup...")
|
||||
|
||||
for {
|
||||
defaultAdmin := util.CreateDefaultAdmin(DB)
|
||||
|
||||
if util.Confirm("Are the above credentials correct?") {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := DB.InsertOne(ctx, defaultAdmin)
|
||||
if err != nil {
|
||||
log.Printf("Failed to create an admin\n")
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Successfully created default admin")
|
||||
log.Printf("Your default admin ID is %s", defaultAdmin.Details.UID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -77,7 +77,7 @@ func (c Cow) CowHandlerQuery(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// CowByIDHandler returns a cow by ID
|
||||
func (c Cow) CowByIDHandler(w http.ResponseWriter, r *http.Request) {
|
||||
func (c Cow) CowByObjectIDHandler(w http.ResponseWriter, r *http.Request) {
|
||||
cowID := mux.Vars(r)["cow_id"]
|
||||
|
||||
cID, err := primitive.ObjectIDFromHex(cowID)
|
||||
|
||||
@@ -79,12 +79,12 @@ func (d Device) DeviceHandlerQuery(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// DeviceByIDHandler returns a cow by ID
|
||||
func (d Device) DeviceByIDHandler(w http.ResponseWriter, r *http.Request) {
|
||||
deviceID := mux.Vars(r)["cow_id"]
|
||||
func (d Device) DeviceByObjectIDHandler(w http.ResponseWriter, r *http.Request) {
|
||||
deviceID := mux.Vars(r)["device_id"]
|
||||
|
||||
dbResp, err := d.DB.FindOne(context.Background(), bson.M{"_id": deviceID})
|
||||
if err != nil {
|
||||
config.ErrorStatus("failed to get device by ID", http.StatusNotFound, w, err)
|
||||
config.ErrorStatus("failed to get device by ObjectID", http.StatusNotFound, w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ func (d Device) DeviceByIDHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// NewDeviceHandler inserts a new cow into the collection and returns a result and error
|
||||
func (d Device) NewDeviceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
var deviceDetails models.DeviceDetails // Json data will represent the cow details model
|
||||
var deviceDetails models.DeviceDetails // Json data will represent the device details model
|
||||
defer cancel()
|
||||
|
||||
// validate the request body
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
|
||||
"github.com/SowinskiBraeden/DeviceBookingAPI/config"
|
||||
"github.com/SowinskiBraeden/DeviceBookingAPI/databases"
|
||||
"github.com/SowinskiBraeden/DeviceBookingAPI/models"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
DB databases.UserDatabase
|
||||
}
|
||||
|
||||
// CowByIDHandler returns a cow by ID
|
||||
func (u User) UserByObjectIDHandler(w http.ResponseWriter, r *http.Request) {
|
||||
userID := mux.Vars(r)["user_object_id"]
|
||||
|
||||
cID, err := primitive.ObjectIDFromHex(userID)
|
||||
if err != nil {
|
||||
config.ErrorStatus("failed to get objectID from Hex", http.StatusBadRequest, w, err)
|
||||
return
|
||||
}
|
||||
|
||||
dbResp, err := u.DB.FindOne(context.Background(), bson.M{"_id": cID})
|
||||
if err != nil {
|
||||
config.ErrorStatus("failed to get cow by ID", http.StatusNotFound, w, err)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := json.Marshal(models.UserResponse{Status: http.StatusOK, Message: "success", Data: map[string]interface{}{"result": dbResp}})
|
||||
if err != nil {
|
||||
config.ErrorStatus("failed to marshal response", http.StatusInternalServerError, w, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
// TODO: Handle User.Details.UserType properly i.e only SuperUser
|
||||
// NewUserHandler inserts a new cow into the collection and returns a result and error
|
||||
func (u User) NewUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
var userDetails models.UserDetails // Json data will represent the user details model
|
||||
defer cancel()
|
||||
|
||||
// validate the request body
|
||||
if err := json.NewDecoder(r.Body).Decode(&userDetails); err != nil {
|
||||
config.ErrorStatus("failed to unpack request body", http.StatusInternalServerError, w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// use the validator library to validate required fields
|
||||
if validationErr := validate.Struct(&userDetails); validationErr != nil {
|
||||
config.ErrorStatus("invalid request body", http.StatusBadRequest, w, validationErr)
|
||||
return
|
||||
}
|
||||
|
||||
newUser := models.User{
|
||||
ID: primitive.NewObjectID().Hex(),
|
||||
Details: userDetails,
|
||||
}
|
||||
|
||||
result, err := u.DB.InsertOne(ctx, newUser)
|
||||
if err != nil {
|
||||
config.ErrorStatus("failed to insert user", http.StatusBadRequest, w, err)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := json.Marshal(models.UserResponse{Status: http.StatusCreated, Message: "success", Data: map[string]interface{}{"result": result}})
|
||||
if err != nil {
|
||||
config.ErrorStatus("failed to marshal response", http.StatusInternalServerError, w, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write(b)
|
||||
}
|
||||
+5
-5
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/SowinskiBraeden/DeviceBookingAPI/models"
|
||||
)
|
||||
|
||||
const cowName = "cows"
|
||||
const cowDBO = "cows"
|
||||
|
||||
// CowDatabase contains the methods to use with the cow database
|
||||
type CowDatabase interface {
|
||||
@@ -31,7 +31,7 @@ func NewCowDatabase(db DatabaseHelper) CowDatabase {
|
||||
|
||||
func (c *cowDatabase) FindOne(ctx context.Context, filter interface{}) (*models.Cow, error) {
|
||||
cow := &models.Cow{}
|
||||
err := c.db.Collection(cowName).FindOne(ctx, filter).Decode(&cow)
|
||||
err := c.db.Collection(cowDBO).FindOne(ctx, filter).Decode(&cow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -40,7 +40,7 @@ func (c *cowDatabase) FindOne(ctx context.Context, filter interface{}) (*models.
|
||||
|
||||
func (c *cowDatabase) Find(ctx context.Context, filter interface{}) ([]models.Cow, error) {
|
||||
var cows []models.Cow
|
||||
err := c.db.Collection(cowName).Find(ctx, filter).Decode(&cows)
|
||||
err := c.db.Collection(cowDBO).Find(ctx, filter).Decode(&cows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func (c *cowDatabase) Find(ctx context.Context, filter interface{}) ([]models.Co
|
||||
|
||||
// Returns the result (document id) and error
|
||||
func (c *cowDatabase) InsertOne(ctx context.Context, document interface{}) (*mongoInsertOneResult, error) {
|
||||
result, err := c.db.Collection(cowName).InsertOne(ctx, document)
|
||||
result, err := c.db.Collection(cowDBO).InsertOne(ctx, document)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func (c *cowDatabase) InsertOne(ctx context.Context, document interface{}) (*mon
|
||||
}
|
||||
|
||||
func (c *cowDatabase) UpdateOne(ctx context.Context, filter, update interface{}) (*mongoUpdateResult, error) {
|
||||
result, err := c.db.Collection(cowName).UpdateOne(ctx, filter, update)
|
||||
result, err := c.db.Collection(cowDBO).UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+5
-5
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/SowinskiBraeden/DeviceBookingAPI/models"
|
||||
)
|
||||
|
||||
const deviceName = "devices"
|
||||
const deviceDBO = "devices"
|
||||
|
||||
// DeviceDatabase contains the methods to use with the cow database
|
||||
type DeviceDatabase interface {
|
||||
@@ -31,7 +31,7 @@ func NewDeviceDatabase(db DatabaseHelper) DeviceDatabase {
|
||||
|
||||
func (d *deviceDatabase) FindOne(ctx context.Context, filter interface{}) (*models.Device, error) {
|
||||
device := &models.Device{}
|
||||
err := d.db.Collection(deviceName).FindOne(ctx, filter).Decode(&device)
|
||||
err := d.db.Collection(deviceDBO).FindOne(ctx, filter).Decode(&device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -40,7 +40,7 @@ func (d *deviceDatabase) FindOne(ctx context.Context, filter interface{}) (*mode
|
||||
|
||||
func (d *deviceDatabase) Find(ctx context.Context, filter interface{}) ([]models.Device, error) {
|
||||
var devices []models.Device
|
||||
err := d.db.Collection(deviceName).FindOne(ctx, filter).Decode(&devices)
|
||||
err := d.db.Collection(deviceDBO).FindOne(ctx, filter).Decode(&devices)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -48,7 +48,7 @@ func (d *deviceDatabase) Find(ctx context.Context, filter interface{}) ([]models
|
||||
}
|
||||
|
||||
func (d *deviceDatabase) InsertOne(ctx context.Context, document interface{}) (*mongoInsertOneResult, error) {
|
||||
result, err := d.db.Collection(deviceName).InsertOne(ctx, document)
|
||||
result, err := d.db.Collection(deviceDBO).InsertOne(ctx, document)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -56,7 +56,7 @@ func (d *deviceDatabase) InsertOne(ctx context.Context, document interface{}) (*
|
||||
}
|
||||
|
||||
func (d *deviceDatabase) UpdateOne(ctx context.Context, filter, update interface{}) (*mongoUpdateResult, error) {
|
||||
result, err := d.db.Collection(deviceName).UpdateOne(ctx, filter, update)
|
||||
result, err := d.db.Collection(deviceDBO).UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package databases
|
||||
|
||||
// go generate: mockery --name CowDatabase
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SowinskiBraeden/DeviceBookingAPI/models"
|
||||
)
|
||||
|
||||
const userDBO = "users"
|
||||
|
||||
// UserDatabase contains the methods to use with the cow database
|
||||
type UserDatabase interface {
|
||||
FindOne(ctx context.Context, filter interface{}) (*models.User, error)
|
||||
Find(ctx context.Context, filter interface{}) ([]models.User, error)
|
||||
InsertOne(ctx context.Context, document interface{}) (*mongoInsertOneResult, error)
|
||||
UpdateOne(ctx context.Context, filter, document interface{}) (*mongoUpdateResult, error)
|
||||
}
|
||||
|
||||
type userDatabase struct {
|
||||
db DatabaseHelper
|
||||
}
|
||||
|
||||
// NewCowDatabase initialized a new instance of a cow database with the provided db conntection
|
||||
func NewUserDatabase(db DatabaseHelper) UserDatabase {
|
||||
return &userDatabase{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *userDatabase) FindOne(ctx context.Context, filter interface{}) (*models.User, error) {
|
||||
user := &models.User{}
|
||||
err := u.db.Collection(userDBO).FindOne(ctx, filter).Decode(&user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (u *userDatabase) Find(ctx context.Context, filter interface{}) ([]models.User, error) {
|
||||
var users []models.User
|
||||
err := u.db.Collection(userDBO).Find(ctx, filter).Decode(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// Returns the result (document id) and error
|
||||
func (u *userDatabase) InsertOne(ctx context.Context, document interface{}) (*mongoInsertOneResult, error) {
|
||||
result, err := u.db.Collection(userDBO).InsertOne(ctx, document)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (u *userDatabase) UpdateOne(ctx context.Context, filter, update interface{}) (*mongoUpdateResult, error) {
|
||||
result, err := u.db.Collection(userDBO).UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
@@ -5,10 +5,12 @@ go 1.19
|
||||
require (
|
||||
github.com/go-playground/validator/v10 v10.11.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef
|
||||
github.com/joho/godotenv v1.4.0
|
||||
github.com/thanhpk/randstr v1.0.4
|
||||
go.mongodb.org/mongo-driver v1.10.2
|
||||
go.uber.org/zap v1.23.0
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -25,8 +27,8 @@ require (
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
)
|
||||
@@ -17,6 +17,8 @@ github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM=
|
||||
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
|
||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
|
||||
@@ -78,6 +80,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package models
|
||||
|
||||
import "go.mongodb.org/mongo-driver/bson/primitive"
|
||||
|
||||
type Business struct {
|
||||
ID primitive.ObjectID `bson:"_id"`
|
||||
Admins []string `json:"admins"` // array of admin user ID's
|
||||
Users []string `json:"users"` // array of user ID's
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const (
|
||||
lowerCharSet = "abcdedfghijklmnopqrst"
|
||||
upperCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
specialCharSet = "!@#$%&*?,.`~"
|
||||
numberSet = "0123456789"
|
||||
allCharSet = lowerCharSet + upperCharSet + specialCharSet + numberSet
|
||||
)
|
||||
|
||||
// User types
|
||||
const (
|
||||
// SuperUsers can:
|
||||
// - Create/Delete/Modify businesses
|
||||
// - Create/Delete/Modify Admins & Users
|
||||
// - Assign Admins & Users to businesses
|
||||
TypeSuperUser int = 1
|
||||
|
||||
// Admins can:
|
||||
// - Manage/Modify assigned business
|
||||
// - Use reservation system for their business
|
||||
// - Manage/Modify Cows & Devices for their business
|
||||
// - Create Users & assign to their business
|
||||
// - Promote Users to Admins in their business
|
||||
TypeAdmin = 2
|
||||
|
||||
// Users can:
|
||||
// - Use reservation system for their business
|
||||
TypeUser = 3
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID string `bson:"_id"`
|
||||
Details UserDetails `json:"details"`
|
||||
}
|
||||
|
||||
type UserDetails struct {
|
||||
FirstName string `json:"firstname" validate:"required"`
|
||||
LastName string `json:"lastname" validate:"required"`
|
||||
Email string `json:"email" validate:"required"`
|
||||
Password string `json:"-" validate:"min=10,max=32"`
|
||||
TempPassword bool `json:"temppassword"`
|
||||
UID string `json:"uid"`
|
||||
Business string `json:"business"`
|
||||
UserType int `json:"usertype"`
|
||||
Created_at time.Time `json:"created_at"`
|
||||
Updated_at time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (s *User) HashPassword(password string) string {
|
||||
hash, _ := bcrypt.GenerateFromPassword([]byte(password), 14)
|
||||
return string(hash)
|
||||
}
|
||||
|
||||
func (a *User) ComparePasswords(password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(a.Details.Password), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (a *User) CheckPasswordStrength(password string) bool {
|
||||
|
||||
var hasUpper bool = false
|
||||
for _, r := range password {
|
||||
if unicode.IsUpper(r) && unicode.IsLetter(r) {
|
||||
hasUpper = true
|
||||
}
|
||||
}
|
||||
|
||||
var hasLower bool = false
|
||||
for _, r := range password {
|
||||
if !unicode.IsLower(r) && unicode.IsLetter(r) {
|
||||
hasLower = true
|
||||
}
|
||||
}
|
||||
|
||||
if strings.ContainsAny(password, specialCharSet) && hasLower && hasUpper && len(password) >= 8 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (a *User) GeneratePassword(passwordLength, minSpecialChar, minNum, minUpperCase int) string {
|
||||
var password strings.Builder
|
||||
|
||||
//Set special character
|
||||
for i := 0; i < minSpecialChar; i++ {
|
||||
random := rand.Intn(len(specialCharSet))
|
||||
password.WriteString(string(specialCharSet[random]))
|
||||
}
|
||||
|
||||
//Set numeric
|
||||
for i := 0; i < minNum; i++ {
|
||||
random := rand.Intn(len(numberSet))
|
||||
password.WriteString(string(numberSet[random]))
|
||||
}
|
||||
|
||||
//Set uppercase
|
||||
for i := 0; i < minUpperCase; i++ {
|
||||
random := rand.Intn(len(upperCharSet))
|
||||
password.WriteString(string(upperCharSet[random]))
|
||||
}
|
||||
|
||||
remainingLength := passwordLength - minSpecialChar - minNum - minUpperCase
|
||||
for i := 0; i < remainingLength; i++ {
|
||||
random := rand.Intn(len(allCharSet))
|
||||
password.WriteString(string(allCharSet[random]))
|
||||
}
|
||||
inRune := []rune(password.String())
|
||||
rand.Shuffle(len(inRune), func(i, j int) {
|
||||
inRune[i], inRune[j] = inRune[j], inRune[i]
|
||||
})
|
||||
return string(inRune)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
|
||||
"github.com/SowinskiBraeden/DeviceBookingAPI/databases"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
var table = [...]byte{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}
|
||||
|
||||
func ValidateID(id string, DB databases.UserDatabase) bool { // true: valid id, false: id already in use
|
||||
|
||||
dbResp, err := DB.Find(context.TODO(), bson.M{})
|
||||
if err != nil {
|
||||
zap.S().With(err).Error("failed to get users")
|
||||
return false
|
||||
}
|
||||
|
||||
// If len == 0 then ID is not in use
|
||||
if len(dbResp) == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GenerateID(length int) string {
|
||||
b := make([]byte, length)
|
||||
n, err := io.ReadAtLeast(rand.Reader, b, length)
|
||||
if n != length {
|
||||
panic(err)
|
||||
}
|
||||
for i := 0; i < len(b); i++ {
|
||||
b[i] = table[int(b[i])%len(table)]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/mail"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/howeyc/gopass"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
|
||||
"github.com/SowinskiBraeden/DeviceBookingAPI/databases"
|
||||
"github.com/SowinskiBraeden/DeviceBookingAPI/models"
|
||||
)
|
||||
|
||||
func ValidMailAddress(address string) (string, bool) {
|
||||
addr, err := mail.ParseAddress(address)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return addr.Address, true
|
||||
}
|
||||
|
||||
func Confirm(s string) bool {
|
||||
r := bufio.NewReader(os.Stdin)
|
||||
|
||||
fmt.Printf("%s [y/n]: ", s)
|
||||
res, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return strings.ToLower(strings.TrimSpace(res))[0] == 'y'
|
||||
}
|
||||
|
||||
func CreateDefaultAdmin(DB databases.UserDatabase) models.User {
|
||||
fmt.Println()
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("First Name: ")
|
||||
firstname, _ := reader.ReadString('\n')
|
||||
fmt.Print("Last Name: ")
|
||||
lastname, _ := reader.ReadString('\n')
|
||||
fmt.Print("Email: ")
|
||||
email, _ := reader.ReadString('\n')
|
||||
fmt.Print("Password: ")
|
||||
password, _ := gopass.GetPasswd()
|
||||
|
||||
// Clear values of new lines and enter characters
|
||||
firstname = strings.ReplaceAll(firstname, "\n", "")
|
||||
lastname = strings.ReplaceAll(lastname, "\n", "")
|
||||
email = strings.ReplaceAll(email, "\n", "")
|
||||
firstname = strings.ReplaceAll(firstname, "\r", "")
|
||||
lastname = strings.ReplaceAll(lastname, "\r", "")
|
||||
email = strings.ReplaceAll(email, "\r", "")
|
||||
|
||||
var admin models.User
|
||||
admin.Details.FirstName = firstname
|
||||
admin.Details.LastName = lastname
|
||||
admin.Details.Email = email
|
||||
|
||||
pass := strings.TrimSuffix(string(password), "\n")
|
||||
admin.Details.Password = admin.HashPassword(pass)
|
||||
admin.Details.TempPassword = false
|
||||
|
||||
var aid string
|
||||
for {
|
||||
aid = GenerateID(6)
|
||||
if ValidateID(aid, DB) {
|
||||
break
|
||||
}
|
||||
}
|
||||
admin.Details.UID = aid
|
||||
|
||||
admin.Details.UserType = models.TypeSuperUser
|
||||
admin.Details.Created_at, _ = time.Parse(time.RFC3339, time.Now().Format(time.RFC3339))
|
||||
admin.Details.Updated_at, _ = time.Parse(time.RFC3339, time.Now().Format(time.RFC3339))
|
||||
admin.ID = primitive.NewObjectID().Hex()
|
||||
|
||||
return admin
|
||||
}
|
||||
Reference in new issue
Block a user