GraphQL API and utilities for the rpdata project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
3.8 KiB

  1. package session
  2. import (
  3. "crypto/rand"
  4. "encoding/hex"
  5. "log"
  6. "net/http"
  7. "strings"
  8. "sync"
  9. "time"
  10. "git.aiterp.net/aiterp/wikiauth"
  11. "git.aiterp.net/rpdata/api/internal/config"
  12. "git.aiterp.net/rpdata/api/internal/store"
  13. "github.com/globalsign/mgo"
  14. "github.com/globalsign/mgo/bson"
  15. )
  16. var sessionCollection *mgo.Collection
  17. // A Session represents a login session.
  18. type Session struct {
  19. mutex sync.Mutex
  20. ID string `bson:"_id"`
  21. Time time.Time `bson:"time"`
  22. UserID string `bson:"userId"`
  23. user *User
  24. w http.ResponseWriter
  25. }
  26. // Load loads a session from a cookie, returning either `r` or a request
  27. // with the session context.
  28. func Load(w http.ResponseWriter, r *http.Request) *http.Request {
  29. cookie, err := r.Cookie("aiterp_session")
  30. if err != nil {
  31. return r.WithContext(contextWithSession(r.Context(), &Session{w: w}))
  32. }
  33. id := cookie.Value
  34. session := Session{}
  35. err = sessionCollection.FindId(id).One(&session)
  36. if err != nil || time.Since(session.Time) > time.Hour*168 {
  37. return r.WithContext(contextWithSession(r.Context(), &Session{w: w}))
  38. }
  39. if session.ID != "" && time.Since(session.Time) > time.Second*30 {
  40. session.Time = time.Now()
  41. go sessionCollection.UpdateId(id, bson.M{"$set": bson.M{"time": session.Time}})
  42. }
  43. cookie.Expires = time.Now().Add(time.Hour * 168)
  44. http.SetCookie(w, cookie)
  45. session.w = w
  46. return r.WithContext(contextWithSession(r.Context(), &session))
  47. }
  48. // Login logs a user in.
  49. func (session *Session) Login(username, password string) error {
  50. auth := wikiauth.New(config.Global().Wiki.URL)
  51. err := auth.Login(username, password)
  52. if err != nil {
  53. return err
  54. }
  55. // Allow bot passwords
  56. username = strings.SplitN(username, "@", 2)[0]
  57. data := make([]byte, 32)
  58. _, err = rand.Read(data)
  59. if err != nil {
  60. return err
  61. }
  62. session.ID = hex.EncodeToString(data)
  63. session.UserID = username
  64. session.Time = time.Now()
  65. err = sessionCollection.Insert(&session)
  66. if err != nil {
  67. return err
  68. }
  69. http.SetCookie(session.w, &http.Cookie{
  70. Name: "aiterp_session",
  71. Value: session.ID,
  72. Expires: time.Now().Add(time.Hour * 2160), // 90 days
  73. HttpOnly: true,
  74. })
  75. user, err := FindUser(session.UserID)
  76. if err == mgo.ErrNotFound {
  77. user = User{ID: username, Nick: "", Permissions: DefaultPermissions()}
  78. err := userCollection.Insert(user)
  79. if err != nil {
  80. return err
  81. }
  82. } else if err != nil {
  83. return err
  84. }
  85. return nil
  86. }
  87. // Logout logs out the session
  88. func (session *Session) Logout() {
  89. http.SetCookie(session.w, &http.Cookie{
  90. Name: "aiterp_session",
  91. Value: "",
  92. Expires: time.Unix(0, 0),
  93. HttpOnly: true,
  94. })
  95. session.mutex.Lock()
  96. session.user = nil
  97. session.UserID = ""
  98. session.ID = ""
  99. session.mutex.Unlock()
  100. sessionCollection.RemoveId(session.ID)
  101. }
  102. // User gets the user information for the session.
  103. func (session *Session) User() *User {
  104. session.mutex.Lock()
  105. defer session.mutex.Unlock()
  106. if session.user != nil {
  107. return session.user
  108. }
  109. if session.UserID == "" {
  110. return nil
  111. }
  112. user, err := FindUser(session.UserID)
  113. if err != nil {
  114. return nil
  115. }
  116. return &user
  117. }
  118. // NameOrPermitted is a shorthand for checking the username OR permissions, e.g. to check
  119. // if a logged in user can edit a certain post.
  120. func (session *Session) NameOrPermitted(userid string, permissions ...string) bool {
  121. if session.UserID == userid {
  122. return true
  123. }
  124. user := session.User()
  125. if user == nil {
  126. return false
  127. }
  128. return user.Permitted()
  129. }
  130. func init() {
  131. store.HandleInit(func(db *mgo.Database) {
  132. sessionCollection = db.C("core.sessions")
  133. sessionCollection.EnsureIndexKey("nick")
  134. sessionCollection.EnsureIndexKey("userId")
  135. err := sessionCollection.EnsureIndex(mgo.Index{
  136. Name: "time",
  137. Key: []string{"time"},
  138. ExpireAfter: time.Hour * 168,
  139. })
  140. if err != nil {
  141. log.Fatalln(err)
  142. }
  143. })
  144. }