webservice/pkg.go

218 lines
5.8 KiB
Go
Raw Normal View History

2024-01-04 20:19:47 +01:00
/*
The webservice package is used to provide a webservice with sessions:
func sqlProvider(dbname string, version int) (sql []byte, found bool) {
var err error
sql, err = embeddedSQL.ReadFile(fmt.Sprintf("sql/%05d.sql", version))
if err != nil {
return
}
found = true
return
}
service, err := webservice.New("/etc/some/webservice.yaml")
if err != nil {
logger.Error("application", "error", err)
os.Exit(1)
}
service.SetDatabase(sqlProvider)
service.SetAuthenticationHandler(authenticate)
service.SetAuthorizationHandler(authorize)
service.Register("/foo", true, true, foo)
service.Register("/bar", true, false, bar)
err = service.Start()
if err != nil {
logger.Error("webserver", "error", err)
os.Exit(1)
}
*/
package webservice
import (
// Internal
"git.gibonuddevalla.se/go/webservice/config"
"git.gibonuddevalla.se/go/webservice/database"
"git.gibonuddevalla.se/go/webservice/session"
// Standard
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"os"
)
const VERSION = "v0.1.0"
type HttpHandler func(http.ResponseWriter, *http.Request)
type ErrorHandler func(err error, w http.ResponseWriter)
type ServiceError struct {
OK bool
Error string
}
type Service struct {
logger *slog.Logger
sessions map[string]*session.T
config config.Config
Db *database.T
errorHandler ErrorHandler
authenticationHandler AuthenticationHandler
authorizationHandler AuthorizationHandler
}
type ServiceHandler func(http.ResponseWriter, *http.Request, *session.T)
func New(configFilename string) (service *Service, err error) { // {{{
service = new(Service)
service.config, err = config.New(configFilename)
if err != nil {
return
}
opts := slog.HandlerOptions{}
service.logger = slog.New(slog.NewJSONHandler(os.Stdout, &opts))
service.sessions = make(map[string]*session.T, 128)
service.errorHandler = service.defaultErrorHandler
service.authenticationHandler = service.defaultAuthenticationHandler
service.authorizationHandler = service.defaultAuthorizationHandler
service.Register("/_session/new", false, false, service.sessionNew)
service.Register("/_session/authenticate", true, false, service.sessionAuthenticate)
return
} // }}}
func (service *Service) defaultAuthenticationHandler(req AuthenticationRequest, alreadyAuthenticated bool) (resp AuthenticationResponse, err error) { // {{{
resp.Authenticated = alreadyAuthenticated
2024-01-05 09:00:09 +01:00
service.logger.Info("webservice", "op", "authentication", "username", req.Username, "authenticated", resp.Authenticated)
2024-01-04 20:19:47 +01:00
return
} // }}}
func (service *Service) defaultAuthorizationHandler(sess *session.T, r *http.Request) (resp bool, err error) { // {{{
service.logger.Error("webservice", "op", "authorization", "session", sess.UUID, "request", r, "authorized", false)
return
} // }}}
func (service *Service) defaultErrorHandler(err error, w http.ResponseWriter) { // {{{
service.logger.Error("webservice", "error", err)
errMsg := ServiceError{}
errMsg.OK = false
errMsg.Error = err.Error()
errJSON, _ := json.Marshal(errMsg)
w.Write(errJSON)
} // }}}
func (service *Service) SetLogger(logger *slog.Logger) { // {{{
service.logger = logger
} // }}}
func (service *Service) SetErrorHandler(h ErrorHandler) { // {{{
service.errorHandler = h
} // }}}
func (service *Service) SetAuthenticationHandler(h AuthenticationHandler) { // {{{
service.authenticationHandler = h
} // }}}
func (service *Service) SetAuthorizationHandler(h AuthorizationHandler) { // {{{
service.authorizationHandler = h
} // }}}
func (service *Service) SetDatabase(sqlProv database.SqlProvider) { // {{{
service.Db = database.New(service.config.Database)
service.Db.SetLogger(service.logger)
service.Db.SetSQLProvider(sqlProv)
return
} // }}}
func (service *Service) Register(path string, requireSession, requireAuthentication bool, handler ServiceHandler) { // {{{
http.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
var session *session.T
var found bool
var authorized bool
var err error
if requireAuthentication && !requireSession {
requireSession = true
}
if requireSession {
headerSessionUUID, err := sessionUUID(r)
if err != nil {
service.errorHandler(fmt.Errorf("Header X-Session-ID missing"), w)
return
}
session, found = service.sessionRetrieve(headerSessionUUID)
if !found {
service.errorHandler(fmt.Errorf("Session '%s' not found", headerSessionUUID), w)
return
}
}
if requireAuthentication {
if !session.Authenticated {
service.errorHandler(fmt.Errorf("Session '%s' not authenticated", session.UUID), w)
return
}
authorized, err = service.authorizationHandler(session, r)
if err != nil {
service.errorHandler(err, w)
return
}
if !authorized {
service.errorHandler(fmt.Errorf("Session '%s' not authorized for %s", session.UUID, r.URL.String()), w)
return
}
}
handler(w, r, session)
})
} // }}}
func (service *Service) InitDatabaseConnection() (err error) { // {{{
err = service.Db.Upgrade()
if err != nil {
return
}
err = service.Db.Connect()
if err != nil {
return
}
return
} // }}}
func (service *Service) CreateUser(username, password, name string) (err error) { // {{{
if service.Db != nil {
err = service.InitDatabaseConnection()
if err != nil {
return
}
}
err = service.Db.CreateUser(username, password, name)
return
} // }}}
func (service *Service) Start() (err error) { // {{{
if service.Db != nil {
err = service.InitDatabaseConnection()
if err != nil {
return
}
}
listen := fmt.Sprintf("%s:%d", service.config.Network.Address, service.config.Network.Port)
service.logger.Info("webserver", "listen", listen)
err = http.ListenAndServe(listen, nil)
return
} // }}}
func sessionUUID(r *http.Request) (string, error) { // {{{
headers := r.Header["X-Session-Id"]
if len(headers) > 0 {
return headers[0], nil
}
return "", errors.New("Invalid session")
} // }}}