révision
83e496a224
11 fichiers modifiés avec 183 ajouts et 34 suppressions
|
@ -13,6 +13,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 get github.com/microcosm-cc/bluemonday
|
||||
- go get github.com/tcnksm/ghr
|
||||
- go get github.com/axw/gocov/gocov
|
||||
|
|
|
@ -106,6 +106,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
|
||||
|
@ -32,6 +33,7 @@ func init() {
|
|||
Router.HandleFunc("/user/register", UserRegisterFormHandler).Name("user_register").Methods("GET")
|
||||
Router.HandleFunc("/user/login", UserLoginFormHandler).Name("user_login").Methods("GET")
|
||||
Router.HandleFunc("/user/register", UserRegisterPostHandler).Name("user_register").Methods("POST")
|
||||
Router.PathPrefix("/captcha").Methods("GET").HandlerFunc(captcha.ServeFiles)
|
||||
|
||||
Router.NotFoundHandler = http.HandlerFunc(NotFoundHandler)
|
||||
}
|
||||
|
|
|
@ -3,14 +3,16 @@ package router
|
|||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"github.com/ewhal/nyaa/util"
|
||||
"github.com/ewhal/nyaa/util/metainfo"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/zeebo/bencode"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ewhal/nyaa/service/captcha"
|
||||
"github.com/ewhal/nyaa/util"
|
||||
"github.com/ewhal/nyaa/util/metainfo"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/zeebo/bencode"
|
||||
)
|
||||
|
||||
// UploadForm serializing HTTP form for torrent upload
|
||||
|
@ -22,6 +24,7 @@ type UploadForm struct {
|
|||
CategoryId int
|
||||
SubCategoryId int
|
||||
Description string
|
||||
captcha.Captcha
|
||||
}
|
||||
|
||||
// TODO: these should be in another package (?)
|
||||
|
@ -64,6 +67,7 @@ func (f *UploadForm) ExtractInfo(r *http.Request) error {
|
|||
f.Category = r.FormValue(UploadFormCategory)
|
||||
f.Description = r.FormValue(UploadFormDescription)
|
||||
f.Magnet = r.FormValue(UploadFormMagnet)
|
||||
f.Captcha = captcha.Extract(r)
|
||||
|
||||
// trim whitespaces
|
||||
f.Name = util.TrimWhitespaces(f.Name)
|
||||
|
@ -76,6 +80,7 @@ func (f *UploadForm) ExtractInfo(r *http.Request) error {
|
|||
|
||||
if len(f.Description) == 0 {
|
||||
return ErrInvalidTorrentDescription
|
||||
|
||||
}
|
||||
|
||||
catsSplit := strings.Split(f.Category, "_")
|
||||
|
|
|
@ -2,13 +2,15 @@ package router
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ewhal/nyaa/db"
|
||||
"github.com/ewhal/nyaa/model"
|
||||
"github.com/gorilla/mux"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ewhal/nyaa/db"
|
||||
"github.com/ewhal/nyaa/model"
|
||||
"github.com/ewhal/nyaa/service/captcha"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var uploadTemplate = template.Must(template.New("upload").Funcs(FuncMap).ParseFiles("templates/index.html", "templates/upload.html"))
|
||||
|
@ -24,6 +26,12 @@ func UploadHandler(w http.ResponseWriter, r *http.Request) {
|
|||
defer r.Body.Close()
|
||||
err = uploadForm.ExtractInfo(r)
|
||||
if err == nil {
|
||||
if !captcha.Authenticate(uploadForm.Captcha) {
|
||||
// TODO: Prettier passing of mistyoed captcha errors
|
||||
http.Error(w, captcha.ErrInvalidCaptcha.Error(), 403)
|
||||
return
|
||||
}
|
||||
|
||||
//validate name + hash
|
||||
//add to db and redirect depending on result
|
||||
torrent := model.Torrents{
|
||||
|
@ -45,6 +53,7 @@ func UploadHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
fmt.Printf("%+v\n", uploadForm)
|
||||
} else if r.Method == "GET" {
|
||||
uploadForm.CaptchaID = captcha.GetID(r.RemoteAddr)
|
||||
htv := UploadTemplateVariables{uploadForm, NewSearchForm(), Navigation{}, r.URL, mux.CurrentRoute(r)}
|
||||
err = uploadTemplate.ExecuteTemplate(w, "index.html", htv)
|
||||
} else {
|
||||
|
|
|
@ -4,16 +4,17 @@ import (
|
|||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"github.com/ewhal/nyaa/service/user/form"
|
||||
"github.com/ewhal/nyaa/service/user"
|
||||
"github.com/ewhal/nyaa/util/modelHelper"
|
||||
"github.com/ewhal/nyaa/service/user/form"
|
||||
"github.com/ewhal/nyaa/util/languages"
|
||||
"github.com/ewhal/nyaa/util/modelHelper"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var viewRegisterTemplate = template.Must(template.New("userRegister").Funcs(FuncMap).ParseFiles("templates/index.html", "templates/user/register.html"))
|
||||
var viewLoginTemplate = template.Must(template.New("userLogin").Funcs(FuncMap).ParseFiles("templates/index.html", "templates/user/login.html"))
|
||||
var viewRegisterSuccessTemplate = template.Must(template.New("userRegisterSuccess").Funcs(FuncMap).ParseFiles("templates/index.html", "templates/user/signup_success.html"))
|
||||
|
||||
//var viewTemplate = template.Must(template.New("view").Funcs(FuncMap).ParseFiles("templates/index.html", "templates/view.html"))
|
||||
//var viewTemplate = template.Must(template.New("view").Funcs(FuncMap).ParseFiles("templates/index.html", "templates/view.html"))
|
||||
|
||||
|
@ -24,7 +25,6 @@ func init() {
|
|||
}
|
||||
|
||||
// Getting View User Registration
|
||||
|
||||
func UserRegisterFormHandler(w http.ResponseWriter, r *http.Request) {
|
||||
b := form.RegistrationForm{}
|
||||
modelHelper.BindValueForm(&b, r)
|
||||
|
@ -61,15 +61,15 @@ func UserProfileFormHandler(w http.ResponseWriter, r *http.Request) {
|
|||
// Post Registration controller
|
||||
func UserRegisterPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Check same Password
|
||||
if ((r.PostFormValue("password") == r.PostFormValue("password_confirm"))&&(r.PostFormValue("password") != "")) {
|
||||
if ((form.EmailValidation(r.PostFormValue("email")))&&(form.ValidateUsername(r.PostFormValue("username")))) {
|
||||
_ , err := userService.CreateUser(w, r)
|
||||
if (err == nil) {
|
||||
if (r.PostFormValue("password") == r.PostFormValue("password_confirm")) && (r.PostFormValue("password") != "") {
|
||||
if (form.EmailValidation(r.PostFormValue("email"))) && (form.ValidateUsername(r.PostFormValue("username"))) {
|
||||
_, err := userService.CreateUser(w, r)
|
||||
if err == nil {
|
||||
b := form.RegistrationForm{}
|
||||
htv := UserRegisterTemplateVariables{b, NewSearchForm(), Navigation{}, r.URL, mux.CurrentRoute(r)}
|
||||
err = viewRegisterSuccessTemplate.ExecuteTemplate(w, "index.html", htv)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
UserRegisterFormHandler(w, r)
|
||||
|
|
108
service/captcha/capthca.go
Fichier normal
108
service/captcha/capthca.go
Fichier normal
|
@ -0,0 +1,108 @@
|
|||
package captcha
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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),
|
||||
}
|
||||
|
||||
ErrInvalidCaptcha = errors.New("invalid captcha")
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Extract a Captcha struct from an HTML form
|
||||
func Extract(r *http.Request) Captcha {
|
||||
return Captcha{
|
||||
CaptchaID: r.FormValue("captchaID"),
|
||||
Solution: r.FormValue("solution"),
|
||||
}
|
||||
}
|
||||
|
||||
// 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"
|
||||
)
|
||||
|
||||
|
@ -18,30 +19,35 @@ func EmailValidation(email string) bool {
|
|||
return false
|
||||
}
|
||||
func ValidateUsername(username string) bool {
|
||||
exp, err := regexp.Compile(USERNAME_REGEX)
|
||||
exp, err := regexp.Compile(USERNAME_REGEX)
|
||||
|
||||
if (username == "") {
|
||||
return false;
|
||||
|
||||
}
|
||||
if ((len(username) < 3) || (len(username) > 15)) {
|
||||
return false;
|
||||
|
||||
}
|
||||
if regexpCompiled := log.CheckError(err); regexpCompiled {
|
||||
if username == "" {
|
||||
return false
|
||||
|
||||
}
|
||||
if (len(username) < 3) || (len(username) > 15) {
|
||||
return false
|
||||
|
||||
}
|
||||
if regexpCompiled := log.CheckError(err); regexpCompiled {
|
||||
if exp.MatchString(username) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return true
|
||||
}
|
||||
|
||||
// RegistrationForm is used when creating a user.
|
||||
type RegistrationForm struct {
|
||||
Username string `form:"username" binding:"required"`
|
||||
Email string `form:"email" binding:"required"`
|
||||
Password string `form:"password" binding:"required"`
|
||||
Username string `form:"registrationUsername" binding:"required"`
|
||||
Email string `form:"registrationEmail" binding:"required"`
|
||||
Password string `form:"registrationPassword" binding:"required"`
|
||||
Username string `form:"username" binding:"required"`
|
||||
Email string `form:"email" binding:"required"`
|
||||
Password string `form:"password" 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}}">
|
||||
style="width:60rem" placeholder="Magnet Link" value="{{.Magnet}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="c">Category</label>
|
||||
|
@ -47,6 +47,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 />
|
||||
|
|
|
@ -37,7 +37,9 @@
|
|||
{{T "terms_conditions_confirm" }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{block "captcha" .}}{{end}}
|
||||
|
||||
<hr class="colorgraph">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-6"><input type="submit" value="{{T "register" }}" class="btn btn-primary btn-block btn-lg" tabindex="7"></div>
|
||||
|
@ -66,4 +68,4 @@
|
|||
</div><!-- /.modal -->
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "js_footer"}}<script type="text/javascript" charset="utf-8" src="{{.URL.Parse "/js/registerPage.js"}}"></script>{{end}}
|
||||
{{define "js_footer"}}<script type="text/javascript" charset="utf-8" src="{{.URL.Parse "/js/registerPage.js"}}"></script>{{end}}
|
||||
|
|
Référencer dans un nouveau ticket