auth_sqlite.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. package auth
  2. import (
  3. "database/sql"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. _ "github.com/mattn/go-sqlite3" // SQLite driver
  8. "golang.org/x/crypto/bcrypt"
  9. "heckel.io/ntfy/util"
  10. "strings"
  11. )
  12. const (
  13. tokenLength = 32
  14. bcryptCost = 10
  15. intentionalSlowDownHash = "$2a$10$YFCQvqQDwIIwnJM1xkAYOeih0dg17UVGanaTStnrSzC8NCWxcLDwy" // Cost should match bcryptCost
  16. )
  17. // Manager-related queries
  18. const (
  19. createAuthTablesQueries = `
  20. BEGIN;
  21. CREATE TABLE IF NOT EXISTS plan (
  22. id INT NOT NULL,
  23. code TEXT NOT NULL,
  24. messages_limit INT NOT NULL,
  25. emails_limit INT NOT NULL,
  26. attachment_file_size_limit INT NOT NULL,
  27. attachment_total_size_limit INT NOT NULL,
  28. PRIMARY KEY (id)
  29. );
  30. CREATE TABLE IF NOT EXISTS user (
  31. id INTEGER PRIMARY KEY AUTOINCREMENT,
  32. plan_id INT,
  33. user TEXT NOT NULL,
  34. pass TEXT NOT NULL,
  35. role TEXT NOT NULL,
  36. settings JSON,
  37. FOREIGN KEY (plan_id) REFERENCES plan (id)
  38. );
  39. CREATE UNIQUE INDEX idx_user ON user (user);
  40. CREATE TABLE IF NOT EXISTS user_access (
  41. user_id INT NOT NULL,
  42. topic TEXT NOT NULL,
  43. read INT NOT NULL,
  44. write INT NOT NULL,
  45. PRIMARY KEY (user_id, topic),
  46. FOREIGN KEY (user_id) REFERENCES user (id)
  47. );
  48. CREATE TABLE IF NOT EXISTS user_token (
  49. user_id INT NOT NULL,
  50. token TEXT NOT NULL,
  51. expires INT NOT NULL,
  52. PRIMARY KEY (user_id, token)
  53. );
  54. CREATE TABLE IF NOT EXISTS schemaVersion (
  55. id INT PRIMARY KEY,
  56. version INT NOT NULL
  57. );
  58. INSERT INTO user (id, user, pass, role) VALUES (1, '*', '', 'anonymous') ON CONFLICT (id) DO NOTHING;
  59. COMMIT;
  60. `
  61. selectUserByNameQuery = `
  62. SELECT u.user, u.pass, u.role, u.settings, p.code, p.messages_limit, p.emails_limit, p.attachment_file_size_limit, p.attachment_total_size_limit
  63. FROM user u
  64. LEFT JOIN plan p on p.id = u.plan_id
  65. WHERE user = ?
  66. `
  67. selectUserByTokenQuery = `
  68. SELECT u.user, u.pass, u.role, u.settings, p.code, p.messages_limit, p.emails_limit, p.attachment_file_size_limit, p.attachment_total_size_limit
  69. FROM user u
  70. JOIN user_token t on u.id = t.user_id
  71. LEFT JOIN plan p on p.id = u.plan_id
  72. WHERE t.token = ?
  73. `
  74. selectTopicPermsQuery = `
  75. SELECT read, write
  76. FROM user_access
  77. JOIN user ON user.user = '*' OR user.user = ?
  78. WHERE ? LIKE user_access.topic
  79. ORDER BY user.user DESC
  80. `
  81. )
  82. // Manager-related queries
  83. const (
  84. insertUserQuery = `INSERT INTO user (user, pass, role) VALUES (?, ?, ?)`
  85. selectUsernamesQuery = `SELECT user FROM user ORDER BY role, user`
  86. updateUserPassQuery = `UPDATE user SET pass = ? WHERE user = ?`
  87. updateUserRoleQuery = `UPDATE user SET role = ? WHERE user = ?`
  88. updateUserSettingsQuery = `UPDATE user SET settings = ? WHERE user = ?`
  89. deleteUserQuery = `DELETE FROM user WHERE user = ?`
  90. upsertUserAccessQuery = `INSERT INTO user_access (user_id, topic, read, write) VALUES ((SELECT id FROM user WHERE user = ?), ?, ?, ?)`
  91. selectUserAccessQuery = `SELECT topic, read, write FROM user_access WHERE user_id = (SELECT id FROM user WHERE user = ?)`
  92. deleteAllAccessQuery = `DELETE FROM user_access`
  93. deleteUserAccessQuery = `DELETE FROM user_access WHERE user_id = (SELECT id FROM user WHERE user = ?)`
  94. deleteTopicAccessQuery = `DELETE FROM user_access WHERE user_id = (SELECT id FROM user WHERE user = ?) AND topic = ?`
  95. insertTokenQuery = `INSERT INTO user_token (user_id, token, expires) VALUES ((SELECT id FROM user WHERE user = ?), ?, ?)`
  96. deleteTokenQuery = `DELETE FROM user_token WHERE user_id = (SELECT id FROM user WHERE user = ?) AND token = ?`
  97. deleteUserTokenQuery = `DELETE FROM user_token WHERE user_id = (SELECT id FROM user WHERE user = ?)`
  98. )
  99. // Schema management queries
  100. const (
  101. currentSchemaVersion = 1
  102. insertSchemaVersion = `INSERT INTO schemaVersion VALUES (1, ?)`
  103. selectSchemaVersionQuery = `SELECT version FROM schemaVersion WHERE id = 1`
  104. )
  105. // SQLiteAuthManager is an implementation of Manager and Manager. It stores users and access control list
  106. // in a SQLite database.
  107. type SQLiteAuthManager struct {
  108. db *sql.DB
  109. defaultRead bool
  110. defaultWrite bool
  111. }
  112. var _ Manager = (*SQLiteAuthManager)(nil)
  113. // NewSQLiteAuthManager creates a new SQLiteAuthManager instance
  114. func NewSQLiteAuthManager(filename string, defaultRead, defaultWrite bool) (*SQLiteAuthManager, error) {
  115. db, err := sql.Open("sqlite3", filename)
  116. if err != nil {
  117. return nil, err
  118. }
  119. if err := setupAuthDB(db); err != nil {
  120. return nil, err
  121. }
  122. return &SQLiteAuthManager{
  123. db: db,
  124. defaultRead: defaultRead,
  125. defaultWrite: defaultWrite,
  126. }, nil
  127. }
  128. // Authenticate checks username and password and returns a user if correct. The method
  129. // returns in constant-ish time, regardless of whether the user exists or the password is
  130. // correct or incorrect.
  131. func (a *SQLiteAuthManager) Authenticate(username, password string) (*User, error) {
  132. if username == Everyone {
  133. return nil, ErrUnauthenticated
  134. }
  135. user, err := a.User(username)
  136. if err != nil {
  137. bcrypt.CompareHashAndPassword([]byte(intentionalSlowDownHash),
  138. []byte("intentional slow-down to avoid timing attacks"))
  139. return nil, ErrUnauthenticated
  140. }
  141. if err := bcrypt.CompareHashAndPassword([]byte(user.Hash), []byte(password)); err != nil {
  142. return nil, ErrUnauthenticated
  143. }
  144. return user, nil
  145. }
  146. func (a *SQLiteAuthManager) AuthenticateToken(token string) (*User, error) {
  147. user, err := a.userByToken(token)
  148. if err != nil {
  149. return nil, ErrUnauthenticated
  150. }
  151. user.Token = token
  152. return user, nil
  153. }
  154. func (a *SQLiteAuthManager) CreateToken(user *User) (string, error) {
  155. token := util.RandomString(tokenLength)
  156. expires := 1 // FIXME
  157. if _, err := a.db.Exec(insertTokenQuery, user.Name, token, expires); err != nil {
  158. return "", err
  159. }
  160. return token, nil
  161. }
  162. func (a *SQLiteAuthManager) RemoveToken(user *User) error {
  163. if user.Token == "" {
  164. return ErrUnauthorized
  165. }
  166. if _, err := a.db.Exec(deleteTokenQuery, user.Name, user.Token); err != nil {
  167. return err
  168. }
  169. return nil
  170. }
  171. func (a *SQLiteAuthManager) ChangeSettings(user *User) error {
  172. settings, err := json.Marshal(user.Prefs)
  173. if err != nil {
  174. return err
  175. }
  176. if _, err := a.db.Exec(updateUserSettingsQuery, string(settings), user.Name); err != nil {
  177. return err
  178. }
  179. return nil
  180. }
  181. // Authorize returns nil if the given user has access to the given topic using the desired
  182. // permission. The user param may be nil to signal an anonymous user.
  183. func (a *SQLiteAuthManager) Authorize(user *User, topic string, perm Permission) error {
  184. if user != nil && user.Role == RoleAdmin {
  185. return nil // Admin can do everything
  186. }
  187. username := Everyone
  188. if user != nil {
  189. username = user.Name
  190. }
  191. // Select the read/write permissions for this user/topic combo. The query may return two
  192. // rows (one for everyone, and one for the user), but prioritizes the user. The value for
  193. // user.Name may be empty (= everyone).
  194. rows, err := a.db.Query(selectTopicPermsQuery, username, topic)
  195. if err != nil {
  196. return err
  197. }
  198. defer rows.Close()
  199. if !rows.Next() {
  200. return a.resolvePerms(a.defaultRead, a.defaultWrite, perm)
  201. }
  202. var read, write bool
  203. if err := rows.Scan(&read, &write); err != nil {
  204. return err
  205. } else if err := rows.Err(); err != nil {
  206. return err
  207. }
  208. return a.resolvePerms(read, write, perm)
  209. }
  210. func (a *SQLiteAuthManager) resolvePerms(read, write bool, perm Permission) error {
  211. if perm == PermissionRead && read {
  212. return nil
  213. } else if perm == PermissionWrite && write {
  214. return nil
  215. }
  216. return ErrUnauthorized
  217. }
  218. // AddUser adds a user with the given username, password and role. The password should be hashed
  219. // before it is stored in a persistence layer.
  220. func (a *SQLiteAuthManager) AddUser(username, password string, role Role) error {
  221. if !AllowedUsername(username) || !AllowedRole(role) {
  222. return ErrInvalidArgument
  223. }
  224. hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost)
  225. if err != nil {
  226. return err
  227. }
  228. if _, err = a.db.Exec(insertUserQuery, username, hash, role); err != nil {
  229. return err
  230. }
  231. return nil
  232. }
  233. // RemoveUser deletes the user with the given username. The function returns nil on success, even
  234. // if the user did not exist in the first place.
  235. func (a *SQLiteAuthManager) RemoveUser(username string) error {
  236. if !AllowedUsername(username) {
  237. return ErrInvalidArgument
  238. }
  239. if _, err := a.db.Exec(deleteUserAccessQuery, username); err != nil {
  240. return err
  241. }
  242. if _, err := a.db.Exec(deleteUserTokenQuery, username); err != nil {
  243. return err
  244. }
  245. if _, err := a.db.Exec(deleteUserQuery, username); err != nil {
  246. return err
  247. }
  248. return nil
  249. }
  250. // Users returns a list of users. It always also returns the Everyone user ("*").
  251. func (a *SQLiteAuthManager) Users() ([]*User, error) {
  252. rows, err := a.db.Query(selectUsernamesQuery)
  253. if err != nil {
  254. return nil, err
  255. }
  256. defer rows.Close()
  257. usernames := make([]string, 0)
  258. for rows.Next() {
  259. var username string
  260. if err := rows.Scan(&username); err != nil {
  261. return nil, err
  262. } else if err := rows.Err(); err != nil {
  263. return nil, err
  264. }
  265. usernames = append(usernames, username)
  266. }
  267. rows.Close()
  268. users := make([]*User, 0)
  269. for _, username := range usernames {
  270. user, err := a.User(username)
  271. if err != nil {
  272. return nil, err
  273. }
  274. users = append(users, user)
  275. }
  276. everyone, err := a.everyoneUser()
  277. if err != nil {
  278. return nil, err
  279. }
  280. users = append(users, everyone)
  281. return users, nil
  282. }
  283. // User returns the user with the given username if it exists, or ErrNotFound otherwise.
  284. // You may also pass Everyone to retrieve the anonymous user and its Grant list.
  285. func (a *SQLiteAuthManager) User(username string) (*User, error) {
  286. if username == Everyone {
  287. return a.everyoneUser()
  288. }
  289. rows, err := a.db.Query(selectUserByNameQuery, username)
  290. if err != nil {
  291. return nil, err
  292. }
  293. return a.readUser(rows)
  294. }
  295. func (a *SQLiteAuthManager) userByToken(token string) (*User, error) {
  296. rows, err := a.db.Query(selectUserByTokenQuery, token)
  297. if err != nil {
  298. return nil, err
  299. }
  300. return a.readUser(rows)
  301. }
  302. func (a *SQLiteAuthManager) readUser(rows *sql.Rows) (*User, error) {
  303. defer rows.Close()
  304. var username, hash, role string
  305. var prefs, planCode sql.NullString
  306. var messagesLimit, emailsLimit, attachmentFileSizeLimit, attachmentTotalSizeLimit sql.NullInt64
  307. if !rows.Next() {
  308. return nil, ErrNotFound
  309. }
  310. if err := rows.Scan(&username, &hash, &role, &prefs, &planCode, &messagesLimit, &emailsLimit, &attachmentFileSizeLimit, &attachmentTotalSizeLimit); err != nil {
  311. return nil, err
  312. } else if err := rows.Err(); err != nil {
  313. return nil, err
  314. }
  315. grants, err := a.readGrants(username)
  316. if err != nil {
  317. return nil, err
  318. }
  319. user := &User{
  320. Name: username,
  321. Hash: hash,
  322. Role: Role(role),
  323. Grants: grants,
  324. }
  325. if prefs.Valid {
  326. user.Prefs = &UserPrefs{}
  327. if err := json.Unmarshal([]byte(prefs.String), user.Prefs); err != nil {
  328. return nil, err
  329. }
  330. }
  331. if planCode.Valid {
  332. user.Plan = &Plan{
  333. Code: planCode.String,
  334. Upgradable: true, // FIXME
  335. MessageLimit: messagesLimit.Int64,
  336. EmailsLimit: emailsLimit.Int64,
  337. AttachmentFileSizeLimit: attachmentFileSizeLimit.Int64,
  338. AttachmentTotalSizeLimit: attachmentTotalSizeLimit.Int64,
  339. }
  340. }
  341. return user, nil
  342. }
  343. func (a *SQLiteAuthManager) everyoneUser() (*User, error) {
  344. grants, err := a.readGrants(Everyone)
  345. if err != nil {
  346. return nil, err
  347. }
  348. return &User{
  349. Name: Everyone,
  350. Hash: "",
  351. Role: RoleAnonymous,
  352. Grants: grants,
  353. }, nil
  354. }
  355. func (a *SQLiteAuthManager) readGrants(username string) ([]Grant, error) {
  356. rows, err := a.db.Query(selectUserAccessQuery, username)
  357. if err != nil {
  358. return nil, err
  359. }
  360. defer rows.Close()
  361. grants := make([]Grant, 0)
  362. for rows.Next() {
  363. var topic string
  364. var read, write bool
  365. if err := rows.Scan(&topic, &read, &write); err != nil {
  366. return nil, err
  367. } else if err := rows.Err(); err != nil {
  368. return nil, err
  369. }
  370. grants = append(grants, Grant{
  371. TopicPattern: fromSQLWildcard(topic),
  372. AllowRead: read,
  373. AllowWrite: write,
  374. })
  375. }
  376. return grants, nil
  377. }
  378. // ChangePassword changes a user's password
  379. func (a *SQLiteAuthManager) ChangePassword(username, password string) error {
  380. hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost)
  381. if err != nil {
  382. return err
  383. }
  384. if _, err := a.db.Exec(updateUserPassQuery, hash, username); err != nil {
  385. return err
  386. }
  387. return nil
  388. }
  389. // ChangeRole changes a user's role. When a role is changed from RoleUser to RoleAdmin,
  390. // all existing access control entries (Grant) are removed, since they are no longer needed.
  391. func (a *SQLiteAuthManager) ChangeRole(username string, role Role) error {
  392. if !AllowedUsername(username) || !AllowedRole(role) {
  393. return ErrInvalidArgument
  394. }
  395. if _, err := a.db.Exec(updateUserRoleQuery, string(role), username); err != nil {
  396. return err
  397. }
  398. if role == RoleAdmin {
  399. if _, err := a.db.Exec(deleteUserAccessQuery, username); err != nil {
  400. return err
  401. }
  402. }
  403. return nil
  404. }
  405. // AllowAccess adds or updates an entry in th access control list for a specific user. It controls
  406. // read/write access to a topic. The parameter topicPattern may include wildcards (*).
  407. func (a *SQLiteAuthManager) AllowAccess(username string, topicPattern string, read bool, write bool) error {
  408. if (!AllowedUsername(username) && username != Everyone) || !AllowedTopicPattern(topicPattern) {
  409. return ErrInvalidArgument
  410. }
  411. if _, err := a.db.Exec(upsertUserAccessQuery, username, toSQLWildcard(topicPattern), read, write); err != nil {
  412. return err
  413. }
  414. return nil
  415. }
  416. // ResetAccess removes an access control list entry for a specific username/topic, or (if topic is
  417. // empty) for an entire user. The parameter topicPattern may include wildcards (*).
  418. func (a *SQLiteAuthManager) ResetAccess(username string, topicPattern string) error {
  419. if !AllowedUsername(username) && username != Everyone && username != "" {
  420. return ErrInvalidArgument
  421. } else if !AllowedTopicPattern(topicPattern) && topicPattern != "" {
  422. return ErrInvalidArgument
  423. }
  424. if username == "" && topicPattern == "" {
  425. _, err := a.db.Exec(deleteAllAccessQuery, username)
  426. return err
  427. } else if topicPattern == "" {
  428. _, err := a.db.Exec(deleteUserAccessQuery, username)
  429. return err
  430. }
  431. _, err := a.db.Exec(deleteTopicAccessQuery, username, toSQLWildcard(topicPattern))
  432. return err
  433. }
  434. // DefaultAccess returns the default read/write access if no access control entry matches
  435. func (a *SQLiteAuthManager) DefaultAccess() (read bool, write bool) {
  436. return a.defaultRead, a.defaultWrite
  437. }
  438. func toSQLWildcard(s string) string {
  439. return strings.ReplaceAll(s, "*", "%")
  440. }
  441. func fromSQLWildcard(s string) string {
  442. return strings.ReplaceAll(s, "%", "*")
  443. }
  444. func setupAuthDB(db *sql.DB) error {
  445. // If 'schemaVersion' table does not exist, this must be a new database
  446. rowsSV, err := db.Query(selectSchemaVersionQuery)
  447. if err != nil {
  448. return setupNewAuthDB(db)
  449. }
  450. defer rowsSV.Close()
  451. // If 'schemaVersion' table exists, read version and potentially upgrade
  452. schemaVersion := 0
  453. if !rowsSV.Next() {
  454. return errors.New("cannot determine schema version: database file may be corrupt")
  455. }
  456. if err := rowsSV.Scan(&schemaVersion); err != nil {
  457. return err
  458. }
  459. rowsSV.Close()
  460. // Do migrations
  461. if schemaVersion == currentSchemaVersion {
  462. return nil
  463. }
  464. return fmt.Errorf("unexpected schema version found: %d", schemaVersion)
  465. }
  466. func setupNewAuthDB(db *sql.DB) error {
  467. if _, err := db.Exec(createAuthTablesQueries); err != nil {
  468. return err
  469. }
  470. if _, err := db.Exec(insertSchemaVersion, currentSchemaVersion); err != nil {
  471. return err
  472. }
  473. return nil
  474. }