move back to netoik-api repo

This commit is contained in:
samuel 2023-01-19 17:05:02 +01:00
parent 9eb7609bd0
commit 2b91282f4f
36 changed files with 0 additions and 338 deletions

View File

@ -1,2 +0,0 @@
build:
go build -o bin/server cmd/server/main.go

Binary file not shown.

View File

@ -1,35 +0,0 @@
package main
import (
"flag"
"fmt"
"net/http"
"netoik.io/netoik-website/pkg/captcha"
"netoik.io/netoik-website/pkg/conf"
"netoik.io/netoik-website/pkg/contact"
"os"
)
func main() {
// Parse command line arguments
path := flag.String("c", "server.conf", "Config file")
flag.Parse()
// Parse config file
if !conf.ParseFile(*path) {
os.Exit(1)
}
// Setup captcha
captcha.Setup()
// Declare api routes
http.HandleFunc("/api/contact/send", contact.HandleSend)
http.HandleFunc("/api/captcha/new", captcha.HandleNew)
// Start listening
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", conf.Conf.BindHost, conf.Conf.BindPort), nil); err != nil {
fmt.Fprintf(os.Stderr, "cannot listen at %s:%d: %s\n", conf.Conf.BindHost, conf.Conf.BindPort, err.Error())
os.Exit(1)
}
}

View File

@ -1,10 +0,0 @@
module netoik.io/netoik-website
go 1.18
require (
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
)

View File

@ -1,8 +0,0 @@
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=

View File

@ -1,18 +0,0 @@
package api
import (
"encoding/json"
"net/http"
)
type Answer struct {
Success bool `json:"success"`
Id string `json:"id,omitempty"`
}
func Reply(w http.ResponseWriter, code int, answer Answer) {
a, _ := json.Marshal(answer)
w.Header().Set(http.CanonicalHeaderKey("content-type"), "application/json")
w.WriteHeader(code)
w.Write(a)
}

View File

@ -1,60 +0,0 @@
package captcha
import (
"errors"
"fmt"
"github.com/dchest/captcha"
"net/http"
"netoik.io/netoik-website/pkg/api"
"netoik.io/netoik-website/pkg/conf"
"os"
"path/filepath"
"time"
)
func writeImage(id string) bool {
path := filepath.Join(conf.Conf.CaptchaDirectory, id+".png")
file, err := os.Create(path)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: cannot write file %q: %s\n", path, err.Error())
return false
}
if err = captcha.WriteImage(file, id, conf.Conf.CaptchaWidth, conf.Conf.CaptchaHeight); err != nil {
fmt.Fprintf(os.Stderr, "ERROR: cannot write captcha into file %q: %s\n", path, err.Error())
return false
}
return true
}
func HandleNew(w http.ResponseWriter, r *http.Request) {
// Check method
if r.Method != "POST" {
api.Reply(w, 405, api.Answer{})
return
}
// Create new captcha
id := captcha.NewLen(conf.Conf.CaptchaLength)
// Write captcha image
if !writeImage(id) {
api.Reply(w, 500, api.Answer{})
return
}
// Remove captcha image after expiration time
go func(id string) {
time.Sleep(conf.Conf.CaptchaExpiration)
path := filepath.Join(conf.Conf.CaptchaDirectory, id+".png")
if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
fmt.Fprintf(os.Stderr, "ERROR: cannot remove captcha image after expiration %q: %s", path, err.Error())
}
}(id)
// Return captcha id
api.Reply(w, 200, api.Answer{Success: true, Id: id})
}
func Setup() {
captcha.SetCustomStore(captcha.NewMemoryStore(captcha.CollectNum, conf.Conf.CaptchaExpiration))
}

View File

