From 5bc4adceea935d42084be1539984e2a1672e7fdf Mon Sep 17 00:00:00 2001 From: Braeden Sowinski Date: Thu, 28 Sep 2023 21:42:58 -0700 Subject: [PATCH] prepare user system --- api/handlers/api.go | 46 ++++++++++++++- api/handlers/cow.go | 2 +- api/handlers/device.go | 8 +-- api/handlers/user.go | 84 ++++++++++++++++++++++++++++ databases/cow.go | 10 ++-- databases/device.go | 10 ++-- databases/user.go | 65 ++++++++++++++++++++++ go.mod | 4 +- go.sum | 3 + models/business.go | 9 +++ models/user.go | 123 +++++++++++++++++++++++++++++++++++++++++ util/idManager.go | 41 ++++++++++++++ util/system.go | 83 +++++++++++++++++++++++++++ 13 files changed, 470 insertions(+), 18 deletions(-) create mode 100644 api/handlers/user.go create mode 100644 databases/user.go create mode 100644 models/business.go create mode 100644 models/user.go create mode 100644 util/idManager.go create mode 100644 util/system.go diff --git a/api/handlers/api.go b/api/handlers/api.go index f3e8eec..b845259 100644 --- a/api/handlers/api.go +++ b/api/handlers/api.go @@ -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 + } + } + } +} diff --git a/api/handlers/cow.go b/api/handlers/cow.go index 5eecfa8..0f8353b 100644 --- a/api/handlers/cow.go +++ b/api/handlers/cow.go @@ -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) diff --git a/api/handlers/device.go b/api/handlers/device.go index 0c16615..d5b0a89 100644 --- a/api/handlers/device.go +++ b/api/handlers/device.go @@ -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 diff --git a/api/handlers/user.go b/api/handlers/user.go new file mode 100644 index 0000000..ee7f6e2 --- /dev/null +++ b/api/handlers/user.go @@ -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) +} diff --git a/databases/cow.go b/databases/cow.go index 24304be..6691453 100644 --- a/databases/cow.go +++ b/databases/cow.go @@ -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 } diff --git a/databases/device.go b/databases/device.go index 77bb5bd..df699af 100644 --- a/databases/device.go +++ b/databases/device.go @@ -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 } diff --git a/databases/user.go b/databases/user.go new file mode 100644 index 0000000..78733d7 --- /dev/null +++ b/databases/user.go @@ -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 +} diff --git a/go.mod b/go.mod index 7e8e09e..78d69e1 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 2fcb4b8..fc00503 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/models/business.go b/models/business.go new file mode 100644 index 0000000..2e06c5d --- /dev/null +++ b/models/business.go @@ -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 +} diff --git a/models/user.go b/models/user.go new file mode 100644 index 0000000..2c0624d --- /dev/null +++ b/models/user.go @@ -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) +} diff --git a/util/idManager.go b/util/idManager.go new file mode 100644 index 0000000..d0f053a --- /dev/null +++ b/util/idManager.go @@ -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) +} diff --git a/util/system.go b/util/system.go new file mode 100644 index 0000000..e06e559 --- /dev/null +++ b/util/system.go @@ -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 +}