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.
type User struct {
// TODO
ID string
Username string
Hash string
}
type BBJResponse struct {
@ -177,39 +178,55 @@ func writeResponse(w http.ResponseWriter, resp BBJResponse) {
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) {
w.WriteHeader(code)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
// NB breaking: i'm not just returning 200 always but using http status codes
type AuthInfo struct {
Username string
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 == "" {
func getUserFromReq(opts options, req *http.Request) (u User, err error) {
u.Username = req.Header.Get("User")
u.Hash = req.Header.Get("Auth")
if u.Username == "" {
err = errors.New("no User header set")
return
}
if ai.Username == "anon" {
if u.Username == "anon" {
if !opts.Config.AllowAnon {
err = errors.New("anonymous access disabled")
}
return
}
}
err = checkAuth(opts, ai)
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
}
func checkAuth(opts options, ai AuthInfo) error {
func checkAuth(opts options, username, hash string) error {
db := opts.DB
stmt, err := db.Prepare("select auth_hash from users where user_name = ?")
if err != nil {
@ -217,17 +234,17 @@ func checkAuth(opts options, ai AuthInfo) error {
}
defer stmt.Close()
opts.Logf("querying for %s", ai.Username)
opts.Logf("querying for %s", username)
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") {
return errors.New("no such user")
}
return fmt.Errorf("db error: %w", err)
}
if authHash != ai.Hash {
if authHash != hash {
return errors.New("bad credentials")
}
@ -296,7 +313,7 @@ func setupAPI(opts options) {
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)
// code 4 apparently
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
authInfo, err := getAuthInfo(opts, req)
user, err := getUserFromReq(opts, req)
if err != nil {
writeErrorResponse(w, 403, BBJResponse{
Error: true,
@ -455,8 +472,6 @@ func setupAPI(opts options) {
}
defer stmt.Close()
// TODO user id, not username
threadID, err := uuid.NewRandom()
if err != nil {
serverErr(w, err)
@ -465,11 +480,11 @@ func setupAPI(opts options) {
now := time.Now()
if _, err = stmt.Exec(
threadID,
authInfo.Username,
user.ID,
args.Title,
now,
now,
authInfo.Username,
user.Username,
); err != nil {
serverErr(w, err)
return
@ -484,7 +499,7 @@ func setupAPI(opts options) {
if _, err = stmt.Exec(
threadID,
authInfo.Username,
user.ID,
now,
args.Body,
args.SendRaw,
@ -498,8 +513,83 @@ func setupAPI(opts options) {
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 foreign keys
create table threads (
thread_id text, -- uuid string