Compare commits
10 Commits
29e4666bd9
...
9bb80f1f6c
Author | SHA1 | Date |
---|---|---|
Alexander Andreev | 9bb80f1f6c | |
Alexander Andreev | ee93284cdc | |
Alexander Andreev | 073384cc34 | |
Alexander Andreev | 6ab62fc43b | |
Alexander Andreev | ea9877f6c4 | |
Alexander Andreev | 49a64f553e | |
Alexander Andreev | 275e3c500b | |
Alexander Andreev | e4225db960 | |
Alexander Andreev | b91c24998a | |
Alexander Andreev | d7599bdf46 |
4
Makefile
4
Makefile
|
@ -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}
|
||||
|
|
46
README.md
46
README.md
|
@ -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
|
||||
...
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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
2
go.mod
|
@ -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
6
go.sum
|
@ -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=
|
||||
|
|
|
@ -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.
|
|
@ -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 {
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue