parent
197afc4f59
commit
272e4e43b1
@ -0,0 +1,40 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func init() {
|
||||
err := godotenv.Load(".env")
|
||||
if err != nil {
|
||||
fmt.Println("* No .env file found")
|
||||
}
|
||||
Connect()
|
||||
}
|
||||
|
||||
var Client *mongo.Client
|
||||
|
||||
func Connect() {
|
||||
dbUsername := os.Getenv("DB_USERNAME")
|
||||
dbPassword := os.Getenv("DB_PASSWORD")
|
||||
|
||||
serverAPIOptions := options.ServerAPI(options.ServerAPIVersion1)
|
||||
clientOptions := options.Client().
|
||||
ApplyURI("mongodb+srv://" + dbUsername + ":" + dbPassword + "@budgetbuddy.3doyojf.mongodb.net/?retryWrites=true&w=majority").
|
||||
SetServerAPIOptions(serverAPIOptions)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
client, err := mongo.Connect(ctx, clientOptions)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
Client = client
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
module github.com/jacobmveber-01839764/BudgetBuddy
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.0.8 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.1 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
go.mongodb.org/mongo-driver v1.11.3 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
)
|
||||
@ -0,0 +1,52 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
||||
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
|
||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||
github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
|
||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
go.mongodb.org/mongo-driver v1.11.3 h1:Ql6K6qYHEzB6xvu4+AU0BoRoqf9vFPcc4o7MUIdPW8Y=
|
||||
go.mongodb.org/mongo-driver v1.11.3/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/jacobmveber-01839764/BudgetBuddy/db"
|
||||
"github.com/jacobmveber-01839764/BudgetBuddy/routes"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
// disconnect to DB on application exit
|
||||
defer db.Client.Disconnect(context.Background())
|
||||
|
||||
r.Post("/auth/login", routes.Login)
|
||||
r.Post("/auth/login/session", routes.Login)
|
||||
r.Post("/auth/createaccount", routes.CreateAccount)
|
||||
|
||||
log.Fatal(http.ListenAndServe(":3030", r))
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jacobmveber-01839764/BudgetBuddy/db"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func CreateAccount(w http.ResponseWriter, r *http.Request) {
|
||||
// prepare DB
|
||||
err := db.Client.Ping(context.Background(), readpref.Primary())
|
||||
if err != nil {
|
||||
db.Connect()
|
||||
}
|
||||
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||
|
||||
// var v contains POST credentials
|
||||
var v Credentials
|
||||
r.ParseForm()
|
||||
v.Email = r.FormValue("email")
|
||||
v.Password = r.FormValue("password")
|
||||
if v.Email == "" || v.Password == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if len(v.Password) < 8 || len(v.Password) > 255 || !EmailIsValid(v.Email) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprint(w, "{\"error\":\"there was a problem with your request. Please try again with different values\"}")
|
||||
return
|
||||
}
|
||||
|
||||
// search if user with that username already exists
|
||||
find := userCollection.FindOne(r.Context(), bson.D{primitive.E{Key: "email", Value: strings.ToLower(v.Email)}})
|
||||
if find.Err() == nil {
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
fmt.Fprint(w, "{\"error\":\"user already exists with that email\"}")
|
||||
return
|
||||
}
|
||||
|
||||
// create a new session for the new user
|
||||
sessionID := uuid.NewString()
|
||||
session := &http.Cookie{
|
||||
Name: "session",
|
||||
Value: sessionID,
|
||||
Path: "/",
|
||||
Secure: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
MaxAge: 0,
|
||||
}
|
||||
http.SetCookie(w, session)
|
||||
|
||||
// hash and store the user's hashed password
|
||||
hashedPass, err := bcrypt.GenerateFromPassword([]byte(v.Password), 8)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "{\"error\":\"internal server error, please try again later\"}")
|
||||
}
|
||||
|
||||
// add the new user to the database
|
||||
v.Session = sessionID
|
||||
v.Password = string(hashedPass)
|
||||
_, err = userCollection.InsertOne(r.Context(), v)
|
||||
if err != nil {
|
||||
log.Println("* Error inserting new user")
|
||||
}
|
||||
|
||||
// return the account information to the user
|
||||
ret, err := json.Marshal(LoginResponse{
|
||||
Email: v.Email,
|
||||
Session: v.Session,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("* Error marshalling bson.D response")
|
||||
}
|
||||
fmt.Fprint(w, string(ret))
|
||||
}
|
||||
|
||||
func EmailIsValid(email string) bool {
|
||||
_, err := mail.ParseAddress(email)
|
||||
return err == nil
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jacobmveber-01839764/BudgetBuddy/db"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type Credentials struct {
|
||||
Email string `json:"email" bson:"email"`
|
||||
Password string `json:"password" bson:"password"`
|
||||
Session string `json:"session" bson:"session"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
Email string `json:"email"`
|
||||
Session string `json:"session"`
|
||||
}
|
||||
|
||||
func Login(w http.ResponseWriter, r *http.Request) {
|
||||
// prepare DB
|
||||
err := db.Client.Ping(context.Background(), readpref.Primary())
|
||||
if err != nil {
|
||||
db.Connect()
|
||||
}
|
||||
var userCollection = db.Client.Database("budgetbuddy").Collection("users")
|
||||
|
||||
// var v contains POST credentials
|
||||
var v Credentials
|
||||
r.ParseForm()
|
||||
v.Email = r.FormValue("email")
|
||||
v.Password = r.FormValue("password")
|
||||
if v.Email == "" || v.Password == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// cmp struct will be compared with v to verify credentials
|
||||
var cmp Credentials
|
||||
|
||||
found := userCollection.FindOne(r.Context(), bson.D{primitive.E{Key: "email", Value: strings.ToLower(v.Email)}})
|
||||
if found.Err() != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintf(w, "{\"error\":\"account with that email does not exist\"}")
|
||||
return
|
||||
}
|
||||
err = found.Decode(&cmp)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword([]byte(cmp.Password), []byte(v.Password))
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintf(w, "{\"error\":\"invalid password\"}")
|
||||
return
|
||||
}
|
||||
|
||||
// prepare ReturnedAccount struct to be sent back to the client
|
||||
var account LoginResponse
|
||||
err = found.Decode(&account)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// set new session cookie for user, either persistent (remember me) or temporary
|
||||
sessionID := uuid.NewString()
|
||||
var session = &http.Cookie{
|
||||
Name: "session",
|
||||
Value: sessionID,
|
||||
Path: "/",
|
||||
Secure: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
MaxAge: 0,
|
||||
}
|
||||
http.SetCookie(w, session)
|
||||
|
||||
// update the new user session in the DB
|
||||
filter := bson.D{primitive.E{Key: "email", Value: strings.ToLower(account.Email)}}
|
||||
opts := options.Update().SetUpsert(true)
|
||||
update := bson.D{primitive.E{Key: "$set", Value: bson.D{primitive.E{Key: "session", Value: sessionID}}}}
|
||||
userCollection.UpdateOne(context.TODO(), filter, update, opts)
|
||||
|
||||
acc, err := json.Marshal(account)
|
||||
if err != nil {
|
||||
fmt.Println("Error marshalling bson.D response")
|
||||
}
|
||||
fmt.Fprint(w, string(acc))
|
||||
// fmt.Println("logged in user successfully")
|
||||
}
|
||||
Loading…
Reference in new issue