Page MenuHomeMusing Studio

No OneTemporary

diff --git a/app.go b/app.go
index f51908c..669c751 100644
--- a/app.go
+++ b/app.go
@@ -1,93 +1,95 @@
package writefreely
import (
"flag"
"fmt"
_ "github.com/go-sql-driver/mysql"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/config"
)
const (
staticDir = "static/"
)
type app struct {
router *mux.Router
cfg *config.Config
keys *keychain
sessionStore *sessions.CookieStore
}
var shttp = http.NewServeMux()
func Serve() {
createConfig := flag.Bool("create-config", false, "Creates a basic configuration and exits")
flag.Parse()
if *createConfig {
log.Info("Creating configuration...")
c := config.New()
log.Info("Saving configuration...")
config.Save(c)
os.Exit(0)
}
log.Info("Initializing...")
log.Info("Loading configuration...")
cfg, err := config.Load()
if err != nil {
log.Error("Unable to load configuration: %v", err)
os.Exit(1)
}
app := &app{
cfg: cfg,
}
// Load keys
log.Info("Loading encryption keys...")
err = initKeys(app)
if err != nil {
log.Error("\n%s\n", err)
}
// Initialize modules
+ app.sessionStore = initSession(app)
+
r := mux.NewRouter()
handler := NewHandler(app.sessionStore)
// Handle app routes
initRoutes(handler, r, app.cfg)
// Handle static files
fs := http.FileServer(http.Dir(staticDir))
shttp.Handle("/", fs)
r.PathPrefix("/").Handler(fs)
// Handle shutdown
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
log.Info("Shutting down...")
shutdown(app)
log.Info("Done.")
os.Exit(0)
}()
// Start web application server
http.Handle("/", r)
log.Info("Serving on http://localhost:%d\n", app.cfg.Server.Port)
log.Info("---")
http.ListenAndServe(fmt.Sprintf(":%d", app.cfg.Server.Port), nil)
}
func shutdown(app *app) {
}
diff --git a/errors.go b/errors.go
new file mode 100644
index 0000000..28819fb
--- /dev/null
+++ b/errors.go
@@ -0,0 +1,11 @@
+package writefreely
+
+import (
+ "github.com/writeas/impart"
+ "net/http"
+)
+
+// Commonly returned HTTP errors
+var (
+ ErrInternalCookieSession = impart.HTTPError{http.StatusInternalServerError, "Could not get cookie session."}
+)
diff --git a/keys.go b/keys.go
index 505141e..3f594f5 100644
--- a/keys.go
+++ b/keys.go
@@ -1,26 +1,31 @@
package writefreely
import (
"io/ioutil"
)
type keychain struct {
- cookieAuthKey, cookieKey []byte
+ emailKey, cookieAuthKey, cookieKey []byte
}
func initKeys(app *app) error {
var err error
app.keys = &keychain{}
+ app.keys.emailKey, err = ioutil.ReadFile("keys/email.aes256")
+ if err != nil {
+ return err
+ }
+
app.keys.cookieAuthKey, err = ioutil.ReadFile("keys/cookies_auth.aes256")
if err != nil {
return err
}
app.keys.cookieKey, err = ioutil.ReadFile("keys/cookies_enc.aes256")
if err != nil {
return err
}
return nil
}
diff --git a/session.go b/session.go
new file mode 100644
index 0000000..bd768cb
--- /dev/null
+++ b/session.go
@@ -0,0 +1,126 @@
+package writefreely
+
+import (
+ "encoding/gob"
+ "github.com/gorilla/sessions"
+ "github.com/writeas/web-core/log"
+ "net/http"
+ "strings"
+)
+
+const (
+ day = 86400
+ sessionLength = 180 * day
+ cookieName = "wfu"
+ cookieUserVal = "u"
+)
+
+// initSession creates the cookie store. It depends on the keychain already
+// being loaded.
+func initSession(app *app) *sessions.CookieStore {
+ // Register complex data types we'll be storing in cookies
+ gob.Register(&User{})
+
+ // Create the cookie store
+ store := sessions.NewCookieStore(app.keys.cookieAuthKey, app.keys.cookieKey)
+ store.Options = &sessions.Options{
+ Path: "/",
+ MaxAge: sessionLength,
+ HttpOnly: true,
+ Secure: strings.HasPrefix(app.cfg.Server.Host, "https://"),
+ }
+ return store
+}
+
+func getSessionFlashes(app *app, w http.ResponseWriter, r *http.Request, session *sessions.Session) ([]string, error) {
+ var err error
+ if session == nil {
+ session, err = app.sessionStore.Get(r, cookieName)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ f := []string{}
+ if flashes := session.Flashes(); len(flashes) > 0 {
+ for _, flash := range flashes {
+ if str, ok := flash.(string); ok {
+ f = append(f, str)
+ }
+ }
+ }
+ saveUserSession(app, r, w)
+
+ return f, nil
+}
+
+func addSessionFlash(app *app, w http.ResponseWriter, r *http.Request, m string, session *sessions.Session) error {
+ var err error
+ if session == nil {
+ session, err = app.sessionStore.Get(r, cookieName)
+ }
+
+ if err != nil {
+ log.Error("Unable to add flash '%s': %v", m, err)
+ return err
+ }
+
+ session.AddFlash(m)
+ saveUserSession(app, r, w)
+ return nil
+}
+
+func getUserAndSession(app *app, r *http.Request) (*User, *sessions.Session) {
+ session, err := app.sessionStore.Get(r, cookieName)
+ if err == nil {
+ // Got the currently logged-in user
+ val := session.Values[cookieUserVal]
+ var u = &User{}
+ var ok bool
+ if u, ok = val.(*User); ok {
+ return u, session
+ }
+ }
+
+ return nil, nil
+}
+
+func getUserSession(app *app, r *http.Request) *User {
+ u, _ := getUserAndSession(app, r)
+ return u
+}
+
+func saveUserSession(app *app, r *http.Request, w http.ResponseWriter) error {
+ session, err := app.sessionStore.Get(r, cookieName)
+ if err != nil {
+ return ErrInternalCookieSession
+ }
+
+ // Extend the session
+ session.Options.MaxAge = int(sessionLength)
+
+ // Remove any information that accidentally got added
+ // FIXME: find where Plan information is getting saved to cookie.
+ val := session.Values[cookieUserVal]
+ var u = &User{}
+ var ok bool
+ if u, ok = val.(*User); ok {
+ session.Values[cookieUserVal] = u.Cookie()
+ }
+
+ err = session.Save(r, w)
+ if err != nil {
+ log.Error("Couldn't saveUserSession: %v", err)
+ }
+ return err
+}
+
+func getFullUserSession(app *app, r *http.Request) *User {
+ u := getUserSession(app, r)
+ if u == nil {
+ return nil
+ }
+
+ u, _ = app.db.GetUserByID(u.ID)
+ return u
+}
diff --git a/users.go b/users.go
new file mode 100644
index 0000000..e9e63a5
--- /dev/null
+++ b/users.go
@@ -0,0 +1,51 @@
+package writefreely
+
+import (
+ "time"
+
+ "github.com/guregu/null/zero"
+ "github.com/writeas/web-core/data"
+ "github.com/writeas/web-core/log"
+)
+
+type (
+ // User is a consistent user object in the database and all contexts (auth
+ // and non-auth) in the API.
+ User struct {
+ ID int64 `json:"-"`
+ Username string `json:"username"`
+ HashedPass []byte `json:"-"`
+ HasPass bool `json:"has_pass"`
+ Email zero.String `json:"email"`
+ Created time.Time `json:"created"`
+
+ clearEmail string `json:"email"`
+ }
+)
+
+// EmailClear decrypts and returns the user's email, caching it in the user
+// object.
+func (u *User) EmailClear(keys *keychain) string {
+ if u.clearEmail != "" {
+ return u.clearEmail
+ }
+
+ if u.Email.Valid && u.Email.String != "" {
+ email, err := data.Decrypt(keys.emailKey, []byte(u.Email.String))
+ if err != nil {
+ log.Error("Error decrypting user email: %v", err)
+ } else {
+ u.clearEmail = string(email)
+ return u.clearEmail
+ }
+ }
+ return ""
+}
+
+// Cookie strips down an AuthUser to contain only information necessary for
+// cookies.
+func (u User) Cookie() *User {
+ u.HashedPass = []byte{}
+
+ return &u
+}

File Metadata

Mime Type
text/x-diff
Expires
Thu, May 15, 8:52 AM (13 h, 34 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3239600

Event Timeline