@ -1,108 +0,0 @@
package conf
import (
"fmt"
"github.com/pelletier/go-toml"
"net"
"os"
"time"
)
type conf struct {
BindHost string `toml:"bind_host"`
BindPort int `toml:"bind_port"`
SMTPHost string `toml:"smtp_host"`
SMTPPort int `toml:"smtp_port"`
SMTPUsername string `toml:"smtp_username"`
SMTPPassword string `toml:"smtp_password"`
SMTPReceiver string `toml:"smtp_receiver"`
CaptchaDirectory string `toml:"captcha_directory"`
CaptchaLength int `toml:"captcha_length"`
CaptchaWidth int `toml:"captcha_width"`
CaptchaHeight int `toml:"captcha_height"`
CaptchaExpiration time.Duration `toml:"captcha_expiration"`
}
var Conf = conf{
BindHost: "127.0.0.1",
BindPort: 8000,
CaptchaLength: 6,
CaptchaWidth: 240,
CaptchaHeight: 80,
CaptchaExpiration: time.Hour,
}
func ParseFile(path string) bool {
var failure bool
// Open config file
file, err := os.Open(path)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: cannot open config file %q: %s\n", path, err.Error())
return false
}
// Read data from config file
var data = make([]byte, 10000)
n, err := file.Read(data)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: cannot read config file %q: %s\n", path, err.Error())
return false
}
// Parse toml from config file
if err = toml.Unmarshal(data[:n], &Conf); err != nil {
fmt.Fprintf(os.Stderr, "ERROR: cannot load toml config from file %q: %s\n", path, err.Error())
return false
}
// Check arguments in config file
if net.ParseIP(Conf.BindHost) == nil {
fmt.Fprintf(os.Stderr, "ERROR: bad value for 'bind_host' in config file %q: should be a valid IP address\n", path)
failure = true
}
if Conf.BindPort < 1 || Conf.BindPort > 65535 {
fmt.Fprintf(os.Stderr, "ERROR: bad value for 'bind_port' in config file %q: should be an integer in 1-65535\n", path)
failure = true
}
if Conf.SMTPHost == "" {
fmt.Fprintf(os.Stderr, "ERROR: missing value for 'smtp_host' in config file %q\n", path)
failure = true
}
if Conf.SMTPPort < 1 || Conf.SMTPPort > 65535 {
fmt.Fprintf(os.Stderr, "ERROR: bad value for 'smtp_port' in config file %q: should be an integer in 1-65535\n", path)
failure = true
}
if Conf.SMTPUsername == "" {
fmt.Fprintf(os.Stderr, "ERROR: missing value for 'smtp_username' in config file %q\n", path)
failure = true
}
if Conf.SMTPPassword == "" {
fmt.Fprintf(os.Stderr, "ERROR: missing value for 'smtp_password' in config file %q\n", path)
failure = true
}
if Conf.SMTPReceiver == "" {
fmt.Fprintf(os.Stderr, "ERROR: missing value for 'smtp_receiver' in config file %q\n", path)
failure = true
}
if Conf.CaptchaDirectory == "" {
fmt.Fprintf(os.Stderr, "ERROR: missing value for 'captcha_directory' in config file %q\n", path)
failure = true
}
if Conf.CaptchaLength == 0 {
fmt.Fprintf(os.Stderr, "ERROR: missing value for 'captcha_length' in config file %q\n", path)
failure = true
}
if Conf.CaptchaWidth == 0 {
fmt.Fprintf(os.Stderr, "ERROR: missing value for 'captcha_width' in config file %q\n", path)
failure = true
}
if Conf.CaptchaHeight == 0 {
fmt.Fprintf(os.Stderr, "ERROR: missing value for 'captcha_height' in config file %q\n", path)
failure = true
}
return !failure
}

View File

@ -1,97 +0,0 @@
package contact
import (
"encoding/json"
"errors"
"fmt"
"github.com/dchest/captcha"
"gopkg.in/gomail.v2"
"net/http"
"netoik.io/netoik-website/pkg/api"
"netoik.io/netoik-website/pkg/conf"
"os"
"path/filepath"
)
type request struct {
Name string `json:"name"`
Email string `json:"email"`
Phone string `json:"phone"`
Company string `json:"company"`
Message string `json:"message"`
CaptchaId string `json:"captchaId"`
CaptchaDigits string `json:"captchaDigits"`
}
func HandleSend(w http.ResponseWriter, r *http.Request) {
// Check method
if r.Method != "POST" {
api.Reply(w, 405, api.Answer{})
return
}
// Parse json from request body
var data request
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
api.Reply(w, 400, api.Answer{})
return
}
if data.Name == "" || len(data.Name) > 200 {
api.Reply(w, 400, api.Answer{})
return
}
if data.Email == "" || len(data.Email) > 200 {
api.Reply(w, 400, api.Answer{})
return
}
if len(data.Phone) > 200 {
api.Reply(w, 400, api.Answer{})
return
}
if len(data.Company) > 200 {
api.Reply(w, 400, api.Answer{})
return
}
if data.Message == "" || len(data.Message) > 10000 {
api.Reply(w, 400, api.Answer{})
return
}
if data.CaptchaId == "" {
api.Reply(w, 400, api.Answer{})
return
}
if data.CaptchaDigits == "" {
api.Reply(w, 400, api.Answer{})
return
}
// Check captcha digits
if !captcha.VerifyString(data.CaptchaId, data.CaptchaDigits) {
api.Reply(w, 418, api.Answer{})
return
}
// Captcha has been verified, so remove image
path := filepath.Join(conf.Conf.CaptchaDirectory, data.CaptchaId+".png")
if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
fmt.Fprintf(os.Stderr, "ERROR: cannot remove captcha image %q: %s", path, err.Error())
}
// Build email
msg := gomail.NewMessage()
msg.SetHeader("From", conf.Conf.SMTPUsername)
msg.SetHeader("To", conf.Conf.SMTPReceiver)
msg.SetHeader("Subject", "Message from www.netoik.io")
msg.SetBody("text/plain", fmt.Sprintf(
"You have received a message from frontend.\nname: %s\nemail: %s\nphone: %s\ncompany: %s\n%s",
data.Name, data.Email, data.Phone, data.Company, data.Message))
// Configure SMTP dialer and send email
dialer := gomail.NewDialer(conf.Conf.SMTPHost, conf.Conf.SMTPPort, conf.Conf.SMTPUsername, conf.Conf.SMTPPassword)
if err := dialer.DialAndSend(msg); err != nil {
fmt.Fprintf(os.Stderr, "cannot send email: %s\n", err.Error())
api.Reply(w, 400, api.Answer{})
return
}
api.Reply(w, 200, api.Answer{Success: true})
}

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 311 KiB

After

Width:  |  Height:  |  Size: 311 KiB