mirror of
https://github.com/gabehf/massflip.git
synced 2026-03-17 19:26:31 -07:00
0.0.4 rate limiting, captcha, more better auth, bug fixes
This commit is contained in:
parent
fdbf7217e9
commit
a3e56fa753
19 changed files with 363 additions and 95 deletions
82
accounts.go
82
accounts.go
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -13,11 +15,15 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Note: I would love to embed a lot of these structs to avoid
|
||||||
|
// duplicate fields, but it breaks json.(Un)marshal and I dont want to deal with that
|
||||||
|
|
||||||
type Credentials struct {
|
type Credentials struct {
|
||||||
Username string `json:"username" bson:"username"`
|
Username string `json:"username" bson:"username"`
|
||||||
Password string `json:"password" bson:"password"`
|
Password string `json:"password" bson:"password"`
|
||||||
|
|
@ -29,6 +35,13 @@ type Login struct {
|
||||||
RememberMe bool `json:"remember"`
|
RememberMe bool `json:"remember"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateAccount struct {
|
||||||
|
Username string `json:"username" bson:"username"`
|
||||||
|
Password string `json:"password" bson:"password"`
|
||||||
|
RememberMe bool `json:"remember"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
Session string `json:"session" bson:"session"`
|
Session string `json:"session" bson:"session"`
|
||||||
}
|
}
|
||||||
|
|
@ -61,13 +74,55 @@ func createAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
DB = openDB()
|
DB = openDB()
|
||||||
}
|
}
|
||||||
userCollection := DB.Database("Users").Collection("Users")
|
var userCollection *mongo.Collection
|
||||||
|
if os.Getenv("ENVIRONMENT") == "production" {
|
||||||
|
userCollection = DB.Database("Users").Collection("Users")
|
||||||
|
} else {
|
||||||
|
userCollection = DB.Database("Development").Collection("Users")
|
||||||
|
}
|
||||||
|
|
||||||
// var v contains POST credentials
|
// var v contains POST credentials
|
||||||
var v Login
|
var v CreateAccount
|
||||||
err = json.NewDecoder(r.Body).Decode(&v)
|
err = json.NewDecoder(r.Body).Decode(&v)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
log.Println("* Create Account Refused: Bad form data")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil || len(v.Password) < 8 || len(v.Password) > 255 || !UsernameRegex.MatchString(v.Username) {
|
var verify struct {
|
||||||
|
Secret string
|
||||||
|
Response string
|
||||||
|
}
|
||||||
|
verify.Secret = os.Getenv("CAPTCHA_SECRET")
|
||||||
|
verify.Response = v.Token
|
||||||
|
|
||||||
|
//url := "https://www.google.com/recaptcha/api/siteverify?secret=" + url.QueryEscape(verify.Secret) + "?response=" + url.QueryEscape(verify.Response)
|
||||||
|
u := "https://www.google.com/recaptcha/api/siteverify"
|
||||||
|
d := url.Values{"secret": []string{verify.Secret}, "response": []string{verify.Response}}
|
||||||
|
|
||||||
|
resp, err := http.PostForm(u, d)
|
||||||
|
|
||||||
|
var captchaResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Time primitive.Timestamp `json:"challenge_ts"`
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
Errors []string `json:"error-codes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
json.NewDecoder(resp.Body).Decode(&captchaResponse)
|
||||||
|
|
||||||
|
if !captchaResponse.Success {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
log.Print("* Create Account Refused: Unsuccessful reCaptcha challenge\nErrors: ")
|
||||||
|
for _, e := range captchaResponse.Errors {
|
||||||
|
fmt.Print(e + " ")
|
||||||
|
}
|
||||||
|
fmt.Print("\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.Password) < 8 || len(v.Password) > 255 || !UsernameRegex.MatchString(v.Username) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprint(w, "{\"error\":\"there was a problem with your request. Please try again with different values\"}")
|
fmt.Fprint(w, "{\"error\":\"there was a problem with your request. Please try again with different values\"}")
|
||||||
return
|
return
|
||||||
|
|
@ -148,7 +203,12 @@ func login(w http.ResponseWriter, r *http.Request) {
|
||||||
DB = openDB()
|
DB = openDB()
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
userCollection := DB.Database("Users").Collection("Users")
|
var userCollection *mongo.Collection
|
||||||
|
if os.Getenv("ENVIRONMENT") == "production" {
|
||||||
|
userCollection = DB.Database("Users").Collection("Users")
|
||||||
|
} else {
|
||||||
|
userCollection = DB.Database("Development").Collection("Users")
|
||||||
|
}
|
||||||
|
|
||||||
// decode POST into v struct
|
// decode POST into v struct
|
||||||
var v Login
|
var v Login
|
||||||
|
|
@ -234,7 +294,12 @@ func loginBySession(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
DB = openDB()
|
DB = openDB()
|
||||||
}
|
}
|
||||||
userCollection := DB.Database("Users").Collection("Users")
|
var userCollection *mongo.Collection
|
||||||
|
if os.Getenv("ENVIRONMENT") == "production" {
|
||||||
|
userCollection = DB.Database("Users").Collection("Users")
|
||||||
|
} else {
|
||||||
|
userCollection = DB.Database("Development").Collection("Users")
|
||||||
|
}
|
||||||
|
|
||||||
var id Session
|
var id Session
|
||||||
var account ReturnedAccount
|
var account ReturnedAccount
|
||||||
|
|
@ -263,7 +328,12 @@ func logout(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
DB = openDB()
|
DB = openDB()
|
||||||
}
|
}
|
||||||
userCollection := DB.Database("Users").Collection("Users")
|
var userCollection *mongo.Collection
|
||||||
|
if os.Getenv("ENVIRONMENT") == "production" {
|
||||||
|
userCollection = DB.Database("Users").Collection("Users")
|
||||||
|
} else {
|
||||||
|
userCollection = DB.Database("Development").Collection("Users")
|
||||||
|
}
|
||||||
|
|
||||||
var v Credentials
|
var v Credentials
|
||||||
|
|
||||||
|
|
|
||||||
9
chat.go
9
chat.go
|
|
@ -4,10 +4,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||||
)
|
)
|
||||||
|
|
@ -23,7 +25,12 @@ func chatColor(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
DB = openDB()
|
DB = openDB()
|
||||||
}
|
}
|
||||||
userCollection := DB.Database("Users").Collection("Users")
|
var userCollection *mongo.Collection
|
||||||
|
if os.Getenv("ENVIRONMENT") == "production" {
|
||||||
|
userCollection = DB.Database("Users").Collection("Users")
|
||||||
|
} else {
|
||||||
|
userCollection = DB.Database("Development").Collection("Users")
|
||||||
|
}
|
||||||
|
|
||||||
// decode PUT into v struct
|
// decode PUT into v struct
|
||||||
var v ColorRequest
|
var v ColorRequest
|
||||||
|
|
|
||||||
26
client.go
26
client.go
|
|
@ -47,6 +47,14 @@ type Client struct {
|
||||||
send chan []byte
|
send chan []byte
|
||||||
|
|
||||||
username string
|
username string
|
||||||
|
|
||||||
|
auth bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Auth struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Key string `json:"key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// readPump pumps messages from the websocket connection to the hub.
|
// readPump pumps messages from the websocket connection to the hub.
|
||||||
|
|
@ -70,18 +78,28 @@ func (c *Client) readPump() {
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
var v WsBetMessage
|
var v Auth
|
||||||
err = json.Unmarshal(message, &v)
|
err = json.Unmarshal(message, &v)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if v.Type == "bind" {
|
// check for authorization messages
|
||||||
|
if v.Type == "auth" {
|
||||||
|
// bind the WS client to the username (useful)
|
||||||
c.username = strings.ToLower(v.Username)
|
c.username = strings.ToLower(v.Username)
|
||||||
|
// if auth key matches session, they are authorized to bet/chat etc
|
||||||
|
if DBGetUserByUsername(c.username).Session == v.Key {
|
||||||
|
c.auth = true
|
||||||
|
}
|
||||||
|
// if the user has bet (and then reloaded the page) send them their state
|
||||||
if c.hub.allUsers[c.username] != "" {
|
if c.hub.allUsers[c.username] != "" {
|
||||||
c.send <- []byte("{\"type\":\"hasbet\",\"value\":true,\"bet\":\"" + c.hub.allUsers[c.username] + "\"}")
|
c.send <- []byte("{\"type\":\"state\",\"value\":true,\"bet\":\"" + c.hub.allUsers[c.username] + "\"}")
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.hub.broadcast <- message
|
// only authorized clients are allowed to push to the Hub
|
||||||
|
if c.auth {
|
||||||
|
c.hub.broadcast <- message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
3
clock.go
3
clock.go
|
|
@ -24,8 +24,7 @@ func (h *Hub) runGameClock() {
|
||||||
msg = "{\"type\":\"flip\",\"value\":\"tails\"}"
|
msg = "{\"type\":\"flip\",\"value\":\"tails\"}"
|
||||||
}
|
}
|
||||||
h.broadcast <- []byte(msg)
|
h.broadcast <- []byte(msg)
|
||||||
time.Sleep(4 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
h.broadcastPoolUpdate()
|
h.broadcastPoolUpdate()
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
db.go
34
db.go
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
|
@ -15,26 +14,21 @@ import (
|
||||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DB = openDB()
|
var DB *mongo.Client
|
||||||
|
|
||||||
func openDB() *mongo.Client {
|
func openDB() *mongo.Client {
|
||||||
err := godotenv.Load()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to load database. Shutting down...")
|
|
||||||
}
|
|
||||||
dbUsername := os.Getenv("DB_USERNAME")
|
dbUsername := os.Getenv("DB_USERNAME")
|
||||||
dbPassword := os.Getenv("DB_PASSWORD")
|
dbPassword := os.Getenv("DB_PASSWORD")
|
||||||
|
|
||||||
serverAPIOptions := options.ServerAPI(options.ServerAPIVersion1)
|
serverAPIOptions := options.ServerAPI(options.ServerAPIVersion1)
|
||||||
clientOptions := options.Client().
|
clientOptions := options.Client().
|
||||||
ApplyURI("mongodb+srv://" + dbUsername + ":" + dbPassword + "@cluster0.tqrat.mongodb.net/myFirstDatabase?retryWrites=true&w=majority").
|
ApplyURI("mongodb+srv://" + dbUsername + ":" + dbPassword + "@cluster0.tqrat.mongodb.net/?retryWrites=true&w=majority").
|
||||||
SetServerAPIOptions(serverAPIOptions)
|
SetServerAPIOptions(serverAPIOptions)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
client, err := mongo.Connect(ctx, clientOptions)
|
client, err := mongo.Connect(ctx, clientOptions)
|
||||||
err = client.Ping(ctx, readpref.Primary())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("DB Error: " + err.Error())
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
@ -44,7 +38,13 @@ func DBSubtractPoints(user string, p int) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
DB = openDB()
|
DB = openDB()
|
||||||
}
|
}
|
||||||
userCollection := DB.Database("Users").Collection("Users")
|
var userCollection *mongo.Collection
|
||||||
|
if os.Getenv("ENVIRONMENT") == "production" {
|
||||||
|
userCollection = DB.Database("Users").Collection("Users")
|
||||||
|
} else {
|
||||||
|
userCollection = DB.Database("Development").Collection("Users")
|
||||||
|
}
|
||||||
|
|
||||||
var v ExistingAccount
|
var v ExistingAccount
|
||||||
err = userCollection.FindOne(context.Background(), bson.D{primitive.E{Key: "username", Value: user}}).Decode(&v)
|
err = userCollection.FindOne(context.Background(), bson.D{primitive.E{Key: "username", Value: user}}).Decode(&v)
|
||||||
|
|
||||||
|
|
@ -67,7 +67,12 @@ func DBAddPoints(user string, p int) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
DB = openDB()
|
DB = openDB()
|
||||||
}
|
}
|
||||||
userCollection := DB.Database("Users").Collection("Users")
|
var userCollection *mongo.Collection
|
||||||
|
if os.Getenv("ENVIRONMENT") == "production" {
|
||||||
|
userCollection = DB.Database("Users").Collection("Users")
|
||||||
|
} else {
|
||||||
|
userCollection = DB.Database("Development").Collection("Users")
|
||||||
|
}
|
||||||
var v ExistingAccount
|
var v ExistingAccount
|
||||||
err = userCollection.FindOne(context.Background(), bson.D{primitive.E{Key: "username", Value: user}}).Decode(&v)
|
err = userCollection.FindOne(context.Background(), bson.D{primitive.E{Key: "username", Value: user}}).Decode(&v)
|
||||||
|
|
||||||
|
|
@ -92,7 +97,12 @@ func DBGetUserByUsername(user string) ExistingAccount {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
DB = openDB()
|
DB = openDB()
|
||||||
}
|
}
|
||||||
userCollection := DB.Database("Users").Collection("Users")
|
var userCollection *mongo.Collection
|
||||||
|
if os.Getenv("ENVIRONMENT") == "production" {
|
||||||
|
userCollection = DB.Database("Users").Collection("Users")
|
||||||
|
} else {
|
||||||
|
userCollection = DB.Database("Development").Collection("Users")
|
||||||
|
}
|
||||||
var v ExistingAccount
|
var v ExistingAccount
|
||||||
err = userCollection.FindOne(context.Background(), bson.D{primitive.E{Key: "username", Value: user}}).Decode(&v)
|
err = userCollection.FindOne(context.Background(), bson.D{primitive.E{Key: "username", Value: user}}).Decode(&v)
|
||||||
|
|
||||||
|
|
|
||||||
36
frontend/package-lock.json
generated
36
frontend/package-lock.json
generated
|
|
@ -1,18 +1,19 @@
|
||||||
{
|
{
|
||||||
"name": "massflip",
|
"name": "massflip",
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "massflip",
|
"name": "massflip",
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.8.3",
|
"core-js": "^3.8.3",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"pinia": "^2.0.0-rc.10",
|
"pinia": "^2.0.0-rc.10",
|
||||||
"vue": "^3.2.13",
|
"vue": "^3.2.13",
|
||||||
"vue-gtag": "^2.0.1"
|
"vue-gtag": "^2.0.1",
|
||||||
|
"vue-recaptcha-v3": "^2.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.16",
|
"@babel/core": "^7.12.16",
|
||||||
|
|
@ -9264,6 +9265,11 @@
|
||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/recaptcha-v3": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/recaptcha-v3/-/recaptcha-v3-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-aGTxYSk3FFNKnXeKDbLpgRDRyIHRZNBF5HyaXXAN1Aj4TSyyZvmoAn9CylvpqLV3pYpIQavwc+2rzhNFn5SsLQ=="
|
||||||
|
},
|
||||||
"node_modules/regenerate": {
|
"node_modules/regenerate": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||||
|
|
@ -10788,6 +10794,17 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-recaptcha-v3": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-recaptcha-v3/-/vue-recaptcha-v3-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-isEDtOfHU4wWRrZZuxciAELtQmPOeEEdicPNa0f1AOyLPy3sCcBEcpFt+FOcO3RQv5unJ3Yn5NlsWtXv9rXqjg==",
|
||||||
|
"dependencies": {
|
||||||
|
"recaptcha-v3": "^1.8.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.0.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-style-loader": {
|
"node_modules/vue-style-loader": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
||||||
|
|
@ -18387,6 +18404,11 @@
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"recaptcha-v3": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/recaptcha-v3/-/recaptcha-v3-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-aGTxYSk3FFNKnXeKDbLpgRDRyIHRZNBF5HyaXXAN1Aj4TSyyZvmoAn9CylvpqLV3pYpIQavwc+2rzhNFn5SsLQ=="
|
||||||
|
},
|
||||||
"regenerate": {
|
"regenerate": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||||
|
|
@ -19558,6 +19580,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vue-recaptcha-v3": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-recaptcha-v3/-/vue-recaptcha-v3-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-isEDtOfHU4wWRrZZuxciAELtQmPOeEEdicPNa0f1AOyLPy3sCcBEcpFt+FOcO3RQv5unJ3Yn5NlsWtXv9rXqjg==",
|
||||||
|
"requires": {
|
||||||
|
"recaptcha-v3": "^1.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"vue-style-loader": {
|
"vue-style-loader": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "massflip",
|
"name": "massflip",
|
||||||
"version": "0.0.2",
|
"version": "0.0.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
|
|
@ -12,7 +12,8 @@
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"pinia": "^2.0.0-rc.10",
|
"pinia": "^2.0.0-rc.10",
|
||||||
"vue": "^3.2.13",
|
"vue": "^3.2.13",
|
||||||
"vue-gtag": "^2.0.1"
|
"vue-gtag": "^2.0.1",
|
||||||
|
"vue-recaptcha-v3": "^2.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.16",
|
"@babel/core": "^7.12.16",
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ onMounted(() => {
|
||||||
let id = jar["session"]
|
let id = jar["session"]
|
||||||
// handle logged in user
|
// handle logged in user
|
||||||
let req = new XMLHttpRequest
|
let req = new XMLHttpRequest
|
||||||
req.open("POST", "/api/login/bysession")
|
req.open('POST', '/api/login/bysession')
|
||||||
req.send(JSON.stringify({
|
req.send(JSON.stringify({
|
||||||
session: id
|
session: id
|
||||||
}))
|
}))
|
||||||
|
|
@ -36,14 +36,16 @@ onMounted(() => {
|
||||||
if (req.readyState == XMLHttpRequest.DONE) {
|
if (req.readyState == XMLHttpRequest.DONE) {
|
||||||
let usr = JSON.parse(req.responseText)
|
let usr = JSON.parse(req.responseText)
|
||||||
if ("error" in usr) {
|
if ("error" in usr) {
|
||||||
document.cookie = "session=; Max-Age=-99999999"
|
document.cookie = 'session=; Max-Age=-99999999'
|
||||||
console.log(usr["error"])
|
console.log(usr['error'])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userStore().updateUser(usr)
|
userStore().updateUser(usr)
|
||||||
|
let jar = cookiesToObj(cookieStr)
|
||||||
let msg = JSON.stringify({
|
let msg = JSON.stringify({
|
||||||
type: "bind",
|
type: "auth",
|
||||||
username: usr.username
|
username: usr.username,
|
||||||
|
key: jar['session']
|
||||||
})
|
})
|
||||||
WSSend(msg)
|
WSSend(msg)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ onMounted(() => {
|
||||||
if (isNaN(hP)) {
|
if (isNaN(hP)) {
|
||||||
headsPercent.value = 0
|
headsPercent.value = 0
|
||||||
headsStyle['--p'] = 50
|
headsStyle['--p'] = 50
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
headsPercent.value = hP
|
headsPercent.value = hP
|
||||||
headsStyle['--p'] = headsPercent.value
|
headsStyle['--p'] = headsPercent.value
|
||||||
|
|
@ -83,26 +84,30 @@ onMounted(() => {
|
||||||
tailsPool.value = wsMsg.tailspool
|
tailsPool.value = wsMsg.tailspool
|
||||||
}
|
}
|
||||||
WS.addEventListener("message", function (evt) {
|
WS.addEventListener("message", function (evt) {
|
||||||
let wsMsg = JSON.parse(evt.data)
|
let MSGs = evt.data.split('\n')
|
||||||
if (wsMsg.type == "pool") {
|
MSGs.forEach((i) => {
|
||||||
updatePool(wsMsg)
|
let wsMsg = JSON.parse(i)
|
||||||
} else if (wsMsg.type == "win") {
|
if (wsMsg.type == "pool") {
|
||||||
userStore().addPoints(wsMsg.value)
|
updatePool(wsMsg)
|
||||||
} else if (wsMsg.type == "tick") {
|
} else if (wsMsg.type == "win") {
|
||||||
let time = wsMsg.clock
|
userStore().addPoints(wsMsg.value)
|
||||||
let timeString = (Math.floor(time/60)).toString() + ":" + ((time%60)>9?"":"0") + (time%60).toString() + "s"
|
} else if (wsMsg.type == "tick") {
|
||||||
clock.value = timeString
|
let time = wsMsg.clock
|
||||||
until.value = 'until next flip'
|
let timeString = (Math.floor(time/60)).toString() + ":" + ((time%60)>9?"":"0") + (time%60).toString() + "s"
|
||||||
updatePool(wsMsg)
|
clock.value = timeString
|
||||||
} else if (wsMsg.type == "flip") {
|
until.value = 'until next flip'
|
||||||
clock.value = wsMsg.value
|
updatePool(wsMsg)
|
||||||
until.value = ''
|
} else if (wsMsg.type == "flip") {
|
||||||
userStore().setBet("")
|
clock.value = wsMsg.value
|
||||||
} else if (wsMsg.type == "hasbet") {
|
until.value = ''
|
||||||
if (wsMsg.value == true) {
|
userStore().setBet("")
|
||||||
userStore().bet = wsMsg.bet
|
} else if (wsMsg.type == "state") {
|
||||||
|
if (wsMsg.value == true) {
|
||||||
|
userStore().bet = wsMsg.bet
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -192,6 +197,9 @@ onMounted(() => {
|
||||||
background:var(--c);
|
background:var(--c);
|
||||||
transform:rotate(calc(var(--p)*3.6deg - 90deg)) translate(calc(var(--w)/2 - 50%));
|
transform:rotate(calc(var(--p)*3.6deg - 90deg)) translate(calc(var(--w)/2 - 50%));
|
||||||
}
|
}
|
||||||
|
@-moz-keyframes p{
|
||||||
|
from{--p:0}
|
||||||
|
}
|
||||||
@keyframes p{
|
@keyframes p{
|
||||||
from{--p:0}
|
from{--p:0}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,14 @@ function submitBet(HorT){
|
||||||
if (userStore().bet != '') {
|
if (userStore().bet != '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (bet.value <= 0) {
|
if (bet.value <= 0 || isNaN(bet.value)) {
|
||||||
hasError.value = true
|
hasError.value = true
|
||||||
error.value = "Error: bet must be greater than 0"
|
error.value = "Error: bet must be greater than 0"
|
||||||
return
|
return
|
||||||
|
} else if (bet.value % 1 != 0) {
|
||||||
|
hasError.value = true
|
||||||
|
error.value = "Error: bet must be a whole number"
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (userStore().points - bet.value < 0) {
|
if (userStore().points - bet.value < 0) {
|
||||||
hasError.value = true
|
hasError.value = true
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,11 @@ const ChatColors = {
|
||||||
green: "limegreen",
|
green: "limegreen",
|
||||||
yellow: "gold",
|
yellow: "gold",
|
||||||
cyan: "cyan",
|
cyan: "cyan",
|
||||||
red: "firebrick",
|
red: "crimson",
|
||||||
pink: "fuchsia",
|
pink: "fuchsia",
|
||||||
violet: "violet",
|
violet: "violet",
|
||||||
orange: "orange",
|
orange: "orange",
|
||||||
|
blue: "cornflowerblue"
|
||||||
}
|
}
|
||||||
|
|
||||||
const _CHAT_MAX_HISTORY = 75;
|
const _CHAT_MAX_HISTORY = 75;
|
||||||
|
|
@ -49,28 +50,31 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WS.addEventListener("message", function (evt) {
|
WS.addEventListener("message", function (evt) {
|
||||||
let wsMsg = JSON.parse(evt.data)
|
let MSGs = evt.data.split('\n')
|
||||||
if (wsMsg.type == "chat") {
|
MSGs.forEach((i) => {
|
||||||
chatQueue.enqueue(wsMsg)
|
let wsMsg = JSON.parse(i)
|
||||||
if (chatQueue.length >= _CHAT_MAX_HISTORY) {
|
if (wsMsg.type == "chat") {
|
||||||
chatQueue.dequeue()
|
chatQueue.enqueue(wsMsg)
|
||||||
|
if (chatQueue.length >= _CHAT_MAX_HISTORY) {
|
||||||
|
chatQueue.dequeue()
|
||||||
|
}
|
||||||
|
log.innerHTML = ""
|
||||||
|
for (let message of Object.values(chatQueue.elements)) {
|
||||||
|
var item = document.createElement("div")
|
||||||
|
let fromUser = document.createElement("span")
|
||||||
|
fromUser.style = `color: ${message.color};`
|
||||||
|
fromUser.innerText = message.username
|
||||||
|
item.appendChild(fromUser)
|
||||||
|
let chatScore = document.createElement("span")
|
||||||
|
chatScore.innerText = `(${message.points})`
|
||||||
|
chatScore.style = `color: ${message.color};font-family: 'Helvetica';font-size: 12px;`
|
||||||
|
item.appendChild(chatScore)
|
||||||
|
let chatMsg = document.createTextNode(`: ${message.message}`)
|
||||||
|
item.appendChild(chatMsg)
|
||||||
|
appendLog(item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log.innerHTML = ""
|
})
|
||||||
for (let message of Object.values(chatQueue.elements)) {
|
|
||||||
var item = document.createElement("div")
|
|
||||||
let fromUser = document.createElement("span")
|
|
||||||
fromUser.style = `color: ${message.color};`
|
|
||||||
fromUser.innerText = message.username
|
|
||||||
item.appendChild(fromUser)
|
|
||||||
let chatScore = document.createElement("span")
|
|
||||||
chatScore.innerText = `(${message.points})`
|
|
||||||
chatScore.style = `color: ${message.color};font-family: 'Helvetica';font-size: 12px;`
|
|
||||||
item.appendChild(chatScore)
|
|
||||||
let chatMsg = document.createTextNode(`: ${message.message}`)
|
|
||||||
item.appendChild(chatMsg)
|
|
||||||
appendLog(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="noAcc" @click="toggleCreate">Don't have an account?</div>
|
<div id="noAcc" @click="toggleCreate">Don't have an account?</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="submit" v-show="!tCreate" @click="login">Login</button>
|
<button class="submit" v-if="!tCreate" @click="login">Login</button>
|
||||||
<button class="submit" v-show="tCreate" @click="createAccount">Create</button>
|
|
||||||
|
<button class="submit" v-if="tCreate" @click="createAccount">Create</button>
|
||||||
</form>
|
</form>
|
||||||
<p id="serverResponse"> {{ serverResponse }}</p>
|
<p id="serverResponse"> {{ serverResponse }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -26,6 +27,8 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineEmits, ref, reactive } from 'vue'
|
import { defineEmits, ref, reactive } from 'vue'
|
||||||
|
import { useReCaptcha } from 'vue-recaptcha-v3'
|
||||||
|
const { executeRecaptcha, recaptchaLoaded } = useReCaptcha()
|
||||||
defineEmits(['display'])
|
defineEmits(['display'])
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
username: '',
|
username: '',
|
||||||
|
|
@ -39,6 +42,11 @@ const pHasError = ref(false)
|
||||||
const serverResponse = ref('')
|
const serverResponse = ref('')
|
||||||
function toggleCreate() {
|
function toggleCreate() {
|
||||||
tCreate.value = !tCreate.value
|
tCreate.value = !tCreate.value
|
||||||
|
if (tCreate.value) {
|
||||||
|
document.getElementById("noAcc").innerText = "Already have an account?"
|
||||||
|
} else {
|
||||||
|
document.getElementById("noAcc").innerText = "Don't have an account?"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function loginFieldsReady() {
|
function loginFieldsReady() {
|
||||||
let ret = true
|
let ret = true
|
||||||
|
|
@ -73,13 +81,17 @@ async function createAccount(e) {
|
||||||
serverResponse.value = "passwords do not match"
|
serverResponse.value = "passwords do not match"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
serverResponse.value = ""
|
||||||
|
await recaptchaLoaded()
|
||||||
|
const token = await executeRecaptcha('login')
|
||||||
let req = new XMLHttpRequest()
|
let req = new XMLHttpRequest()
|
||||||
req.open("POST", "/api/createaccount")
|
req.open("POST", "/api/createaccount")
|
||||||
req.withCredentials = true
|
req.withCredentials = true
|
||||||
req.send(JSON.stringify({
|
req.send(JSON.stringify({
|
||||||
username: form.username,
|
username: form.username,
|
||||||
password: form.password,
|
password: form.password,
|
||||||
remember: form.remember
|
remember: form.remember,
|
||||||
|
token: token
|
||||||
}))
|
}))
|
||||||
req.onreadystatechange = () => {
|
req.onreadystatechange = () => {
|
||||||
if (req.readyState == XMLHttpRequest.DONE) {
|
if (req.readyState == XMLHttpRequest.DONE) {
|
||||||
|
|
@ -104,7 +116,7 @@ function login(e) {
|
||||||
req.send(JSON.stringify({
|
req.send(JSON.stringify({
|
||||||
username: form.username,
|
username: form.username,
|
||||||
password: form.password,
|
password: form.password,
|
||||||
remember: form.remember
|
remember: form.remember,
|
||||||
}))
|
}))
|
||||||
req.onreadystatechange = () => {
|
req.onreadystatechange = () => {
|
||||||
if (req.readyState == XMLHttpRequest.DONE) {
|
if (req.readyState == XMLHttpRequest.DONE) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<p>Massflip v0.0.3 - Copyright 2022 MNRVA</p>
|
<p>Massflip v0.0.4 - Copyright 2022 MNRVA</p>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ import { createApp } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import VueGtag from "vue-gtag";
|
import VueGtag from "vue-gtag";
|
||||||
|
import { VueReCaptcha } from 'vue-recaptcha-v3';
|
||||||
|
|
||||||
// switch these in production
|
// switch these in production
|
||||||
export const WS = new WebSocket("wss://" + "massflip.mnrva.dev" + "/ws")
|
export const WS = new WebSocket("wss://" + "massflip.mnrva.dev" + "/ws")
|
||||||
//export const WS = new WebSocket("ws://" + "127.0.0.1:8000" + "/ws")
|
//export const WS = new WebSocket("ws://" + "localhost:8000" + "/ws")
|
||||||
|
|
||||||
WS.onclose = function() {
|
WS.onclose = function() {
|
||||||
alert("WebSocket connection closed.")
|
alert("WebSocket connection closed.")
|
||||||
|
|
@ -65,5 +66,8 @@ const pinia = createPinia()
|
||||||
var app = createApp(App)
|
var app = createApp(App)
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
app.use(VueGtag, {config: { id: "G-C3WQH98SZB" }})
|
app.use(VueGtag, {config: { id: "G-C3WQH98SZB" }})
|
||||||
|
app.use(VueReCaptcha, { siteKey: '6LeDtKUgAAAAAH0OVNYPyxE8-k9EtjeSDW5jamle' }) // prod
|
||||||
|
//app.use(VueReCaptcha, { siteKey: '6LfD6qUgAAAAAHCKSiEW1fuyuCJiZrAPya26Ro8Z' }) // dev
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ module.exports = defineConfig({
|
||||||
module.exports = {
|
module.exports = {
|
||||||
devServer: {
|
devServer: {
|
||||||
host: "localhost",
|
host: "localhost",
|
||||||
|
port: 8000,
|
||||||
proxy: {
|
proxy: {
|
||||||
"/": {
|
"/": {
|
||||||
target: "http://localhost:8000",
|
target: "http://localhost:8000",
|
||||||
secure: false,
|
secure: false
|
||||||
ws: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
go.mod
12
go.mod
|
|
@ -6,20 +6,24 @@ require github.com/go-chi/chi/v5 v5.0.7 // direct
|
||||||
|
|
||||||
require github.com/gorilla/websocket v1.5.0 // direct
|
require github.com/gorilla/websocket v1.5.0 // direct
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/uuid v1.3.0
|
||||||
|
github.com/joho/godotenv v1.4.0
|
||||||
|
go.mongodb.org/mongo-driver v1.8.4
|
||||||
|
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f
|
||||||
|
golang.org/x/time v0.0.0-20220609170525-579cf78fd858
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-chi/cors v1.2.0 // indirect
|
github.com/go-chi/cors v1.2.0 // indirect
|
||||||
github.com/go-stack/stack v1.8.0 // indirect
|
github.com/go-stack/stack v1.8.0 // indirect
|
||||||
github.com/golang/snappy v0.0.1 // indirect
|
github.com/golang/snappy v0.0.1 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
|
||||||
github.com/joho/godotenv v1.4.0 // indirect
|
|
||||||
github.com/klauspost/compress v1.13.6 // indirect
|
github.com/klauspost/compress v1.13.6 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
github.com/xdg-go/scram v1.0.2 // indirect
|
github.com/xdg-go/scram v1.0.2 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.2 // indirect
|
github.com/xdg-go/stringprep v1.0.2 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||||
go.mongodb.org/mongo-driver v1.8.4 // indirect
|
|
||||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f // indirect
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||||
golang.org/x/text v0.3.5 // indirect
|
golang.org/x/text v0.3.5 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -51,6 +51,8 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
|
||||||
|
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
|
||||||
80
limits.go
Normal file
80
limits.go
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IP limiter code taken from
|
||||||
|
// https://medium.com/@pliutau/rate-limiting-http-requests-in-go-based-on-ip-address-4e66d1bea4cf
|
||||||
|
|
||||||
|
var limiter = NewIPRateLimiter(1, 10)
|
||||||
|
|
||||||
|
// IPRateLimiter
|
||||||
|
type IPRateLimiter struct {
|
||||||
|
ips map[string]*rate.Limiter
|
||||||
|
mu *sync.RWMutex
|
||||||
|
r rate.Limit
|
||||||
|
b int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIPRateLimiter
|
||||||
|
func NewIPRateLimiter(r rate.Limit, b int) *IPRateLimiter {
|
||||||
|
i := &IPRateLimiter{
|
||||||
|
ips: make(map[string]*rate.Limiter),
|
||||||
|
mu: &sync.RWMutex{},
|
||||||
|
r: r,
|
||||||
|
b: b,
|
||||||
|
}
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddIP creates a new rate limiter and adds it to the ips map,
|
||||||
|
// using the IP address as the key
|
||||||
|
func (i *IPRateLimiter) AddIP(ip string) *rate.Limiter {
|
||||||
|
i.mu.Lock()
|
||||||
|
defer i.mu.Unlock()
|
||||||
|
|
||||||
|
limiter := rate.NewLimiter(i.r, i.b)
|
||||||
|
|
||||||
|
i.ips[ip] = limiter
|
||||||
|
|
||||||
|
return limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLimiter returns the rate limiter for the provided IP address if it exists.
|
||||||
|
// Otherwise calls AddIP to add IP address to the map
|
||||||
|
func (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
|
||||||
|
i.mu.Lock()
|
||||||
|
limiter, exists := i.ips[ip]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
i.mu.Unlock()
|
||||||
|
return i.AddIP(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
i.mu.Unlock()
|
||||||
|
|
||||||
|
return limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func limitMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
limiter := limiter.GetLimiter(ip)
|
||||||
|
if !limiter.Allow() {
|
||||||
|
http.Error(w, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
25
main.go
25
main.go
|
|
@ -7,16 +7,27 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO:
|
0.0.4
|
||||||
Later:
|
- added rate limiting
|
||||||
- user pages
|
- added captcha for account creation
|
||||||
- figure out an actual goal for the game
|
- added WebSocket authentication
|
||||||
*
|
- fixed BetInput bugs (NaN, decimals)
|
||||||
|
- seperated development and production environment
|
||||||
|
- frontend bug fixes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
err := godotenv.Load()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("* No .env file found")
|
||||||
|
}
|
||||||
|
DB = openDB()
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
// prepare router
|
// prepare router
|
||||||
|
|
@ -30,6 +41,8 @@ func main() {
|
||||||
// disconnect to DB on application exit
|
// disconnect to DB on application exit
|
||||||
defer DB.Disconnect(context.Background())
|
defer DB.Disconnect(context.Background())
|
||||||
|
|
||||||
|
// rate limiting middleware
|
||||||
|
r.Use(limitMiddleware)
|
||||||
// handlers
|
// handlers
|
||||||
r.Handle("/*", http.FileServer(http.Dir("./frontend/dist")))
|
r.Handle("/*", http.FileServer(http.Dir("./frontend/dist")))
|
||||||
r.Get("/ws", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/ws", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -42,6 +55,6 @@ func main() {
|
||||||
r.Put("/api/chatcolor", chatColor)
|
r.Put("/api/chatcolor", chatColor)
|
||||||
|
|
||||||
// run server
|
// run server
|
||||||
fmt.Println("* Listening on localhost:8000")
|
log.Println("* Listening on localhost:8000")
|
||||||
log.Fatal(http.ListenAndServe(":8000", r))
|
log.Fatal(http.ListenAndServe(":8000", r))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue