tokidoki/cmd/tokidoki/main.go
Conrad Hoffmann 40bae8dc31 Switch to a proper logging library
Structured logs can be enabled with `-log.json`.
2022-12-01 13:46:25 +01:00

167 lines
4.3 KiB
Go

package main
import (
"context"
"encoding/base64"
"flag"
"fmt"
"net/http"
"os"
"github.com/emersion/go-webdav"
"github.com/emersion/go-webdav/caldav"
"github.com/emersion/go-webdav/carddav"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"git.sr.ht/~sircmpwn/tokidoki/auth"
"git.sr.ht/~sircmpwn/tokidoki/storage"
)
type userPrincipalBackend struct{}
func (u *userPrincipalBackend) CurrentUserPrincipal(ctx context.Context) (string, error) {
authCtx, ok := auth.FromContext(ctx)
if !ok {
panic("Invalid data in auth context!")
}
if authCtx == nil {
return "", fmt.Errorf("unauthenticated requests are not supported")
}
userDir := base64.RawStdEncoding.EncodeToString([]byte(authCtx.UserName))
return "/" + userDir + "/", nil
}
type tokidokiHandler struct {
upBackend webdav.UserPrincipalBackend
authBackend auth.AuthProvider
caldavBackend caldav.Backend
carddavBackend carddav.Backend
}
func (u *tokidokiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
userPrincipalPath, err := u.upBackend.CurrentUserPrincipal(r.Context())
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
}
var homeSets []webdav.BackendSuppliedHomeSet
if u.caldavBackend != nil {
path, err := u.caldavBackend.CalendarHomeSetPath(r.Context())
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
} else {
homeSets = append(homeSets, caldav.NewCalendarHomeSet(path))
}
}
if u.carddavBackend != nil {
path, err := u.carddavBackend.AddressbookHomeSetPath(r.Context())
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
} else {
homeSets = append(homeSets, carddav.NewAddressBookHomeSet(path))
}
}
if r.URL.Path == userPrincipalPath {
opts := webdav.ServePrincipalOptions{
CurrentUserPrincipalPath: userPrincipalPath,
HomeSets: homeSets,
}
webdav.ServePrincipal(w, r, &opts)
return
}
// TODO serve something on / that signals this being a DAV server?
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
}
func main() {
var (
addr string
authURL string
debug bool
jsonLog bool
storageURL string
)
flag.StringVar(&addr, "addr", ":8080", "listening address")
flag.StringVar(&authURL, "auth.url", "", "auth backend URL (required)")
flag.StringVar(&storageURL, "storage.url", "", "storage backend URL (required)")
flag.BoolVar(&debug, "log.debug", false, "enable debug logs")
flag.BoolVar(&jsonLog, "log.json", false, "enable structured logs")
flag.Parse()
if len(flag.Args()) != 0 || authURL == "" || storageURL == "" {
flag.Usage()
os.Exit(1)
}
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
zerolog.SetGlobalLevel(zerolog.InfoLevel)
if debug {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
if !jsonLog {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
}
for _, method := range []string{
"PROPFIND",
"PROPPATCH",
"REPORT",
"MKCOL",
"COPY",
"MOVE",
} {
chi.RegisterMethod(method)
}
mux := chi.NewRouter()
mux.Use(middleware.Logger)
authProvider, err := auth.NewFromURL(authURL)
if err != nil {
log.Fatal().Err(err).Msg("failed to load auth provider")
}
mux.Use(authProvider.Middleware())
upBackend := &userPrincipalBackend{}
caldavBackend, carddavBackend, err := storage.NewFromURL(storageURL, "/calendar/", "/contacts/", upBackend)
if err != nil {
log.Fatal().Err(err).Msg("failed to load storage backend")
}
carddavHandler := carddav.Handler{Backend: carddavBackend}
caldavHandler := caldav.Handler{Backend: caldavBackend}
handler := tokidokiHandler{
upBackend: upBackend,
authBackend: authProvider,
caldavBackend: caldavBackend,
carddavBackend: carddavBackend,
}
mux.Mount("/", &handler)
mux.Mount("/.well-known/caldav", &caldavHandler)
mux.Mount("/.well-known/carddav", &carddavHandler)
mux.Mount("/{user}/contacts", &carddavHandler)
mux.Mount("/{user}/calendar", &caldavHandler)
server := http.Server{
Addr: addr,
Handler: mux,
}
log.Info().Str("address", addr).Msg("starting server")
log.Debug().Msg("debug output enabled")
err = server.ListenAndServe()
if err != http.ErrServerClosed {
log.Fatal().Err(err).Msg("ListenAndServe() error")
}
}