work on porting routers to more gorilla/mux style and making all actions POST and csrf trackable

This commit is contained in:
Dan Ballard 2015-11-11 09:28:07 -08:00
parent e07b20b531
commit df5dca3a52
9 changed files with 60 additions and 53 deletions

2
.gitignore vendored
View File

@ -29,3 +29,5 @@ _testmain.go
*.exe *.exe
*.test *.test
*.prof *.prof
.*.swp

View File

@ -3,14 +3,12 @@ $(document).ready( function () {
$(".confirm-export").confirm({ $(".confirm-export").confirm({
"text": "Mark current batch exported?", "text": "Mark current batch exported?",
"title": "Export confrimation", "title": "Export confirmation",
confirm: function() { "confirm": function(form) {
window.location = "/export-commit"; form.submit();
}, },
}); });
if( $('.addedLink').length > 0) { if( $('.addedLink').length > 0) {
setTimeout(function (){ setTimeout(function (){
window.close(); window.close();
@ -35,10 +33,13 @@ $(document).ready( function () {
cancelButton: "No", cancelButton: "No",
}); });
$(".confirm-delete").confirm({ $(".confirm-news-delete").confirm({
"text": "Delete news item?", "text": "Delete news item?",
"title": "Delete confirmation", "title": "Delete confirmation",
confirmButton: "Yes", confirmButton: "Yes",
cancelButton: "No", cancelButton: "No",
"confirm": function(form) {
form.submit();
}
}); });
}); });

11
main.go
View File

@ -102,14 +102,15 @@ func main() {
loadConfig(*envFlag) loadConfig(*envFlag)
dbConnect() dbConnect()
initTemplates() initTemplates()
//CSRF := csrf.Protect([]byte(csrfSecret())); muxRouter := init_route_handlers()
r := init_route_handlers() //errHandler := csrf.ErrorHandler( CSRFErrorHandler{} )
errHandle := csrf.ErrorHandler( CSRFErrorHandler{} )
sec := csrf.Secure(false) // Terrible. TODO: Get SSL for prod, and then wrap in if(dev) { {
csrfSecurityOption := csrf.Secure(false)
fmt.Println("Listening on", config.Port, "...") fmt.Println("Listening on", config.Port, "...")
err := http.ListenAndServe(":"+config.Port, csrf.Protect([]byte("12345678901234567890123456789012"), errHandle, sec)(r)) //csrfSecret()))(r)) err := http.ListenAndServe(":"+config.Port, csrf.Protect([]byte(csrfSecret()), /*errHandler,*/ csrfSecurityOption)(muxRouter))
if err != nil { if err != nil {
fmt.Println("Fatal Error: ", err) fmt.Println("Fatal Error: ", err)
} }

View File

@ -17,11 +17,10 @@ type News struct {
Category_id int Category_id int
Date time.Time Date time.Time
Notes string Notes string
Expoerted bool
} }
const ( const (
SQL_NEWS_FIELDS = "id, url, title, category_id, timestamp, notes, exported" SQL_NEWS_FIELDS = "id, url, title, category_id, timestamp, notes"
) )
/* Storage Node containing: /* Storage Node containing:
@ -95,7 +94,7 @@ func Get(db *sql.DB, id int) (*News, error) {
func LoadPage(db *sql.DB, offset, amount int) ([]*News, int, error) { func LoadPage(db *sql.DB, offset, amount int) ([]*News, int, error) {
categories.LoadCategories(db) // required by addContainer categories.LoadCategories(db) // required by addContainer
rows, err := db.Query("SELECT "+SQL_NEWS_FIELDS+" FROM news WHERE exported is null order by timestamp DESC") rows, err := db.Query("SELECT "+SQL_NEWS_FIELDS+" FROM news order by timestamp DESC")
if err != nil { if err != nil {
fmt.Println("DB errpr reading LoadPage news: ", err) fmt.Println("DB errpr reading LoadPage news: ", err)
return nil, 0, err return nil, 0, err
@ -174,8 +173,7 @@ func scanNews(rows *sql.Rows) (*News, error) {
news := &News{} news := &News{}
var url, title, notes sql.NullString var url, title, notes sql.NullString
var category_id sql.NullInt64 var category_id sql.NullInt64
var exported sql.NullBool err := rows.Scan(&news.id, &url, &title, &category_id, &news.Date, &notes)
err := rows.Scan(&news.id, &url, &title, &category_id, &news.Date, &notes, &exported)
if err != nil { if err != nil {
fmt.Println("Error reading news from DB: " + err.Error()) fmt.Println("Error reading news from DB: " + err.Error())
return nil, err return nil, err
@ -184,7 +182,6 @@ func scanNews(rows *sql.Rows) (*News, error) {
news.Url = nullStringToString(&url) news.Url = nullStringToString(&url)
news.Title = nullStringToString(&title) news.Title = nullStringToString(&title)
news.Notes = nullStringToString(&notes) news.Notes = nullStringToString(&notes)
news.Expoerted = nullBoolToBool(&exported)
if category_id.Valid { if category_id.Valid {
news.Category_id = int(category_id.Int64) news.Category_id = int(category_id.Int64)

View File

@ -176,7 +176,7 @@ func addPostHandler(w http.ResponseWriter, r *http.Request, user *user.User, ses
session.AddFlash("Added news \""+news.Title+"\"", flash_info) session.AddFlash("Added news \""+news.Title+"\"", flash_info)
session.Save(r, w) session.Save(r, w)
if popup == "1" { if popup == "1" {
http.Redirect(w, r, "/added", http.StatusFound) http.Redirect(w, r, "/news/added", http.StatusFound)
} else { } else {
http.Redirect(w, r, "/", http.StatusFound) http.Redirect(w, r, "/", http.StatusFound)
} }
@ -284,7 +284,7 @@ func exportHandler(w http.ResponseWriter, r *http.Request, user *user.User, sess
session.AddFlash("Last batch of news marked exported", flash_info) session.AddFlash("Last batch of news marked exported", flash_info)
} }
session.Save(r, w) session.Save(r, w)
http.Redirect(w, r, "/", http.StatusFound) http.Redirect(w, r, "/export", http.StatusFound)
} }
func addedHandler(w http.ResponseWriter, r *http.Request, user *user.User, session *sessions.Session) { func addedHandler(w http.ResponseWriter, r *http.Request, user *user.User, session *sessions.Session) {
@ -294,7 +294,7 @@ func addedHandler(w http.ResponseWriter, r *http.Request, user *user.User, sessi
} }
func deleteHandler(w http.ResponseWriter, r *http.Request, user *user.User, session *sessions.Session) { func deleteHandler(w http.ResponseWriter, r *http.Request, user *user.User, session *sessions.Session) {
id, idErr := strconv.Atoi(r.FormValue("id")) id, idErr := strconv.Atoi(mux.Vars(r)["id"])
if idErr != nil { if idErr != nil {
session.AddFlash("Invalid news to delete", flash_err) session.AddFlash("Invalid news to delete", flash_err)
@ -412,7 +412,6 @@ func newsFormHandler(w http.ResponseWriter, r *http.Request, user *user.User, se
} }
ShowTemplate("news", w, r, map[string]interface{}{"user": user, "flashes": flashes, "news": news, "count": count, "categories": categories.CategoriesFlat}) ShowTemplate("news", w, r, map[string]interface{}{"user": user, "flashes": flashes, "news": news, "count": count, "categories": categories.CategoriesFlat})
} }
func ServeFileHandler(res http.ResponseWriter, req *http.Request) { func ServeFileHandler(res http.ResponseWriter, req *http.Request) {
@ -421,41 +420,46 @@ func ServeFileHandler(res http.ResponseWriter, req *http.Request) {
} }
func init_route_handlers() *mux.Router { func init_route_handlers() *mux.Router {
// Mux + CSRF
r := mux.NewRouter() r := mux.NewRouter()
// Basic Handle - static files - no CSRF wrapper // Basic Handle - static files - no CSRF wrapper
r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(http.Dir("js/")))) r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(http.Dir("js/"))))
r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(http.Dir("css/")))) r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(http.Dir("css/"))))
r.PathPrefix("/fonts/").Handler(http.StripPrefix("/fonts", http.FileServer(http.Dir("fonts/")))) r.PathPrefix("/fonts/").Handler(http.StripPrefix("/fonts", http.FileServer(http.Dir("fonts/"))))
r.HandleFunc("/favicon.ico", ServeFileHandler) r.HandleFunc("/favicon.ico", ServeFileHandler)
rGet := r.Methods("GET").Subrouter() rGet := r.Methods("GET").Subrouter()
rPost := r.Methods("POST").Subrouter() rPost := r.Methods("POST").Subrouter()
rGet.HandleFunc("/login", LoginFormHandler) rGet.HandleFunc("/login", LoginFormHandler)
rPost.HandleFunc("/login", LoginPostHandler) rPost.HandleFunc("/login", LoginPostHandler)
r.HandleFunc("/logout", userHandler(LogoutHandler)) rPost.HandleFunc("/logout", userHandler(LogoutHandler))
r.HandleFunc("/add", getPostHandler(userHandler(addFormHandler), userHandler(addPostHandler))) rGet.HandleFunc("/news/add", userHandler(addFormHandler))
r.HandleFunc("/", userHandler(newsFormHandler)) rPost.HandleFunc("/news/add", userHandler(addPostHandler))
r.HandleFunc("/news", userHandler(newsFormHandler))
r.HandleFunc("/export", userHandler(templateFormHandler))
r.HandleFunc("/export-commit", userHandler(exportHandler))
r.HandleFunc("/added", userHandler(addedHandler))
r.HandleFunc("/delete", userHandler(deleteHandler))
r.HandleFunc("/edit", getPostHandler(userHandler(editFormHandler), userHandler(editPostHandler)))
r.HandleFunc("/categories", getPostHandler(userHandler(categoriesFormHandler), userHandler(categoriesPostHandler))) rGet.HandleFunc("/", userHandler(newsFormHandler))
r.HandleFunc("/categories/change-parent", userHandler(categoryChangeParentHandler)) rGet.HandleFunc("/news", userHandler(newsFormHandler))
r.HandleFunc("/categories/add", userHandler(categoryAddHandler))
r.HandleFunc("/categories/delete", userHandler(categoryDeleteHandler))
//http.Handle("/", r) rGet.HandleFunc("/news/export", userHandler(templateFormHandler))
rPost.HandleFunc("/news/export", userHandler(exportHandler))
rGet.HandleFunc("/news/added", userHandler(addedHandler))
// TODO to post and {id}
rPost.HandleFunc("/news/{id:[0-9]+}/delete", userHandler(deleteHandler))
// TODO post {id} ?
rGet.HandleFunc("/news/{id:[0-9]+}/edit", userHandler(editFormHandler))
rPost.HandleFunc("/news/{id:[0-9]+}/edit", userHandler(editPostHandler))
rGet.HandleFunc("/categories", userHandler(categoriesFormHandler))
rPost.HandleFunc("/caegories", userHandler(categoriesPostHandler))
// TODO post, add {id}
rPost.HandleFunc("/categories/change-parent", userHandler(categoryChangeParentHandler))
rPost.HandleFunc("/categories/add", userHandler(categoryAddHandler))
rPost.HandleFunc("/categories/delete", userHandler(categoryDeleteHandler))
return r return r
} }

View File

@ -32,15 +32,15 @@
</div> </div>
<div class="collapse navbar-collapse"> <div class="collapse navbar-collapse">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li><a href="/add">add</a></li> <li><a href="/news/add">add</a></li>
<li><a href="/export">export</a></li> <li><a href="/news/export">export</a></li>
<li><a href="/categories">categories</a></li> <li><a href="/categories">categories</a></li>
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
{{if .user}} {{if .user}}
<li>{{.user.Username}}</li> <li>{{.user.Username}}</li>
<li><a href="/logout">Logout</a></li> <li><form method="POST" action="/logout">{{ .csrfField }}<input type="submit" value="Logout" class="btn btn-sm btn-primary btn-block" /></form></li>
{{else}} {{else}}
<li><a href="/login">Log in</a></li> <li><a href="/login">Log in</a></li>
{{end}} {{end}}

View File

@ -6,7 +6,10 @@
<div class="row"> <div class="row">
<div class="col-xs-2"> <div class="col-xs-2">
<button class="confirm-export btn btn-lg btn-primary btn-block" type="submit">Export</button> <form method="POST" action="/news/export" class="confirm-export">
{{ .csrfField }}
<input class="btn btn-lg btn-primary btn-block" type="submit" value="Export" />
</form>
</div> </div>
<div class="col-xs-6"> <div class="col-xs-6">

View File

@ -44,12 +44,12 @@
<div class="row"> <div class="row">
<div class="col-xs-2">&nbsp;</div> <div class="col-xs-2">&nbsp;</div>
<div class="col-xs-8 post-preview">{{truncate .post.Notes 500}}</div> <div class="col-xs-8 post-preview">{{truncate .post.Notes 500}}</div>
<div class="col-xs-2 post-delete"><a class="confirm-delete" href="/delete?id={{.post.Id}}">Delete</a></div> <div class="col-xs-2 post-delete"><form method="POST" action="/news/{{.post.Id}}/delete" class="confirm-news-delete">{{ .csrfField }}<input type="submit" class="btn btn-primary btn-block" value="Delete" /></form></div>
</div> </div>
</div> </div>
{{end}} {{end}}
<!-- JS for the launcher of the add bookmarklet --> <!-- JS for the launcher of the add bookmarklet -->
{{define "launch-add"}} {{define "launch-add"}}
javascript:var d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='{{.url}}add',l=d.location,e=encodeURIComponent,u=f+'?popup=1&url='+e(l.href)+'&title='+e(d.title);a=function(){if(!w.open(u,'t','toolbar=0,resizable=1,scrollbars=1,status=1,width=720,height=410'))l.href=u;};if (/Firefox/.test(navigator.userAgent)) setTimeout(a, 0); else a();void(0) javascript:var d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='{{.url}}news/add',l=d.location,e=encodeURIComponent,u=f+'?popup=1&url='+e(l.href)+'&title='+e(d.title);a=function(){if(!w.open(u,'t','toolbar=0,resizable=1,scrollbars=1,status=1,width=720,height=410'))l.href=u;};if (/Firefox/.test(navigator.userAgent)) setTimeout(a, 0); else a();void(0)
{{end}} {{end}}

View File

@ -1,9 +1,8 @@
{{define "body"}} {{define "body"}}
<h2 class="form-add-heading">{{if eq .mode "add"}}Add Link{{else}}Edit News{{end}}</h2> <h2 class="form-add-heading">{{if eq .mode "add"}}Add Link{{else}}Edit News{{end}}</h2>
{{template "flashes" .}} {{template "flashes" .}}
<form class="form-add" action="{{if eq "add" .mode}}/add{{else}}/edit{{end}}" method="post" role="form" class="container col-form"> <form class="form-add" action="{{if eq "add" .mode}}/news/add{{else}}/news/{{.id}}/edit{{end}}" method="post" role="form" class="container col-form">
<input type="hidden" name="popup" value="{{.popup}}" /> <input type="hidden" name="popup" value="{{.popup}}" />
<input type="hidden" name="id" value="{{.id}}" />
<div class="row"> <div class="row">
<div class="col-xs-2">Link:</div><div class="col-xs-10"><input type="text" class="form-control" name="link" placeholder="Link" value="{{.link}}"/></div> <div class="col-xs-2">Link:</div><div class="col-xs-10"><input type="text" class="form-control" name="link" placeholder="Link" value="{{.link}}"/></div>
<div class="col-xs-2">Title:</div><div class="col-xs-10"><input type="text" class="form-control" name="title" placeholder="Title" value="{{.title}}"/></div> <div class="col-xs-2">Title:</div><div class="col-xs-10"><input type="text" class="form-control" name="title" placeholder="Title" value="{{.title}}"/></div>