From bf466b26ac93afc55fa4f23e320521e46ff0761c Mon Sep 17 00:00:00 2001 From: Zoe <62722391+juls0730@users.noreply.github.com> Date: Wed, 9 Apr 2025 04:48:38 -0500 Subject: [PATCH] Better docs, bug fixes, and config structure The project now uses a more sensible config structure to allow for actual defaults. Passport now has NO reliance on javascript whatsoever, and can be used identically without javascript enabled. --- .env.example | 3 +- README.md | 42 +++++++--- go.mod | 1 + go.sum | 2 + main.go | 166 ++++++++++++++++++++++++++++---------- styles/main.css | 4 +- templates/views/index.hbs | 33 +++----- zqdgr.config.json | 4 +- 8 files changed, 174 insertions(+), 81 deletions(-) diff --git a/.env.example b/.env.example index d802a0c..c28ea21 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,8 @@ +PASSPORT_ENABLE_WEATHER=true OPENWEATHER_API_KEY=1234567890 OPENWEATHER_LAT=34.052235 OPENWEATHER_LON=-118.243683 OPENWEATHER_UPDATE_INTERVAL=15 PASSPORT_ADMIN_USERNAME=admin PASSPORT_ADMIN_PASSWORD=P@ssw0rd -PASSPORT_SEARCH_PROVIDER=https://google.com/search?q=%s +PASSPORT_SEARCH_PROVIDER=https://google.com/search \ No newline at end of file diff --git a/README.md b/README.md index 1242328..82d7066 100644 --- a/README.md +++ b/README.md @@ -15,29 +15,45 @@ Passport is a simple, fast, and lightweight web dashboard/new tab replacement. - [sqlite3](https://www.sqlite.org/download.html) - [TailwdinCSS CLI](https://github.com/tailwindlabs/tailwindcss/releases/latest) -### Usage +## Usage 1. Clone the repository 2. Configure the `.env` file, an example is provided in the `.env.example` file, see below for every available environment variable 4. Deploy `passport` to your web server 5. profit -#### Configuration +### Configuration -| Environment Variable | Description | Required | Default | +#### Passport configuration + +| Environment Variable | Description | Required | Default | | --- | --- | --- | --- | -| `PASSPORT_DEV_MODE` | Enables dev mode | false | false | -| `PASSPORT_ENABLE_WEATHER` | Enables weather data, requires an OpenWeather API key | false | true | -| `PASSPORT_ENABLE_UPTIME` | Enables uptime data, requires an UptimeRobot API key | false | true | +| `PASSPORT_DEV_MODE` | Enables dev mode | false | false | +| `PASSPORT_ENABLE_PREFORK` | Enables preforking | false | false | +| `PASSPORT_ENABLE_WEATHER` | Enables weather data, see [Weather configuration](#weather-configuration) | false | false | +| `PASSPORT_ENABLE_UPTIME` | Enables uptime data, see [Uptime configuration](#uptime-configuration) | false | false | | `PASSPORT_ADMIN_USERNAME` | The username for the admin dashboard | true | | `PASSPORT_ADMIN_PASSWORD` | The password for the admin dashboard | true | -| `PASSPORT_SEARCH_PROVIDER` | The search provider to use for the search bar | true | -| `OPENWEATHER_API_KEY` | The OpenWeather API key | if enabled | -| `OPENWEATHER_LAT` | The latitude of your location | if enabled | -| `OPENWEATHER_LON` | The longitude of your location | if enabled | -| `OPENWEATHER_UPDATE_INTERVAL` | The interval in minutes to update the weather data | false | 15 | -| `UPTIMEROBOT_API_KEY` | The UptimeRobot API key | if enabled | -| `UPTIMEROBOT_UPDATE_INTERVAL` | The interval in seconds to update the uptime data | false | 300 | +| `PASSPORT_SEARCH_PROVIDER` | The search provider to use for the search bar, without any query parameters | true | +| `PASSPORT_SEARCH_PROVIDER_QUERY_PARAM` | The query parameter to use for the search provider, e.g. `q` for most providers |false | q | + +#### Weather configuration + +| Environment Variable | Description | Required | Default | +| --- | --- | --- | --- | +| `OPENWEATHER_PROVIDER` | The weather provider to use, currently only `openweathermap` is supported | true | openweathermap | +| `OPENWEATHER_API_KEY` | The OpenWeather API key | if enabled | | +| `OPENWEATHER_TEMP_UNITS` | The temperature units to use, either `metric` or `imperial` | false | metric | +| `OPENWEATHER_LAT` | The latitude of your location | if enabled | | +| `OPENWEATHER_LON` | The longitude of your location | if enabled | | +| `OPENWEATHER_UPDATE_INTERVAL` | The interval in minutes to update the weather data | false | 15 | + +#### Uptime configuration + +| Environment Variable | Description | Required | Default | +| --- | --- | --- | --- | +| `UPTIMEROBOT_API_KEY` | The UptimeRobot API key | if enabled | | +| `UPTIMEROBOT_UPDATE_INTERVAL` | The interval in seconds to update the uptime data | false | 300 | ### Adding links and categories diff --git a/go.mod b/go.mod index 11423fe..a8ed7a8 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/juls0730/passport go 1.23.2 require ( + github.com/caarlos0/env/v11 v11.3.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gofiber/schema v1.2.0 // indirect github.com/mailgun/raymond/v2 v2.0.48 // indirect diff --git a/go.sum b/go.sum index 4e1a827..fe828c0 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1 github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= +github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk= github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/main.go b/main.go index c623ff6..37d4c03 100644 --- a/main.go +++ b/main.go @@ -20,11 +20,11 @@ import ( "net/http" "os" "path/filepath" - "strconv" "strings" "sync" "time" + "github.com/caarlos0/env/v11" "github.com/chai2010/webp" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/helmet" @@ -67,14 +67,88 @@ var ( insertLinkStmt *sql.Stmt ) +type WeatherProvider string + +const ( + OpenWeatherMap WeatherProvider = "openweathermap" +) + +type WeatherConfig struct { + Provider WeatherProvider `env:"OPENWEATHER_PROVIDER" envDefault:"openweathermap"` + OpenWeather struct { + APIKey string `env:"OPENWEATHER_API_KEY"` + Units string `env:"OPENWEATHER_TEMP_UNITS" envDefault:"metric"` + Lat float64 `env:"OPENWEATHER_LAT"` + Lon float64 `env:"OPENWEATHER_LON"` + } + UpdateInterval int `env:"OPENWEATHER_UPDATE_INTERVAL" envDefault:"15"` +} + +type UptimeConfig struct { + APIKey string `env:"UPTIMEROBOT_API_KEY"` + UpdateInterval int `env:"UPTIMEROBOT_UPDATE_INTERVAL" envDefault:"300"` +} + +type Config struct { + DevMode bool `env:"PASSPORT_DEV_MODE" envDefault:"false"` + Prefork bool `env:"PASSPORT_ENABLE_PREFORK" envDefault:"false"` + + WeatherEnabled bool `env:"PASSPORT_ENABLE_WEATHER" envDefault:"false"` + Weather *WeatherConfig + + UptimeEnabled bool `env:"PASSPORT_ENABLE_UPTIME" envDefault:"false"` + Uptime *UptimeConfig + + Admin struct { + Username string `env:"PASSPORT_ADMIN_USERNAME"` + Password string `env:"PASSPORT_ADMIN_PASSWORD"` + } + + SearchProvider struct { + URL string `env:"PASSPORT_SEARCH_PROVIDER"` + Query string `env:"PASSPORT_SEARCH_PROVIDER_QUERY_PARAM" envDefault:"q"` + } +} + +func ParseConfig() (*Config, error) { + config := Config{} + + err := env.Parse(&config) + if err != nil { + return nil, err + } + + if config.WeatherEnabled { + config.Weather = &WeatherConfig{} + if err := env.Parse(config.Weather); err != nil { + return nil, err + } + } + + if config.UptimeEnabled { + config.Uptime = &UptimeConfig{} + if err := env.Parse(config.Uptime); err != nil { + return nil, err + } + } + + return &config, nil +} + type App struct { - *WeatherCache + *Config *CategoryManager + *WeatherCache *UptimeManager db *sql.DB } func NewApp(dbPath string) (*App, error) { + config, err := ParseConfig() + if err != nil { + return nil, err + } + db, err := sql.Open("sqlite3", dbPath) if err != nil { return nil, err @@ -96,16 +170,17 @@ func NewApp(dbPath string) (*App, error) { } var weatherCache *WeatherCache - if os.Getenv("PASSPORT_ENABLE_WEATHER") != "false" { - weatherCache = NewWeatherCache() + if config.WeatherEnabled { + weatherCache = NewWeatherCache(config.Weather) } var uptimeManager *UptimeManager - if os.Getenv("PASSPORT_ENABLE_UPTIME") != "false" { - uptimeManager = NewUptimeManager() + if config.UptimeEnabled { + uptimeManager = NewUptimeManager(config.Uptime) } return &App{ + Config: config, WeatherCache: weatherCache, CategoryManager: categoryManager, UptimeManager: uptimeManager, @@ -127,21 +202,21 @@ type UptimeManager struct { apiKey string } -func NewUptimeManager() *UptimeManager { - if os.Getenv("UPTIMEROBOT_API_KEY") == "" { +func NewUptimeManager(config *UptimeConfig) *UptimeManager { + if config.APIKey == "" { log.Fatalln("UptimeRobot API Key is required!") return nil } - updateInterval, err := strconv.Atoi(os.Getenv("UPTIMEROBOT_UPDATE_INTERVAL")) - if err != nil || updateInterval < 1 { + updateInterval := config.UpdateInterval + if updateInterval < 1 { updateInterval = 300 } uptimeManager := &UptimeManager{ updateChan: make(chan struct{}), updateInterval: updateInterval, - apiKey: os.Getenv("UPTIMEROBOT_API_KEY"), + apiKey: config.APIKey, sites: []UptimeRobotSite{}, } @@ -228,22 +303,27 @@ type WeatherCache struct { tempUnits string updateInterval int apiKey string - lat string - lon string + lat float64 + lon float64 } -func NewWeatherCache() *WeatherCache { - if os.Getenv("OPENWEATHER_API_KEY") == "" || os.Getenv("OPENWEATHER_LAT") == "" || os.Getenv("OPENWEATHER_LON") == "" { - log.Fatalln("OpenWeather API Key, and your latitude and longitude are required!") +func NewWeatherCache(config *WeatherConfig) *WeatherCache { + if config.Provider != OpenWeatherMap { + log.Fatalln("Only OpenWeatherMap is supported!") return nil } - updateInterval, err := strconv.Atoi(os.Getenv("OPENWEATHER_UPDATE_INTERVAL")) - if err != nil || updateInterval < 1 { + if config.OpenWeather.APIKey == "" { + log.Fatalln("An API Key required for OpenWeather!") + return nil + } + + updateInterval := config.UpdateInterval + if updateInterval < 1 { updateInterval = 15 } - units := os.Getenv("OPENWEATHER_TEMP_UNITS") + units := config.OpenWeather.Units if units == "" { units = "metric" } @@ -253,9 +333,9 @@ func NewWeatherCache() *WeatherCache { updateChan: make(chan struct{}), tempUnits: units, updateInterval: updateInterval, - apiKey: os.Getenv("OPENWEATHER_API_KEY"), - lat: os.Getenv("OPENWEATHER_LAT"), - lon: os.Getenv("OPENWEATHER_LON"), + apiKey: config.OpenWeather.APIKey, + lat: config.OpenWeather.Lat, + lon: config.OpenWeather.Lon, } go cache.weatherWorker() @@ -286,7 +366,7 @@ func (c *WeatherCache) weatherWorker() { } func (c *WeatherCache) updateWeather() { - url := fmt.Sprintf("https://api.openweathermap.org/data/2.5/weather?lat=%s&lon=%s&appid=%s&units=%s", + url := fmt.Sprintf("https://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%s&units=%s", c.lat, c.lon, c.apiKey, c.tempUnits) resp, err := http.Get(url) @@ -549,17 +629,17 @@ func (manager *CategoryManager) DeleteLink(id any) error { } var WeatherIcons = map[string]string{ - "clear-day": ``, - "clear-night": ``, - "partly-cloudy-day": ``, - "partly-cloudy-night": ``, - "mostly-cloudy-day": ``, - "mostly-cloudy-night": ``, - "light-rain": ``, - "rain": ``, - "thunder": ``, - "snow": ``, - "mist": ``, + "clear-day": ``, + "clear-night": ``, + "partly-cloudy-day": ``, + "partly-cloudy-night": ``, + "mostly-cloudy-day": ``, + "mostly-cloudy-night": ``, + "light-rain": ``, + "rain": ``, + "thunder": ``, + "snow": ``, + "mist": ``, } func getWeatherIcon(iconId string) string { @@ -593,7 +673,7 @@ func getWeatherIcon(iconId string) string { func init() { if err := godotenv.Load(); err != nil { - fmt.Println("No .env file found") + fmt.Println("No .env file found, using default values") } } @@ -630,7 +710,7 @@ func main() { }) engine.AddFunc("devContent", func() string { - if os.Getenv("PASSPORT_DEV_MODE") == "true" { + if app.Config.DevMode { return devContent } return "" @@ -658,11 +738,12 @@ func main() { router.Get("/", func(c fiber.Ctx) error { renderData := fiber.Map{ - "SearchProvider": os.Getenv("PASSPORT_SEARCH_PROVIDER"), - "Categories": app.CategoryManager.Categories, + "SearchProviderURL": app.Config.SearchProvider.URL, + "SearchParam": app.Config.SearchProvider.Query, + "Categories": app.CategoryManager.Categories, } - if os.Getenv("PASSPORT_ENABLE_WEATHER") != "false" { + if app.Config.WeatherEnabled { weather := app.WeatherCache.GetWeather() renderData["WeatherData"] = fiber.Map{ @@ -672,7 +753,7 @@ func main() { } } - if os.Getenv("PASSPORT_ENABLE_UPTIME") != "false" { + if app.Config.UptimeEnabled { renderData["UptimeData"] = app.UptimeManager.getUptime() } @@ -702,7 +783,8 @@ func main() { return err } - if loginData.Username != os.Getenv("PASSPORT_ADMIN_USERNAME") || loginData.Password != os.Getenv("PASSPORT_ADMIN_PASSWORD") { + // possible vulnerable to timing attacks + if loginData.Username != app.Config.Admin.Username || loginData.Password != app.Config.Admin.Password { return c.Status(http.StatusUnauthorized).JSON(fiber.Map{"message": "Invalid username or password"}) } @@ -942,6 +1024,6 @@ func main() { } router.Listen(":3000", fiber.ListenConfig{ - EnablePrefork: os.Getenv("PASSPORT_ENABLE_PREFORK") == "true", + EnablePrefork: app.Config.Prefork, }) } diff --git a/styles/main.css b/styles/main.css index 71503d6..c328a8e 100644 --- a/styles/main.css +++ b/styles/main.css @@ -7,7 +7,7 @@ } :root { - --default-font-family: "Instrument Sans", ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; + --default-font-family: "Instrument Sans", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } html { @@ -25,7 +25,7 @@ h6 { } h1 { - font-size: clamp(48px, 10vw, 64px); + font-size: clamp(42px, 10vw, 64px); } h2 { diff --git a/templates/views/index.hbs b/templates/views/index.hbs index b5e8142..adb3ef8 100644 --- a/templates/views/index.hbs +++ b/templates/views/index.hbs @@ -1,7 +1,7 @@
-
+
- {{#if WeatherData}} + {{#if WeatherData}}
{{{WeatherData.Icon}}} @@ -11,10 +11,10 @@

{{WeatherData.Desc}}

- {{/if}} + {{/if}}
- {{#if UptimeData}} + {{#if UptimeData}}
{{#each UptimeData}}
@@ -33,12 +33,12 @@
{{/each}}
- {{/if}} + {{/if}}
- @@ -60,9 +60,11 @@

Passport

- +
+ +
@@ -89,15 +91,4 @@ {{/each}} -
- - \ No newline at end of file + \ No newline at end of file diff --git a/zqdgr.config.json b/zqdgr.config.json index c9901a9..c9a84c1 100644 --- a/zqdgr.config.json +++ b/zqdgr.config.json @@ -1,6 +1,6 @@ { "name": "passport", - "version": "0.0.1", + "version": "0.2.0", "description": "Passport is a simple, lightweight, and fast dashboard/new tab page for your browser.", "author": "juls0730", "license": "BSL-1.0", @@ -13,5 +13,5 @@ "dev": "go generate; PASSPORT_DEV_MODE=true go run main.go", "build": "go generate && go build -tags netgo,prod -o passport" }, - "pattern": "**/*.go,templates/views/**/*.hbs,styles/**/*.css,assets/**/*.{svg,png,jpg,jpeg,webp,woff2,ttf,otf,eot,ico,gif,webp}" + "pattern": "**/*.go,templates/views/**/*.hbs,styles/**/*.css,assets/**/*.{svg,png,jpg,jpeg,webp,woff2,ttf,otf,eot,ico,gif,webp}" } \ No newline at end of file