db.go (view raw)
1package main
2
3import (
4 "crypto/rand"
5 "database/sql"
6 "fmt"
7 "golang.org/x/crypto/bcrypt"
8 "io"
9 "io/ioutil"
10 "log"
11 "os"
12 "path"
13 "path/filepath"
14 "sort"
15 "strings"
16 "time"
17)
18
19var DB *sql.DB
20
21func initializeDB() {
22 var err error
23 DB, err = sql.Open("sqlite3", c.DBFile)
24 if err != nil {
25 log.Fatal(err)
26 }
27 createTablesIfDNE()
28}
29
30// returns nil if login OK, err otherwise
31// log in with email or username
32func checkLogin(name string, password string) (string, bool, error) {
33 row := DB.QueryRow("SELECT username, password_hash, active, admin FROM user where username = $1 OR email = $1", name)
34 var db_password []byte
35 var username string
36 var active bool
37 var isAdmin bool
38 err := row.Scan(&username, &db_password, &active, &isAdmin)
39 if err != nil {
40 if strings.Contains(err.Error(), "no rows") {
41 return username, isAdmin, fmt.Errorf("Username or email '" + name + "' does not exist")
42 } else {
43 return username, isAdmin, err
44 }
45 }
46 if db_password != nil && !active {
47 return username, isAdmin, fmt.Errorf("Your account is not active yet. Pending admin approval", c)
48 }
49 if bcrypt.CompareHashAndPassword(db_password, []byte(password)) == nil {
50 return username, isAdmin, nil
51 } else {
52 return username, isAdmin, fmt.Errorf("Invalid password")
53 }
54}
55
56func getAnalyticsDB() (*sql.DB, error) {
57 db, err := sql.Open("sqlite3", c.AnalyticsDBFile)
58 _, err = db.Exec(`CREATE TABLE IF NOT EXISTS log (
59 id INTEGER PRIMARY KEY NOT NULL,
60 timestamp TEXT NOT NULL,
61 protocol TEXT NOT NULL,
62 request_ip TEXT,
63 request_user TEXT,
64 status INTEGER,
65 destination_host TEXT,
66 path TEXT,
67 method TEXT,
68 referer TEXT
69);`)
70 return db, err
71}
72
73type File struct { // also folders
74 Creator string
75 Name string // includes folder
76 UpdatedTime time.Time
77 TimeAgo string
78 IsText bool
79 Children []File
80 Host string
81}
82
83func fileFromPath(fullPath string) File {
84 info, _ := os.Stat(fullPath)
85 creatorFolder := getCreator(fullPath)
86 isText := isTextFile(fullPath)
87 updatedTime := info.ModTime()
88 return File{
89 Name: getLocalPath(fullPath),
90 Creator: path.Base(creatorFolder),
91 UpdatedTime: updatedTime,
92 IsText: isText,
93 TimeAgo: timeago(&updatedTime),
94 Host: c.Host,
95 }
96
97}
98
99type User struct {
100 Username string
101 Email string
102 Active bool
103 Admin bool
104 CreatedAt int64 // timestamp
105 Reference string
106 Domain string
107 DomainEnabled bool
108}
109
110func getActiveUserNames() ([]string, error) {
111 rows, err := DB.Query(`SELECT username from user WHERE active is true order by username`)
112 if err != nil {
113 return nil, err
114 }
115 var users []string
116 for rows.Next() {
117 var user string
118 err = rows.Scan(&user)
119 if err != nil {
120 return nil, err
121 }
122 users = append(users, user)
123 }
124
125 return users, nil
126}
127
128var domains map[string]string
129
130func refreshDomainMap() error {
131 domains = make(map[string]string)
132 rows, err := DB.Query(`SELECT domain, username from user WHERE domain != ""`)
133 if err != nil {
134 log.Println(err)
135 return err
136 }
137 for rows.Next() {
138 var domain string
139 var username string
140 err = rows.Scan(&domain, &username)
141 if err != nil {
142 return err
143 }
144 domains[domain] = username
145 }
146 return nil
147}
148
149func getUserByName(username string) (*User, error) {
150 var user User
151 row := DB.QueryRow(`SELECT username, email, active, admin, created_at, reference, domain, domain_enabled from user WHERE username = ?`, username)
152 err := row.Scan(&user.Username, &user.Email, &user.Active, &user.Admin, &user.CreatedAt, &user.Reference, &user.Domain, &user.DomainEnabled)
153 if err != nil {
154 return nil, err
155 }
156 return &user, nil
157}
158
159func getUsers() ([]User, error) {
160 rows, err := DB.Query(`SELECT username, email, active, admin, created_at, reference, domain from user ORDER BY created_at DESC`)
161 if err != nil {
162 return nil, err
163 }
164 var users []User
165 for rows.Next() {
166 var user User
167 err = rows.Scan(&user.Username, &user.Email, &user.Active, &user.Admin, &user.CreatedAt, &user.Reference, &user.Domain)
168 if err != nil {
169 return nil, err
170 }
171 users = append(users, user)
172 }
173 return users, nil
174}
175
176func getIndexFiles(admin bool) ([]*File, error) { // cache this function
177 result := []*File{}
178 err := filepath.Walk(c.FilesDirectory, func(thepath string, info os.FileInfo, err error) error {
179 if err != nil {
180 log.Printf("Failure accessing a path %q: %v\n", thepath, err)
181 return err // think about
182 }
183 if !admin && info.IsDir() && info.Name() == HiddenFolder {
184 return filepath.SkipDir
185 }
186 // make this do what it should
187 if !info.IsDir() {
188 res := fileFromPath(thepath)
189 result = append(result, &res)
190 }
191 return nil
192 })
193 if err != nil {
194 return nil, err
195 }
196 sort.Slice(result, func(i, j int) bool {
197 return result[i].UpdatedTime.After(result[j].UpdatedTime)
198 })
199 if len(result) > 50 {
200 result = result[:50]
201 }
202 return result, nil
203} // todo clean up paths
204
205func getMyFilesRecursive(p string, creator string) ([]File, error) {
206 result := []File{}
207 files, err := ioutil.ReadDir(p)
208 if err != nil {
209 return nil, err
210 }
211 for _, file := range files {
212 fullPath := path.Join(p, file.Name())
213 f := fileFromPath(fullPath)
214 if file.IsDir() {
215 f.Children, err = getMyFilesRecursive(path.Join(p, file.Name()), creator)
216 }
217 result = append(result, f)
218 }
219 return result, nil
220}
221
222func createTablesIfDNE() {
223 _, err := DB.Exec(`CREATE TABLE user (
224 id INTEGER PRIMARY KEY NOT NULL,
225 username TEXT NOT NULL UNIQUE,
226 email TEXT NOT NULL UNIQUE,
227 password_hash TEXT NOT NULL,
228 reference TEXT NOT NULL default "",
229 active boolean NOT NULL DEFAULT false,
230 admin boolean NOT NULL DEFAULT false,
231 created_at INTEGER DEFAULT (strftime('%s', 'now')),
232 domain TEXT NOT NULL default "",
233 domain_enabled BOOLEAN NOT NULL DEFAULT false
234);`)
235 if err == nil {
236 // on first creation, create admin user with pw admin
237 hashedPassword, err := bcrypt.GenerateFromPassword([]byte("admin"), 8) // TODO handle error
238 if err != nil {
239 log.Fatal(err)
240 }
241 _, err = DB.Exec(`INSERT OR IGNORE INTO user (username, email, password_hash, admin) values ('admin', 'default@flounder.local', ?, true)`, hashedPassword)
242 activateUser("admin")
243 if err != nil {
244 log.Fatal(err)
245 }
246 }
247
248 _, err = DB.Exec(`CREATE TABLE IF NOT EXISTS cookie_key (
249 value TEXT NOT NULL
250);`)
251 if err != nil {
252 log.Fatal(err)
253 }
254}
255
256// Generate a cryptographically secure key for the cookie store
257func generateCookieKeyIfDNE() []byte {
258 rows, err := DB.Query("SELECT value FROM cookie_key LIMIT 1")
259 defer rows.Close()
260 if err != nil {
261 log.Fatal(err)
262 }
263 if rows.Next() {
264 var cookie []byte
265 err := rows.Scan(&cookie)
266 if err != nil {
267 log.Fatal(err)
268 }
269 return cookie
270 } else {
271 k := make([]byte, 32)
272 _, err := io.ReadFull(rand.Reader, k)
273 if err != nil {
274 log.Fatal(err)
275 }
276 _, err = DB.Exec("insert into cookie_key values (?)", k)
277 if err != nil {
278 log.Fatal(err)
279 }
280 return k
281 }
282}