Internal IP-specific captchas
Cette révision appartient à :
Parent
2ab9224dfe
révision
2f55e478e0
11 fichiers modifiés avec 159 ajouts et 24 suppressions
|
@ -12,6 +12,7 @@ install:
|
|||
- go get github.com/gorilla/securecookie
|
||||
- go get golang.org/x/crypto/bcrypt
|
||||
- go get github.com/nicksnyder/go-i18n/i18n
|
||||
- go get github.com/dchest/captcha
|
||||
- go build
|
||||
deploy:
|
||||
provider: releases
|
||||
|
|
|
@ -103,6 +103,12 @@ a {
|
|||
padding: 4px;
|
||||
}
|
||||
|
||||
.captcha-container {
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
grid-template-columns: 240px;
|
||||
}
|
||||
|
||||
tr.torrent-info td.date {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/ewhal/nyaa/service/captcha"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var Router *mux.Router
|
||||
|
@ -30,4 +31,5 @@ func init() {
|
|||
Router.HandleFunc("/view/{id}", ViewHandler).Name("view_torrent")
|
||||
Router.HandleFunc("/upload", UploadHandler).Name("upload")
|
||||
Router.HandleFunc("/user/register", UserRegisterFormHandler).Name("user_register")
|
||||
Router.PathPrefix("/captcha").Methods("GET").HandlerFunc(captcha.ServeFiles)
|
||||
}
|
||||
|
|
|
@ -2,18 +2,16 @@ package router
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/ewhal/nyaa/util"
|
||||
"github.com/ewhal/nyaa/util/metainfo"
|
||||
"github.com/zeebo/bencode"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// UploadForm serializing HTTP form for torrent upload
|
||||
type UploadForm struct {
|
||||
Name string
|
||||
Magnet string
|
||||
Category string
|
||||
Description string
|
||||
Name, Magnet, Category, Description, CaptchaID string
|
||||
}
|
||||
|
||||
// TODO: these should be in another package (?)
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"github.com/ewhal/nyaa/service/captcha"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
|
@ -15,18 +16,27 @@ func init() {
|
|||
|
||||
func UploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
var uploadForm UploadForm
|
||||
if r.Method == "POST" {
|
||||
switch r.Method {
|
||||
case "POST":
|
||||
var form UploadForm
|
||||
defer r.Body.Close()
|
||||
err = uploadForm.ExtractInfo(r)
|
||||
err = form.ExtractInfo(r)
|
||||
if err == nil {
|
||||
//validate name + hash
|
||||
//add to db and redirect depending on result
|
||||
// validate name + hash
|
||||
// authenticate captcha
|
||||
// add to db and redirect depending on result
|
||||
}
|
||||
case "GET":
|
||||
htv := UploadTemplateVariables{
|
||||
Upload: UploadForm{
|
||||
CaptchaID: captcha.GetID(r.RemoteAddr),
|
||||
},
|
||||
Search: NewSearchForm(),
|
||||
URL: r.URL,
|
||||
Route: mux.CurrentRoute(r),
|
||||
}
|
||||
} else if r.Method == "GET" {
|
||||
htv := UploadTemplateVariables{uploadForm, NewSearchForm(), Navigation{}, r.URL, mux.CurrentRoute(r)}
|
||||
err = uploadTemplate.ExecuteTemplate(w, "index.html", htv)
|
||||
} else {
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"github.com/ewhal/nyaa/service/captcha"
|
||||
"github.com/ewhal/nyaa/service/user/form"
|
||||
"github.com/ewhal/nyaa/util/modelHelper"
|
||||
"github.com/gorilla/mux"
|
||||
|
@ -21,10 +22,16 @@ func init() {
|
|||
// Getting View User Registration
|
||||
|
||||
func UserRegisterFormHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
b := form.RegistrationForm{}
|
||||
b := form.RegistrationForm{
|
||||
CaptchaID: captcha.GetID(r.RemoteAddr),
|
||||
}
|
||||
modelHelper.BindValueForm(b, r)
|
||||
htv := UserRegisterTemplateVariables{b, NewSearchForm(), Navigation{}, r.URL, mux.CurrentRoute(r)}
|
||||
htv := UserRegisterTemplateVariables{
|
||||
RegistrationForm: b,
|
||||
Search: NewSearchForm(),
|
||||
URL: r.URL,
|
||||
Route: mux.CurrentRoute(r),
|
||||
}
|
||||
err := viewTemplate.ExecuteTemplate(w, "index.html", htv)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
|
97
service/captcha/capthca.go
Fichier normal
97
service/captcha/capthca.go
Fichier normal
|
@ -0,0 +1,97 @@
|
|||
package captcha
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dchest/captcha"
|
||||
)
|
||||
|
||||
const lifetime = time.Minute * 20
|
||||
|
||||
var (
|
||||
server = captcha.Server(captcha.StdWidth, captcha.StdHeight)
|
||||
captchas = captchaMap{
|
||||
m: make(map[string]store, 64),
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
captcha.SetCustomStore(captcha.NewMemoryStore(1<<10, lifetime))
|
||||
|
||||
go func() {
|
||||
t := time.Tick(time.Minute)
|
||||
for {
|
||||
<-t
|
||||
captchas.cleanUp()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Captcha is to be embedded into any form struct requiring a captcha
|
||||
type Captcha struct {
|
||||
CaptchaID, Solution string
|
||||
}
|
||||
|
||||
// Captchas are IP-specific and need eventual cleanup
|
||||
type captchaMap struct {
|
||||
sync.Mutex
|
||||
m map[string]store
|
||||
}
|
||||
|
||||
type store struct {
|
||||
id string
|
||||
created time.Time
|
||||
}
|
||||
|
||||
// Returns a captcha id by IP. If a captcha for this IP already exists, it is
|
||||
// reloaded and returned. Otherwise, a new captcha is created.
|
||||
func (n *captchaMap) get(ip string) string {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
old, ok := n.m[ip]
|
||||
|
||||
// No existing captcha, it expired or this IP already used the captcha
|
||||
if !ok || !captcha.Reload(old.id) {
|
||||
id := captcha.New()
|
||||
n.m[ip] = store{
|
||||
id: id,
|
||||
created: time.Now(),
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
old.created = time.Now()
|
||||
n.m[ip] = old
|
||||
return old.id
|
||||
}
|
||||
|
||||
// Remove expired ip -> captchaID mappings
|
||||
func (n *captchaMap) cleanUp() {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
till := time.Now().Add(-lifetime)
|
||||
for ip, c := range n.m {
|
||||
if c.created.Before(till) {
|
||||
delete(n.m, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetID returns a new or previous captcha id by IP
|
||||
func GetID(ip string) string {
|
||||
return captchas.get(ip)
|
||||
}
|
||||
|
||||
// ServeFiles serves captcha images and audio
|
||||
func ServeFiles(w http.ResponseWriter, r *http.Request) {
|
||||
server.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Authenticate check's if a captcha solution is valid
|
||||
func Authenticate(req Captcha) bool {
|
||||
return captcha.VerifyString(req.CaptchaID, req.Solution)
|
||||
}
|
|
@ -2,6 +2,7 @@ package form
|
|||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/ewhal/nyaa/util/log"
|
||||
)
|
||||
|
||||
|
@ -19,9 +20,10 @@ func EmailValidation(email string) bool {
|
|||
|
||||
// RegistrationForm is used when creating a user.
|
||||
type RegistrationForm struct {
|
||||
Username string `form:"registrationUsername" binding:"required"`
|
||||
Email string `form:"registrationEmail" binding:"required"`
|
||||
Password string `form:"registrationPassword" binding:"required"`
|
||||
Username string `form:"registrationUsername" binding:"required"`
|
||||
Email string `form:"registrationEmail" binding:"required"`
|
||||
Password string `form:"registrationPassword" binding:"required"`
|
||||
CaptchaID string `form:"captchaID" binding:"required"`
|
||||
}
|
||||
|
||||
// RegistrationForm is used when creating a user authentication.
|
||||
|
|
7
templates/_capthca.html
Fichier normal
7
templates/_capthca.html
Fichier normal
|
@ -0,0 +1,7 @@
|
|||
{{define "captcha"}}
|
||||
<div class="form-group captcha-container">
|
||||
<input type="text" name="capthcaID" value="{{.CaptchaID}}" hidden>
|
||||
<img src="/captcha/{{.CaptchaID}}.png">
|
||||
<input type="number" name="solution" class="form-control" placeholder="Captcha" required>
|
||||
</div>
|
||||
{{end}}
|
|
@ -17,7 +17,7 @@
|
|||
<div class="form-group">
|
||||
<label for="Magnet">Magnet Link</label>
|
||||
<input type="text" name="magnet" class="form-control"
|
||||
style="width:60rem" placeholder="Magnet Link" value="{{.Magnet}}" required>
|
||||
style="width:60rem" placeholder="Magnet Link" value="{{.Magnet}}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="c">Category</label>
|
||||
|
@ -53,6 +53,9 @@
|
|||
<label for="desc">Torrent Description</label>
|
||||
<textarea name="desc" class="form-control" rows="10">{{.Description}}</textarea>
|
||||
</div>
|
||||
|
||||
{{block "captcha" .}}{{end}}
|
||||
|
||||
<button type="submit" class="btn btn-success">Upload</button>
|
||||
<br />
|
||||
<br />
|
||||
|
|
|
@ -36,7 +36,9 @@
|
|||
By clicking <strong class="label label-primary">Register</strong>, you agree to the <a href="#" data-toggle="modal" data-target="#t_and_c_m">Terms and Conditions</a> set out by this site, including our Cookie Use.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{block "captcha" .}}{{end}}
|
||||
|
||||
<hr class="colorgraph">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-6"><input type="submit" value="Register" class="btn btn-primary btn-block btn-lg" tabindex="7"></div>
|
||||
|
@ -63,4 +65,4 @@
|
|||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
Référencer dans un nouveau ticket