Skip to content

Commit 7941309

Browse files
committed
cookie session 和 https-redirect 提供对 koa 的支持
1 parent 3f0cdd9 commit 7941309

16 files changed

Lines changed: 438 additions & 284 deletions

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ node_js:
55

66
script:
77
- npm test
8-
- FRAMEWORK=koa npm test
8+
- npm run test-koa

API.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -204,26 +204,33 @@ AV.Cloud.httpRequest({
204204
205205
### cookie-session
206206
207-
该中间件提供了在 Express 中维护用户状态的能力
207+
该中间件提供了在 Express 或 Koa 中维护用户状态的能力,在 Express 中:
208208
209209
```javascript
210-
app.use(AV.Cloud.CookieSession({ secret: 'my secret', maxAge: 3600000, fetchUser: true }));
210+
app.use(AV.Cloud.CookieSession({secret: 'my secret', maxAge: 3600000, fetchUser: true}));
211+
```
212+
213+
在 Koa 中(添加 `framework: 'koa'` 参数):
214+
215+
```javascript
216+
app.use(AV.Cloud.CookieSession({framework: 'koa', secret: 'my secret', maxAge: 3600000, fetchUser: true}));
211217
```
212218
213219
参数包括:
214220
221+
* `koa?: boolean`:返回一个 koa(而不是 express)中间件。
215222
* `secret: string`:对 Cookie 进行签名的密钥,请选用一个随机字符串。
216223
* `name?: string`:Cookie 名称,默认为 `avos.sess`
217224
* `maxAge?: number`:Cookie 过期时间。
218225
* `fetchUser?: boolean`:是否自动查询用户信息,默认为 `false`,即不自动查询,这种情况下只能访问用户的 `id``sessionToken`.
219226
* `httpOnly?: boolean`: 不允许客户端读写该 Cookie,默认 `false`.
220227
221-
express 的 `Request` 上会有这些属性可用:
228+
express 的 `Request`(或 koa 的 `ctx.request`上会有这些属性可用:
222229
223230
* `currentUser?: AV.User`:和当前客户端关联的用户信息(根据 Cookie),如未开启 `cookie-session``fetchUser` 选项则只可以访问 `id``sessionToken`.
224231
* `sessionToken?: string`:和当前客户端关联的 `sessionToken`(根据 Cookie)。
225232
226-
express 的 `Response` 上会有这些属性可用:
233+
express 的 `Response`(或 koa 的 `ctx.response`上会有这些属性可用:
227234
228235
* `saveCurrentUser(user: AV.User)`:将当前客户端与特定用户关联(会写入 Cookie)。
229236
* `clearCurrentUser()`:清除当前客户端关联的用户(删除 Cookie)。
@@ -232,8 +239,16 @@ express 的 `Response` 上会有这些属性可用:
232239
233240
### https-redirect
234241
235-
该中间件会自动将 HTTP 请求重定向到 HTTPS 上:
242+
该中间件会自动将 HTTP 请求重定向到 HTTPS 上,在 Express 中
236243
237244
```javascript
245+
app.enable('trust proxy');
238246
app.use(AV.Cloud.HttpsRedirect());
239247
```
248+
249+
Koa 中(添加 `framework: 'koa'` 参数):
250+
251+
```javascript
252+
app.proxy = true;
253+
app.use(AV.Cloud.HttpsRedirect({framework: 'koa'}));
254+
```

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ npm install leanengine@next --save
2222
* 网站托管开发指南:<https://leancloud.cn/docs/leanengine_webhosting_guide-node.html>
2323
* 云函数开发指南:<https://leancloud.cn/docs/leanengine_cloudfunction_guide-node.html>
2424
* 云引擎命令行工具使用详解:<https://leancloud.cn/docs/leanengine_cli.html>
25+
* API 参考文档:<https://github.com/leancloud/leanengine-node-sdk/blob/master/API.md>
2526

2627
## 项目示例
2728

lib/leanengine.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,21 @@ if (https.globalAgent && https.globalAgent.options) {
6060
https.globalAgent.options.rejectUnauthorized = false;
6161
}
6262

63-
AV.Cloud.CookieSession = require('../middleware/cookie-session')(AV);
64-
AV.Cloud.HttpsRedirect = require('../middleware/https-redirect')(AV);
63+
AV.Cloud.CookieSession = function(options) {
64+
if (options && options.framework == 'koa') {
65+
return require('../middleware/cookie-session-koa')(AV)(options);
66+
} else {
67+
return require('../middleware/cookie-session')(AV)(options);
68+
}
69+
};
70+
71+
AV.Cloud.HttpsRedirect = function(options) {
72+
if (options && options.framework == 'koa') {
73+
return require('../middleware/https-redirect-koa')(AV)(options);
74+
} else {
75+
return require('../middleware/https-redirect')(AV)(options);
76+
}
77+
}
6578

6679
function createCloudFunctionRouter(options) {
6780
options = options || {};

lib/utils.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,7 @@ exports.prepareResponseObject = function(res, callback) {
7474
var getRemoveAddress = exports.getRemoveAddress = function(req) {
7575
return req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress
7676
};
77+
78+
exports.endsWith = function(str, suffix) {
79+
return str.indexOf(suffix, str.length - suffix.length) !== -1;
80+
};

middleware/cookie-session-koa.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module.exports = function(AV) {
2+
return function (options) {
3+
var middleware = require('./cookie-session')(AV)(options);
4+
5+
return function *(next) {
6+
yield middleware.bind(null, this.req, this.res);
7+
8+
this.request.currentUser = this.req.currentUser;
9+
this.request.sessionToken = this.req.sessionToken;
10+
11+
this.response.saveCurrentUser = this.res.saveCurrentUser;
12+
this.response.clearCurrentUser = this.res.clearCurrentUser;
13+
14+
yield next;
15+
}
16+
};
17+
};

middleware/cookie-session.js

Lines changed: 103 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,120 @@
1-
/**
2-
* update from cookie-sesion middleware
3-
*/
4-
(function() {
5-
'use strict';
6-
var Cookies = require('cookies');
7-
var onHeaders = require('on-headers');
8-
var debug = require('debug')('AV:cookieSession');
9-
10-
module.exports = function(AV) {
11-
return function(opts) {
12-
opts = opts || {};
13-
14-
// name - previously "opts.key"
15-
var name = opts.name || opts.key || 'avos:sess';
16-
17-
// secrets
18-
var keys = opts.keys;
19-
if (!keys && opts.secret) {
20-
keys = [opts.secret];
21-
}
1+
'use strict';
222

23-
// defaults
24-
if (!opts.overwrite) {
25-
opts.overwrite = true;
26-
}
27-
opts.httpOnly = true;
28-
opts.signed = true;
3+
var Cookies = require('cookies');
4+
var onHeaders = require('on-headers');
5+
var debug = require('debug')('AV:cookieSession');
296

30-
if (!keys && opts.signed) {
31-
throw new Error('.keys required for avos cookie sessions.');
32-
}
7+
module.exports = function(AV) {
8+
return function(opts) {
9+
opts = opts || {};
3310

34-
debug('session options %j', opts);
11+
// name - previously "opts.key"
12+
var name = opts.name || opts.key || 'avos:sess';
3513

36-
return function cookieSession(req, res, next) {
37-
var cookies = req.sessionCookies = new Cookies(req, res, keys);
38-
var responseUser;
14+
// secrets
15+
var keys = opts.keys;
16+
if (!keys && opts.secret) {
17+
keys = [opts.secret];
18+
}
3919

40-
// 兼容 connect
41-
if (!res.req) res.req = req;
42-
if (!req.res) req.res = res;
20+
// defaults
21+
if (!opts.overwrite) {
22+
opts.overwrite = true;
23+
}
24+
opts.httpOnly = true;
25+
opts.signed = true;
4326

44-
// to pass to Session()
45-
req.sessionOptions = opts;
46-
req.sessionKey = name;
27+
if (!keys && opts.signed) {
28+
throw new Error('.keys required for avos cookie sessions.');
29+
}
4730

48-
res.saveCurrentUser = function(user) {
49-
responseUser = user;
50-
};
31+
debug('session options %j', opts);
5132

52-
res.clearCurrentUser = function() {
53-
responseUser = null;
54-
};
33+
return function cookieSession(req, res, next) {
34+
var cookies = req.sessionCookies = new Cookies(req, res, keys);
35+
var responseUser;
5536

56-
onHeaders(res, function setHeaders() {
57-
var session = null;
37+
// 兼容 connect
38+
if (!res.req) res.req = req;
39+
if (!req.res) req.res = res;
5840

59-
if (responseUser) {
60-
session = {
61-
_uid: responseUser.id,
62-
_sessionToken: responseUser._sessionToken
63-
};
41+
// to pass to Session()
42+
req.sessionOptions = opts;
43+
req.sessionKey = name;
6444

65-
debug('session %j', session);
66-
cookies.set(name, encode(session), opts);
67-
} else if (responseUser === null) {
68-
debug('clear session');
69-
cookies.set(name, '', opts);
70-
}
71-
});
45+
res.saveCurrentUser = function(user) {
46+
responseUser = user;
47+
};
48+
49+
res.clearCurrentUser = function() {
50+
responseUser = null;
51+
};
7252

73-
var session = {};
74-
var json = cookies.get(name, opts);
75-
if (json) {
76-
session = decode(json);
53+
onHeaders(res, function setHeaders() {
54+
var session = null;
55+
56+
if (responseUser) {
57+
session = {
58+
_uid: responseUser.id,
59+
_sessionToken: responseUser._sessionToken
60+
};
61+
62+
debug('session %j', session);
63+
cookies.set(name, encode(session), opts);
64+
} else if (responseUser === null) {
65+
debug('clear session');
66+
cookies.set(name, '', opts);
7767
}
78-
var uid = session._uid;
79-
var sessionToken = session._sessionToken;
80-
req.AV = req.AV || {};
81-
if (uid && sessionToken) {
82-
AV.Cloud.logInByIdAndSessionToken(uid, sessionToken, opts.fetchUser, function(err, user) {
83-
if(err) {
84-
debug('sessionToken invalid, uid: %s', uid);
85-
} else {
86-
req.AV.user = user;
87-
req.currentUser = user;
88-
req.sessionToken = user.getSessionToken();
89-
}
90-
return next();
91-
});
92-
} else {
68+
});
69+
70+
var session = {};
71+
var json = cookies.get(name, opts);
72+
if (json) {
73+
session = decode(json);
74+
}
75+
var uid = session._uid;
76+
var sessionToken = session._sessionToken;
77+
req.AV = req.AV || {};
78+
if (uid && sessionToken) {
79+
AV.Cloud.logInByIdAndSessionToken(uid, sessionToken, opts.fetchUser, function(err, user) {
80+
if(err) {
81+
debug('sessionToken invalid, uid: %s', uid);
82+
} else {
83+
req.AV.user = user;
84+
req.currentUser = user;
85+
req.sessionToken = user.getSessionToken();
86+
}
9387
return next();
94-
}
95-
};
88+
});
89+
} else {
90+
return next();
91+
}
9692
};
9793
};
94+
};
95+
96+
/**
97+
* Decode the base64 cookie value to an object.
98+
*
99+
* @param {String} string
100+
* @return {Object}
101+
* @private
102+
*/
103+
104+
function decode(string) {
105+
var body = new Buffer(string, 'base64').toString('utf8');
106+
return JSON.parse(body);
107+
}
108+
109+
/**
110+
* Encode an object into a base64-encoded JSON string.
111+
*
112+
* @param {Object} body
113+
* @return {String}
114+
* @private
115+
*/
98116

99-
/**
100-
* Decode the base64 cookie value to an object.
101-
*
102-
* @param {String} string
103-
* @return {Object}
104-
* @private
105-
*/
106-
107-
function decode(string) {
108-
var body = new Buffer(string, 'base64').toString('utf8');
109-
return JSON.parse(body);
110-
}
111-
112-
/**
113-
* Encode an object into a base64-encoded JSON string.
114-
*
115-
* @param {Object} body
116-
* @return {String}
117-
* @private
118-
*/
119-
120-
function encode(body) {
121-
body = JSON.stringify(body);
122-
return new Buffer(body).toString('base64');
123-
}
124-
}).call(this);
117+
function encode(body) {
118+
body = JSON.stringify(body);
119+
return new Buffer(body).toString('base64');
120+
}

middleware/https-redirect-koa.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
var endsWith = require('../lib/utils').endsWith;
2+
3+
module.exports = function(AV) {
4+
return function() {
5+
return function *(next) {
6+
if ((AV.Cloud.__prod || endsWith(this.request.hostname, '.leanapp.cn')) && (!this.request.secure)) {
7+
this.response.redirect('https://' + this.request.hostname + this.request.originalUrl);
8+
} else {
9+
yield next;
10+
}
11+
}
12+
}
13+
};

middleware/https-redirect.js

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
1-
// Generated by CoffeeScript 1.8.0
2-
(function() {
3-
'use strict';
4-
module.exports = function(AV) {
5-
return function() {
6-
return function(req, res, next) {
7-
if ((AV.Cloud.__prod || endsWith(req.get('host'), '.leanapp.cn')) && (!req.secure)) {
8-
return res.redirect('https://' + req.get('host') + req.originalUrl);
9-
} else {
10-
return next();
11-
}
12-
};
13-
};
14-
};
1+
'use strict';
152

16-
function endsWith(str, suffix) {
17-
return str.indexOf(suffix, str.length - suffix.length) !== -1;
18-
}
3+
var endsWith = require('../lib/utils').endsWith;
194

20-
}).call(this);
5+
module.exports = function(AV) {
6+
return function() {
7+
return function(req, res, next) {
8+
if ((AV.Cloud.__prod || endsWith(req.get('host'), '.leanapp.cn')) && (!req.secure)) {
9+
return res.redirect('https://' + req.get('host') + req.originalUrl);
10+
} else {
11+
return next();
12+
}
13+
}
14+
}
15+
};

0 commit comments

Comments
 (0)