diff --git a/docs/en/install/README.md b/docs/en/install/README.md index 87f02f8bac..eb265abde5 100644 --- a/docs/en/install/README.md +++ b/docs/en/install/README.md @@ -315,7 +315,9 @@ Partial routes have a strict anti-crawler policy, and can be configured to use p Routes in `protected_route.js` will be protected using HTTP Basic Authentication. -When adding feeds using RSS readers with HTTP Basic Authentication support, authentication information is required, eg:http://usernam3:passw0rd@localhost:1200/protected/rsshub/routes. +When adding feeds using RSS readers with HTTP Basic Authentication support, authentication information is required, eg:http://usernam3:passw0rd@rsshub.app/protected/rsshub/routes. + +For readers that do not support HTTP Basic authentication, please refer to [Access Control Configuration](#access-control-configuration). `HTTP_BASIC_AUTH_NAME`: Http basic authentication username, default to `usernam3`, please change asap @@ -323,11 +325,36 @@ When adding feeds using RSS readers with HTTP Basic Authentication support, auth ### Access Control Configuration -Access control includes a whitelist and a blacklist, support IP and route, use `,` as the delimiter to separate multiple values. When both are defined, values in `BLACKLIST` will be disregarded. +RSSHub supports access control via access key/code, whitelisting and blacklisting, enabling any will activate access control for all routes. + +#### White/blacklisting + +- `WHITELIST`: the blacklist. When set, values in `BLACKLIST` are disregarded - `BLACKLIST`: the blacklist -- `WHITELIST`: the blacklist. When set, values in `BLACKLIST` are disregarded. +White/blacklisting support IP and route as values. Use `,` as the delimiter to separate multiple values, eg: `WHITELIST=1.1.1.1,2.2.2.2,/qdaily/column/59` + +#### Access Key/Code + +- `ACCESS_KEY`: the access key. When set, access via the key directly or the access code described above + +Access code is the md5 generated based on the access key + route, eg: + +| Access key | Route | Generating access code | Access code | +| ----------- | ----------------- | ---------------------------------------- | -------------------------------- | +| ILoveRSSHub | /qdaily/column/59 | md5('/qdaily/column/59' + 'ILoveRSSHub') | 0f820530128805ffc10351f22b5fd121 | + +- Routes are accessible via `code`, eg: + +- Or using `key` directly, eg: + +See the relation between access key/code and white/blacklisting. + +| | Whitelisted | Blacklisted | Correct access key/code | Wrong access key/code | No access key/code | +| ----------- | ----------- | ----------- | ----------------------- | --------------------- | ------------------ | +| Whitelisted | ✅ | ✅ | ✅ | ✅ | ✅ | +| Blacklisted | ✅ | ❌ | ✅ | ❌ | ❌ | ### Other Application Configurations diff --git a/docs/en/parameter.md b/docs/en/parameter.md index 94449840cc..77e289ebbb 100644 --- a/docs/en/parameter.md +++ b/docs/en/parameter.md @@ -42,7 +42,7 @@ Default: `true` Example: [https://rsshub.app/dribbble/popular?filter=BluE|yeLLow|BlaCK&filter_case_sensitive=false](https://rsshub.app/dribbble/popular?filter=BluE|yeLLow|BlaCK&filter_case_sensitive=false) -### Limit Entries +## Limit Entries Set `limit` to limit the number of articles in the feed. @@ -54,6 +54,10 @@ Enable fulltext via `mode` parameter. Eg: bilibili article +## Access Control + +Set `key` or `code` to grant access to requests. See [Access Control Configuration](install/#access-key-code). + ## Telegram Instant View Replace website link with Telegram's Instant View link. @@ -72,7 +76,7 @@ Output Sci-hub link in scientific journal routes, this supports major journals o 举例: -### Output Formats +## Output Formats RSSHub conforms to RSS 2.0 and Atom Standard, simply append `.rss` `.atom` to the end of the feed address to obtain the feed in corresponding format, default to RSS 2.0. diff --git a/docs/install/README.md b/docs/install/README.md index 2b03316c55..b3ac3e05c8 100644 --- a/docs/install/README.md +++ b/docs/install/README.md @@ -6,14 +6,14 @@ sidebar: auto 部署 RSSHub 需要基本的计算机编程常识,如果您在部署过程中遇到无法解决的问题请到 [issues](https://github.com/DIYgod/RSSHub/issues) 寻找类似的问题或 [向我们提问](https://github.com/DIYgod/RSSHub/issues/new/choose),我们会尽快给您答复 -部署涉及到以下基本编程常识: +部署涉及到以下基本编程常识: 1. 命令行操作 1. [Git](https://git-scm.com/) 1. [Node.js](https://nodejs.org/) 1. [npm](https://www.npmjs.com/get-npm) 或 [yarn](https://yarnpkg.com/zh-Hans/docs/install) -部署到可外网访问则可能涉及到: +部署到可外网访问则可能涉及到: 1. [Nginx](https://www.nginx.com/resources/wiki/start/topics/tutorials/install/) 1. [Docker](https://www.docker.com/get-started) 或 [docker-compose](https://docs.docker.com/compose/install/) @@ -96,7 +96,7 @@ $ docker rm rsshub 配置运行在 docker 中的 RSSHub,最便利的方法是使用 docker 环境变量 -以设置缓存时间为 1 小时举例,只需要在运行时增加参数: `-e CACHE_EXPIRE=3600` +以设置缓存时间为 1 小时举例,只需要在运行时增加参数:`-e CACHE_EXPIRE=3600` ```bash $ docker run -d --name rsshub -p 1200:1200 -e CACHE_EXPIRE=3600 -e GITHUB_ACCESS_TOKEN=example diygod/rsshub @@ -198,7 +198,7 @@ $ git pull [Before you begin](https://cloud.google.com/appengine/docs/flexible/nodejs/quickstart) -按照这里的引导完成 GCP 账号设置,创建 GCP 项目,创建 App Engine 项目,开通付费功能(必须),安装 git 与 gcloud 工具。并完成 gcloud 工具的初始化,初始化具体方式[请查看这个链接](https://cloud.google.com/sdk/gcloud/?hl=zh-CN)。如果你不打算在本地调试本项目,可以不安装 Node.js 环境。 +按照这里的引导完成 GCP 账号设置,创建 GCP 项目,创建 App Engine 项目,开通付费功能(必须),安装 git 与 gcloud 工具。并完成 gcloud 工具的初始化,初始化具体方式 [请查看这个链接](https://cloud.google.com/sdk/gcloud/?hl=zh-CN)。如果你不打算在本地调试本项目,可以不安装 Node.js 环境。 请注意,GAE 免费用量不支持 Flexible Environment,部署到 Flexible Environment 前请确认收费标准。 @@ -315,11 +315,13 @@ RSSHub 支持 `memory` 和 `redis` 两种缓存方式 `PROXY_URL_REGEX`: 启用代理的 URL 正则表达式,默认全部开启 `.*` -### 用户认证配置 +### 用户认证 `protected_route.js` 内的路由将启用 HTTP Basic Authentication 认证 -支持该认证协议的阅读器,在添加源地址时,需要在源地址前添加认证信息,例如:http://usernam3:passw0rd@127.0.0.1:1200/protected/rsshub/routes +支持该认证协议的阅读器,在添加源地址时,需要在源地址前添加认证信息,例如:http://usernam3:passw0rd@rsshub.app/protected/rsshub/routes。 + +对于不支持该认证协议的阅读器,请参考 [访问控制配置](#fang-wen-kong-zhi-pei-zhi)。 `HTTP_BASIC_AUTH_NAME`: Http basic authentication 用户名,默认为 `usernam3`,请务必修改 @@ -327,11 +329,36 @@ RSSHub 支持 `memory` 和 `redis` 两种缓存方式 ### 访问控制配置 -配置黑名单和白名单,支持 IP 和路由,设置多项时用英文逗号 `,` 隔开,同时设置黑名单和白名单时仅白名单有效 +RSSHub 支持使用访问密钥/码,白名单和黑名单三种方式进行访问控制。开启任意选项将会激活全局访问控制,没有访问权限将会导致访问被拒绝。 + +#### 黑白名单 + +- `WHITELIST`: 白名单,设置白名单后黑名单无效 - `BLACKLIST`: 黑名单 -- `WHITELIST`: 白名单,设置白名单后黑名单无效 +黑白名单支持 IP 和路由,设置多项时用英文逗号 `,` 隔开,例如 `WHITELIST=1.1.1.1,2.2.2.2,/qdaily/column/59` + +#### 访问密钥/码 + +- `ACCESS_KEY`: 访问密钥,用于直接访问所有路由或者生成访问码 + +访问码为 访问密钥 + 路由 共同生成的 md5,例如: + +| 访问密钥 | 路由 | 生成过程 | 访问码 | +| ----------- | ----------------- | ---------------------------------------- | -------------------------------- | +| ILoveRSSHub | /qdaily/column/59 | md5('/qdaily/column/59' + 'ILoveRSSHub') | 0f820530128805ffc10351f22b5fd121 | + +- 此时可以通过 `code` 访问路由,例如: + +- 或使用访问密钥 `key` 直接访问所有路由,例如: + +访问密钥/码与黑白名单的访问控制关系如下: + +| | 在白名单中 | 在黑名单中 | 正确访问密钥/码 | 错误访问密钥/码 | 无访问密钥/码 | +| ---------- | ---------- | ---------- | --------------- | --------------- | ------------- | +| 在白名单中 | ✅ | ✅ | ✅ | ✅ | ✅ | +| 在黑名单中 | ✅ | ❌ | ✅ | ❌ | ❌ | ### 其他应用配置 @@ -347,7 +374,7 @@ RSSHub 支持 `memory` 和 `redis` 两种缓存方式 `DEBUG_INFO`: 是否在首页显示路由信息,默认 `true` -`LOGGER_LEVEL`: 指明输出到 console 和日志文件的日志的最大[等级](https://github.com/winstonjs/winston#logging-levels),默认 `info` +`LOGGER_LEVEL`: 指明输出到 console 和日志文件的日志的最大 [等级](https://github.com/winstonjs/winston#logging-levels),默认 `info` `NODE_NAME`: 节点名,用于负载均衡,识别当前节点 @@ -357,21 +384,21 @@ RSSHub 支持 `memory` 和 `redis` 两种缓存方式 `DISALLOW_ROBOT`: 阻止搜索引擎收录,默认开启,设置 false 或 0 关闭 -`HOTLINK_TEMPLATE`: 用于处理描述中图片的链接,绕过防盗链等限制,留空不生效。用法参考[#2769](https://github.com/DIYgod/RSSHub/issues/2769)。可以使用[URL](https://developer.mozilla.org/en-US/docs/Web/API/URL#Properties)的所有属性,格式为 JS 变量模板。例子:`${protocol}//${host}${pathname}`, `https://i3.wp.com/${host}${pathname}` +`HOTLINK_TEMPLATE`: 用于处理描述中图片的链接,绕过防盗链等限制,留空不生效。用法参考 [#2769](https://github.com/DIYgod/RSSHub/issues/2769)。可以使用 [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL#Properties) 的所有属性,格式为 JS 变量模板。例子:`${protocol}//${host}${pathname}`, `https://i3.wp.com/${host}${pathname}` ### 部分 RSS 模块配置 -- pixiv 全部路由: [注册地址](https://accounts.pixiv.net/signup) +- pixiv 全部路由:[注册地址](https://accounts.pixiv.net/signup) - `PIXIV_USERNAME`: Pixiv 用户名 - `PIXIV_PASSWORD`: Pixiv 密码 -- disqus 全部路由: [申请地址](https://disqus.com/api/applications/) +- disqus 全部路由:[申请地址](https://disqus.com/api/applications/) - `DISQUS_API_KEY`: Disqus API -- twitter 全部路由: [申请地址](https://apps.twitter.com) +- twitter 全部路由:[申请地址](https://apps.twitter.com) - `TWITTER_CONSUMER_KEY`: Twitter Consumer Key,支持多个 key,用英文逗号 `,` 隔开 @@ -379,15 +406,15 @@ RSSHub 支持 `memory` 和 `redis` 两种缓存方式 - `TWITTER_TOKEN_{id}`: 对应 id 的 Twitter token,`{id}` 替换为 id,值为 `consumer_key consumer_secret access_token access_token_secret` 用逗号隔开,即:`{consumer_key},{consumer_secret},{access_token},{access_token_secret}` -- youtube 全部路由: [申请地址](https://console.developers.google.com/) +- youtube 全部路由:[申请地址](https://console.developers.google.com/) - `YOUTUBE_KEY`: YouTube API Key,支持多个 key,用英文逗号 `,` 隔开 -- telegram - 贴纸包路由: [Telegram 机器人](https://telegram.org/blog/bot-revolution) +- telegram - 贴纸包路由:[Telegram 机器人](https://telegram.org/blog/bot-revolution) - `TELEGRAM_TOKEN`: Telegram 机器人 token -- github 全部路由: [申请地址](https://github.com/settings/tokens) +- github 全部路由:[申请地址](https://github.com/settings/tokens) - `GITHUB_ACCESS_TOKEN`: GitHub Access Token @@ -395,11 +422,11 @@ RSSHub 支持 `memory` 和 `redis` 两种缓存方式 - `BILIBILI_COOKIE_{uid}`: 对应 uid 的 b 站用户登录后的 Cookie 值,`{uid}` 替换为 uid,如 `BILIBILI_COOKIE_2267573`,获取方式:1. 打开 2. 打开控制台 3. 切换到 Network 面板 4. 刷新 5. 点击 dynamic_new 请求 6. 找到 Cookie -- 语雀 全部路由: [注册地址](https://www.yuque.com/register) +- 语雀 全部路由:[注册地址](https://www.yuque.com/register) - - `YUQUE_TOKEN`: 语雀 Token,[获取地址](https://www.yuque.com/settings/tokens)。语雀接口做了访问频率限制,为保证正常访问建议配置 Token,详见[语雀开发者文档](https://www.yuque.com/yuque/developer/api#5b3a1535)。 + - `YUQUE_TOKEN`: 语雀 Token,[获取地址](https://www.yuque.com/settings/tokens)。语雀接口做了访问频率限制,为保证正常访问建议配置 Token,详见 [语雀开发者文档](https://www.yuque.com/yuque/developer/api#5b3a1535)。 -- 邮箱 邮件列表路由: +- 邮箱 邮件列表路由: - `EMAIL_CONFIG_{email}`: 邮箱设置,替换 `{email}` 为 邮箱账号,邮件账户的 `@` 替换为 `.`,例如 `EMAIL_CONFIG_xxx.qq.com`。内容格式为 `password=密码&host=服务器&port=端口`,例如 `password=123456&host=imap.qq.com&port=993`。 @@ -407,26 +434,26 @@ RSSHub 支持 `memory` 和 `redis` 两种缓存方式 - `CHUINIU_MEMBER`: 吹牛部落登录后的 x-member,获取方式:1. 登陆后点开文章正文 2. 打开控制台 3. 刷新 4. 找到 开头的请求 5. 找到请求头中的 x-member -- 微博 个人时间线路由: [申请地址](https://open.weibo.com/connect) +- 微博 个人时间线路由:[申请地址](https://open.weibo.com/connect) - `WEIBO_APP_KEY`: 微博 App Key - `WEIBO_APP_SECRET`: 微博 App Secret - - `WEIBO_REDIRECT_URL`: 微博登录授权回调地址,默认为 `RSSHub地址/weibo/timeline/0`,自定义回调地址请确保最后可以转跳到 `RSSHub地址/weibo/timeline/0?code=xxx` + - `WEIBO_REDIRECT_URL`: 微博登录授权回调地址,默认为 `RSSHub 地址/weibo/timeline/0`,自定义回调地址请确保最后可以转跳到 `RSSHub 地址/weibo/timeline/0?code=xxx` -- 饭否 全部路由: [申请地址](https://github.com/FanfouAPI/FanFouAPIDoc/wiki/Oauth) +- 饭否 全部路由:[申请地址](https://github.com/FanfouAPI/FanFouAPIDoc/wiki/Oauth) - `FANFOU_CONSUMER_KEY`: 饭否 Consumer Key - `FANFOU_CONSUMER_SECRET`: 饭否 Consumer Secret - `FANFOU_USERNAME`: 饭否登录用户名、邮箱、手机号 - `FANFOU_PASSWORD`: 饭否密码 -- Last.fm 全部路由: [申请地址](https://www.last.fm/api/) +- Last.fm 全部路由:[申请地址](https://www.last.fm/api/) - `LASTFM_API_KEY`: Last.fm API Key - 北大未名 BBS 全站十大 - - `PKUBBS_COOKIE`: BBS 注册用户登录后的 Cookie 值,获取方式:1.登录后打开论坛首页 2. 打开控制台 3. 刷新 4. 找到 请求 5. 找到请求头中的 Cookie + - `PKUBBS_COOKIE`: BBS 注册用户登录后的 Cookie 值,获取方式:1. 登录后打开论坛首页 2. 打开控制台 3. 刷新 4. 找到 请求 5. 找到请求头中的 Cookie - nhentai torrent: [注册地址](https://nhentai.net/register/) @@ -435,7 +462,7 @@ RSSHub 支持 `memory` 和 `redis` 两种缓存方式 - discuz cookies 设定 - - `DISCUZ_COOKIE_{cid}`: 某 Discuz 驱动的论坛,用户注册后的 Cookie 值 , cid 可自由设定,取值范围[00, 99], 使用 discuz 通用路由时, 通过指定 cid 来调用该 cookie + - `DISCUZ_COOKIE_{cid}`: 某 Discuz 驱动的论坛,用户注册后的 Cookie 值 , cid 可自由设定,取值范围 [00, 99], 使用 discuz 通用路由时,通过指定 cid 来调用该 cookie - Sci-hub 设置,用于科学期刊路由。 diff --git a/docs/parameter.md b/docs/parameter.md index dc47c0f277..e98cffa706 100644 --- a/docs/parameter.md +++ b/docs/parameter.md @@ -45,16 +45,20 @@ filter_case_sensitive 过滤是否区分大小写,filter 和 filterout 同时 ## 条数限制 -可以使用 limit 参数限制最大条数, 主要用于排行榜类 RSS +可以使用 `limit` 参数限制最大条数, 主要用于排行榜类 RSS 举例: bilibili 排行榜前 10 ## 全文输出 -可以使用 mode 参数来开启自动提取全文内容功能 +可以使用 `mode` 参数来开启自动提取全文内容功能 举例: bilibili 专栏全文输出 +## 访问控制 + +可以使用 `code` 或 `key` 进行访问控制。参考[访问控制配置](install/#fang-wen-mi-yue-ma)。 + ## 输出 Telegram 即时预览链接 可以输出 Telegram 可识别的即时预览链接, 主要用于文章类 RSS diff --git a/lib/config.js b/lib/config.js index bd3d652775..06b08c8f62 100644 --- a/lib/config.js +++ b/lib/config.js @@ -94,6 +94,7 @@ const calculateValue = () => { }, blacklist: envs.BLACKLIST && envs.BLACKLIST.split(','), whitelist: envs.WHITELIST && envs.WHITELIST.split(','), + accessKey: envs.ACCESS_KEY, enableCluster: envs.ENABLE_CLUSTER, email: { config: email_config, diff --git a/lib/middleware/access-control.js b/lib/middleware/access-control.js index 7276b20cb6..c4b3c5ddd3 100644 --- a/lib/middleware/access-control.js +++ b/lib/middleware/access-control.js @@ -1,4 +1,5 @@ const config = require('@/config').value; +const md5 = require('@/utils/md5'); const reject = (ctx) => { ctx.response.status = 403; @@ -9,24 +10,42 @@ const reject = (ctx) => { module.exports = async (ctx, next) => { const ip = ctx.ips[0] || ctx.ip; const requestPath = ctx.request.path; + const accessKey = ctx.query.key; + const accessCode = ctx.query.code; + + const isControlled = config.accessKey || config.whitelist || config.blacklist; + + const grant = async () => { + if (ctx.response.status !== 403) { + await next(); + } + }; if (requestPath === '/') { await next(); } else { - if (config.whitelist) { - if (!(config.whitelist.includes(ip) || config.whitelist.includes(requestPath))) { - reject(ctx); - } - } else { - if (config.blacklist) { - if (config.blacklist.includes(ip) || config.blacklist.includes(requestPath)) { - reject(ctx); - } + if (!isControlled) { + return await grant(); + } + + if (config.accessKey) { + if (config.accessKey === accessKey || accessCode === md5(requestPath + config.accessKey)) { + return await grant(); } } - if (ctx.response.status !== 403) { - await next(); + if (config.whitelist) { + if (config.whitelist.includes(ip) || config.whitelist.includes(requestPath)) { + return await grant(); + } } + + if (config.blacklist) { + if (!(config.blacklist.includes(ip) || config.blacklist.includes(requestPath))) { + return await grant(); + } + } + + reject(ctx); } }; diff --git a/test/middleware/access-control.js b/test/middleware/access-control.js index 9e755da703..b6e83f9f99 100644 --- a/test/middleware/access-control.js +++ b/test/middleware/access-control.js @@ -1,4 +1,5 @@ const supertest = require('supertest'); +const md5 = require('md5'); let server; jest.mock('request-promise-native'); @@ -8,6 +9,7 @@ async function checkBlock(response) { } afterEach(() => { + delete process.env.ACCESS_KEY; delete process.env.BLACKLIST; delete process.env.WHITELIST; jest.resetModules(); @@ -16,7 +18,10 @@ afterEach(() => { describe('access-control', () => { it(`blacklist`, async () => { + const key = '1L0veRSSHub'; + const code = md5('/test/2' + key); process.env.BLACKLIST = '/test/1,233.233.233.233'; + process.env.ACCESS_KEY = key; server = require('../../lib/index'); const request = supertest(server); @@ -31,10 +36,34 @@ describe('access-control', () => { const response22 = await request.get('/test/2').set('X-Forwarded-For', '233.233.233.233'); checkBlock(response22); + + // wrong key/code, not on blacklist + const response311 = await request.get(`/test/2?key=wrong+${key}`); + expect(response311.status).toBe(200); + + const response312 = await request.get(`/test/2?code=wrong+${code}`); + expect(response312.status).toBe(200); + + // wrong key/code, on blacklist + const response321 = await request.get(`/test/2?key=wrong+${key}`).set('X-Forwarded-For', '233.233.233.233'); + checkBlock(response321); + + const response322 = await request.get(`/test/2?code=wrong+${code}`).set('X-Forwarded-For', '233.233.233.233'); + checkBlock(response322); + + // right key/code, on blacklist + const response331 = await request.get(`/test/2?key=${key}`).set('X-Forwarded-For', '233.233.233.233'); + expect(response331.status).toBe(200); + + const response332 = await request.get(`/test/2?code=${code}`).set('X-Forwarded-For', '233.233.233.233'); + expect(response332.status).toBe(200); }); it(`whitelist`, async () => { + const key = '1L0veRSSHub'; + const code = md5('/test/2' + key); process.env.WHITELIST = '/test/1,233.233.233.233'; + process.env.ACCESS_KEY = key; server = require('../../lib/index'); const request = supertest(server); @@ -49,5 +78,26 @@ describe('access-control', () => { const response22 = await request.get('/test/2').set('X-Forwarded-For', '233.233.233.233'); expect(response22.status).toBe(200); + + // wrong key/code, not on whitelist + const response311 = await request.get(`/test/2?code=wrong+${code}`); + checkBlock(response311); + + const response312 = await request.get(`/test/2?key=wrong+${key}`); + checkBlock(response312); + + // wrong key/code, on whitelist + const response321 = await request.get(`/test/2?code=wrong+${code}`).set('X-Forwarded-For', '233.233.233.233'); + expect(response321.status).toBe(200); + + const response322 = await request.get(`/test/2?key=wrong+${key}`).set('X-Forwarded-For', '233.233.233.233'); + expect(response322.status).toBe(200); + + // right key/code + const response331 = await request.get(`/test/2?code=${code}`); + expect(response331.status).toBe(200); + + const response332 = await request.get(`/test/2?key=${key}`); + expect(response332.status).toBe(200); }); });