user.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. // Copyright 2017 Frédéric Guillot. All rights reserved.
  2. // Use of this source code is governed by the Apache 2.0
  3. // license that can be found in the LICENSE file.
  4. package storage // import "miniflux.app/storage"
  5. import (
  6. "database/sql"
  7. "fmt"
  8. "runtime"
  9. "strings"
  10. "miniflux.app/logger"
  11. "miniflux.app/model"
  12. "github.com/lib/pq"
  13. "golang.org/x/crypto/bcrypt"
  14. )
  15. // CountUsers returns the total number of users.
  16. func (s *Storage) CountUsers() int {
  17. var result int
  18. err := s.db.QueryRow(`SELECT count(*) FROM users`).Scan(&result)
  19. if err != nil {
  20. return 0
  21. }
  22. return result
  23. }
  24. // SetLastLogin updates the last login date of a user.
  25. func (s *Storage) SetLastLogin(userID int64) error {
  26. query := `UPDATE users SET last_login_at=now() WHERE id=$1`
  27. _, err := s.db.Exec(query, userID)
  28. if err != nil {
  29. return fmt.Errorf(`store: unable to update last login date: %v`, err)
  30. }
  31. return nil
  32. }
  33. // UserExists checks if a user exists by using the given username.
  34. func (s *Storage) UserExists(username string) bool {
  35. var result bool
  36. s.db.QueryRow(`SELECT true FROM users WHERE username=LOWER($1)`, username).Scan(&result)
  37. return result
  38. }
  39. // AnotherUserExists checks if another user exists with the given username.
  40. func (s *Storage) AnotherUserExists(userID int64, username string) bool {
  41. var result bool
  42. s.db.QueryRow(`SELECT true FROM users WHERE id != $1 AND username=LOWER($2)`, userID, username).Scan(&result)
  43. return result
  44. }
  45. // CreateUser creates a new user.
  46. func (s *Storage) CreateUser(user *model.User) (err error) {
  47. hashedPassword := ""
  48. if user.Password != "" {
  49. hashedPassword, err = hashPassword(user.Password)
  50. if err != nil {
  51. return err
  52. }
  53. }
  54. query := `
  55. INSERT INTO users
  56. (username, password, is_admin, google_id, openid_connect_id)
  57. VALUES
  58. (LOWER($1), $2, $3, $4, $5)
  59. RETURNING
  60. id,
  61. username,
  62. is_admin,
  63. language,
  64. theme,
  65. timezone,
  66. entry_direction,
  67. entries_per_page,
  68. keyboard_shortcuts,
  69. show_reading_time,
  70. entry_swipe,
  71. stylesheet,
  72. google_id,
  73. openid_connect_id
  74. `
  75. tx, err := s.db.Begin()
  76. if err != nil {
  77. return fmt.Errorf(`store: unable to start transaction: %v`, err)
  78. }
  79. err = tx.QueryRow(query, user.Username, hashedPassword, user.IsAdmin, user.GoogleID, user.OpenIDConnectID).Scan(
  80. &user.ID,
  81. &user.Username,
  82. &user.IsAdmin,
  83. &user.Language,
  84. &user.Theme,
  85. &user.Timezone,
  86. &user.EntryDirection,
  87. &user.EntriesPerPage,
  88. &user.KeyboardShortcuts,
  89. &user.ShowReadingTime,
  90. &user.EntrySwipe,
  91. &user.Stylesheet,
  92. &user.GoogleID,
  93. &user.OpenIDConnectID,
  94. )
  95. if err != nil {
  96. tx.Rollback()
  97. return fmt.Errorf(`store: unable to create user: %v`, err)
  98. }
  99. _, err = tx.Exec(`INSERT INTO categories (user_id, title) VALUES ($1, $2)`, user.ID, "All")
  100. if err != nil {
  101. tx.Rollback()
  102. return fmt.Errorf(`store: unable to create user default category: %v`, err)
  103. }
  104. _, err = tx.Exec(`INSERT INTO integrations (user_id) VALUES ($1)`, user.ID)
  105. if err != nil {
  106. tx.Rollback()
  107. return fmt.Errorf(`store: unable to create integration row: %v`, err)
  108. }
  109. if err := tx.Commit(); err != nil {
  110. return fmt.Errorf(`store: unable to commit transaction: %v`, err)
  111. }
  112. return nil
  113. }
  114. // UpdateUser updates a user.
  115. func (s *Storage) UpdateUser(user *model.User) error {
  116. if user.Password != "" {
  117. hashedPassword, err := hashPassword(user.Password)
  118. if err != nil {
  119. return err
  120. }
  121. query := `
  122. UPDATE users SET
  123. username=LOWER($1),
  124. password=$2,
  125. is_admin=$3,
  126. theme=$4,
  127. language=$5,
  128. timezone=$6,
  129. entry_direction=$7,
  130. entries_per_page=$8,
  131. keyboard_shortcuts=$9,
  132. show_reading_time=$10,
  133. entry_swipe=$11,
  134. stylesheet=$12,
  135. google_id=$13,
  136. openid_connect_id=$14
  137. WHERE
  138. id=$15
  139. `
  140. _, err = s.db.Exec(
  141. query,
  142. user.Username,
  143. hashedPassword,
  144. user.IsAdmin,
  145. user.Theme,
  146. user.Language,
  147. user.Timezone,
  148. user.EntryDirection,
  149. user.EntriesPerPage,
  150. user.KeyboardShortcuts,
  151. user.ShowReadingTime,
  152. user.EntrySwipe,
  153. user.Stylesheet,
  154. user.GoogleID,
  155. user.OpenIDConnectID,
  156. user.ID,
  157. )
  158. if err != nil {
  159. return fmt.Errorf(`store: unable to update user: %v`, err)
  160. }
  161. } else {
  162. query := `
  163. UPDATE users SET
  164. username=LOWER($1),
  165. is_admin=$2,
  166. theme=$3,
  167. language=$4,
  168. timezone=$5,
  169. entry_direction=$6,
  170. entries_per_page=$7,
  171. keyboard_shortcuts=$8,
  172. show_reading_time=$9,
  173. entry_swipe=$10,
  174. stylesheet=$11,
  175. google_id=$12,
  176. openid_connect_id=$13
  177. WHERE
  178. id=$14
  179. `
  180. _, err := s.db.Exec(
  181. query,
  182. user.Username,
  183. user.IsAdmin,
  184. user.Theme,
  185. user.Language,
  186. user.Timezone,
  187. user.EntryDirection,
  188. user.EntriesPerPage,
  189. user.KeyboardShortcuts,
  190. user.ShowReadingTime,
  191. user.EntrySwipe,
  192. user.Stylesheet,
  193. user.GoogleID,
  194. user.OpenIDConnectID,
  195. user.ID,
  196. )
  197. if err != nil {
  198. return fmt.Errorf(`store: unable to update user: %v`, err)
  199. }
  200. }
  201. return nil
  202. }
  203. // UserLanguage returns the language of the given user.
  204. func (s *Storage) UserLanguage(userID int64) (language string) {
  205. err := s.db.QueryRow(`SELECT language FROM users WHERE id = $1`, userID).Scan(&language)
  206. if err != nil {
  207. return "en_US"
  208. }
  209. return language
  210. }
  211. // UserByID finds a user by the ID.
  212. func (s *Storage) UserByID(userID int64) (*model.User, error) {
  213. query := `
  214. SELECT
  215. id,
  216. username,
  217. is_admin,
  218. theme,
  219. language,
  220. timezone,
  221. entry_direction,
  222. entries_per_page,
  223. keyboard_shortcuts,
  224. show_reading_time,
  225. entry_swipe,
  226. last_login_at,
  227. stylesheet,
  228. google_id,
  229. openid_connect_id
  230. FROM
  231. users
  232. WHERE
  233. id = $1
  234. `
  235. return s.fetchUser(query, userID)
  236. }
  237. // UserByUsername finds a user by the username.
  238. func (s *Storage) UserByUsername(username string) (*model.User, error) {
  239. query := `
  240. SELECT
  241. id,
  242. username,
  243. is_admin,
  244. theme,
  245. language,
  246. timezone,
  247. entry_direction,
  248. entries_per_page,
  249. keyboard_shortcuts,
  250. show_reading_time,
  251. entry_swipe,
  252. last_login_at,
  253. stylesheet,
  254. google_id,
  255. openid_connect_id
  256. FROM
  257. users
  258. WHERE
  259. username=LOWER($1)
  260. `
  261. return s.fetchUser(query, username)
  262. }
  263. // UserByField finds a user by a field value.
  264. func (s *Storage) UserByField(field, value string) (*model.User, error) {
  265. query := `
  266. SELECT
  267. id,
  268. username,
  269. is_admin,
  270. theme,
  271. language,
  272. timezone,
  273. entry_direction,
  274. entries_per_page,
  275. keyboard_shortcuts,
  276. show_reading_time,
  277. entry_swipe,
  278. last_login_at,
  279. stylesheet,
  280. google_id,
  281. openid_connect_id
  282. FROM
  283. users
  284. WHERE
  285. %s=$1
  286. `
  287. return s.fetchUser(fmt.Sprintf(query, pq.QuoteIdentifier(field)), value)
  288. }
  289. // AnotherUserWithFieldExists returns true if a user has the value set for the given field.
  290. func (s *Storage) AnotherUserWithFieldExists(userID int64, field, value string) bool {
  291. var result bool
  292. s.db.QueryRow(fmt.Sprintf(`SELECT true FROM users WHERE id <> $1 AND %s=$2`, pq.QuoteIdentifier(field)), userID, value).Scan(&result)
  293. return result
  294. }
  295. // UserByAPIKey returns a User from an API Key.
  296. func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
  297. query := `
  298. SELECT
  299. u.id,
  300. u.username,
  301. u.is_admin,
  302. u.theme,
  303. u.language,
  304. u.timezone,
  305. u.entry_direction,
  306. u.entries_per_page,
  307. u.keyboard_shortcuts,
  308. u.show_reading_time,
  309. u.entry_swipe,
  310. u.last_login_at,
  311. u.stylesheet,
  312. u.google_id,
  313. u.openid_connect_id
  314. FROM
  315. users u
  316. LEFT JOIN
  317. api_keys ON api_keys.user_id=u.id
  318. WHERE
  319. api_keys.token = $1
  320. `
  321. return s.fetchUser(query, token)
  322. }
  323. func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, error) {
  324. user := model.NewUser()
  325. err := s.db.QueryRow(query, args...).Scan(
  326. &user.ID,
  327. &user.Username,
  328. &user.IsAdmin,
  329. &user.Theme,
  330. &user.Language,
  331. &user.Timezone,
  332. &user.EntryDirection,
  333. &user.EntriesPerPage,
  334. &user.KeyboardShortcuts,
  335. &user.ShowReadingTime,
  336. &user.EntrySwipe,
  337. &user.LastLoginAt,
  338. &user.Stylesheet,
  339. &user.GoogleID,
  340. &user.OpenIDConnectID,
  341. )
  342. if err == sql.ErrNoRows {
  343. return nil, nil
  344. } else if err != nil {
  345. return nil, fmt.Errorf(`store: unable to fetch user: %v`, err)
  346. }
  347. return user, nil
  348. }
  349. // RemoveUser deletes a user.
  350. func (s *Storage) RemoveUser(userID int64) error {
  351. tx, err := s.db.Begin()
  352. if err != nil {
  353. return fmt.Errorf(`store: unable to start transaction: %v`, err)
  354. }
  355. if _, err := tx.Exec(`DELETE FROM users WHERE id=$1`, userID); err != nil {
  356. tx.Rollback()
  357. return fmt.Errorf(`store: unable to remove user #%d: %v`, userID, err)
  358. }
  359. if _, err := tx.Exec(`DELETE FROM integrations WHERE user_id=$1`, userID); err != nil {
  360. tx.Rollback()
  361. return fmt.Errorf(`store: unable to remove integration settings for user #%d: %v`, userID, err)
  362. }
  363. if err := tx.Commit(); err != nil {
  364. return fmt.Errorf(`store: unable to commit transaction: %v`, err)
  365. }
  366. return nil
  367. }
  368. // RemoveUserAsync deletes user data without locking the database.
  369. func (s *Storage) RemoveUserAsync(userID int64) {
  370. go func() {
  371. deleteUserFeeds(s.db, userID)
  372. s.db.Exec(`DELETE FROM users WHERE id=$1`, userID)
  373. s.db.Exec(`DELETE FROM integrations WHERE user_id=$1`, userID)
  374. logger.Debug(`[MASS DELETE] User #%d has been deleted (%d GoRoutines)`, userID, runtime.NumGoroutine())
  375. }()
  376. }
  377. // Users returns all users.
  378. func (s *Storage) Users() (model.Users, error) {
  379. query := `
  380. SELECT
  381. id,
  382. username,
  383. is_admin,
  384. theme,
  385. language,
  386. timezone,
  387. entry_direction,
  388. entries_per_page,
  389. keyboard_shortcuts,
  390. show_reading_time,
  391. entry_swipe,
  392. last_login_at,
  393. stylesheet,
  394. google_id,
  395. openid_connect_id
  396. FROM
  397. users
  398. ORDER BY username ASC
  399. `
  400. rows, err := s.db.Query(query)
  401. if err != nil {
  402. return nil, fmt.Errorf(`store: unable to fetch users: %v`, err)
  403. }
  404. defer rows.Close()
  405. var users model.Users
  406. for rows.Next() {
  407. user := model.NewUser()
  408. err := rows.Scan(
  409. &user.ID,
  410. &user.Username,
  411. &user.IsAdmin,
  412. &user.Theme,
  413. &user.Language,
  414. &user.Timezone,
  415. &user.EntryDirection,
  416. &user.EntriesPerPage,
  417. &user.KeyboardShortcuts,
  418. &user.ShowReadingTime,
  419. &user.EntrySwipe,
  420. &user.LastLoginAt,
  421. &user.Stylesheet,
  422. &user.GoogleID,
  423. &user.OpenIDConnectID,
  424. )
  425. if err != nil {
  426. return nil, fmt.Errorf(`store: unable to fetch users row: %v`, err)
  427. }
  428. users = append(users, user)
  429. }
  430. return users, nil
  431. }
  432. // CheckPassword validate the hashed password.
  433. func (s *Storage) CheckPassword(username, password string) error {
  434. var hash string
  435. username = strings.ToLower(username)
  436. err := s.db.QueryRow("SELECT password FROM users WHERE username=$1", username).Scan(&hash)
  437. if err == sql.ErrNoRows {
  438. return fmt.Errorf(`store: unable to find this user: %s`, username)
  439. } else if err != nil {
  440. return fmt.Errorf(`store: unable to fetch user: %v`, err)
  441. }
  442. if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)); err != nil {
  443. return fmt.Errorf(`store: invalid password for "%s" (%v)`, username, err)
  444. }
  445. return nil
  446. }
  447. // HasPassword returns true if the given user has a password defined.
  448. func (s *Storage) HasPassword(userID int64) (bool, error) {
  449. var result bool
  450. query := `SELECT true FROM users WHERE id=$1 AND password <> ''`
  451. err := s.db.QueryRow(query, userID).Scan(&result)
  452. if err == sql.ErrNoRows {
  453. return false, nil
  454. } else if err != nil {
  455. return false, fmt.Errorf(`store: unable to execute query: %v`, err)
  456. }
  457. if result {
  458. return true, nil
  459. }
  460. return false, nil
  461. }
  462. func hashPassword(password string) (string, error) {
  463. bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
  464. return string(bytes), err
  465. }
  466. func deleteUserFeeds(db *sql.DB, userID int64) {
  467. query := `SELECT id FROM feeds WHERE user_id=$1`
  468. rows, err := db.Query(query, userID)
  469. if err != nil {
  470. logger.Error(`store: unable to get user feeds: %v`, err)
  471. return
  472. }
  473. defer rows.Close()
  474. var feedIDs []int64
  475. for rows.Next() {
  476. var feedID int64
  477. rows.Scan(&feedID)
  478. feedIDs = append(feedIDs, feedID)
  479. }
  480. worker := func(jobs <-chan int64, results chan<- bool) {
  481. for feedID := range jobs {
  482. logger.Debug(`[MASS DELETE] Deleting feed #%d for user #%d (%d GoRoutines)`, feedID, userID, runtime.NumGoroutine())
  483. deleteUserEntries(db, userID, feedID)
  484. db.Exec(`DELETE FROM feeds WHERE id=$1`, feedID)
  485. results <- true
  486. }
  487. }
  488. numJobs := len(feedIDs)
  489. jobs := make(chan int64, numJobs)
  490. results := make(chan bool, numJobs)
  491. for w := 0; w < 2; w++ {
  492. go worker(jobs, results)
  493. }
  494. for j := 0; j < numJobs; j++ {
  495. jobs <- feedIDs[j]
  496. }
  497. close(jobs)
  498. for a := 1; a <= numJobs; a++ {
  499. <-results
  500. }
  501. }
  502. func deleteUserEntries(db *sql.DB, userID int64, feedID int64) {
  503. rows, err := db.Query(`SELECT id FROM entries WHERE user_id=$1 AND feed_id=$2`, userID, feedID)
  504. if err != nil {
  505. logger.Error(`store: unable to get user feed entries: %v`, err)
  506. return
  507. }
  508. defer rows.Close()
  509. for rows.Next() {
  510. var entryID int64
  511. rows.Scan(&entryID)
  512. logger.Debug(`[MASS DELETE] Deleting entry #%d for user #%d (%d GoRoutines)`, entryID, userID, runtime.NumGoroutine())
  513. db.Exec(`DELETE FROM enclosures WHERE entry_id=$1 AND user_id=$2`, entryID, userID)
  514. db.Exec(`DELETE FROM entries WHERE id=$1 AND user_id=$2`, entryID, userID)
  515. }
  516. }