Skip to content

Commit 6d906f6

Browse files
committed
feat: add support for AsyncProperties
1 parent 2629a12 commit 6d906f6

10 files changed

Lines changed: 496 additions & 80 deletions

File tree

extensions/idp/entraid/Extension.tsx

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,48 @@
11
import React from "react";
22
import { Cognito } from "@webiny/cognito";
33

4+
async function getCredentials() {
5+
return {
6+
client_id: process.env.ENTRA_CLIENT_ID,
7+
client_secret: process.env.ENTRA_CLIENT_SECRET,
8+
oidc_issuer: process.env.ENTRA_OIDC_ISSUER
9+
};
10+
}
11+
412
export const CognitoFederation = () => {
513
return (
614
<Cognito
715
mfa={true}
816
apiConfig={"@/extensions/idp/entraid/EntraIdApiConfig.ts"}
9-
federation={{
10-
domain: "myproj-webiny-with-entraid",
11-
callbackUrls: ["https://webiny-6.4.x.localhost"],
12-
responseType: "code",
13-
allowCredentialsLogin: true,
14-
identityProviders: [
15-
{
16-
name: "EntraID",
17-
type: "oidc",
18-
label: "Sign in with Microsoft",
19-
providerDetails: {
20-
attributes_request_method: "POST",
21-
authorize_scopes: "email profile openid",
22-
client_id: process.env.ENTRA_CLIENT_ID,
23-
client_secret: process.env.ENTRA_CLIENT_SECRET,
24-
oidc_issuer: process.env.ENTRA_OIDC_ISSUER
25-
},
26-
attributeMapping: {
27-
"custom:id": "sub",
28-
username: "sub",
29-
email: "email",
30-
given_name: "given_name",
31-
family_name: "family_name",
32-
preferred_username: "email"
17+
federation={async () => {
18+
const credentials = await getCredentials();
19+
20+
return {
21+
domain: "myproj-webiny-with-entraid",
22+
callbackUrls: ["https://webiny-6.4.x.localhost"],
23+
responseType: "code",
24+
allowCredentialsLogin: true,
25+
identityProviders: [
26+
{
27+
name: "EntraID",
28+
type: "oidc",
29+
label: "Sign in with Microsoft",
30+
providerDetails: {
31+
attributes_request_method: "POST",
32+
authorize_scopes: "email profile openid",
33+
...credentials
34+
},
35+
attributeMapping: {
36+
"custom:id": "sub",
37+
username: "sub",
38+
email: "email",
39+
given_name: "given_name",
40+
family_name: "family_name",
41+
preferred_username: "email"
42+
}
3343
}
34-
}
35-
]
44+
]
45+
};
3646
}}
3747
/>
3848
);

packages/cognito/src/Cognito.tsx

Lines changed: 56 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react";
22
import { defineExtension } from "@webiny/project/defineExtension/index.js";
33
import { Api, Admin, Infra } from "@webiny/project-aws";
4+
import { Await } from "@webiny/react-properties";
45
import { z } from "zod";
56

67
const identityProviderSchema = z.object({
@@ -28,11 +29,23 @@ export const Cognito = defineExtension({
2829
paramsSchema: z.object({
2930
apiConfig: z.string().describe("Path to API configuration.").optional(),
3031
adminConfig: z.string().describe("Path to Admin configuration.").optional(),
31-
federation: federationSchema.optional(),
32+
federation: z
33+
.union([
34+
federationSchema,
35+
z.custom<() => Promise<z.infer<typeof federationSchema>>>(
36+
val => typeof val === "function"
37+
)
38+
])
39+
.optional(),
3240
mfa: z.boolean().describe("Enable TOTP MFA for all users.").default(false)
3341
}),
3442
render: props => {
35-
const federation = props.federation;
43+
const federationProp = props.federation;
44+
const federationFn = federationProp
45+
? typeof federationProp === "function"
46+
? federationProp
47+
: () => Promise.resolve(federationProp)
48+
: null;
3649

3750
return (
3851
<>
@@ -51,43 +64,47 @@ export const Cognito = defineExtension({
5164
{props.adminConfig ? <Admin.Extension src={props.adminConfig} /> : null}
5265

5366
{/* Federation infra + admin config */}
54-
{federation ? (
55-
<>
56-
{/* Pulumi: create User Pool Domain, IdP resources, OAuth client */}
57-
<Infra.EnvVar
58-
varName={"COGNITO_FEDERATION_INFRA_CONFIG"}
59-
value={JSON.stringify({
60-
domain: federation.domain,
61-
callbackUrls: federation.callbackUrls,
62-
logoutUrls: federation.logoutUrls,
63-
identityProviders: federation.identityProviders.map(idp => ({
64-
type: idp.type,
65-
name: idp.name,
66-
providerDetails: idp.providerDetails,
67-
idpIdentifiers: idp.idpIdentifiers,
68-
attributeMapping: idp.attributeMapping
69-
}))
70-
})}
71-
/>
72-
<Infra.Core.Pulumi
73-
src={import.meta.dirname + "/infra/CognitoFederationPulumi.js"}
74-
/>
75-
76-
{/* Admin: pass federation config as build param */}
77-
<Admin.BuildParam
78-
paramName={"cognitoFederation"}
79-
value={{
80-
callbackUrls: federation.callbackUrls,
81-
logoutUrls: federation.logoutUrls || federation.callbackUrls,
82-
responseType: federation.responseType,
83-
allowCredentialsLogin: federation.allowCredentialsLogin,
84-
providers: federation.identityProviders.map(idp => ({
85-
name: idp.name || idp.type,
86-
label: idp.label
87-
}))
88-
}}
89-
/>
90-
</>
67+
{federationFn ? (
68+
<Await fn={federationFn}>
69+
{federation => (
70+
<>
71+
<Infra.EnvVar
72+
varName={"COGNITO_FEDERATION_INFRA_CONFIG"}
73+
value={JSON.stringify({
74+
domain: federation.domain,
75+
callbackUrls: federation.callbackUrls,
76+
logoutUrls: federation.logoutUrls,
77+
identityProviders: federation.identityProviders.map(
78+
idp => ({
79+
type: idp.type,
80+
name: idp.name,
81+
providerDetails: idp.providerDetails,
82+
idpIdentifiers: idp.idpIdentifiers,
83+
attributeMapping: idp.attributeMapping
84+
})
85+
)
86+
})}
87+
/>
88+
<Infra.Core.Pulumi
89+
src={import.meta.dirname + "/infra/CognitoFederationPulumi.js"}
90+
/>
91+
<Admin.BuildParam
92+
paramName={"cognitoFederation"}
93+
value={{
94+
callbackUrls: federation.callbackUrls,
95+
logoutUrls:
96+
federation.logoutUrls || federation.callbackUrls,
97+
responseType: federation.responseType,
98+
allowCredentialsLogin: federation.allowCredentialsLogin,
99+
providers: federation.identityProviders.map(idp => ({
100+
name: idp.name || idp.type,
101+
label: idp.label
102+
}))
103+
}}
104+
/>
105+
</>
106+
)}
107+
</Await>
91108
) : null}
92109
</>
93110
);

packages/project/src/services/GetProjectConfigService/renderConfigWorker.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import "tsx/esm";
2-
import { Properties, toObject } from "@webiny/react-properties";
2+
import { AsyncProperties, toObject } from "@webiny/react-properties";
33
import debounce from "debounce";
44
import React from "react";
55
import { createRoot } from "react-dom/client";
@@ -58,7 +58,20 @@ const { Extensions } = await import(
5858
toImportSpecifier(project.paths.webinyConfigBaseFile.toString())
5959
);
6060

61+
const RENDER_TIMEOUT_MS = 30_000;
62+
63+
const timeout = setTimeout(() => {
64+
sendError(
65+
new Error(
66+
`Config rendering timed out after ${RENDER_TIMEOUT_MS}ms. ` +
67+
`This usually means an <Await> promise never settled.`
68+
)
69+
);
70+
process.exit(1);
71+
}, RENDER_TIMEOUT_MS);
72+
6173
const onChange = debounce((value: any) => {
74+
clearTimeout(timeout);
6275
sendSuccess(toObject(value));
6376
process.exit(0);
6477
});
@@ -77,9 +90,9 @@ reactRoot.render(
7790
<WcpProjectLicenseProvider>
7891
<EnvProvider>
7992
<ProductionEnvironmentsCollector>
80-
<Properties onChange={onChange}>
93+
<AsyncProperties onChange={onChange}>
8194
<Extensions />
82-
</Properties>
95+
</AsyncProperties>
8396
</ProductionEnvironmentsCollector>
8497
</EnvProvider>
8598
</WcpProjectLicenseProvider>

0 commit comments

Comments
 (0)