-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathsession.js
141 lines (115 loc) · 4.15 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
141
const jwa = require('jwa')('HS256');
const config = require('./config');
const log = require('./log');
const { connectDatabase, makeModel, json, long } = require('./db');
const enabled = !!config.session;
const stores = {};
let store;
function cryptSession (id) {
let result = { id, expires: Date.now() + config.session.ttl };
result.sign = jwa.sign(result, config.session.secret);
return result;
}
function decodeSession (input) {
const { sign } = input;
delete input.sign;
if (!jwa.verify(input, sign, config.session.secret)) return;
else if (input.expires <= Date.now()) return;
else return input;
}
function wrap (document) {
document.save = async () => {
await document._init;
if (!document.id && !document._id) {
return log.error('Cannot save session, `id` or `_id` field not present');
}
const data = {};
for (const key in document) {
if (key == 'id' || key == '_id' || key == 'expires' || key == '_init') continue;
const value = document[key];
if (typeof value == 'function') continue;
data[key] = document[key];
}
return await store.update(document.id || document._id, data);
}
document.destroy = async () => {
await document._init;
if (!document.id && !document._id) {
return log.error('Cannot destroy session, `id` or `_id` field not present');
}
return await store.delete(document.id || document._id);
};
return document;
}
module.exports = {
enabled,
cryptSession,
decodeSession,
init () {
const object = wrap({});
object._init = store
.create(Date.now() + config.session.ttl)
.then(document => {
object.id = document.id;
object._id = document._id;
return cryptSession(document.id || document._id);
});
return object;
},
async parse (header) {
if (!header) return;
const parsed = decodeSession(header);
if (!parsed) return;
const session = await store.get(parsed.id);
if (!session) return;
const { data } = session;
Object.assign(session, data);
delete session.data;
return wrap(session);
},
registerStore (StoreClass, id) {
stores[id] = StoreClass;
},
// todo: rename init()
initSessions () {
if (enabled) {
const [storeName, configKey] = config.session.store.split('.', 2);
const StoreClass = stores[storeName];
if (StoreClass) {
store = new StoreClass(configKey);
async function cleanup () {
await store.destroyExpired(Date.now() - config.session.ttl);
}
if (config.session.ttl !== false) {
cleanup();
setInterval(cleanup, config.session.ttl / 3);
}
} else {
// todo: delete deprecated
log.warn('Using database instead of session store is deprecated');
const db = connectDatabase(config.session.store);
// todo: implement in dc-api-mysql
const model = makeModel(db, 'Session', {
data: json().required,
expires: long().required
});
let _initPromise;
if (model.init) _initPromise = model.init();
store = {
create: expires => model.create({ data: {}, expires }),
get: _id => model.findById(_id),
save: (_id, data) => model.updateById(_id, { data }),
destroy: _id => model.deleteById(_id)
};
async function cleanup () {
await _initPromise;
await model.delete({ expires: { $lte: Date.now() - config.session.ttl } });
}
if (config.session.ttl !== false) {
cleanup();
setInterval(cleanup, config.session.ttl / 3);
}
}
}
}
};