@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.
Files changed (121) hide show
  1. package/README.md +240 -0
  2. package/dist/package.json +58 -0
  3. package/dist/src/OpenIdConfigManager.d.ts +10 -0
  4. package/dist/src/OpenIdConfigManager.d.ts.map +1 -0
  5. package/dist/src/OpenIdConfigManager.js +77 -0
  6. package/dist/src/config.d.ts +128 -0
  7. package/dist/src/config.d.ts.map +1 -0
  8. package/dist/src/config.js +55 -0
  9. package/dist/src/middleware/OidcMiddleware.d.ts +20 -0
  10. package/dist/src/middleware/OidcMiddleware.d.ts.map +1 -0
  11. package/dist/src/middleware/OidcMiddleware.js +232 -0
  12. package/dist/src/middleware/oidc-routes.d.mts.map +1 -0
  13. package/dist/src/middleware/oidc-routes.d.ts +3 -0
  14. package/dist/src/middleware/oidc-routes.d.ts.map +1 -0
  15. package/dist/src/middleware/oidc-routes.js +10 -0
  16. package/dist/src/middleware/proxy-routes.d.mts.map +1 -0
  17. package/dist/src/middleware/proxy-routes.d.ts +4 -0
  18. package/dist/src/middleware/proxy-routes.d.ts.map +1 -0
  19. package/dist/src/middleware/proxy-routes.js +28 -0
  20. package/dist/{middleware → src/middleware}/proxy-routes.mjs +5 -5
  21. package/dist/src/middleware/security-headers.d.mts.map +1 -0
  22. package/dist/src/middleware/security-headers.d.ts +4 -0
  23. package/dist/src/middleware/security-headers.d.ts.map +1 -0
  24. package/dist/src/middleware/security-headers.js +31 -0
  25. package/dist/{middleware → src/middleware}/security-headers.mjs +2 -2
  26. package/dist/src/middleware/sessions/dynamoDbSessionStore.d.mts +3 -0
  27. package/dist/src/middleware/sessions/dynamoDbSessionStore.d.mts.map +1 -0
  28. package/dist/src/middleware/sessions/dynamoDbSessionStore.d.ts +3 -0
  29. package/dist/src/middleware/sessions/dynamoDbSessionStore.d.ts.map +1 -0
  30. package/dist/src/middleware/sessions/dynamoDbSessionStore.js +41 -0
  31. package/dist/{middleware → src/middleware}/sessions/dynamoDbSessionStore.mjs +12 -17
  32. package/dist/src/middleware/sessions/memorySessionStore.d.mts +3 -0
  33. package/dist/src/middleware/sessions/memorySessionStore.d.mts.map +1 -0
  34. package/dist/src/middleware/sessions/memorySessionStore.d.ts +3 -0
  35. package/dist/src/middleware/sessions/memorySessionStore.d.ts.map +1 -0
  36. package/dist/src/middleware/sessions/memorySessionStore.js +11 -0
  37. package/dist/src/middleware/sessions/sessions.d.mts +2 -0
  38. package/dist/src/middleware/sessions/sessions.d.mts.map +1 -0
  39. package/dist/src/middleware/sessions/sessions.d.ts +3 -0
  40. package/dist/src/middleware/sessions/sessions.d.ts.map +1 -0
  41. package/dist/src/middleware/sessions/sessions.js +39 -0
  42. package/dist/{middleware → src/middleware}/sessions/sessions.mjs +3 -4
  43. package/dist/src/middleware/static-routes.d.mts.map +1 -0
  44. package/dist/src/middleware/static-routes.d.ts +3 -0
  45. package/dist/src/middleware/static-routes.d.ts.map +1 -0
  46. package/dist/src/middleware/static-routes.js +19 -0
  47. package/dist/src/react/AuthContext.d.ts.map +1 -0
  48. package/dist/src/react/AuthContextProvider.d.ts.map +1 -0
  49. package/dist/{react → src/react}/AuthContextProvider.jsx +15 -27
  50. package/dist/src/react/UseAuthContext.d.ts +2 -0
  51. package/dist/src/react/UseAuthContext.d.ts.map +1 -0
  52. package/dist/{react → src/react}/UseAuthContext.jsx +2 -2
  53. package/dist/src/react/global-user.d.ts.map +1 -0
  54. package/dist/src/react/index.d.ts +5 -0
  55. package/dist/src/react/index.d.ts.map +1 -0
  56. package/dist/src/react/index.js +4 -0
  57. package/dist/src/react/poller.d.ts.map +1 -0
  58. package/dist/src/react/poller.js +28 -0
  59. package/dist/{server.d.mts.map → src/server.d.mts.map} +1 -1
  60. package/dist/src/server.d.ts +3 -0
  61. package/dist/src/server.d.ts.map +1 -0
  62. package/dist/src/server.js +43 -0
  63. package/dist/{server.mjs → src/server.mjs} +2 -2
  64. package/dist/src/utils.d.ts +2 -0
  65. package/dist/src/utils.d.ts.map +1 -0
  66. package/dist/src/utils.js +3 -0
  67. package/dist/src/vite-plugin.d.mts.map +1 -0
  68. package/dist/src/vite-plugin.d.ts +10 -0
  69. package/dist/src/vite-plugin.d.ts.map +1 -0
  70. package/dist/src/vite-plugin.js +26 -0
  71. package/dist/src/vite-plugin.mjs +35 -0
  72. package/package.json +24 -16
  73. package/dist/client.d.mts +0 -7
  74. package/dist/client.d.mts.map +0 -1
  75. package/dist/client.mjs +0 -92
  76. package/dist/config.d.mts +0 -4
  77. package/dist/config.d.mts.map +0 -1
  78. package/dist/config.mjs +0 -69
  79. package/dist/middleware/oidc-routes.d.mts.map +0 -1
  80. package/dist/middleware/oidc.d.mts +0 -17
  81. package/dist/middleware/oidc.d.mts.map +0 -1
  82. package/dist/middleware/oidc.mjs +0 -220
  83. package/dist/middleware/proxy-routes.d.mts.map +0 -1
  84. package/dist/middleware/security-headers.d.mts.map +0 -1
  85. package/dist/middleware/sessions/dynamoDbSessionStore.d.mts +0 -2
  86. package/dist/middleware/sessions/dynamoDbSessionStore.d.mts.map +0 -1
  87. package/dist/middleware/sessions/memorySessionStore.d.mts +0 -2
  88. package/dist/middleware/sessions/memorySessionStore.d.mts.map +0 -1
  89. package/dist/middleware/sessions/sessions.d.mts +0 -2
  90. package/dist/middleware/sessions/sessions.d.mts.map +0 -1
  91. package/dist/middleware/static-routes.d.mts.map +0 -1
  92. package/dist/react/AuthContext.d.ts.map +0 -1
  93. package/dist/react/AuthContextProvider.d.ts.map +0 -1
  94. package/dist/react/UseAuthContext.d.ts +0 -2
  95. package/dist/react/UseAuthContext.d.ts.map +0 -1
  96. package/dist/react/global-user.d.ts.map +0 -1
  97. package/dist/react/index.d.ts +0 -5
  98. package/dist/react/index.d.ts.map +0 -1
  99. package/dist/react/index.js +0 -4
  100. package/dist/react/poller.d.ts.map +0 -1
  101. package/dist/react/poller.js +0 -39
  102. package/dist/utils.d.ts +0 -2
  103. package/dist/utils.d.ts.map +0 -1
  104. package/dist/utils.js +0 -3
  105. package/dist/vite-plugin.d.mts.map +0 -1
  106. package/dist/vite-plugin.mjs +0 -44
  107. /package/dist/{middleware → src/middleware}/oidc-routes.d.mts +0 -0
  108. /package/dist/{middleware → src/middleware}/oidc-routes.mjs +0 -0
  109. /package/dist/{middleware → src/middleware}/proxy-routes.d.mts +0 -0
  110. /package/dist/{middleware → src/middleware}/security-headers.d.mts +0 -0
  111. /package/dist/{middleware → src/middleware}/sessions/memorySessionStore.mjs +0 -0
  112. /package/dist/{middleware → src/middleware}/static-routes.d.mts +0 -0
  113. /package/dist/{middleware → src/middleware}/static-routes.mjs +0 -0
  114. /package/dist/{react → src/react}/AuthContext.d.ts +0 -0
  115. /package/dist/{react → src/react}/AuthContext.jsx +0 -0
  116. /package/dist/{react → src/react}/AuthContextProvider.d.ts +0 -0
  117. /package/dist/{react → src/react}/global-user.d.ts +0 -0
  118. /package/dist/{react → src/react}/global-user.js +0 -0
  119. /package/dist/{react → src/react}/poller.d.ts +0 -0
  120. /package/dist/{server.d.mts → src/server.d.mts} +0 -0
  121. /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"}