@@ -1,3 +1,4 @@ | |||
bin/ | |||
pkg/bundled/ | |||
public_html/.settings.json | |||
*.sqlite |
@@ -8,6 +8,7 @@ import ( | |||
"os" | |||
"git.kirsle.net/apps/gophertype/pkg" | |||
"git.kirsle.net/apps/gophertype/pkg/console" | |||
_ "git.kirsle.net/apps/gophertype/pkg/controllers" | |||
_ "github.com/jinzhu/gorm/dialects/mysql" | |||
_ "github.com/jinzhu/gorm/dialects/postgres" | |||
@@ -24,6 +25,7 @@ var ( | |||
var ( | |||
optDebug bool | |||
optBind string | |||
optRoot string | |||
// Database option flags. | |||
optSQLite string | |||
@@ -38,6 +40,7 @@ var ( | |||
func init() { | |||
flag.BoolVar(&optDebug, "debug", false, "Debug level logging") | |||
flag.StringVar(&optBind, "bind", ":8000", "Bind address for HTTP server") | |||
flag.StringVar(&optRoot, "root", "./public_html", "User root for custom web pages") | |||
// Database driver. Choose one. | |||
flag.StringVar(&optSQLite, "sqlite3", "", "Use SQLite database, default 'database.sqlite'") | |||
@@ -52,6 +55,8 @@ func init() { | |||
func main() { | |||
flag.Parse() | |||
console.SetDebug(optDebug) | |||
// Validate the choice of database. | |||
if optSQLite != "" { | |||
dbDriver = "sqlite3" | |||
@@ -74,7 +79,7 @@ func main() { | |||
fmt.Println("Hello world") | |||
app := gophertype.NewSite() | |||
app := gophertype.NewSite(optRoot) | |||
app.UseDB(dbDriver, dbPath) | |||
app.SetupRouter() | |||
app.ListenAndServe(optBind) | |||
@@ -8,6 +8,7 @@ require ( | |||
github.com/gorilla/sessions v1.2.0 | |||
github.com/jinzhu/gorm v1.9.11 | |||
github.com/kirsle/blog v0.0.0-20191022175051-d78814b9c99b | |||
github.com/kirsle/golog v0.0.0-20180411020913-51290b4f9292 | |||
github.com/satori/go.uuid v1.2.0 | |||
github.com/urfave/negroni v1.0.0 | |||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 | |||
@@ -1,6 +1,7 @@ | |||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | |||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | |||
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= | |||
git.kirsle.net/go/log v0.0.0-20180505005739-e046e3e7b0b1 h1:F4cwOb7q/ZtZ4ADIhCgXlHpIahyIvtomrjCSZb4550I= | |||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | |||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= | |||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= | |||
@@ -1,10 +1,11 @@ | |||
package gophertype | |||
import ( | |||
"log" | |||
"net/http" | |||
"git.kirsle.net/apps/gophertype/pkg/console" | |||
"git.kirsle.net/apps/gophertype/pkg/models" | |||
"git.kirsle.net/apps/gophertype/pkg/settings" | |||
"github.com/gorilla/mux" | |||
"github.com/jinzhu/gorm" | |||
"github.com/urfave/negroni" | |||
@@ -17,7 +18,12 @@ type Site struct { | |||
} | |||
// NewSite initializes the Site. | |||
func NewSite() *Site { | |||
func NewSite(pubroot string) *Site { | |||
// Initialize the settings.json inside the user root. | |||
if err := settings.SetFilename(pubroot); err != nil { | |||
panic(err) | |||
} | |||
site := &Site{} | |||
n := negroni.New() | |||
@@ -35,13 +41,13 @@ func (s *Site) UseDB(driver string, path string) error { | |||
return err | |||
} | |||
log.Printf("Using database driver '%s'", driver) | |||
console.Info("Using database driver '%s'", driver) | |||
models.UseDB(db) | |||
return nil | |||
} | |||
// ListenAndServe starts the HTTP service. | |||
func (s *Site) ListenAndServe(addr string) error { | |||
log.Printf("Listening on %s", addr) | |||
console.Info("Listening on %s", addr) | |||
return http.ListenAndServe(addr, s.n) | |||
} |
@@ -3,9 +3,9 @@ package authentication | |||
import ( | |||
"context" | |||
"errors" | |||
"log" | |||
"net/http" | |||
"git.kirsle.net/apps/gophertype/pkg/console" | |||
"git.kirsle.net/apps/gophertype/pkg/models" | |||
"git.kirsle.net/apps/gophertype/pkg/session" | |||
) | |||
@@ -28,7 +28,7 @@ func Login(w http.ResponseWriter, r *http.Request, user models.User) { | |||
sess.Values["logged-in"] = true | |||
sess.Values["user-id"] = int(user.ID) | |||
if err := sess.Save(r, w); err != nil { | |||
log.Printf("ERROR: Login() Session error: " + err.Error()) | |||
console.Error("Login() Session error: " + err.Error()) | |||
} | |||
} | |||
@@ -0,0 +1,43 @@ | |||
// Package console implements a colorful logger for Gophertype. | |||
package console | |||
import "github.com/kirsle/golog" | |||
var log *golog.Logger | |||
func init() { | |||
log = golog.GetLogger("gophertype") | |||
log.Configure(&golog.Config{ | |||
Colors: golog.ExtendedColor, | |||
Theme: golog.DarkTheme, | |||
}) | |||
} | |||
// SetDebug turns debug logging on or off. | |||
func SetDebug(on bool) { | |||
if on { | |||
log.Config.Level = golog.DebugLevel | |||
} else { | |||
log.Config.Level = golog.InfoLevel | |||
} | |||
} | |||
// Info level log. | |||
func Info(msg string, v ...interface{}) { | |||
log.Info(msg, v...) | |||
} | |||
// Debug level log. | |||
func Debug(msg string, v ...interface{}) { | |||
log.Debug(msg, v...) | |||
} | |||
// Warn level log. | |||
func Warn(msg string, v ...interface{}) { | |||
log.Warn(msg, v...) | |||
} | |||
// Error level log. | |||
func Error(msg string, v ...interface{}) { | |||
log.Error(msg, v...) | |||
} |
@@ -1,7 +1,6 @@ | |||
package controllers | |||
import ( | |||
"log" | |||
"net/http" | |||
"git.kirsle.net/apps/gophertype/pkg/authentication" | |||
@@ -32,7 +31,6 @@ func init() { | |||
val.Require("password") | |||
if val.HasErrors() { | |||
v.ValidationError = val.ErrorMap() | |||
log.Printf("validation: %+v", v.ValidationError) | |||
break | |||
} | |||
@@ -3,7 +3,6 @@ package controllers | |||
import ( | |||
"errors" | |||
"fmt" | |||
"log" | |||
"net/http" | |||
"git.kirsle.net/apps/gophertype/pkg/constants" | |||
@@ -23,81 +22,86 @@ func init() { | |||
Middleware: []mux.MiddlewareFunc{ | |||
middleware.ExampleMiddleware, | |||
}, | |||
Handler: func(w http.ResponseWriter, r *http.Request) { | |||
// See if we already have an admin account. | |||
if _, err := models.FirstAdmin(); err == nil { | |||
responses.Panic(w, http.StatusForbidden, "This site is already initialized.") | |||
return | |||
} | |||
Handler: InitialSetup, | |||
}) | |||
// Template variables. | |||
v := responses.NewTemplateVars(w, r) | |||
v.SetupNeeded = false // supress the banner on this page. | |||
} | |||
// POST handler: create the admin account. | |||
for r.Method == http.MethodPost { | |||
form, err := forms.Parse(r) | |||
if err != nil { | |||
responses.Error(w, r, http.StatusBadRequest, err.Error()) | |||
return | |||
} | |||
// InitialSetup at "/admin/setup" | |||
func InitialSetup(w http.ResponseWriter, r *http.Request) { | |||
// Template variables. | |||
v := responses.NewTemplateVars(w, r) | |||
v.SetupNeeded = false // supress the banner on this page. | |||
v.FormValues = form.Values | |||
// See if we already have an admin account. | |||
if _, err := models.FirstAdmin(); err == nil { | |||
v.Message = "This site is already initialized." | |||
responses.Forbidden(w, r, "This site has already been initialized.") | |||
return | |||
} | |||
// Validate form parameters. | |||
val := form.Validator() | |||
val.Require("email") | |||
val.MatchEmail("email") | |||
val.MinLength("password", 8) | |||
val.Require("password2") | |||
val.Equal("password", "password2") | |||
if val.HasErrors() { | |||
v.Error = fmt.Errorf("validation error") | |||
v.ValidationError = val.ErrorMap() | |||
log.Printf("validation: %+v", v.ValidationError) | |||
break | |||
} | |||
// POST handler: create the admin account. | |||
for r.Method == http.MethodPost { | |||
form, err := forms.Parse(r) | |||
if err != nil { | |||
responses.Error(w, r, http.StatusBadRequest, err.Error()) | |||
return | |||
} | |||
var ( | |||
email = form.Get("email") | |||
displayName = form.Get("name") | |||
password = form.Get("password") | |||
password2 = form.Get("password2") | |||
) | |||
v.FormValues = form.Values | |||
// Username and display name validation happens in CreateUser. | |||
// Validate the passwords match here. | |||
if len(password) < constants.PasswordMinLength { | |||
v.Error = fmt.Errorf("your password is too short (must be %d+ characters)", constants.PasswordMinLength) | |||
} | |||
if password != password2 { | |||
v.Error = errors.New("your passwords don't match") | |||
} else { | |||
admin := models.User{ | |||
Email: email, | |||
Name: displayName, | |||
IsAdmin: true, | |||
} | |||
admin.SetPassword(password) | |||
// Validate form parameters. | |||
val := form.Validator() | |||
val.Require("email") | |||
val.MatchEmail("email") | |||
val.MinLength("password", 8) | |||
val.Require("password2") | |||
val.Equal("password", "password2") | |||
if val.HasErrors() { | |||
v.Error = fmt.Errorf("validation error") | |||
v.ValidationError = val.ErrorMap() | |||
break | |||
} | |||
if err := models.CreateUser(admin); err != nil { | |||
v.Error = err | |||
} else { | |||
// Admin created! Make the default config. | |||
cfg := settings.Load() | |||
cfg.Initialized = true | |||
cfg.Save() | |||
var ( | |||
email = form.Get("email") | |||
displayName = form.Get("name") | |||
password = form.Get("password") | |||
password2 = form.Get("password2") | |||
) | |||
responses.Redirect(w, r, "/login") | |||
return | |||
} | |||
} | |||
// Username and display name validation happens in CreateUser. | |||
// Validate the passwords match here. | |||
if len(password) < constants.PasswordMinLength { | |||
v.Error = fmt.Errorf("your password is too short (must be %d+ characters)", constants.PasswordMinLength) | |||
} | |||
if password != password2 { | |||
v.Error = errors.New("your passwords don't match") | |||
} else { | |||
admin := models.User{ | |||
Email: email, | |||
Name: displayName, | |||
IsAdmin: true, | |||
} | |||
admin.SetPassword(password) | |||
break | |||
if err := models.CreateUser(admin); err != nil { | |||
v.Error = err | |||
} else { | |||
// Admin created! Make the default config. | |||
cfg := settings.Load() | |||
cfg.Initialized = true | |||
if err := cfg.Save(); err != nil { | |||
v.Error = err | |||
} else { | |||
responses.Redirect(w, r, "/login") | |||
return | |||
} | |||
} | |||
} | |||
responses.RenderTemplate(w, r, "_builtin/initial_setup.gohtml", v) | |||
}, | |||
}) | |||
break | |||
} | |||
responses.RenderTemplate(w, r, "_builtin/initial_setup.gohtml", v) | |||
} |
@@ -1,10 +1,10 @@ | |||
package controllers | |||
import ( | |||
"log" | |||
"net/http" | |||
"strings" | |||
"git.kirsle.net/apps/gophertype/pkg/console" | |||
"git.kirsle.net/apps/gophertype/pkg/responses" | |||
) | |||
@@ -13,7 +13,7 @@ import ( | |||
// templates from the builtin web root or the user root. | |||
func CatchAllHandler(w http.ResponseWriter, r *http.Request) { | |||
path := r.URL.Path | |||
log.Printf("Wildcard path: %s", path) | |||
console.Debug("Wildcard path: %s", path) | |||
// Resolve the target path. | |||
filepath, err := responses.ResolveFile(path) | |||
@@ -2,7 +2,6 @@ package glue | |||
import ( | |||
"fmt" | |||
"log" | |||
"net/http" | |||
"sort" | |||
"sync" | |||
@@ -33,7 +32,6 @@ func Register(e Endpoint) { | |||
} | |||
registry[e.Path] = e | |||
registryLock.Unlock() | |||
log.Printf("Register: %s", e.Path) | |||
} | |||
// GetControllers returns all the routes and handler functions. | |||
@@ -1,14 +1,15 @@ | |||
package middleware | |||
import ( | |||
"log" | |||
"net/http" | |||
"git.kirsle.net/apps/gophertype/pkg/console" | |||
) | |||
// ExampleMiddleware is a test middleware. | |||
func ExampleMiddleware(next http.Handler) http.Handler { | |||
middleware := func(w http.ResponseWriter, r *http.Request) { | |||
log.Printf("ExampleMiddleware called on route %s", r.URL.Path) | |||
console.Warn("ExampleMiddleware called on route %s", r.URL.Path) | |||
next.ServeHTTP(w, r) | |||
} | |||
@@ -1,72 +0,0 @@ | |||
package models | |||
import ( | |||
"crypto/rand" | |||
"encoding/base64" | |||
"github.com/jinzhu/gorm" | |||
) | |||
// AppSetting singleton holds the app configuration. | |||
type AppSetting struct { | |||
gorm.Model | |||
// Site information | |||
Title string | |||
Description string | |||
Email string // primary email for notifications | |||
NSFW bool | |||
BaseURL string | |||
// Blog settings | |||
PostsPerPage int | |||
// Mail settings | |||
MailEnabled bool | |||
MailSender string | |||
MailHost string | |||
MailPort int | |||
MailUsername string | |||
MailPassword string | |||
// Security | |||
SecretKey string `json:"-"` | |||
} | |||
// GetSettings gets or creates the App Settings. | |||
func GetSettings() AppSetting { | |||
var s AppSetting | |||
r := DB.First(&s) | |||
if r.Error != nil { | |||
s = AppSetting{ | |||
Title: "Untitled Site", | |||
Description: "Just another web blog.", | |||
SecretKey: MakeSecretKey(), | |||
} | |||
} | |||
if s.SecretKey == "" { | |||
s.SecretKey = MakeSecretKey() | |||
} | |||
return s | |||
} | |||
// Save the settings to DB. | |||
func (s AppSetting) Save() error { | |||
if DB.NewRecord(s) { | |||
r := DB.Create(&s) | |||
return r.Error | |||
} | |||
r := DB.Save(&s) | |||
return r.Error | |||
} | |||
// MakeSecretKey generates a secret key for signing HTTP cookies. | |||
func MakeSecretKey() string { | |||
keyLength := 32 | |||
b := make([]byte, keyLength) | |||
rand.Read(b) | |||
return base64.URLEncoding.EncodeToString(b) | |||
} |
@@ -8,6 +8,5 @@ var DB *gorm.DB | |||
// UseDB registers a database driver. | |||
func UseDB(db *gorm.DB) { | |||
DB = db | |||
DB.AutoMigrate(&AppSetting{}) | |||
DB.AutoMigrate(&User{}) | |||
} |
@@ -3,9 +3,9 @@ package models | |||
import ( | |||
"errors" | |||
"fmt" | |||
"log" | |||
"strings" | |||
"git.kirsle.net/apps/gophertype/pkg/console" | |||
"git.kirsle.net/apps/gophertype/pkg/constants" | |||
"github.com/jinzhu/gorm" | |||
"golang.org/x/crypto/bcrypt" | |||
@@ -36,7 +36,7 @@ func (u *User) Validate() error { | |||
func AuthenticateUser(email string, password string) (User, error) { | |||
user, err := GetUserByEmail(email) | |||
if err != nil { | |||
log.Printf("ERROR: AuthenticateUser: email %s not found: %s", email, err) | |||
console.Error("AuthenticateUser: email %s not found: %s", email, err) | |||
return User{}, errors.New("incorrect email or password") | |||
} | |||
@@ -69,14 +69,13 @@ func (u *User) SetPassword(password string) error { | |||
} | |||
u.HashedPassword = string(hash) | |||
fmt.Printf("Set hashed password: %s", u.HashedPassword) | |||
return nil | |||
} | |||
// VerifyPassword checks if the password matches the user's hashed password. | |||
func (u *User) VerifyPassword(password string) bool { | |||
if u.HashedPassword == "" { | |||
fmt.Printf("ERROR: VerifyPassword: user has no HashedPassword") | |||
console.Error("ERROR: VerifyPassword: user %s has no HashedPassword", u.Email) | |||
return false | |||
} | |||
@@ -84,7 +83,7 @@ func (u *User) VerifyPassword(password string) bool { | |||
if err == nil { | |||
return true | |||
} | |||
log.Printf("ERROR: VerifyPassword: %s", err) | |||
console.Error("VerifyPassword: %s", err) | |||
return false | |||
} | |||
@@ -1,8 +1,10 @@ | |||
package responses | |||
import ( | |||
"log" | |||
"fmt" | |||
"net/http" | |||
"git.kirsle.net/apps/gophertype/pkg/console" | |||
) | |||
// Panic gives a simple error with no template or anything fancy. | |||
@@ -19,23 +21,38 @@ func Error(w http.ResponseWriter, r *http.Request, code int, message string) { | |||
w.WriteHeader(code) | |||
if err := RenderTemplate(w, r, "_builtin/errors/generic.gohtml", v); err != nil { | |||
log.Printf("responses.Error: failed to render a pretty error template: %s", err) | |||
console.Error("responses.Error: failed to render a pretty error template: %s", err) | |||
w.Write([]byte(message)) | |||
} | |||
} | |||
// NotFound returns an HTML 404 page. | |||
func NotFound(w http.ResponseWriter, r *http.Request) { | |||
w.WriteHeader(http.StatusNotFound) | |||
if err := RenderTemplate(w, r, "_builtin/errors/404.gohtml", nil); err != nil { | |||
log.Printf("responses.NotFound: failed to render a pretty error template: %s", err) | |||
console.Error("responses.NotFound: failed to render a pretty error template: %s", err) | |||
Panic(w, http.StatusNotFound, "Not Found") | |||
} | |||
} | |||
// BadRequest returns a 400 Bad Request page. | |||
func BadRequest(w http.ResponseWriter, r *http.Request, vars interface{}) { | |||
if err := RenderTemplate(w, r, "_builtin/errors/generic.gohtml", vars); err != nil { | |||
log.Printf("responses.NotFound: failed to render a pretty error template: %s", err) | |||
w.WriteHeader(http.StatusBadRequest) | |||
if err := RenderTemplate(w, r, "_builtin/errors/400.gohtml", vars); err != nil { | |||
console.Error("responses.BadRequest: failed to render a pretty error template: %s", err) | |||
Panic(w, http.StatusBadRequest, "Bad Request") | |||
} | |||
} | |||
// Forbidden returns a 403 Forbidden page. | |||
func Forbidden(w http.ResponseWriter, r *http.Request, message string, args ...interface{}) { | |||
w.WriteHeader(http.StatusForbidden) | |||
v := NewTemplateVars(w, r) | |||
v.Message = fmt.Sprintf(message, args...) | |||
if err := RenderTemplate(w, r, "_builtin/errors/403.gohtml", v); err != nil { | |||
console.Error("responses.Forbidden: failed to render a pretty error template: %s", err) | |||
Panic(w, http.StatusForbidden, "Forbidden") | |||
} | |||
} |
@@ -20,7 +20,6 @@ func TemplateFuncs(r *http.Request) template.FuncMap { | |||
// CSRF returns the current CSRF token as an HTML hidden form field. | |||
func CSRF(r *http.Request) func() template.HTML { | |||
return func() template.HTML { | |||
fmt.Println("CSRF() func called") | |||
token, _ := r.Cookie(constants.CSRFCookieName) | |||
return template.HTML(fmt.Sprintf( | |||
`<input type="hidden" name="%s" value="%s">`, | |||
@@ -1,10 +1,10 @@ | |||
package responses | |||
import ( | |||
"fmt" | |||
"html/template" | |||
"log" | |||
"net/http" | |||
"git.kirsle.net/apps/gophertype/pkg/console" | |||
) | |||
// RenderTemplate renders a Go HTML template. | |||
@@ -14,13 +14,12 @@ func RenderTemplate(w http.ResponseWriter, r *http.Request, tmpl string, vars in | |||
vars = NewTemplateVars(w, r) | |||
} | |||
w.Header().Set("Content-Type", "text/html; charset=utf-8") | |||
// Look for the built-in template. | |||
if b, err := GetFile(tmpl); err == nil { | |||
b, err := GetFile(tmpl) | |||
if err == nil { | |||
t, err := template.New(tmpl).Funcs(TemplateFuncs(r)).Parse(string(b)) | |||
if err != nil { | |||
log.Printf("bundled template '%s': %s", tmpl, err) | |||
console.Error("RenderTemplate: bundled template '%s': %s", tmpl, err) | |||
return err | |||
} | |||
@@ -28,21 +27,19 @@ func RenderTemplate(w http.ResponseWriter, r *http.Request, tmpl string, vars in | |||
if layout, err := GetFile(".layout.gohtml"); err == nil { | |||
_, err := t.New("layout").Parse(string(layout)) | |||
if err != nil { | |||
log.Printf("RenderTemplate(.layout.gohtml): %s", err) | |||
console.Error("RenderTemplate(.layout.gohtml): %s", err) | |||
} | |||
} else { | |||
log.Printf("RenderTemplate: .layout.gohtml not found to wrap %s", tmpl) | |||
console.Error("RenderTemplate: .layout.gohtml not found to wrap %s", tmpl) | |||
} | |||
fmt.Printf("Render Templ: %s", tmpl) | |||
console.Debug("Render Templ: %s", tmpl) | |||
if err := t.ExecuteTemplate(w, "layout", vars); err != nil { | |||
log.Printf("RenderTemplate(%s): %s", tmpl, err) | |||
console.Error("RenderTemplate(%s): %s", tmpl, err) | |||
} | |||
log.Println("Done") | |||
return nil | |||
} else { | |||
Panic(w, http.StatusInternalServerError, err.Error()) | |||
} | |||
Panic(w, http.StatusInternalServerError, err.Error()) | |||
return nil | |||
} |
@@ -1,11 +1,10 @@ | |||
package gophertype | |||
import ( | |||
"fmt" | |||
"log" | |||
"net/http" | |||
"git.kirsle.net/apps/gophertype/pkg/authentication" | |||
"git.kirsle.net/apps/gophertype/pkg/console" | |||
"git.kirsle.net/apps/gophertype/pkg/controllers" | |||
"git.kirsle.net/apps/gophertype/pkg/glue" | |||
"git.kirsle.net/apps/gophertype/pkg/middleware" | |||
@@ -21,8 +20,9 @@ func (s *Site) SetupRouter() error { | |||
router.Use(authentication.Middleware) | |||
router.Use(middleware.CSRF) | |||
console.Debug("Setting up HTTP Router") | |||
for _, route := range glue.GetControllers() { | |||
log.Printf("Register: %+v", route) | |||
console.Debug("Register: %+v", route) | |||
if len(route.Methods) == 0 { | |||
route.Methods = []string{"GET"} | |||
} | |||
@@ -30,7 +30,7 @@ func (s *Site) SetupRouter() error { | |||
route.Methods = append(route.Methods, "HEAD") | |||
if len(route.Middleware) > 0 { | |||
log.Printf("%+v has middlewares!", route) | |||
console.Debug("%+v has middlewares!", route) | |||
handler := route.Middleware[0](http.HandlerFunc(route.Handler)) | |||
router.Handle(route.Path, handler).Methods(route.Methods...) | |||
@@ -42,11 +42,11 @@ func (s *Site) SetupRouter() error { | |||
router.PathPrefix("/").HandlerFunc(controllers.CatchAllHandler) | |||
router.NotFoundHandler = http.HandlerFunc(controllers.CatchAllHandler) | |||
log.Println("Walk the mux.Router:") | |||
console.Debug("Walk the mux.Router:") | |||
router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { | |||
tpl, err1 := route.GetPathTemplate() | |||
met, err2 := route.GetMethods() | |||
fmt.Println(tpl, err1, met, err2) | |||
console.Debug("path:%s methods:%s path-err:%s met-err:%s", tpl, met, err1, err2) | |||
return nil | |||
}) | |||
@@ -6,8 +6,8 @@ import ( | |||
"net/http" | |||
"time" | |||
"git.kirsle.net/apps/gophertype/pkg/console" | |||
"github.com/gorilla/sessions" | |||
"github.com/kirsle/blog/src/types" | |||
) | |||
// Store holds your cookie store information. | |||
@@ -15,7 +15,6 @@ var Store sessions.Store | |||
// SetSecretKey initializes a session cookie store with the secret key. | |||
func SetSecretKey(keyPairs ...[]byte) { | |||
fmt.Printf("XXXXX SetSecretKey: %+v", keyPairs) | |||
Store = sessions.NewCookieStore(keyPairs...) | |||
} | |||
@@ -27,6 +26,9 @@ func SetSecretKey(keyPairs ...[]byte) { | |||
// context. | |||
func Middleware(next http.Handler) http.Handler { | |||
middleware := func(w http.ResponseWriter, r *http.Request) { | |||
// Set the HTML content-type header by default until overridden by a handler. | |||
w.Header().Set("Content-Type", "text/html; charset=utf-8") | |||
// Store the current datetime on the request context. | |||
ctx := context.WithValue(r.Context(), StartTimeKey, time.Now()) | |||
@@ -47,13 +49,13 @@ func Get(r *http.Request) *sessions.Session { | |||
} | |||
ctx := r.Context() | |||
if session, ok := ctx.Value(types.SessionKey).(*sessions.Session); ok { | |||
if session, ok := ctx.Value(SessionKey).(*sessions.Session); ok { | |||
return session | |||
} | |||
// If the session wasn't on the request, it means I broke something. | |||
fmt.Printf( | |||
"ERROR: Session(): didn't find session in request context! Getting it " + | |||
console.Warn( | |||
"Session(): didn't find session in request context! Getting it " + | |||
"from the session store instead.", | |||
) | |||
session, _ := Store.Get(r, "session") | |||
@@ -3,7 +3,14 @@ package settings | |||
import ( | |||
"crypto/rand" | |||
"encoding/base64" | |||
"encoding/json" | |||
"fmt" | |||
"io" | |||
"io/ioutil" | |||
"os" | |||
"path/filepath" | |||
"git.kirsle.net/apps/gophertype/pkg/console" | |||
"git.kirsle.net/apps/gophertype/pkg/session" | |||
) | |||
@@ -36,7 +43,53 @@ type Spec struct { | |||
MailPassword string | |||
// Security | |||
SecretKey string `json:"-"` | |||
SecretKey string | |||
} | |||
// Filename is the path where the settings.json file is saved to. | |||
var ( | |||
configFilename = ".settings.json" | |||
configPath string | |||
) | |||
// SetFilename sets the config file path, to be inside the user root. | |||
func SetFilename(userRoot string) error { | |||
path := filepath.Join(userRoot, configFilename) | |||
configPath = path | |||
// Initialize it for the first time? | |||
if _, err := os.Stat(path); os.IsNotExist(err) { | |||
console.Warn("No settings.json found; initializing a new settings file at %s", path) | |||
fh, err := os.Create(path) | |||
if err != nil { | |||
return fmt.Errorf("couldn't initialize settings.json at %s: %s", path, err) | |||
} | |||
Current.ToJSON(fh) | |||
fh.Close() | |||
return nil | |||
} | |||
// Read the stored file instead. | |||
fh, err := os.Open(path) | |||
if err != nil { | |||
return fmt.Errorf("couldn't read settings.json from %s: %s", path, err) | |||
} | |||
defer fh.Close() | |||
data, err := ioutil.ReadAll(fh) | |||
if err != nil { | |||
return fmt.Errorf("couldn't read data from settings.json at %s: %s", path, err) | |||
} | |||
spec, err := FromJSON(data) | |||
if err != nil { | |||
return fmt.Errorf("settings.json parse error from %s: %s", path, err) | |||
} | |||
Current = spec | |||
session.SetSecretKey([]byte(Current.SecretKey)) | |||
return nil | |||
} | |||
// Load gets or creates the App Settings. | |||
@@ -52,11 +105,33 @@ func Load() Spec { | |||
return s | |||
} | |||
// FromJSON loads a settings JSON from bytes. | |||
func FromJSON(data []byte) (Spec, error) { | |||
var s Spec | |||
err := json.Unmarshal(data, &s) | |||
return s, err | |||
} | |||
// ToJSON converts the settings spec to JSON. | |||
func (s Spec) ToJSON(w io.Writer) error { | |||
enc := json.NewEncoder(w) | |||
enc.SetIndent("", "\t") | |||
err := enc.Encode(s) | |||
return err | |||
} | |||
// Save the settings to DB. | |||
func (s Spec) Save() error { | |||
Current = s | |||
session.SetSecretKey([]byte(s.SecretKey)) | |||
return nil | |||
fh, err := os.Create(configPath) | |||
if err != nil { | |||
return err | |||
} | |||
defer fh.Close() | |||
return s.ToJSON(fh) | |||
} | |||
// MakeSecretKey generates a secret key for signing HTTP cookies. | |||
@@ -0,0 +1,7 @@ | |||
{{ define "title" }}Bad Request{{ end }} | |||
{{ define "content" }} | |||
<h1>Bad Request</h1> | |||
{{ or .Message "A problem has occurred." }} | |||
{{ end }} |
@@ -0,0 +1,7 @@ | |||
{{ define "title" }}Forbidden{{ end }} | |||
{{ define "content" }} | |||
<h1>Forbidden</h1> | |||
{{ or .Message "Access denied." }} | |||
{{ end }} |