|
|
package session
import ( "crypto/rand" "encoding/hex" "log" "net/http" "strings" "sync" "time"
"git.aiterp.net/aiterp/wikiauth"
"git.aiterp.net/rpdata/api/internal/config" "git.aiterp.net/rpdata/api/internal/store" "github.com/globalsign/mgo" "github.com/globalsign/mgo/bson" )
var sessionCollection *mgo.Collection
// A Session represents a login session.
type Session struct { mutex sync.Mutex
ID string `bson:"_id"` Time time.Time `bson:"time"` UserID string `bson:"userId"`
user *User w http.ResponseWriter }
// Load loads a session from a cookie, returning either `r` or a request
// with the session context.
func Load(w http.ResponseWriter, r *http.Request) *http.Request { cookie, err := r.Cookie("aiterp_session") if err != nil { return r.WithContext(contextWithSession(r.Context(), &Session{w: w})) }
id := cookie.Value
session := Session{} err = sessionCollection.FindId(id).One(&session) if err != nil || time.Since(session.Time) > time.Hour*168 { return r.WithContext(contextWithSession(r.Context(), &Session{w: w})) }
if session.ID != "" && time.Since(session.Time) > time.Second*30 { session.Time = time.Now() go sessionCollection.UpdateId(id, bson.M{"$set": bson.M{"time": session.Time}}) }
cookie.Expires = time.Now().Add(time.Hour * 168) http.SetCookie(w, cookie)
session.w = w
return r.WithContext(contextWithSession(r.Context(), &session)) }
// Login logs a user in.
func (session *Session) Login(username, password string) error { auth := wikiauth.New(config.Global().Wiki.URL)
err := auth.Login(username, password) if err != nil { return err }
// Allow bot passwords
username = strings.SplitN(username, "@", 2)[0]
data := make([]byte, 32) _, err = rand.Read(data) if err != nil { return err }
session.ID = hex.EncodeToString(data) session.UserID = username session.Time = time.Now()
err = sessionCollection.Insert(&session) if err != nil { return err }
http.SetCookie(session.w, &http.Cookie{ Name: "aiterp_session", Value: session.ID, Expires: time.Now().Add(time.Hour * 2160), // 90 days
HttpOnly: true, })
user, err := FindUser(session.UserID) if err == mgo.ErrNotFound { user = User{ID: username, Nick: "", Permissions: DefaultPermissions()}
err := userCollection.Insert(user) if err != nil { return err } } else if err != nil { return err }
return nil }
// Logout logs out the session
func (session *Session) Logout() { http.SetCookie(session.w, &http.Cookie{ Name: "aiterp_session", Value: "", Expires: time.Unix(0, 0), HttpOnly: true, })
session.mutex.Lock() session.user = nil session.UserID = "" session.ID = "" session.mutex.Unlock()
sessionCollection.RemoveId(session.ID) }
// User gets the user information for the session.
func (session *Session) User() *User { session.mutex.Lock() defer session.mutex.Unlock()
if session.user != nil { return session.user }
if session.UserID == "" { return nil }
user, err := FindUser(session.UserID) if err != nil { return nil }
return &user }
// NameOrPermitted is a shorthand for checking the username OR permissions, e.g. to check
// if a logged in user can edit a certain post.
func (session *Session) NameOrPermitted(userid string, permissions ...string) bool { if session.UserID == userid { return true }
user := session.User() if user == nil { return false }
return user.Permitted() }
func init() { store.HandleInit(func(db *mgo.Database) { sessionCollection = db.C("core.sessions")
sessionCollection.EnsureIndexKey("nick") sessionCollection.EnsureIndexKey("userId")
err := sessionCollection.EnsureIndex(mgo.Index{ Name: "time", Key: []string{"time"}, ExpireAfter: time.Hour * 168, }) if err != nil { log.Fatalln(err) } }) }
|