auth_sqlite.go 14 KB

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