diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..40428f1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Remote", + "type": "go", + "request": "attach", + "mode": "remote", + "port": 4321, + "host": "45.76.84.7", + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile index b2ff818..1d78da6 100644 --- a/Makefile +++ b/Makefile @@ -16,5 +16,4 @@ deploy: .PHONY: service service: rsync -P alfheimgame.service alfheim@alfheimgame.com:/home/alfheim - rsync -P Caddyfile alfheim@alfheimgame.com:/home/alfheim - ssh -t root@alfheimgame.com 'mv /home/alfheim/alfheimgame.service /etc/systemd/system && mv /home/alfheim/Caddyfile /etc/caddy && systemctl enable alfheimgame && systemctl restart alfheimgame' + ssh -t root@alfheimgame.com 'mv /home/alfheim/alfheimgame.service /etc/systemd/system && systemctl enable alfheimgame && systemctl restart alfheimgame' diff --git a/alfheimgame.service b/alfheimgame.service index e9161e9..a13a567 100644 --- a/alfheimgame.service +++ b/alfheimgame.service @@ -15,6 +15,7 @@ CapabilityBoundingSet=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE EnvironmentFile=/etc/environment WorkingDirectory=/home/alfheim/linux_amd64 +#ExecStart=/go/bin/dlv --listen=:4321 --headless=true --log=true exec /home/alfheim/linux_amd64/alfheimgame -- -production ExecStart=/home/alfheim/linux_amd64/alfheimgame -production Restart=on-failure diff --git a/handlers.go b/handlers.go index 9a841aa..ca514c6 100644 --- a/handlers.go +++ b/handlers.go @@ -4,527 +4,560 @@ package main -import "fmt" -import "log" -import "net/http" -import "html/template" -import "encoding/json" +import ( + "encoding/json" + "errors" + "fmt" + "html/template" + "io" + "log" + "net/http" + "runtime/debug" + "strings" + "unicode/utf8" + + "github.com/stripe/stripe-go/v78" + "github.com/stripe/stripe-go/v78/billingportal/session" + "github.com/stripe/stripe-go/v78/customer" + "github.com/stripe/stripe-go/v78/customersession" + "github.com/stripe/stripe-go/v78/price" + "github.com/stripe/stripe-go/v78/subscription" + "github.com/stripe/stripe-go/v78/webhook" +) + //import "strconv" -import "strings" -import "unicode/utf8" -import "errors" -import "runtime/debug" -import "io/ioutil" -import "github.com/stripe/stripe-go/v78" -import "github.com/stripe/stripe-go/v78/price" -import "github.com/stripe/stripe-go/v78/customersession" -import "github.com/stripe/stripe-go/v78/billingportal/session" -import "github.com/stripe/stripe-go/v78/webhook" -import "github.com/stripe/stripe-go/v78/customer" -import "github.com/stripe/stripe-go/v78/subscription" type TemplateData struct { - AuthenticatedUser int32 - FormErrors map[string]string - Account Account - Prices []stripe.Price - ClientSecret string - ActiveSubscription bool + AuthenticatedUser int32 + FormErrors map[string]string + Account Account + Prices []stripe.Price + ClientSecret string + ActiveSubscription bool } func favicon(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, "favicon.ico") + http.ServeFile(w, r, "favicon.ico") } func authenticated_user(w http.ResponseWriter, r *http.Request) int32 { - session, err := store.Get(r, "id") - if err != nil { - log.Println(err) - return 0 - } + session, err := store.Get(r, "id") + if err != nil { + log.Println(err) + return 0 + } - id, ok := session.Values["id"].(int32) - if !ok { - trace := fmt.Sprintf("%s\n%s", errors.New("type assertion to int32 failed").Error(), debug.Stack()) - _ = trace - //log.Println(trace) - return 0 - } + id, ok := session.Values["id"].(int32) + if !ok { + trace := fmt.Sprintf("%s\n%s", errors.New("type assertion to int32 failed").Error(), debug.Stack()) + _ = trace + //log.Println(trace) + return 0 + } - // check if the saved id exists in the database, otherwise it's a bad id and has to be removed from the cookies + // check if the saved id exists in the database, otherwise it's a bad id and has to be removed from the cookies - exists := users.ExistsAccount(id) + exists := users.ExistsAccount(id) - if !exists { - session, _ := store.Get(r, "id") + if !exists { + session, _ := store.Get(r, "id") - session.Values["id"] = 0 - session.Save(r, w) + session.Values["id"] = 0 + session.Save(r, w) - return 0 - } + return 0 + } - return id + return id } func home(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - http.NotFound(w, r) - return - } + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } - id := authenticated_user(w, r) - account, err := users.GetAccount(id) - activesubscription := subscriptions.HasActiveSubscription(id) + id := authenticated_user(w, r) + account, _ := users.GetAccount(id) - text, err := template.ParseFiles("ui/base.html", "ui/index.html") - if err != nil { - http.Error(w, "Internal Server Error", 500) - log.Fatal(err) - } + active_subscription := subscriptions.HasActiveSubscription(id) - switch r.Method { - case http.MethodGet: + text, err := template.ParseFiles("ui/base.html", "ui/index.html") + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Println(err) + } - err := text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account, ActiveSubscription: activesubscription}) - if err != nil { - log.Fatal(err) - http.Error(w, "Internal Server Error", 500) - } + switch r.Method { + case http.MethodGet: + err := text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account, ActiveSubscription: active_subscription}) + if err != nil { + log.Println(err) + http.Error(w, "Internal Server Error", 500) + } - case http.MethodPost: - err := text.Execute(w, TemplateData{}) - if err != nil { - log.Fatal(err) - http.Error(w, "Internal Server Error", 500) - } - } + case http.MethodPost: + err := text.Execute(w, TemplateData{}) + if err != nil { + log.Println(err) + http.Error(w, "Internal Server Error", 500) + } + } } func login(w http.ResponseWriter, r *http.Request) { - text, err := template.ParseFiles("ui/base.html", "ui/login.html") - if err != nil { - http.Error(w, "Internal Server Error", 500) - log.Fatal(err) - } + text, err := template.ParseFiles("ui/base.html", "ui/login.html") + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Fatal(err) + } - switch r.Method { - case http.MethodGet: + switch r.Method { + case http.MethodGet: - err := text.Execute(w, TemplateData{}) - if err != nil { - log.Fatal(err) - http.Error(w, "Internal Server Error", 500) - } + err := text.Execute(w, TemplateData{}) + if err != nil { + log.Fatal(err) + http.Error(w, "Internal Server Error", 500) + } - case http.MethodPost: - session, _ := store.Get(r, "id") - password := r.FormValue("password") - username := r.FormValue("username") - errors := make(map[string]string) + case http.MethodPost: + session, _ := store.Get(r, "id") + password := r.FormValue("password") + username := r.FormValue("username") + errors := make(map[string]string) - if strings.TrimSpace(username) == "" { - errors["username"] = "This field cannot be blank" - } else if utf8.RuneCountInString(username) > 20 { - errors["username"] = "This field is too long (the maximum is 20 characters)" - } + if strings.TrimSpace(username) == "" { + errors["username"] = "This field cannot be blank" + } else if utf8.RuneCountInString(username) > 20 { + errors["username"] = "This field is too long (the maximum is 20 characters)" + } - if strings.TrimSpace(password) == "" { - errors["password"] = "This field cannot be blank" - } else if utf8.RuneCountInString(password) < 8 { - errors["password"] = "This field is too short (the minimum is 8 characters)" - } + if strings.TrimSpace(password) == "" { + errors["password"] = "This field cannot be blank" + } else if utf8.RuneCountInString(password) < 8 { + errors["password"] = "This field is too short (the minimum is 8 characters)" + } - if len(errors) > 0 { + if len(errors) > 0 { - err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors}) - if err != nil { - log.Fatal(err) - http.Error(w, "Internal Server Error", 500) - } + err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors}) + if err != nil { + log.Println(err) + http.Error(w, "Internal Server Error", 500) + } - return - } + return + } - id, err := users.Authenticate(username, password) - if err == ErrInvalidCredentials { - errors["generic"] = "Email or Password is incorrect" - err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors}) - if err != nil { - log.Fatal(err) - http.Error(w, "Internal Server Error", 500) - } + id, err := users.Authenticate(username, password) + if err == ErrInvalidCredentials { + errors["generic"] = "Email or Password is incorrect" + err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors}) + if err != nil { + log.Println(err) + http.Error(w, "Internal Server Error", 500) + } - return - } + return + } - if id > 0 { - session.Values["id"] = id - //log.Println("Logged in with id:", id) - session.Save(r, w) - http.Redirect(w, r, "/account", http.StatusSeeOther) - } - } + if id > 0 { + session.Values["id"] = id + //log.Println("Logged in with id:", id) + session.Save(r, w) + http.Redirect(w, r, "/account", http.StatusSeeOther) + } + } } func logout(w http.ResponseWriter, r *http.Request) { - text, err := template.ParseFiles("ui/base.html", "ui/logout.html") - if err != nil { - http.Error(w, "Internal Server Error", 500) - log.Fatal(err) - } + text, err := template.ParseFiles("ui/base.html", "ui/logout.html") + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Println(err) + } - id := authenticated_user(w, r) - account, err := users.GetAccount(id) - activesubscription := subscriptions.HasActiveSubscription(id) + id := authenticated_user(w, r) + account, err := users.GetAccount(id) - switch r.Method { - case http.MethodGet: + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Println(err) + } - err := text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account, ActiveSubscription: activesubscription}) - if err != nil { - log.Fatal(err) - http.Error(w, "Internal Server Error", 500) - } + activesubscription := subscriptions.HasActiveSubscription(id) - case http.MethodPost: - session, _ := store.Get(r, "id") + switch r.Method { + case http.MethodGet: - session.Values["id"] = 0 - session.Save(r, w) - http.Redirect(w, r, "/", http.StatusSeeOther) - } + err := text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account, ActiveSubscription: activesubscription}) + if err != nil { + log.Println(err) + http.Error(w, "Internal Server Error", 500) + } + + case http.MethodPost: + session, _ := store.Get(r, "id") + + session.Values["id"] = 0 + session.Save(r, w) + http.Redirect(w, r, "/", http.StatusSeeOther) + } } func register(w http.ResponseWriter, r *http.Request) { - text, err := template.ParseFiles("ui/base.html", "ui/register.html") - if err != nil { - http.Error(w, "Internal Server Error", 500) - log.Fatal(err) - } + text, err := template.ParseFiles("ui/base.html", "ui/register.html") + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Println(err) + } - id := authenticated_user(w, r) - account, err := users.GetAccount(id) + id := authenticated_user(w, r) + account, err := users.GetAccount(id) - switch r.Method { - case http.MethodGet: + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Println(err) + } - err := text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account}) - if err != nil { - log.Fatal(err) - http.Error(w, "Internal Server Error", 500) - } + switch r.Method { + case http.MethodGet: - case http.MethodPost: - account := Account{Username: r.FormValue("username"), Password: []byte(r.FormValue("password")), Firstname: r.FormValue("firstname"), Lastname: r.FormValue("lastname"), Email: r.FormValue("email")} + err := text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account}) + if err != nil { + log.Println(err) + http.Error(w, "Internal Server Error", 500) + } - errors := make(map[string]string) + case http.MethodPost: + account := Account{Username: r.FormValue("username"), Password: []byte(r.FormValue("password")), Firstname: r.FormValue("firstname"), Lastname: r.FormValue("lastname"), Email: r.FormValue("email")} - if strings.TrimSpace(account.Username) == "" { - errors["username"] = "This field cannot be blank" - } else if utf8.RuneCountInString(account.Username) > 20 { - errors["username"] = "This field is too long (the maximum is 20 characters)" - } + errors := make(map[string]string) - if strings.TrimSpace(string(account.Password)) == "" { - errors["password"] = "This field cannot be blank" - } else if utf8.RuneCountInString(string(account.Password)) < 8 { - errors["password"] = "This field is too short (the minimum is 8 characters)" - } + if strings.TrimSpace(account.Username) == "" { + errors["username"] = "This field cannot be blank" + } else if utf8.RuneCountInString(account.Username) > 20 { + errors["username"] = "This field is too long (the maximum is 20 characters)" + } - if len(errors) > 0 { + if strings.TrimSpace(string(account.Password)) == "" { + errors["password"] = "This field cannot be blank" + } else if utf8.RuneCountInString(string(account.Password)) < 8 { + errors["password"] = "This field is too short (the minimum is 8 characters)" + } - err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors}) - if err != nil { - log.Fatal(err) - http.Error(w, "Internal Server Error", 500) - } - } + if len(errors) > 0 { - _, err := users.Insert(account.Username, string(account.Password), account.Firstname, account.Lastname, account.Email) + err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors}) + if err != nil { + log.Println(err) + http.Error(w, "Internal Server Error", 500) + } + } - if err == ErrDuplicateEmail || err == ErrDuplicateUsername { + _, err := users.Insert(account.Username, string(account.Password), account.Firstname, account.Lastname, account.Email) - } else if err != nil { + if err == ErrDuplicateEmail || err == ErrDuplicateUsername { - } + } else if err != nil { - http.Redirect(w, r, "/login", http.StatusSeeOther) - } + } + + http.Redirect(w, r, "/login", http.StatusSeeOther) + } } func account(w http.ResponseWriter, r *http.Request) { - //id, err := strconv.Atoi(r.URL.Query().Get("id")) - //if err != nil || id < 1 { - // http.NotFound(w, r) - // return - //} - //account, err := users.Get_account(int32(id)) - //if err != nil { - // log.Fatal(err) - //} + //id, err := strconv.Atoi(r.URL.Query().Get("id")) + //if err != nil || id < 1 { + // http.NotFound(w, r) + // return + //} + //account, err := users.Get_account(int32(id)) + //if err != nil { + // log.Fatal(err) + //} - id := authenticated_user(w, r) - account, err := users.GetAccount(id) + id := authenticated_user(w, r) + account, err := users.GetAccount(id) - //log.Println(id, account) + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Println(err) + } - text, err := template.ParseFiles("ui/base.html", "ui/account.html") - if err != nil { - http.Error(w, "Internal Server Error", 500) - log.Fatal(err) - } + //log.Println(id, account) - switch r.Method { - case http.MethodGet: - err := text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account}) - if err != nil { - log.Fatal(err) - http.Error(w, "Internal Server Error", 500) - } - } + text, err := template.ParseFiles("ui/base.html", "ui/account.html") + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Println(err) + } - //case http.MethodPost: - // text.Execute(w, false) - // if err != nil { - // log.Fatal(err) - // http.Error(w, "Internal Server Error", 500) - // } + switch r.Method { + case http.MethodGet: + err := text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account}) + if err != nil { + log.Println(err) + http.Error(w, "Internal Server Error", 500) + } + } + + //case http.MethodPost: + // text.Execute(w, false) + // if err != nil { + // log.Fatal(err) + // http.Error(w, "Internal Server Error", 500) + // } } func deleteaccount(w http.ResponseWriter, r *http.Request) { - id := authenticated_user(w, r) + id := authenticated_user(w, r) - switch r.Method { - case http.MethodPost: - log.Println("Deleting account with id ", id) - users.Delete(id) + switch r.Method { + case http.MethodPost: + log.Println("Deleting account with id ", id) + users.Delete(id) - session, _ := store.Get(r, "id") + session, err := store.Get(r, "id") + if err != nil { + log.Println(err) + http.Error(w, "Internal Server Error", 500) + } - session.Values["id"] = 0 - session.Save(r, w) + session.Values["id"] = 0 + session.Save(r, w) - http.Redirect(w, r, "/", http.StatusSeeOther) - } + http.Redirect(w, r, "/", http.StatusSeeOther) + } } func subscribe(w http.ResponseWriter, r *http.Request) { - id := authenticated_user(w, r) - account, err := users.GetAccount(id) + id := authenticated_user(w, r) + account, err := users.GetAccount(id) - params := &stripe.PriceListParams{} - params.Limit = stripe.Int64(3) - params.AddExpand("data.product") - results := price.List(params) + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Println(err) + } - prices := make([]stripe.Price, 0) + params := &stripe.PriceListParams{} + params.Limit = stripe.Int64(3) + params.AddExpand("data.product") + results := price.List(params) - for results.Next() { - //log.Println(results.Current()) - prices = append(prices, *results.Price()) - } + prices := make([]stripe.Price, 0) - fm := template.FuncMap{ - "divide": func(a, b float64) float64 { - return a / b - }, - } + for results.Next() { + //log.Println(results.Current()) + prices = append(prices, *results.Price()) + } - text, err := template.New("base.html").Funcs(fm).ParseFiles("ui/base.html", "ui/subscribe.html") - if err != nil { - http.Error(w, "Internal Server Error", 500) - log.Fatal(err) - return - } + fm := template.FuncMap{ + "divide": func(a, b float64) float64 { + return a / b + }, + } - err = text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account, Prices: prices}) - if err != nil { - log.Fatal(err) - http.Error(w, "Internal Server Error", 500) - } + text, err := template.New("base.html").Funcs(fm).ParseFiles("ui/base.html", "ui/subscribe.html") + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Println(err) + return + } + + err = text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account, Prices: prices}) + if err != nil { + log.Println(err) + http.Error(w, "Internal Server Error", 500) + } } func subscribe_stripe(w http.ResponseWriter, r *http.Request) { - id := authenticated_user(w, r) - account, err := users.GetAccount(id) + id := authenticated_user(w, r) + account, err := users.GetAccount(id) - params := &stripe.CustomerSessionParams{ - Customer: stripe.String(account.StripeID), - Components: &stripe.CustomerSessionComponentsParams{ - PricingTable: &stripe.CustomerSessionComponentsPricingTableParams{ - Enabled: stripe.Bool(true), - }, - }, - } - result, err := customersession.New(params) - if err != nil { - log.Println(err) - } + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Println(err) + } - text, err := template.ParseFiles("ui/base.html", "ui/subscribe_stripe.html") - if err != nil { - http.Error(w, "Internal Server Error", 500) - log.Fatal(err) - } + params := &stripe.CustomerSessionParams{ + Customer: stripe.String(account.StripeID), + Components: &stripe.CustomerSessionComponentsParams{ + PricingTable: &stripe.CustomerSessionComponentsPricingTableParams{ + Enabled: stripe.Bool(true), + }, + }, + } + result, err := customersession.New(params) + if err != nil { + log.Println(err) + } - err = text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account, ClientSecret: result.ClientSecret}) - if err != nil { - log.Fatal(err) - http.Error(w, "Internal Server Error", 500) - } + text, err := template.ParseFiles("ui/base.html", "ui/subscribe_stripe.html") + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Println(err) + } + + err = text.Execute(w, TemplateData{AuthenticatedUser: id, Account: account, ClientSecret: result.ClientSecret}) + if err != nil { + log.Println(err) + http.Error(w, "Internal Server Error", 500) + } } func managebilling(w http.ResponseWriter, r *http.Request) { - id := authenticated_user(w, r) - account, err := users.GetAccount(id) - if err != nil { - log.Println(err) - } + id := authenticated_user(w, r) + account, err := users.GetAccount(id) + if err != nil { + log.Println(err) + } - params := &stripe.BillingPortalSessionParams{ - Customer: stripe.String(account.StripeID), - ReturnURL: stripe.String("http://localhost:8080/account"), - } + params := &stripe.BillingPortalSessionParams{ + Customer: stripe.String(account.StripeID), + ReturnURL: stripe.String("http://localhost:8080/account"), + } - result, err := session.New(params) - if err != nil { - log.Println(err) - } + result, err := session.New(params) + if err != nil { + log.Println(err) + } - http.Redirect(w, r, result.URL, http.StatusSeeOther) + http.Redirect(w, r, result.URL, http.StatusSeeOther) } func webhooks(w http.ResponseWriter, r *http.Request) { - const MaxBodyBytes = int64(65536) - r.Body = http.MaxBytesReader(w, r.Body, MaxBodyBytes) - payload, err := ioutil.ReadAll(r.Body) - if err != nil { - log.Printf("Error reading request body: %v\n", err) - w.WriteHeader(http.StatusServiceUnavailable) - return - } + const MaxBodyBytes = int64(65536) + r.Body = http.MaxBytesReader(w, r.Body, MaxBodyBytes) + payload, err := io.ReadAll(r.Body) + if err != nil { + log.Printf("Error reading request body: %v\n", err) + w.WriteHeader(http.StatusServiceUnavailable) + return + } - endpointsecret := "whsec_43420f280f7695d9aa411c17da9ffac9afcecc3d36687035a8cb26f7f892f1cf" + endpointsecret := "whsec_43420f280f7695d9aa411c17da9ffac9afcecc3d36687035a8cb26f7f892f1cf" - event, err := webhook.ConstructEvent(payload, r.Header.Get("Stripe-Signature"), endpointsecret) + event, err := webhook.ConstructEvent(payload, r.Header.Get("Stripe-Signature"), endpointsecret) - if err != nil { - log.Printf("Error verifying webhook signature: %v\n", err) - w.WriteHeader(http.StatusBadRequest) // Return a 400 error on a bad signature - return - } + if err != nil { + log.Printf("Error verifying webhook signature: %v\n", err) + w.WriteHeader(http.StatusBadRequest) // Return a 400 error on a bad signature + return + } - err = json.Unmarshal(payload, &event) - if err != nil { - log.Printf("Failed to parse webhook body json: %v\n", err.Error()) - w.WriteHeader(http.StatusBadRequest) - return - } + err = json.Unmarshal(payload, &event) + if err != nil { + log.Printf("Failed to parse webhook body json: %v\n", err.Error()) + w.WriteHeader(http.StatusBadRequest) + return + } - // Unmarshal the event data into an appropriate struct depending on its Type - switch event.Type { - case "checkout.session.completed": - var checkoutSession stripe.CheckoutSession - err := json.Unmarshal(event.Data.Raw, &checkoutSession) - if err != nil { - log.Printf("Error parsing webhook JSON: %v\n", err) - w.WriteHeader(http.StatusBadRequest) - return - } - // Then define and call a func to handle the successful payment intent. - // handlePaymentIntentSucceeded(paymentIntent) - handle_checkout_session_completed(checkoutSession) + // Unmarshal the event data into an appropriate struct depending on its Type + switch event.Type { + case "checkout.session.completed": + var checkoutSession stripe.CheckoutSession + err := json.Unmarshal(event.Data.Raw, &checkoutSession) + if err != nil { + log.Printf("Error parsing webhook JSON: %v\n", err) + w.WriteHeader(http.StatusBadRequest) + return + } + // Then define and call a func to handle the successful payment intent. + // handlePaymentIntentSucceeded(paymentIntent) + handle_checkout_session_completed(checkoutSession) - case "payment_intent.succeeded": - var paymentIntent stripe.PaymentIntent - err := json.Unmarshal(event.Data.Raw, &paymentIntent) - if err != nil { - log.Printf("Error parsing webhook JSON: %v\n", err) - w.WriteHeader(http.StatusBadRequest) - return - } - // Then define and call a func to handle the successful payment intent. - // handlePaymentIntentSucceeded(paymentIntent) + case "payment_intent.succeeded": + var paymentIntent stripe.PaymentIntent + err := json.Unmarshal(event.Data.Raw, &paymentIntent) + if err != nil { + log.Printf("Error parsing webhook JSON: %v\n", err) + w.WriteHeader(http.StatusBadRequest) + return + } + // Then define and call a func to handle the successful payment intent. + // handlePaymentIntentSucceeded(paymentIntent) - case "payment_method.attached": - var paymentMethod stripe.PaymentMethod - err := json.Unmarshal(event.Data.Raw, &paymentMethod) - if err != nil { - log.Printf("Error parsing webhook JSON: %v\n", err) - w.WriteHeader(http.StatusBadRequest) - return - } + case "payment_method.attached": + var paymentMethod stripe.PaymentMethod + err := json.Unmarshal(event.Data.Raw, &paymentMethod) + if err != nil { + log.Printf("Error parsing webhook JSON: %v\n", err) + w.WriteHeader(http.StatusBadRequest) + return + } - case "setup_intent.succeeded": - var setupIntent stripe.SetupIntent - err := json.Unmarshal(event.Data.Raw, &setupIntent) - if err != nil { - log.Printf("Error parsing webhook JSON: %v\n", err) - w.WriteHeader(http.StatusBadRequest) - return - } + case "setup_intent.succeeded": + var setupIntent stripe.SetupIntent + err := json.Unmarshal(event.Data.Raw, &setupIntent) + if err != nil { + log.Printf("Error parsing webhook JSON: %v\n", err) + w.WriteHeader(http.StatusBadRequest) + return + } + // ... handle other event types + default: + log.Printf("Unhandled event type: %s\n", event.Type) + } - // ... handle other event types - default: - log.Printf("Unhandled event type: %s\n", event.Type) - } - - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusOK) } func handle_checkout_session_completed(checkoutsession stripe.CheckoutSession) error { - //toprint, _ := json.MarshalIndent(checkoutSession, "", " ") - //log.Println(string(toprint)) + //toprint, _ := json.MarshalIndent(checkoutSession, "", " ") + //log.Println(string(toprint)) - subscription, err := subscription.Get(checkoutsession.Subscription.ID, nil); - if err != nil { - log.Println(err) - return err - } + subscription, err := subscription.Get(checkoutsession.Subscription.ID, nil) + if err != nil { + log.Println(err) + return err + } - //log.Println(subscription.Customer.ID) + //log.Println(subscription.Customer.ID) - //var status SubscriptionStatus - //switch subscription.Status { - // case "incomplete": - // status = incomplete - // case "incomplete_expired": - // status = incomplete_expired - // case "trialing": - // status = trialing - // case "active": - // status = active - // case "past_due": - // status = past_due - // case "canceled": - // status = canceled - // case "unpaid": - // status = unpaid - // case "paused": - // status = paused - //} + //var status SubscriptionStatus + //switch subscription.Status { + // case "incomplete": + // status = incomplete + // case "incomplete_expired": + // status = incomplete_expired + // case "trialing": + // status = trialing + // case "active": + // status = active + // case "past_due": + // status = past_due + // case "canceled": + // status = canceled + // case "unpaid": + // status = unpaid + // case "paused": + // status = paused + //} - subscriptions.Insert(checkoutsession.Customer.ID, subscription.ID, checkoutsession.ID, subscription.Status) + subscriptions.Insert(checkoutsession.Customer.ID, subscription.ID, checkoutsession.ID, subscription.Status) - return nil + return nil } func handle_payment_method_attached(paymentmethod stripe.PaymentMethod) error { - //toprint, _ := json.MarshalIndent(setupintent, "", " ") - //log.Println(string(toprint)) + //toprint, _ := json.MarshalIndent(setupintent, "", " ") + //log.Println(string(toprint)) - // make this the new customer's default payment method - params := &stripe.CustomerParams{} - params.DefaultSource = &paymentmethod.ID - /*result*/_, err := customer.Update(paymentmethod.Customer.ID, params) - //log.Println(result) - if err != nil { - log.Println(err) - return err - } + // make this the new customer's default payment method + params := &stripe.CustomerParams{} + params.DefaultSource = &paymentmethod.ID + /*result*/ _, err := customer.Update(paymentmethod.Customer.ID, params) + //log.Println(result) + if err != nil { + log.Println(err) + return err + } - return nil + return nil } diff --git a/main.go b/main.go index f78d378..38ccef7 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,6 @@ import ( "flag" "log" "net/http" - "time" "database/sql" "regexp" @@ -16,10 +15,6 @@ import ( "github.com/gorilla/sessions" _ "github.com/lib/pq" "github.com/stripe/stripe-go/v78" - - "crypto/tls" - - "golang.org/x/crypto/acme/autocert" ) var users *Usermodel @@ -32,8 +27,8 @@ var emailrx = regexp.MustCompile("/^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0- func main() { addr := flag.String("addr", "127.0.0.1:8080", "HTTP network addr") - //prodaddr := flag.String("prodaddr", "127.0.0.1:4000", "HTTP network addr") - prodaddr := flag.String("prodaddr", "45.76.84.7:443", "HTTP network addr") + prodaddr := flag.String("prodaddr", "127.0.0.1:4000", "HTTP network addr") + //prodaddr := flag.String("prodaddr", "45.76.84.7:443", "HTTP network addr") production := flag.Bool("production", false, "Whether to use production port and TLS") _ = addr @@ -94,31 +89,25 @@ func main() { mux.HandleFunc("/webhook", webhooks) if *production { - autocertManager := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist("alfheimgame.com"), - Email: "vicenteferrarismith@gmail.com", - Cache: autocert.DirCache("certs"), - } + // autocertManager := autocert.Manager{ + // Prompt: autocert.AcceptTOS, + // HostPolicy: autocert.HostWhitelist("alfheimgame.com"), + // Email: "vicenteferrarismith@gmail.com", + // Cache: autocert.DirCache("certs"), + // } - tlsConfig := &tls.Config{ - GetCertificate: autocertManager.GetCertificate, - PreferServerCipherSuites: true, - CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256}, - NextProtos: []string{"acme-tls/1"}, - } + // server := &http.Server{ + // Addr: *prodaddr, + // Handler: log_request(secure_headers(mux)), + // TLSConfig: autocertManager.TLSConfig(), + // IdleTimeout: time.Minute, + // ReadTimeout: 5 * time.Second, + // WriteTimeout: 10 * time.Second, + // } - server := &http.Server{ - Addr: *prodaddr, - Handler: log_connection(secure_headers(mux)), - TLSConfig: tlsConfig, - IdleTimeout: time.Minute, - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - - log.Fatal(server.ListenAndServeTLS("", "")) + // log.Fatal(server.ListenAndServeTLS("", "")) + log.Fatal(http.ListenAndServe(*prodaddr, log_request(secure_headers(mux)))) } else { - log.Fatal(http.ListenAndServe(*addr, log_connection(secure_headers(mux)))) + log.Fatal(http.ListenAndServe(*addr, log_request(secure_headers(mux)))) } } diff --git a/middleware.go b/middleware.go index cdb9502..1c35e50 100644 --- a/middleware.go +++ b/middleware.go @@ -1,7 +1,9 @@ package main -import "net/http" -import "log" +import ( + "log" + "net/http" +) func secure_headers(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { @@ -14,9 +16,9 @@ func secure_headers(next http.Handler) http.Handler { return http.HandlerFunc(fn) } -func log_connection(next http.Handler) http.Handler { +func log_request(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { - log.Println("Connection!") + log.Printf("%s - %s %s %s", r.RemoteAddr, r.Proto, r.Method, r.URL) next.ServeHTTP(w, r) } diff --git a/models.go b/models.go index 97803a5..2f8beea 100644 --- a/models.go +++ b/models.go @@ -4,33 +4,37 @@ package main -import "fmt" -import "errors" -import "time" -//import "golang.org/x/crypto/bcrypt" -import "database/sql" -import _ "github.com/lib/pq" -import "log" -import "github.com/alexedwards/argon2id" +import ( + "database/sql" + "errors" + "fmt" + "time" -import "github.com/stripe/stripe-go/v78" -import "github.com/stripe/stripe-go/v78/customer" + //import "golang.org/x/crypto/bcrypt" -var ErrNoRecord = errors.New("no matching record found") + "log" + + "github.com/alexedwards/argon2id" + _ "github.com/lib/pq" + "github.com/stripe/stripe-go/v78" + "github.com/stripe/stripe-go/v78/customer" +) + +var ErrNoRecord = errors.New("no matching record found") var ErrInvalidCredentials = errors.New("invalid credentials") -var ErrDuplicateEmail = errors.New("duplicate email") -var ErrDuplicateUsername = errors.New("duplicate username") +var ErrDuplicateEmail = errors.New("duplicate email") +var ErrDuplicateUsername = errors.New("duplicate username") type Account struct { - ID int32 - Username string - Password []byte - Color int32 + ID int32 + Username string + Password []byte + Color int32 Firstname string - Lastname string - Email string - Created time.Time - StripeID string + Lastname string + Email string + Created time.Time + StripeID string } type SubscriptionStatus string @@ -47,11 +51,11 @@ const ( ) type Subscription struct { - ID int32 - AccountID int32 + ID int32 + AccountID int32 StripeSubscriptionID string - StripeCheckoutID string - Status SubscriptionStatus + StripeCheckoutID string + Status SubscriptionStatus } type Usermodel struct { @@ -64,9 +68,14 @@ type SubscriptionModel struct { func (m *Usermodel) Insert(username string, password string, firstname string, lastname string, email string) (int32, error) { - //hashedpassword, err := bcrypt.GenerateFromPassword([]byte(password), 12) hashedpassword, err := argon2id.CreateHash(password, argon2id.DefaultParams) + + if err != nil { + log.Println(err) + return 0, err + } + //log.Println(hashedpassword) stmt := `INSERT INTO accounts (username, password, firstname, lastname, email, created) VALUES ($1, $2, $3, $4, $5, NOW()) RETURNING id` @@ -85,11 +94,16 @@ func (m *Usermodel) Insert(username string, password string, firstname string, l } params := &stripe.CustomerParams{ - Name: stripe.String(fmt.Sprintf("%s %s", firstname, lastname)), + Name: stripe.String(fmt.Sprintf("%s %s", firstname, lastname)), Email: stripe.String(email), } customer, err := customer.New(params) + if err != nil { + log.Println(err) + return 0, err + } + stmt = `UPDATE accounts SET stripe_id = $1 WHERE id = $2` //log.Println(customer.ID, insertid) @@ -106,8 +120,13 @@ func (m *Usermodel) Insert(username string, password string, firstname string, l func (m *Usermodel) Delete(id int32) error { account, err := users.GetAccount(id) + if err != nil { + log.Println(err) + return err + } + if account.StripeID != "" { - /*result*/_, err := customer.Del(account.StripeID, nil) + /*result*/ _, err := customer.Del(account.StripeID, nil) if err != nil { log.Println(err) } @@ -121,7 +140,6 @@ func (m *Usermodel) Delete(id int32) error { log.Println(err) } - return nil }