diff --git a/server/cmd/main.go b/server/cmd/main.go index 02b17cb..0cf0087 100644 --- a/server/cmd/main.go +++ b/server/cmd/main.go @@ -35,7 +35,7 @@ type iostreams struct { Out io.Writer } -type Opts struct { +type options struct { ConfigPath string IO iostreams Log func(string) @@ -53,7 +53,7 @@ func main() { Err: os.Stderr, Out: os.Stdout, } - opts := &Opts{ + opts := &options{ ConfigPath: *configFlag, Reset: *resetFlag, IO: io, @@ -75,7 +75,7 @@ func main() { type Teardown func() -func setupDB(opts *Opts) (Teardown, error) { +func setupDB(opts *options) (Teardown, error) { db, err := sql.Open("sqlite3", opts.Config.DBPath) fmt.Printf("DBG %#v\n", db) @@ -84,7 +84,7 @@ func setupDB(opts *Opts) (Teardown, error) { return func() { db.Close() }, err } -func _main(opts *Opts) error { +func _main(opts *options) error { cfg, err := parseConfig(opts.ConfigPath) if err != nil { fmt.Fprintf(os.Stderr, "could not read config file '%s'", opts.ConfigPath) @@ -115,7 +115,7 @@ func _main(opts *Opts) error { return nil } -func ensureSchema(opts Opts) error { +func ensureSchema(opts options) error { db := opts.DB if opts.Reset { @@ -150,7 +150,7 @@ func ensureSchema(opts Opts) error { return nil } -func handler(opts Opts, f http.HandlerFunc) http.HandlerFunc { +func handler(opts options, f http.HandlerFunc) http.HandlerFunc { // TODO make this more real return func(w http.ResponseWriter, req *http.Request) { opts.Log(req.URL.Path) @@ -188,24 +188,56 @@ func writeErrorResponse(w http.ResponseWriter, code int, resp BBJResponse) { // NB breaking: i'm not just returning 200 always but using http status codes -func setupAPI(opts Opts) { +type AuthInfo struct { + Username string + Hash string +} - http.HandleFunc("/instance_info", handler(opts, func(w http.ResponseWriter, req *http.Request) { +func getAuthInfo(opts options, req *http.Request) (ai AuthInfo, err error) { + ai.Username = req.Header.Get("User") + ai.Hash = req.Header.Get("Auth") + if ai.Username == "" { + err = errors.New("no User header set") + } - type instanceInfo struct { - InstanceName string `json:"instance_name"` - AllowAnon bool `json:"allow_anon"` - Admins []string + if ai.Username == "anon" { + if !opts.Config.AllowAnon { + err = errors.New("anonymous access disabled") } - writeResponse(w, BBJResponse{ - Data: instanceInfo{ - InstanceName: opts.Config.InstanceName, - AllowAnon: opts.Config.AllowAnon, - Admins: opts.Config.Admins, - }, - }) - })) + return + } + err = checkAuth(opts, ai) + + return +} + +func checkAuth(opts options, ai AuthInfo) error { + db := opts.DB + stmt, err := db.Prepare("select auth_hash from users where user_name = ?") + if err != nil { + return fmt.Errorf("db error: %w", err) + } + defer stmt.Close() + + opts.Logf("querying for %s", ai.Username) + + var authHash string + if err = stmt.QueryRow(ai.Username).Scan(&authHash); err != nil { + if strings.Contains(err.Error(), "no rows in result") { + return errors.New("no such user") + } + return fmt.Errorf("db error: %w", err) + } + + if authHash != ai.Hash { + return errors.New("bad credentials") + } + + return nil +} + +func setupAPI(opts options) { serverErr := func(w http.ResponseWriter, err error) { opts.Logf(err.Error()) writeErrorResponse(w, 500, BBJResponse{ @@ -228,6 +260,21 @@ func setupAPI(opts Opts) { }) } + http.HandleFunc("/instance_info", handler(opts, func(w http.ResponseWriter, req *http.Request) { + type instanceInfo struct { + InstanceName string `json:"instance_name"` + AllowAnon bool `json:"allow_anon"` + Admins []string + } + writeResponse(w, BBJResponse{ + Data: instanceInfo{ + InstanceName: opts.Config.InstanceName, + AllowAnon: opts.Config.AllowAnon, + Admins: opts.Config.Admins, + }, + }) + })) + http.HandleFunc("/user_register", handler(opts, func(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { badMethod(w) @@ -236,7 +283,7 @@ func setupAPI(opts Opts) { type AuthArgs struct { Username string `json:"user_name"` - AuthHash string `json:"auth_hash"` + Hash string `json:"auth_hash"` } var args AuthArgs @@ -245,44 +292,34 @@ func setupAPI(opts Opts) { return } - if args.AuthHash == "" || args.Username == "" { + if args.Hash == "" || args.Username == "" { invalidArgs(w) return } - db := opts.DB - stmt, err := db.Prepare("select auth_hash from users where user_name = ?") - if err != nil { - serverErr(w, err) - return - } - defer stmt.Close() - opts.Logf("querying for %s", args.Username) - var authHash string - err = stmt.QueryRow(args.Username).Scan(&authHash) - if err == nil { + if err := checkAuth(opts, AuthInfo{args.Username, args.Hash}); err == nil { opts.Logf("found %s", args.Username) // code 4 apparently writeErrorResponse(w, 403, BBJResponse{ Error: true, Data: "user already exists", }) - return - } else if err != nil && !strings.Contains(err.Error(), "no rows in result") { + } else if err.Error() != "no such user" { serverErr(w, err) return } - stmt, err = db.Prepare(`INSERT INTO users VALUES (?, ?, ?, "", "", 0, 0, ?)`) + db := opts.DB + stmt, err := db.Prepare(`INSERT INTO users VALUES (?, ?, ?, "", "", 0, 0, ?)`) id, err := uuid.NewRandom() if err != nil { serverErr(w, err) return } - _, err = stmt.Exec(id, args.Username, args.AuthHash, time.Now()) + _, err = stmt.Exec(id, args.Username, args.Hash, time.Now()) if err != nil { serverErr(w, err) } @@ -375,7 +412,29 @@ func setupAPI(opts Opts) { })) http.HandleFunc("/thread_create", handler(opts, func(w http.ResponseWriter, req *http.Request) { + if req.Method != "POST" { + badMethod(w) + return + } + + authInfo, err := getAuthInfo(opts, req) + if err != nil { + writeErrorResponse(w, 403, BBJResponse{ + Error: true, + Data: err.Error(), + }) + return + } + + fmt.Printf("DBG %#v\n", authInfo) + + type threadCreateArgs struct { + Title string + Body string + } + // TODO + writeResponse(w, BBJResponse{Data: "TODO"}) })) }