feat: use simple access keys for access control (#4643)

This commit is contained in:
Henry Wang
2020-05-06 13:14:28 +01:00
committed by GitHub
parent 581a469f9c
commit 4a46db99f9
7 changed files with 175 additions and 43 deletions

View File

@@ -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, eghttp://usernam3:passw0rd@localhost:1200/protected/rsshub/routes.
When adding feeds using RSS readers with HTTP Basic Authentication support, authentication information is required, eghttp://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: <https://rsshub.app/qdaily/column/59?code=0f820530128805ffc10351f22b5fd121>
- Or using `key` directly, eg: <https://rsshub.app/qdaily/column/59?key=ILoveRSSHub>
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

View File

@@ -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 <https://rsshub.app/bilibili/user/article/334958638?mode=fulltext>
## 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
举例: <https://rsshub.app/pnas/latest?scihub=1>
### 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.

View File

@@ -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` 访问路由,例如:<https://rsshub.app/qdaily/column/59?code=0f820530128805ffc10351f22b5fd121>
- 或使用访问密钥 `key` 直接访问所有路由,例如:<https://rsshub.app/qdaily/column/59?key=ILoveRSSHub>
访问密钥/码与黑白名单的访问控制关系如下:
| | 在白名单中 | 在黑名单中 | 正确访问密钥/码 | 错误访问密钥/码 | 无访问密钥/码 |
| ---------- | ---------- | ---------- | --------------- | --------------- | ------------- |
| 在白名单中 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 在黑名单中 | ✅ | ❌ | ✅ | ❌ | ❌ |
### 其他应用配置
@@ -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. 打开 <https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid=0&type=8> 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. 找到 <http://api.duanshu.com/h5/content/detail/> 开头的请求 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. 找到 <https://bbs.pku.edu.cn/v2/home.php> 请求 5. 找到请求头中的 Cookie
- `PKUBBS_COOKIE`: BBS 注册用户登录后的 Cookie 值获取方式1. 登录后打开论坛首页 2. 打开控制台 3. 刷新 4. 找到 <https://bbs.pku.edu.cn/v2/home.php> 请求 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 设置,用于科学期刊路由。

View File

@@ -45,16 +45,20 @@ filter_case_sensitive 过滤是否区分大小写filter 和 filterout 同时
## 条数限制
可以使用 limit 参数限制最大条数, 主要用于排行榜类 RSS
可以使用 `limit` 参数限制最大条数, 主要用于排行榜类 RSS
举例: bilibili 排行榜前 10 <https://rsshub.app/bilibili/ranking/0/3?limit=10>
## 全文输出
可以使用 mode 参数来开启自动提取全文内容功能
可以使用 `mode` 参数来开启自动提取全文内容功能
举例: bilibili 专栏全文输出 <https://rsshub.app/bilibili/user/article/334958638?mode=fulltext>
## 访问控制
可以使用 `code``key` 进行访问控制。参考[访问控制配置](install/#fang-wen-mi-yue-ma)。
## 输出 Telegram 即时预览链接
可以输出 Telegram 可识别的即时预览链接, 主要用于文章类 RSS

View File

@@ -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,

View File

@@ -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);
}
};

View File

@@ -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);
});
});