forked from panva/node-oidc-provider
-
Notifications
You must be signed in to change notification settings - Fork 0
/
redis.js
128 lines (104 loc) · 2.98 KB
/
redis.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
// npm i ioredis@^4.0.0
import Redis from 'ioredis'; // eslint-disable-line import/no-unresolved
import isEmpty from 'lodash/isEmpty.js';
const client = new Redis(process.env.REDIS_URL, { keyPrefix: 'oidc:' });
const grantable = new Set([
'AccessToken',
'AuthorizationCode',
'RefreshToken',
'DeviceCode',
'BackchannelAuthenticationRequest',
]);
const consumable = new Set([
'AuthorizationCode',
'RefreshToken',
'DeviceCode',
'BackchannelAuthenticationRequest',
]);
function grantKeyFor(id) {
return `grant:${id}`;
}
function userCodeKeyFor(userCode) {
return `userCode:${userCode}`;
}
function uidKeyFor(uid) {
return `uid:${uid}`;
}
class RedisAdapter {
constructor(name) {
this.name = name;
}
async upsert(id, payload, expiresIn) {
const key = this.key(id);
const store = consumable.has(this.name)
? { payload: JSON.stringify(payload) } : JSON.stringify(payload);
const multi = client.multi();
multi[consumable.has(this.name) ? 'hmset' : 'set'](key, store);
if (expiresIn) {
multi.expire(key, expiresIn);
}
if (grantable.has(this.name) && payload.grantId) {
const grantKey = grantKeyFor(payload.grantId);
multi.rpush(grantKey, key);
// if you're seeing grant key lists growing out of acceptable proportions consider using LTRIM
// here to trim the list to an appropriate length
const ttl = await client.ttl(grantKey);
if (expiresIn > ttl) {
multi.expire(grantKey, expiresIn);
}
}
if (payload.userCode) {
const userCodeKey = userCodeKeyFor(payload.userCode);
multi.set(userCodeKey, id);
multi.expire(userCodeKey, expiresIn);
}
if (payload.uid) {
const uidKey = uidKeyFor(payload.uid);
multi.set(uidKey, id);
multi.expire(uidKey, expiresIn);
}
await multi.exec();
}
async find(id) {
const data = consumable.has(this.name)
? await client.hgetall(this.key(id))
: await client.get(this.key(id));
if (isEmpty(data)) {
return undefined;
}
if (typeof data === 'string') {
return JSON.parse(data);
}
const { payload, ...rest } = data;
return {
...rest,
...JSON.parse(payload),
};
}
async findByUid(uid) {
const id = await client.get(uidKeyFor(uid));
return this.find(id);
}
async findByUserCode(userCode) {
const id = await client.get(userCodeKeyFor(userCode));
return this.find(id);
}
async destroy(id) {
const key = this.key(id);
await client.del(key);
}
async revokeByGrantId(grantId) { // eslint-disable-line class-methods-use-this
const multi = client.multi();
const tokens = await client.lrange(grantKeyFor(grantId), 0, -1);
tokens.forEach((token) => multi.del(token));
multi.del(grantKeyFor(grantId));
await multi.exec();
}
async consume(id) {
await client.hset(this.key(id), 'consumed', Math.floor(Date.now() / 1000));
}
key(id) {
return `${this.name}:${id}`;
}
}
export default RedisAdapter;