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) } }) }