-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
session.js
140 lines (119 loc) · 3.91 KB
/
session.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
const cookieSignature = require('cookie-signature');
const expressSession = require('express-session');
const cookie = require('cookie');
class SessionManager {
constructor({
cookieSecret = 'qwerty',
secureCookies = process.env.NODE_ENV === 'production', // Default to true in production
cookieMaxAge = 1000 * 60 * 60 * 24 * 30, // 30 days
sessionStore,
}) {
this._cookieSecret = cookieSecret;
this._secureCookies = secureCookies;
this._cookieMaxAge = cookieMaxAge;
this._sessionStore = sessionStore;
}
getSessionMiddleware({ keystone }) {
const COOKIE_NAME = 'keystone.sid';
// We have at least one auth strategy
// Setup the session as the very first thing.
// The way express works, the `req.session` (and, really, anything added
// to `req`) will be available to all sub `express()` instances.
// This way, we have one global setting for authentication / sessions that
// all routes on the server can utilize.
const injectAuthCookieMiddleware = (req, res, next) => {
if (!req.headers) {
return next();
}
const authHeader = req.headers.authorization || req.headers.Authorization;
if (!authHeader) {
return next();
}
const [type, token] = req.headers['authorization'].split(' ');
if (type !== 'Bearer') {
// TODO: Use logger
console.warn(`Got Authorization header of type ${type}, but expected Bearer`);
return next();
}
// Split the cookies out
const cookies = cookie.parse(req.headers.cookie || '');
// Construct a "fake" session cookie based on the authorization token
cookies[COOKIE_NAME] = `s:${token}`;
// Then reset the cookies so the session middleware can read it.
req.headers.cookie = Object.entries(cookies)
.map(([name, value]) => `${name}=${value}`)
.join('; ');
// Always call next
next();
};
const sessionMiddleware = expressSession({
secret: this._cookieSecret,
resave: false,
saveUninitialized: false,
name: COOKIE_NAME,
cookie: { secure: this._secureCookies, maxAge: this._cookieMaxAge },
store: this._sessionStore,
});
const _populateAuthedItemMiddleware = async (req, res, next) => {
const item = await this._getAuthedItem(req, keystone);
if (!item) {
// TODO: probably destroy the session
return next();
}
req.user = item;
req.authedListKey = req.session.keystoneListKey;
next();
};
return [injectAuthCookieMiddleware, sessionMiddleware, _populateAuthedItemMiddleware];
}
async _getAuthedItem(req, keystone) {
if (!req.session || !req.session.keystoneItemId) {
return;
}
const list = keystone.lists[req.session.keystoneListKey];
if (!list) {
return;
}
let item;
try {
item = await list.getAccessControlledItem(req.session.keystoneItemId, true, {
operation: 'read',
context: {},
info: {},
});
} catch (e) {
return;
}
if (!item) {
return;
}
return item;
}
startAuthedSession(req, { item, list }) {
return new Promise((resolve, reject) =>
req.session.regenerate(err => {
if (err) return reject(err);
req.session.keystoneListKey = list.key;
req.session.keystoneItemId = item.id;
resolve(cookieSignature.sign(req.session.id, this._cookieSecret));
})
);
}
endAuthedSession(req) {
return new Promise((resolve, reject) =>
req.session.regenerate(err => {
if (err) return reject(err);
resolve({ success: true });
})
);
}
getContext(req) {
return {
startAuthedSession: ({ item, list }) => this.startAuthedSession(req, { item, list }),
endAuthedSession: () => this.endAuthedSession(req),
authedItem: req.user,
authedListKey: req.authedListKey,
};
}
}
module.exports = { SessionManager };