-
Notifications
You must be signed in to change notification settings - Fork 809
feat(http): add options to disable GoogleReader/Fever/API #3505
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
fguillot
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please update the man page with the new config options.
|
Good call |
fguillot
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You also need to update this switch-case, and add unit tests where applicable:
Lines 74 to 258 in 13ef89f
| switch key { | |
| case "LOG_FILE": | |
| p.opts.logFile = parseString(value, defaultLogFile) | |
| case "LOG_DATE_TIME": | |
| p.opts.logDateTime = parseBool(value, defaultLogDateTime) | |
| case "LOG_LEVEL": | |
| parsedValue := parseString(value, defaultLogLevel) | |
| if parsedValue == "debug" || parsedValue == "info" || parsedValue == "warning" || parsedValue == "error" { | |
| p.opts.logLevel = parsedValue | |
| } | |
| case "LOG_FORMAT": | |
| parsedValue := parseString(value, defaultLogFormat) | |
| if parsedValue == "json" || parsedValue == "text" { | |
| p.opts.logFormat = parsedValue | |
| } | |
| case "BASE_URL": | |
| p.opts.baseURL, p.opts.rootURL, p.opts.basePath, err = parseBaseURL(value) | |
| if err != nil { | |
| return err | |
| } | |
| case "PORT": | |
| port = value | |
| case "LISTEN_ADDR": | |
| p.opts.listenAddr = parseStringList(value, []string{defaultListenAddr}) | |
| case "DATABASE_URL": | |
| p.opts.databaseURL = parseString(value, defaultDatabaseURL) | |
| case "DATABASE_URL_FILE": | |
| p.opts.databaseURL = readSecretFile(value, defaultDatabaseURL) | |
| case "DATABASE_MAX_CONNS": | |
| p.opts.databaseMaxConns = parseInt(value, defaultDatabaseMaxConns) | |
| case "DATABASE_MIN_CONNS": | |
| p.opts.databaseMinConns = parseInt(value, defaultDatabaseMinConns) | |
| case "DATABASE_CONNECTION_LIFETIME": | |
| p.opts.databaseConnectionLifetime = parseInt(value, defaultDatabaseConnectionLifetime) | |
| case "FILTER_ENTRY_MAX_AGE_DAYS": | |
| p.opts.filterEntryMaxAgeDays = parseInt(value, defaultFilterEntryMaxAgeDays) | |
| case "RUN_MIGRATIONS": | |
| p.opts.runMigrations = parseBool(value, defaultRunMigrations) | |
| case "DISABLE_HSTS": | |
| p.opts.hsts = !parseBool(value, defaultHSTS) | |
| case "HTTPS": | |
| p.opts.HTTPS = parseBool(value, defaultHTTPS) | |
| case "DISABLE_SCHEDULER_SERVICE": | |
| p.opts.schedulerService = !parseBool(value, defaultSchedulerService) | |
| case "DISABLE_HTTP_SERVICE": | |
| p.opts.httpService = !parseBool(value, defaultHTTPService) | |
| case "CERT_FILE": | |
| p.opts.certFile = parseString(value, defaultCertFile) | |
| case "KEY_FILE": | |
| p.opts.certKeyFile = parseString(value, defaultKeyFile) | |
| case "CERT_DOMAIN": | |
| p.opts.certDomain = parseString(value, defaultCertDomain) | |
| case "CLEANUP_FREQUENCY_HOURS": | |
| p.opts.cleanupFrequencyHours = parseInt(value, defaultCleanupFrequencyHours) | |
| case "CLEANUP_ARCHIVE_READ_DAYS": | |
| p.opts.cleanupArchiveReadDays = parseInt(value, defaultCleanupArchiveReadDays) | |
| case "CLEANUP_ARCHIVE_UNREAD_DAYS": | |
| p.opts.cleanupArchiveUnreadDays = parseInt(value, defaultCleanupArchiveUnreadDays) | |
| case "CLEANUP_ARCHIVE_BATCH_SIZE": | |
| p.opts.cleanupArchiveBatchSize = parseInt(value, defaultCleanupArchiveBatchSize) | |
| case "CLEANUP_REMOVE_SESSIONS_DAYS": | |
| p.opts.cleanupRemoveSessionsDays = parseInt(value, defaultCleanupRemoveSessionsDays) | |
| case "WORKER_POOL_SIZE": | |
| p.opts.workerPoolSize = parseInt(value, defaultWorkerPoolSize) | |
| case "POLLING_FREQUENCY": | |
| p.opts.pollingFrequency = parseInt(value, defaultPollingFrequency) | |
| case "FORCE_REFRESH_INTERVAL": | |
| p.opts.forceRefreshInterval = parseInt(value, defaultForceRefreshInterval) | |
| case "BATCH_SIZE": | |
| p.opts.batchSize = parseInt(value, defaultBatchSize) | |
| case "POLLING_SCHEDULER": | |
| p.opts.pollingScheduler = strings.ToLower(parseString(value, defaultPollingScheduler)) | |
| case "SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL": | |
| p.opts.schedulerEntryFrequencyMaxInterval = parseInt(value, defaultSchedulerEntryFrequencyMaxInterval) | |
| case "SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL": | |
| p.opts.schedulerEntryFrequencyMinInterval = parseInt(value, defaultSchedulerEntryFrequencyMinInterval) | |
| case "SCHEDULER_ENTRY_FREQUENCY_FACTOR": | |
| p.opts.schedulerEntryFrequencyFactor = parseInt(value, defaultSchedulerEntryFrequencyFactor) | |
| case "SCHEDULER_ROUND_ROBIN_MIN_INTERVAL": | |
| p.opts.schedulerRoundRobinMinInterval = parseInt(value, defaultSchedulerRoundRobinMinInterval) | |
| case "SCHEDULER_ROUND_ROBIN_MAX_INTERVAL": | |
| p.opts.schedulerRoundRobinMaxInterval = parseInt(value, defaultSchedulerRoundRobinMaxInterval) | |
| case "POLLING_PARSING_ERROR_LIMIT": | |
| p.opts.pollingParsingErrorLimit = parseInt(value, defaultPollingParsingErrorLimit) | |
| case "MEDIA_PROXY_HTTP_CLIENT_TIMEOUT": | |
| p.opts.mediaProxyHTTPClientTimeout = parseInt(value, defaultMediaProxyHTTPClientTimeout) | |
| case "MEDIA_PROXY_MODE": | |
| p.opts.mediaProxyMode = parseString(value, defaultMediaProxyMode) | |
| case "MEDIA_PROXY_RESOURCE_TYPES": | |
| p.opts.mediaProxyResourceTypes = parseStringList(value, []string{defaultMediaResourceTypes}) | |
| case "MEDIA_PROXY_PRIVATE_KEY": | |
| randomKey := make([]byte, 16) | |
| if _, err := rand.Read(randomKey); err != nil { | |
| return fmt.Errorf("config: unable to generate random key: %w", err) | |
| } | |
| p.opts.mediaProxyPrivateKey = parseBytes(value, randomKey) | |
| case "MEDIA_PROXY_CUSTOM_URL": | |
| p.opts.mediaProxyCustomURL = parseString(value, defaultMediaProxyURL) | |
| case "CREATE_ADMIN": | |
| p.opts.createAdmin = parseBool(value, defaultCreateAdmin) | |
| case "ADMIN_USERNAME": | |
| p.opts.adminUsername = parseString(value, defaultAdminUsername) | |
| case "ADMIN_USERNAME_FILE": | |
| p.opts.adminUsername = readSecretFile(value, defaultAdminUsername) | |
| case "ADMIN_PASSWORD": | |
| p.opts.adminPassword = parseString(value, defaultAdminPassword) | |
| case "ADMIN_PASSWORD_FILE": | |
| p.opts.adminPassword = readSecretFile(value, defaultAdminPassword) | |
| case "OAUTH2_USER_CREATION": | |
| p.opts.oauth2UserCreationAllowed = parseBool(value, defaultOAuth2UserCreation) | |
| case "OAUTH2_CLIENT_ID": | |
| p.opts.oauth2ClientID = parseString(value, defaultOAuth2ClientID) | |
| case "OAUTH2_CLIENT_ID_FILE": | |
| p.opts.oauth2ClientID = readSecretFile(value, defaultOAuth2ClientID) | |
| case "OAUTH2_CLIENT_SECRET": | |
| p.opts.oauth2ClientSecret = parseString(value, defaultOAuth2ClientSecret) | |
| case "OAUTH2_CLIENT_SECRET_FILE": | |
| p.opts.oauth2ClientSecret = readSecretFile(value, defaultOAuth2ClientSecret) | |
| case "OAUTH2_REDIRECT_URL": | |
| p.opts.oauth2RedirectURL = parseString(value, defaultOAuth2RedirectURL) | |
| case "OAUTH2_OIDC_DISCOVERY_ENDPOINT": | |
| p.opts.oidcDiscoveryEndpoint = parseString(value, defaultOAuth2OidcDiscoveryEndpoint) | |
| case "OAUTH2_OIDC_PROVIDER_NAME": | |
| p.opts.oidcProviderName = parseString(value, defaultOauth2OidcProviderName) | |
| case "OAUTH2_PROVIDER": | |
| p.opts.oauth2Provider = parseString(value, defaultOAuth2Provider) | |
| case "DISABLE_LOCAL_AUTH": | |
| p.opts.disableLocalAuth = parseBool(value, defaultDisableLocalAuth) | |
| case "HTTP_CLIENT_TIMEOUT": | |
| p.opts.httpClientTimeout = parseInt(value, defaultHTTPClientTimeout) | |
| case "HTTP_CLIENT_MAX_BODY_SIZE": | |
| p.opts.httpClientMaxBodySize = int64(parseInt(value, defaultHTTPClientMaxBodySize) * 1024 * 1024) | |
| case "HTTP_CLIENT_PROXY": | |
| p.opts.httpClientProxyURL, err = url.Parse(parseString(value, defaultHTTPClientProxy)) | |
| if err != nil { | |
| return fmt.Errorf("config: invalid HTTP_CLIENT_PROXY value: %w", err) | |
| } | |
| case "HTTP_CLIENT_PROXIES": | |
| p.opts.httpClientProxies = parseStringList(value, []string{}) | |
| case "HTTP_CLIENT_USER_AGENT": | |
| p.opts.httpClientUserAgent = parseString(value, defaultHTTPClientUserAgent) | |
| case "HTTP_SERVER_TIMEOUT": | |
| p.opts.httpServerTimeout = parseInt(value, defaultHTTPServerTimeout) | |
| case "AUTH_PROXY_HEADER": | |
| p.opts.authProxyHeader = parseString(value, defaultAuthProxyHeader) | |
| case "AUTH_PROXY_USER_CREATION": | |
| p.opts.authProxyUserCreation = parseBool(value, defaultAuthProxyUserCreation) | |
| case "MAINTENANCE_MODE": | |
| p.opts.maintenanceMode = parseBool(value, defaultMaintenanceMode) | |
| case "MAINTENANCE_MESSAGE": | |
| p.opts.maintenanceMessage = parseString(value, defaultMaintenanceMessage) | |
| case "METRICS_COLLECTOR": | |
| p.opts.metricsCollector = parseBool(value, defaultMetricsCollector) | |
| case "METRICS_REFRESH_INTERVAL": | |
| p.opts.metricsRefreshInterval = parseInt(value, defaultMetricsRefreshInterval) | |
| case "METRICS_ALLOWED_NETWORKS": | |
| p.opts.metricsAllowedNetworks = parseStringList(value, []string{defaultMetricsAllowedNetworks}) | |
| case "METRICS_USERNAME": | |
| p.opts.metricsUsername = parseString(value, defaultMetricsUsername) | |
| case "METRICS_USERNAME_FILE": | |
| p.opts.metricsUsername = readSecretFile(value, defaultMetricsUsername) | |
| case "METRICS_PASSWORD": | |
| p.opts.metricsPassword = parseString(value, defaultMetricsPassword) | |
| case "METRICS_PASSWORD_FILE": | |
| p.opts.metricsPassword = readSecretFile(value, defaultMetricsPassword) | |
| case "FETCH_BILIBILI_WATCH_TIME": | |
| p.opts.fetchBilibiliWatchTime = parseBool(value, defaultFetchBilibiliWatchTime) | |
| case "FETCH_NEBULA_WATCH_TIME": | |
| p.opts.fetchNebulaWatchTime = parseBool(value, defaultFetchNebulaWatchTime) | |
| case "FETCH_ODYSEE_WATCH_TIME": | |
| p.opts.fetchOdyseeWatchTime = parseBool(value, defaultFetchOdyseeWatchTime) | |
| case "FETCH_YOUTUBE_WATCH_TIME": | |
| p.opts.fetchYouTubeWatchTime = parseBool(value, defaultFetchYouTubeWatchTime) | |
| case "YOUTUBE_API_KEY": | |
| p.opts.youTubeApiKey = parseString(value, defaultYouTubeApiKey) | |
| case "YOUTUBE_EMBED_URL_OVERRIDE": | |
| p.opts.youTubeEmbedUrlOverride = parseString(value, defaultYouTubeEmbedUrlOverride) | |
| case "WATCHDOG": | |
| p.opts.watchdog = parseBool(value, defaultWatchdog) | |
| case "INVIDIOUS_INSTANCE": | |
| p.opts.invidiousInstance = parseString(value, defaultInvidiousInstance) | |
| case "WEBAUTHN": | |
| p.opts.webAuthn = parseBool(value, defaultWebAuthn) | |
| } | |
| } |
|
What kind of unit-tests are you thinking of? |
Something similar to what already exists. Few test cases to verify that the parsed config values are what is expected. v2/internal/config/config_test.go Lines 1301 to 1334 in 335dffb
|
a7f85c3 to
928af80
Compare
fguillot
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Fever and Google Reader sections on the integration page should probably be hidden. Same for the Miniflux API Keys page. Any thoughts?
The web ui crashes when using DISABLE_FEVER_API=1. Needs more testing.
level=INFO msg="http: panic serving 127.0.0.1:60350: template: main:650:81: executing \"content\" at <route \"feverEndpoint\">: error calling route: route not found: feverEndpoint\ngoroutine 66 [running]:\nnet/http.(*conn).serve.func1()\n\t/usr/local/go/src/net/http/server.go:1947 +0xbe\npanic({0xd99d20?, 0xc00085e640?})\n\t/usr/local/go/src/runtime/panic.go:792 +0x132\nminiflux.app/v2/internal/template.(*Engine).Render(0xd387a0?, {0xc000db2fd0, 0x11}, 0xc000d97620)\n\t/home/fred/repos/miniflux/v2/internal/template/engine.go:135 +0x39f\nminiflux.app/v2/internal/ui/view.(*View).Render(...)\n\t/home/fred/repos/miniflux/v2/internal/ui/view/view.go:31\nminiflux.app/v2/internal/ui.(*handler).showIntegrationPage(0xc000c32860, {0x109f238, 0xc000390380}, 0xc0001def00)\n\t/home/fred/repos/miniflux/v2/internal/ui/integration_show.go:149 +0xdda\nnet/http.HandlerFunc.ServeHTTP(0xc0001dedc0?, {0x109f238?, 0xc000390380?}, 0x1092898?)\n\t/usr/local/go/src/net/http/server.go:2294 +0x29\nminiflux.app/v2/internal/ui.(*middleware).handleAppSession-fm.(*middleware).handleAppSession.func1({0x109f238, 0xc000390380}, 0xc0001dedc0)\n\t/home/fred/repos/miniflux/v2/internal/ui/middleware.go:131 +0x955\nnet/http.HandlerFunc.ServeHTTP(0xc000cf1cc0?, {0x109f238?, 0xc000390380?}, 0x10928d8?)\n\t/usr/local/go/src/net/http/server.go:2294 +0x29\nminiflux.app/v2/internal/ui.(*middleware).handleUserSession-fm.(*middleware).handleUserSession.func1({0x109f238, 0xc000390380}, 0xc000cf1cc0)\n\t/home/fred/repos/miniflux/v2/internal/ui/middleware.go:59 +0x368\nnet/http.HandlerFunc.ServeHTTP(0xc000e421e0?, {0x109f238?, 0xc000390380?}, 0x1094f58?)\n\t/usr/local/go/src/net/http/server.go:2294 +0x29\nminiflux.app/v2/internal/http/server.middleware.func1({0x109f238, 0xc000390380}, 0xc000cf1b80)\n\t/home/fred/repos/miniflux/v2/internal/http/server/middleware.go:43 +0x351\nnet/http.HandlerFunc.ServeHTTP(0xc000cf1a40?, {0x109f238?, 0xc000390380?}, 0x17612a0?)\n\t/usr/local/go/src/net/http/server.go:2294 +0x29\ngithub.com/gorilla/mux.(*Router).ServeHTTP(0xc0000d8000, {0x109f238, 0xc000390380}, 0xc000cf1900)\n\t/home/fred/go/pkg/mod/github.com/gorilla/[email protected]/mux.go:212 +0x1e2\nnet/http.serverHandler.ServeHTTP({0xc000e421b0?}, {0x109f238?, 0xc000390380?}, 0x6?)\n\t/usr/local/go/src/net/http/server.go:3301 +0x8e\nnet/http.(*conn).serve(0xc0001d7b90, {0x10a0e58, 0xc000ccc270})\n\t/usr/local/go/src/net/http/server.go:2102 +0x625\ncreated by net/http.(*Server).Serve in goroutine 35\n\t/usr/local/go/src/net/http/server.go:3454 +0x485
Some users aren't using those, and it thus makes sense to provide a way to disable them, as they expose quite a lot of sensitive-ish features.
|
I'll try to implement the UI-side of this this week :) |
|
Superseded by #3543 |
Some users aren't using those, it thus makes sense to provide a way to disable them, as they expose quite a lot of sensitive-ish features.