218 lines
5.8 KiB
Go
218 lines
5.8 KiB
Go
|
/*
|
||
|
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
|
||
|
service.logger.Info("webservice", "op", "authentication", "request", req, "authenticated", resp.Authenticated)
|
||
|
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")
|
||
|
} // }}}
|