@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
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
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
|
+
## Configuration
|
|
64
|
+
|
|
65
|
+
Configuration is set in json-files that look like this:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"issuer": "https://example-issuer.com/",
|
|
70
|
+
"clientId": "example-client",
|
|
71
|
+
"clientSecret": "{ssm:/secret/from/parameter/store}",
|
|
72
|
+
"redirectUri": "http://localhost:3000/auth/callback",
|
|
73
|
+
"cookieSecure": false,
|
|
74
|
+
"cookieSameSite": false,
|
|
75
|
+
"postLogoutRedirectUri": "http://localhost:3000/",
|
|
76
|
+
"sessionSecret": "{env:SECRET_FROM_ENV}",
|
|
77
|
+
"sessionStoreType": "memory",
|
|
78
|
+
"proxyTargets": {
|
|
79
|
+
"/api": "http://localhost:8080/api"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
By default it looks for a file named `bff.config.json`. But this can be overridden:
|
|
85
|
+
|
|
86
|
+
Vite:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
plugins: [
|
|
90
|
+
bff({configFile: ['bff.config.local.json', 'bff.config.json']}) //First existing file is used
|
|
91
|
+
]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Standalone:
|
|
95
|
+
|
|
96
|
+
```shell
|
|
97
|
+
auth-bff --configFile /path/to/bff.config.json
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The config file supports two special forms for loading values from other sources. Primarily meant for loading secrets:
|
|
101
|
+
|
|
102
|
+
Environment values:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"sessionSecret": "{env:MY_SECRET}"
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
AWS Parameter store:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"sessionSecret": "{ssm:/name/of/parameter}"
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
This loads from the configured AWS environment. For this to work on your local machine the `AWS_PROFILE` environment
|
|
119
|
+
variable must be set, and you must be signed in to that profile
|
|
120
|
+
|
|
121
|
+
See [config.ts](src/config.ts) description of all config parameters
|
|
122
|
+
|
|
123
|
+
### Using with ID-porten (via `okdata`):
|
|
124
|
+
|
|
125
|
+
`auth-bff` Has special support for keys generated by [`okdata`](https://github.com/oslokommune/okdata-cli).
|
|
126
|
+
|
|
127
|
+
1. Start by creating a new client and key using okdata:
|
|
128
|
+
|
|
129
|
+
```shell
|
|
130
|
+
~> okdata pubs create-client
|
|
131
|
+
* Environment test
|
|
132
|
+
* Team <team>
|
|
133
|
+
* Client type ID-porten
|
|
134
|
+
* Integration name (identifying in which system or case this client will be used) <your name here>
|
|
135
|
+
* Redirect URIs (comma-separated) http://localhost:3000/auth/callback
|
|
136
|
+
* Post logout Redirect URIs (comma-separated) http://localhost:3000
|
|
137
|
+
* Frontchannel logout URI http://localhost:3000/auth/logout
|
|
138
|
+
* Client URI (back URI) http://localhost:3000
|
|
139
|
+
...
|
|
140
|
+
~> okdata pubs create-key
|
|
141
|
+
* Environment test
|
|
142
|
+
* Client <same client as above>
|
|
143
|
+
* Where should the key be stored? Send the key to your AWS Parameter Store
|
|
144
|
+
* AWS account number <accno>
|
|
145
|
+
* AWS region eu-west-1 (Ireland)
|
|
146
|
+
* Automatic key rotation will replace the key in SSM nightly on weekdays.
|
|
147
|
+
Enable automatic key rotation for this client? Yes
|
|
148
|
+
...
|
|
149
|
+
A new key has been created and the following parameters have been written to SSM:
|
|
150
|
+
- /okdata/maskinporten/11111111-2222-3333-4444-555555555555/key.json
|
|
151
|
+
...
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
2. Set the following options in the config file:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"issuer": "https://test.idporten.no/",
|
|
159
|
+
"clientId": "11111111-2222-3333-4444-555555555555",
|
|
160
|
+
"okDataIdPortenKeyName": "/okdata/maskinporten/11111111-2222-3333-4444-555555555555/key.json",
|
|
161
|
+
...
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
3. Done!
|
|
166
|
+
|
|
167
|
+
## Configuring session storage
|
|
168
|
+
|
|
169
|
+
Currently only dynamoDb is supported for storing sessions in production. It requires some setup.
|
|
170
|
+
To work properly, the table must have ttl enabled, and an extra index for the `idp-sid`-property (used to delete
|
|
171
|
+
sessions during front-channel logout)
|
|
172
|
+
|
|
173
|
+
> [!WARNING]
|
|
174
|
+
> If the table does not exist, it will be automatically created with settings not appropriate for production.
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
Here is an example configuration in terraform
|
|
178
|
+
|
|
179
|
+
```terraform
|
|
180
|
+
resource "aws_dynamodb_table" "session_dynamodb_table" {
|
|
181
|
+
name = "${local.environment}-${local.main_container_name}-sessions"
|
|
182
|
+
billing_mode = "PAY_PER_REQUEST"
|
|
183
|
+
hash_key = "id"
|
|
184
|
+
|
|
185
|
+
on_demand_throughput {
|
|
186
|
+
max_read_request_units = 20
|
|
187
|
+
max_write_request_units = 20
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
attribute {
|
|
191
|
+
name = "id"
|
|
192
|
+
type = "S"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
attribute {
|
|
196
|
+
name = "idp-sid"
|
|
197
|
+
type = "S"
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
ttl {
|
|
201
|
+
enabled = true
|
|
202
|
+
attribute_name = "expires"
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
global_secondary_index {
|
|
206
|
+
hash_key = "idp-sid"
|
|
207
|
+
name = "idp-sid-index"
|
|
208
|
+
projection_type = "KEYS_ONLY"
|
|
209
|
+
on_demand_throughput {
|
|
210
|
+
max_read_request_units = 20
|
|
211
|
+
max_write_request_units = 20
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The read/write limits above are just an example, and can be changed/removed
|
|
218
|
+
|
|
219
|
+
## Required AWS permissions
|
|
220
|
+
|
|
221
|
+
When using SSM parameters or `okDataIdPortenKeyName` in the config, the service will need the `ssm:GetParameter`
|
|
222
|
+
permission for each parameter
|
|
223
|
+
|
|
224
|
+
If using dynamodb, the service will need the following permissions for the table
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
dynamodb:CreateTable
|
|
228
|
+
dynamodb:DescribeTable
|
|
229
|
+
dynamodb:PutItem
|
|
230
|
+
dynamodb:DeleteItem
|
|
231
|
+
dynamodb:GetItem
|
|
232
|
+
dynamodb:Scan
|
|
233
|
+
dynamodb:UpdateItem
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## React AuthContext
|
|
237
|
+
|
|
238
|
+
This package also includes a React component for handling authentication state
|
|
239
|
+
|
|
240
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oslokommune/auth-bff",
|
|
3
|
+
"version": "2.0.0-beta2",
|
|
4
|
+
"repository": "https://github.com/oslokommune/auth-bff.git",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"run": "node ./dist/src/server.js",
|
|
11
|
+
"build-and-publish": "tsc && npm publish",
|
|
12
|
+
"build-and-publish-prerelease": "tsc && npm publish --tag prerelease",
|
|
13
|
+
"test": "vitest"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
"./vite-plugin": "./dist/src/vite-plugin.js",
|
|
17
|
+
"./react": "./dist/src/react/index.js"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"auth-bff": "dist/src/server.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"/dist"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"author": "",
|
|
27
|
+
"license": "",
|
|
28
|
+
"description": "",
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/command-line-args": "^5.2.3",
|
|
31
|
+
"@types/compression": "^1.8.1",
|
|
32
|
+
"@types/express": "^4.17.22",
|
|
33
|
+
"@types/express-session": "^1.18.2",
|
|
34
|
+
"@types/node-forge": "1.3.13",
|
|
35
|
+
"@types/react": "17.0.87",
|
|
36
|
+
"@types/supertest": "^6.0.3",
|
|
37
|
+
"react": "17.0.2",
|
|
38
|
+
"supertest": "^7.2.2",
|
|
39
|
+
"typescript": "^5.9.3",
|
|
40
|
+
"vitest": "^4.0.18"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@aws-sdk/client-dynamodb": "^3.990.0",
|
|
44
|
+
"@aws-sdk/client-ssm": "^3.990.0",
|
|
45
|
+
"command-line-args": "^6.0.1",
|
|
46
|
+
"compression": "^1.8.1",
|
|
47
|
+
"connect-dynamodb": "^3.0.5",
|
|
48
|
+
"express": "4.22.1",
|
|
49
|
+
"express-session": "1.19.0",
|
|
50
|
+
"find-up": "^7.0.0",
|
|
51
|
+
"helmet": "^8.1.0",
|
|
52
|
+
"http-proxy-middleware": "^3.0.5",
|
|
53
|
+
"jose": "^6.1.3",
|
|
54
|
+
"node-forge": "1.3.3",
|
|
55
|
+
"openid-client": "^6.8.2",
|
|
56
|
+
"string-replace-middleware": "^1.1.0"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as client from 'openid-client';
|
|
2
|
+
import { BffConfig } from "./config.js";
|
|
3
|
+
export declare class OpenIdConfigManager {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(config: BffConfig, serverMetadata?: client.ServerMetadata);
|
|
6
|
+
init(): Promise<void>;
|
|
7
|
+
updateOpenIdConfig(): Promise<void>;
|
|
8
|
+
get openIdConfig(): client.Configuration;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=OpenIdConfigManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OpenIdConfigManager.d.ts","sourceRoot":"","sources":["../../src/OpenIdConfigManager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAC,SAAS,EAAkB,MAAM,aAAa,CAAC;AASvD,qBAAa,mBAAmB;;gBAMlB,MAAM,EAAE,SAAS,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,cAAc;IAK/D,IAAI;IA4BJ,kBAAkB;IAuCxB,IAAI,YAAY,yBAEf;CAEF"}
|
|
@@ -0,0 +1,77 @@
|
|
|
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 _OpenIdConfigManager_instances, _OpenIdConfigManager_bffConfig, _OpenIdConfigManager_openIdConfig, _OpenIdConfigManager_serverMetadata, _OpenIdConfigManager_p12ToJwk, _OpenIdConfigManager_createKeyFromOkData;
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
import forge from 'node-forge';
|
|
15
|
+
import * as jose from 'jose';
|
|
16
|
+
import * as client from 'openid-client';
|
|
17
|
+
import { getSsmParameter } from "./config.js";
|
|
18
|
+
export class OpenIdConfigManager {
|
|
19
|
+
constructor(config, serverMetadata) {
|
|
20
|
+
_OpenIdConfigManager_instances.add(this);
|
|
21
|
+
_OpenIdConfigManager_bffConfig.set(this, void 0);
|
|
22
|
+
_OpenIdConfigManager_openIdConfig.set(this, void 0);
|
|
23
|
+
_OpenIdConfigManager_serverMetadata.set(this, void 0);
|
|
24
|
+
__classPrivateFieldSet(this, _OpenIdConfigManager_bffConfig, config, "f");
|
|
25
|
+
__classPrivateFieldSet(this, _OpenIdConfigManager_serverMetadata, serverMetadata, "f");
|
|
26
|
+
}
|
|
27
|
+
async init() {
|
|
28
|
+
await this.updateOpenIdConfig();
|
|
29
|
+
if (__classPrivateFieldGet(this, _OpenIdConfigManager_bffConfig, "f").okDataIdPortenKeyName) {
|
|
30
|
+
setInterval(async () => {
|
|
31
|
+
await this.updateOpenIdConfig();
|
|
32
|
+
}, 5 * 60 * 1000);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async updateOpenIdConfig() {
|
|
36
|
+
console.log('Updating OpenId config...');
|
|
37
|
+
let key;
|
|
38
|
+
if (__classPrivateFieldGet(this, _OpenIdConfigManager_bffConfig, "f").okDataIdPortenKeyName) {
|
|
39
|
+
console.log('Fetching okdata key');
|
|
40
|
+
key = await __classPrivateFieldGet(this, _OpenIdConfigManager_instances, "m", _OpenIdConfigManager_createKeyFromOkData).call(this, __classPrivateFieldGet(this, _OpenIdConfigManager_bffConfig, "f").okDataIdPortenKeyName);
|
|
41
|
+
}
|
|
42
|
+
const clientId = __classPrivateFieldGet(this, _OpenIdConfigManager_bffConfig, "f").clientId;
|
|
43
|
+
const clientMetadata = { client_secret: __classPrivateFieldGet(this, _OpenIdConfigManager_bffConfig, "f").clientSecret };
|
|
44
|
+
const clientAuth = key ? client.PrivateKeyJwt(key) : undefined;
|
|
45
|
+
if (__classPrivateFieldGet(this, _OpenIdConfigManager_openIdConfig, "f")) {
|
|
46
|
+
console.log('Reusing OpenId Config with new key');
|
|
47
|
+
__classPrivateFieldSet(this, _OpenIdConfigManager_openIdConfig, new client.Configuration(__classPrivateFieldGet(this, _OpenIdConfigManager_openIdConfig, "f").serverMetadata(), clientId, clientMetadata, clientAuth), "f");
|
|
48
|
+
}
|
|
49
|
+
else if (__classPrivateFieldGet(this, _OpenIdConfigManager_serverMetadata, "f")) {
|
|
50
|
+
console.log('Using OpenId Config with provided server metadata');
|
|
51
|
+
__classPrivateFieldSet(this, _OpenIdConfigManager_openIdConfig, new client.Configuration(__classPrivateFieldGet(this, _OpenIdConfigManager_serverMetadata, "f"), clientId, clientMetadata, clientAuth), "f");
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.log('Fetching OpenId config');
|
|
55
|
+
__classPrivateFieldSet(this, _OpenIdConfigManager_openIdConfig, await client.discovery(new URL(__classPrivateFieldGet(this, _OpenIdConfigManager_bffConfig, "f").issuer), clientId, clientMetadata, clientAuth), "f");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
get openIdConfig() {
|
|
59
|
+
return __classPrivateFieldGet(this, _OpenIdConfigManager_openIdConfig, "f");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
_OpenIdConfigManager_bffConfig = new WeakMap(), _OpenIdConfigManager_openIdConfig = new WeakMap(), _OpenIdConfigManager_serverMetadata = new WeakMap(), _OpenIdConfigManager_instances = new WeakSet(), _OpenIdConfigManager_p12ToJwk = async function _OpenIdConfigManager_p12ToJwk(okdataP12) {
|
|
63
|
+
//TODO: dette er helt sikkert mulig å gjøre i færre steg...
|
|
64
|
+
const p12Der = forge.util.decode64(okdataP12.keystore);
|
|
65
|
+
const p12Asn1 = forge.asn1.fromDer(p12Der);
|
|
66
|
+
const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, okdataP12.key_password);
|
|
67
|
+
const privateKey = p12.getBags({ friendlyName: okdataP12.key_alias }).friendlyName[0].key;
|
|
68
|
+
const privateKeyAsn1 = forge.pki.privateKeyToAsn1(privateKey);
|
|
69
|
+
const privateKeyInfo = forge.pki.wrapRsaPrivateKey(privateKeyAsn1);
|
|
70
|
+
const pem = forge.pki.privateKeyInfoToPem(privateKeyInfo);
|
|
71
|
+
const key = await jose.importPKCS8(pem, 'RS256', { extractable: true });
|
|
72
|
+
return { key: key, kid: okdataP12.key_id };
|
|
73
|
+
}, _OpenIdConfigManager_createKeyFromOkData = async function _OpenIdConfigManager_createKeyFromOkData(ssmName) {
|
|
74
|
+
const keyString = await getSsmParameter(ssmName);
|
|
75
|
+
const okdataKey = JSON.parse(keyString);
|
|
76
|
+
return await __classPrivateFieldGet(this, _OpenIdConfigManager_instances, "m", _OpenIdConfigManager_p12ToJwk).call(this, okdataKey);
|
|
77
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { HelmetOptions } from "helmet";
|
|
2
|
+
import session from "express-session";
|
|
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
|
+
*/
|
|
14
|
+
basePath?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The root path of the static resources to be served
|
|
17
|
+
*
|
|
18
|
+
* Default: `/dist`
|
|
19
|
+
*/
|
|
20
|
+
staticRootPath?: string;
|
|
21
|
+
/**
|
|
22
|
+
* The issuer
|
|
23
|
+
*
|
|
24
|
+
* Example: `https://test.idporten.no/`
|
|
25
|
+
*/
|
|
26
|
+
issuer: string;
|
|
27
|
+
/**
|
|
28
|
+
* The ID of the client
|
|
29
|
+
*/
|
|
30
|
+
clientId: string;
|
|
31
|
+
/**
|
|
32
|
+
* The client secret. Not used if `okDataIdPortenKeyName` is set.
|
|
33
|
+
*/
|
|
34
|
+
clientSecret?: string;
|
|
35
|
+
/**
|
|
36
|
+
* The redirect uri configured for the client
|
|
37
|
+
*/
|
|
38
|
+
redirectUri: string;
|
|
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
|
+
*/
|
|
55
|
+
cookiePath?: string;
|
|
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
|
+
*/
|
|
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
|
+
*/
|
|
77
|
+
okDataIdPortenKeyName: string;
|
|
78
|
+
/**
|
|
79
|
+
* Secret used to sign sessions
|
|
80
|
+
*/
|
|
81
|
+
sessionSecret: string;
|
|
82
|
+
/**
|
|
83
|
+
* The type of session store used. Only `memory` and `dynamodb` currently supported. `memory` is only for dev use
|
|
84
|
+
*/
|
|
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
|
+
*/
|
|
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
|
+
*/
|
|
97
|
+
proxyTargets: {
|
|
98
|
+
[path: string]: string;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* List of claims in the access 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>;
|
|
124
|
+
};
|
|
125
|
+
export declare function getEnv(env: string, defaultVal?: string, parseFn?: (val: string) => string): string;
|
|
126
|
+
export declare function getSsmParameter(name: string, withDecryption?: boolean): Promise<string>;
|
|
127
|
+
export declare function loadConfig(configFile?: string): Promise<BffConfig>;
|
|
128
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { findUp } from 'find-up';
|
|
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
|
+
};
|
|
10
|
+
export function getEnv(env, defaultVal, parseFn) {
|
|
11
|
+
if (process.env[env]) {
|
|
12
|
+
return parseFn ? parseFn(process.env[env]) : process.env[env];
|
|
13
|
+
}
|
|
14
|
+
else if (defaultVal !== undefined) {
|
|
15
|
+
return defaultVal;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
throw Error(`Missing env var: ${env}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
let ssmClient;
|
|
22
|
+
export async function getSsmParameter(name, withDecryption = true) {
|
|
23
|
+
ssmClient ?? (ssmClient = new SSMClient({}));
|
|
24
|
+
return ssmClient.send(new GetParameterCommand({
|
|
25
|
+
Name: name,
|
|
26
|
+
WithDecryption: withDecryption
|
|
27
|
+
})).then(p => p.Parameter.Value);
|
|
28
|
+
}
|
|
29
|
+
let config;
|
|
30
|
+
export async function loadConfig(configFile = 'bff.config.json') {
|
|
31
|
+
if (config)
|
|
32
|
+
return config;
|
|
33
|
+
const userConfigPath = await findUp(configFile);
|
|
34
|
+
if (!userConfigPath) {
|
|
35
|
+
throw Error(`Could not find config file ${configFile}`);
|
|
36
|
+
}
|
|
37
|
+
console.log('Loading config at', userConfigPath);
|
|
38
|
+
const { default: loadedConfig } = await import(userConfigPath, { with: { type: 'json' } });
|
|
39
|
+
for (const [key, value] of Object.entries(loadedConfig)) {
|
|
40
|
+
if (typeof value === "string") {
|
|
41
|
+
const [, varType, varName] = value.match(/\{(\w+):(.*)}/) ?? [];
|
|
42
|
+
if (varType === 'env') {
|
|
43
|
+
loadedConfig[key] = getEnv(varName);
|
|
44
|
+
}
|
|
45
|
+
else if (varType === 'ssm') {
|
|
46
|
+
loadedConfig[key] = await getSsmParameter(varName);
|
|
47
|
+
}
|
|
48
|
+
else if (varType) {
|
|
49
|
+
throw Error(`unknown varType: ${varType}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
config = { ...defaultConfig, ...loadedConfig };
|
|
54
|
+
return config;
|
|
55
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { OpenIdConfigManager } from "../OpenIdConfigManager.js";
|
|
2
|
+
import { BffConfig } from "../config.js";
|
|
3
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
4
|
+
export declare class OidcMiddleware {
|
|
5
|
+
#private;
|
|
6
|
+
/**
|
|
7
|
+
* @private
|
|
8
|
+
* @param config
|
|
9
|
+
* @param configManager
|
|
10
|
+
*/
|
|
11
|
+
constructor(config: BffConfig, configManager: OpenIdConfigManager);
|
|
12
|
+
static create(config: BffConfig): Promise<OidcMiddleware>;
|
|
13
|
+
get ensureFreshToken(): (req: Request, res: Response, next: NextFunction) => void;
|
|
14
|
+
get login(): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
15
|
+
get callback(): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
16
|
+
get user(): (req: Request, res: Response, next: NextFunction) => Promise<Response<any, Record<string, any>>>;
|
|
17
|
+
get logout(): (req: Request, res: Response) => void;
|
|
18
|
+
get frontChannelLogout(): (req: Request, res: Response) => Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=OidcMiddleware.d.ts.map
|
|
@@ -0,0 +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,iDAc9D;IAED,IAAI,MAAM,KACA,KAAK,OAAO,EAAE,KAAK,QAAQ,UASpC;IAED,IAAI,kBAAkB,KACN,KAAK,OAAO,EAAE,KAAK,QAAQ,mBAc1C;CACF"}
|