-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
index.ts
237 lines (208 loc) · 6.69 KB
/
index.ts
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/**
* Created by zhangsong on 2018/8/9.
*/
import Grant from './middleware/auth-utils/grant.js';
import BearerStore from './stores/bearer-store';
// import CookieStore from './stores/cookie-store';
// import SessionStore from './stores/session-store';
import Admin from './middleware/admin';
import Config from './middleware/auth-utils/config';
import GrantManager from './middleware/auth-utils/grant-manager';
import GrantAttacher from './middleware/grant-attacher';
import Logout from './middleware/logout';
// import PostAuth from './middleware/post-auth';
import Protect from './middleware/protect';
import Setup from './middleware/setup';
import IConfig from './interface/iconfig';
/**
* Instantiate a Keycloak.
*
* The `config` and `keycloakConfig` hashes are both optional.
*
* The `config` hash, if provided, may include either `store`, pointing
* to the actual session-store used by your application, or `cookies`
* with boolean `true` as the value to support using cookies as your
* authentication store.
*
* A session-based store is recommended, as it allows more assured control
* from the Keycloak console to explicitly logout some or all sessions.
*
* In all cases, also, authentication through a Bearer authentication
* header is supported for non-interactive APIs.
*
* The `keycloakConfig` object, by default, is populated by the contents of
* a `keycloak.json` file installed alongside your application, copied from
* the Keycloak administration console when you provision your application.
*
* @constructor
*
* @param {Object} config Configuration for the Keycloak connector.
* @param {Object} keycloakConfig Keycloak-specific configuration.
*
* @return {Keycloak} A constructed Keycloak object.
*
*/
class Keycloak {
public config;
public grantManager;
public stores;
// If keycloakConfig is null, Config() will search for `keycloak.json`.
constructor(config?: IConfig, keycloakConfig?: string | object) {
this.config = new Config(keycloakConfig); // 读取配置文件
this.grantManager = new GrantManager(this.config); // 设置
this.stores = [BearerStore];
if (!config) {
throw new Error('Adapter configuration must be provided.');
}
// Add the custom scope value
this.config.scope = config.scope;
// if (config && config.store && config.cookies) {
// throw new Error('Either `store` or `cookies` may be set, but not both');
// }
if (config && config.store) {
if (Array.isArray(config.store)) {
this.stores.push(...config.store);
} else {
this.stores.push(config.store);
}
}
// if (config && config.store) {
// this.stores.push(new SessionStore(config.store));
// } else if (config && config.cookies) {
// this.stores.push(CookieStore);
// }
}
/**
* Obtain an array of middleware for use in your application.
*
* Generally this should be installed at the root of your application,
* as it provides general wiring for Keycloak interaction, without actually
* causing Keycloak to get involved with any particular URL until asked
* by using `protect(...)`.
*
* Example:
*
* var app = express();
* var keycloak = new Keycloak();
* app.use( keycloak.middleware() );
*
* Options:
*
* - `logout` URL for logging a user out. Defaults to `/logout`.
* - `admin` Root URL for Keycloak admin callbacks. Defaults to `/`.
*
* @param {Object} options Optional options for specifying details.
*/
public middleware(options?: { logout: '', admin: '' }) {
let option = { logout: '', admin: '' };
if (options) {
option = options;
}
option.logout = option.logout || '/logout';
option.admin = option.admin || '/';
const middlewares = [];
middlewares.push(Setup);
// middlewares.push(PostAuth(this));
middlewares.push(Admin(this, option.admin));
middlewares.push(GrantAttacher(this));
middlewares.push(Logout(this, option.logout));
return middlewares;
}
public protect(spec?: any) {
return Protect(this, spec);
}
public authenticated(ctx) {
// no-op
}
public eauthenticated(ctx) {
// no-op
}
public accessDenied(ctx) {
ctx.throw(403, 'Access denied');
}
public getGrant(ctx) {
let rawData;
for (const item of this.stores) {
rawData = item.get(ctx);
if (rawData) {
// store = this.stores[i];
break;
}
}
let grantData = rawData;
if (typeof (grantData) === 'string') {
grantData = JSON.parse(grantData);
}
if (grantData && !grantData.error) {
const self = this;
return this.grantManager.createGrant(JSON.stringify(grantData))
.then((grant: Grant) => {
self.storeGrant(grant, ctx);
return grant;
})
.catch((e) => {
return Promise.resolve();
});
}
return Promise.resolve();
}
public storeGrant(grant, ctx) {
if (this.stores.length < 2 || BearerStore.get(ctx)) {
// cannot store bearer-only, and should not store if grant is from the
// authorization header
return;
}
if (!grant) {
this.accessDenied(ctx);
return;
}
this.stores[1].wrap(grant);
grant.store(ctx);
return grant;
}
public unstoreGrant(sessionId) {
if (this.stores.length < 2) {
// cannot unstore, bearer-only, this is weird
return;
}
this.stores[1].clear(sessionId);
}
public getGrantFromCode(code, ctx) {
if (this.stores.length < 2) {
// bearer-only, cannot do this;
throw new Error('Cannot exchange code for grant in bearer-only mode');
}
const sessionId = ctx.session.id;
const self = this;
return this.grantManager.obtainFromCode(ctx, code, sessionId)
.then((grant) => {
self.storeGrant(grant, ctx);
return grant;
});
}
// 组成登录的 URL 重定向过去
public loginUrl(uuid, redirectUrl) {
return this.config.realmUrl +
'/protocol/openid-connect/auth' +
'?client_id=' + encodeURIComponent(this.config.clientId) +
'&state=' + encodeURIComponent(uuid) +
'&redirect_uri=' + encodeURIComponent(redirectUrl) +
'&scope=' + encodeURIComponent(this.config.scope ? 'openid ' + this.config.scope : 'openid') +
'&response_type=code';
}
public logoutUrl(redirectUrl) {
return this.config.realmUrl +
'/protocol/openid-connect/logout' +
'?redirect_uri=' + encodeURIComponent(redirectUrl);
}
public accountUrl() {
return this.config.realmUrl + '/account';
}
public getAccount(token) {
return this.grantManager.getAccount(token);
}
public redirectToLogin(ctx) {
return !this.config.bearerOnly;
}
}
export default Keycloak;