git.alexw.nyc home about git garden
    1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258
  259
  260
  261
  262
  263
  264
  265
  266
  267
  268
  269
  270
  271
  272
  273
  274
  275
  276
  277
  278
  279
  280
  281
  282
  283
  284
  285
  286
  287
  288
  289
  290
  291
  292
  293
  294
  295
  296
  297
  298
  299
  300
  301
package main

import (
	"crypto/rand"
	"database/sql"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"golang.org/x/crypto/bcrypt"
)

var DB *sql.DB

func initializeDB() {
	var err error
	DB, err = sql.Open("sqlite3", c.DBFile)
	if err != nil {
		log.Fatal(err)
	}
	createTablesIfDNE()
}

// returns nil if login OK, err otherwise
// log in with email or username
func checkLogin(name string, password string) (string, bool, error) {
	row := DB.QueryRow("SELECT username, password_hash, active, admin FROM user where username = $1 OR email = $1", name)
	var db_password []byte
	var username string
	var active bool
	var isAdmin bool
	err := row.Scan(&username, &db_password, &active, &isAdmin)
	if err != nil {
		if strings.Contains(err.Error(), "no rows") {
			return username, isAdmin, fmt.Errorf("Username or email '" + name + "' does not exist")
		} else {
			return username, isAdmin, err
		}
	}
	if db_password != nil && !active {
		return username, isAdmin, fmt.Errorf("Your account is not active yet. Pending admin approval")
	}
	if bcrypt.CompareHashAndPassword(db_password, []byte(password)) == nil {
		return username, isAdmin, nil
	} else {
		return username, isAdmin, fmt.Errorf("Invalid password")
	}
}

type File struct { // also folders
	Creator     string
	Name        string // includes folder
	UpdatedTime time.Time
	TimeAgo     string
	IsText      bool
	Children    []File
	Host        string
}

func fileFromPath(fullPath string) File {
	info, _ := os.Stat(fullPath)
	creatorFolder := getCreator(fullPath)
	updatedTime := info.ModTime()
	return File{
		Name:        getLocalPath(fullPath),
		Creator:     path.Base(creatorFolder),
		UpdatedTime: updatedTime,
		TimeAgo:     timeago(&updatedTime),
		Host:        c.Host,
	}

}

type User struct {
	Username      string
	Email         string
	Active        bool
	Admin         bool
	CreatedAt     int64 // timestamp
	Reference     string
	Domain        string
	DomainEnabled bool
}

// kinda hacky
func getActiveUserNames(all bool) ([]string, error) {
	var rows *sql.Rows
	var err error
	if all {
		rows, err = DB.Query(`SELECT username from user WHERE active is true`)
	} else {
		rows, err = DB.Query(`SELECT username from user WHERE active is true order by random() limit 10`)
	}
	if err != nil {
		return nil, err
	}
	var users []string
	for rows.Next() {
		var user string
		err = rows.Scan(&user)
		if err != nil {
			return nil, err
		}
		users = append(users, user)
	}

	return users, nil
}

var domains map[string]string

func refreshDomainMap() error {
	domains = make(map[string]string)
	rows, err := DB.Query(`SELECT domain, username from user WHERE domain != ""`)
	if err != nil {
		log.Println(err)
		return err
	}
	for rows.Next() {
		var domain string
		var username string
		err = rows.Scan(&domain, &username)
		if err != nil {
			return err
		}
		domains[domain] = username
	}
	return nil
}

func getUserByName(username string) (*User, error) {
	var user User
	row := DB.QueryRow(`SELECT username, email, active, admin, created_at, reference, domain, domain_enabled from user WHERE username = ?`, username)
	err := row.Scan(&user.Username, &user.Email, &user.Active, &user.Admin, &user.CreatedAt, &user.Reference, &user.Domain, &user.DomainEnabled)
	if err != nil {
		return nil, err
	}
	return &user, nil
}

func getUsers() ([]User, error) {
	rows, err := DB.Query(`SELECT username, email, active, admin, created_at, reference, domain from user ORDER BY created_at DESC`)
	if err != nil {
		return nil, err
	}
	var users []User
	for rows.Next() {
		var user User
		err = rows.Scan(&user.Username, &user.Email, &user.Active, &user.Admin, &user.CreatedAt, &user.Reference, &user.Domain)
		if err != nil {
			return nil, err
		}
		users = append(users, user)
	}
	return users, nil
}

