Page Menu
Home
Musing Studio
Search
Configure Global Search
Log In
Files
F10668785
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
7 KB
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Thu, May 15, 8:52 AM (16 h, 11 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3239600
Attached To
rWF WriteFreely
Event Timeline
Log In to Comment