Skip to content
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

Add functionality to fetch guest accounts on demand #1116

Open
wants to merge 9 commits into
base: guest_accounts
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 8 additions & 6 deletions nitter.example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ enableRSS = true # set this to false to disable RSS feeds
enableDebug = false # enable request logs and debug endpoints (/.accounts)
proxy = "" # http/https url, SOCKS proxies are not supported
proxyAuth = ""
tokenCount = 10
# minimum amount of usable tokens. tokens are used to authorize API requests,
# but they expire after ~1 hour, and have a limit of 500 requests per endpoint.
# the limits reset every 15 minutes, and the pool is filled up so there's
# always at least `tokenCount` usable tokens. only increase this if you receive
# major bursts all the time and don't have a rate limiting setup via e.g. nginx

# Instead of guest_accounts.json, fetch guest accounts from an external URL.
# Nitter will re-fetch accounts if it runs out of valid ones.
[GuestAccounts]
usePool = false # enable fetching accounts from external pool.
poolUrl = "" # https://example.com/download
poolId = "" # defaults to nitter's hostname
poolAuth = "" # random secret string for authentication

# Change default preferences here, see src/prefs_impl.nim for a complete list
[Preferences]
Expand Down
47 changes: 40 additions & 7 deletions src/auth.nim
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#SPDX-License-Identifier: AGPL-3.0-only
import std/[asyncdispatch, times, json, random, sequtils, strutils, tables, packedsets, os]
import types
import std/[httpclient, asyncdispatch, times, json, random, sequtils, strutils, tables, packedsets, os]
import nimcrypto
import types, http_pool
import experimental/parser/guestaccount

# max requests at a time per account to avoid race conditions
Expand Down Expand Up @@ -197,13 +198,45 @@ proc initAccountPool*(cfg: Config; path: string) =
elif fileExists(path):
log "Parsing JSON guest accounts file: ", path
accountPool = parseGuestAccounts(path)
else:
echo "[accounts] ERROR: ", path, " not found. This file is required to authenticate API requests."
elif not cfg.guestAccountsUsePool:
echo "[accounts] ERROR: ", path, " not found. This file is required to authenticate API requests. Alternatively, configure the guest account pool in nitter.conf"
quit 1

let accountsPrePurge = accountPool.len
accountPool.keepItIf(not it.hasExpired)

log "Successfully added ", accountPool.len, " valid accounts."
if accountsPrePurge > accountPool.len:
log "Purged ", accountsPrePurge - accountPool.len, " expired accounts."

proc updateAccountPool*(cfg: Config) {.async.} =
if not cfg.guestAccountsUsePool:
return

while true:
if accountPool.len == 0:
zedeus marked this conversation as resolved.
Show resolved Hide resolved
let pool = HttpPool()
drpepper66 marked this conversation as resolved.
Show resolved Hide resolved

log "fetching more accounts from service"
pool.use(newHttpHeaders()):
let resp = await c.get("$1?id=$2&auth=$3" % [cfg.guestAccountsPoolUrl, cfg.guestAccountsPoolId, cfg.guestAccountsPoolAuth])
zedeus marked this conversation as resolved.
Show resolved Hide resolved
let guestAccounts = await resp.body

log "status code from service: ", resp.status

for line in guestAccounts.splitLines:
if line != "":
accountPool.add parseGuestAccount(line)

accountPool.keepItIf(not it.hasExpired)

await sleepAsync(3600)
zedeus marked this conversation as resolved.
Show resolved Hide resolved

proc getAuthHash*(cfg: Config): string =
if cfg.guestAccountsPoolAuth == "":
zedeus marked this conversation as resolved.
Show resolved Hide resolved
# If somebody turns on pool auth and provides a dummy key, we should
# prevent third parties from using that mis-configured auth and impersonate
# this instance
log "poolAuth is set to bogus value, responding with empty string"
zedeus marked this conversation as resolved.
Show resolved Hide resolved
return ""

let hashStr = $sha_256.digest(cfg.guestAccountsPoolAuth)

return hashStr.toLowerAscii
9 changes: 7 additions & 2 deletions src/config.nim
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,16 @@ proc getConfig*(path: string): (Config, parseCfg.Config) =
# Config
hmacKey: cfg.get("Config", "hmacKey", "secretkey"),
base64Media: cfg.get("Config", "base64Media", false),
minTokens: cfg.get("Config", "tokenCount", 10),
enableRss: cfg.get("Config", "enableRSS", true),
enableDebug: cfg.get("Config", "enableDebug", false),
proxy: cfg.get("Config", "proxy", ""),
proxyAuth: cfg.get("Config", "proxyAuth", "")
proxyAuth: cfg.get("Config", "proxyAuth", ""),

# GuestAccounts
guestAccountsUsePool: cfg.get("GuestAccounts", "usePool", false),
guestAccountsPoolUrl: cfg.get("GuestAccounts", "poolUrl", ""),
guestAccountsPoolAuth: cfg.get("GuestAccounts", "poolAuth", ""),
guestAccountsPoolId: cfg.get("GuestAccounts", "poolId", cfg.get("Server", "hostname", ""))
)

return (conf, cfg)
5 changes: 4 additions & 1 deletion src/nitter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import types, config, prefs, formatters, redis_cache, http_pool, auth
import views/[general, about]
import routes/[
preferences, timeline, status, media, search, rss, list, debug,
unsupported, embed, resolver, router_utils]
unsupported, embed, resolver, router_utils, auth]

const instancesUrl = "https://github.com/zedeus/nitter/wiki/Instances"
const issuesUrl = "https://github.com/zedeus/nitter/issues"
Expand All @@ -22,6 +22,7 @@ let
accountsPath = getEnv("NITTER_ACCOUNTS_FILE", "./guest_accounts.json")

initAccountPool(cfg, accountsPath)
asyncCheck updateAccountPool(cfg)

if not cfg.enableDebug:
# Silence Jester's query warning
Expand Down Expand Up @@ -54,6 +55,7 @@ createMediaRouter(cfg)
createEmbedRouter(cfg)
createRssRouter(cfg)
createDebugRouter(cfg)
createAuthRouter(cfg)

settings:
port = Port(cfg.port)
Expand Down Expand Up @@ -108,3 +110,4 @@ routes:
extend embed, ""
extend debug, ""
extend unsupported, ""
extend auth, ""
12 changes: 12 additions & 0 deletions src/routes/auth.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: AGPL-3.0-only

import jester

import router_utils
import ".."/[types, auth]

proc createAuthRouter*(cfg: Config) =
router auth:
get "/.well-known/nitter-request-auth":
cond cfg.guestAccountsUsePool
resp Http200, {"content-type": "text/plain"}, getAuthHash(cfg)
6 changes: 5 additions & 1 deletion src/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,16 @@ type

hmacKey*: string
base64Media*: bool
minTokens*: int
enableRss*: bool
enableDebug*: bool
proxy*: string
proxyAuth*: string

guestAccountsUsePool*: bool
guestAccountsPoolUrl*: string
guestAccountsPoolId*: string
guestAccountsPoolAuth*: string

rssCacheTime*: int
listCacheTime*: int

Expand Down