user.go 13 KB

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