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.

329 lines
8.4 KiB

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