trunk
vilmibm 2022-05-18 02:09:17 +00:00
parent 5498804032
commit 758acb3b4e
2 changed files with 124 additions and 33 deletions

View File

@ -161,8 +161,9 @@ func handler(opts options, f http.HandlerFunc) http.HandlerFunc {
// transport encryption and then, on the server side, proper salted hashing. // transport encryption and then, on the server side, proper salted hashing.
type User struct { type User struct {
// TODO ID string
ID string Username string
Hash string
} }
type BBJResponse struct { type BBJResponse struct {
@ -177,39 +178,55 @@ func writeResponse(w http.ResponseWriter, resp BBJResponse) {
json.NewEncoder(w).Encode(resp) json.NewEncoder(w).Encode(resp)
} }
// NB breaking: i'm not just returning 200 always but using http status codes
func writeErrorResponse(w http.ResponseWriter, code int, resp BBJResponse) { func writeErrorResponse(w http.ResponseWriter, code int, resp BBJResponse) {
w.WriteHeader(code) w.WriteHeader(code)
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp) json.NewEncoder(w).Encode(resp)
} }
// NB breaking: i'm not just returning 200 always but using http status codes func getUserFromReq(opts options, req *http.Request) (u User, err error) {
u.Username = req.Header.Get("User")
type AuthInfo struct { u.Hash = req.Header.Get("Auth")
Username string if u.Username == "" {
Hash string
}
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") err = errors.New("no User header set")
}
if ai.Username == "anon" {
if !opts.Config.AllowAnon {
err = errors.New("anonymous access disabled")
}
return return
} }
err = checkAuth(opts, ai) if u.Username == "anon" {
if !opts.Config.AllowAnon {
err = errors.New("anonymous access disabled")
return
}
}
db := opts.DB
stmt, err := db.Prepare("select auth_hash, id from users where user_name = ?")
if err != nil {
err = fmt.Errorf("db error: %w", err)
return
}
defer stmt.Close()
opts.Logf("querying for %s", u.Username)
var authHash string
if err = stmt.QueryRow(u.Username).Scan(&authHash, u.ID); err != nil {
if strings.Contains(err.Error(), "no rows in result") {
err = errors.New("no such user")
} else {
err = fmt.Errorf("db error: %w", err)
}
}
if authHash != u.Hash {
err = errors.New("bad credentials")
}
return return
} }
func checkAuth(opts options, ai AuthInfo) error { func checkAuth(opts options, username, hash string) error {
db := opts.DB db := opts.DB
stmt, err := db.Prepare("select auth_hash from users where user_name = ?") stmt, err := db.Prepare("select auth_hash from users where user_name = ?")
if err != nil { if err != nil {
@ -217,17 +234,17 @@ func checkAuth(opts options, ai AuthInfo) error {
} }
defer stmt.Close() defer stmt.Close()
opts.Logf("querying for %s", ai.Username) opts.Logf("querying for %s", username)
var authHash string var authHash string
if err = stmt.QueryRow(ai.Username).Scan(&authHash); err != nil { if err = stmt.QueryRow(username).Scan(&authHash); err != nil {
if strings.Contains(err.Error(), "no rows in result") { if strings.Contains(err.Error(), "no rows in result") {
return errors.New("no such user") return errors.New("no such user")
} }
return fmt.Errorf("db error: %w", err) return fmt.Errorf("db error: %w", err)
} }
if authHash != ai.Hash { if authHash != hash {
return errors.New("bad credentials") return errors.New("bad credentials")
} }
@ -296,7 +313,7 @@ func setupAPI(opts options) {
opts.Logf("querying for %s", args.Username) opts.Logf("querying for %s", args.Username)
if err := checkAuth(opts, AuthInfo{args.Username, args.Hash}); err == nil { if err := checkAuth(opts, args.Username, args.Hash); err == nil {
opts.Logf("found %s", args.Username) opts.Logf("found %s", args.Username)
// code 4 apparently // code 4 apparently
writeErrorResponse(w, 403, BBJResponse{ writeErrorResponse(w, 403, BBJResponse{
@ -415,7 +432,7 @@ func setupAPI(opts options) {
} }
// TODO make this getUserInfoFromReq or similar so we can use the user ID later // TODO make this getUserInfoFromReq or similar so we can use the user ID later
authInfo, err := getAuthInfo(opts, req) user, err := getUserFromReq(opts, req)
if err != nil { if err != nil {
writeErrorResponse(w, 403, BBJResponse{ writeErrorResponse(w, 403, BBJResponse{
Error: true, Error: true,
@ -455,8 +472,6 @@ func setupAPI(opts options) {
} }
defer stmt.Close() defer stmt.Close()
// TODO user id, not username
threadID, err := uuid.NewRandom() threadID, err := uuid.NewRandom()
if err != nil { if err != nil {
serverErr(w, err) serverErr(w, err)
@ -465,11 +480,11 @@ func setupAPI(opts options) {
now := time.Now() now := time.Now()
if _, err = stmt.Exec( if _, err = stmt.Exec(
threadID, threadID,
authInfo.Username, user.ID,
args.Title, args.Title,
now, now,
now, now,
authInfo.Username, user.Username,
); err != nil { ); err != nil {
serverErr(w, err) serverErr(w, err)
return return
@ -484,7 +499,7 @@ func setupAPI(opts options) {
if _, err = stmt.Exec( if _, err = stmt.Exec(
threadID, threadID,
authInfo.Username, user.ID,
now, now,
args.Body, args.Body,
args.SendRaw, args.SendRaw,
@ -498,8 +513,83 @@ func setupAPI(opts options) {
return return
} }
// TODO return the thread stmt, err = db.Prepare("select * from threads where thread_id = ? limit 1")
if err != nil {
serverErr(w, err)
return
}
defer stmt.Close()
writeResponse(w, BBJResponse{Data: "TODO"}) t := &Thread{}
// TODO fill in rest of thread
if err = stmt.QueryRow(threadID).Scan(
t.ID,
t.Author,
t.Title,
t.LastMod,
t.Created,
t.ReplyCount,
t.Pinned,
t.LastAuthor,
); err != nil {
serverErr(w, err)
return
}
stmt, err = db.Prepare("select * from messages where thread_id = ?")
if err != nil {
serverErr(w, err)
return
}
defer stmt.Close()
rows, err := stmt.Query(threadID)
if err != nil {
serverErr(w, err)
return
}
t.Messages = []Message{}
for rows.Next() {
m := &Message{}
if err := rows.Scan(
m.ThreadID,
m.PostID,
m.Author,
m.Created,
m.Edited,
m.Body,
m.SendRaw,
); err != nil {
serverErr(w, err)
return
}
t.Messages = append(t.Messages, *m)
}
writeResponse(w, BBJResponse{Data: t})
})) }))
} }
type Thread struct {
ID string `json:"thread_id"`
Author string
Title string
LastMod time.Time `json:"last_mod"`
Created time.Time
ReplyCount int `json:"reply_count"`
Pinned int // TODO bool
LastAuthor string `json:"last_author"`
Messages []Message
}
type Message struct {
ThreadID string `json:"thread_id"`
PostID string `json:"post_id"`
Author string
Created time.Time
Edited int // TODO bool
Body string
SendRaw int `json:"send_raw"` // TODO bool
}

View File

@ -27,6 +27,7 @@ insert into users values (
); );
-- TODO unique constraint on user_name? -- TODO unique constraint on user_name?
-- TODO foreign keys
create table threads ( create table threads (
thread_id text, -- uuid string thread_id text, -- uuid string