stufflog graphql server
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.

317 lines
8.0 KiB

4 years ago
4 years ago
  1. package services
  2. import (
  3. "context"
  4. "errors"
  5. "git.aiterp.net/stufflog/server/database/repositories"
  6. "git.aiterp.net/stufflog/server/internal/generate"
  7. "git.aiterp.net/stufflog/server/internal/slerrors"
  8. "git.aiterp.net/stufflog/server/models"
  9. "github.com/gin-gonic/gin"
  10. "math/rand"
  11. "time"
  12. )
  13. var ErrLoginFailed = errors.New("login failed")
  14. var ErrInternalLoginFailure = errors.New("login failed due to internal error")
  15. var ErrInternalPermissionFailure = errors.New("permission check failed due to internal error")
  16. var ErrWrongCurrentPassword = errors.New("current password is missing")
  17. var ErrMissingCurrentPassword = errors.New("current password is missing")
  18. var authCookieName = "stufflog_cookie"
  19. var authCtxKey = "stufflog.auth"
  20. var ginCtxKey = "stufflog.gin"
  21. type Auth struct {
  22. users repositories.UserRepository
  23. session repositories.SessionRepository
  24. projects repositories.ProjectRepository
  25. issues repositories.IssueRepository
  26. }
  27. func (auth *Auth) Login(ctx context.Context, username, password string) (*models.User, error) {
  28. user, err := auth.users.Find(ctx, username)
  29. if err != nil {
  30. select {
  31. case <-time.After(time.Millisecond * time.Duration(rand.Int63n(100)+100)):
  32. case <-ctx.Done():
  33. }
  34. return nil, ErrLoginFailed
  35. }
  36. if !user.CheckPassword(password) {
  37. select {
  38. case <-time.After(time.Millisecond * time.Duration(rand.Int63n(50))):
  39. case <-ctx.Done():
  40. }
  41. return nil, ErrLoginFailed
  42. }
  43. session := models.Session{
  44. ID: generate.SessionID(),
  45. UserID: user.ID,
  46. ExpiryTime: time.Now().Add(time.Hour * 168),
  47. }
  48. err = auth.session.Save(ctx, session)
  49. if err != nil {
  50. return nil, ErrInternalLoginFailure
  51. }
  52. if c := ctx.Value(ginCtxKey).(*gin.Context); c != nil {
  53. c.SetCookie(authCookieName, session.ID, 3600*168, "/", "", false, true)
  54. } else {
  55. return nil, ErrInternalLoginFailure
  56. }
  57. return user, nil
  58. }
  59. func (auth *Auth) Logout(ctx context.Context) (*models.User, error) {
  60. user := auth.UserFromContext(ctx)
  61. if user == nil {
  62. return nil, slerrors.PermissionDenied
  63. }
  64. c, ok := ctx.Value(ginCtxKey).(*gin.Context)
  65. if !ok {
  66. return nil, ErrInternalLoginFailure
  67. }
  68. c.SetCookie(authCookieName, "", 0, "/", "", false, true)
  69. return user, nil
  70. }
  71. func (auth *Auth) CreateUser(ctx context.Context, username, password, name string, active, admin bool) (*models.User, error) {
  72. loggedInUser := auth.UserFromContext(ctx)
  73. if loggedInUser == nil || !loggedInUser.Admin {
  74. return nil, slerrors.PermissionDenied
  75. }
  76. user := &models.User{
  77. ID: username,
  78. Name: name,
  79. Active: active,
  80. Admin: admin,
  81. }
  82. err := user.SetPassword(password)
  83. if err != nil {
  84. return nil, err
  85. }
  86. user, err = auth.users.Insert(ctx, *user)
  87. if err != nil {
  88. return nil, err
  89. }
  90. return user, nil
  91. }
  92. func (auth *Auth) UserFromContext(ctx context.Context) *models.User {
  93. user, _ := ctx.Value(authCtxKey).(*models.User)
  94. return user
  95. }
  96. func (auth *Auth) ProjectPermission(ctx context.Context, project models.Project) (*models.ProjectPermission, error) {
  97. user := auth.UserFromContext(ctx)
  98. if user == nil || !user.Active {
  99. return nil, slerrors.PermissionDenied
  100. }
  101. permission, err := auth.projects.GetPermission(ctx, project, *user)
  102. if err != nil {
  103. return nil, ErrInternalPermissionFailure
  104. }
  105. if permission.Level == models.ProjectPermissionLevelNoAccess {
  106. return nil, slerrors.PermissionDenied
  107. }
  108. return permission, nil
  109. }
  110. func (auth *Auth) IssuePermission(ctx context.Context, issue models.Issue) (*models.ProjectPermission, error) {
  111. user := auth.UserFromContext(ctx)
  112. if user == nil || !user.Active {
  113. return nil, slerrors.PermissionDenied
  114. }
  115. isOwnedOrAssigned := issue.AssigneeID == user.ID || issue.OwnerID == user.ID
  116. permission, err := auth.projects.GetIssuePermission(ctx, issue, *user)
  117. if err != nil {
  118. return nil, ErrInternalPermissionFailure
  119. }
  120. if permission.Level == models.ProjectPermissionLevelNoAccess {
  121. return nil, slerrors.PermissionDenied
  122. }
  123. if !(permission.CanViewAnyIssue() || (permission.CanViewOwnIssue() && isOwnedOrAssigned)) {
  124. return nil, slerrors.PermissionDenied
  125. }
  126. return permission, nil
  127. }
  128. func (auth *Auth) CheckGinSession(c *gin.Context) {
  129. ctx := context.WithValue(c.Request.Context(), ginCtxKey, c)
  130. cookie, err := c.Cookie(authCookieName)
  131. if err != nil {
  132. c.Request = c.Request.WithContext(ctx)
  133. return
  134. }
  135. session, err := auth.session.Find(c.Request.Context(), cookie)
  136. if err != nil {
  137. c.Request = c.Request.WithContext(ctx)
  138. return
  139. }
  140. if time.Until(session.ExpiryTime) < time.Hour*167 {
  141. session.ExpiryTime = time.Now().Add(time.Hour * 168)
  142. _ = auth.session.Save(c.Request.Context(), *session)
  143. c.SetCookie(authCookieName, session.ID, 3600*168, "/", "", false, true)
  144. }
  145. user, err := auth.users.Find(c.Request.Context(), session.UserID)
  146. if err != nil {
  147. c.Request = c.Request.WithContext(ctx)
  148. return
  149. }
  150. ctx = context.WithValue(ctx, authCtxKey, user)
  151. c.Request = c.Request.WithContext(ctx)
  152. }
  153. func (auth *Auth) EditUser(ctx context.Context, username string, setName *string, currentPassword *string, newPassword *string) (*models.User, error) {
  154. loggedInUser := auth.UserFromContext(ctx)
  155. if loggedInUser == nil {
  156. return nil, slerrors.PermissionDenied
  157. }
  158. user, err := auth.users.Find(ctx, username)
  159. if err != nil {
  160. return nil, err
  161. }
  162. if user.ID != loggedInUser.ID && !loggedInUser.Admin {
  163. return nil, slerrors.PermissionDenied
  164. }
  165. if newPassword != nil {
  166. // Only require current password if it's given, or if the user is NOT an admin changing
  167. // another user's password
  168. if currentPassword != nil || !(loggedInUser.Admin && loggedInUser.ID != user.ID) {
  169. if currentPassword == nil {
  170. return nil, ErrMissingCurrentPassword
  171. }
  172. if !user.CheckPassword(*currentPassword) {
  173. return nil, ErrWrongCurrentPassword
  174. }
  175. }
  176. err = user.SetPassword(*newPassword)
  177. if err != nil {
  178. return nil, err
  179. }
  180. }
  181. if setName != nil && *setName != "" {
  182. user.Name = *setName
  183. }
  184. err = auth.users.Save(ctx, *user)
  185. if err != nil {
  186. return nil, err
  187. }
  188. return user, nil
  189. }
  190. func (auth *Auth) FilterLogList(ctx context.Context, logs *[]*models.Log) {
  191. user := auth.UserFromContext(ctx)
  192. if user == nil {
  193. panic("Auth.FilterLogList called without user")
  194. }
  195. auth.FilterLog(ctx, *logs...)
  196. deleteList := make([]int, 0, len(*logs)/2)
  197. for i, log := range *logs {
  198. if log.Empty() && log.UserID != user.ID {
  199. deleteList = append(deleteList, i-len(deleteList))
  200. }
  201. }
  202. list := *logs
  203. for _, index := range deleteList {
  204. list = append(list[:index], list[index+1:]...)
  205. }
  206. *logs = list
  207. }
  208. func (auth *Auth) FilterLog(ctx context.Context, logs ...*models.Log) {
  209. userID := ""
  210. if user := auth.UserFromContext(ctx); user != nil {
  211. userID = user.ID
  212. }
  213. accessMap := make(map[string]bool)
  214. deleteList := make([]int, 0, 16)
  215. for _, log := range logs {
  216. if userID == log.UserID {
  217. continue
  218. }
  219. deleteList = deleteList[:0]
  220. for i, item := range log.Items {
  221. if access, ok := accessMap[item.IssueID]; ok && access {
  222. continue
  223. } else if ok && !access {
  224. deleteList = append(deleteList, i-len(deleteList))
  225. continue
  226. }
  227. issue, err := auth.issues.Find(ctx, item.IssueID)
  228. if err != nil {
  229. deleteList = append(deleteList, i-len(deleteList))
  230. accessMap[item.IssueID] = true
  231. continue
  232. }
  233. _, err = auth.IssuePermission(ctx, *issue)
  234. if err != nil {
  235. deleteList = append(deleteList, i-len(deleteList))
  236. }
  237. accessMap[issue.ID] = err != nil
  238. }
  239. for _, index := range deleteList {
  240. log.Items = append(log.Items[:index], log.Items[index+1:]...)
  241. }
  242. deleteList = deleteList[:0]
  243. for i, task := range log.Tasks {
  244. if access, ok := accessMap[task.IssueID]; ok && access {
  245. continue
  246. } else if ok && !access {
  247. deleteList = append(deleteList, i-len(deleteList))
  248. continue
  249. }
  250. issue, err := auth.issues.Find(ctx, task.IssueID)
  251. if err != nil {
  252. deleteList = append(deleteList, i-len(deleteList))
  253. accessMap[task.IssueID] = true
  254. continue
  255. }
  256. _, err = auth.IssuePermission(ctx, *issue)
  257. if err != nil {
  258. deleteList = append(deleteList, i-len(deleteList))
  259. }
  260. accessMap[issue.ID] = err != nil
  261. }
  262. for _, index := range deleteList {
  263. log.Tasks = append(log.Tasks[:index], log.Tasks[index+1:]...)
  264. }
  265. }
  266. }