Files
openfsd/web/fsd-jwt_handler.go
Reese Norris 57d54d6705 v0.1.0-alpha
Changes:
- Implement bootstrapping library for managing several concurrent internal services
- Refactor concurrency model for connections/logical clients and their associated I/O
- Refactor server context singleton
- Refactor error handling
    - Most errors are now gracefully sent to the FSD client directly encoded as an $ER packet,
      enhancing visibility and debugging
    - Most errors are now rightfully treated as non-fatal
- Refactor package/dependency graph
- Refactor calling conventions/interfaces for many packages
- Refactor database package
- Refactor post office

Features:
- Add VATSIM-esque HTTP/JSON "data feed"
- Add ephemeral in-memory database option
- Add user management REST API
- Add improved web interface
- Add MySQL support (drop SQLite support)
2024-10-07 12:50:39 -07:00

96 lines
2.8 KiB
Go

package web
import (
"bytes"
"database/sql"
"encoding/json"
"errors"
"github.com/renorris/openfsd/auth"
"github.com/renorris/openfsd/database"
"github.com/renorris/openfsd/protocol"
"github.com/renorris/openfsd/servercontext"
"golang.org/x/crypto/bcrypt"
"io"
"net/http"
"strconv"
"time"
)
// FSDJWTHandler administers tokens for base-level privilege FSD server connections
func FSDJWTHandler(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 256)
w.Header().Set("Content-Type", "application/json")
resp := auth.FSDJWTResponse{}
var respBytes []byte
// Load the request body
var body []byte
var err error
if body, err = io.ReadAll(r.Body); err != nil {
resp.Success, resp.ErrorMsg = false, "error reading request body"
writeResponseError(w, http.StatusBadRequest, &resp)
return
}
// Parse the request body
var req auth.FSDJWTRequest
if err = json.Unmarshal(body, &req); err != nil {
resp.Success, resp.ErrorMsg = false, "invalid request body"
writeResponseError(w, http.StatusBadRequest, &resp)
return
}
// Parse CID integer
var cidInt int
if cidInt, err = strconv.Atoi(req.CID); err != nil {
resp.Success, resp.ErrorMsg = false, "invalid CID"
writeResponseError(w, http.StatusBadRequest, &resp)
return
}
// Load user record from database
userRecord := database.FSDUserRecord{}
if err = userRecord.LoadByCID(servercontext.DB(), cidInt); err != nil {
if errors.Is(err, sql.ErrNoRows) {
resp.Success, resp.ErrorMsg = false, "invalid CID"
writeResponseError(w, http.StatusUnauthorized, &resp)
return
}
resp.Success, resp.ErrorMsg = false, "internal server error"
writeResponseError(w, http.StatusInternalServerError, &resp)
return
}
// Verify password hash
if err = bcrypt.CompareHashAndPassword([]byte(userRecord.FSDPassword), []byte(req.Password)); err != nil {
resp.Success, resp.ErrorMsg = false, "invalid credentials"
writeResponseError(w, http.StatusUnauthorized, &resp)
return
}
// Verify account standing
if userRecord.NetworkRating <= protocol.NetworkRatingSUS {
resp.Success, resp.ErrorMsg = false, "account suspended/inactive"
writeResponseError(w, http.StatusForbidden, &resp)
return
}
// All good. Administer the token.
// use "fsd" audience to specify that this token is only valid for connecting to FSD.
claims := auth.NewFSDJWTClaims(userRecord.CID, userRecord.NetworkRating, userRecord.PilotRating, []string{"fsd"})
var token string
if token, err = claims.MakeToken(time.Now().Add(420 * time.Second)); err != nil {
resp.Success, resp.ErrorMsg = false, "internal server error"
writeResponseError(w, http.StatusInternalServerError, &resp)
return
}
resp.Success, resp.Token = true, token
if respBytes, err = json.Marshal(resp); err == nil {
io.Copy(w, bytes.NewReader(respBytes))
}
}