Skip to content
This repository was archived by the owner on Nov 20, 2024. It is now read-only.

Commit f82bc94

Browse files
chore(vendor): update @lightbasenl/backend (#95)
_This PR is created by sync and will be force-pushed daily. Overwriting any manual changes done to this PR._ - feat(backend): fix digid xml signature checks (lightbasenl/platform-components@b335a6c) - chore(deps): Bump the production group across 1 directory with 2 updates (lightbasenl/platform-components#1320) (lightbasenl/platform-components@cb23ae0) - feat(backend): add feature-flag and tenant cache (lightbasenl/platform-components@2bd3d58) - chore(deps-dev): Bump the development group across 1 directory with 11 updates (lightbasenl/platform-components#1319) (lightbasenl/platform-components@1e0a999)- Failed to execute `npx compas lint`. Sync is not able to correct this, so human checks and fixes are necessary for this PR. Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 472f7ff commit f82bc94

File tree

10 files changed

+208
-157
lines changed

10 files changed

+208
-157
lines changed

package-lock.json

Lines changed: 10 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/backend/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
],
1414
"scripts": {},
1515
"dependencies": {
16+
"@lightbase/pull-through-cache": "0.1.2",
1617
"@xmldom/xmldom": "0.8.10",
1718
"bcrypt": "5.1.1",
18-
"rate-limiter-flexible": "5.0.0",
19+
"rate-limiter-flexible": "5.0.3",
1920
"speakeasy": "2.0.0",
2021
"xml-crypto": "6.0.0",
2122
"xpath": "0.0.34"
@@ -29,5 +30,5 @@
2930
"url": "https://github.com/lightbasenl/platform-components.git",
3031
"directory": "packages/backend"
3132
},
32-
"gitHead": "10d17891e252e6f8d231a26057f41ea8189ee3ea"
33+
"gitHead": "b335a6c8aa6f5e14489b582b02b6fa45beda00b6"
3334
}

vendor/backend/src/auth/digid-based/assets/digid-dev.pub.pem

Lines changed: 0 additions & 40 deletions
This file was deleted.

vendor/backend/src/auth/digid-based/assets/digid-prod.pub.pem

Lines changed: 0 additions & 39 deletions
This file was deleted.

vendor/backend/src/auth/digid-based/events.js

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,6 @@ const cryptoSignAlgorithm = "RSA-SHA256";
3737
*/
3838
let certificateChain = undefined;
3939

40-
/**
41-
* Public key used to verify payload signatures that we got from DigiD
42-
*
43-
* @type {string|undefined}
44-
*/
45-
let digidPublicKey = undefined;
46-
4740
/**
4841
* @typedef {object} AuthDigidBasedRegisterBody
4942
* @property {string} bsn
@@ -552,7 +545,9 @@ async function authDigidBasedVerifySignaturesForXmlPayload(event, payload) {
552545

553546
for (const sig of signatures) {
554547
const xmlSigner = new xmlCrypto.SignedXml({
555-
publicCert: getDigidPublicKey(),
548+
getCertFromKeyInfo: xmlCrypto.SignedXml.getCertFromKeyInfo,
549+
canonicalizationAlgorithm: "http://www.w3.org/2001/10/xml-exc-c14n#",
550+
signatureAlgorithm,
556551
});
557552

558553
xmlSigner.loadSignature(sig);
@@ -670,12 +665,22 @@ function authDigidBasedVerifyArtifactStatus(
670665
subStatus,
671666
subSubStatus,
672667
) {
668+
const errInfo = AppError.serverError({
669+
mainStatus,
670+
subStatus,
671+
subSubStatus,
672+
});
673+
673674
if (mainStatus.indexOf("urn:oasis:names:tc:SAML:2.0:status:Success") !== -1) {
674675
if (
675676
subSubStatus.indexOf("urn:oasis:names:tc:SAML:2.0:status:AuthnFailed") !==
676677
-1
677678
) {
678-
throw new AppError("authDigidBased.resolveArtifact.aborted", 401);
679+
throw new AppError(
680+
"authDigidBased.resolveArtifact.aborted",
681+
401,
682+
errInfo,
683+
);
679684
}
680685
return;
681686
}
@@ -690,7 +695,7 @@ function authDigidBasedVerifyArtifactStatus(
690695
if (
691696
subStatus.indexOf("urn:oasis:names:tc:SAML:2.0:status:AuthnFailed") !== -1
692697
) {
693-
throw new AppError("authDigidBased.resolveArtifact.aborted", 401);
698+
throw new AppError("authDigidBased.resolveArtifact.aborted", 401, errInfo);
694699
}
695700

696701
if (
@@ -700,19 +705,27 @@ function authDigidBasedVerifyArtifactStatus(
700705
throw new AppError(
701706
"authDigidBased.resolveArtifact.insufficientSecurityLevel",
702707
401,
708+
errInfo,
703709
);
704710
}
705711

706712
if (
707713
subStatus.indexOf("urn:oasis:names:tc:SAML:2.0:status:RequestDenied") !== -1
708714
) {
709-
throw new AppError("authDigidBased.resolveArtifact.invalidSAMLArt", 401);
715+
throw new AppError(
716+
"authDigidBased.resolveArtifact.invalidSAMLArt",
717+
401,
718+
errInfo,
719+
);
710720
}
711721

712-
throw AppError.serverError({
713-
message: "Unknown DigiD SAML error when resolving artifact",
714-
ourError,
715-
});
722+
throw AppError.serverError(
723+
{
724+
message: "Unknown DigiD SAML error when resolving artifact",
725+
ourError,
726+
},
727+
errInfo,
728+
);
716729
}
717730

718731
/**
@@ -755,26 +768,3 @@ function getCertificateChain() {
755768

756769
return certificateChain;
757770
}
758-
759-
/**
760-
* Sync read operation, since it only happens once.
761-
*
762-
* @returns {string}
763-
*/
764-
function getDigidPublicKey() {
765-
if (isNil(digidPublicKey)) {
766-
if (isStaging()) {
767-
digidPublicKey = readFileSync(
768-
pathJoin(dirnameForModule(import.meta), "assets/digid-dev.pub.pem"),
769-
"utf-8",
770-
);
771-
} else {
772-
digidPublicKey = readFileSync(
773-
pathJoin(dirnameForModule(import.meta), "assets/digid-prod.pub.pem"),
774-
"utf-8",
775-
);
776-
}
777-
}
778-
779-
return digidPublicKey;
780-
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { AppError } from "@compas/stdlib";
2+
import { PullThroughCache } from "@lightbase/pull-through-cache";
3+
import { queryFeatureFlag, sql } from "../services.js";
4+
5+
/**
6+
* Short TTL feature flag cache. Keeps all flags for 5 seconds in memory,
7+
*
8+
* @type {PullThroughCache<FeatureFlagIdentifier, BackendFeatureFlag>}
9+
*/
10+
export const featureFlagCache = new PullThroughCache()
11+
.withTTL({
12+
// Note the value is in milliseconds.
13+
// We don't want to expand this value too much. This could enable
14+
// race-conditions between the TTL in different instances.
15+
ttl: 5 * 1000,
16+
})
17+
.withFetcher({
18+
fetcher: featureFlagFetcher,
19+
});
20+
21+
/**
22+
*
23+
* @param {PullThroughCache<FeatureFlagIdentifier, BackendFeatureFlag>} _cache
24+
* @param {FeatureFlagIdentifier} key
25+
* @returns {Promise<BackendFeatureFlag>}
26+
*/
27+
async function featureFlagFetcher(_cache, key) {
28+
const flags = await queryFeatureFlag({}).exec(sql);
29+
_cache.setMany(flags.map((it) => [it.name, it]));
30+
31+
const foundValue = flags.find((it) => it.name === key);
32+
if (!foundValue) {
33+
throw AppError.serverError({
34+
message: "Received a feature flag identifier that doesn't exist.",
35+
identifier: key,
36+
});
37+
}
38+
39+
return foundValue;
40+
}

vendor/backend/src/feature-flag/events.js

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AppError, eventStart, eventStop, isNil } from "@compas/stdlib";
22
import { featureFlags, queries, queryFeatureFlag, sql } from "../services.js";
3+
import { featureFlagCache } from "./cache.js";
34

45
/**
56
* Get current feature flags.
@@ -11,7 +12,19 @@ import { featureFlags, queries, queryFeatureFlag, sql } from "../services.js";
1112
export async function featureFlagCurrent(event, tenant) {
1213
eventStart(event, "featureFlag.current");
1314

14-
const flags = await queryFeatureFlag({}).exec(sql);
15+
let flags = featureFlagCache.getAll();
16+
if (!featureFlagCache.isEnabled()) {
17+
flags = await queryFeatureFlag({}).exec(sql);
18+
}
19+
20+
if (
21+
featureFlagCache.isEnabled() &&
22+
flags.length !== featureFlags.availableFlags.length
23+
) {
24+
// The cache is empty / everything expired, fetch a single key to prime the cache.
25+
await featureFlagCache.get(featureFlags.availableFlags[0]);
26+
flags = featureFlagCache.getAll();
27+
}
1528

1629
/**
1730
* @type {FeatureFlagCurrentResponse}
@@ -94,19 +107,7 @@ export async function featureFlagSyncAvailableFlags(event, sql) {
94107
export async function featureFlagGetDynamic(event, tenant, user, identifier) {
95108
eventStart(event, "featureFlag.getDynamic");
96109

97-
const [flag] = await queryFeatureFlag({
98-
where: {
99-
name: identifier,
100-
},
101-
}).exec(sql);
102-
103-
if (isNil(flag) || flag.name !== identifier) {
104-
throw AppError.serverError({
105-
message: "Received a feature flag identifier that doesn't exist.",
106-
identifier,
107-
});
108-
}
109-
110+
const flag = await featureFlagCache.get(identifier);
110111
const tenantSpecificValue = flag?.tenantValues?.[tenant?.tenant?.name];
111112

112113
eventStop(event);
@@ -155,5 +156,11 @@ export async function featureFlagSetDynamic(
155156
},
156157
});
157158

159+
if (featureFlagCache.isEnabled()) {
160+
// Clear the cache if enabled. This method function is often only used in test code, which most likely disables the cache anyways.
161+
featureFlagCache.disable();
162+
featureFlagCache.enable();
163+
}
164+
158165
eventStop(event);
159166
}

vendor/backend/src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export { backendGetTenantAndUser } from "./events.js";
77
export { extendWithManagement } from "./management/structure.js";
88
export { managementInvalidateUsers } from "./management/jobs.js";
99

10+
export { tenantCache } from "./multitenant/cache.js";
1011
export {
1112
multitenantConfigForTenant,
1213
multitenantEnabledTenantNames,
@@ -17,6 +18,7 @@ export {
1718
} from "./multitenant/events.js";
1819

1920
export { extendWithFeatureFlag } from "./feature-flag/structure.js";
21+
export { featureFlagCache } from "./feature-flag/cache.js";
2022
export {
2123
featureFlagGetDynamic,
2224
featureFlagSetDynamic,

0 commit comments

Comments
 (0)