@oslokommune/auth-bff 1.0.0-beta6
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/package.json +57 -0
- package/src/client.mjs +69 -0
- package/src/config-utils.mjs +0 -0
- package/src/config.mjs +49 -0
- package/src/middleware/oidc-routes.mjs +13 -0
- package/src/middleware/oidc.mjs +89 -0
- package/src/middleware/proxy-routes.mjs +27 -0
- package/src/middleware/security-headers.mjs +37 -0
- package/src/middleware/sessions.mjs +34 -0
- package/src/middleware/static-routes.mjs +22 -0
- package/src/react/AuthContext.tsx +8 -0
- package/src/react/AuthContextProvider.tsx +39 -0
- package/src/react/UseAuthContext.tsx +11 -0
- package/src/react/index.ts +3 -0
- package/src/server.mjs +36 -0
- package/src/vite-plugin.mjs +32 -0
- package/types/client.d.mts +18 -0
- package/types/client.d.mts.map +1 -0
- package/types/config-utils.d.mts +2 -0
- package/types/config-utils.d.mts.map +1 -0
- package/types/config.d.mts +4 -0
- package/types/config.d.mts.map +1 -0
- package/types/middleware/oidc-routes.d.mts +2 -0
- package/types/middleware/oidc-routes.d.mts.map +1 -0
- package/types/middleware/oidc.d.mts +6 -0
- package/types/middleware/oidc.d.mts.map +1 -0
- package/types/middleware/proxy-routes.d.mts +2 -0
- package/types/middleware/proxy-routes.d.mts.map +1 -0
- package/types/middleware/sessions.d.mts +2 -0
- package/types/middleware/sessions.d.mts.map +1 -0
- package/types/vite-plugin.d.mts +20 -0
- package/types/vite-plugin.d.mts.map +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oslokommune/auth-bff",
|
|
3
|
+
"version": "1.0.0-beta6",
|
|
4
|
+
"repository": "https://github.com/oslokommune/auth-bff.git",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"main": "src/server.mjs",
|
|
9
|
+
"types": "types/index.d.mts",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build-types": "tsc",
|
|
12
|
+
"run": "node ./src/server.mjs",
|
|
13
|
+
"compile-and-publish": "tsc && npm publish"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
"./vite-plugin": {
|
|
17
|
+
"types": "./types/vite-plugin.d.mts",
|
|
18
|
+
"default": "./src/vite-plugin.mjs"
|
|
19
|
+
},
|
|
20
|
+
"./config": {
|
|
21
|
+
"types": "./types/config-utils.d.mts",
|
|
22
|
+
"default": "./src/config-utils.mjs"
|
|
23
|
+
},
|
|
24
|
+
"./react": {
|
|
25
|
+
"default": "./src/react/index.ts"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"okauthbff": "src/server.mjs"
|
|
30
|
+
},
|
|
31
|
+
"files": ["/src", "/types"],
|
|
32
|
+
"type": "module",
|
|
33
|
+
"author": "",
|
|
34
|
+
"license": "ISC",
|
|
35
|
+
"description": "",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/express": "^4.17.21",
|
|
38
|
+
"react": "17.0.2",
|
|
39
|
+
"typescript": "^5.8.3"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@aws-sdk/client-dynamodb": "^3.535.0",
|
|
43
|
+
"@aws-sdk/client-ssm": "^3.535.0",
|
|
44
|
+
"compression": "^1.7.4",
|
|
45
|
+
"connect-dynamodb": "^3.0.5",
|
|
46
|
+
"dotenv": "^16.4.5",
|
|
47
|
+
"express": "^4.19.2",
|
|
48
|
+
"express-session": "^1.18.0",
|
|
49
|
+
"findup-sync": "^5.0.0",
|
|
50
|
+
"helmet": "^7.1.0",
|
|
51
|
+
"http-proxy-middleware": "^3.0.5",
|
|
52
|
+
"jose": "^6.0.11",
|
|
53
|
+
"node-forge": "^1.3.1",
|
|
54
|
+
"openid-client": "^5.6.5",
|
|
55
|
+
"string-replace-middleware": "^1.1.0"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/client.mjs
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import forge from "node-forge";
|
|
2
|
+
import * as jose from 'jose'
|
|
3
|
+
import {Issuer} from "openid-client";
|
|
4
|
+
import {config} from "./config.mjs";
|
|
5
|
+
import {getSsmParameter} from "./config.mjs";
|
|
6
|
+
const issuer = await Issuer.discover(config.oidcDiscoveryUri)
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Takes the JSON object `okdata` produces and converts it to a JWKS
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} okdataP12 - The keys.json created by okdata
|
|
12
|
+
* @param {string} okdataP12.keystore - Base64 encoded p12 keystore
|
|
13
|
+
* @param {string} okdataP12.key_password - P12 password
|
|
14
|
+
* @param {string} okdataP12.key_alias - alias of key to extract
|
|
15
|
+
* @param {string} okdataP12.key_id - id of key to extract
|
|
16
|
+
* @returns {Promise<*>}
|
|
17
|
+
*/
|
|
18
|
+
export async function p12ToJwks(okdataP12) {
|
|
19
|
+
const p12Der = forge.util.decode64(okdataP12.keystore)
|
|
20
|
+
const p12Asn1 = forge.asn1.fromDer(p12Der)
|
|
21
|
+
const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, okdataP12.key_password);
|
|
22
|
+
const privateKey = p12.getBagsByFriendlyName(okdataP12.key_alias)[0].key
|
|
23
|
+
const privateKeyAsn1 = forge.pki.privateKeyToAsn1(privateKey)
|
|
24
|
+
const privateKeyInfo = forge.pki.wrapRsaPrivateKey(privateKeyAsn1)
|
|
25
|
+
const pem = forge.pki.privateKeyInfoToPem(privateKeyInfo);
|
|
26
|
+
const k = await jose.importPKCS8(pem, 'RS256', {extractable: true})
|
|
27
|
+
const jwk = await jose.exportJWK(k)
|
|
28
|
+
jwk.kid = okdataP12.key_id
|
|
29
|
+
jwk.use = 'sig'
|
|
30
|
+
jwk.alg = 'RS256'
|
|
31
|
+
return {
|
|
32
|
+
keys: [jwk]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function createKeyStoreFromOkData(ssmName) {
|
|
37
|
+
const keyString = await getSsmParameter(ssmName)
|
|
38
|
+
const okdataP12 = JSON.parse(keyString)
|
|
39
|
+
return await p12ToJwks(okdataP12)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function createClient() {
|
|
43
|
+
let keyStore
|
|
44
|
+
if(config.okDataIdPortenKeyName) {
|
|
45
|
+
keyStore = await createKeyStoreFromOkData(config.okDataIdPortenKeyName)
|
|
46
|
+
} else if(config.keyStore) {
|
|
47
|
+
keyStore = config.keyStore
|
|
48
|
+
}
|
|
49
|
+
return new issuer.Client(
|
|
50
|
+
{
|
|
51
|
+
client_id: config.clientId,
|
|
52
|
+
client_secret: config.clientSecret,
|
|
53
|
+
redirect_uris: [config.redirectUri],
|
|
54
|
+
response_types: ["code"],
|
|
55
|
+
token_endpoint_auth_method: config.clientSecret ? "client_secret_post" : "private_key_jwt",
|
|
56
|
+
token_endpoint_auth_signing_alg: "RS256",
|
|
57
|
+
post_logout_redirect_uris: config.postLogoutRedirectUris
|
|
58
|
+
},
|
|
59
|
+
keyStore
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export let client = await createClient()
|
|
64
|
+
|
|
65
|
+
if(config.okDataIdPortenKeyName) {
|
|
66
|
+
setInterval(async () => {
|
|
67
|
+
client = await createClient()
|
|
68
|
+
}, 5 * 60 * 1000)
|
|
69
|
+
}
|
|
File without changes
|
package/src/config.mjs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import findup from 'findup-sync'
|
|
2
|
+
import {GetParameterCommand, SSMClient} from "@aws-sdk/client-ssm";
|
|
3
|
+
|
|
4
|
+
export function getEnv(env, defaultVal, parseFn) {
|
|
5
|
+
if (process.env[env]) {
|
|
6
|
+
return parseFn ? parseFn(process.env[env]) : process.env[env]
|
|
7
|
+
} else if (defaultVal !== undefined) {
|
|
8
|
+
return defaultVal
|
|
9
|
+
} else {
|
|
10
|
+
throw Error(`Missing env var: ${env}`)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let ssmClient
|
|
15
|
+
export async function getSsmParameter(name, withDecryption = true) {
|
|
16
|
+
ssmClient ??= new SSMClient({})
|
|
17
|
+
return ssmClient.send(new GetParameterCommand({
|
|
18
|
+
Name: name,
|
|
19
|
+
WithDecryption: withDecryption
|
|
20
|
+
})).then(p => p.Parameter.Value)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const defaultConfig = {
|
|
24
|
+
port: "3000",
|
|
25
|
+
basePath: "",
|
|
26
|
+
cookieSecure: true,
|
|
27
|
+
cookieSameSite: 'lax',
|
|
28
|
+
staticRootPath: './dist'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const userConfigPath = findup('bff.config*.json')
|
|
32
|
+
console.log('BFF: Using config at', userConfigPath)
|
|
33
|
+
const {default: loadedConfig} = await import(userConfigPath, {with: {type: 'json'}});
|
|
34
|
+
|
|
35
|
+
for (const [k, v] of Object.entries(loadedConfig)) {
|
|
36
|
+
if(typeof v === "string") {
|
|
37
|
+
const [, varType, varName] = v.match(/\{(\w+):(.*)}/) ?? []
|
|
38
|
+
if(varType === 'env') {
|
|
39
|
+
loadedConfig[k] = getEnv(varName)
|
|
40
|
+
} else if (varType === 'ssm') {
|
|
41
|
+
loadedConfig[k] = await getSsmParameter(varName)
|
|
42
|
+
} else if(varType) {
|
|
43
|
+
throw Error(`unknown varType: ${varType}`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const config = {...defaultConfig, ...loadedConfig}
|
|
49
|
+
console.log('config!', config)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import {callback, login, logout, user} from "./oidc.mjs";
|
|
3
|
+
|
|
4
|
+
export function oidcRoutes() {
|
|
5
|
+
const router = new express.Router()
|
|
6
|
+
|
|
7
|
+
router.get('/auth/login', login)
|
|
8
|
+
router.get('/auth/callback', callback)
|
|
9
|
+
router.get('/auth/logout', logout)
|
|
10
|
+
router.get('/auth/user', user)
|
|
11
|
+
|
|
12
|
+
return router
|
|
13
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {generators, TokenSet} from "openid-client";
|
|
2
|
+
import {config} from "../config.mjs";
|
|
3
|
+
import {client} from '../client.mjs'
|
|
4
|
+
|
|
5
|
+
async function getFreshTokenSet(req) {
|
|
6
|
+
const tokenSet = req.session.tokenSet && new TokenSet(req.session.tokenSet)
|
|
7
|
+
if (!tokenSet) {
|
|
8
|
+
console.log("No tokenSet found in session")
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
if (tokenSet.expired()) {
|
|
12
|
+
try {
|
|
13
|
+
const refreshedTokenSet = await client.refresh(tokenSet.refresh_token)
|
|
14
|
+
Object.assign(req.session.tokenSet, refreshedTokenSet)
|
|
15
|
+
return new TokenSet(req.session.tokenSet)
|
|
16
|
+
} catch (err) {
|
|
17
|
+
console.log("Token refresh failed", err)
|
|
18
|
+
req.session.tokenSet = null
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
return tokenSet
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function ensureFreshToken(req, _, next) {
|
|
26
|
+
getFreshTokenSet(req).then(tokenSet => {
|
|
27
|
+
req.tokenSet = tokenSet
|
|
28
|
+
next()
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function login(req, res) {
|
|
33
|
+
const codeVerifier = generators.codeVerifier()
|
|
34
|
+
const codeChallenge = generators.codeChallenge(codeVerifier)
|
|
35
|
+
|
|
36
|
+
const authorizationUrl = client.authorizationUrl({
|
|
37
|
+
scope: "openid profile",
|
|
38
|
+
code_challenge: codeChallenge,
|
|
39
|
+
code_challenge_method: "S256",
|
|
40
|
+
resource: config.resources,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
req.session.codeVerifier = codeVerifier
|
|
44
|
+
req.session.save(() => {
|
|
45
|
+
res.redirect(authorizationUrl)
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function callback(req, res) {
|
|
50
|
+
const params = client.callbackParams(req)
|
|
51
|
+
const codeVerifier = req.session.codeVerifier
|
|
52
|
+
const redirectUri = `${req.protocol}://${req.headers.host}${config.basePath}/auth/callback`
|
|
53
|
+
try {
|
|
54
|
+
const tokenSet = await client.callback(redirectUri, params, {
|
|
55
|
+
code_verifier: codeVerifier
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
delete req.session.codeVerifier
|
|
59
|
+
req.session.tokenSet = new TokenSet(tokenSet)
|
|
60
|
+
req.session.save(() => {
|
|
61
|
+
res.redirect(config.basePath || "/") //TODO: skal denne kunne redirecte et annet sted?
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error(err)
|
|
66
|
+
res.status(500).send("Error during callback")
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function user(req, res) {
|
|
71
|
+
const tokenSet = await getFreshTokenSet(req)
|
|
72
|
+
if (!tokenSet) {
|
|
73
|
+
return res.sendStatus(401)
|
|
74
|
+
}
|
|
75
|
+
return res.send(tokenSet.claims())
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function logout(req, res) {
|
|
79
|
+
const tokenSet = req.session.tokenSet && new TokenSet(req.session.tokenSet)
|
|
80
|
+
|
|
81
|
+
//TODO: støtt frontchannel SLO
|
|
82
|
+
|
|
83
|
+
req.session.destroy(() => {
|
|
84
|
+
res.redirect(client.endSessionUrl({
|
|
85
|
+
id_token_hint: tokenSet?.id_token,
|
|
86
|
+
|
|
87
|
+
}))
|
|
88
|
+
})
|
|
89
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import {ensureFreshToken} from "./oidc.mjs";
|
|
3
|
+
import {createProxyMiddleware} from "http-proxy-middleware";
|
|
4
|
+
import {config} from "../config.mjs";
|
|
5
|
+
|
|
6
|
+
export function proxyRoutes() {
|
|
7
|
+
const router = new express.Router()
|
|
8
|
+
for (const [path, target] of Object.entries(config.proxyTargets)) {
|
|
9
|
+
router.use(
|
|
10
|
+
path,
|
|
11
|
+
ensureFreshToken,
|
|
12
|
+
createProxyMiddleware({
|
|
13
|
+
target: target,
|
|
14
|
+
changeOrigin: true,
|
|
15
|
+
onProxyReq: (proxyReq, req, res) => {
|
|
16
|
+
const tokenSet = req.tokenSet
|
|
17
|
+
if (!tokenSet) {
|
|
18
|
+
return res.status(401)
|
|
19
|
+
}
|
|
20
|
+
proxyReq.setHeader("Authorization", `Bearer ${tokenSet.access_token}`)
|
|
21
|
+
proxyReq.removeHeader("Cookie")
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
return router
|
|
27
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import helmet from "helmet";
|
|
3
|
+
|
|
4
|
+
export function securityHeaders() {
|
|
5
|
+
const generateCspNonceMiddleware = (req, res, next) => {
|
|
6
|
+
res.locals.cspNonce = crypto.randomBytes(16).toString("hex");
|
|
7
|
+
next();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const helmetMiddleware = helmet({
|
|
11
|
+
strictTransportSecurity: {
|
|
12
|
+
maxAge: 31536000,
|
|
13
|
+
includeSubDomains: false,
|
|
14
|
+
preload: false,
|
|
15
|
+
},
|
|
16
|
+
contentSecurityPolicy: {
|
|
17
|
+
useDefaults: true,
|
|
18
|
+
directives: {
|
|
19
|
+
"default-src": [
|
|
20
|
+
"'self'",
|
|
21
|
+
"https://*.oslo.kommune.no",
|
|
22
|
+
"https://*.oslo.systems",
|
|
23
|
+
],
|
|
24
|
+
"script-src": ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`],
|
|
25
|
+
"style-src": ["'self'"],
|
|
26
|
+
"frame-ancestors": ["'none'"],
|
|
27
|
+
"img-src": ["'self'"],
|
|
28
|
+
"font-src": ["'self'"],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return [
|
|
34
|
+
generateCspNonceMiddleware,
|
|
35
|
+
helmetMiddleware
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import session from "express-session";
|
|
2
|
+
import {config} from "../config.mjs";
|
|
3
|
+
import dynamoDbStore from "connect-dynamodb";
|
|
4
|
+
|
|
5
|
+
function dynamoDbSessionStore(config = {}) {
|
|
6
|
+
const DynamoDbStore = dynamoDbStore({session})
|
|
7
|
+
return new DynamoDbStore(config)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function sessions() {
|
|
11
|
+
let sessionStore
|
|
12
|
+
if(config.sessionStoreType === 'memory') {
|
|
13
|
+
sessionStore = undefined
|
|
14
|
+
} else if(config.sessionStoreType === 'dynamodb') {
|
|
15
|
+
const sessionStoreConfig = config.sessionStoreConfig ?? {}
|
|
16
|
+
sessionStore = dynamoDbSessionStore(sessionStoreConfig)
|
|
17
|
+
} else if(config.sessionStoreType) {
|
|
18
|
+
throw Error(`unknown sessionStoreType ${config.sessionStoreType}`)
|
|
19
|
+
} else {
|
|
20
|
+
throw Error('missing sessionStoreType')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return session({
|
|
24
|
+
secret: config.sessionSecret,
|
|
25
|
+
store: sessionStore,
|
|
26
|
+
resave: false,
|
|
27
|
+
saveUninitialized: false,
|
|
28
|
+
cookie: {
|
|
29
|
+
httpOnly: true,
|
|
30
|
+
secure: config.cookieSecure,
|
|
31
|
+
sameSite: config.cookieSameSite
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import {stringReplace} from "string-replace-middleware";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import {config} from "../config.mjs";
|
|
5
|
+
|
|
6
|
+
export function staticRoutes() {
|
|
7
|
+
const router = new express.Router()
|
|
8
|
+
|
|
9
|
+
router.use(stringReplace({
|
|
10
|
+
'__CSP_NONCE__': (req, res) => res.locals.cspNonce
|
|
11
|
+
}, {
|
|
12
|
+
contentTypeFilterRegexp: /^text\/html/
|
|
13
|
+
}))
|
|
14
|
+
const staticPath = path.resolve(import.meta.dirname, config.staticRootPath)
|
|
15
|
+
router.use(express.static(staticPath, {index: false}))
|
|
16
|
+
router.get('*', function (req, res) {
|
|
17
|
+
res.set('Cache-Control', 'no-store')
|
|
18
|
+
res.sendFile(path.resolve(import.meta.dirname, config.staticRootPath, 'index.html'))
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
return router
|
|
22
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {React, ReactNode, useEffect, useRef, useState} from "react";
|
|
2
|
+
import {AuthContext, AuthContextProps} from "./AuthContext";
|
|
3
|
+
|
|
4
|
+
type AuthContextProviderProps = {
|
|
5
|
+
children: ReactNode,
|
|
6
|
+
authRequired?: boolean,
|
|
7
|
+
loaderComponent: ReactNode
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function AuthContextProvider({children, authRequired = false, loaderComponent = null}: AuthContextProviderProps) {
|
|
11
|
+
const [user, setUser] = useState<AuthContextProps['user']>(undefined)
|
|
12
|
+
const [state, setState] = useState<AuthContextProps['state']>('pending')
|
|
13
|
+
const userPromise = useRef<Promise<unknown | void> | undefined>(undefined)
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
(userPromise.current ??= fetch('/auth/user').then(res => res.ok && res.json()))
|
|
17
|
+
.then(json => {
|
|
18
|
+
if (json) {
|
|
19
|
+
setUser(json)
|
|
20
|
+
setState('authenticated')
|
|
21
|
+
} else {
|
|
22
|
+
setState('unauthenticated')
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
}, [])
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (authRequired && state === 'unauthenticated') {
|
|
29
|
+
window.location.assign('/auth/login')
|
|
30
|
+
}
|
|
31
|
+
}, [authRequired, state])
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<AuthContext.Provider value={{user, state}}>
|
|
35
|
+
{(authRequired && state !== 'authenticated' || !authRequired && state === 'pending') && loaderComponent}
|
|
36
|
+
{(authRequired && state === 'authenticated' || !authRequired && state !== 'pending') && children}
|
|
37
|
+
</AuthContext.Provider>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {useContext} from "react";
|
|
2
|
+
import {AuthContext} from "./AuthContext";
|
|
3
|
+
|
|
4
|
+
export function useAuthContext(required: boolean = false) {
|
|
5
|
+
const authContext = useContext(AuthContext)
|
|
6
|
+
if (required && authContext?.state === 'unauthenticated') {
|
|
7
|
+
window.location.assign('/auth/login')
|
|
8
|
+
return
|
|
9
|
+
}
|
|
10
|
+
return authContext
|
|
11
|
+
}
|
package/src/server.mjs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import express from "express"
|
|
3
|
+
import compression from "compression"
|
|
4
|
+
import {config} from './config.mjs'
|
|
5
|
+
import {proxyRoutes} from "./middleware/proxy-routes.mjs";
|
|
6
|
+
import {staticRoutes} from "./middleware/static-routes.mjs";
|
|
7
|
+
import {securityHeaders} from "./middleware/security-headers.mjs";
|
|
8
|
+
import {sessions} from "./middleware/sessions.mjs";
|
|
9
|
+
import {oidcRoutes} from "./middleware/oidc-routes.mjs";
|
|
10
|
+
|
|
11
|
+
const app = express()
|
|
12
|
+
|
|
13
|
+
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
|
|
14
|
+
app.disable("x-powered-by")
|
|
15
|
+
|
|
16
|
+
app.use(compression())
|
|
17
|
+
app.use(sessions())
|
|
18
|
+
|
|
19
|
+
if (!config.devMode) {
|
|
20
|
+
app.use(securityHeaders())
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
app.get("/health", (req, res) => {
|
|
24
|
+
res.send("OK")
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const basePath = config.basePath || "/"
|
|
28
|
+
|
|
29
|
+
app.use(basePath, oidcRoutes())
|
|
30
|
+
app.use(basePath, proxyRoutes())
|
|
31
|
+
app.use(basePath, staticRoutes())
|
|
32
|
+
|
|
33
|
+
app.listen(config.port, () => {
|
|
34
|
+
console.log(`Server started on port ${config.port}`)
|
|
35
|
+
})
|
|
36
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import express from "express"
|
|
2
|
+
|
|
3
|
+
async function configureServer({middlewares}) {
|
|
4
|
+
const {oidcRoutes} = await import("./middleware/oidc-routes.mjs")
|
|
5
|
+
const {proxyRoutes} = await import("./middleware/proxy-routes.mjs")
|
|
6
|
+
const {sessions} = await import("./middleware/sessions.mjs")
|
|
7
|
+
const basePath = "" || "/"
|
|
8
|
+
const app = express()
|
|
9
|
+
app.use(sessions())
|
|
10
|
+
app.use(basePath, oidcRoutes())
|
|
11
|
+
app.use(basePath, proxyRoutes())
|
|
12
|
+
|
|
13
|
+
middlewares.use(app)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @returns {{
|
|
19
|
+
* name: string,
|
|
20
|
+
* apply: 'serve',
|
|
21
|
+
* configureServer: ((function({middlewares: *}): Promise<void>)|*),
|
|
22
|
+
* configurePreviewServer: ((function({middlewares: *}): Promise<void>)|*)
|
|
23
|
+
* }}
|
|
24
|
+
*/
|
|
25
|
+
export default function bff() {
|
|
26
|
+
return {
|
|
27
|
+
name: 'bff',
|
|
28
|
+
apply: 'serve',
|
|
29
|
+
configureServer: configureServer,
|
|
30
|
+
configurePreviewServer: configureServer
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Takes the JSON object `okdata` produces and converts it to a JWKS
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} okdataP12 - The keys.json created by okdata
|
|
5
|
+
* @param {string} okdataP12.keystore - Base64 encoded p12 keystore
|
|
6
|
+
* @param {string} okdataP12.key_password - P12 password
|
|
7
|
+
* @param {string} okdataP12.key_alias - alias of key to extract
|
|
8
|
+
* @param {string} okdataP12.key_id - id of key to extract
|
|
9
|
+
* @returns {Promise<*>}
|
|
10
|
+
*/
|
|
11
|
+
export function p12ToJwks(okdataP12: {
|
|
12
|
+
keystore: string;
|
|
13
|
+
key_password: string;
|
|
14
|
+
key_alias: string;
|
|
15
|
+
key_id: string;
|
|
16
|
+
}): Promise<any>;
|
|
17
|
+
export let client: import("openid-client").BaseClient;
|
|
18
|
+
//# sourceMappingURL=client.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.mts","sourceRoot":"","sources":["../src/client.mjs"],"names":[],"mappings":"AAOA;;;;;;;;;GASG;AACH,qCANG;IAA0B,QAAQ,EAA1B,MAAM;IACY,YAAY,EAA9B,MAAM;IACY,SAAS,EAA3B,MAAM;IACY,MAAM,EAAxB,MAAM;CACd,GAAU,OAAO,CAAC,GAAC,CAAC,CAkBtB;AA6BD,sDAAwC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-utils.d.mts","sourceRoot":"","sources":["../src/config-utils.mjs"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.mts","sourceRoot":"","sources":["../src/config.mjs"],"names":[],"mappings":"AAGA,qEAQC;AAGD,mFAMC;AA4BD,yBAAyD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-routes.d.mts","sourceRoot":"","sources":["../../src/middleware/oidc-routes.mjs"],"names":[],"mappings":"AAGA,kCASC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export function ensureFreshToken(req: any, _: any, next: any): void;
|
|
2
|
+
export function login(req: any, res: any): void;
|
|
3
|
+
export function callback(req: any, res: any): Promise<void>;
|
|
4
|
+
export function user(req: any, res: any): Promise<any>;
|
|
5
|
+
export function logout(req: any, res: any): void;
|
|
6
|
+
//# sourceMappingURL=oidc.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc.d.mts","sourceRoot":"","sources":["../../src/middleware/oidc.mjs"],"names":[],"mappings":"AAwBA,oEAKC;AAED,gDAeC;AAED,4DAmBC;AAED,uDAMC;AAED,iDAWC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy-routes.d.mts","sourceRoot":"","sources":["../../src/middleware/proxy-routes.mjs"],"names":[],"mappings":"AAKA,mCAqBC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.mts","sourceRoot":"","sources":["../../src/middleware/sessions.mjs"],"names":[],"mappings":"AASA,gCAwBC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @returns {{
|
|
4
|
+
* name: string,
|
|
5
|
+
* apply: 'serve',
|
|
6
|
+
* configureServer: ((function({middlewares: *}): Promise<void>)|*),
|
|
7
|
+
* configurePreviewServer: ((function({middlewares: *}): Promise<void>)|*)
|
|
8
|
+
* }}
|
|
9
|
+
*/
|
|
10
|
+
export default function bff(): {
|
|
11
|
+
name: string;
|
|
12
|
+
apply: "serve";
|
|
13
|
+
configureServer: (((arg0: {
|
|
14
|
+
middlewares: any;
|
|
15
|
+
}) => Promise<void>) | any);
|
|
16
|
+
configurePreviewServer: (((arg0: {
|
|
17
|
+
middlewares: any;
|
|
18
|
+
}) => Promise<void>) | any);
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=vite-plugin.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-plugin.d.mts","sourceRoot":"","sources":["../src/vite-plugin.mjs"],"names":[],"mappings":"AAeA;;;;;;;;GAQG;AACH,+BAPa;IACT,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,EAAE,CAAC,CAAC,CAAS,IAAgB,EAAhB;QAAC,WAAW,EAAE,GAAC,CAAA;KAAC,KAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAC,GAAC,CAAC,CAAC;IACjE,sBAAsB,EAAE,CAAC,CAAC,CAAS,IAAgB,EAAhB;QAAC,WAAW,EAAE,GAAC,CAAA;KAAC,KAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAC,GAAC,CAAC,CAAA;CACvE,CASH"}
|