user.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package storage // import "miniflux.app/v2/internal/storage"
  4. import (
  5. "database/sql"
  6. "fmt"
  7. "log/slog"
  8. "runtime"
  9. "strings"
  10. "miniflux.app/v2/internal/crypto"
  11. "miniflux.app/v2/internal/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) LIMIT 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) LIMIT 1`, userID, username).Scan(&result)
  43. return result
  44. }
  45. // CreateUser creates a new user.
  46. func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*model.User, error) {
  47. var hashedPassword string
  48. if userCreationRequest.Password != "" {
  49. var err error
  50. hashedPassword, err = crypto.HashPassword(userCreationRequest.Password)
  51. if err != nil {
  52. return nil, err
  53. }
  54. }
  55. query := `
  56. INSERT INTO users
  57. (username, password, is_admin, google_id, openid_connect_id)
  58. VALUES
  59. (LOWER($1), $2, $3, $4, $5)
  60. RETURNING
  61. id,
  62. username,
  63. is_admin,
  64. language,
  65. theme,
  66. timezone,
  67. entry_direction,
  68. entries_per_page,
  69. keyboard_shortcuts,
  70. show_reading_time,
  71. entry_swipe,
  72. gesture_nav,
  73. stylesheet,
  74. custom_js,
  75. external_font_hosts,
  76. google_id,
  77. openid_connect_id,
  78. display_mode,
  79. entry_order,
  80. default_reading_speed,
  81. cjk_reading_speed,
  82. default_home_page,
  83. categories_sorting_order,
  84. mark_read_on_view,
  85. media_playback_rate,
  86. block_filter_entry_rules,
  87. keep_filter_entry_rules,
  88. always_open_external_links,
  89. open_external_links_in_new_tab
  90. `
  91. tx, err := s.db.Begin()
  92. if err != nil {
  93. return nil, fmt.Errorf(`store: unable to start transaction: %v`, err)
  94. }
  95. var user model.User
  96. err = tx.QueryRow(
  97. query,
  98. userCreationRequest.Username,
  99. hashedPassword,
  100. userCreationRequest.IsAdmin,
  101. userCreationRequest.GoogleID,
  102. userCreationRequest.OpenIDConnectID,
  103. ).Scan(
  104. &user.ID,
  105. &user.Username,
  106. &user.IsAdmin,
  107. &user.Language,
  108. &user.Theme,
  109. &user.Timezone,
  110. &user.EntryDirection,
  111. &user.EntriesPerPage,
  112. &user.KeyboardShortcuts,
  113. &user.ShowReadingTime,
  114. &user.EntrySwipe,
  115. &user.GestureNav,
  116. &user.Stylesheet,
  117. &user.CustomJS,
  118. &user.ExternalFontHosts,
  119. &user.GoogleID,
  120. &user.OpenIDConnectID,
  121. &user.DisplayMode,
  122. &user.EntryOrder,
  123. &user.DefaultReadingSpeed,
  124. &user.CJKReadingSpeed,
  125. &user.DefaultHomePage,
  126. &user.CategoriesSortingOrder,
  127. &user.MarkReadOnView,
  128. &user.MediaPlaybackRate,
  129. &user.BlockFilterEntryRules,
  130. &user.KeepFilterEntryRules,
  131. &user.AlwaysOpenExternalLinks,
  132. &user.OpenExternalLinksInNewTab,
  133. )
  134. if err != nil {
  135. tx.Rollback()
  136. return nil, fmt.Errorf(`store: unable to create user: %v`, err)
  137. }
  138. _, err = tx.Exec(`INSERT INTO categories (user_id, title) VALUES ($1, $2)`, user.ID, "All")
  139. if err != nil {
  140. tx.Rollback()
  141. return nil, fmt.Errorf(`store: unable to create user default category: %v`, err)
  142. }
  143. _, err = tx.Exec(`INSERT INTO integrations (user_id) VALUES ($1)`, user.ID)
  144. if err != nil {
  145. tx.Rollback()
  146. return nil, fmt.Errorf(`store: unable to create integration row: %v`, err)
  147. }
  148. if err := tx.Commit(); err != nil {
  149. return nil, fmt.Errorf(`store: unable to commit transaction: %v`, err)
  150. }
  151. return &user, nil
  152. }
  153. // UpdateUser updates a user.
  154. func (s *Storage) UpdateUser(user *model.User) error {
  155. user.ExternalFontHosts = strings.TrimSpace(user.ExternalFontHosts)
  156. if user.Password != "" {
  157. hashedPassword, err := crypto.HashPassword(user.Password)
  158. if err != nil {
  159. return err
  160. }
  161. query := `
  162. UPDATE users SET
  163. username=LOWER($1),
  164. password=$2,
  165. is_admin=$3,
  166. theme=$4,
  167. language=$5,
  168. timezone=$6,
  169. entry_direction=$7,
  170. entries_per_page=$8,
  171. keyboard_shortcuts=$9,
  172. show_reading_time=$10,
  173. entry_swipe=$11,
  174. gesture_nav=$12,
  175. stylesheet=$13,
  176. custom_js=$14,
  177. external_font_hosts=$15,
  178. google_id=$16,
  179. openid_connect_id=$17,
  180. display_mode=$18,
  181. entry_order=$19,
  182. default_reading_speed=$20,
  183. cjk_reading_speed=$21,
  184. default_home_page=$22,
  185. categories_sorting_order=$23,
  186. mark_read_on_view=$24,
  187. mark_read_on_media_player_completion=$25,
  188. media_playback_rate=$26,
  189. block_filter_entry_rules=$27,
  190. keep_filter_entry_rules=$28,
  191. always_open_external_links=$29,
  192. open_external_links_in_new_tab=$30
  193. WHERE
  194. id=$31
  195. `
  196. _, err = s.db.Exec(
  197. query,
  198. user.Username,
  199. hashedPassword,
  200. user.IsAdmin,
  201. user.Theme,
  202. user.Language,
  203. user.Timezone,
  204. user.EntryDirection,
  205. user.EntriesPerPage,
  206. user.KeyboardShortcuts,
  207. user.ShowReadingTime,
  208. user.EntrySwipe,
  209. user.GestureNav,
  210. user.Stylesheet,
  211. user.CustomJS,
  212. user.ExternalFontHosts,
  213. user.GoogleID,
  214. user.OpenIDConnectID,
  215. user.DisplayMode,
  216. user.EntryOrder,
  217. user.DefaultReadingSpeed,
  218. user.CJKReadingSpeed,
  219. user.DefaultHomePage,
  220. user.CategoriesSortingOrder,
  221. user.MarkReadOnView,
  222. user.MarkReadOnMediaPlayerCompletion,
  223. user.MediaPlaybackRate,
  224. user.BlockFilterEntryRules,
  225. user.KeepFilterEntryRules,
  226. user.AlwaysOpenExternalLinks,
  227. user.OpenExternalLinksInNewTab,
  228. user.ID,
  229. )
  230. if err != nil {
  231. return fmt.Errorf(`store: unable to update user: %v`, err)
  232. }
  233. } else {
  234. query := `
  235. UPDATE users SET
  236. username=LOWER($1),
  237. is_admin=$2,
  238. theme=$3,
  239. language=$4,
  240. timezone=$5,
  241. entry_direction=$6,
  242. entries_per_page=$7,
  243. keyboard_shortcuts=$8,
  244. show_reading_time=$9,
  245. entry_swipe=$10,
  246. gesture_nav=$11,
  247. stylesheet=$12,
  248. custom_js=$13,
  249. external_font_hosts=$14,
  250. google_id=$15,
  251. openid_connect_id=$16,
  252. display_mode=$17,
  253. entry_order=$18,
  254. default_reading_speed=$19,
  255. cjk_reading_speed=$20,
  256. default_home_page=$21,
  257. categories_sorting_order=$22,
  258. mark_read_on_view=$23,
  259. mark_read_on_media_player_completion=$24,
  260. media_playback_rate=$25,
  261. block_filter_entry_rules=$26,
  262. keep_filter_entry_rules=$27,
  263. always_open_external_links=$28,
  264. open_external_links_in_new_tab=$29
  265. WHERE
  266. id=$30
  267. `
  268. _, err := s.db.Exec(
  269. query,
  270. user.Username,
  271. user.IsAdmin,
  272. user.Theme,
  273. user.Language,
  274. user.Timezone,
  275. user.EntryDirection,
  276. user.EntriesPerPage,
  277. user.KeyboardShortcuts,
  278. user.ShowReadingTime,
  279. user.EntrySwipe,
  280. user.GestureNav,
  281. user.Stylesheet,
  282. user.CustomJS,
  283. user.ExternalFontHosts,
  284. user.GoogleID,
  285. user.OpenIDConnectID,
  286. user.DisplayMode,
  287. user.EntryOrder,
  288. user.DefaultReadingSpeed,
  289. user.CJKReadingSpeed,
  290. user.DefaultHomePage,
  291. user.CategoriesSortingOrder,
  292. user.MarkReadOnView,
  293. user.MarkReadOnMediaPlayerCompletion,
  294. user.MediaPlaybackRate,
  295. user.BlockFilterEntryRules,
  296. user.KeepFilterEntryRules,
  297. user.AlwaysOpenExternalLinks,
  298. user.OpenExternalLinksInNewTab,
  299. user.ID,
  300. )
  301. if err != nil {
  302. return fmt.Errorf(`store: unable to update user: %v`, err)
  303. }
  304. }
  305. return nil
  306. }
  307. // UserLanguage returns the language of the given user.
  308. func (s *Storage) UserLanguage(userID int64) (language string) {
  309. err := s.db.QueryRow(`SELECT language FROM users WHERE id = $1`, userID).Scan(&language)
  310. if err != nil {
  311. return "en_US"
  312. }
  313. return language
  314. }
  315. // UserByID finds a user by the ID.
  316. func (s *Storage) UserByID(userID int64) (*model.User, error) {
  317. query := `
  318. SELECT
  319. id,
  320. username,
  321. is_admin,
  322. theme,
  323. language,
  324. timezone,
  325. entry_direction,
  326. entries_per_page,
  327. keyboard_shortcuts,
  328. show_reading_time,
  329. entry_swipe,
  330. gesture_nav,
  331. last_login_at,
  332. stylesheet,
  333. custom_js,
  334. external_font_hosts,
  335. google_id,
  336. openid_connect_id,
  337. display_mode,
  338. entry_order,
  339. default_reading_speed,
  340. cjk_reading_speed,
  341. default_home_page,
  342. categories_sorting_order,
  343. mark_read_on_view,
  344. mark_read_on_media_player_completion,
  345. media_playback_rate,
  346. block_filter_entry_rules,
  347. keep_filter_entry_rules,
  348. always_open_external_links,
  349. open_external_links_in_new_tab
  350. FROM
  351. users
  352. WHERE
  353. id = $1
  354. `
  355. return s.fetchUser(query, userID)
  356. }
  357. // UserByUsername finds a user by the username.
  358. func (s *Storage) UserByUsername(username string) (*model.User, error) {
  359. query := `
  360. SELECT
  361. id,
  362. username,
  363. is_admin,
  364. theme,
  365. language,
  366. timezone,
  367. entry_direction,
  368. entries_per_page,
  369. keyboard_shortcuts,
  370. show_reading_time,
  371. entry_swipe,
  372. gesture_nav,
  373. last_login_at,
  374. stylesheet,
  375. custom_js,
  376. external_font_hosts,
  377. google_id,
  378. openid_connect_id,
  379. display_mode,
  380. entry_order,
  381. default_reading_speed,
  382. cjk_reading_speed,
  383. default_home_page,
  384. categories_sorting_order,
  385. mark_read_on_view,
  386. mark_read_on_media_player_completion,
  387. media_playback_rate,
  388. block_filter_entry_rules,
  389. keep_filter_entry_rules,
  390. always_open_external_links,
  391. open_external_links_in_new_tab
  392. FROM
  393. users
  394. WHERE
  395. username=LOWER($1)
  396. `
  397. return s.fetchUser(query, username)
  398. }
  399. // UserByField finds a user by a field value.
  400. func (s *Storage) UserByField(field, value string) (*model.User, error) {
  401. query := `
  402. SELECT
  403. id,
  404. username,
  405. is_admin,
  406. theme,
  407. language,
  408. timezone,
  409. entry_direction,
  410. entries_per_page,
  411. keyboard_shortcuts,
  412. show_reading_time,
  413. entry_swipe,
  414. gesture_nav,
  415. last_login_at,
  416. stylesheet,
  417. custom_js,
  418. external_font_hosts,
  419. google_id,
  420. openid_connect_id,
  421. display_mode,
  422. entry_order,
  423. default_reading_speed,
  424. cjk_reading_speed,
  425. default_home_page,
  426. categories_sorting_order,
  427. mark_read_on_view,
  428. mark_read_on_media_player_completion,
  429. media_playback_rate,
  430. block_filter_entry_rules,
  431. keep_filter_entry_rules,
  432. always_open_external_links,
  433. open_external_links_in_new_tab
  434. FROM
  435. users
  436. WHERE
  437. %s=$1
  438. `
  439. return s.fetchUser(fmt.Sprintf(query, pq.QuoteIdentifier(field)), value)
  440. }
  441. // AnotherUserWithFieldExists returns true if a user has the value set for the given field.
  442. func (s *Storage) AnotherUserWithFieldExists(userID int64, field, value string) bool {
  443. var result bool
  444. s.db.QueryRow(fmt.Sprintf(`SELECT true FROM users WHERE id <> $1 AND %s=$2 LIMIT 1`, pq.QuoteIdentifier(field)), userID, value).Scan(&result)
  445. return result
  446. }
  447. // UserByAPIKey returns a User from an API Key.
  448. func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
  449. query := `
  450. SELECT
  451. u.id,
  452. u.username,
  453. u.is_admin,
  454. u.theme,
  455. u.language,
  456. u.timezone,
  457. u.entry_direction,
  458. u.entries_per_page,
  459. u.keyboard_shortcuts,
  460. u.show_reading_time,
  461. u.entry_swipe,
  462. u.gesture_nav,
  463. u.last_login_at,
  464. u.stylesheet,
  465. u.custom_js,
  466. u.external_font_hosts,
  467. u.google_id,
  468. u.openid_connect_id,
  469. u.display_mode,
  470. u.entry_order,
  471. u.default_reading_speed,
  472. u.cjk_reading_speed,
  473. u.default_home_page,
  474. u.categories_sorting_order,
  475. u.mark_read_on_view,
  476. u.mark_read_on_media_player_completion,
  477. media_playback_rate,
  478. u.block_filter_entry_rules,
  479. u.keep_filter_entry_rules,
  480. u.always_open_external_links,
  481. u.open_external_links_in_new_tab
  482. FROM
  483. users u
  484. LEFT JOIN
  485. api_keys ON api_keys.user_id=u.id
  486. WHERE
  487. api_keys.token = $1
  488. `
  489. return s.fetchUser(query, token)
  490. }
  491. func (s *Storage) fetchUser(query string, args ...any) (*model.User, error) {
  492. var user model.User
  493. err := s.db.QueryRow(query, args...).Scan(
  494. &user.ID,
  495. &user.Username,
  496. &user.IsAdmin,
  497. &user.Theme,
  498. &user.Language,
  499. &user.Timezone,
  500. &user.EntryDirection,
  501. &user.EntriesPerPage,
  502. &user.KeyboardShortcuts,
  503. &user.ShowReadingTime,
  504. &user.EntrySwipe,
  505. &user.GestureNav,
  506. &user.LastLoginAt,
  507. &user.Stylesheet,
  508. &user.CustomJS,
  509. &user.ExternalFontHosts,
  510. &user.GoogleID,
  511. &user.OpenIDConnectID,
  512. &user.DisplayMode,
  513. &user.EntryOrder,
  514. &user.DefaultReadingSpeed,
  515. &user.CJKReadingSpeed,
  516. &user.DefaultHomePage,
  517. &user.CategoriesSortingOrder,
  518. &user.MarkReadOnView,
  519. &user.MarkReadOnMediaPlayerCompletion,
  520. &user.MediaPlaybackRate,
  521. &user.BlockFilterEntryRules,
  522. &user.KeepFilterEntryRules,
  523. &user.AlwaysOpenExternalLinks,
  524. &user.OpenExternalLinksInNewTab,
  525. )
  526. if err == sql.ErrNoRows {
  527. return nil, nil
  528. } else if err != nil {
  529. return nil, fmt.Errorf(`store: unable to fetch user: %v`, err)
  530. }
  531. return &user, nil
  532. }
  533. // RemoveUser deletes a user.
  534. func (s *Storage) RemoveUser(userID int64) error {
  535. tx, err := s.db.Begin()
  536. if err != nil {
  537. return fmt.Errorf(`store: unable to start transaction: %v`, err)
  538. }
  539. if _, err := tx.Exec(`DELETE FROM users WHERE id=$1`, userID); err != nil {
  540. tx.Rollback()
  541. return fmt.Errorf(`store: unable to remove user #%d: %v`, userID, err)
  542. }
  543. if _, err := tx.Exec(`DELETE FROM integrations WHERE user_id=$1`, userID); err != nil {
  544. tx.Rollback()
  545. return fmt.Errorf(`store: unable to remove integration settings for user #%d: %v`, userID, err)
  546. }
  547. if err := tx.Commit(); err != nil {
  548. return fmt.Errorf(`store: unable to commit transaction: %v`, err)
  549. }
  550. return nil
  551. }
  552. // RemoveUserAsync deletes user data without locking the database.
  553. func (s *Storage) RemoveUserAsync(userID int64) {
  554. go func() {
  555. if err := s.deleteUserFeeds(userID); err != nil {
  556. slog.Error("Unable to delete user feeds",
  557. slog.Int64("user_id", userID),
  558. slog.Any("error", err),
  559. )
  560. return
  561. }
  562. s.db.Exec(`DELETE FROM users WHERE id=$1`, userID)
  563. s.db.Exec(`DELETE FROM integrations WHERE user_id=$1`, userID)
  564. slog.Debug("User deleted",
  565. slog.Int64("user_id", userID),
  566. slog.Int("goroutines", runtime.NumGoroutine()),
  567. )
  568. }()
  569. }
  570. func (s *Storage) deleteUserFeeds(userID int64) error {
  571. rows, err := s.db.Query(`SELECT id FROM feeds WHERE user_id=$1`, userID)
  572. if err != nil {
  573. return fmt.Errorf(`store: unable to get user feeds: %v`, err)
  574. }
  575. defer rows.Close()
  576. for rows.Next() {
  577. var feedID int64
  578. rows.Scan(&feedID)
  579. slog.Debug("Deleting feed",
  580. slog.Int64("user_id", userID),
  581. slog.Int64("feed_id", feedID),
  582. slog.Int("goroutines", runtime.NumGoroutine()),
  583. )
  584. if err := s.RemoveFeed(userID, feedID); err != nil {
  585. return err
  586. }
  587. }
  588. return nil
  589. }
  590. // Users returns all users.
  591. func (s *Storage) Users() (model.Users, error) {
  592. query := `
  593. SELECT
  594. id,
  595. username,
  596. is_admin,
  597. theme,
  598. language,
  599. timezone,
  600. entry_direction,
  601. entries_per_page,
  602. keyboard_shortcuts,
  603. show_reading_time,
  604. entry_swipe,
  605. gesture_nav,
  606. last_login_at,
  607. stylesheet,
  608. custom_js,
  609. external_font_hosts,
  610. google_id,
  611. openid_connect_id,
  612. display_mode,
  613. entry_order,
  614. default_reading_speed,
  615. cjk_reading_speed,
  616. default_home_page,
  617. categories_sorting_order,
  618. mark_read_on_view,
  619. mark_read_on_media_player_completion,
  620. media_playback_rate,
  621. block_filter_entry_rules,
  622. keep_filter_entry_rules,
  623. always_open_external_links,
  624. open_external_links_in_new_tab
  625. FROM
  626. users
  627. ORDER BY username ASC
  628. `
  629. rows, err := s.db.Query(query)
  630. if err != nil {
  631. return nil, fmt.Errorf(`store: unable to fetch users: %v`, err)
  632. }
  633. defer rows.Close()
  634. var users model.Users
  635. for rows.Next() {
  636. var user model.User
  637. err := rows.Scan(
  638. &user.ID,
  639. &user.Username,
  640. &user.IsAdmin,
  641. &user.Theme,
  642. &user.Language,
  643. &user.Timezone,
  644. &user.EntryDirection,
  645. &user.EntriesPerPage,
  646. &user.KeyboardShortcuts,
  647. &user.ShowReadingTime,
  648. &user.EntrySwipe,
  649. &user.GestureNav,
  650. &user.LastLoginAt,
  651. &user.Stylesheet,
  652. &user.CustomJS,
  653. &user.ExternalFontHosts,
  654. &user.GoogleID,
  655. &user.OpenIDConnectID,
  656. &user.DisplayMode,
  657. &user.EntryOrder,
  658. &user.DefaultReadingSpeed,
  659. &user.CJKReadingSpeed,
  660. &user.DefaultHomePage,
  661. &user.CategoriesSortingOrder,
  662. &user.MarkReadOnView,
  663. &user.MarkReadOnMediaPlayerCompletion,
  664. &user.MediaPlaybackRate,
  665. &user.BlockFilterEntryRules,
  666. &user.KeepFilterEntryRules,
  667. &user.AlwaysOpenExternalLinks,
  668. &user.OpenExternalLinksInNewTab,
  669. )
  670. if err != nil {
  671. return nil, fmt.Errorf(`store: unable to fetch users row: %v`, err)
  672. }
  673. users = append(users, &user)
  674. }
  675. return users, nil
  676. }
  677. // CheckPassword validate the hashed password.
  678. func (s *Storage) CheckPassword(username, password string) error {
  679. var hash string
  680. username = strings.ToLower(username)
  681. err := s.db.QueryRow("SELECT password FROM users WHERE username=$1", username).Scan(&hash)
  682. if err == sql.ErrNoRows {
  683. return fmt.Errorf(`store: unable to find this user: %s`, username)
  684. } else if err != nil {
  685. return fmt.Errorf(`store: unable to fetch user: %v`, err)
  686. }
  687. if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)); err != nil {
  688. return fmt.Errorf(`store: invalid password for "%s" (%v)`, username, err)
  689. }
  690. return nil
  691. }
  692. // HasPassword returns true if the given user has a password defined.
  693. func (s *Storage) HasPassword(userID int64) (bool, error) {
  694. var result bool
  695. query := `SELECT true FROM users WHERE id=$1 AND password <> '' LIMIT 1`
  696. err := s.db.QueryRow(query, userID).Scan(&result)
  697. if err == sql.ErrNoRows {
  698. return false, nil
  699. } else if err != nil {
  700. return false, fmt.Errorf(`store: unable to execute query: %v`, err)
  701. }
  702. if result {
  703. return true, nil
  704. }
  705. return false, nil
  706. }