stuff
parent
5498804032
commit
758acb3b4e
|
@ -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
|
||||
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")
|
||||
}
|
||||
|
||||
if ai.Username == "anon" {
|
||||
if !opts.Config.AllowAnon {
|
||||
err = errors.New("anonymous access disabled")
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue