@oslokommune/auth-bff 2.0.0-beta1 → 2.0.0-beta3
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 +344 -0
- package/dist/package.json +12 -11
- package/dist/src/config.d.ts +107 -4
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +7 -7
- package/dist/src/middleware/OidcMiddleware.d.ts.map +1 -1
- package/dist/src/middleware/OidcMiddleware.js +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.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/src/middleware/proxy-routes.mjs +1 -1
- 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/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/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.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/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/server.d.ts +3 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +43 -0
- package/dist/src/server.mjs +0 -0
- package/dist/src/utils.d.ts +1 -1
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/vite-plugin.d.ts +5 -0
- package/dist/src/vite-plugin.d.ts.map +1 -0
- package/dist/src/vite-plugin.js +26 -0
- package/package.json +12 -11
package/README.md
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# auth-bff
|
|
2
|
+
|
|
3
|
+
A NodeJS Backend for frontend.
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
|
|
7
|
+
* Two "modes" of operation
|
|
8
|
+
* A vite plugin for use during development
|
|
9
|
+
* A standalone mode for use in production (e.g. inside a docker container)
|
|
10
|
+
* Supports generic OIDC auth code flow clients
|
|
11
|
+
* Has special support for `okdata`-generated Idporten clients
|
|
12
|
+
* Handles login/logout and sessions (using DynamoDb as a store)
|
|
13
|
+
* Proxies API calls
|
|
14
|
+
* Serves a static web app
|
|
15
|
+
* Includes simple React components for handling login-state
|
|
16
|
+
|
|
17
|
+
See https://github.com/oslokommune/auth-bff-example for an example React app using this package.
|
|
18
|
+
|
|
19
|
+
## Getting started
|
|
20
|
+
|
|
21
|
+
### Dev mode
|
|
22
|
+
|
|
23
|
+
1. Install package
|
|
24
|
+
|
|
25
|
+
```shell
|
|
26
|
+
npm install @oslokommune/auth-bff
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
2. Add plugin to your vite config
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import {defineConfig} from 'vite'
|
|
33
|
+
import bff from '@oslokommune/auth-bff/vite-plugin'
|
|
34
|
+
|
|
35
|
+
export default defineConfig({
|
|
36
|
+
plugins: [
|
|
37
|
+
bff()
|
|
38
|
+
],
|
|
39
|
+
...
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
3. Create a [config file](#configuration)
|
|
44
|
+
|
|
45
|
+
4. Run!
|
|
46
|
+
|
|
47
|
+
### Standalone mode
|
|
48
|
+
|
|
49
|
+
1. Install package globally
|
|
50
|
+
|
|
51
|
+
```shell
|
|
52
|
+
npm install -g @oslokommune/auth-bff
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
2. Create a [config file](#configuration)
|
|
56
|
+
|
|
57
|
+
3. Run with
|
|
58
|
+
|
|
59
|
+
```shell
|
|
60
|
+
auth-bff
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Running in Docker
|
|
64
|
+
|
|
65
|
+
When running in docker you should specify the version to use, and make sure it matches the one used in package.json.
|
|
66
|
+
|
|
67
|
+
Example dockerfile:
|
|
68
|
+
```dockerfile
|
|
69
|
+
FROM node:23-alpine AS base
|
|
70
|
+
|
|
71
|
+
FROM base AS react-build
|
|
72
|
+
WORKDIR /home/react
|
|
73
|
+
COPY package*.json /home/react
|
|
74
|
+
RUN npm install
|
|
75
|
+
COPY . ./
|
|
76
|
+
RUN npm run build
|
|
77
|
+
|
|
78
|
+
FROM base
|
|
79
|
+
WORKDIR /application
|
|
80
|
+
EXPOSE 8080
|
|
81
|
+
COPY --from=react-build /home/react/dist /application/dist
|
|
82
|
+
ENV NODE_ENV=production
|
|
83
|
+
RUN npm install -g @oslokommune/auth-bff@2.0.0-beta3
|
|
84
|
+
COPY bff.config.json /application/
|
|
85
|
+
CMD ["auth-bff"]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
To use different configuration for different environments, you can create separate config files for each and select it at build time (using build args).
|
|
89
|
+
For example, with `bff.config.dev.json` and `bff.config.prod.json`:
|
|
90
|
+
|
|
91
|
+
```dockerfile
|
|
92
|
+
ARG ENVIRONMENT=''
|
|
93
|
+
COPY bff.config.${ENVIRONMENT}.json /application/bff.config.json
|
|
94
|
+
CMD ["auth-bff"]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Or select it at runtime, using an env var:
|
|
98
|
+
|
|
99
|
+
```dockerfile
|
|
100
|
+
COPY bff.config*.json /application/
|
|
101
|
+
CMD exec auth-bff --configFile bff.config.${ENVIRONMENT}.json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
## Configuration
|
|
106
|
+
|
|
107
|
+
Configuration is defined in json-files that look like this:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"issuer": "https://example-issuer.com/",
|
|
112
|
+
"clientId": "example-client",
|
|
113
|
+
"clientSecret": "{ssm:/secret/from/parameter/store}",
|
|
114
|
+
"redirectUri": "http://localhost:3000/auth/callback",
|
|
115
|
+
"cookieSecure": false,
|
|
116
|
+
"cookieSameSite": false,
|
|
117
|
+
"postLogoutRedirectUri": "http://localhost:3000/",
|
|
118
|
+
"sessionSecret": "{env:SECRET_FROM_ENV}",
|
|
119
|
+
"sessionStoreType": "memory",
|
|
120
|
+
"proxyTargets": {
|
|
121
|
+
"/api": "http://localhost:8080/api"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
By default it looks for a file named `bff.config.json`, but this can be overridden:
|
|
127
|
+
|
|
128
|
+
Vite:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
plugins: [
|
|
132
|
+
bff({configFile: ['bff.config.local.json', 'bff.config.json']}) //First existing file is used
|
|
133
|
+
]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Standalone:
|
|
137
|
+
|
|
138
|
+
```shell
|
|
139
|
+
auth-bff --configFile /path/to/bff.config.json
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The config file supports two special forms for loading values from other sources. Primarily meant for loading secrets:
|
|
143
|
+
|
|
144
|
+
Environment values:
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"sessionSecret": "{env:MY_SECRET}"
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
AWS Parameter store:
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"sessionSecret": "{ssm:/name/of/parameter}"
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
This loads from the configured AWS environment. For this to work on your local machine the `AWS_PROFILE` environment
|
|
161
|
+
variable must be set, and you must be signed in to that profile
|
|
162
|
+
|
|
163
|
+
ℹ️ [See `config.ts` for a description of all config parameters](src/config.ts)
|
|
164
|
+
|
|
165
|
+
## Using with ID-porten (via `okdata`):
|
|
166
|
+
|
|
167
|
+
`auth-bff` Has special support for keys generated by [`okdata`](https://github.com/oslokommune/okdata-cli).
|
|
168
|
+
|
|
169
|
+
1. Start by creating a new client and key using okdata:
|
|
170
|
+
|
|
171
|
+
```shell
|
|
172
|
+
~> okdata pubs create-client
|
|
173
|
+
* Environment test
|
|
174
|
+
* Team <team>
|
|
175
|
+
* Client type ID-porten
|
|
176
|
+
* Integration name (identifying in which system or case this client will be used) <your name here>
|
|
177
|
+
* Redirect URIs (comma-separated) http://localhost:3000/auth/callback
|
|
178
|
+
* Post logout Redirect URIs (comma-separated) http://localhost:3000
|
|
179
|
+
* Frontchannel logout URI http://localhost:3000/auth/logout
|
|
180
|
+
* Client URI (back URI) http://localhost:3000
|
|
181
|
+
...
|
|
182
|
+
~> okdata pubs create-key
|
|
183
|
+
* Environment test
|
|
184
|
+
* Client <same client as above>
|
|
185
|
+
* Where should the key be stored? Send the key to your AWS Parameter Store
|
|
186
|
+
* AWS account number <accno>
|
|
187
|
+
* AWS region eu-west-1 (Ireland)
|
|
188
|
+
* Automatic key rotation will replace the key in SSM nightly on weekdays.
|
|
189
|
+
Enable automatic key rotation for this client? Yes
|
|
190
|
+
...
|
|
191
|
+
A new key has been created and the following parameters have been written to SSM:
|
|
192
|
+
- /okdata/maskinporten/11111111-2222-3333-4444-555555555555/key.json
|
|
193
|
+
...
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
2. Set the following options in the config file:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"issuer": "https://test.idporten.no/",
|
|
201
|
+
"clientId": "11111111-2222-3333-4444-555555555555",
|
|
202
|
+
"okDataIdPortenKeyName": "/okdata/maskinporten/11111111-2222-3333-4444-555555555555/key.json",
|
|
203
|
+
...
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
3. Done!
|
|
208
|
+
|
|
209
|
+
## Configuring session storage
|
|
210
|
+
|
|
211
|
+
Currently only dynamoDb is supported for storing sessions in production. It requires some setup.
|
|
212
|
+
To work properly, the table must have ttl enabled, and an extra index for the `idp-sid`-property (used to delete
|
|
213
|
+
sessions during front-channel logout)
|
|
214
|
+
|
|
215
|
+
> [!WARNING]
|
|
216
|
+
> If the table does not exist, it will be automatically created with settings not appropriate for production.
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
Here is an example configuration in terraform
|
|
220
|
+
|
|
221
|
+
```terraform
|
|
222
|
+
resource "aws_dynamodb_table" "session_dynamodb_table" {
|
|
223
|
+
name = "${local.environment}-${local.main_container_name}-sessions"
|
|
224
|
+
billing_mode = "PAY_PER_REQUEST"
|
|
225
|
+
hash_key = "id"
|
|
226
|
+
|
|
227
|
+
on_demand_throughput {
|
|
228
|
+
max_read_request_units = 20
|
|
229
|
+
max_write_request_units = 20
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
attribute {
|
|
233
|
+
name = "id"
|
|
234
|
+
type = "S"
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
attribute {
|
|
238
|
+
name = "idp-sid"
|
|
239
|
+
type = "S"
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
ttl {
|
|
243
|
+
enabled = true
|
|
244
|
+
attribute_name = "expires"
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
global_secondary_index {
|
|
248
|
+
hash_key = "idp-sid"
|
|
249
|
+
name = "idp-sid-index"
|
|
250
|
+
projection_type = "KEYS_ONLY"
|
|
251
|
+
on_demand_throughput {
|
|
252
|
+
max_read_request_units = 20
|
|
253
|
+
max_write_request_units = 20
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Then remember to update the config to use the new table:
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"sessionStoreOptions": {
|
|
264
|
+
"table": "myteam-dev-myservice-sessions"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The read/write limits above are just an example, and can be changed/removed
|
|
270
|
+
|
|
271
|
+
## Required AWS permissions
|
|
272
|
+
|
|
273
|
+
When using SSM parameters or `okDataIdPortenKeyName` in the config, the service will need the `ssm:GetParameter`
|
|
274
|
+
permission for each parameter
|
|
275
|
+
|
|
276
|
+
If using dynamodb, the service will need the following permissions for the table
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
dynamodb:CreateTable
|
|
280
|
+
dynamodb:DescribeTable
|
|
281
|
+
dynamodb:PutItem
|
|
282
|
+
dynamodb:DeleteItem
|
|
283
|
+
dynamodb:GetItem
|
|
284
|
+
dynamodb:Scan
|
|
285
|
+
dynamodb:UpdateItem
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## React component
|
|
289
|
+
|
|
290
|
+
This package also includes a React component for handling authentication state. It will redirect to login if required
|
|
291
|
+
and optionally automatically poll for changes to authentication state.
|
|
292
|
+
|
|
293
|
+
### AuthContextProvider
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
import {AuthContextProvider} from "@oslokommune/auth-bff/react";
|
|
297
|
+
import {PktLoader} from "@oslokommune/punkt-react";
|
|
298
|
+
|
|
299
|
+
const fiveMinutes = 5 * 60 * 1000;
|
|
300
|
+
|
|
301
|
+
<AuthContextProvider authRequired={true} loaderComponent={<PktLoader/>} pollInterval={fiveMinues}>
|
|
302
|
+
<App/>
|
|
303
|
+
</AuthContextProvider>
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
| Option | Description |
|
|
307
|
+
|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
308
|
+
| authRequired | Whether authentication is required. If true, will redirect to login before rendering child components (default: true) |
|
|
309
|
+
| loaderComponent | React component to display while loading auth state. (default: null) |
|
|
310
|
+
| baseUrl | Must be set to the same baseUrl as in the json config for login/logout to work correctly (default: '') |
|
|
311
|
+
| pollInterval | Minimum interval in milliseconds between checks if session is still active. Will set authState to 'expired' if session is expired (default: disabled) |
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
### useAuthContext
|
|
315
|
+
|
|
316
|
+
Hook to get current AuthState. Must be called in a component inside the AuthContextProvider.
|
|
317
|
+
```tsx
|
|
318
|
+
import {useAuthContext} from "@oslokommune/auth-bff/react";
|
|
319
|
+
|
|
320
|
+
const {user, authState, login} = useAuthContext()
|
|
321
|
+
if(authState === 'authenticated') {
|
|
322
|
+
console.log(`Hello, ${user.pid}`)
|
|
323
|
+
} else {
|
|
324
|
+
login()
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
| Property | Description |
|
|
330
|
+
|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
331
|
+
| user | The currently logged in user, or null if not logged in. User object contains the id_token claims returned from the usr endpoint. See config option `userClaims`. |
|
|
332
|
+
| login | Function that will redirect to the login endpoint when invoked |
|
|
333
|
+
| logout | Function that will redirect to the logout endpoint when invoked |
|
|
334
|
+
| authState | Current auth state. See table below for values | | |
|
|
335
|
+
|
|
336
|
+
#### AuthState
|
|
337
|
+
| Value | Description |
|
|
338
|
+
|-----------------|------------------------------------------------------------------------------------------------------------------|
|
|
339
|
+
| pending | Initial value before auth state has been determined |
|
|
340
|
+
| authenticated | User is authenticated. |
|
|
341
|
+
| unauthenticated | User is not authenticated |
|
|
342
|
+
| expired | User was authenticated, but the session has expired. Can be used to display message to user or redirect to login | | |
|
|
343
|
+
| error | Failed to determine auth state | | |
|
|
344
|
+
|
package/dist/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oslokommune/auth-bff",
|
|
3
|
-
"version": "2.0.0-
|
|
3
|
+
"version": "2.0.0-beta3",
|
|
4
4
|
"repository": "https://github.com/oslokommune/auth-bff.git",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsc",
|
|
10
|
-
"run": "node ./dist/server.
|
|
10
|
+
"run": "node ./dist/src/server.js",
|
|
11
11
|
"build-and-publish": "tsc && npm publish",
|
|
12
12
|
"build-and-publish-prerelease": "tsc && npm publish --tag prerelease",
|
|
13
13
|
"test": "vitest"
|
|
14
14
|
},
|
|
15
15
|
"exports": {
|
|
16
|
-
"./vite-plugin": "./dist/src/vite-plugin.
|
|
16
|
+
"./vite-plugin": "./dist/src/vite-plugin.js",
|
|
17
17
|
"./react": "./dist/src/react/index.js"
|
|
18
18
|
},
|
|
19
19
|
"bin": {
|
|
20
|
-
"auth-bff": "dist/src/server.
|
|
20
|
+
"auth-bff": "dist/src/server.js"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"/dist"
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"license": "",
|
|
28
28
|
"description": "",
|
|
29
29
|
"devDependencies": {
|
|
30
|
+
"@types/command-line-args": "^5.2.3",
|
|
30
31
|
"@types/compression": "^1.8.1",
|
|
31
32
|
"@types/express": "^4.17.22",
|
|
32
33
|
"@types/express-session": "^1.18.2",
|
|
@@ -39,19 +40,19 @@
|
|
|
39
40
|
"vitest": "^4.0.18"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
|
-
"@aws-sdk/client-dynamodb": "^3.
|
|
43
|
-
"@aws-sdk/client-ssm": "^3.
|
|
43
|
+
"@aws-sdk/client-dynamodb": "^3.990.0",
|
|
44
|
+
"@aws-sdk/client-ssm": "^3.990.0",
|
|
44
45
|
"command-line-args": "^6.0.1",
|
|
45
|
-
"compression": "^1.8.
|
|
46
|
+
"compression": "^1.8.1",
|
|
46
47
|
"connect-dynamodb": "^3.0.5",
|
|
47
|
-
"express": "4.
|
|
48
|
-
"express-session": "1.
|
|
48
|
+
"express": "4.22.1",
|
|
49
|
+
"express-session": "1.19.0",
|
|
49
50
|
"find-up": "^7.0.0",
|
|
50
51
|
"helmet": "^8.1.0",
|
|
51
52
|
"http-proxy-middleware": "^3.0.5",
|
|
52
|
-
"jose": "^6.
|
|
53
|
+
"jose": "^6.1.3",
|
|
53
54
|
"node-forge": "1.3.3",
|
|
54
|
-
"openid-client": "^6.8.
|
|
55
|
+
"openid-client": "^6.8.2",
|
|
55
56
|
"string-replace-middleware": "^1.1.0"
|
|
56
57
|
}
|
|
57
58
|
}
|
package/dist/src/config.d.ts
CHANGED
|
@@ -1,23 +1,126 @@
|
|
|
1
|
+
import { HelmetOptions } from "helmet";
|
|
2
|
+
import session from "express-session";
|
|
1
3
|
export type BffConfig = {
|
|
4
|
+
/**
|
|
5
|
+
* The port at which the app will be served. Only used in standalone mode, to change the port used during development,
|
|
6
|
+
* set it in your vite config instead.
|
|
7
|
+
*/
|
|
8
|
+
port: number;
|
|
9
|
+
/**
|
|
10
|
+
* The base root path. Change if app is served from a non-root path.
|
|
11
|
+
*
|
|
12
|
+
* Default: `/`
|
|
13
|
+
*/
|
|
2
14
|
basePath?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The root path of the static resources to be served
|
|
17
|
+
*
|
|
18
|
+
* Default: `/dist`
|
|
19
|
+
*/
|
|
3
20
|
staticRootPath?: string;
|
|
21
|
+
/**
|
|
22
|
+
* The issuer
|
|
23
|
+
*
|
|
24
|
+
* Example: `https://test.idporten.no/`
|
|
25
|
+
*/
|
|
4
26
|
issuer: string;
|
|
27
|
+
/**
|
|
28
|
+
* The ID of the client
|
|
29
|
+
*/
|
|
5
30
|
clientId: string;
|
|
31
|
+
/**
|
|
32
|
+
* The client secret. Not used if `okDataIdPortenKeyName` is set.
|
|
33
|
+
*/
|
|
6
34
|
clientSecret?: string;
|
|
35
|
+
/**
|
|
36
|
+
* The redirect uri configured for the client
|
|
37
|
+
*/
|
|
7
38
|
redirectUri: string;
|
|
8
|
-
|
|
39
|
+
/**
|
|
40
|
+
* The intended audience for the tokens. Value(s) set here can be used to verify audience on the recipient end.
|
|
41
|
+
* See https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-indicators-05.
|
|
42
|
+
*
|
|
43
|
+
* Example: `["https://example.com/api1", "https://example.com/api2"]`
|
|
44
|
+
*/
|
|
45
|
+
resources?: Array<string>;
|
|
46
|
+
/**
|
|
47
|
+
* express-session cookie options, note that if this is set, the below cookie*-options will have no effect
|
|
48
|
+
*/
|
|
49
|
+
cookie?: session.CookieOptions;
|
|
50
|
+
/**
|
|
51
|
+
* Sets the Path attribute of the cookie. Should most likely be the same as basePath. See https://expressjs.com/en/resources/middleware/session.html
|
|
52
|
+
*
|
|
53
|
+
* Default: `/`
|
|
54
|
+
*/
|
|
9
55
|
cookiePath?: string;
|
|
10
|
-
|
|
11
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Sets the Secure attribute of the cookie. See https://expressjs.com/en/resources/middleware/session.html
|
|
58
|
+
*
|
|
59
|
+
* Default: `true`
|
|
60
|
+
*/
|
|
61
|
+
cookieSecure?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Sets the SameSite attribute of the cookie. See https://expressjs.com/en/resources/middleware/session.html
|
|
64
|
+
*
|
|
65
|
+
* Default: `"lax"`
|
|
66
|
+
*/
|
|
67
|
+
cookieSameSite: boolean | "lax" | "none" | "strict";
|
|
68
|
+
/**
|
|
69
|
+
* The post logout redirect uri configured for the client
|
|
70
|
+
*/
|
|
12
71
|
postLogoutRedirectUri: string;
|
|
72
|
+
/**
|
|
73
|
+
* The name of the key that okdata stored in parameter store.
|
|
74
|
+
*
|
|
75
|
+
* Example: `/okdata/maskinporten/11111111-2222-3333-4444-555555555555/key.json`
|
|
76
|
+
*/
|
|
13
77
|
okDataIdPortenKeyName: string;
|
|
78
|
+
/**
|
|
79
|
+
* Secret used to sign sessions
|
|
80
|
+
*/
|
|
14
81
|
sessionSecret: string;
|
|
82
|
+
/**
|
|
83
|
+
* The type of session store used. Only `memory` and `dynamodb` currently supported. `memory` is only for dev use
|
|
84
|
+
*/
|
|
15
85
|
sessionStoreType: 'memory' | 'dynamodb';
|
|
86
|
+
/**
|
|
87
|
+
* Options that will be passed to the chosen sessionStore. Options depend on the type of store chosen
|
|
88
|
+
*
|
|
89
|
+
* Example: `{"table": "my-custom-dynamodb-table"}`
|
|
90
|
+
*/
|
|
16
91
|
sessionStoreOptions?: object;
|
|
92
|
+
/**
|
|
93
|
+
* Map of paths and remote targets that will be forwarded by the proxy
|
|
94
|
+
*
|
|
95
|
+
* Example: `{'/api': 'http://example.com/api'}`
|
|
96
|
+
*/
|
|
17
97
|
proxyTargets: {
|
|
18
98
|
[path: string]: string;
|
|
19
99
|
};
|
|
20
|
-
|
|
100
|
+
/**
|
|
101
|
+
* List of claims in the id_token that are returned by the /user-endpoint. By default all are returned
|
|
102
|
+
*
|
|
103
|
+
* Example: `["pid"]`
|
|
104
|
+
*/
|
|
105
|
+
userClaims?: Array<string>;
|
|
106
|
+
/**
|
|
107
|
+
* Content security policy configuration passed to helmet.
|
|
108
|
+
* See https://github.com/helmetjs/helmet for details. Note that since the config in limited to json,
|
|
109
|
+
* some features are not supported. To set a nonce value, use the special value `"{nonce}"` instead.
|
|
110
|
+
*
|
|
111
|
+
*
|
|
112
|
+
* Example:
|
|
113
|
+
* ```json
|
|
114
|
+
* {
|
|
115
|
+
* "directives": {
|
|
116
|
+
* "default-src": ["'self'", "https://*.oslo.kommune.no", "https://*.oslo.systems"],
|
|
117
|
+
* "script-src": ["'self'", "{nonce}"],
|
|
118
|
+
* ...
|
|
119
|
+
* }
|
|
120
|
+
* }
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
contentSecurityPolicy?: Exclude<HelmetOptions['contentSecurityPolicy'], Boolean>;
|
|
21
124
|
};
|
|
22
125
|
export declare function getEnv(env: string, defaultVal?: string, parseFn?: (val: string) => string): string;
|
|
23
126
|
export declare function getSsmParameter(name: string, withDecryption?: boolean): Promise<string>;
|
package/dist/src/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,aAAa,EAAC,MAAM,QAAQ,CAAC;AACrC,OAAO,OAAO,MAAM,iBAAiB,CAAC;AAEtC,MAAM,MAAM,SAAS,GAAG;IACtB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAA;IACd;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IACzB;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAA;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;;OAIG;IACH,cAAc,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAA;IACnD;;OAEG;IACH,qBAAqB,EAAE,MAAM,CAAA;IAC7B;;;;OAIG;IACH,qBAAqB,EAAE,MAAM,CAAA;IAC7B;;OAEG;IACH,aAAa,EAAE,MAAM,CAAA;IACrB;;OAEG;IACH,gBAAgB,EAAE,QAAQ,GAAG,UAAU,CAAA;IACvC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;OAIG;IACH,YAAY,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAA;IACxC;;;;OAIG;IACH,UAAU,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IAC1B;;;;;;;;;;;;;;;;OAgBG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC,CAAA;CACjF,CAAA;AAUD,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,UAQzF;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,GAAE,OAAc,mBAMjF;AAKD,wBAAsB,UAAU,CAAC,UAAU,GAAE,MAA0B,sBAyBtE"}
|
package/dist/src/config.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { findUp } from 'find-up';
|
|
2
2
|
import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
|
|
3
|
+
const defaultConfig = {
|
|
4
|
+
basePath: "",
|
|
5
|
+
cookiePath: '/',
|
|
6
|
+
cookieSecure: true,
|
|
7
|
+
cookieSameSite: 'lax',
|
|
8
|
+
staticRootPath: './dist'
|
|
9
|
+
};
|
|
3
10
|
export function getEnv(env, defaultVal, parseFn) {
|
|
4
11
|
if (process.env[env]) {
|
|
5
12
|
return parseFn ? parseFn(process.env[env]) : process.env[env];
|
|
@@ -19,13 +26,6 @@ export async function getSsmParameter(name, withDecryption = true) {
|
|
|
19
26
|
WithDecryption: withDecryption
|
|
20
27
|
})).then(p => p.Parameter.Value);
|
|
21
28
|
}
|
|
22
|
-
const defaultConfig = {
|
|
23
|
-
basePath: "",
|
|
24
|
-
cookiePath: '/',
|
|
25
|
-
cookieSecure: true,
|
|
26
|
-
cookieSameSite: 'lax',
|
|
27
|
-
staticRootPath: './dist'
|
|
28
|
-
};
|
|
29
29
|
let config;
|
|
30
30
|
export async function loadConfig(configFile = 'bff.config.json') {
|
|
31
31
|
if (config)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OidcMiddleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/OidcMiddleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,mBAAmB,EAAC,MAAM,2BAA2B,CAAC;AAE9D,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,EAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAC,MAAM,SAAS,CAAA;AAK5D,qBAAa,cAAc;;IAKzB;;;;OAIG;gBACS,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,mBAAmB;WASpD,MAAM,CAAC,MAAM,EAAE,SAAS;IAgErC,IAAI,gBAAgB,KACV,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,UAWxD;IAED,IAAI,KAAK,KACO,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,mBA+B9D;IAqBD,IAAI,QAAQ,KACI,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,mBAuC9D;IAED,IAAI,IAAI,KACQ,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"OidcMiddleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/OidcMiddleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,mBAAmB,EAAC,MAAM,2BAA2B,CAAC;AAE9D,OAAO,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,EAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAC,MAAM,SAAS,CAAA;AAK5D,qBAAa,cAAc;;IAKzB;;;;OAIG;gBACS,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,mBAAmB;WASpD,MAAM,CAAC,MAAM,EAAE,SAAS;IAgErC,IAAI,gBAAgB,KACV,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,UAWxD;IAED,IAAI,KAAK,KACO,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,mBA+B9D;IAqBD,IAAI,QAAQ,KACI,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,mBAuC9D;IAED,IAAI,IAAI,KACQ,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,iDAc9D;IAED,IAAI,MAAM,KACA,KAAK,OAAO,EAAE,KAAK,QAAQ,UASpC;IAED,IAAI,kBAAkB,KACN,KAAK,OAAO,EAAE,KAAK,QAAQ,mBAc1C;CACF"}
|
|
@@ -121,6 +121,7 @@ export class OidcMiddleware {
|
|
|
121
121
|
try {
|
|
122
122
|
const tokenResponse = await __classPrivateFieldGet(this, _OidcMiddleware_instances, "m", _OidcMiddleware_getFreshTokens).call(this, req);
|
|
123
123
|
if (!tokenResponse) {
|
|
124
|
+
console.log('/user 401: No tokenset');
|
|
124
125
|
return res.sendStatus(401);
|
|
125
126
|
}
|
|
126
127
|
return res.send(req.session.userClaims);
|
|
@@ -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,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
|
+
}
|
|
@@ -18,7 +18,7 @@ export function proxyRoutes(config, oidcMiddleware) {
|
|
|
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,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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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,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
|
+
}
|
|
@@ -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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { stringReplace } from "string-replace-middleware";
|
|
3
|
+
import path from "path";
|
|
4
|
+
export function staticRoutes(config) {
|
|
5
|
+
const router = express.Router();
|
|
6
|
+
router.use(stringReplace({
|
|
7
|
+
'__CSP_NONCE__': (_, res) => res.locals.cspNonce
|
|
8
|
+
}, {
|
|
9
|
+
contentTypeFilterRegexp: /^text\/html/
|
|
10
|
+
}));
|
|
11
|
+
const staticPath = path.resolve(process.cwd(), config.staticRootPath);
|
|
12
|
+
console.log(`Serving static content from '${staticPath}'`);
|
|
13
|
+
router.use(express.static(staticPath, { index: false }));
|
|
14
|
+
router.get('*', function (_, res) {
|
|
15
|
+
res.set('Cache-Control', 'no-store');
|
|
16
|
+
res.sendFile(path.resolve(staticPath, 'index.html'));
|
|
17
|
+
});
|
|
18
|
+
return router;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import express from "express";
|
|
3
|
+
import compression from "compression";
|
|
4
|
+
import { loadConfig } from './config.js';
|
|
5
|
+
import { proxyRoutes } from "./middleware/proxy-routes.js";
|
|
6
|
+
import { staticRoutes } from "./middleware/static-routes.js";
|
|
7
|
+
import { securityHeaders } from "./middleware/security-headers.js";
|
|
8
|
+
import { sessions } from "./middleware/sessions/sessions.js";
|
|
9
|
+
import { oidcRoutes } from "./middleware/oidc-routes.js";
|
|
10
|
+
import { OidcMiddleware } from "./middleware/OidcMiddleware.js";
|
|
11
|
+
import commandLineArgs from "command-line-args";
|
|
12
|
+
import packageJson from "../package.json" with { type: 'json' };
|
|
13
|
+
const options = commandLineArgs([{ name: 'configFile' }]);
|
|
14
|
+
const config = await loadConfig(options.configFile);
|
|
15
|
+
const port = process.env.port || config.port || 8080;
|
|
16
|
+
const oidcMiddleware = await OidcMiddleware.create(config);
|
|
17
|
+
const requestLogger = (req, _, next) => {
|
|
18
|
+
next();
|
|
19
|
+
console.log(`${req.method} ${req.originalUrl}, referer=${req.get('Referer')}, user-agent=${req.get('User-Agent')}`);
|
|
20
|
+
};
|
|
21
|
+
const app = express();
|
|
22
|
+
app.set('trust proxy', true); // TODO: sjekk om denne kan/bør være strengere: https://expressjs.com/en/api.html#trust.proxy.options.table
|
|
23
|
+
app.disable("x-powered-by");
|
|
24
|
+
app.use(compression());
|
|
25
|
+
app.use(sessions(config));
|
|
26
|
+
app.use(securityHeaders(config));
|
|
27
|
+
app.get("/health", (_, res) => {
|
|
28
|
+
res.send("OK");
|
|
29
|
+
});
|
|
30
|
+
const basePath = config.basePath || "/";
|
|
31
|
+
app.use(basePath, oidcRoutes(oidcMiddleware));
|
|
32
|
+
app.use(requestLogger); //NB, må stå her for å ikke logge auth-requestene over
|
|
33
|
+
app.use(basePath, proxyRoutes(config, oidcMiddleware));
|
|
34
|
+
app.use(basePath, staticRoutes(config));
|
|
35
|
+
const server = app.listen(port, () => {
|
|
36
|
+
console.log(`auth-bff ${packageJson.version} started on port ${port}`);
|
|
37
|
+
});
|
|
38
|
+
process.on('SIGTERM', () => {
|
|
39
|
+
console.log('SIGTERM received. Closing...');
|
|
40
|
+
server.close(() => {
|
|
41
|
+
console.log('Server closed');
|
|
42
|
+
});
|
|
43
|
+
});
|
package/dist/src/server.mjs
CHANGED
|
File without changes
|
package/dist/src/utils.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export function redact(string:
|
|
1
|
+
export declare function redact(string: string, length?: number): string;
|
|
2
2
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/src/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,MAAU,UAExD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../../src/vite-plugin.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,MAAM,EAAC,MAAM,MAAM,CAAA;AAsB1C,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,EAAC,UAAU,EAAC,GAAE;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAM,GAAG,MAAM,CAO5E"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { loadConfig } from "./config.js";
|
|
3
|
+
import { OidcMiddleware } from "./middleware/OidcMiddleware.js";
|
|
4
|
+
function configureServer(configFilePath) {
|
|
5
|
+
return async ({ middlewares }) => {
|
|
6
|
+
const { oidcRoutes } = await import("./middleware/oidc-routes.js");
|
|
7
|
+
const { proxyRoutes } = await import("./middleware/proxy-routes.js");
|
|
8
|
+
const { sessions } = await import("./middleware/sessions/sessions.js");
|
|
9
|
+
const config = await loadConfig(configFilePath);
|
|
10
|
+
const oidcMiddleware = await OidcMiddleware.create(config);
|
|
11
|
+
const basePath = config.basePath || "/";
|
|
12
|
+
const app = express();
|
|
13
|
+
app.use(sessions(config));
|
|
14
|
+
app.use(basePath, oidcRoutes(oidcMiddleware));
|
|
15
|
+
app.use(basePath, proxyRoutes(config, oidcMiddleware));
|
|
16
|
+
middlewares.use(app);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export default function bff({ configFile } = {}) {
|
|
20
|
+
return {
|
|
21
|
+
name: 'bff',
|
|
22
|
+
apply: 'serve',
|
|
23
|
+
configureServer: configureServer(configFile),
|
|
24
|
+
configurePreviewServer: configureServer(configFile)
|
|
25
|
+
};
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oslokommune/auth-bff",
|
|
3
|
-
"version": "2.0.0-
|
|
3
|
+
"version": "2.0.0-beta3",
|
|
4
4
|
"repository": "https://github.com/oslokommune/auth-bff.git",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsc",
|
|
10
|
-
"run": "node ./dist/server.
|
|
10
|
+
"run": "node ./dist/src/server.js",
|
|
11
11
|
"build-and-publish": "tsc && npm publish",
|
|
12
12
|
"build-and-publish-prerelease": "tsc && npm publish --tag prerelease",
|
|
13
13
|
"test": "vitest"
|
|
14
14
|
},
|
|
15
15
|
"exports": {
|
|
16
|
-
"./vite-plugin": "./dist/src/vite-plugin.
|
|
16
|
+
"./vite-plugin": "./dist/src/vite-plugin.js",
|
|
17
17
|
"./react": "./dist/src/react/index.js"
|
|
18
18
|
},
|
|
19
19
|
"bin": {
|
|
20
|
-
"auth-bff": "dist/src/server.
|
|
20
|
+
"auth-bff": "dist/src/server.js"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"/dist"
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"license": "",
|
|
28
28
|
"description": "",
|
|
29
29
|
"devDependencies": {
|
|
30
|
+
"@types/command-line-args": "^5.2.3",
|
|
30
31
|
"@types/compression": "^1.8.1",
|
|
31
32
|
"@types/express": "^4.17.22",
|
|
32
33
|
"@types/express-session": "^1.18.2",
|
|
@@ -39,19 +40,19 @@
|
|
|
39
40
|
"vitest": "^4.0.18"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
|
-
"@aws-sdk/client-dynamodb": "^3.
|
|
43
|
-
"@aws-sdk/client-ssm": "^3.
|
|
43
|
+
"@aws-sdk/client-dynamodb": "^3.990.0",
|
|
44
|
+
"@aws-sdk/client-ssm": "^3.990.0",
|
|
44
45
|
"command-line-args": "^6.0.1",
|
|
45
|
-
"compression": "^1.8.
|
|
46
|
+
"compression": "^1.8.1",
|
|
46
47
|
"connect-dynamodb": "^3.0.5",
|
|
47
|
-
"express": "4.
|
|
48
|
-
"express-session": "1.
|
|
48
|
+
"express": "4.22.1",
|
|
49
|
+
"express-session": "1.19.0",
|
|
49
50
|
"find-up": "^7.0.0",
|
|
50
51
|
"helmet": "^8.1.0",
|
|
51
52
|
"http-proxy-middleware": "^3.0.5",
|
|
52
|
-
"jose": "^6.
|
|
53
|
+
"jose": "^6.1.3",
|
|
53
54
|
"node-forge": "1.3.3",
|
|
54
|
-
"openid-client": "^6.8.
|
|
55
|
+
"openid-client": "^6.8.2",
|
|
55
56
|
"string-replace-middleware": "^1.1.0"
|
|
56
57
|
}
|
|
57
58
|
}
|