bbj2/server/cmd/api/api.go

135 lines
2.7 KiB
Go

package api
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"git.tilde.town/tildetown/bbj2/server/cmd/config"
"git.tilde.town/tildetown/bbj2/server/cmd/db"
)
type HTTPError struct {
Msg string
Code int
}
func (e *HTTPError) Error() string {
return fmt.Sprintf("%d %s", e.Code, e.Msg)
}
type BBJResponse struct {
Error bool `json:"error"`
Data interface{} `json:"data"`
Usermap map[string]db.User `json:"usermap"`
}
type APIHandler func() (*BBJResponse, error)
type API struct {
User *db.User
Opts config.Options
Req *http.Request
}
func NewAPI(opts config.Options, req *http.Request) (*API, error) {
user, err := getUserFromReq(opts, req)
if err != nil {
return nil, &HTTPError{Msg: err.Error(), Code: 403}
}
return &API{
Opts: opts,
User: user,
Req: req,
}, nil
}
func Invoke(w http.ResponseWriter, apiFn APIHandler) {
resp, err := apiFn()
if err != nil {
he := &HTTPError{}
_ = errors.As(err, &he)
resp := BBJResponse{
Error: true,
Data: he.Msg,
}
w.WriteHeader(he.Code)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
return
}
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func getUserFromReq(opts config.Options, req *http.Request) (u *db.User, err error) {
u = &db.User{}
u.Username = req.Header.Get("User")
u.Hash = req.Header.Get("Auth")
if u.Username == "" || u.Username == "anon" {
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.Logger.Printf("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
}
type instanceInfo struct {
InstanceName string `json:"instance_name"`
AllowAnon bool `json:"allow_anon"`
Admins []string
}
func (a *API) IsGet() bool {
return a.Req.Method == "GET"
}
func (a *API) IsPost() bool {
return a.Req.Method == "POST"
}
func (a *API) InstanceInfo() (*BBJResponse, error) {
if !a.IsGet() {
return nil, &HTTPError{Msg: "bad method", Code: 400}
}
return &BBJResponse{
Data: instanceInfo{
InstanceName: a.Opts.Config.InstanceName,
AllowAnon: a.Opts.Config.AllowAnon,
Admins: a.Opts.Config.Admins,
},
}, nil
}
func (a *API) UserRegister() (*BBJResponse, error) {
if !a.IsPost() {
return nil, &HTTPError{Msg: "bad method", Code: 400}
}
return nil, nil
}