8 _ "github.com/mattn/go-sqlite3"
11 // UserDB is an implementation of UserStore backed by a SQLite3 database.
12 // Users are fully authenticated after confirming ownership of their
13 // email address by supplying a matching ticket to a generated ticket file.
19 // OpenUserDB opens the named user database file and ticket directory.
20 func OpenUserDB(name, dir string) (*UserDB, error) {
21 db, err := sql.Open("sqlite3", name)
25 return &UserDB{db, dir}, db.Ping()
28 func (db *UserDB) Lookup(name string) (User, error) {
30 row := db.QueryRow("SELECT username, password FROM users WHERE username = ?", name)
31 if err := row.Scan(&u.name, &u.password); err != nil {
32 if errors.Is(err, sql.ErrNoRows) {
33 return User{}, fmt.Errorf("lookup %s: %w", name, ErrUnknownUser)
35 return User{}, fmt.Errorf("lookup %s: %w", name, err)
40 func (db *UserDB) add(username string, pw Password) error {
41 _, err := db.Exec("INSERT INTO users (username, password) VALUES (?, ?)", username, pw)
43 return fmt.Errorf("add %s: %w", username, err)
45 if err := createTicket(db.TicketDir, username, pw); err != nil {
46 return fmt.Errorf("add %s: create ticket: %w", username, err)
51 func (db *UserDB) Change(name string, new Password) error {
52 _, err := db.Lookup(name)
53 if errors.Is(err, ErrUnknownUser) {
54 return db.add(name, new)
57 return fmt.Errorf("change %s: %w", name, err)
60 _, err = db.Exec("UPDATE users SET password = ? WHERE username = ?", new, name)
62 return fmt.Errorf("change %s: %w", name, err)
67 func (db *UserDB) Delete(name string) error {
68 _, err := db.Lookup(name)
70 return fmt.Errorf("delete %s: %w", name, err)
72 _, err = db.Exec("DELETE FROM users WHERE username = ?", name)
74 return fmt.Errorf("delete %s: %w", name, err)