package main import ( "embed" "fmt" "io/fs" "log" "net/http" "strings" ) const ( COOKIE_NAME_TOKEN = `token` ) //go:embed build/frontend/public var frontendEmbed embed.FS func init() { // /api/registration must always be accessible without authentication router.HandleFunc("/api/registration", registration).Methods("POST") protected := router.PathPrefix("/api").Subrouter() protected.Use(authentication) protected.HandleFunc("/version", version) // frontend must come last to make sure /api takes precedence frontend, err := fs.Sub(frontendEmbed, "build/frontend/public") if err != nil { log.Fatalf("No embedded frontend: %v.", err) } router.PathPrefix("/").Handler(http.FileServerFS(frontend)) } func version(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, versionTxt) } func registration(w http.ResponseWriter, r *http.Request) { token, err := NewToken() if err != nil { http.Error(w, "Failed to generate token.", http.StatusInternalServerError) return } cookie := http.Cookie{ Name: COOKIE_NAME_TOKEN, Value: token.Private(), HttpOnly: true, Secure: true, SameSite: http.SameSiteStrictMode, } http.SetCookie(w, &cookie) fmt.Fprintf(w, "%s\n", token.Public()) } func authentication(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie(COOKIE_NAME_TOKEN) if err != nil { http.Error(w, "No authentication cookie present.", http.StatusForbidden) return } candidate, found := strings.CutPrefix(cookie.String(), COOKIE_NAME_TOKEN + "=") if !found { http.Error(w, "Unexpected cookie prefix.", http.StatusForbidden) return } user := authenticate(candidate) if user == nil { http.Error(w, "Unknown authentication token.", http.StatusForbidden) return } log.Printf("Authenticated user number %d\n", *user) next.ServeHTTP(w, r) }) }