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
*.test
*.prof
.*.swp

View File

@ -3,42 +3,43 @@ $(document).ready( function () {
$(".confirm-export").confirm({
"text": "Mark current batch exported?",
"title": "Export confrimation",
confirm: function() {
window.location = "/export-commit";
"title": "Export confirmation",
"confirm": function(form) {
form.submit();
},
});
if( $('.addedLink').length > 0) {
setTimeout(function (){
window.close();
}, 500);
}
$("form.form-add").submit( function(e) {
if ( $(".add-category").val() == -1) {
e.preventDefault();
$(".add-category-col").addClass("has-error");
}
});
$(".category-change-parent").change(function (e) {
e.target.parentElement.submit();
});
$('.cat-delete').confirm({
text: "Are you sure you want to delete this category?",
title: "Confirmation required",
confirmButton: "Yes",
cancelButton: "No",
});
$(".confirm-delete").confirm({
$(".confirm-news-delete").confirm({
"text": "Delete news item?",
"title": "Delete confirmation",
confirmButton: "Yes",
cancelButton: "No",
"confirm": function(form) {
form.submit();
}
});
});

11
main.go
View File

@ -102,14 +102,15 @@ func main() {
loadConfig(*envFlag)
dbConnect()
initTemplates()
//CSRF := csrf.Protect([]byte(csrfSecret()));
r := init_route_handlers()
errHandle := csrf.ErrorHandler( CSRFErrorHandler{} )
sec := csrf.Secure(false)
muxRouter := init_route_handlers()
//errHandler := csrf.ErrorHandler( CSRFErrorHandler{} )
// Terrible. TODO: Get SSL for prod, and then wrap in if(dev) { {
csrfSecurityOption := csrf.Secure(false)
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 {
fmt.Println("Fatal Error: ", err)
}

View File

@ -17,11 +17,10 @@ type News struct {
Category_id int
Date time.Time
Notes string
Expoerted bool
}
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:
@ -95,7 +94,7 @@ func Get(db *sql.DB, id int) (*News, error) {
func LoadPage(db *sql.DB, offset, amount int) ([]*News, int, error) {
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 {
fmt.Println("DB errpr reading LoadPage news: ", err)
return nil, 0, err
@ -174,8 +173,7 @@ func scanNews(rows *sql.Rows) (*News, error) {
news := &News{}
var url, title, notes sql.NullString
var category_id sql.NullInt64
var exported sql.NullBool
err := rows.Scan(&news.id, &url, &title, &category_id, &news.Date, &notes, &exported)
err := rows.Scan(&news.id, &url, &title, &category_id, &news.Date, &notes)
if err != nil {
fmt.Println("Error reading news from DB: " + err.Error())
return nil, err
@ -184,7 +182,6 @@ func scanNews(rows *sql.Rows) (*News, error) {
news.Url = nullStringToString(&url)
news.Title = nullStringToString(&title)
news.Notes = nullStringToString(&notes)
news.Expoerted = nullBoolToBool(&exported)
if category_id.Valid {
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.Save(r, w)
if popup == "1" {
http.Redirect(w, r, "/added", http.StatusFound)
http.Redirect(w, r, "/news/added", http.StatusFound)
} else {
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.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) {
@ -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) {
id, idErr := strconv.Atoi(r.FormValue("id"))
id, idErr := strconv.Atoi(mux.Vars(r)["id"])
if idErr != nil {
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})
}
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 {
// Mux + CSRF
r := mux.NewRouter()
// Basic Handle - static files - no CSRF wrapper
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("/fonts/").Handler(http.StripPrefix("/fonts", http.FileServer(http.Dir("fonts/"))))
r.HandleFunc("/favicon.ico", ServeFileHandler)
rGet := r.Methods("GET").Subrouter()
rPost := r.Methods("POST").Subrouter()
rGet.HandleFunc("/login", LoginFormHandler)
rPost.HandleFunc("/login", LoginPostHandler)
r.HandleFunc("/logout", userHandler(LogoutHandler))
rPost.HandleFunc("/logout", userHandler(LogoutHandler))
r.HandleFunc("/add", getPostHandler(userHandler(addFormHandler), userHandler(addPostHandler)))
r.HandleFunc("/", userHandler(newsFormHandler))
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)))
rGet.HandleFunc("/news/add", userHandler(addFormHandler))
rPost.HandleFunc("/news/add", userHandler(addPostHandler))
rGet.HandleFunc("/", userHandler(newsFormHandler))
rGet.HandleFunc("/news", userHandler(newsFormHandler))
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))
r.HandleFunc("/categories", getPostHandler(userHandler(categoriesFormHandler), userHandler(categoriesPostHandler)))
r.HandleFunc("/categories/change-parent", userHandler(categoryChangeParentHandler))
r.HandleFunc("/categories/add", userHandler(categoryAddHandler))
r.HandleFunc("/categories/delete", userHandler(categoryDeleteHandler))
//http.Handle("/", r)
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
}

View File

@ -32,15 +32,15 @@
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="/add">add</a></li>
<li><a href="/export">export</a></li>
<li><a href="/news/add">add</a></li>
<li><a href="/news/export">export</a></li>
<li><a href="/categories">categories</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{{if .user}}
<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}}
<li><a href="/login">Log in</a></li>
{{end}}

View File

@ -6,7 +6,10 @@
<div class="row">
<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 class="col-xs-6">

View File

@ -44,12 +44,12 @@
<div class="row">
<div class="col-xs-2">&nbsp;</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>
{{end}}
<!-- JS for the launcher of the add bookmarklet -->
{{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}}

View File

@ -1,9 +1,8 @@
{{define "body"}}
<h2 class="form-add-heading">{{if eq .mode "add"}}Add Link{{else}}Edit News{{end}}</h2>
{{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="id" value="{{.id}}" />
<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">Title:</div><div class="col-xs-10"><input type="text" class="form-control" name="title" placeholder="Title" value="{{.title}}"/></div>