Compare commits

...

10 Commits

11 changed files with 64 additions and 38 deletions

View File

@ -6,7 +6,7 @@ SYSDDIR_=${shell pkg-config systemd --variable=systemdsystemunitdir}
SYSDDIR=${SYSDDIR_:/%=%}
DESTDIR=/
LDFLAGS=-ldflags "-s -w -X main.version=2.0.0" -tags osusergo,netgo
LDFLAGS=-ldflags "-s -w -X main.version=2.0.1" -tags osusergo,netgo
all: ${TARGET}
@ -22,7 +22,7 @@ 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/${PACKAGE_NAME}.service ${DESTDIR}${SYSDDIR}/${PACKAGE_NAME}.service
install -Dm 0644 init/systemd.service ${DESTDIR}${SYSDDIR}/${PACKAGE_NAME}.service
uninstall:
rm ${DESTDIR}usr/bin/${TARGET}

View File

@ -1,17 +1,17 @@
justcaptcha ver. 2.0.0
justcaptcha ver. 2.0.1
======================
A simple CAPTCHA service implementation.
## Usage
## Service usage
justcaptchad -expiry 5m -listen /var/run/justcaptcha/c.sock
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.
## API
## Service HTTP API
### Errors
@ -33,7 +33,7 @@ It will return an ID of a new CAPTCHA in `text/plain`.
Responds with an image in JPEG format (`image/jpeg`).
An optional URL parameter `style=` set a name of a CAPTCHA style if it is
implemented by used CAPTCHA implementation.
supported by CAPTCHA implementation.
#### HTTP codes
- `200` if exists
@ -95,4 +95,38 @@ 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
GET /n60f2K9JiD5c4qX9MYe90A54nT0nnJrtgfhAjfaWtBg
## Library usage
A simple example using built-in dwelling CAPTCHA implementation.
Create a new CAPTCHA:
c := dwcaptcha.NewDwellingCaptcha(expiry)
_, id := inmemdb.New(someAdditionalDataUsedInIDGenerationUsuallyIPAddr, c)
Get an image for a CAPTCHA:
i := inmemdb.Image(captchaID, captchaStyle)
if i == nil {
... // error handling
}
jpeg.Encode(w, *i, &jpeg.Options{Quality: 20})
Solve a CAPTCHA:
if ok := inmemdb.Solve(captchaID, answer); !ok {
... // not solved
}
// solved
...
Check is CAPTCHA was solved:
if ok := inmemdb.IsSolved(captchaID); !ok {
... // not solved
}
// solved
...

View File

@ -1,6 +1,6 @@
# Maintainer: Alexander "Arav" Andreev <me@arav.top>
pkgname=justcaptcha
pkgver=2.0.0
pkgver=2.0.1
pkgrel=1
pkgdesc="Just a CAPTCHA service"
arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64')
@ -15,7 +15,7 @@ replaces=()
backup=()
options=()
install=
source=('https://git.arav.top/Arav/justcaptcha/archive/2.0.0.tar.gz')
source=('https://git.arav.top/Arav/justcaptcha/archive/2.0.1.tar.gz')
noextract=()
md5sums=('SKIP')

View File

