40bae8dc31
Structured logs can be enabled with `-log.json`.
167 lines
4.3 KiB
Go
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")
|
|
}
|
|
}
|