forked from tildetown/bbj2
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.
|
// 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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue