From ce1df27e3ab3c53f8a77bc14595808920a10020a Mon Sep 17 00:00:00 2001 From: "Alexander \"Arav\" Andreev" Date: Sat, 12 Aug 2023 22:23:52 +0400 Subject: [PATCH] Removed code for a standalone executable. go min ver was downgraded to 1.12. --- Makefile | 33 ------------- README.md | 100 +------------------------------------- build/archlinux/PKGBUILD | 23 --------- cmd/justcaptchad/main.go | 69 -------------------------- go.mod | 7 +-- go.sum | 2 - init/systemd.service | 39 --------------- internal/http/handlers.go | 78 ----------------------------- internal/http/server.go | 76 ----------------------------- 9 files changed, 4 insertions(+), 423 deletions(-) delete mode 100755 Makefile delete mode 100644 build/archlinux/PKGBUILD delete mode 100644 cmd/justcaptchad/main.go delete mode 100755 init/systemd.service delete mode 100644 internal/http/handlers.go delete mode 100644 internal/http/server.go diff --git a/Makefile b/Makefile deleted file mode 100755 index 36625d3..0000000 --- a/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -PACKAGE_NAME=justcaptcha -TARGET=${PACKAGE_NAME}d - -SYSCTL=${shell which systemctl} -SYSDDIR_=${shell pkg-config systemd --variable=systemdsystemunitdir} -SYSDDIR=${SYSDDIR_:/%=%} -DESTDIR=/ - -VERSION=2.0.2 - -LDFLAGS=-ldflags "-s -w -X main.version=${VERSION}" -tags osusergo,netgo - -all: ${TARGET} - -.PHONY: ${TARGET} - -${TARGET}: - go build -o bin/$@ ${LDFLAGS} cmd/$@/main.go - -run: - bin/${TARGET} -expiry 1m -listen 127.0.0.1:19134 - -install: - install -Dm 0755 bin/${TARGET} ${DESTDIR}usr/bin/${TARGET} - install -Dm 0644 LICENSE ${DESTDIR}usr/share/licenses/${PACKAGE_NAME}/LICENSE - - install -Dm 0644 init/systemd.service ${DESTDIR}${SYSDDIR}/${PACKAGE_NAME}.service - -uninstall: - rm ${DESTDIR}usr/bin/${TARGET} - rm ${DESTDIR}usr/share/licenses/${PACKAGE_NAME}/LICENSE - - rm ${DESTDIR}${SYSDDIR}/${PACKAGE_NAME}.service diff --git a/README.md b/README.md index 5e607c5..812563d 100644 --- a/README.md +++ b/README.md @@ -1,105 +1,9 @@ justcaptcha =========== -A simple CAPTCHA service implementation. +A simple CAPTCHA implementation. -## Service usage - - justcaptchad -expiry 10m -listen /var/run/justcaptcha/sock - -`-expiry` takes time for CAPTCHA to be valid for in format X{s,m,h}. - -`-listen` is `ip:port` or `/path/to/unix.sock` to listen on. - -## Service HTTP API - -### Errors - -All error codes are returned with an error message in `text/plain`. - -### Create a new CAPTCHA - - POST / - -It will return an ID of a new CAPTCHA in `text/plain`. - -#### HTTP codes -- `201` if created (always being created) - -### Get an image for a CAPTCHA - - GET /:captcha_id/image?style= - -Responds with an image in JPEG format (`image/jpeg`). - -An optional URL parameter `style=` set a name of a CAPTCHA style if it is -supported by CAPTCHA implementation. E.g. `style=dark`. - -#### HTTP codes -- `200` if exists -- `404` if doesn't exist -- `500` if for some reason an image wasn't created - -### Submit an answer - - POST /:captcha_id - -Accepts `application/x-www-form-urlencoded` content type. - -It takes one parameter `answer=123456`. - -Responds with an empty body and one of the HTTP codes. - -#### HTTP codes -- `202` if solved -- `403` if not solved - -### Check if captcha is solved - - GET /:captcha_id?remove - -Responds with an empty body and one of the HTTP codes. - -If an optional `remove` parameter without a value supplied CAPTCHA will be -removed without checking and a HTTP code `204` will be sent. Otherwise, a `403` -HTTP code will be sent if it is not solved. - -A `remove` parameter was added because browsers will print an error in a console -if HTTP code is not within `2xx`, so to keep a console clean you can provide -this parameter. - -This can be useful to remove an unused CAPTCHA from a DB without waiting for it -to be expired. E.g. when a visitor requests for a new CAPTCHA or leaving a page. - -#### HTTP codes -- `204` if solved -- `403` if not solved - -### Example of interaction - -First a client makes a POST request with empty body to create a new CAPTCHA and obtains an ID for it. - - POST / - -As a result we get an ID `n60f2K9JiD5c4qX9MYe90A54nT0nnJrtgfhAjfaWtBg`. - -Then a client requests an image for a new CAPTCHA. E.g. with a dark style. - - GET /n60f2K9JiD5c4qX9MYe90A54nT0nnJrtgfhAjfaWtBg/image?style=dark - -Then a client submits an answer for a CAPTCHA. - - POST 'answer=198807' /n60f2K9JiD5c4qX9MYe90A54nT0nnJrtgfhAjfaWtBg - -And if answer was correct a client gets a HTTP code 202. Or 403 otherwise. - -Then a server checks if CAPTCHA was solved with following request. - - GET /n60f2K9JiD5c4qX9MYe90A54nT0nnJrtgfhAjfaWtBg - -## Library usage - -A simple example using built-in dwelling CAPTCHA implementation. +An example using built-in "dwelling" implementation. Create a new CAPTCHA: diff --git a/build/archlinux/PKGBUILD b/build/archlinux/PKGBUILD deleted file mode 100644 index 1c064fd..0000000 --- a/build/archlinux/PKGBUILD +++ /dev/null @@ -1,23 +0,0 @@ -# Maintainer: Alexander "Arav" Andreev -pkgname=justcaptcha -pkgver=2.0.2 -pkgrel=2 -pkgdesc="Just a standalone simple CAPTCHA service" -arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64') -url="https://git.arav.su/Arav/justcaptcha" -license=('MIT') -makedepends=('go') -provides=('justcaptcha') -conflicts=('justcaptcha') -source=("https://git.arav.su/Arav/justcaptcha/archive/v${pkgver}.tar.gz") -md5sums=('SKIP') - -build() { - cd "$srcdir/$pkgname" - make VERSION=$pkgver DESTDIR="$pkgdir/" -} - -package() { - cd "$srcdir/$pkgname" - make VERSION=$pkgver DESTDIR="$pkgdir/" install -} \ No newline at end of file diff --git a/cmd/justcaptchad/main.go b/cmd/justcaptchad/main.go deleted file mode 100644 index a946234..0000000 --- a/cmd/justcaptchad/main.go +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "net/netip" - "os" - "os/signal" - "strings" - "syscall" - "time" - - "git.arav.su/Arav/justcaptcha/internal/http" -) - -var version string - -var listenAddress *string = flag.String("listen", "/var/run/justcaptcha/sock", "listen address (ip:port|unix_path)") -var captchaExpiry *time.Duration = flag.Duration("expiry", 10*time.Minute, "CAPTCHA expiry in format XX{s,m,h}, e.g. 5m, 300s") -var showVersion *bool = flag.Bool("v", false, "show version") - -func main() { - flag.Parse() - log.SetFlags(0) - - if *showVersion { - fmt.Println("justcaptchad ver.", version, "\nCopyright (c) 2022 Alexander \"Arav\" Andreev ") - return - } - - hand := http.NewCaptchaHandlers(*captchaExpiry) - srv := http.NewHttpServer() - - srv.POST("/", hand.New) - srv.POST("/:captcha", hand.Solve) - srv.GET("/:captcha", hand.IsSolved) - srv.GET("/:captcha/image", hand.Image) - - var network string - if !strings.ContainsRune(*listenAddress, ':') { - network = "unix" - defer os.Remove(*listenAddress) - } else { - ap, err := netip.ParseAddrPort(*listenAddress) - if err != nil { - log.Fatalln(err) - } - - if ap.Addr().Is4() { - network = "tcp4" - } else if ap.Addr().Is6() { - network = "tcp6" - } - } - - if err := srv.Start(network, *listenAddress); err != nil { - log.Fatalln("failed to start a server:", err) - } - - doneSignal := make(chan os.Signal, 1) - signal.Notify(doneSignal, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - <-doneSignal - - if err := srv.Stop(); err != nil { - log.Fatalln("failed to properly shutdown a server:", err) - } -} diff --git a/go.mod b/go.mod index 47b7f0d..758cf90 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,8 @@ module git.arav.su/Arav/justcaptcha -go 1.19 +go 1.12 -require ( - github.com/fogleman/gg v1.3.0 - github.com/julienschmidt/httprouter v1.3.0 -) +require github.com/fogleman/gg v1.3.0 require ( github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect diff --git a/go.sum b/go.sum index 41786bd..43e52d0 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= diff --git a/init/systemd.service b/init/systemd.service deleted file mode 100755 index fe3c6e1..0000000 --- a/init/systemd.service +++ /dev/null @@ -1,39 +0,0 @@ -[Unit] -Description=A simple CAPTCHA service for your website -After=network.target - -[Service] -Type=simple -Restart=on-failure -DynamicUser=yes -ExecStart=/usr/bin/justcaptchad -expiry 10m -listen /var/run/justcaptcha/sock - -ReadOnlyPaths=/ -# Set here path to directory where uploads are stored. -NoExecPaths=/ -ExecPaths=/usr/bin/justcaptchad - -RuntimeDirectory=justcaptcha - -AmbientCapabilities= -CapabilityBoundingSet= - -LockPersonality=true -MemoryDenyWriteExecute=true -NoNewPrivileges=true -PrivateDevices=true -ProtectClock=true -ProtectControlGroups=true -ProtectHome=true -ProtectKernelLogs=true -ProtectKernelModules=true -ProtectKernelTunables=true -ProtectSystem=strict -RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX -RestrictNamespaces=true -RestrictRealtime=true -RestrictSUIDSGID=true -SystemCallArchitectures=native - -[Install] -WantedBy=multi-user.target diff --git a/internal/http/handlers.go b/internal/http/handlers.go deleted file mode 100644 index 9d18855..0000000 --- a/internal/http/handlers.go +++ /dev/null @@ -1,78 +0,0 @@ -package http - -import ( - "fmt" - "image/jpeg" - "net/http" - "time" - - "git.arav.su/Arav/justcaptcha/pkg/captcha" - "git.arav.su/Arav/justcaptcha/pkg/captcha/inmemdb" - "git.arav.su/Arav/justcaptcha/pkg/dwcaptcha" -) - -const errMsgWrongAnswer = "An answer provided was wrong" -const errMsgImageNotFound = "cannot get an image for a non-existing CAPTCHA" - -type CaptchaHandlers struct { - expiry time.Duration -} - -func NewCaptchaHandlers(expiry time.Duration) *CaptchaHandlers { - inmemdb.SetExpiry(expiry) - return &CaptchaHandlers{expiry: expiry} -} - -func (h *CaptchaHandlers) New(w http.ResponseWriter, r *http.Request) { - dc := dwcaptcha.NewDwellingCaptcha(h.expiry) - _, id := inmemdb.New(r.RemoteAddr, dc) - w.WriteHeader(http.StatusCreated) - fmt.Fprint(w, id) -} - -func (h *CaptchaHandlers) Image(w http.ResponseWriter, r *http.Request) { - captchaID := captcha.ID(getURLParam(r, "captcha")) - captchaStyle := r.URL.Query().Get("style") - - captchaImage := inmemdb.Image(captchaID, captchaStyle) - if captchaImage == nil { - http.Error(w, errMsgImageNotFound, http.StatusNotFound) - return - } - - w.Header().Add("Content-Disposition", "inline; filename=\""+string(captchaID)+"\"") - - jpeg.Encode(w, *captchaImage, &jpeg.Options{Quality: 20}) -} - -func (h *CaptchaHandlers) Solve(w http.ResponseWriter, r *http.Request) { - captchaID := captcha.ID(getURLParam(r, "captcha")) - - r.ParseForm() - answer := captcha.Answer(r.FormValue("answer")) - - if ok := inmemdb.Solve(captchaID, answer); !ok { - http.Error(w, errMsgWrongAnswer, http.StatusForbidden) - return - } - - w.WriteHeader(http.StatusAccepted) -} - -func (h *CaptchaHandlers) IsSolved(w http.ResponseWriter, r *http.Request) { - captchaID := captcha.ID(getURLParam(r, "captcha")) - isJustRemove := r.URL.Query().Has("remove") - - if isJustRemove { - inmemdb.Remove(captchaID) - w.WriteHeader(http.StatusNoContent) - return - } - - if solved := inmemdb.IsSolved(captchaID); !solved { - http.Error(w, errMsgWrongAnswer, http.StatusForbidden) - return - } - - w.WriteHeader(http.StatusNoContent) -} diff --git a/internal/http/server.go b/internal/http/server.go deleted file mode 100644 index 8f6426e..0000000 --- a/internal/http/server.go +++ /dev/null @@ -1,76 +0,0 @@ -package http - -import ( - "context" - "log" - "net" - "net/http" - "os" - "time" - - "github.com/julienschmidt/httprouter" -) - -type HttpServer struct { - server *http.Server - router *httprouter.Router -} - -func NewHttpServer() *HttpServer { - r := httprouter.New() - return &HttpServer{ - server: &http.Server{ - ReadTimeout: 3 * time.Second, - WriteTimeout: 3 * time.Second, - Handler: r, - }, - router: r, - } -} - -func (s *HttpServer) GET(path string, handler http.HandlerFunc) { - s.router.Handler(http.MethodGet, path, handler) -} - -func (s *HttpServer) POST(path string, handler http.HandlerFunc) { - s.router.Handler(http.MethodPost, path, handler) -} - -func (s *HttpServer) SetNotFoundHandler(handler http.HandlerFunc) { - s.router.NotFound = handler -} - -// getURLParam wrapper around underlying router for getting URL parameters. -func getURLParam(r *http.Request, param string) string { - return httprouter.ParamsFromContext(r.Context()).ByName(param) -} - -func (s *HttpServer) Start(network, address string) error { - listener, err := net.Listen(network, address) - if err != nil { - return err - } - - if listener.Addr().Network() == "unix" { - os.Chmod(address, 0777) - } - - go func() { - if err = s.server.Serve(listener); err != nil && err != http.ErrServerClosed { - log.Fatalln(err) - } - }() - - return nil -} - -func (s *HttpServer) Stop() error { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - if err := s.server.Shutdown(ctx); err != nil { - return err - } - - return nil -}