mirror of
https://github.com/writefreely/writefreely
synced 2024-11-14 05:07:07 +00:00
Add web session management
This commit is contained in:
parent
af601d7b0c
commit
62abc11142
5 changed files with 196 additions and 1 deletions
2
app.go
2
app.go
|
@ -60,6 +60,8 @@ func Serve() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize modules
|
// Initialize modules
|
||||||
|
app.sessionStore = initSession(app)
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
handler := NewHandler(app.sessionStore)
|
handler := NewHandler(app.sessionStore)
|
||||||
|
|
||||||
|
|
11
errors.go
Normal file
11
errors.go
Normal file
|
@ -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."}
|
||||||
|
)
|
7
keys.go
7
keys.go
|
@ -5,13 +5,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type keychain struct {
|
type keychain struct {
|
||||||
cookieAuthKey, cookieKey []byte
|
emailKey, cookieAuthKey, cookieKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func initKeys(app *app) error {
|
func initKeys(app *app) error {
|
||||||
var err error
|
var err error
|
||||||
app.keys = &keychain{}
|
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")
|
app.keys.cookieAuthKey, err = ioutil.ReadFile("keys/cookies_auth.aes256")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
126
session.go
Normal file
126
session.go
Normal file
|
@ -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
|
||||||
|
}
|
51
users.go
Normal file
51
users.go
Normal file
|
@ -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
|
||||||
|
}
|
Loading…
Reference in a new issue