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 all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
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
55 changes: 49 additions & 6 deletions src/auth.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#SPDX-License-Identifier: AGPL-3.0-only
import std/[asyncdispatch, times, json, random, sequtils, strutils, tables, packedsets, os]
import std/[httpclient, asyncdispatch, times, json, random, sequtils, strutils, tables, packedsets, os, uri]
import nimcrypto
import types
import experimental/parser/guestaccount

Expand Down Expand Up @@ -197,13 +198,55 @@ 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

# wait for a few seconds before fetching guest accounts, so that
# /.well-known/... is served correctly
await sleepAsync(10 * 1000)

while true:
if accountPool.len == 0:
zedeus marked this conversation as resolved.
Show resolved Hide resolved
log "fetching more accounts from service"

let client = newAsyncHttpClient("nitter-accounts")

try:
let resp = await client.get($(cfg.guestAccountsPoolUrl ? {"id": cfg.guestAccountsPoolId, "auth": cfg.guestAccountsPoolAuth}))
let guestAccounts = await resp.body

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

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

except Exception as e:
log "failed to fetch from accounts service: ", e.msg
finally:
client.close()

accountPool.keepItIf(not it.hasExpired)

await sleepAsync(3600 * 1000)

proc getAuthHash*(cfg: Config): string =
if cfg.guestAccountsPoolAuth.len == 0:
# 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 empty, authentication with accounts service will fail"
return ""

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

return hashStr.toLowerAscii
12 changes: 9 additions & 3 deletions src/config.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: AGPL-3.0-only
import parsecfg except Config
import std/parsecfg except Config
import std/uri
import types, strutils

proc get*[T](config: parseCfg.Config; section, key: string; default: T): T =
Expand Down Expand Up @@ -36,11 +37,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: parseUri(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-auth":
cond cfg.guestAccountsUsePool
resp Http200, {"content-type": "text/plain"}, getAuthHash(cfg)
8 changes: 6 additions & 2 deletions src/types.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: AGPL-3.0-only
import times, sequtils, options, tables
import std/[times, sequtils, options, tables, uri]
import prefs_impl

genPrefsType()
Expand Down Expand Up @@ -257,12 +257,16 @@ type

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

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

rssCacheTime*: int
listCacheTime*: int

Expand Down