@oslokommune/auth-bff 1.6.1 → 2.0.0-beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +240 -0
- package/dist/package.json +58 -0
- package/dist/src/OpenIdConfigManager.d.ts +10 -0
- package/dist/src/OpenIdConfigManager.d.ts.map +1 -0
- package/dist/src/OpenIdConfigManager.js +77 -0
- package/dist/src/config.d.ts +128 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +55 -0
- package/dist/src/middleware/OidcMiddleware.d.ts +20 -0
- package/dist/src/middleware/OidcMiddleware.d.ts.map +1 -0
- package/dist/src/middleware/OidcMiddleware.js +232 -0
- package/dist/src/middleware/oidc-routes.d.mts.map +1 -0
- package/dist/src/middleware/oidc-routes.d.ts +3 -0
- package/dist/src/middleware/oidc-routes.d.ts.map +1 -0
- package/dist/src/middleware/oidc-routes.js +10 -0
- package/dist/src/middleware/proxy-routes.d.mts.map +1 -0
- package/dist/src/middleware/proxy-routes.d.ts +4 -0
- package/dist/src/middleware/proxy-routes.d.ts.map +1 -0
- package/dist/src/middleware/proxy-routes.js +28 -0
- package/dist/{middleware → src/middleware}/proxy-routes.mjs +5 -5
- package/dist/src/middleware/security-headers.d.mts.map +1 -0
- package/dist/src/middleware/security-headers.d.ts +4 -0
- package/dist/src/middleware/security-headers.d.ts.map +1 -0
- package/dist/src/middleware/security-headers.js +31 -0
- package/dist/{middleware → src/middleware}/security-headers.mjs +2 -2
- package/dist/src/middleware/sessions/dynamoDbSessionStore.d.mts +3 -0
- package/dist/src/middleware/sessions/dynamoDbSessionStore.d.mts.map +1 -0
- package/dist/src/middleware/sessions/dynamoDbSessionStore.d.ts +3 -0
- package/dist/src/middleware/sessions/dynamoDbSessionStore.d.ts.map +1 -0
- package/dist/src/middleware/sessions/dynamoDbSessionStore.js +41 -0
- package/dist/{middleware → src/middleware}/sessions/dynamoDbSessionStore.mjs +12 -17
- package/dist/src/middleware/sessions/memorySessionStore.d.mts +3 -0
- package/dist/src/middleware/sessions/memorySessionStore.d.mts.map +1 -0
- package/dist/src/middleware/sessions/memorySessionStore.d.ts +3 -0
- package/dist/src/middleware/sessions/memorySessionStore.d.ts.map +1 -0
- package/dist/src/middleware/sessions/memorySessionStore.js +11 -0
- package/dist/src/middleware/sessions/sessions.d.mts +2 -0
- package/dist/src/middleware/sessions/sessions.d.mts.map +1 -0
- package/dist/src/middleware/sessions/sessions.d.ts +3 -0
- package/dist/src/middleware/sessions/sessions.d.ts.map +1 -0
- package/dist/src/middleware/sessions/sessions.js +39 -0
- package/dist/{middleware → src/middleware}/sessions/sessions.mjs +3 -4
- package/dist/src/middleware/static-routes.d.mts.map +1 -0
- package/dist/src/middleware/static-routes.d.ts +3 -0
- package/dist/src/middleware/static-routes.d.ts.map +1 -0
- package/dist/src/middleware/static-routes.js +19 -0
- package/dist/src/react/AuthContext.d.ts.map +1 -0
- package/dist/src/react/AuthContextProvider.d.ts.map +1 -0
- package/dist/{react → src/react}/AuthContextProvider.jsx +15 -27
- package/dist/src/react/UseAuthContext.d.ts +2 -0
- package/dist/src/react/UseAuthContext.d.ts.map +1 -0
- package/dist/{react → src/react}/UseAuthContext.jsx +2 -2
- package/dist/src/react/global-user.d.ts.map +1 -0
- package/dist/src/react/index.d.ts +5 -0
- package/dist/src/react/index.d.ts.map +1 -0
- package/dist/src/react/index.js +4 -0
- package/dist/src/react/poller.d.ts.map +1 -0
- package/dist/src/react/poller.js +28 -0
- package/dist/{server.d.mts.map → src/server.d.mts.map} +1 -1
- package/dist/src/server.d.ts +3 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +43 -0
- package/dist/{server.mjs → src/server.mjs} +2 -2
- package/dist/src/utils.d.ts +2 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +3 -0
- package/dist/src/vite-plugin.d.mts.map +1 -0
- package/dist/src/vite-plugin.d.ts +10 -0
- package/dist/src/vite-plugin.d.ts.map +1 -0
- package/dist/src/vite-plugin.js +26 -0
- package/dist/src/vite-plugin.mjs +35 -0
- package/package.json +24 -16
- package/dist/client.d.mts +0 -7
- package/dist/client.d.mts.map +0 -1
- package/dist/client.mjs +0 -92
- package/dist/config.d.mts +0 -4
- package/dist/config.d.mts.map +0 -1
- package/dist/config.mjs +0 -69
- package/dist/middleware/oidc-routes.d.mts.map +0 -1
- package/dist/middleware/oidc.d.mts +0 -17
- package/dist/middleware/oidc.d.mts.map +0 -1
- package/dist/middleware/oidc.mjs +0 -220
- package/dist/middleware/proxy-routes.d.mts.map +0 -1
- package/dist/middleware/security-headers.d.mts.map +0 -1
- package/dist/middleware/sessions/dynamoDbSessionStore.d.mts +0 -2
- package/dist/middleware/sessions/dynamoDbSessionStore.d.mts.map +0 -1
- package/dist/middleware/sessions/memorySessionStore.d.mts +0 -2
- package/dist/middleware/sessions/memorySessionStore.d.mts.map +0 -1
- package/dist/middleware/sessions/sessions.d.mts +0 -2
- package/dist/middleware/sessions/sessions.d.mts.map +0 -1
- package/dist/middleware/static-routes.d.mts.map +0 -1
- package/dist/react/AuthContext.d.ts.map +0 -1
- package/dist/react/AuthContextProvider.d.ts.map +0 -1
- package/dist/react/UseAuthContext.d.ts +0 -2
- package/dist/react/UseAuthContext.d.ts.map +0 -1
- package/dist/react/global-user.d.ts.map +0 -1
- package/dist/react/index.d.ts +0 -5
- package/dist/react/index.d.ts.map +0 -1
- package/dist/react/index.js +0 -4
- package/dist/react/poller.d.ts.map +0 -1
- package/dist/react/poller.js +0 -39
- package/dist/utils.d.ts +0 -2
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -3
- package/dist/vite-plugin.d.mts.map +0 -1
- package/dist/vite-plugin.mjs +0 -44
- /package/dist/{middleware → src/middleware}/oidc-routes.d.mts +0 -0
- /package/dist/{middleware → src/middleware}/oidc-routes.mjs +0 -0
- /package/dist/{middleware → src/middleware}/proxy-routes.d.mts +0 -0
- /package/dist/{middleware → src/middleware}/security-headers.d.mts +0 -0
- /package/dist/{middleware → src/middleware}/sessions/memorySessionStore.mjs +0 -0
- /package/dist/{middleware → src/middleware}/static-routes.d.mts +0 -0
- /package/dist/{middleware → src/middleware}/static-routes.mjs +0 -0
- /package/dist/{react → src/react}/AuthContext.d.ts +0 -0
- /package/dist/{react → src/react}/AuthContext.jsx +0 -0
- /package/dist/{react → src/react}/AuthContextProvider.d.ts +0 -0
- /package/dist/{react → src/react}/global-user.d.ts +0 -0
- /package/dist/{react → src/react}/global-user.js +0 -0
- /package/dist/{react → src/react}/poller.d.ts +0 -0
- /package/dist/{server.d.mts → src/server.d.mts} +0 -0
- /package/dist/{vite-plugin.d.mts → src/vite-plugin.d.mts} +0 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _OidcMiddleware_instances, _OidcMiddleware_configManager, _OidcMiddleware_bffConfig, _OidcMiddleware_refreshPromises, _OidcMiddleware_openIdConfig_get, _OidcMiddleware_refreshTokens, _OidcMiddleware_getFreshTokens, _OidcMiddleware_getUserClaims, _OidcMiddleware_getAccessTokenExpiryTime;
|
|
13
|
+
import * as openIdClient from "openid-client";
|
|
14
|
+
import { OpenIdConfigManager } from "../OpenIdConfigManager.js";
|
|
15
|
+
import { redact } from "../utils.js";
|
|
16
|
+
export class OidcMiddleware {
|
|
17
|
+
/**
|
|
18
|
+
* @private
|
|
19
|
+
* @param config
|
|
20
|
+
* @param configManager
|
|
21
|
+
*/
|
|
22
|
+
constructor(config, configManager) {
|
|
23
|
+
_OidcMiddleware_instances.add(this);
|
|
24
|
+
_OidcMiddleware_configManager.set(this, void 0);
|
|
25
|
+
_OidcMiddleware_bffConfig.set(this, void 0);
|
|
26
|
+
_OidcMiddleware_refreshPromises.set(this, {}
|
|
27
|
+
/**
|
|
28
|
+
* @private
|
|
29
|
+
* @param config
|
|
30
|
+
* @param configManager
|
|
31
|
+
*/
|
|
32
|
+
);
|
|
33
|
+
__classPrivateFieldSet(this, _OidcMiddleware_configManager, configManager, "f");
|
|
34
|
+
__classPrivateFieldSet(this, _OidcMiddleware_bffConfig, config, "f");
|
|
35
|
+
}
|
|
36
|
+
static async create(config) {
|
|
37
|
+
const configManager = new OpenIdConfigManager(config);
|
|
38
|
+
await configManager.init();
|
|
39
|
+
return new OidcMiddleware(config, configManager);
|
|
40
|
+
}
|
|
41
|
+
get ensureFreshToken() {
|
|
42
|
+
return (req, res, next) => {
|
|
43
|
+
__classPrivateFieldGet(this, _OidcMiddleware_instances, "m", _OidcMiddleware_getFreshTokens).call(this, req).then(tokenResponse => {
|
|
44
|
+
if (tokenResponse) {
|
|
45
|
+
req.tokenResponse = tokenResponse;
|
|
46
|
+
next();
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
console.warn(`401: No valid tokens in session sid=${redact(req.session.id)}`);
|
|
50
|
+
res.sendStatus(401);
|
|
51
|
+
}
|
|
52
|
+
}).catch(next);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
get login() {
|
|
56
|
+
return async (req, res, next) => {
|
|
57
|
+
try {
|
|
58
|
+
const codeVerifier = openIdClient.randomPKCECodeVerifier();
|
|
59
|
+
const codeChallenge = await openIdClient.calculatePKCECodeChallenge(codeVerifier);
|
|
60
|
+
const stateKey = openIdClient.randomState();
|
|
61
|
+
const redirectUrl = req.query.redirectUrl; //TODO: håndtering av andre typer her?
|
|
62
|
+
const params = new URLSearchParams();
|
|
63
|
+
params.append('scope', "openid profile");
|
|
64
|
+
params.append('code_challenge', codeChallenge);
|
|
65
|
+
params.append('code_challenge_method', 'S256');
|
|
66
|
+
params.append('redirect_uri', __classPrivateFieldGet(this, _OidcMiddleware_bffConfig, "f").redirectUri);
|
|
67
|
+
params.append('state', stateKey);
|
|
68
|
+
__classPrivateFieldGet(this, _OidcMiddleware_bffConfig, "f").resources?.forEach(resource => {
|
|
69
|
+
params.append('resource', resource);
|
|
70
|
+
});
|
|
71
|
+
const authorizationUrl = openIdClient.buildAuthorizationUrl(__classPrivateFieldGet(this, _OidcMiddleware_instances, "a", _OidcMiddleware_openIdConfig_get), params);
|
|
72
|
+
req.session.codeVerifier = codeVerifier;
|
|
73
|
+
req.session.stateKey = stateKey;
|
|
74
|
+
req.session.stateValue = { redirectUrl };
|
|
75
|
+
req.session.save(() => {
|
|
76
|
+
res.redirect(authorizationUrl.toString());
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
console.error(e);
|
|
81
|
+
next(e);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
get callback() {
|
|
86
|
+
return async (req, res, next) => {
|
|
87
|
+
try {
|
|
88
|
+
const { codeVerifier, stateKey, stateValue } = req.session;
|
|
89
|
+
const url = new URL(`${req.protocol}://${req.headers.host}${req.originalUrl}`);
|
|
90
|
+
const tokenResponse = await openIdClient.authorizationCodeGrant(__classPrivateFieldGet(this, _OidcMiddleware_instances, "a", _OidcMiddleware_openIdConfig_get), url, {
|
|
91
|
+
expectedState: stateKey,
|
|
92
|
+
pkceCodeVerifier: codeVerifier
|
|
93
|
+
});
|
|
94
|
+
req.session.tokenResponse = tokenResponse;
|
|
95
|
+
req.session["idp-sid"] = tokenResponse.claims().sid;
|
|
96
|
+
req.session.userClaims = __classPrivateFieldGet(this, _OidcMiddleware_instances, "m", _OidcMiddleware_getUserClaims).call(this, tokenResponse);
|
|
97
|
+
req.session.accessTokenExpiresAt = __classPrivateFieldGet(this, _OidcMiddleware_instances, "m", _OidcMiddleware_getAccessTokenExpiryTime).call(this, tokenResponse);
|
|
98
|
+
delete req.session.codeVerifier;
|
|
99
|
+
delete req.session.stateKey;
|
|
100
|
+
delete req.session.stateValue;
|
|
101
|
+
req.session.save(() => {
|
|
102
|
+
let redirectUrl = stateValue.redirectUrl;
|
|
103
|
+
//only allow relative redirecturls:
|
|
104
|
+
const absoluteUrlRegex = /^(?:[a-z+]+:)?\/\//;
|
|
105
|
+
if (!redirectUrl || absoluteUrlRegex.test(redirectUrl)) {
|
|
106
|
+
redirectUrl = __classPrivateFieldGet(this, _OidcMiddleware_bffConfig, "f").basePath || "/";
|
|
107
|
+
}
|
|
108
|
+
res.redirect(redirectUrl);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
console.error(e);
|
|
113
|
+
req.session.destroy(() => {
|
|
114
|
+
next(e);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
get user() {
|
|
120
|
+
return async (req, res, next) => {
|
|
121
|
+
try {
|
|
122
|
+
const tokenResponse = await __classPrivateFieldGet(this, _OidcMiddleware_instances, "m", _OidcMiddleware_getFreshTokens).call(this, req);
|
|
123
|
+
if (!tokenResponse) {
|
|
124
|
+
console.log('/user 401: No tokenset');
|
|
125
|
+
return res.sendStatus(401);
|
|
126
|
+
}
|
|
127
|
+
return res.send(req.session.userClaims);
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
console.error(`Error in /user sid=${redact(req.session?.id)}`, e);
|
|
131
|
+
next(e);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
get logout() {
|
|
136
|
+
return (req, res) => {
|
|
137
|
+
const tokenResponse = req.session.tokenResponse;
|
|
138
|
+
req.session.destroy(() => {
|
|
139
|
+
const endSessionUrl = openIdClient.buildEndSessionUrl(__classPrivateFieldGet(this, _OidcMiddleware_instances, "a", _OidcMiddleware_openIdConfig_get), {
|
|
140
|
+
id_token_hint: tokenResponse?.id_token,
|
|
141
|
+
});
|
|
142
|
+
res.redirect(endSessionUrl.toString());
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
get frontChannelLogout() {
|
|
147
|
+
return async (req, res) => {
|
|
148
|
+
const { iss, sid } = req.query;
|
|
149
|
+
console.log(`Front channel logout: params iss=${iss}, sid=${redact(sid)}`);
|
|
150
|
+
if (sid) {
|
|
151
|
+
try {
|
|
152
|
+
await req.destroySessionByIdpSid?.(sid);
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
console.error("Failed to destroy session", e);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
res.sendStatus(200);
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
_OidcMiddleware_configManager = new WeakMap(), _OidcMiddleware_bffConfig = new WeakMap(), _OidcMiddleware_refreshPromises = new WeakMap(), _OidcMiddleware_instances = new WeakSet(), _OidcMiddleware_openIdConfig_get = function _OidcMiddleware_openIdConfig_get() {
|
|
163
|
+
return __classPrivateFieldGet(this, _OidcMiddleware_configManager, "f").openIdConfig;
|
|
164
|
+
}, _OidcMiddleware_refreshTokens = async function _OidcMiddleware_refreshTokens(req, tokenResponse) {
|
|
165
|
+
var _a;
|
|
166
|
+
const sessionId = req.session.id;
|
|
167
|
+
const refreshToken = tokenResponse.refresh_token;
|
|
168
|
+
const doRefresh = async () => {
|
|
169
|
+
console.log(`Token refresh starting. sid=${redact(sessionId)}`);
|
|
170
|
+
try {
|
|
171
|
+
const tokenResponse = await openIdClient.refreshTokenGrant(__classPrivateFieldGet(this, _OidcMiddleware_instances, "a", _OidcMiddleware_openIdConfig_get), refreshToken);
|
|
172
|
+
console.log(`Token refresh OK. sid=${redact(sessionId)}`);
|
|
173
|
+
return tokenResponse;
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
console.log(`Token refresh failed. sid=${redact(sessionId)}`, err);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const refreshPromise = (_a = __classPrivateFieldGet(this, _OidcMiddleware_refreshPromises, "f"))[refreshToken] ?? (_a[refreshToken] = doRefresh().finally(() => {
|
|
181
|
+
console.log(`Token refresh finished. Cleaning up. sid=${redact(sessionId)}`);
|
|
182
|
+
setTimeout(() => {
|
|
183
|
+
delete __classPrivateFieldGet(this, _OidcMiddleware_refreshPromises, "f")[refreshToken];
|
|
184
|
+
}, 10000);
|
|
185
|
+
}));
|
|
186
|
+
const refreshedTokenResponse = await refreshPromise;
|
|
187
|
+
if (refreshedTokenResponse) {
|
|
188
|
+
Object.assign(req.session.tokenResponse, refreshedTokenResponse);
|
|
189
|
+
req.session.accessTokenExpiresAt = __classPrivateFieldGet(this, _OidcMiddleware_instances, "m", _OidcMiddleware_getAccessTokenExpiryTime).call(this, refreshedTokenResponse);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
req.session.tokenResponse = null;
|
|
193
|
+
}
|
|
194
|
+
return req.session.tokenResponse;
|
|
195
|
+
}, _OidcMiddleware_getFreshTokens = async function _OidcMiddleware_getFreshTokens(req) {
|
|
196
|
+
const tokenResponse = req.session.tokenResponse;
|
|
197
|
+
if (!tokenResponse) {
|
|
198
|
+
console.log(`No tokenResponse found in session sid=${redact(req.session.id)}`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const now = new Date().getTime();
|
|
202
|
+
const expiresAt = req.session.accessTokenExpiresAt || 0;
|
|
203
|
+
/*if(!expiresAt) {
|
|
204
|
+
//For at ting ikke skal eksplodere hvis man får inn en gammel session.
|
|
205
|
+
//TODO: denne kan fjernes når den har kjørt i prod i et døgn+
|
|
206
|
+
console.error('accessTokenExpiresAt was not set')
|
|
207
|
+
return
|
|
208
|
+
}*/
|
|
209
|
+
const expiresInSeconds = (expiresAt - now) / 1000;
|
|
210
|
+
if (expiresInSeconds < 5) {
|
|
211
|
+
console.log(`Access token expired. sid=${redact(req.session.id)}, expiresInSeconds=${expiresInSeconds}`);
|
|
212
|
+
return await __classPrivateFieldGet(this, _OidcMiddleware_instances, "m", _OidcMiddleware_refreshTokens).call(this, req, tokenResponse);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
return tokenResponse;
|
|
216
|
+
}
|
|
217
|
+
}, _OidcMiddleware_getUserClaims = function _OidcMiddleware_getUserClaims(tokenResponse) {
|
|
218
|
+
let claims = tokenResponse.claims();
|
|
219
|
+
if (__classPrivateFieldGet(this, _OidcMiddleware_bffConfig, "f").userClaims) {
|
|
220
|
+
claims = __classPrivateFieldGet(this, _OidcMiddleware_bffConfig, "f").userClaims.reduce((acc, claim) => {
|
|
221
|
+
acc[claim] = claims[claim];
|
|
222
|
+
return acc;
|
|
223
|
+
}, {});
|
|
224
|
+
}
|
|
225
|
+
return claims;
|
|
226
|
+
}, _OidcMiddleware_getAccessTokenExpiryTime = function _OidcMiddleware_getAccessTokenExpiryTime(tokenResponse) {
|
|
227
|
+
if (tokenResponse.expires_in !== undefined) {
|
|
228
|
+
const now = new Date();
|
|
229
|
+
now.setSeconds(now.getSeconds() + tokenResponse.expires_in);
|
|
230
|
+
return now.getTime();
|
|
231
|
+
}
|
|
232
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-routes.d.mts","sourceRoot":"","sources":["../../../src/middleware/oidc-routes.mjs"],"names":[],"mappings":"AAEA,qDAUC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-routes.d.ts","sourceRoot":"","sources":["../../../src/middleware/oidc-routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AAEnD,wBAAgB,UAAU,CAAC,cAAc,EAAE,cAAc,8CAUxD"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
export function oidcRoutes(oidcMiddleware) {
|
|
3
|
+
const router = express.Router();
|
|
4
|
+
router.get('/auth/login', oidcMiddleware.login);
|
|
5
|
+
router.get('/auth/callback', oidcMiddleware.callback);
|
|
6
|
+
router.get('/auth/logout', oidcMiddleware.logout);
|
|
7
|
+
router.get('/auth/user', oidcMiddleware.user);
|
|
8
|
+
router.get('/auth/front-channel-logout', oidcMiddleware.frontChannelLogout);
|
|
9
|
+
return router;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy-routes.d.mts","sourceRoot":"","sources":["../../../src/middleware/proxy-routes.mjs"],"names":[],"mappings":"AAGA,mEA4BC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { BffConfig } from "../config.js";
|
|
2
|
+
import { OidcMiddleware } from "./OidcMiddleware.js";
|
|
3
|
+
export declare function proxyRoutes(config: BffConfig, oidcMiddleware: OidcMiddleware): import("express-serve-static-core").Router;
|
|
4
|
+
//# sourceMappingURL=proxy-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy-routes.d.ts","sourceRoot":"","sources":["../../../src/middleware/proxy-routes.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AACvC,OAAO,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AAEnD,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,cAAc,8CA6B5E"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
3
|
+
export function proxyRoutes(config, oidcMiddleware) {
|
|
4
|
+
const router = express.Router();
|
|
5
|
+
for (const [path, target] of Object.entries(config.proxyTargets)) {
|
|
6
|
+
console.log(`Setting up auth proxy: ${path} -> ${target}`);
|
|
7
|
+
router.use(path, oidcMiddleware.ensureFreshToken, createProxyMiddleware({
|
|
8
|
+
target: target,
|
|
9
|
+
changeOrigin: true,
|
|
10
|
+
on: {
|
|
11
|
+
proxyReq: (proxyReq, req) => {
|
|
12
|
+
const accessToken = req.tokenResponse?.access_token;
|
|
13
|
+
if (!accessToken) {
|
|
14
|
+
console.error("proxy: missing token");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
proxyReq.setHeader("Authorization", `Bearer ${accessToken}`);
|
|
18
|
+
proxyReq.removeHeader("Cookie");
|
|
19
|
+
},
|
|
20
|
+
proxyRes: (proxyRes, req) => {
|
|
21
|
+
// @ts-ignore //TODO: proxyRes har en mystisk type som mangler req, men den er der
|
|
22
|
+
console.log(`Proxied: ${req.method} ${req.originalUrl} -> ${proxyRes.req.protocol}//${proxyRes.req.host}${proxyRes.req.path}, status=${proxyRes.statusCode}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
return router;
|
|
28
|
+
}
|
|
@@ -9,16 +9,16 @@ export function proxyRoutes(config, oidcMiddleware) {
|
|
|
9
9
|
changeOrigin: true,
|
|
10
10
|
on: {
|
|
11
11
|
proxyReq: (proxyReq, req, res) => {
|
|
12
|
-
const
|
|
13
|
-
if (!
|
|
14
|
-
console.error("proxy: missing
|
|
12
|
+
const accessToken = req.tokenResponse?.access_token;
|
|
13
|
+
if (!accessToken) {
|
|
14
|
+
console.error("proxy: missing token");
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
-
proxyReq.setHeader("Authorization", `Bearer ${
|
|
17
|
+
proxyReq.setHeader("Authorization", `Bearer ${accessToken}`);
|
|
18
18
|
proxyReq.removeHeader("Cookie");
|
|
19
19
|
},
|
|
20
20
|
proxyRes: (proxyRes, req, res) => {
|
|
21
|
-
console.log(`Proxied ${req.originalUrl}
|
|
21
|
+
console.log(`Proxied: ${req.method} ${req.originalUrl} -> ${proxyRes.req.protocol}//${proxyRes.req.host}${proxyRes.req.path}, status=${proxyRes.statusCode}`);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-headers.d.mts","sourceRoot":"","sources":["../../../src/middleware/security-headers.mjs"],"names":[],"mappings":"AAGA,0FA+BC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { BffConfig } from "../config.js";
|
|
3
|
+
export declare function securityHeaders(config: BffConfig): ((_: Request, res: Response, next: NextFunction) => void)[];
|
|
4
|
+
//# sourceMappingURL=security-headers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../../../src/middleware/security-headers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAC,MAAM,SAAS,CAAA;AACvD,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAEvC,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,QAcR,OAAO,OAAO,QAAQ,QAAQ,YAAY,aAkBlF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import helmet from "helmet";
|
|
3
|
+
export function securityHeaders(config) {
|
|
4
|
+
const contentSecurityPolicy = config.contentSecurityPolicy;
|
|
5
|
+
if (contentSecurityPolicy?.directives) {
|
|
6
|
+
for (const [_, values] of Object.entries(contentSecurityPolicy.directives)) {
|
|
7
|
+
// @ts-ignore //TODO values her har type Iterable (som ikke har `entries()`), men er egentlig en array. Kan sikkert skrives om litt.
|
|
8
|
+
for (const [i, value] of values.entries()) {
|
|
9
|
+
if (value === '{nonce}') {
|
|
10
|
+
values[i] = (_, res) => `'nonce-${res.locals.cspNonce}'`;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const generateCspNonceMiddleware = (_, res, next) => {
|
|
16
|
+
res.locals.cspNonce = crypto.randomBytes(16).toString("hex");
|
|
17
|
+
next();
|
|
18
|
+
};
|
|
19
|
+
const helmetMiddleware = helmet({
|
|
20
|
+
strictTransportSecurity: {
|
|
21
|
+
maxAge: 31536000,
|
|
22
|
+
includeSubDomains: false,
|
|
23
|
+
preload: false,
|
|
24
|
+
},
|
|
25
|
+
contentSecurityPolicy: contentSecurityPolicy ?? false,
|
|
26
|
+
});
|
|
27
|
+
return [
|
|
28
|
+
generateCspNonceMiddleware,
|
|
29
|
+
helmetMiddleware
|
|
30
|
+
];
|
|
31
|
+
}
|
|
@@ -2,7 +2,7 @@ import crypto from "crypto";
|
|
|
2
2
|
import helmet from "helmet";
|
|
3
3
|
export function securityHeaders(config) {
|
|
4
4
|
const contentSecurityPolicy = config.contentSecurityPolicy;
|
|
5
|
-
if (contentSecurityPolicy
|
|
5
|
+
if (contentSecurityPolicy?.directives) {
|
|
6
6
|
for (const [_, values] of Object.entries(contentSecurityPolicy.directives)) {
|
|
7
7
|
for (const [i, value] of values.entries()) {
|
|
8
8
|
if (value === '{nonce}') {
|
|
@@ -21,7 +21,7 @@ export function securityHeaders(config) {
|
|
|
21
21
|
includeSubDomains: false,
|
|
22
22
|
preload: false,
|
|
23
23
|
},
|
|
24
|
-
contentSecurityPolicy: contentSecurityPolicy
|
|
24
|
+
contentSecurityPolicy: contentSecurityPolicy ?? false,
|
|
25
25
|
});
|
|
26
26
|
return [
|
|
27
27
|
generateCspNonceMiddleware,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dynamoDbSessionStore.d.mts","sourceRoot":"","sources":["../../../../src/middleware/sessions/dynamoDbSessionStore.mjs"],"names":[],"mappings":"AA6BA,wGAcC;0BA1CyB,kBAAkB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dynamoDbSessionStore.d.ts","sourceRoot":"","sources":["../../../../src/middleware/sessions/dynamoDbSessionStore.ts"],"names":[],"mappings":"AACA,OAAO,aAAqC,MAAM,kBAAkB,CAAC;AA6BrE,wBAAgB,oBAAoB,CAAC,MAAM,KAAK,wDAc/C"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { DeleteItemCommand, DynamoDBClient, QueryCommand } from "@aws-sdk/client-dynamodb";
|
|
2
|
+
import dynamoDbStore from "connect-dynamodb";
|
|
3
|
+
import session from "express-session";
|
|
4
|
+
import { redact } from "../../utils.js";
|
|
5
|
+
const destroyByIdpSid = (config, client) => {
|
|
6
|
+
return async (idpSid) => {
|
|
7
|
+
console.log(`Front channel logout: deleting session(s) with idp-sid=${redact(idpSid)}`);
|
|
8
|
+
const query = new QueryCommand({
|
|
9
|
+
TableName: config['table'],
|
|
10
|
+
IndexName: "idp-sid-index",
|
|
11
|
+
ExpressionAttributeValues: { ":sid": { S: idpSid } },
|
|
12
|
+
ExpressionAttributeNames: { "#k": "idp-sid" },
|
|
13
|
+
KeyConditionExpression: "#k = :sid",
|
|
14
|
+
ProjectionExpression: "id"
|
|
15
|
+
});
|
|
16
|
+
const res = await client.send(query);
|
|
17
|
+
await Promise.all(res.Items.map((item) => {
|
|
18
|
+
console.log(`Front channel logout: deleting session ${redact(item.id?.S, 10)}`);
|
|
19
|
+
return client.send(new DeleteItemCommand({
|
|
20
|
+
TableName: config['table'],
|
|
21
|
+
Key: { id: item.id }
|
|
22
|
+
}));
|
|
23
|
+
}));
|
|
24
|
+
console.log(`Front channel logout: completed. ${res.Count} session(s) deleted`);
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export function dynamoDbSessionStore(config = {}) {
|
|
28
|
+
const client = new DynamoDBClient({});
|
|
29
|
+
const DynamoDbStore = dynamoDbStore(session);
|
|
30
|
+
const sessionStoreConfig = {
|
|
31
|
+
...config,
|
|
32
|
+
client,
|
|
33
|
+
specialKeys: [
|
|
34
|
+
{ name: "idp-sid", type: "S" }
|
|
35
|
+
],
|
|
36
|
+
skipThrowMissingSpecialKeys: true
|
|
37
|
+
};
|
|
38
|
+
const sessionStore = new DynamoDbStore(sessionStoreConfig);
|
|
39
|
+
sessionStore.destroyByIdpSid = destroyByIdpSid(config, client);
|
|
40
|
+
return sessionStore;
|
|
41
|
+
}
|
|
@@ -1,18 +1,9 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import { DeleteItemCommand, DynamoDBClient, QueryCommand } from "@aws-sdk/client-dynamodb";
|
|
11
2
|
import dynamoDbStore from "connect-dynamodb";
|
|
12
3
|
import session from "express-session";
|
|
13
4
|
import { redact } from "../../utils.js";
|
|
14
5
|
const destroyByIdpSid = (config, client) => {
|
|
15
|
-
return (idpSid) =>
|
|
6
|
+
return async (idpSid) => {
|
|
16
7
|
console.log(`Front channel logout: deleting session(s) with idp-sid=${redact(idpSid)}`);
|
|
17
8
|
const query = new QueryCommand({
|
|
18
9
|
TableName: config.table,
|
|
@@ -22,24 +13,28 @@ const destroyByIdpSid = (config, client) => {
|
|
|
22
13
|
KeyConditionExpression: "#k = :sid",
|
|
23
14
|
ProjectionExpression: "id"
|
|
24
15
|
});
|
|
25
|
-
const res =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
console.log(`Front channel logout: deleting session ${redact((_a = item.id) === null || _a === void 0 ? void 0 : _a.S, 10)}`);
|
|
16
|
+
const res = await client.send(query);
|
|
17
|
+
await Promise.all(res.Items.map((item) => {
|
|
18
|
+
console.log(`Front channel logout: deleting session ${redact(item.id?.S, 10)}`);
|
|
29
19
|
return client.send(new DeleteItemCommand({
|
|
30
20
|
TableName: config.table,
|
|
31
21
|
Key: { id: item.id }
|
|
32
22
|
}));
|
|
33
23
|
}));
|
|
34
24
|
console.log(`Front channel logout: completed. ${res.Count} session(s) deleted`);
|
|
35
|
-
}
|
|
25
|
+
};
|
|
36
26
|
};
|
|
37
27
|
export function dynamoDbSessionStore(config = {}) {
|
|
38
28
|
const client = new DynamoDBClient({});
|
|
39
29
|
const DynamoDbStore = dynamoDbStore({ session });
|
|
40
|
-
const sessionStoreConfig =
|
|
30
|
+
const sessionStoreConfig = {
|
|
31
|
+
...config,
|
|
32
|
+
client,
|
|
33
|
+
specialKeys: [
|
|
41
34
|
{ name: "idp-sid", type: "S" }
|
|
42
|
-
],
|
|
35
|
+
],
|
|
36
|
+
skipThrowMissingSpecialKeys: true
|
|
37
|
+
};
|
|
43
38
|
const sessionStore = new DynamoDbStore(sessionStoreConfig);
|
|
44
39
|
sessionStore.destroyByIdpSid = destroyByIdpSid(config, client);
|
|
45
40
|
return sessionStore;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memorySessionStore.d.mts","sourceRoot":"","sources":["../../../../src/middleware/sessions/memorySessionStore.mjs"],"names":[],"mappings":"AAQA,qEAIC;oBAZmB,iBAAiB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memorySessionStore.d.ts","sourceRoot":"","sources":["../../../../src/middleware/sessions/memorySessionStore.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,iBAAiB,CAAC;AAQtC,wBAAgB,kBAAkB,CAAC,MAAM,GAAE,MAAW,iBAIrD"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import session from "express-session";
|
|
2
|
+
import { redact } from "../../utils.js";
|
|
3
|
+
const destroyByIdpSid = async (idpSid) => {
|
|
4
|
+
// This is not supposed to be used outside localhost, so it is not implemented
|
|
5
|
+
console.log(`Pretending to destroyByIdpSid. idp-sid=${redact(idpSid)}`);
|
|
6
|
+
};
|
|
7
|
+
export function memorySessionStore(config = {}) {
|
|
8
|
+
const sessionStore = new session.MemoryStore(config);
|
|
9
|
+
sessionStore.destroyByIdpSid = destroyByIdpSid;
|
|
10
|
+
return sessionStore;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.mts","sourceRoot":"","sources":["../../../../src/middleware/sessions/sessions.mjs"],"names":[],"mappings":"AAIA,sLAiCC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { BffConfig } from "../../config.js";
|
|
2
|
+
export declare function sessions(config: BffConfig): import("express").RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>[];
|
|
3
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../../../src/middleware/sessions/sessions.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAG1C,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,kJAiCzC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import session from "express-session";
|
|
2
|
+
import { dynamoDbSessionStore } from "./dynamoDbSessionStore.js";
|
|
3
|
+
import { memorySessionStore } from "./memorySessionStore.js";
|
|
4
|
+
export function sessions(config) {
|
|
5
|
+
let sessionStore;
|
|
6
|
+
if (config.sessionStoreType === 'memory') {
|
|
7
|
+
const sessionStoreOptions = config.sessionStoreOptions ?? {};
|
|
8
|
+
sessionStore = memorySessionStore(sessionStoreOptions);
|
|
9
|
+
}
|
|
10
|
+
else if (config.sessionStoreType === 'dynamodb') {
|
|
11
|
+
const sessionStoreOptions = config.sessionStoreOptions ?? {};
|
|
12
|
+
sessionStore = dynamoDbSessionStore(sessionStoreOptions);
|
|
13
|
+
}
|
|
14
|
+
else if (config.sessionStoreType) {
|
|
15
|
+
throw Error(`unknown sessionStoreType ${config.sessionStoreType}`);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
throw Error('missing sessionStoreType');
|
|
19
|
+
}
|
|
20
|
+
return [
|
|
21
|
+
session({
|
|
22
|
+
secret: config.sessionSecret,
|
|
23
|
+
store: sessionStore,
|
|
24
|
+
resave: false,
|
|
25
|
+
saveUninitialized: false,
|
|
26
|
+
cookie: config.cookie || {
|
|
27
|
+
httpOnly: true,
|
|
28
|
+
path: config.cookiePath,
|
|
29
|
+
secure: config.cookieSecure,
|
|
30
|
+
sameSite: config.cookieSameSite
|
|
31
|
+
},
|
|
32
|
+
}),
|
|
33
|
+
(req, _, next) => {
|
|
34
|
+
// make this function available to request handlers
|
|
35
|
+
req.destroySessionByIdpSid = sessionStore?.destroyByIdpSid;
|
|
36
|
+
next();
|
|
37
|
+
}
|
|
38
|
+
];
|
|
39
|
+
}
|
|
@@ -2,14 +2,13 @@ import session from "express-session";
|
|
|
2
2
|
import { dynamoDbSessionStore } from "./dynamoDbSessionStore.mjs";
|
|
3
3
|
import { memorySessionStore } from "./memorySessionStore.mjs";
|
|
4
4
|
export function sessions(config) {
|
|
5
|
-
var _a, _b;
|
|
6
5
|
let sessionStore;
|
|
7
6
|
if (config.sessionStoreType === 'memory') {
|
|
8
|
-
const sessionStoreOptions =
|
|
7
|
+
const sessionStoreOptions = config.sessionStoreOptions ?? {};
|
|
9
8
|
sessionStore = memorySessionStore(sessionStoreOptions);
|
|
10
9
|
}
|
|
11
10
|
else if (config.sessionStoreType === 'dynamodb') {
|
|
12
|
-
const sessionStoreOptions =
|
|
11
|
+
const sessionStoreOptions = config.sessionStoreOptions ?? {};
|
|
13
12
|
sessionStore = dynamoDbSessionStore(sessionStoreOptions);
|
|
14
13
|
}
|
|
15
14
|
else if (config.sessionStoreType) {
|
|
@@ -33,7 +32,7 @@ export function sessions(config) {
|
|
|
33
32
|
}),
|
|
34
33
|
(req, _, next) => {
|
|
35
34
|
// make this function available to request handlers
|
|
36
|
-
req.destroySessionByIdpSid = sessionStore
|
|
35
|
+
req.destroySessionByIdpSid = sessionStore?.destroyByIdpSid;
|
|
37
36
|
next();
|
|
38
37
|
}
|
|
39
38
|
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-routes.d.mts","sourceRoot":"","sources":["../../../src/middleware/static-routes.mjs"],"names":[],"mappings":"AAIA,+CAiBC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-routes.d.ts","sourceRoot":"","sources":["../../../src/middleware/static-routes.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAEvC,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,8CAiB7C"}
|