@ -3,8 +3,7 @@ package main
import (
"flag"
"fmt"
"justcaptcha/internal/handlers"
"justcaptcha/pkg/server"
"justcaptcha/internal/http"
"log"
"net/netip"
"os"
@ -16,8 +15,8 @@ import (
var version string
var listenAddress *string = flag.String("listen", "/var/run/justcaptcha/c.sock", "listen address (ip:port|unix_path)")
var captchaExpiry *time.Duration = flag.Duration("expiry", 5*time.Minute, "CAPTCHA expiry in format XX{s,m,h}, e.g. 5m, 300s")
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() {
@ -29,8 +28,8 @@ func main() {
return
}
hand := handlers.New(*captchaExpiry)
srv := server.NewHttpServer()
hand := http.NewCaptchaHandlers(*captchaExpiry)
srv := http.NewHttpServer()
srv.POST("/", hand.New)
srv.POST("/:captcha", hand.Solve)

2
go.mod
View File

@ -9,5 +9,5 @@ require (
require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
golang.org/x/image v0.1.0 // indirect
golang.org/x/image v0.3.0 // indirect
)

6
go.sum
View File

@ -7,8 +7,8 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
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=
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk=
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg=
golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@ -25,7 +25,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

View File

@ -6,7 +6,7 @@ After=network.target
Type=simple
Restart=on-failure
DynamicUser=yes
ExecStart=/usr/bin/justcaptchad -expiry 5m -listen /var/run/justcaptcha/j.sock
ExecStart=/usr/bin/justcaptchad -expiry 10m -listen /var/run/justcaptcha/sock
ReadOnlyPaths=/
# Set here path to directory where uploads are stored.

View File

@ -1,12 +1,11 @@
package handlers
package http
import (
"fmt"
"image/jpeg"
"justcaptcha/internal/dwcaptcha"
"justcaptcha/pkg/captcha"
"justcaptcha/pkg/captcha/inmemdb"
"justcaptcha/pkg/server"
"justcaptcha/pkg/dwcaptcha"
"net/http"
"time"
)
@ -18,7 +17,7 @@ type CaptchaHandlers struct {
expiry time.Duration
}
func New(expiry time.Duration) *CaptchaHandlers {
func NewCaptchaHandlers(expiry time.Duration) *CaptchaHandlers {
inmemdb.SetExpiry(expiry)
return &CaptchaHandlers{expiry: expiry}
}
@ -31,7 +30,7 @@ func (h *CaptchaHandlers) New(w http.ResponseWriter, r *http.Request) {
}
func (h *CaptchaHandlers) Image(w http.ResponseWriter, r *http.Request) {
captchaID := captcha.ID(server.GetURLParam(r, "captcha"))
captchaID := captcha.ID(getURLParam(r, "captcha"))
captchaStyle := r.URL.Query().Get("style")
captchaImage := inmemdb.Image(captchaID, captchaStyle)
@ -46,7 +45,7 @@ func (h *CaptchaHandlers) Image(w http.ResponseWriter, r *http.Request) {
}
func (h *CaptchaHandlers) Solve(w http.ResponseWriter, r *http.Request) {
captchaID := captcha.ID(server.GetURLParam(r, "captcha"))
captchaID := captcha.ID(getURLParam(r, "captcha"))
r.ParseForm()
answer := captcha.Answer(r.FormValue("answer"))
@ -60,7 +59,7 @@ func (h *CaptchaHandlers) Solve(w http.ResponseWriter, r *http.Request) {
}
func (h *CaptchaHandlers) IsSolved(w http.ResponseWriter, r *http.Request) {
captchaID := captcha.ID(server.GetURLParam(r, "captcha"))
captchaID := captcha.ID(getURLParam(r, "captcha"))
isJustRemove := r.URL.Query().Has("remove")
if isJustRemove {

View File

@ -1,4 +1,4 @@
package server
package http
import (
"context"
@ -40,8 +40,8 @@ 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 {
// getURLParam wrapper around underlying router for getting URL parameters.
func getURLParam(r *http.Request, param string) string {
return httprouter.ParamsFromContext(r.Context()).ByName(param)
}

View File

@ -45,7 +45,7 @@ type BaseCaptcha struct {
func NewBaseCaptcha(expiry time.Duration) *BaseCaptcha {
return &BaseCaptcha{
expiry: ExpiryDate(expiry),
expiry: time.Now().Add(expiry),
}
}
@ -72,9 +72,3 @@ func (c *BaseCaptcha) IsSolved() bool {
func (c *BaseCaptcha) Expiry() time.Time {
return c.expiry
}
// ExpiryDate returns a date when CAPTCHA expires. It adds a passed
// expiry duration to a current time.
func ExpiryDate(expiry time.Duration) time.Time {
return time.Now().Add(expiry)
}