// admin tells you whether to show hidden files etc
// make sure user is a clean string
func getUpdatedFiles(admin bool, user string) ([]*File, error) { // TODO cache this function
	result := []*File{}
	dir := c.FilesDirectory
	if user != "" {
		dir = path.Join(dir, user)
	}
	err := filepath.Walk(dir, func(thepath string, info os.FileInfo, err error) error {
		if err != nil {
			log.Printf("Failure accessing a path %q: %v\n", thepath, err)
			return err // think about
		}
		if info.Name() == "bl4kers" {
			// Lazy hack
			return filepath.SkipDir
		}
		if !admin && info.IsDir() && info.Name() == HiddenFolder {
			return filepath.SkipDir
		}
		// make this do what it should
		if !info.IsDir() && !(strings.HasPrefix(info.Name(), HiddenFolder) && !admin) {
			res := fileFromPath(thepath)
			result = append(result, &res)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	sort.Slice(result, func(i, j int) bool {
		return result[i].UpdatedTime.After(result[j].UpdatedTime)
	})
	// if many in a row, truncate
	if user == "" {
		newResult := []*File{}
		for _, f := range result {
			var already bool
			// slow hack
			for _, ff := range newResult {
				if ff.Creator == f.Creator {
					already = true
					break
				}
			}
			if !already {
				newResult = append(newResult, f)
			}
		}
		result = newResult
	}

	if len(result) > 50 {
		result = result[:50]
	}
	return result, nil
} // todo clean up paths

func getMyFilesRecursive(p string, creator string) ([]File, error) {
	result := []File{}
	files, err := ioutil.ReadDir(p)
	if err != nil {
		return nil, err
	}
	for _, file := range files {
		fullPath := path.Join(p, file.Name())
		f := fileFromPath(fullPath)
		f.IsText = isTextFile(fullPath)
		if file.IsDir() {
			f.Children, err = getMyFilesRecursive(path.Join(p, file.Name()), creator)
		}
		result = append(result, f)
	}
	return result, nil
}

func createTablesIfDNE() {
	_, err := DB.Exec(`CREATE TABLE user (
  id INTEGER PRIMARY KEY NOT NULL,
  username TEXT NOT NULL UNIQUE,
  email TEXT NOT NULL UNIQUE,
  password_hash TEXT NOT NULL,
  reference TEXT NOT NULL default "",
  active boolean NOT NULL DEFAULT false,
  admin boolean NOT NULL DEFAULT false,
  created_at INTEGER DEFAULT (strftime('%s', 'now')),
  domain TEXT NOT NULL default "",
  domain_enabled BOOLEAN NOT NULL DEFAULT false
);`)
	if err == nil {
		// on first creation, create admin user with pw admin
		hashedPassword, err := bcrypt.GenerateFromPassword([]byte("admin"), 8) // TODO handle error
		if err != nil {
			log.Fatal(err)
		}
		_, err = DB.Exec(`INSERT OR IGNORE INTO user (username, email, password_hash, admin) values ('admin', 'default@flounder.local', ?, true)`, hashedPassword)
		activateUser("admin")
		if err != nil {
			log.Fatal(err)
		}
	}

	_, err = DB.Exec(`CREATE TABLE IF NOT EXISTS cookie_key (
  value TEXT NOT NULL
);`)
	if err != nil {
		log.Fatal(err)
	}
}

// Generate a cryptographically secure key for the cookie store
func generateCookieKeyIfDNE() []byte {
	rows, err := DB.Query("SELECT value FROM cookie_key LIMIT 1")
	defer rows.Close()
	if err != nil {
		log.Fatal(err)
	}
	if rows.Next() {
		var cookie []byte
		err := rows.Scan(&cookie)
		if err != nil {
			log.Fatal(err)
		}
		return cookie
	} else {
		k := make([]byte, 32)
		_, err := io.ReadFull(rand.Reader, k)
		if err != nil {
			log.Fatal(err)
		}
		_, err = DB.Exec("insert into cookie_key values (?)", k)
		if err != nil {
			log.Fatal(err)
		}
		return k
	}
}