@openhi/constructs 0.0.137 → 0.0.139
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/lib/index.d.mts +51 -1
- package/lib/index.d.ts +51 -1
- package/lib/index.js +921 -824
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +943 -848
- package/lib/index.mjs.map +1 -1
- package/package.json +13 -13
package/lib/index.mjs
CHANGED
|
@@ -736,23 +736,10 @@ import { UserPoolClient } from "aws-cdk-lib/aws-cognito";
|
|
|
736
736
|
var CognitoUserPoolClient = class extends UserPoolClient {
|
|
737
737
|
constructor(scope, props) {
|
|
738
738
|
super(scope, "user-pool-client", {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
739
|
+
// Default: SPA client (no secret). OAuth flow + callback/logout URL
|
|
740
|
+
// composition is the owning service's responsibility — pass via
|
|
741
|
+
// `props.oAuth` (see `OpenHiAuthService.resolveOAuthRedirectUrls`).
|
|
742
742
|
generateSecret: false,
|
|
743
|
-
oAuth: {
|
|
744
|
-
flows: {
|
|
745
|
-
authorizationCodeGrant: true,
|
|
746
|
-
implicitCodeGrant: true
|
|
747
|
-
},
|
|
748
|
-
callbackUrls: [
|
|
749
|
-
`http://localhost:3000/oauth/callback`,
|
|
750
|
-
`https://localhost:3000/oauth/callback`
|
|
751
|
-
]
|
|
752
|
-
},
|
|
753
|
-
/**
|
|
754
|
-
* Overrideable props
|
|
755
|
-
*/
|
|
756
743
|
...props
|
|
757
744
|
});
|
|
758
745
|
}
|
|
@@ -1989,6 +1976,7 @@ var StaticContent = class extends Construct10 {
|
|
|
1989
1976
|
};
|
|
1990
1977
|
|
|
1991
1978
|
// src/services/open-hi-auth-service.ts
|
|
1979
|
+
var import_config7 = __toESM(require_lib2());
|
|
1992
1980
|
import {
|
|
1993
1981
|
LambdaVersion,
|
|
1994
1982
|
UserPool as UserPool2,
|
|
@@ -1996,7 +1984,7 @@ import {
|
|
|
1996
1984
|
UserPoolDomain as UserPoolDomain2,
|
|
1997
1985
|
UserPoolOperation
|
|
1998
1986
|
} from "aws-cdk-lib/aws-cognito";
|
|
1999
|
-
import { Effect as
|
|
1987
|
+
import { Effect as Effect7, PolicyStatement as PolicyStatement7 } from "aws-cdk-lib/aws-iam";
|
|
2000
1988
|
import { Key as Key2 } from "aws-cdk-lib/aws-kms";
|
|
2001
1989
|
import { Stack as Stack7 } from "aws-cdk-lib/core";
|
|
2002
1990
|
|
|
@@ -2578,633 +2566,285 @@ var _OpenHiDataService = class _OpenHiDataService extends OpenHiService {
|
|
|
2578
2566
|
_OpenHiDataService.SERVICE_TYPE = "data";
|
|
2579
2567
|
var OpenHiDataService = _OpenHiDataService;
|
|
2580
2568
|
|
|
2581
|
-
// src/
|
|
2569
|
+
// src/services/open-hi-website-service.ts
|
|
2570
|
+
var import_config6 = __toESM(require_lib2());
|
|
2571
|
+
import { Bucket as Bucket3 } from "aws-cdk-lib/aws-s3";
|
|
2572
|
+
|
|
2573
|
+
// src/services/open-hi-rest-api-service.ts
|
|
2574
|
+
var import_config5 = __toESM(require_lib2());
|
|
2575
|
+
import {
|
|
2576
|
+
CorsHttpMethod,
|
|
2577
|
+
DomainName,
|
|
2578
|
+
HttpApi as HttpApi2,
|
|
2579
|
+
HttpMethod,
|
|
2580
|
+
HttpNoneAuthorizer,
|
|
2581
|
+
HttpRoute,
|
|
2582
|
+
HttpRouteKey
|
|
2583
|
+
} from "aws-cdk-lib/aws-apigatewayv2";
|
|
2584
|
+
import { HttpUserPoolAuthorizer } from "aws-cdk-lib/aws-apigatewayv2-authorizers";
|
|
2585
|
+
import { HttpLambdaIntegration } from "aws-cdk-lib/aws-apigatewayv2-integrations";
|
|
2586
|
+
import { Effect as Effect5, PolicyStatement as PolicyStatement5 } from "aws-cdk-lib/aws-iam";
|
|
2587
|
+
import {
|
|
2588
|
+
ARecord as ARecord3,
|
|
2589
|
+
HostedZone as HostedZone3,
|
|
2590
|
+
RecordTarget as RecordTarget3
|
|
2591
|
+
} from "aws-cdk-lib/aws-route53";
|
|
2592
|
+
import { ApiGatewayv2DomainProperties } from "aws-cdk-lib/aws-route53-targets";
|
|
2593
|
+
import { Duration as Duration9 } from "aws-cdk-lib/core";
|
|
2594
|
+
import { Construct as Construct19 } from "constructs";
|
|
2595
|
+
|
|
2596
|
+
// src/data/lambda/cors-options-lambda.ts
|
|
2582
2597
|
import fs10 from "fs";
|
|
2583
2598
|
import path10 from "path";
|
|
2584
|
-
import { Duration as Duration9 } from "aws-cdk-lib";
|
|
2585
|
-
import { Rule as Rule4 } from "aws-cdk-lib/aws-events";
|
|
2586
|
-
import { LambdaFunction as LambdaFunction4 } from "aws-cdk-lib/aws-events-targets";
|
|
2587
|
-
import { Effect as Effect5, PolicyStatement as PolicyStatement5 } from "aws-cdk-lib/aws-iam";
|
|
2588
2599
|
import { Runtime as Runtime10 } from "aws-cdk-lib/aws-lambda";
|
|
2589
2600
|
import { NodejsFunction as NodejsFunction10 } from "aws-cdk-lib/aws-lambda-nodejs";
|
|
2590
2601
|
import { Construct as Construct17 } from "constructs";
|
|
2591
|
-
var HANDLER_NAME9 = "
|
|
2602
|
+
var HANDLER_NAME9 = "cors-options-lambda.handler.js";
|
|
2592
2603
|
function resolveHandlerEntry9(dirname) {
|
|
2593
2604
|
const sameDir = path10.join(dirname, HANDLER_NAME9);
|
|
2594
2605
|
if (fs10.existsSync(sameDir)) {
|
|
2595
2606
|
return sameDir;
|
|
2596
2607
|
}
|
|
2597
|
-
|
|
2608
|
+
const fromLib = path10.join(dirname, "..", "..", "..", "lib", HANDLER_NAME9);
|
|
2609
|
+
return fromLib;
|
|
2598
2610
|
}
|
|
2599
|
-
var
|
|
2600
|
-
constructor(scope,
|
|
2601
|
-
super(scope,
|
|
2611
|
+
var CorsOptionsLambda = class extends Construct17 {
|
|
2612
|
+
constructor(scope, id = "cors-options-lambda") {
|
|
2613
|
+
super(scope, id);
|
|
2602
2614
|
this.lambda = new NodejsFunction10(this, "handler", {
|
|
2603
2615
|
entry: resolveHandlerEntry9(__dirname),
|
|
2604
2616
|
runtime: Runtime10.NODEJS_LATEST,
|
|
2605
|
-
memorySize:
|
|
2606
|
-
environment: {
|
|
2607
|
-
DYNAMO_TABLE_NAME: props.dataStoreTable.tableName
|
|
2608
|
-
}
|
|
2609
|
-
});
|
|
2610
|
-
props.dataStoreTable.grant(
|
|
2611
|
-
this.lambda,
|
|
2612
|
-
"dynamodb:PutItem",
|
|
2613
|
-
"dynamodb:UpdateItem"
|
|
2614
|
-
);
|
|
2615
|
-
this.lambda.addToRolePolicy(
|
|
2616
|
-
new PolicyStatement5({
|
|
2617
|
-
effect: Effect5.ALLOW,
|
|
2618
|
-
actions: ["dynamodb:Query"],
|
|
2619
|
-
resources: [`${props.dataStoreTable.tableArn}/index/*`]
|
|
2620
|
-
})
|
|
2621
|
-
);
|
|
2622
|
-
this.rule = new Rule4(this, "rule", {
|
|
2623
|
-
eventBus: props.controlEventBus,
|
|
2624
|
-
eventPattern: {
|
|
2625
|
-
source: [USER_ONBOARDING_EVENT_SOURCE],
|
|
2626
|
-
detailType: [PROVISION_DEFAULT_WORKSPACE_DETAIL_TYPE]
|
|
2627
|
-
},
|
|
2628
|
-
targets: [
|
|
2629
|
-
new LambdaFunction4(this.lambda, {
|
|
2630
|
-
retryAttempts: 2,
|
|
2631
|
-
maxEventAge: Duration9.hours(2)
|
|
2632
|
-
})
|
|
2633
|
-
]
|
|
2617
|
+
memorySize: 128
|
|
2634
2618
|
});
|
|
2635
2619
|
}
|
|
2636
2620
|
};
|
|
2637
2621
|
|
|
2638
|
-
// src/
|
|
2622
|
+
// src/data/lambda/rest-api-lambda.ts
|
|
2623
|
+
import fs11 from "fs";
|
|
2624
|
+
import path11 from "path";
|
|
2625
|
+
import { Runtime as Runtime11 } from "aws-cdk-lib/aws-lambda";
|
|
2626
|
+
import { NodejsFunction as NodejsFunction11 } from "aws-cdk-lib/aws-lambda-nodejs";
|
|
2639
2627
|
import { Construct as Construct18 } from "constructs";
|
|
2640
|
-
var
|
|
2628
|
+
var HANDLER_NAME10 = "rest-api-lambda.handler.js";
|
|
2629
|
+
function resolveHandlerEntry10(dirname) {
|
|
2630
|
+
const sameDir = path11.join(dirname, HANDLER_NAME10);
|
|
2631
|
+
if (fs11.existsSync(sameDir)) {
|
|
2632
|
+
return sameDir;
|
|
2633
|
+
}
|
|
2634
|
+
const fromLib = path11.join(dirname, "..", "..", "..", "lib", HANDLER_NAME10);
|
|
2635
|
+
return fromLib;
|
|
2636
|
+
}
|
|
2637
|
+
var RestApiLambda = class extends Construct18 {
|
|
2641
2638
|
constructor(scope, props) {
|
|
2642
|
-
super(scope, "
|
|
2643
|
-
this.
|
|
2644
|
-
|
|
2645
|
-
|
|
2639
|
+
super(scope, "rest-api-lambda");
|
|
2640
|
+
this.lambda = new NodejsFunction11(this, "handler", {
|
|
2641
|
+
entry: resolveHandlerEntry10(__dirname),
|
|
2642
|
+
runtime: Runtime11.NODEJS_LATEST,
|
|
2643
|
+
memorySize: 1024,
|
|
2644
|
+
environment: {
|
|
2645
|
+
DYNAMO_TABLE_NAME: props.dynamoTableName,
|
|
2646
|
+
BRANCH_TAG_VALUE: props.branchTagValue,
|
|
2647
|
+
HTTP_API_TAG_VALUE: props.httpApiTagValue,
|
|
2648
|
+
OPENHI_PG_CLUSTER_ARN: props.postgresClusterArn,
|
|
2649
|
+
OPENHI_PG_SECRET_ARN: props.postgresSecretArn,
|
|
2650
|
+
OPENHI_PG_DATABASE: props.postgresDatabase,
|
|
2651
|
+
OPENHI_PG_SCHEMA: props.postgresSchema,
|
|
2652
|
+
...props.extraEnvironment
|
|
2653
|
+
},
|
|
2654
|
+
bundling: {
|
|
2655
|
+
minify: true,
|
|
2656
|
+
sourceMap: false
|
|
2657
|
+
}
|
|
2646
2658
|
});
|
|
2647
2659
|
}
|
|
2648
2660
|
};
|
|
2649
2661
|
|
|
2650
|
-
// src/services/open-hi-
|
|
2651
|
-
var
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
this.preTokenGenerationLambda = this.createPreTokenGenerationLambda();
|
|
2665
|
-
this.postAuthenticationLambda = this.createPostAuthenticationLambda();
|
|
2666
|
-
this.postConfirmationLambda = this.createPostConfirmationLambda();
|
|
2667
|
-
this.userOnboardingWorkflow = this.createUserOnboardingWorkflow();
|
|
2668
|
-
this.userPool = this.createUserPool();
|
|
2669
|
-
this.grantPreTokenGenerationPermissions();
|
|
2670
|
-
this.grantPostAuthenticationPermissions();
|
|
2671
|
-
this.grantPostConfirmationPermissions();
|
|
2672
|
-
this.userPoolClient = this.createUserPoolClient();
|
|
2673
|
-
this.userPoolDomain = this.createUserPoolDomain();
|
|
2674
|
-
}
|
|
2662
|
+
// src/services/open-hi-rest-api-service.ts
|
|
2663
|
+
var REST_API_BASE_URL_SSM_NAME = "REST_API_BASE_URL";
|
|
2664
|
+
var REST_API_DOMAIN_NAME_SSM_NAME = "REST_API_DOMAIN_NAME";
|
|
2665
|
+
var DEV_CORS_ALLOW_ORIGINS = [
|
|
2666
|
+
"http://localhost:3000",
|
|
2667
|
+
"https://localhost:3000",
|
|
2668
|
+
"http://localhost:5173",
|
|
2669
|
+
"https://localhost:5173",
|
|
2670
|
+
"http://127.0.0.1:3000",
|
|
2671
|
+
"https://127.0.0.1:3000",
|
|
2672
|
+
"http://127.0.0.1:5173",
|
|
2673
|
+
"https://127.0.0.1:5173"
|
|
2674
|
+
];
|
|
2675
|
+
var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
|
|
2675
2676
|
/**
|
|
2676
|
-
*
|
|
2677
|
+
* Compose the REST API's full per-deploy domain. Thin wrapper over
|
|
2678
|
+
* {@link OpenHiService.composeServiceDomain} that pins `domainPrefix`
|
|
2679
|
+
* to {@link API_DOMAIN_PREFIX}.
|
|
2680
|
+
*
|
|
2681
|
+
* Use from sibling stacks that need to predict the API's hostname
|
|
2682
|
+
* before the REST API stack is synthesised.
|
|
2677
2683
|
*/
|
|
2678
|
-
static
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2684
|
+
static composeFullDomain(opts) {
|
|
2685
|
+
return OpenHiService.composeServiceDomain({
|
|
2686
|
+
...opts,
|
|
2687
|
+
domainPrefix: _OpenHiRestApiService.API_DOMAIN_PREFIX
|
|
2682
2688
|
});
|
|
2683
|
-
return UserPool2.fromUserPoolId(scope, "user-pool", userPoolId);
|
|
2684
|
-
}
|
|
2685
|
-
/**
|
|
2686
|
-
* Returns an IUserPoolClient by looking up the Auth stack's User Pool Client ID from SSM.
|
|
2687
|
-
*/
|
|
2688
|
-
static userPoolClientFromConstruct(scope) {
|
|
2689
|
-
const userPoolClientId = DiscoverableStringParameter.valueForLookupName(
|
|
2690
|
-
scope,
|
|
2691
|
-
{
|
|
2692
|
-
ssmParamName: CognitoUserPoolClient.SSM_PARAM_NAME,
|
|
2693
|
-
serviceType: _OpenHiAuthService.SERVICE_TYPE
|
|
2694
|
-
}
|
|
2695
|
-
);
|
|
2696
|
-
return UserPoolClient2.fromUserPoolClientId(
|
|
2697
|
-
scope,
|
|
2698
|
-
"user-pool-client",
|
|
2699
|
-
userPoolClientId
|
|
2700
|
-
);
|
|
2701
2689
|
}
|
|
2702
2690
|
/**
|
|
2703
|
-
* Returns an
|
|
2691
|
+
* Returns an IHttpApi by looking up the REST API stack's HTTP API ID from SSM.
|
|
2704
2692
|
*/
|
|
2705
|
-
static
|
|
2706
|
-
const
|
|
2707
|
-
ssmParamName:
|
|
2708
|
-
serviceType:
|
|
2693
|
+
static rootHttpApiFromConstruct(scope) {
|
|
2694
|
+
const httpApiId = DiscoverableStringParameter.valueForLookupName(scope, {
|
|
2695
|
+
ssmParamName: RootHttpApi.SSM_PARAM_NAME,
|
|
2696
|
+
serviceType: _OpenHiRestApiService.SERVICE_TYPE
|
|
2709
2697
|
});
|
|
2710
|
-
return
|
|
2698
|
+
return HttpApi2.fromHttpApiAttributes(scope, "http-api", { httpApiId });
|
|
2711
2699
|
}
|
|
2712
2700
|
/**
|
|
2713
|
-
* Returns the
|
|
2714
|
-
*
|
|
2715
|
-
* the Auth stack's User Pool Domain from SSM and composing it with the
|
|
2716
|
-
* calling stack's region.
|
|
2717
|
-
*
|
|
2718
|
-
* Equivalent to `UserPoolDomain.baseUrl()` on the concrete construct,
|
|
2719
|
-
* but works across stacks where the looked-up `IUserPoolDomain` is an
|
|
2720
|
-
* `Import` and does not carry the `baseUrl()` method. Assumes the
|
|
2721
|
-
* domain was created as a Cognito-managed prefix domain (the only
|
|
2722
|
-
* variant `OpenHiAuthService.createUserPoolDomain` produces).
|
|
2701
|
+
* Returns the REST API base URL (e.g. https://api.example.com) by looking it up from SSM.
|
|
2702
|
+
* Use in other stacks for E2E, scripts, or config.
|
|
2723
2703
|
*/
|
|
2724
|
-
static
|
|
2725
|
-
|
|
2726
|
-
ssmParamName:
|
|
2727
|
-
serviceType:
|
|
2704
|
+
static restApiBaseUrlFromConstruct(scope) {
|
|
2705
|
+
return DiscoverableStringParameter.valueForLookupName(scope, {
|
|
2706
|
+
ssmParamName: REST_API_BASE_URL_SSM_NAME,
|
|
2707
|
+
serviceType: _OpenHiRestApiService.SERVICE_TYPE
|
|
2728
2708
|
});
|
|
2729
|
-
const region = Stack7.of(scope).region;
|
|
2730
|
-
return `https://${domainName}.auth.${region}.amazoncognito.com`;
|
|
2731
2709
|
}
|
|
2732
2710
|
/**
|
|
2733
|
-
* Returns
|
|
2711
|
+
* Returns the REST API's custom domain name (bare hostname, no scheme — e.g.
|
|
2712
|
+
* `api.example.com`) by looking it up from SSM. Use as the host for a
|
|
2713
|
+
* CloudFront `HttpOrigin` so the website's distribution can proxy `/api/*`
|
|
2714
|
+
* to this stack's API Gateway without per-branch DNS knowledge.
|
|
2734
2715
|
*/
|
|
2735
|
-
static
|
|
2736
|
-
|
|
2737
|
-
ssmParamName:
|
|
2738
|
-
serviceType:
|
|
2716
|
+
static restApiDomainNameFromConstruct(scope) {
|
|
2717
|
+
return DiscoverableStringParameter.valueForLookupName(scope, {
|
|
2718
|
+
ssmParamName: REST_API_DOMAIN_NAME_SSM_NAME,
|
|
2719
|
+
serviceType: _OpenHiRestApiService.SERVICE_TYPE
|
|
2739
2720
|
});
|
|
2740
|
-
return Key2.fromKeyArn(scope, "kms-key", keyArn);
|
|
2741
2721
|
}
|
|
2742
2722
|
get serviceType() {
|
|
2743
|
-
return
|
|
2723
|
+
return _OpenHiRestApiService.SERVICE_TYPE;
|
|
2724
|
+
}
|
|
2725
|
+
constructor(ohEnv, props = {}) {
|
|
2726
|
+
super(ohEnv, _OpenHiRestApiService.SERVICE_TYPE, props);
|
|
2727
|
+
this.props = props;
|
|
2728
|
+
this.validateConfig(props);
|
|
2729
|
+
const hostedZone = this.createHostedZone();
|
|
2730
|
+
const certificate = this.createCertificate();
|
|
2731
|
+
this.apiDomainName = this.createApiDomainNameString(hostedZone);
|
|
2732
|
+
this.createRestApiBaseUrlParameter(this.apiDomainName);
|
|
2733
|
+
this.createRestApiDomainNameParameter(this.apiDomainName);
|
|
2734
|
+
const domainName = this.createDomainName(hostedZone, certificate);
|
|
2735
|
+
this.rootHttpApi = this.createRootHttpApi(domainName);
|
|
2736
|
+
this.createRestApiLambdaAndRoutes(hostedZone, domainName);
|
|
2744
2737
|
}
|
|
2745
2738
|
/**
|
|
2746
|
-
*
|
|
2747
|
-
* Look up via {@link OpenHiAuthService.userPoolKmsKeyFromConstruct}.
|
|
2748
|
-
* Override to customize.
|
|
2739
|
+
* Validates that config required for the REST API stack is present.
|
|
2749
2740
|
*/
|
|
2750
|
-
|
|
2751
|
-
const
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2741
|
+
validateConfig(props) {
|
|
2742
|
+
const { config } = props;
|
|
2743
|
+
if (!config) {
|
|
2744
|
+
throw new Error("Config is required");
|
|
2745
|
+
}
|
|
2746
|
+
if (!config.hostedZoneId) {
|
|
2747
|
+
throw new Error("Hosted zone ID is required");
|
|
2748
|
+
}
|
|
2749
|
+
if (!config.zoneName) {
|
|
2750
|
+
throw new Error("Zone name is required");
|
|
2751
|
+
}
|
|
2758
2752
|
}
|
|
2759
2753
|
/**
|
|
2760
|
-
* Creates the
|
|
2761
|
-
*
|
|
2762
|
-
* (GSI2) and injects `ohi_tid`, `ohi_wid`, `ohi_uid`, `ohi_uname` into
|
|
2763
|
-
* both the ID token and the access token (ADR 2026-03-17-01).
|
|
2754
|
+
* Creates the hosted zone reference (imported from config).
|
|
2755
|
+
* Override to customize.
|
|
2764
2756
|
*/
|
|
2765
|
-
|
|
2766
|
-
const
|
|
2767
|
-
|
|
2757
|
+
createHostedZone() {
|
|
2758
|
+
const { config } = this.props;
|
|
2759
|
+
return HostedZone3.fromHostedZoneAttributes(this, "root-zone", {
|
|
2760
|
+
hostedZoneId: config.hostedZoneId,
|
|
2761
|
+
zoneName: config.zoneName
|
|
2768
2762
|
});
|
|
2769
|
-
return construct.lambda;
|
|
2770
2763
|
}
|
|
2771
2764
|
/**
|
|
2772
|
-
* Creates the
|
|
2773
|
-
*
|
|
2774
|
-
* sessions per ADR 2026-03-17-01.
|
|
2765
|
+
* Creates the wildcard certificate (imported from Global stack via SSM).
|
|
2766
|
+
* Override to customize.
|
|
2775
2767
|
*/
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
return construct.lambda;
|
|
2768
|
+
createCertificate() {
|
|
2769
|
+
return OpenHiGlobalService.rootWildcardCertificateFromConstruct(this);
|
|
2779
2770
|
}
|
|
2780
2771
|
/**
|
|
2781
|
-
*
|
|
2782
|
-
*
|
|
2783
|
-
*
|
|
2772
|
+
* Returns the API domain name string (e.g. api.example.com or api-\{prefix\}.example.com).
|
|
2773
|
+
* Delegates to {@link OpenHiRestApiService.composeFullDomain} so the
|
|
2774
|
+
* release-vs-feature composition stays in one place; picks up
|
|
2775
|
+
* `this.defaultReleaseBranch` (not a hard-coded `"main"`).
|
|
2776
|
+
* Override to customize.
|
|
2784
2777
|
*/
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
createUserOnboardingWorkflow() {
|
|
2792
|
-
return new UserOnboardingWorkflow(this, {
|
|
2793
|
-
controlEventBus: this.controlEventBus(),
|
|
2794
|
-
dataStoreTable: this.dataStoreTable()
|
|
2778
|
+
createApiDomainNameString(hostedZone) {
|
|
2779
|
+
return _OpenHiRestApiService.composeFullDomain({
|
|
2780
|
+
branchName: this.branchName,
|
|
2781
|
+
defaultReleaseBranch: this.defaultReleaseBranch,
|
|
2782
|
+
childZonePrefix: this.childZonePrefix,
|
|
2783
|
+
zoneName: hostedZone.zoneName
|
|
2795
2784
|
});
|
|
2796
2785
|
}
|
|
2797
|
-
dataStoreTable() {
|
|
2798
|
-
if (this._dataStoreTable === null) {
|
|
2799
|
-
this._dataStoreTable = OpenHiDataService.dynamoDbDataStoreFromConstruct(this);
|
|
2800
|
-
}
|
|
2801
|
-
return this._dataStoreTable;
|
|
2802
|
-
}
|
|
2803
|
-
controlEventBus() {
|
|
2804
|
-
if (this._controlEventBus === null) {
|
|
2805
|
-
this._controlEventBus = OpenHiGlobalService.controlEventBusFromConstruct(this);
|
|
2806
|
-
}
|
|
2807
|
-
return this._controlEventBus;
|
|
2808
|
-
}
|
|
2809
2786
|
/**
|
|
2810
|
-
* Creates the
|
|
2811
|
-
* Look up via {@link
|
|
2787
|
+
* Creates the SSM parameter for the REST API base URL.
|
|
2788
|
+
* Look up via {@link OpenHiRestApiService.restApiBaseUrlFromConstruct}.
|
|
2812
2789
|
* Override to customize.
|
|
2813
2790
|
*/
|
|
2814
|
-
|
|
2815
|
-
const
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
UserPoolOperation.PRE_TOKEN_GENERATION_CONFIG,
|
|
2821
|
-
this.preTokenGenerationLambda,
|
|
2822
|
-
LambdaVersion.V2_0
|
|
2823
|
-
);
|
|
2824
|
-
userPool.addTrigger(
|
|
2825
|
-
UserPoolOperation.POST_AUTHENTICATION,
|
|
2826
|
-
this.postAuthenticationLambda
|
|
2827
|
-
);
|
|
2828
|
-
userPool.addTrigger(
|
|
2829
|
-
UserPoolOperation.POST_CONFIRMATION,
|
|
2830
|
-
this.postConfirmationLambda
|
|
2831
|
-
);
|
|
2832
|
-
new DiscoverableStringParameter(this, "user-pool-param", {
|
|
2833
|
-
ssmParamName: CognitoUserPool.SSM_PARAM_NAME,
|
|
2834
|
-
stringValue: userPool.userPoolId,
|
|
2835
|
-
description: "Cognito User Pool ID for this Auth stack; cross-stack reference"
|
|
2791
|
+
createRestApiBaseUrlParameter(apiDomainName) {
|
|
2792
|
+
const restApiBaseUrl = `https://${apiDomainName}`;
|
|
2793
|
+
new DiscoverableStringParameter(this, "rest-api-base-url-param", {
|
|
2794
|
+
ssmParamName: REST_API_BASE_URL_SSM_NAME,
|
|
2795
|
+
stringValue: restApiBaseUrl,
|
|
2796
|
+
description: "REST API base URL for this deployment (E2E, scripts)"
|
|
2836
2797
|
});
|
|
2837
|
-
return userPool;
|
|
2838
|
-
}
|
|
2839
|
-
/**
|
|
2840
|
-
* Grants the Pre Token Generation Lambda read-only access on the data
|
|
2841
|
-
* store table and its GSIs. The Lambda only needs:
|
|
2842
|
-
* - `Query` on GSI2 to resolve a User by Cognito `sub`
|
|
2843
|
-
* - `GetItem` on the base table for direct User reads
|
|
2844
|
-
*
|
|
2845
|
-
* No write or scan access: a User missing `currentTenant`/`currentWorkspace`
|
|
2846
|
-
* falls into the absent-claims path; repair belongs in a separate backfill.
|
|
2847
|
-
*/
|
|
2848
|
-
grantPreTokenGenerationPermissions() {
|
|
2849
|
-
const dataStoreTable = this.dataStoreTable();
|
|
2850
|
-
const dynamoActions = ["dynamodb:GetItem", "dynamodb:Query"];
|
|
2851
|
-
dataStoreTable.grant(this.preTokenGenerationLambda, ...dynamoActions);
|
|
2852
|
-
this.preTokenGenerationLambda.addToRolePolicy(
|
|
2853
|
-
new PolicyStatement6({
|
|
2854
|
-
effect: Effect6.ALLOW,
|
|
2855
|
-
actions: [...dynamoActions],
|
|
2856
|
-
resources: [`${dataStoreTable.tableArn}/index/*`]
|
|
2857
|
-
})
|
|
2858
|
-
);
|
|
2859
|
-
}
|
|
2860
|
-
/**
|
|
2861
|
-
* Grants the Post Authentication Lambda permission to call
|
|
2862
|
-
* `cognito-idp:AdminUserGlobalSignOut`.
|
|
2863
|
-
*
|
|
2864
|
-
* Scoped via `Stack.of(this).formatArn` rather than `userPool.userPoolArn`
|
|
2865
|
-
* because the User Pool registers this Lambda as a Post Authentication
|
|
2866
|
-
* trigger, creating the cycle:
|
|
2867
|
-
* userPool → lambda (trigger ARN) → role policy → userPool ARN.
|
|
2868
|
-
* Using `formatArn` avoids referencing the User Pool resource directly
|
|
2869
|
-
* while still scoping to user pools in this account+region. The Lambda
|
|
2870
|
-
* is invoked only by Cognito with a Cognito-provided `event.userPoolId`,
|
|
2871
|
-
* so the runtime target is constrained by the trigger contract.
|
|
2872
|
-
*/
|
|
2873
|
-
grantPostAuthenticationPermissions() {
|
|
2874
|
-
this.postAuthenticationLambda.addToRolePolicy(
|
|
2875
|
-
new PolicyStatement6({
|
|
2876
|
-
actions: ["cognito-idp:AdminUserGlobalSignOut"],
|
|
2877
|
-
resources: [
|
|
2878
|
-
Stack7.of(this).formatArn({
|
|
2879
|
-
service: "cognito-idp",
|
|
2880
|
-
resource: "userpool",
|
|
2881
|
-
resourceName: "*"
|
|
2882
|
-
})
|
|
2883
|
-
]
|
|
2884
|
-
})
|
|
2885
|
-
);
|
|
2886
2798
|
}
|
|
2887
2799
|
/**
|
|
2888
|
-
*
|
|
2889
|
-
*
|
|
2800
|
+
* Creates the SSM parameter exposing the REST API's custom domain (bare
|
|
2801
|
+
* hostname, no scheme). Consumed by the website service as the CloudFront
|
|
2802
|
+
* `/api/*` origin host.
|
|
2803
|
+
* Look up via {@link OpenHiRestApiService.restApiDomainNameFromConstruct}.
|
|
2804
|
+
* Override to customize.
|
|
2890
2805
|
*/
|
|
2891
|
-
|
|
2892
|
-
|
|
2806
|
+
createRestApiDomainNameParameter(apiDomainName) {
|
|
2807
|
+
new DiscoverableStringParameter(this, "rest-api-domain-name-param", {
|
|
2808
|
+
ssmParamName: REST_API_DOMAIN_NAME_SSM_NAME,
|
|
2809
|
+
stringValue: apiDomainName,
|
|
2810
|
+
description: "REST API custom domain name (bare hostname) for cross-stack CloudFront origin lookup"
|
|
2811
|
+
});
|
|
2893
2812
|
}
|
|
2894
2813
|
/**
|
|
2895
|
-
* Creates the
|
|
2896
|
-
* Look up via {@link OpenHiAuthService.userPoolClientFromConstruct}.
|
|
2814
|
+
* Creates the API Gateway custom domain name resource.
|
|
2897
2815
|
* Override to customize.
|
|
2898
2816
|
*/
|
|
2899
|
-
|
|
2900
|
-
const
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
ssmParamName: CognitoUserPoolClient.SSM_PARAM_NAME,
|
|
2905
|
-
stringValue: client.userPoolClientId,
|
|
2906
|
-
description: "Cognito User Pool Client ID for this Auth stack; cross-stack reference"
|
|
2817
|
+
createDomainName(_hostedZone, certificate) {
|
|
2818
|
+
const apiDomainName = this.createApiDomainNameString(_hostedZone);
|
|
2819
|
+
return new DomainName(this, "domain", {
|
|
2820
|
+
domainName: apiDomainName,
|
|
2821
|
+
certificate
|
|
2907
2822
|
});
|
|
2908
|
-
return client;
|
|
2909
2823
|
}
|
|
2910
2824
|
/**
|
|
2911
|
-
* Creates the
|
|
2912
|
-
*
|
|
2913
|
-
* Override to customize.
|
|
2825
|
+
* Creates the Lambda integration, HTTP routes, and API DNS record.
|
|
2826
|
+
* Override to customize. Uses {@link rootHttpApi} set by the constructor.
|
|
2914
2827
|
*/
|
|
2915
|
-
|
|
2916
|
-
const
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
new
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
var OpenHiAuthService = _OpenHiAuthService;
|
|
2932
|
-
|
|
2933
|
-
// src/services/open-hi-rest-api-service.ts
|
|
2934
|
-
var import_config5 = __toESM(require_lib2());
|
|
2935
|
-
import {
|
|
2936
|
-
CorsHttpMethod,
|
|
2937
|
-
DomainName,
|
|
2938
|
-
HttpApi as HttpApi2,
|
|
2939
|
-
HttpMethod,
|
|
2940
|
-
HttpNoneAuthorizer,
|
|
2941
|
-
HttpRoute,
|
|
2942
|
-
HttpRouteKey
|
|
2943
|
-
} from "aws-cdk-lib/aws-apigatewayv2";
|
|
2944
|
-
import { HttpUserPoolAuthorizer } from "aws-cdk-lib/aws-apigatewayv2-authorizers";
|
|
2945
|
-
import { HttpLambdaIntegration } from "aws-cdk-lib/aws-apigatewayv2-integrations";
|
|
2946
|
-
import { Effect as Effect7, PolicyStatement as PolicyStatement7 } from "aws-cdk-lib/aws-iam";
|
|
2947
|
-
import {
|
|
2948
|
-
ARecord as ARecord3,
|
|
2949
|
-
HostedZone as HostedZone3,
|
|
2950
|
-
RecordTarget as RecordTarget3
|
|
2951
|
-
} from "aws-cdk-lib/aws-route53";
|
|
2952
|
-
import { ApiGatewayv2DomainProperties } from "aws-cdk-lib/aws-route53-targets";
|
|
2953
|
-
import { Duration as Duration10 } from "aws-cdk-lib/core";
|
|
2954
|
-
import { Construct as Construct21 } from "constructs";
|
|
2955
|
-
|
|
2956
|
-
// src/data/lambda/cors-options-lambda.ts
|
|
2957
|
-
import fs11 from "fs";
|
|
2958
|
-
import path11 from "path";
|
|
2959
|
-
import { Runtime as Runtime11 } from "aws-cdk-lib/aws-lambda";
|
|
2960
|
-
import { NodejsFunction as NodejsFunction11 } from "aws-cdk-lib/aws-lambda-nodejs";
|
|
2961
|
-
import { Construct as Construct19 } from "constructs";
|
|
2962
|
-
var HANDLER_NAME10 = "cors-options-lambda.handler.js";
|
|
2963
|
-
function resolveHandlerEntry10(dirname) {
|
|
2964
|
-
const sameDir = path11.join(dirname, HANDLER_NAME10);
|
|
2965
|
-
if (fs11.existsSync(sameDir)) {
|
|
2966
|
-
return sameDir;
|
|
2967
|
-
}
|
|
2968
|
-
const fromLib = path11.join(dirname, "..", "..", "..", "lib", HANDLER_NAME10);
|
|
2969
|
-
return fromLib;
|
|
2970
|
-
}
|
|
2971
|
-
var CorsOptionsLambda = class extends Construct19 {
|
|
2972
|
-
constructor(scope, id = "cors-options-lambda") {
|
|
2973
|
-
super(scope, id);
|
|
2974
|
-
this.lambda = new NodejsFunction11(this, "handler", {
|
|
2975
|
-
entry: resolveHandlerEntry10(__dirname),
|
|
2976
|
-
runtime: Runtime11.NODEJS_LATEST,
|
|
2977
|
-
memorySize: 128
|
|
2978
|
-
});
|
|
2979
|
-
}
|
|
2980
|
-
};
|
|
2981
|
-
|
|
2982
|
-
// src/data/lambda/rest-api-lambda.ts
|
|
2983
|
-
import fs12 from "fs";
|
|
2984
|
-
import path12 from "path";
|
|
2985
|
-
import { Runtime as Runtime12 } from "aws-cdk-lib/aws-lambda";
|
|
2986
|
-
import { NodejsFunction as NodejsFunction12 } from "aws-cdk-lib/aws-lambda-nodejs";
|
|
2987
|
-
import { Construct as Construct20 } from "constructs";
|
|
2988
|
-
var HANDLER_NAME11 = "rest-api-lambda.handler.js";
|
|
2989
|
-
function resolveHandlerEntry11(dirname) {
|
|
2990
|
-
const sameDir = path12.join(dirname, HANDLER_NAME11);
|
|
2991
|
-
if (fs12.existsSync(sameDir)) {
|
|
2992
|
-
return sameDir;
|
|
2993
|
-
}
|
|
2994
|
-
const fromLib = path12.join(dirname, "..", "..", "..", "lib", HANDLER_NAME11);
|
|
2995
|
-
return fromLib;
|
|
2996
|
-
}
|
|
2997
|
-
var RestApiLambda = class extends Construct20 {
|
|
2998
|
-
constructor(scope, props) {
|
|
2999
|
-
super(scope, "rest-api-lambda");
|
|
3000
|
-
this.lambda = new NodejsFunction12(this, "handler", {
|
|
3001
|
-
entry: resolveHandlerEntry11(__dirname),
|
|
3002
|
-
runtime: Runtime12.NODEJS_LATEST,
|
|
3003
|
-
memorySize: 1024,
|
|
3004
|
-
environment: {
|
|
3005
|
-
DYNAMO_TABLE_NAME: props.dynamoTableName,
|
|
3006
|
-
BRANCH_TAG_VALUE: props.branchTagValue,
|
|
3007
|
-
HTTP_API_TAG_VALUE: props.httpApiTagValue,
|
|
3008
|
-
OPENHI_PG_CLUSTER_ARN: props.postgresClusterArn,
|
|
3009
|
-
OPENHI_PG_SECRET_ARN: props.postgresSecretArn,
|
|
3010
|
-
OPENHI_PG_DATABASE: props.postgresDatabase,
|
|
3011
|
-
OPENHI_PG_SCHEMA: props.postgresSchema,
|
|
3012
|
-
...props.extraEnvironment
|
|
3013
|
-
},
|
|
3014
|
-
bundling: {
|
|
3015
|
-
minify: true,
|
|
3016
|
-
sourceMap: false
|
|
3017
|
-
}
|
|
3018
|
-
});
|
|
3019
|
-
}
|
|
3020
|
-
};
|
|
3021
|
-
|
|
3022
|
-
// src/services/open-hi-rest-api-service.ts
|
|
3023
|
-
var REST_API_BASE_URL_SSM_NAME = "REST_API_BASE_URL";
|
|
3024
|
-
var REST_API_DOMAIN_NAME_SSM_NAME = "REST_API_DOMAIN_NAME";
|
|
3025
|
-
var DEV_CORS_ALLOW_ORIGINS = [
|
|
3026
|
-
"http://localhost:3000",
|
|
3027
|
-
"https://localhost:3000",
|
|
3028
|
-
"http://localhost:5173",
|
|
3029
|
-
"https://localhost:5173",
|
|
3030
|
-
"http://127.0.0.1:3000",
|
|
3031
|
-
"https://127.0.0.1:3000",
|
|
3032
|
-
"http://127.0.0.1:5173",
|
|
3033
|
-
"https://127.0.0.1:5173"
|
|
3034
|
-
];
|
|
3035
|
-
var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
|
|
3036
|
-
/**
|
|
3037
|
-
* Compose the REST API's full per-deploy domain. Thin wrapper over
|
|
3038
|
-
* {@link OpenHiService.composeServiceDomain} that pins `domainPrefix`
|
|
3039
|
-
* to {@link API_DOMAIN_PREFIX}.
|
|
3040
|
-
*
|
|
3041
|
-
* Use from sibling stacks that need to predict the API's hostname
|
|
3042
|
-
* before the REST API stack is synthesised.
|
|
3043
|
-
*/
|
|
3044
|
-
static composeFullDomain(opts) {
|
|
3045
|
-
return OpenHiService.composeServiceDomain({
|
|
3046
|
-
...opts,
|
|
3047
|
-
domainPrefix: _OpenHiRestApiService.API_DOMAIN_PREFIX
|
|
3048
|
-
});
|
|
3049
|
-
}
|
|
3050
|
-
/**
|
|
3051
|
-
* Returns an IHttpApi by looking up the REST API stack's HTTP API ID from SSM.
|
|
3052
|
-
*/
|
|
3053
|
-
static rootHttpApiFromConstruct(scope) {
|
|
3054
|
-
const httpApiId = DiscoverableStringParameter.valueForLookupName(scope, {
|
|
3055
|
-
ssmParamName: RootHttpApi.SSM_PARAM_NAME,
|
|
3056
|
-
serviceType: _OpenHiRestApiService.SERVICE_TYPE
|
|
3057
|
-
});
|
|
3058
|
-
return HttpApi2.fromHttpApiAttributes(scope, "http-api", { httpApiId });
|
|
3059
|
-
}
|
|
3060
|
-
/**
|
|
3061
|
-
* Returns the REST API base URL (e.g. https://api.example.com) by looking it up from SSM.
|
|
3062
|
-
* Use in other stacks for E2E, scripts, or config.
|
|
3063
|
-
*/
|
|
3064
|
-
static restApiBaseUrlFromConstruct(scope) {
|
|
3065
|
-
return DiscoverableStringParameter.valueForLookupName(scope, {
|
|
3066
|
-
ssmParamName: REST_API_BASE_URL_SSM_NAME,
|
|
3067
|
-
serviceType: _OpenHiRestApiService.SERVICE_TYPE
|
|
3068
|
-
});
|
|
3069
|
-
}
|
|
3070
|
-
/**
|
|
3071
|
-
* Returns the REST API's custom domain name (bare hostname, no scheme — e.g.
|
|
3072
|
-
* `api.example.com`) by looking it up from SSM. Use as the host for a
|
|
3073
|
-
* CloudFront `HttpOrigin` so the website's distribution can proxy `/api/*`
|
|
3074
|
-
* to this stack's API Gateway without per-branch DNS knowledge.
|
|
3075
|
-
*/
|
|
3076
|
-
static restApiDomainNameFromConstruct(scope) {
|
|
3077
|
-
return DiscoverableStringParameter.valueForLookupName(scope, {
|
|
3078
|
-
ssmParamName: REST_API_DOMAIN_NAME_SSM_NAME,
|
|
3079
|
-
serviceType: _OpenHiRestApiService.SERVICE_TYPE
|
|
3080
|
-
});
|
|
3081
|
-
}
|
|
3082
|
-
get serviceType() {
|
|
3083
|
-
return _OpenHiRestApiService.SERVICE_TYPE;
|
|
3084
|
-
}
|
|
3085
|
-
constructor(ohEnv, props = {}) {
|
|
3086
|
-
super(ohEnv, _OpenHiRestApiService.SERVICE_TYPE, props);
|
|
3087
|
-
this.props = props;
|
|
3088
|
-
this.validateConfig(props);
|
|
3089
|
-
const hostedZone = this.createHostedZone();
|
|
3090
|
-
const certificate = this.createCertificate();
|
|
3091
|
-
this.apiDomainName = this.createApiDomainNameString(hostedZone);
|
|
3092
|
-
this.createRestApiBaseUrlParameter(this.apiDomainName);
|
|
3093
|
-
this.createRestApiDomainNameParameter(this.apiDomainName);
|
|
3094
|
-
const domainName = this.createDomainName(hostedZone, certificate);
|
|
3095
|
-
this.rootHttpApi = this.createRootHttpApi(domainName);
|
|
3096
|
-
this.createRestApiLambdaAndRoutes(hostedZone, domainName);
|
|
3097
|
-
}
|
|
3098
|
-
/**
|
|
3099
|
-
* Validates that config required for the REST API stack is present.
|
|
3100
|
-
*/
|
|
3101
|
-
validateConfig(props) {
|
|
3102
|
-
const { config } = props;
|
|
3103
|
-
if (!config) {
|
|
3104
|
-
throw new Error("Config is required");
|
|
3105
|
-
}
|
|
3106
|
-
if (!config.hostedZoneId) {
|
|
3107
|
-
throw new Error("Hosted zone ID is required");
|
|
3108
|
-
}
|
|
3109
|
-
if (!config.zoneName) {
|
|
3110
|
-
throw new Error("Zone name is required");
|
|
3111
|
-
}
|
|
3112
|
-
}
|
|
3113
|
-
/**
|
|
3114
|
-
* Creates the hosted zone reference (imported from config).
|
|
3115
|
-
* Override to customize.
|
|
3116
|
-
*/
|
|
3117
|
-
createHostedZone() {
|
|
3118
|
-
const { config } = this.props;
|
|
3119
|
-
return HostedZone3.fromHostedZoneAttributes(this, "root-zone", {
|
|
3120
|
-
hostedZoneId: config.hostedZoneId,
|
|
3121
|
-
zoneName: config.zoneName
|
|
3122
|
-
});
|
|
3123
|
-
}
|
|
3124
|
-
/**
|
|
3125
|
-
* Creates the wildcard certificate (imported from Global stack via SSM).
|
|
3126
|
-
* Override to customize.
|
|
3127
|
-
*/
|
|
3128
|
-
createCertificate() {
|
|
3129
|
-
return OpenHiGlobalService.rootWildcardCertificateFromConstruct(this);
|
|
3130
|
-
}
|
|
3131
|
-
/**
|
|
3132
|
-
* Returns the API domain name string (e.g. api.example.com or api-\{prefix\}.example.com).
|
|
3133
|
-
* Delegates to {@link OpenHiRestApiService.composeFullDomain} so the
|
|
3134
|
-
* release-vs-feature composition stays in one place; picks up
|
|
3135
|
-
* `this.defaultReleaseBranch` (not a hard-coded `"main"`).
|
|
3136
|
-
* Override to customize.
|
|
3137
|
-
*/
|
|
3138
|
-
createApiDomainNameString(hostedZone) {
|
|
3139
|
-
return _OpenHiRestApiService.composeFullDomain({
|
|
3140
|
-
branchName: this.branchName,
|
|
3141
|
-
defaultReleaseBranch: this.defaultReleaseBranch,
|
|
3142
|
-
childZonePrefix: this.childZonePrefix,
|
|
3143
|
-
zoneName: hostedZone.zoneName
|
|
3144
|
-
});
|
|
3145
|
-
}
|
|
3146
|
-
/**
|
|
3147
|
-
* Creates the SSM parameter for the REST API base URL.
|
|
3148
|
-
* Look up via {@link OpenHiRestApiService.restApiBaseUrlFromConstruct}.
|
|
3149
|
-
* Override to customize.
|
|
3150
|
-
*/
|
|
3151
|
-
createRestApiBaseUrlParameter(apiDomainName) {
|
|
3152
|
-
const restApiBaseUrl = `https://${apiDomainName}`;
|
|
3153
|
-
new DiscoverableStringParameter(this, "rest-api-base-url-param", {
|
|
3154
|
-
ssmParamName: REST_API_BASE_URL_SSM_NAME,
|
|
3155
|
-
stringValue: restApiBaseUrl,
|
|
3156
|
-
description: "REST API base URL for this deployment (E2E, scripts)"
|
|
3157
|
-
});
|
|
3158
|
-
}
|
|
3159
|
-
/**
|
|
3160
|
-
* Creates the SSM parameter exposing the REST API's custom domain (bare
|
|
3161
|
-
* hostname, no scheme). Consumed by the website service as the CloudFront
|
|
3162
|
-
* `/api/*` origin host.
|
|
3163
|
-
* Look up via {@link OpenHiRestApiService.restApiDomainNameFromConstruct}.
|
|
3164
|
-
* Override to customize.
|
|
3165
|
-
*/
|
|
3166
|
-
createRestApiDomainNameParameter(apiDomainName) {
|
|
3167
|
-
new DiscoverableStringParameter(this, "rest-api-domain-name-param", {
|
|
3168
|
-
ssmParamName: REST_API_DOMAIN_NAME_SSM_NAME,
|
|
3169
|
-
stringValue: apiDomainName,
|
|
3170
|
-
description: "REST API custom domain name (bare hostname) for cross-stack CloudFront origin lookup"
|
|
3171
|
-
});
|
|
3172
|
-
}
|
|
3173
|
-
/**
|
|
3174
|
-
* Creates the API Gateway custom domain name resource.
|
|
3175
|
-
* Override to customize.
|
|
3176
|
-
*/
|
|
3177
|
-
createDomainName(_hostedZone, certificate) {
|
|
3178
|
-
const apiDomainName = this.createApiDomainNameString(_hostedZone);
|
|
3179
|
-
return new DomainName(this, "domain", {
|
|
3180
|
-
domainName: apiDomainName,
|
|
3181
|
-
certificate
|
|
3182
|
-
});
|
|
3183
|
-
}
|
|
3184
|
-
/**
|
|
3185
|
-
* Creates the Lambda integration, HTTP routes, and API DNS record.
|
|
3186
|
-
* Override to customize. Uses {@link rootHttpApi} set by the constructor.
|
|
3187
|
-
*/
|
|
3188
|
-
createRestApiLambdaAndRoutes(hostedZone, domainName) {
|
|
3189
|
-
const dataStoreTable = OpenHiDataService.dynamoDbDataStoreFromConstruct(this);
|
|
3190
|
-
const postgresClusterArn = DataStorePostgresReplica.clusterArnFromConstruct(this);
|
|
3191
|
-
const postgresSecretArn = DataStorePostgresReplica.secretArnFromConstruct(this);
|
|
3192
|
-
const postgresDatabase = DataStorePostgresReplica.databaseNameFromConstruct(this);
|
|
3193
|
-
const postgresSchema = getPostgresReplicaSchemaName(this.branchHash);
|
|
3194
|
-
const extraEnvironment = this.resolveRuntimeConfigEnvVars();
|
|
3195
|
-
const { lambda } = new RestApiLambda(this, {
|
|
3196
|
-
dynamoTableName: dataStoreTable.tableName,
|
|
3197
|
-
branchTagValue: this.branchName,
|
|
3198
|
-
httpApiTagValue: RootHttpApi.SSM_PARAM_NAME,
|
|
3199
|
-
postgresClusterArn,
|
|
3200
|
-
postgresSecretArn,
|
|
3201
|
-
postgresDatabase,
|
|
3202
|
-
postgresSchema,
|
|
3203
|
-
extraEnvironment
|
|
2828
|
+
createRestApiLambdaAndRoutes(hostedZone, domainName) {
|
|
2829
|
+
const dataStoreTable = OpenHiDataService.dynamoDbDataStoreFromConstruct(this);
|
|
2830
|
+
const postgresClusterArn = DataStorePostgresReplica.clusterArnFromConstruct(this);
|
|
2831
|
+
const postgresSecretArn = DataStorePostgresReplica.secretArnFromConstruct(this);
|
|
2832
|
+
const postgresDatabase = DataStorePostgresReplica.databaseNameFromConstruct(this);
|
|
2833
|
+
const postgresSchema = getPostgresReplicaSchemaName(this.branchHash);
|
|
2834
|
+
const extraEnvironment = this.resolveRuntimeConfigEnvVars();
|
|
2835
|
+
const { lambda } = new RestApiLambda(this, {
|
|
2836
|
+
dynamoTableName: dataStoreTable.tableName,
|
|
2837
|
+
branchTagValue: this.branchName,
|
|
2838
|
+
httpApiTagValue: RootHttpApi.SSM_PARAM_NAME,
|
|
2839
|
+
postgresClusterArn,
|
|
2840
|
+
postgresSecretArn,
|
|
2841
|
+
postgresDatabase,
|
|
2842
|
+
postgresSchema,
|
|
2843
|
+
extraEnvironment
|
|
3204
2844
|
});
|
|
3205
2845
|
lambda.addToRolePolicy(
|
|
3206
|
-
new
|
|
3207
|
-
effect:
|
|
2846
|
+
new PolicyStatement5({
|
|
2847
|
+
effect: Effect5.ALLOW,
|
|
3208
2848
|
actions: [
|
|
3209
2849
|
"rds-data:ExecuteStatement",
|
|
3210
2850
|
"rds-data:BatchExecuteStatement"
|
|
@@ -3213,8 +2853,8 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
|
|
|
3213
2853
|
})
|
|
3214
2854
|
);
|
|
3215
2855
|
lambda.addToRolePolicy(
|
|
3216
|
-
new
|
|
3217
|
-
effect:
|
|
2856
|
+
new PolicyStatement5({
|
|
2857
|
+
effect: Effect5.ALLOW,
|
|
3218
2858
|
actions: ["secretsmanager:GetSecretValue"],
|
|
3219
2859
|
resources: [postgresSecretArn]
|
|
3220
2860
|
})
|
|
@@ -3232,15 +2872,15 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
|
|
|
3232
2872
|
];
|
|
3233
2873
|
dataStoreTable.grant(lambda, ...dynamoActions);
|
|
3234
2874
|
lambda.addToRolePolicy(
|
|
3235
|
-
new
|
|
3236
|
-
effect:
|
|
2875
|
+
new PolicyStatement5({
|
|
2876
|
+
effect: Effect5.ALLOW,
|
|
3237
2877
|
actions: [...dynamoActions],
|
|
3238
2878
|
resources: [`${dataStoreTable.tableArn}/index/*`]
|
|
3239
2879
|
})
|
|
3240
2880
|
);
|
|
3241
2881
|
lambda.addToRolePolicy(
|
|
3242
|
-
new
|
|
3243
|
-
effect:
|
|
2882
|
+
new PolicyStatement5({
|
|
2883
|
+
effect: Effect5.ALLOW,
|
|
3244
2884
|
actions: [
|
|
3245
2885
|
"ssm:GetParameter",
|
|
3246
2886
|
"ssm:GetParameters",
|
|
@@ -3321,11 +2961,18 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
|
|
|
3321
2961
|
const { corsPreflight: cors, ...restRootHttpApiProps } = this.props.rootHttpApiProps ?? {};
|
|
3322
2962
|
const isNonProd = this.ohEnv.ohStage.stageType !== import_config5.OPEN_HI_STAGE.PROD;
|
|
3323
2963
|
const callerOrigins = cors?.allowOrigins ?? [];
|
|
3324
|
-
const
|
|
3325
|
-
const
|
|
2964
|
+
const autoOrigins = this.resolveAutoInjectedCorsOrigins();
|
|
2965
|
+
const mergedOrigins = Array.from(
|
|
2966
|
+
/* @__PURE__ */ new Set([
|
|
2967
|
+
...callerOrigins,
|
|
2968
|
+
...autoOrigins,
|
|
2969
|
+
...isNonProd ? DEV_CORS_ALLOW_ORIGINS : []
|
|
2970
|
+
])
|
|
2971
|
+
);
|
|
2972
|
+
const corsPreflight = this.buildCorsPreflightOptions(mergedOrigins, cors);
|
|
3326
2973
|
const rootHttpApi = new RootHttpApi(this, {
|
|
3327
2974
|
...restRootHttpApiProps,
|
|
3328
|
-
|
|
2975
|
+
corsPreflight,
|
|
3329
2976
|
defaultDomainMapping: {
|
|
3330
2977
|
domainName,
|
|
3331
2978
|
mappingKey: void 0
|
|
@@ -3339,6 +2986,33 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
|
|
|
3339
2986
|
});
|
|
3340
2987
|
return rootHttpApi;
|
|
3341
2988
|
}
|
|
2989
|
+
/**
|
|
2990
|
+
* Returns the admin-console and marketing-website origins this REST API
|
|
2991
|
+
* stack should accept by default, composed from the same branch context
|
|
2992
|
+
* the website service will see at synth time. Both hostnames are
|
|
2993
|
+
* `https://`-only — they always resolve to real DNS records.
|
|
2994
|
+
*
|
|
2995
|
+
* Auto-injected on every stage (no `isNonProd` gate) so the admin SPA can
|
|
2996
|
+
* call the API cross-origin without the caller having to predict the
|
|
2997
|
+
* per-deploy hostname. Override to customize the auto-injected set.
|
|
2998
|
+
*/
|
|
2999
|
+
resolveAutoInjectedCorsOrigins() {
|
|
3000
|
+
const zoneName = this.props.config.zoneName;
|
|
3001
|
+
const adminHost = OpenHiWebsiteService.composeFullDomain({
|
|
3002
|
+
domainPrefix: ADMIN_DOMAIN_PREFIX,
|
|
3003
|
+
branchName: this.branchName,
|
|
3004
|
+
defaultReleaseBranch: this.defaultReleaseBranch,
|
|
3005
|
+
childZonePrefix: this.childZonePrefix,
|
|
3006
|
+
zoneName
|
|
3007
|
+
});
|
|
3008
|
+
const websiteHost = OpenHiWebsiteService.composeFullDomain({
|
|
3009
|
+
branchName: this.branchName,
|
|
3010
|
+
defaultReleaseBranch: this.defaultReleaseBranch,
|
|
3011
|
+
childZonePrefix: this.childZonePrefix,
|
|
3012
|
+
zoneName
|
|
3013
|
+
});
|
|
3014
|
+
return [`https://${adminHost}`, `https://${websiteHost}`];
|
|
3015
|
+
}
|
|
3342
3016
|
/**
|
|
3343
3017
|
* Builds the full `CorsPreflightOptions` from a merged origins array,
|
|
3344
3018
|
* filling defaults for `allowMethods`/`allowHeaders`/`allowCredentials`/
|
|
@@ -3358,7 +3032,7 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
|
|
|
3358
3032
|
],
|
|
3359
3033
|
allowHeaders: cors?.allowHeaders ?? ["Content-Type", "Authorization"],
|
|
3360
3034
|
allowCredentials: cors?.allowCredentials ?? true,
|
|
3361
|
-
maxAge: cors?.maxAge ??
|
|
3035
|
+
maxAge: cors?.maxAge ?? Duration9.days(1),
|
|
3362
3036
|
...cors?.exposeHeaders !== void 0 && {
|
|
3363
3037
|
exposeHeaders: cors.exposeHeaders
|
|
3364
3038
|
}
|
|
@@ -3376,7 +3050,7 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
|
|
|
3376
3050
|
* client-side from `window.location.origin`.
|
|
3377
3051
|
*/
|
|
3378
3052
|
resolveRuntimeConfigEnvVars() {
|
|
3379
|
-
const cognitoScope = new
|
|
3053
|
+
const cognitoScope = new Construct19(this, "runtime-config");
|
|
3380
3054
|
const userPool = OpenHiAuthService.userPoolFromConstruct(cognitoScope);
|
|
3381
3055
|
const userPoolClient = OpenHiAuthService.userPoolClientFromConstruct(cognitoScope);
|
|
3382
3056
|
const cognitoDomainUrl = OpenHiAuthService.userPoolDomainBaseUrlFromConstruct(cognitoScope);
|
|
@@ -3387,342 +3061,761 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
|
|
|
3387
3061
|
OPENHI_RUNTIME_CONFIG_API_BASE_URL: `https://${this.apiDomainName}`
|
|
3388
3062
|
};
|
|
3389
3063
|
}
|
|
3390
|
-
};
|
|
3391
|
-
_OpenHiRestApiService.SERVICE_TYPE = "rest-api";
|
|
3392
|
-
/**
|
|
3393
|
-
* Sub-domain prefix used by the REST API. Release-branch hostname is
|
|
3394
|
-
* `api.<zone>`; per-PR preview hostname is `api-<childZonePrefix>.<zone>`.
|
|
3395
|
-
*/
|
|
3396
|
-
_OpenHiRestApiService.API_DOMAIN_PREFIX = "api";
|
|
3397
|
-
var OpenHiRestApiService = _OpenHiRestApiService;
|
|
3398
|
-
|
|
3399
|
-
// src/services/open-hi-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3064
|
+
};
|
|
3065
|
+
_OpenHiRestApiService.SERVICE_TYPE = "rest-api";
|
|
3066
|
+
/**
|
|
3067
|
+
* Sub-domain prefix used by the REST API. Release-branch hostname is
|
|
3068
|
+
* `api.<zone>`; per-PR preview hostname is `api-<childZonePrefix>.<zone>`.
|
|
3069
|
+
*/
|
|
3070
|
+
_OpenHiRestApiService.API_DOMAIN_PREFIX = "api";
|
|
3071
|
+
var OpenHiRestApiService = _OpenHiRestApiService;
|
|
3072
|
+
|
|
3073
|
+
// src/services/open-hi-website-service.ts
|
|
3074
|
+
var SSM_PARAM_NAME_FULL_DOMAIN = "WEBSITE_FULL_DOMAIN";
|
|
3075
|
+
var ADMIN_DOMAIN_PREFIX = "admin";
|
|
3076
|
+
var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
|
|
3077
|
+
/**
|
|
3078
|
+
* Compose the website's full per-deploy domain. Thin wrapper over
|
|
3079
|
+
* {@link OpenHiService.composeServiceDomain} that fills in
|
|
3080
|
+
* {@link DEFAULT_DOMAIN_PREFIX} when `domainPrefix` is omitted.
|
|
3081
|
+
*
|
|
3082
|
+
* Use from sibling stacks that need to predict the website's hostname
|
|
3083
|
+
* before the website stack is synthesised — e.g. the REST API stack
|
|
3084
|
+
* computing its CORS `allowOrigins` for the admin-console.
|
|
3085
|
+
*/
|
|
3086
|
+
static composeFullDomain(opts) {
|
|
3087
|
+
return OpenHiService.composeServiceDomain({
|
|
3088
|
+
...opts,
|
|
3089
|
+
domainPrefix: opts.domainPrefix ?? _OpenHiWebsiteService.DEFAULT_DOMAIN_PREFIX
|
|
3090
|
+
});
|
|
3091
|
+
}
|
|
3092
|
+
/**
|
|
3093
|
+
* Looks up the static-hosting bucket ARN published by the release-branch
|
|
3094
|
+
* deploy of this service.
|
|
3095
|
+
*/
|
|
3096
|
+
static bucketArnFromConstruct(scope) {
|
|
3097
|
+
return DiscoverableStringParameter.valueForLookupName(scope, {
|
|
3098
|
+
ssmParamName: StaticHosting.SSM_PARAM_NAME_BUCKET_ARN,
|
|
3099
|
+
serviceType: _OpenHiWebsiteService.SERVICE_TYPE
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
3102
|
+
/**
|
|
3103
|
+
* Looks up the CloudFront distribution ARN published by the release-branch
|
|
3104
|
+
* deploy of this service.
|
|
3105
|
+
*/
|
|
3106
|
+
static distributionArnFromConstruct(scope) {
|
|
3107
|
+
return DiscoverableStringParameter.valueForLookupName(scope, {
|
|
3108
|
+
ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_ARN,
|
|
3109
|
+
serviceType: _OpenHiWebsiteService.SERVICE_TYPE
|
|
3110
|
+
});
|
|
3111
|
+
}
|
|
3112
|
+
/**
|
|
3113
|
+
* Looks up the CloudFront distribution domain
|
|
3114
|
+
* (e.g. dXXXXX.cloudfront.net) published by the release-branch deploy.
|
|
3115
|
+
*/
|
|
3116
|
+
static distributionDomainFromConstruct(scope) {
|
|
3117
|
+
return DiscoverableStringParameter.valueForLookupName(scope, {
|
|
3118
|
+
ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_DOMAIN,
|
|
3119
|
+
serviceType: _OpenHiWebsiteService.SERVICE_TYPE
|
|
3120
|
+
});
|
|
3121
|
+
}
|
|
3122
|
+
/**
|
|
3123
|
+
* Looks up the CloudFront distribution ID published by the release-branch
|
|
3124
|
+
* deploy of this service.
|
|
3125
|
+
*/
|
|
3126
|
+
static distributionIdFromConstruct(scope) {
|
|
3127
|
+
return DiscoverableStringParameter.valueForLookupName(scope, {
|
|
3128
|
+
ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_ID,
|
|
3129
|
+
serviceType: _OpenHiWebsiteService.SERVICE_TYPE
|
|
3130
|
+
});
|
|
3131
|
+
}
|
|
3132
|
+
/**
|
|
3133
|
+
* Looks up the website's full domain (e.g. www.example.com) published by
|
|
3134
|
+
* the release-branch deploy of this service.
|
|
3135
|
+
*/
|
|
3136
|
+
static fullDomainFromConstruct(scope) {
|
|
3137
|
+
return DiscoverableStringParameter.valueForLookupName(scope, {
|
|
3138
|
+
ssmParamName: SSM_PARAM_NAME_FULL_DOMAIN,
|
|
3139
|
+
serviceType: _OpenHiWebsiteService.SERVICE_TYPE
|
|
3140
|
+
});
|
|
3141
|
+
}
|
|
3142
|
+
get serviceType() {
|
|
3143
|
+
return _OpenHiWebsiteService.SERVICE_TYPE;
|
|
3144
|
+
}
|
|
3145
|
+
constructor(ohEnv, props) {
|
|
3146
|
+
super(ohEnv, _OpenHiWebsiteService.SERVICE_TYPE, props);
|
|
3147
|
+
this.props = props;
|
|
3148
|
+
this.validateConfig(props);
|
|
3149
|
+
const isReleaseBranch = this.branchName === this.defaultReleaseBranch;
|
|
3150
|
+
const hostedZone = this.createHostedZone();
|
|
3151
|
+
this.fullDomain = this.computeFullDomain(hostedZone);
|
|
3152
|
+
const shouldCreateHostingInfra = props.createHostingInfrastructure ?? isReleaseBranch;
|
|
3153
|
+
if (shouldCreateHostingInfra) {
|
|
3154
|
+
const certificate = this.createCertificate();
|
|
3155
|
+
this.staticHosting = this.createStaticHosting({
|
|
3156
|
+
certificate,
|
|
3157
|
+
hostedZone
|
|
3158
|
+
});
|
|
3159
|
+
this.createFullDomainParameter();
|
|
3160
|
+
} else if (!isReleaseBranch) {
|
|
3161
|
+
this.perBranchHostname = this.createPerBranchHostname(hostedZone);
|
|
3162
|
+
}
|
|
3163
|
+
if (props.createStaticContent !== false) {
|
|
3164
|
+
const bucket = this.resolveStaticHostingBucket();
|
|
3165
|
+
this.staticContent = this.createStaticContent(bucket);
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
/**
|
|
3169
|
+
* Validates that config required for the website stack is present.
|
|
3170
|
+
*/
|
|
3171
|
+
validateConfig(props) {
|
|
3172
|
+
const { config } = props;
|
|
3173
|
+
if (!config) {
|
|
3174
|
+
throw new Error("Config is required");
|
|
3175
|
+
}
|
|
3176
|
+
if (!config.zoneName) {
|
|
3177
|
+
throw new Error("Zone name is required");
|
|
3178
|
+
}
|
|
3179
|
+
if (!config.hostedZoneId) {
|
|
3180
|
+
throw new Error("Hosted zone ID is required to import the website zone");
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
/**
|
|
3184
|
+
* Imports the website's hosted zone from config attributes (no SSM lookup).
|
|
3185
|
+
* The website attaches DNS records here on the release-branch deploy and
|
|
3186
|
+
* the same zone is imported on feature-branch deploys for any sub-domain
|
|
3187
|
+
* routing.
|
|
3188
|
+
* Override to customize.
|
|
3189
|
+
*/
|
|
3190
|
+
createHostedZone() {
|
|
3191
|
+
return OpenHiGlobalService.rootHostedZoneFromConstruct(this, {
|
|
3192
|
+
zoneName: this.config.zoneName,
|
|
3193
|
+
hostedZoneId: this.config.hostedZoneId
|
|
3194
|
+
});
|
|
3195
|
+
}
|
|
3196
|
+
/**
|
|
3197
|
+
* Returns the wildcard certificate looked up from the Global service.
|
|
3198
|
+
* Override to customize.
|
|
3199
|
+
*/
|
|
3200
|
+
createCertificate() {
|
|
3201
|
+
return OpenHiGlobalService.rootWildcardCertificateFromConstruct(this);
|
|
3202
|
+
}
|
|
3203
|
+
/**
|
|
3204
|
+
* Computes the full website domain from `domainPrefix`,
|
|
3205
|
+
* `childZonePrefix`, and the child zone name. Release-branch deploys
|
|
3206
|
+
* serve at `\<domainPrefix\>.\<zone\>` (e.g. `admin.dev.openhi.org`);
|
|
3207
|
+
* every other deploy serves a per-PR preview at
|
|
3208
|
+
* `\<domainPrefix\>-\<childZonePrefix\>.\<zone\>`
|
|
3209
|
+
* (e.g. `admin-feat-1093-patient-migration.dev.openhi.org`).
|
|
3210
|
+
*
|
|
3211
|
+
* Delegates to {@link OpenHiWebsiteService.composeFullDomain} so the
|
|
3212
|
+
* release-vs-feature composition stays in one place.
|
|
3213
|
+
*/
|
|
3214
|
+
computeFullDomain(hostedZone) {
|
|
3215
|
+
return _OpenHiWebsiteService.composeFullDomain({
|
|
3216
|
+
domainPrefix: this.props.domainPrefix,
|
|
3217
|
+
branchName: this.branchName,
|
|
3218
|
+
defaultReleaseBranch: this.defaultReleaseBranch,
|
|
3219
|
+
childZonePrefix: this.childZonePrefix,
|
|
3220
|
+
zoneName: hostedZone.zoneName
|
|
3221
|
+
});
|
|
3222
|
+
}
|
|
3223
|
+
/**
|
|
3224
|
+
* Returns the sub-domain label (left of the zone) for the current
|
|
3225
|
+
* deploy. Used for the per-branch S3 key prefix passed to
|
|
3226
|
+
* {@link StaticContent} so the upload prefix always matches the
|
|
3227
|
+
* served hostname.
|
|
3228
|
+
*
|
|
3229
|
+
* Non-release deploys compose the per-PR slug as
|
|
3230
|
+
* `\<domainPrefix\>-\<childZonePrefix\>`, mirroring the REST API's
|
|
3231
|
+
* `api-\<childZonePrefix\>` convention. When `domainPrefix` is `admin`
|
|
3232
|
+
* (the only consumer today), the resulting sub-domain starts with
|
|
3233
|
+
* {@link PER_BRANCH_PREVIEW_PREFIX}, so the per-PR S3 key prefix
|
|
3234
|
+
* matches what `StaticHosting`'s lifecycle rule expires.
|
|
3235
|
+
*/
|
|
3236
|
+
computeSubDomain() {
|
|
3237
|
+
const isReleaseBranch = this.branchName === this.defaultReleaseBranch;
|
|
3238
|
+
const domainPrefix = this.props.domainPrefix ?? _OpenHiWebsiteService.DEFAULT_DOMAIN_PREFIX;
|
|
3239
|
+
if (isReleaseBranch) {
|
|
3240
|
+
return domainPrefix;
|
|
3241
|
+
}
|
|
3242
|
+
return `${domainPrefix}-${this.childZonePrefix}`;
|
|
3243
|
+
}
|
|
3244
|
+
/**
|
|
3245
|
+
* Creates the StaticHosting infrastructure (bucket + distribution +
|
|
3246
|
+
* Lambda@Edge + 4 SSM params + DNS). The release-branch distribution
|
|
3247
|
+
* adds `*.\<zone\>` as a wildcard alt-name on top of the canonical
|
|
3248
|
+
* hostname so per-PR previews resolve via the same distribution.
|
|
3249
|
+
*
|
|
3250
|
+
* The bucket carries an S3 lifecycle rule that expires per-PR
|
|
3251
|
+
* preview content (keys under {@link PER_BRANCH_PREVIEW_PREFIX})
|
|
3252
|
+
* on non-production stages. PROD never gets the rule — see
|
|
3253
|
+
* `enablePreviewLifecycle`.
|
|
3254
|
+
*/
|
|
3255
|
+
createStaticHosting(deps) {
|
|
3256
|
+
const restApi = this.props.restApi === true ? this.resolveRestApi() : void 0;
|
|
3257
|
+
const wildcardSan = `*.${deps.hostedZone.zoneName}`;
|
|
3258
|
+
return new StaticHosting(this, "static-hosting", {
|
|
3259
|
+
serviceType: _OpenHiWebsiteService.SERVICE_TYPE,
|
|
3260
|
+
certificate: deps.certificate,
|
|
3261
|
+
hostedZone: deps.hostedZone,
|
|
3262
|
+
domainNames: [this.fullDomain, wildcardSan],
|
|
3263
|
+
description: `OpenHI website (${this.fullDomain})`,
|
|
3264
|
+
prefixPattern: PER_BRANCH_PREVIEW_PREFIX,
|
|
3265
|
+
enablePreviewLifecycle: this.ohEnv.ohStage.stageType !== import_config6.OPEN_HI_STAGE.PROD,
|
|
3266
|
+
...restApi !== void 0 && { restApi }
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
3269
|
+
/**
|
|
3270
|
+
* Resolves the REST API custom-domain hostname from the rest-api stack's
|
|
3271
|
+
* `REST_API_DOMAIN_NAME` SSM parameter. Wrapped in a private method so
|
|
3272
|
+
* it can be overridden / stubbed in subclasses and tests.
|
|
3273
|
+
*/
|
|
3274
|
+
resolveRestApi() {
|
|
3275
|
+
return {
|
|
3276
|
+
domainName: OpenHiRestApiService.restApiDomainNameFromConstruct(this)
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
/**
|
|
3280
|
+
* Creates the SSM parameter that publishes the website's full domain.
|
|
3281
|
+
* Look up via {@link OpenHiWebsiteService.fullDomainFromConstruct}.
|
|
3282
|
+
*/
|
|
3283
|
+
createFullDomainParameter() {
|
|
3284
|
+
new DiscoverableStringParameter(this, "full-domain-param", {
|
|
3285
|
+
ssmParamName: SSM_PARAM_NAME_FULL_DOMAIN,
|
|
3286
|
+
serviceType: _OpenHiWebsiteService.SERVICE_TYPE,
|
|
3287
|
+
stringValue: this.fullDomain,
|
|
3288
|
+
description: "Full website domain (e.g. www.example.com)"
|
|
3289
|
+
});
|
|
3290
|
+
}
|
|
3291
|
+
/**
|
|
3292
|
+
* Creates the StaticContent uploader. Receives the resolved static-hosting
|
|
3293
|
+
* bucket from the constructor — on the release-branch deploy this is the
|
|
3294
|
+
* just-created {@link staticHosting} bucket (no SSM round-trip within a
|
|
3295
|
+
* single stack); on every other deploy it is imported from the bucket ARN
|
|
3296
|
+
* the release-branch deploy publishes to SSM, addressed against
|
|
3297
|
+
* {@link OpenHiService.releaseBranchHash}. See
|
|
3298
|
+
* {@link resolveStaticHostingBucket}.
|
|
3299
|
+
*
|
|
3300
|
+
* The S3 key prefix is `\<sub-domain\>.\<zone\>/\<contentDest\>` so the
|
|
3301
|
+
* upload location matches the Host-header-derived folder the Lambda@Edge
|
|
3302
|
+
* viewer-request handler prepends. Passing the zone name (rather than
|
|
3303
|
+
* `this.fullDomain`) for the `fullDomain` prop keeps the prefix flat —
|
|
3304
|
+
* `admin-feat-foo.dev.openhi.org/`, not
|
|
3305
|
+
* `admin-feat-foo.admin.dev.openhi.org/`.
|
|
3306
|
+
*/
|
|
3307
|
+
createStaticContent(bucket) {
|
|
3308
|
+
const { contentSourceDirectory, contentDestinationDirectory } = this.props;
|
|
3309
|
+
return new StaticContent(this, "static-content", {
|
|
3310
|
+
bucket,
|
|
3311
|
+
contentSourceDirectory,
|
|
3312
|
+
contentDestinationDirectory,
|
|
3313
|
+
subDomain: this.computeSubDomain(),
|
|
3314
|
+
fullDomain: this.config.zoneName
|
|
3315
|
+
});
|
|
3316
|
+
}
|
|
3317
|
+
/**
|
|
3318
|
+
* Creates the per-PR `PerBranchHostname` alias record on non-release
|
|
3319
|
+
* branch deploys. The record points
|
|
3320
|
+
* `\<domainPrefix\>-\<childZonePrefix\>.\<zone\>` at the release-branch
|
|
3321
|
+
* CloudFront distribution (resolved from SSM against
|
|
3322
|
+
* {@link OpenHiService.releaseBranchHash}).
|
|
3323
|
+
*/
|
|
3324
|
+
createPerBranchHostname(hostedZone) {
|
|
3325
|
+
return new PerBranchHostname(this, "per-branch-hostname", {
|
|
3326
|
+
hostname: this.fullDomain,
|
|
3327
|
+
hostedZone,
|
|
3328
|
+
serviceType: _OpenHiWebsiteService.SERVICE_TYPE
|
|
3329
|
+
});
|
|
3330
|
+
}
|
|
3331
|
+
/**
|
|
3332
|
+
* Returns an {@link IBucket} pointing at the static-hosting bucket the
|
|
3333
|
+
* uploaders write to. On the release-branch deploy this is the bucket
|
|
3334
|
+
* just provisioned by {@link staticHosting}; on every other deploy it's
|
|
3335
|
+
* imported from the bucket ARN the release-branch deploy publishes to
|
|
3336
|
+
* SSM, addressed against {@link OpenHiService.releaseBranchHash}.
|
|
3337
|
+
*/
|
|
3338
|
+
resolveStaticHostingBucket() {
|
|
3339
|
+
if (this.staticHosting) {
|
|
3340
|
+
return this.staticHosting.bucket;
|
|
3341
|
+
}
|
|
3342
|
+
const bucketArn = DiscoverableStringParameter.valueForLookupName(this, {
|
|
3343
|
+
ssmParamName: StaticHosting.SSM_PARAM_NAME_BUCKET_ARN,
|
|
3344
|
+
serviceType: _OpenHiWebsiteService.SERVICE_TYPE,
|
|
3345
|
+
branchHash: this.releaseBranchHash
|
|
3346
|
+
});
|
|
3347
|
+
return Bucket3.fromBucketArn(this, "shared-bucket", bucketArn);
|
|
3348
|
+
}
|
|
3349
|
+
};
|
|
3350
|
+
_OpenHiWebsiteService.SERVICE_TYPE = "website";
|
|
3351
|
+
/**
|
|
3352
|
+
* Default `domainPrefix` for this service when none is supplied.
|
|
3353
|
+
* Release-branch hostname is `www.<zone>`; per-PR preview hostname is
|
|
3354
|
+
* `www-<childZonePrefix>.<zone>`.
|
|
3355
|
+
*/
|
|
3356
|
+
_OpenHiWebsiteService.DEFAULT_DOMAIN_PREFIX = "www";
|
|
3357
|
+
var OpenHiWebsiteService = _OpenHiWebsiteService;
|
|
3358
|
+
|
|
3359
|
+
// src/workflows/control-plane/user-onboarding/provision-default-workspace-lambda.ts
|
|
3360
|
+
import fs12 from "fs";
|
|
3361
|
+
import path12 from "path";
|
|
3362
|
+
import { Duration as Duration10 } from "aws-cdk-lib";
|
|
3363
|
+
import { Rule as Rule4 } from "aws-cdk-lib/aws-events";
|
|
3364
|
+
import { LambdaFunction as LambdaFunction4 } from "aws-cdk-lib/aws-events-targets";
|
|
3365
|
+
import { Effect as Effect6, PolicyStatement as PolicyStatement6 } from "aws-cdk-lib/aws-iam";
|
|
3366
|
+
import { Runtime as Runtime12 } from "aws-cdk-lib/aws-lambda";
|
|
3367
|
+
import { NodejsFunction as NodejsFunction12 } from "aws-cdk-lib/aws-lambda-nodejs";
|
|
3368
|
+
import { Construct as Construct20 } from "constructs";
|
|
3369
|
+
var HANDLER_NAME11 = "provision-default-workspace.handler.js";
|
|
3370
|
+
function resolveHandlerEntry11(dirname) {
|
|
3371
|
+
const sameDir = path12.join(dirname, HANDLER_NAME11);
|
|
3372
|
+
if (fs12.existsSync(sameDir)) {
|
|
3373
|
+
return sameDir;
|
|
3374
|
+
}
|
|
3375
|
+
return path12.join(dirname, "..", "..", "..", "..", "lib", HANDLER_NAME11);
|
|
3376
|
+
}
|
|
3377
|
+
var ProvisionDefaultWorkspaceLambda = class extends Construct20 {
|
|
3378
|
+
constructor(scope, props) {
|
|
3379
|
+
super(scope, "provision-default-workspace-lambda");
|
|
3380
|
+
this.lambda = new NodejsFunction12(this, "handler", {
|
|
3381
|
+
entry: resolveHandlerEntry11(__dirname),
|
|
3382
|
+
runtime: Runtime12.NODEJS_LATEST,
|
|
3383
|
+
memorySize: 1024,
|
|
3384
|
+
environment: {
|
|
3385
|
+
DYNAMO_TABLE_NAME: props.dataStoreTable.tableName
|
|
3386
|
+
}
|
|
3387
|
+
});
|
|
3388
|
+
props.dataStoreTable.grant(
|
|
3389
|
+
this.lambda,
|
|
3390
|
+
"dynamodb:PutItem",
|
|
3391
|
+
"dynamodb:UpdateItem"
|
|
3392
|
+
);
|
|
3393
|
+
this.lambda.addToRolePolicy(
|
|
3394
|
+
new PolicyStatement6({
|
|
3395
|
+
effect: Effect6.ALLOW,
|
|
3396
|
+
actions: ["dynamodb:Query"],
|
|
3397
|
+
resources: [`${props.dataStoreTable.tableArn}/index/*`]
|
|
3398
|
+
})
|
|
3399
|
+
);
|
|
3400
|
+
this.rule = new Rule4(this, "rule", {
|
|
3401
|
+
eventBus: props.controlEventBus,
|
|
3402
|
+
eventPattern: {
|
|
3403
|
+
source: [USER_ONBOARDING_EVENT_SOURCE],
|
|
3404
|
+
detailType: [PROVISION_DEFAULT_WORKSPACE_DETAIL_TYPE]
|
|
3405
|
+
},
|
|
3406
|
+
targets: [
|
|
3407
|
+
new LambdaFunction4(this.lambda, {
|
|
3408
|
+
retryAttempts: 2,
|
|
3409
|
+
maxEventAge: Duration10.hours(2)
|
|
3410
|
+
})
|
|
3411
|
+
]
|
|
3412
|
+
});
|
|
3413
|
+
}
|
|
3414
|
+
};
|
|
3415
|
+
|
|
3416
|
+
// src/workflows/control-plane/user-onboarding/user-onboarding-workflow.ts
|
|
3417
|
+
import { Construct as Construct21 } from "constructs";
|
|
3418
|
+
var UserOnboardingWorkflow = class extends Construct21 {
|
|
3419
|
+
constructor(scope, props) {
|
|
3420
|
+
super(scope, "user-onboarding-workflow");
|
|
3421
|
+
this.provisionDefaultWorkspace = new ProvisionDefaultWorkspaceLambda(this, {
|
|
3422
|
+
dataStoreTable: props.dataStoreTable,
|
|
3423
|
+
controlEventBus: props.controlEventBus
|
|
3424
|
+
});
|
|
3425
|
+
}
|
|
3426
|
+
};
|
|
3427
|
+
|
|
3428
|
+
// src/services/open-hi-auth-service.ts
|
|
3429
|
+
var LOCALHOST_OAUTH_CALLBACK_URLS = [
|
|
3430
|
+
"http://localhost:3000/oauth/callback",
|
|
3431
|
+
"https://localhost:3000/oauth/callback"
|
|
3432
|
+
];
|
|
3433
|
+
var LOCALHOST_OAUTH_LOGOUT_URLS = [
|
|
3434
|
+
"http://localhost:3000/oauth/logout",
|
|
3435
|
+
"https://localhost:3000/oauth/logout"
|
|
3436
|
+
];
|
|
3437
|
+
var _OpenHiAuthService = class _OpenHiAuthService extends OpenHiService {
|
|
3438
|
+
constructor(ohEnv, props = {}) {
|
|
3439
|
+
super(ohEnv, _OpenHiAuthService.SERVICE_TYPE, props);
|
|
3440
|
+
/**
|
|
3441
|
+
* Cross-stack reference to the data store table. Cached so repeated
|
|
3442
|
+
* lookups share a single CDK construct id ("dynamo-db-data-store") in
|
|
3443
|
+
* this stack — a second `Table.fromTableName` call under the same scope
|
|
3444
|
+
* would collide.
|
|
3445
|
+
*/
|
|
3446
|
+
this._dataStoreTable = null;
|
|
3447
|
+
this._controlEventBus = null;
|
|
3448
|
+
this.props = props;
|
|
3449
|
+
this.userPoolKmsKey = this.createUserPoolKmsKey();
|
|
3450
|
+
this.preTokenGenerationLambda = this.createPreTokenGenerationLambda();
|
|
3451
|
+
this.postAuthenticationLambda = this.createPostAuthenticationLambda();
|
|
3452
|
+
this.postConfirmationLambda = this.createPostConfirmationLambda();
|
|
3453
|
+
this.userOnboardingWorkflow = this.createUserOnboardingWorkflow();
|
|
3454
|
+
this.userPool = this.createUserPool();
|
|
3455
|
+
this.grantPreTokenGenerationPermissions();
|
|
3456
|
+
this.grantPostAuthenticationPermissions();
|
|
3457
|
+
this.grantPostConfirmationPermissions();
|
|
3458
|
+
this.userPoolClient = this.createUserPoolClient();
|
|
3459
|
+
this.userPoolDomain = this.createUserPoolDomain();
|
|
3460
|
+
}
|
|
3405
3461
|
/**
|
|
3406
|
-
* Returns
|
|
3407
|
-
* Use from other stacks to obtain an IGraphqlApi reference.
|
|
3462
|
+
* Returns an IUserPool by looking up the Auth stack's User Pool ID from SSM.
|
|
3408
3463
|
*/
|
|
3409
|
-
static
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
constructor(ohEnv, props = {}) {
|
|
3416
|
-
super(ohEnv, _OpenHiGraphqlService.SERVICE_TYPE, props);
|
|
3417
|
-
this.props = props;
|
|
3418
|
-
this.rootGraphqlApi = this.createRootGraphqlApi();
|
|
3464
|
+
static userPoolFromConstruct(scope) {
|
|
3465
|
+
const userPoolId = DiscoverableStringParameter.valueForLookupName(scope, {
|
|
3466
|
+
ssmParamName: CognitoUserPool.SSM_PARAM_NAME,
|
|
3467
|
+
serviceType: _OpenHiAuthService.SERVICE_TYPE
|
|
3468
|
+
});
|
|
3469
|
+
return UserPool2.fromUserPoolId(scope, "user-pool", userPoolId);
|
|
3419
3470
|
}
|
|
3420
|
-
/**
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
defaultAction: UserPoolDefaultAction.ALLOW
|
|
3430
|
-
}
|
|
3431
|
-
}
|
|
3471
|
+
/**
|
|
3472
|
+
* Returns an IUserPoolClient by looking up the Auth stack's User Pool Client ID from SSM.
|
|
3473
|
+
*/
|
|
3474
|
+
static userPoolClientFromConstruct(scope) {
|
|
3475
|
+
const userPoolClientId = DiscoverableStringParameter.valueForLookupName(
|
|
3476
|
+
scope,
|
|
3477
|
+
{
|
|
3478
|
+
ssmParamName: CognitoUserPoolClient.SSM_PARAM_NAME,
|
|
3479
|
+
serviceType: _OpenHiAuthService.SERVICE_TYPE
|
|
3432
3480
|
}
|
|
3481
|
+
);
|
|
3482
|
+
return UserPoolClient2.fromUserPoolClientId(
|
|
3483
|
+
scope,
|
|
3484
|
+
"user-pool-client",
|
|
3485
|
+
userPoolClientId
|
|
3486
|
+
);
|
|
3487
|
+
}
|
|
3488
|
+
/**
|
|
3489
|
+
* Returns an IUserPoolDomain by looking up the Auth stack's User Pool Domain from SSM.
|
|
3490
|
+
*/
|
|
3491
|
+
static userPoolDomainFromConstruct(scope) {
|
|
3492
|
+
const domainName = DiscoverableStringParameter.valueForLookupName(scope, {
|
|
3493
|
+
ssmParamName: CognitoUserPoolDomain.SSM_PARAM_NAME,
|
|
3494
|
+
serviceType: _OpenHiAuthService.SERVICE_TYPE
|
|
3433
3495
|
});
|
|
3496
|
+
return UserPoolDomain2.fromDomainName(scope, "user-pool-domain", domainName);
|
|
3434
3497
|
}
|
|
3435
|
-
};
|
|
3436
|
-
_OpenHiGraphqlService.SERVICE_TYPE = "graphql-api";
|
|
3437
|
-
var OpenHiGraphqlService = _OpenHiGraphqlService;
|
|
3438
|
-
|
|
3439
|
-
// src/services/open-hi-website-service.ts
|
|
3440
|
-
var import_config6 = __toESM(require_lib2());
|
|
3441
|
-
import { Bucket as Bucket3 } from "aws-cdk-lib/aws-s3";
|
|
3442
|
-
var SSM_PARAM_NAME_FULL_DOMAIN = "WEBSITE_FULL_DOMAIN";
|
|
3443
|
-
var ADMIN_DOMAIN_PREFIX = "admin";
|
|
3444
|
-
var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
|
|
3445
3498
|
/**
|
|
3446
|
-
*
|
|
3447
|
-
*
|
|
3448
|
-
*
|
|
3499
|
+
* Returns the full Cognito Hosted UI base URL (e.g.
|
|
3500
|
+
* `https://auth-abc.auth.us-east-2.amazoncognito.com`) by looking up
|
|
3501
|
+
* the Auth stack's User Pool Domain from SSM and composing it with the
|
|
3502
|
+
* calling stack's region.
|
|
3449
3503
|
*
|
|
3450
|
-
*
|
|
3451
|
-
*
|
|
3452
|
-
*
|
|
3504
|
+
* Equivalent to `UserPoolDomain.baseUrl()` on the concrete construct,
|
|
3505
|
+
* but works across stacks where the looked-up `IUserPoolDomain` is an
|
|
3506
|
+
* `Import` and does not carry the `baseUrl()` method. Assumes the
|
|
3507
|
+
* domain was created as a Cognito-managed prefix domain (the only
|
|
3508
|
+
* variant `OpenHiAuthService.createUserPoolDomain` produces).
|
|
3453
3509
|
*/
|
|
3454
|
-
static
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3510
|
+
static userPoolDomainBaseUrlFromConstruct(scope) {
|
|
3511
|
+
const domainName = DiscoverableStringParameter.valueForLookupName(scope, {
|
|
3512
|
+
ssmParamName: CognitoUserPoolDomain.SSM_PARAM_NAME,
|
|
3513
|
+
serviceType: _OpenHiAuthService.SERVICE_TYPE
|
|
3458
3514
|
});
|
|
3515
|
+
const region = Stack7.of(scope).region;
|
|
3516
|
+
return `https://${domainName}.auth.${region}.amazoncognito.com`;
|
|
3459
3517
|
}
|
|
3460
3518
|
/**
|
|
3461
|
-
*
|
|
3462
|
-
* deploy of this service.
|
|
3519
|
+
* Returns an IKey (KMS) by looking up the Auth stack's User Pool KMS Key ARN from SSM.
|
|
3463
3520
|
*/
|
|
3464
|
-
static
|
|
3465
|
-
|
|
3466
|
-
ssmParamName:
|
|
3467
|
-
serviceType:
|
|
3521
|
+
static userPoolKmsKeyFromConstruct(scope) {
|
|
3522
|
+
const keyArn = DiscoverableStringParameter.valueForLookupName(scope, {
|
|
3523
|
+
ssmParamName: CognitoUserPoolKmsKey.SSM_PARAM_NAME,
|
|
3524
|
+
serviceType: _OpenHiAuthService.SERVICE_TYPE
|
|
3468
3525
|
});
|
|
3526
|
+
return Key2.fromKeyArn(scope, "kms-key", keyArn);
|
|
3527
|
+
}
|
|
3528
|
+
get serviceType() {
|
|
3529
|
+
return _OpenHiAuthService.SERVICE_TYPE;
|
|
3469
3530
|
}
|
|
3470
3531
|
/**
|
|
3471
|
-
*
|
|
3472
|
-
*
|
|
3532
|
+
* Creates the KMS key for the Cognito User Pool and exports its ARN to SSM.
|
|
3533
|
+
* Look up via {@link OpenHiAuthService.userPoolKmsKeyFromConstruct}.
|
|
3534
|
+
* Override to customize.
|
|
3473
3535
|
*/
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3536
|
+
createUserPoolKmsKey() {
|
|
3537
|
+
const key = new CognitoUserPoolKmsKey(this);
|
|
3538
|
+
new DiscoverableStringParameter(this, "kms-key-param", {
|
|
3539
|
+
ssmParamName: CognitoUserPoolKmsKey.SSM_PARAM_NAME,
|
|
3540
|
+
stringValue: key.keyArn,
|
|
3541
|
+
description: "KMS key ARN for Cognito User Pool (e.g. custom sender); cross-stack reference"
|
|
3478
3542
|
});
|
|
3543
|
+
return key;
|
|
3479
3544
|
}
|
|
3480
3545
|
/**
|
|
3481
|
-
*
|
|
3482
|
-
*
|
|
3546
|
+
* Creates the Pre Token Generation Lambda (Cognito trigger). On every
|
|
3547
|
+
* sign-in and token refresh the Lambda resolves the User by Cognito `sub`
|
|
3548
|
+
* (GSI2) and injects `ohi_tid`, `ohi_wid`, `ohi_uid`, `ohi_uname` into
|
|
3549
|
+
* both the ID token and the access token (ADR 2026-03-17-01).
|
|
3483
3550
|
*/
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
serviceType: _OpenHiWebsiteService.SERVICE_TYPE
|
|
3551
|
+
createPreTokenGenerationLambda() {
|
|
3552
|
+
const construct = new PreTokenGenerationLambda(this, {
|
|
3553
|
+
dynamoTableName: this.dataStoreTable().tableName
|
|
3488
3554
|
});
|
|
3555
|
+
return construct.lambda;
|
|
3489
3556
|
}
|
|
3490
3557
|
/**
|
|
3491
|
-
*
|
|
3492
|
-
*
|
|
3558
|
+
* Creates the Post Authentication Lambda (Cognito trigger). Calls
|
|
3559
|
+
* AdminUserGlobalSignOut on every sign-in to enforce single-device-per-user
|
|
3560
|
+
* sessions per ADR 2026-03-17-01.
|
|
3493
3561
|
*/
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
serviceType: _OpenHiWebsiteService.SERVICE_TYPE
|
|
3498
|
-
});
|
|
3562
|
+
createPostAuthenticationLambda() {
|
|
3563
|
+
const construct = new PostAuthenticationLambda(this);
|
|
3564
|
+
return construct.lambda;
|
|
3499
3565
|
}
|
|
3500
3566
|
/**
|
|
3501
|
-
*
|
|
3502
|
-
*
|
|
3567
|
+
* Creates the Post Confirmation Lambda (Cognito trigger). On sign-up
|
|
3568
|
+
* confirmation, publishes a control-plane workflow event; provisioning lives
|
|
3569
|
+
* behind EventBridge.
|
|
3503
3570
|
*/
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
serviceType: _OpenHiWebsiteService.SERVICE_TYPE
|
|
3571
|
+
createPostConfirmationLambda() {
|
|
3572
|
+
const construct = new PostConfirmationLambda(this, {
|
|
3573
|
+
controlEventBusName: this.controlEventBus().eventBusName
|
|
3508
3574
|
});
|
|
3575
|
+
return construct.lambda;
|
|
3509
3576
|
}
|
|
3510
|
-
|
|
3511
|
-
return
|
|
3577
|
+
createUserOnboardingWorkflow() {
|
|
3578
|
+
return new UserOnboardingWorkflow(this, {
|
|
3579
|
+
controlEventBus: this.controlEventBus(),
|
|
3580
|
+
dataStoreTable: this.dataStoreTable()
|
|
3581
|
+
});
|
|
3512
3582
|
}
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
this.validateConfig(props);
|
|
3517
|
-
const isReleaseBranch = this.branchName === this.defaultReleaseBranch;
|
|
3518
|
-
const hostedZone = this.createHostedZone();
|
|
3519
|
-
this.fullDomain = this.computeFullDomain(hostedZone);
|
|
3520
|
-
const shouldCreateHostingInfra = props.createHostingInfrastructure ?? isReleaseBranch;
|
|
3521
|
-
if (shouldCreateHostingInfra) {
|
|
3522
|
-
const certificate = this.createCertificate();
|
|
3523
|
-
this.staticHosting = this.createStaticHosting({
|
|
3524
|
-
certificate,
|
|
3525
|
-
hostedZone
|
|
3526
|
-
});
|
|
3527
|
-
this.createFullDomainParameter();
|
|
3528
|
-
} else if (!isReleaseBranch) {
|
|
3529
|
-
this.perBranchHostname = this.createPerBranchHostname(hostedZone);
|
|
3530
|
-
}
|
|
3531
|
-
if (props.createStaticContent !== false) {
|
|
3532
|
-
const bucket = this.resolveStaticHostingBucket();
|
|
3533
|
-
this.staticContent = this.createStaticContent(bucket);
|
|
3583
|
+
dataStoreTable() {
|
|
3584
|
+
if (this._dataStoreTable === null) {
|
|
3585
|
+
this._dataStoreTable = OpenHiDataService.dynamoDbDataStoreFromConstruct(this);
|
|
3534
3586
|
}
|
|
3587
|
+
return this._dataStoreTable;
|
|
3535
3588
|
}
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
validateConfig(props) {
|
|
3540
|
-
const { config } = props;
|
|
3541
|
-
if (!config) {
|
|
3542
|
-
throw new Error("Config is required");
|
|
3543
|
-
}
|
|
3544
|
-
if (!config.zoneName) {
|
|
3545
|
-
throw new Error("Zone name is required");
|
|
3546
|
-
}
|
|
3547
|
-
if (!config.hostedZoneId) {
|
|
3548
|
-
throw new Error("Hosted zone ID is required to import the website zone");
|
|
3589
|
+
controlEventBus() {
|
|
3590
|
+
if (this._controlEventBus === null) {
|
|
3591
|
+
this._controlEventBus = OpenHiGlobalService.controlEventBusFromConstruct(this);
|
|
3549
3592
|
}
|
|
3593
|
+
return this._controlEventBus;
|
|
3550
3594
|
}
|
|
3551
3595
|
/**
|
|
3552
|
-
*
|
|
3553
|
-
*
|
|
3554
|
-
* the same zone is imported on feature-branch deploys for any sub-domain
|
|
3555
|
-
* routing.
|
|
3596
|
+
* Creates the Cognito User Pool and exports its ID to SSM.
|
|
3597
|
+
* Look up via {@link OpenHiAuthService.userPoolFromConstruct}.
|
|
3556
3598
|
* Override to customize.
|
|
3557
3599
|
*/
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3600
|
+
createUserPool() {
|
|
3601
|
+
const userPool = new CognitoUserPool(this, {
|
|
3602
|
+
...this.props.userPoolProps,
|
|
3603
|
+
customSenderKmsKey: this.userPoolKmsKey
|
|
3604
|
+
});
|
|
3605
|
+
userPool.addTrigger(
|
|
3606
|
+
UserPoolOperation.PRE_TOKEN_GENERATION_CONFIG,
|
|
3607
|
+
this.preTokenGenerationLambda,
|
|
3608
|
+
LambdaVersion.V2_0
|
|
3609
|
+
);
|
|
3610
|
+
userPool.addTrigger(
|
|
3611
|
+
UserPoolOperation.POST_AUTHENTICATION,
|
|
3612
|
+
this.postAuthenticationLambda
|
|
3613
|
+
);
|
|
3614
|
+
userPool.addTrigger(
|
|
3615
|
+
UserPoolOperation.POST_CONFIRMATION,
|
|
3616
|
+
this.postConfirmationLambda
|
|
3617
|
+
);
|
|
3618
|
+
new DiscoverableStringParameter(this, "user-pool-param", {
|
|
3619
|
+
ssmParamName: CognitoUserPool.SSM_PARAM_NAME,
|
|
3620
|
+
stringValue: userPool.userPoolId,
|
|
3621
|
+
description: "Cognito User Pool ID for this Auth stack; cross-stack reference"
|
|
3562
3622
|
});
|
|
3623
|
+
return userPool;
|
|
3563
3624
|
}
|
|
3564
3625
|
/**
|
|
3565
|
-
*
|
|
3566
|
-
*
|
|
3626
|
+
* Grants the Pre Token Generation Lambda read-only access on the data
|
|
3627
|
+
* store table and its GSIs. The Lambda only needs:
|
|
3628
|
+
* - `Query` on GSI2 to resolve a User by Cognito `sub`
|
|
3629
|
+
* - `GetItem` on the base table for direct User reads
|
|
3630
|
+
*
|
|
3631
|
+
* No write or scan access: a User missing `currentTenant`/`currentWorkspace`
|
|
3632
|
+
* falls into the absent-claims path; repair belongs in a separate backfill.
|
|
3567
3633
|
*/
|
|
3568
|
-
|
|
3569
|
-
|
|
3634
|
+
grantPreTokenGenerationPermissions() {
|
|
3635
|
+
const dataStoreTable = this.dataStoreTable();
|
|
3636
|
+
const dynamoActions = ["dynamodb:GetItem", "dynamodb:Query"];
|
|
3637
|
+
dataStoreTable.grant(this.preTokenGenerationLambda, ...dynamoActions);
|
|
3638
|
+
this.preTokenGenerationLambda.addToRolePolicy(
|
|
3639
|
+
new PolicyStatement7({
|
|
3640
|
+
effect: Effect7.ALLOW,
|
|
3641
|
+
actions: [...dynamoActions],
|
|
3642
|
+
resources: [`${dataStoreTable.tableArn}/index/*`]
|
|
3643
|
+
})
|
|
3644
|
+
);
|
|
3570
3645
|
}
|
|
3571
3646
|
/**
|
|
3572
|
-
*
|
|
3573
|
-
* `
|
|
3574
|
-
* serve at `\<domainPrefix\>.\<zone\>` (e.g. `admin.dev.openhi.org`);
|
|
3575
|
-
* every other deploy serves a per-PR preview at
|
|
3576
|
-
* `\<domainPrefix\>-\<childZonePrefix\>.\<zone\>`
|
|
3577
|
-
* (e.g. `admin-feat-1093-patient-migration.dev.openhi.org`).
|
|
3647
|
+
* Grants the Post Authentication Lambda permission to call
|
|
3648
|
+
* `cognito-idp:AdminUserGlobalSignOut`.
|
|
3578
3649
|
*
|
|
3579
|
-
*
|
|
3580
|
-
*
|
|
3650
|
+
* Scoped via `Stack.of(this).formatArn` rather than `userPool.userPoolArn`
|
|
3651
|
+
* because the User Pool registers this Lambda as a Post Authentication
|
|
3652
|
+
* trigger, creating the cycle:
|
|
3653
|
+
* userPool → lambda (trigger ARN) → role policy → userPool ARN.
|
|
3654
|
+
* Using `formatArn` avoids referencing the User Pool resource directly
|
|
3655
|
+
* while still scoping to user pools in this account+region. The Lambda
|
|
3656
|
+
* is invoked only by Cognito with a Cognito-provided `event.userPoolId`,
|
|
3657
|
+
* so the runtime target is constrained by the trigger contract.
|
|
3581
3658
|
*/
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3659
|
+
grantPostAuthenticationPermissions() {
|
|
3660
|
+
this.postAuthenticationLambda.addToRolePolicy(
|
|
3661
|
+
new PolicyStatement7({
|
|
3662
|
+
actions: ["cognito-idp:AdminUserGlobalSignOut"],
|
|
3663
|
+
resources: [
|
|
3664
|
+
Stack7.of(this).formatArn({
|
|
3665
|
+
service: "cognito-idp",
|
|
3666
|
+
resource: "userpool",
|
|
3667
|
+
resourceName: "*"
|
|
3668
|
+
})
|
|
3669
|
+
]
|
|
3670
|
+
})
|
|
3671
|
+
);
|
|
3590
3672
|
}
|
|
3591
3673
|
/**
|
|
3592
|
-
*
|
|
3593
|
-
*
|
|
3594
|
-
* {@link StaticContent} so the upload prefix always matches the
|
|
3595
|
-
* served hostname.
|
|
3596
|
-
*
|
|
3597
|
-
* Non-release deploys compose the per-PR slug as
|
|
3598
|
-
* `\<domainPrefix\>-\<childZonePrefix\>`, mirroring the REST API's
|
|
3599
|
-
* `api-\<childZonePrefix\>` convention. When `domainPrefix` is `admin`
|
|
3600
|
-
* (the only consumer today), the resulting sub-domain starts with
|
|
3601
|
-
* {@link PER_BRANCH_PREVIEW_PREFIX}, so the per-PR S3 key prefix
|
|
3602
|
-
* matches what `StaticHosting`'s lifecycle rule expires.
|
|
3674
|
+
* Grants the Post Confirmation Lambda publish-only access to the
|
|
3675
|
+
* control-plane event bus. Workflow Lambdas own DynamoDB writes.
|
|
3603
3676
|
*/
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
const domainPrefix = this.props.domainPrefix ?? _OpenHiWebsiteService.DEFAULT_DOMAIN_PREFIX;
|
|
3607
|
-
if (isReleaseBranch) {
|
|
3608
|
-
return domainPrefix;
|
|
3609
|
-
}
|
|
3610
|
-
return `${domainPrefix}-${this.childZonePrefix}`;
|
|
3677
|
+
grantPostConfirmationPermissions() {
|
|
3678
|
+
this.controlEventBus().grantPutEventsTo(this.postConfirmationLambda);
|
|
3611
3679
|
}
|
|
3612
3680
|
/**
|
|
3613
|
-
* Creates the
|
|
3614
|
-
*
|
|
3615
|
-
*
|
|
3616
|
-
*
|
|
3617
|
-
*
|
|
3618
|
-
* The bucket carries an S3 lifecycle rule that expires per-PR
|
|
3619
|
-
* preview content (keys under {@link PER_BRANCH_PREVIEW_PREFIX})
|
|
3620
|
-
* on non-production stages. PROD never gets the rule — see
|
|
3621
|
-
* `enablePreviewLifecycle`.
|
|
3681
|
+
* Creates the User Pool Client and exports its ID to SSM (AUTH service type).
|
|
3682
|
+
* OAuth flows are enabled with auto-injected callback/logout URLs derived
|
|
3683
|
+
* from this stack's branch context (see {@link resolveOAuthRedirectUrls}).
|
|
3684
|
+
* Look up via {@link OpenHiAuthService.userPoolClientFromConstruct}.
|
|
3685
|
+
* Override to customize.
|
|
3622
3686
|
*/
|
|
3623
|
-
|
|
3624
|
-
const
|
|
3625
|
-
const
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3687
|
+
createUserPoolClient() {
|
|
3688
|
+
const { callbackUrls, logoutUrls } = this.resolveOAuthRedirectUrls();
|
|
3689
|
+
const client = new CognitoUserPoolClient(this, {
|
|
3690
|
+
userPool: this.userPool,
|
|
3691
|
+
oAuth: {
|
|
3692
|
+
flows: {
|
|
3693
|
+
authorizationCodeGrant: true,
|
|
3694
|
+
implicitCodeGrant: true
|
|
3695
|
+
},
|
|
3696
|
+
callbackUrls,
|
|
3697
|
+
logoutUrls
|
|
3698
|
+
}
|
|
3699
|
+
});
|
|
3700
|
+
new DiscoverableStringParameter(this, "user-pool-client-param", {
|
|
3701
|
+
ssmParamName: CognitoUserPoolClient.SSM_PARAM_NAME,
|
|
3702
|
+
stringValue: client.userPoolClientId,
|
|
3703
|
+
description: "Cognito User Pool Client ID for this Auth stack; cross-stack reference"
|
|
3635
3704
|
});
|
|
3705
|
+
return client;
|
|
3636
3706
|
}
|
|
3637
3707
|
/**
|
|
3638
|
-
*
|
|
3639
|
-
*
|
|
3640
|
-
*
|
|
3708
|
+
* Returns the OAuth `callbackUrls` and `logoutUrls` the Cognito User Pool
|
|
3709
|
+
* Client should accept. Composed from the same branch context the website
|
|
3710
|
+
* service will see at synth time:
|
|
3711
|
+
*
|
|
3712
|
+
* - `https://admin{,-<childZonePrefix>}.<zone>/oauth/{callback,logout}`
|
|
3713
|
+
* - `https://www{,-<childZonePrefix>}.<zone>/oauth/{callback,logout}`
|
|
3714
|
+
*
|
|
3715
|
+
* Both deployed-host pairs are auto-injected on every stage. On non-prod
|
|
3716
|
+
* stages the localhost dev URLs from {@link LOCALHOST_OAUTH_CALLBACK_URLS}
|
|
3717
|
+
* / {@link LOCALHOST_OAUTH_LOGOUT_URLS} join the merge; on prod they are
|
|
3718
|
+
* deliberately excluded.
|
|
3719
|
+
*
|
|
3720
|
+
* If `zoneName` is absent (no-DNS test configurations), the deployed-host
|
|
3721
|
+
* pairs are skipped — only the localhost set survives, and only on
|
|
3722
|
+
* non-prod. Override to customize.
|
|
3641
3723
|
*/
|
|
3642
|
-
|
|
3724
|
+
resolveOAuthRedirectUrls() {
|
|
3725
|
+
const isNonProd = this.ohEnv.ohStage.stageType !== import_config7.OPEN_HI_STAGE.PROD;
|
|
3726
|
+
const zoneName = this.props.config?.zoneName;
|
|
3727
|
+
const deployedOrigins = [];
|
|
3728
|
+
if (zoneName !== void 0) {
|
|
3729
|
+
const adminHost = OpenHiWebsiteService.composeFullDomain({
|
|
3730
|
+
domainPrefix: ADMIN_DOMAIN_PREFIX,
|
|
3731
|
+
branchName: this.branchName,
|
|
3732
|
+
defaultReleaseBranch: this.defaultReleaseBranch,
|
|
3733
|
+
childZonePrefix: this.childZonePrefix,
|
|
3734
|
+
zoneName
|
|
3735
|
+
});
|
|
3736
|
+
const websiteHost = OpenHiWebsiteService.composeFullDomain({
|
|
3737
|
+
branchName: this.branchName,
|
|
3738
|
+
defaultReleaseBranch: this.defaultReleaseBranch,
|
|
3739
|
+
childZonePrefix: this.childZonePrefix,
|
|
3740
|
+
zoneName
|
|
3741
|
+
});
|
|
3742
|
+
deployedOrigins.push(`https://${adminHost}`, `https://${websiteHost}`);
|
|
3743
|
+
}
|
|
3744
|
+
const localhostCallbacks = isNonProd ? LOCALHOST_OAUTH_CALLBACK_URLS : [];
|
|
3745
|
+
const localhostLogouts = isNonProd ? LOCALHOST_OAUTH_LOGOUT_URLS : [];
|
|
3643
3746
|
return {
|
|
3644
|
-
|
|
3747
|
+
callbackUrls: [
|
|
3748
|
+
...deployedOrigins.map((o) => `${o}/oauth/callback`),
|
|
3749
|
+
...localhostCallbacks
|
|
3750
|
+
],
|
|
3751
|
+
logoutUrls: [
|
|
3752
|
+
...deployedOrigins.map((o) => `${o}/oauth/logout`),
|
|
3753
|
+
...localhostLogouts
|
|
3754
|
+
]
|
|
3645
3755
|
};
|
|
3646
3756
|
}
|
|
3647
3757
|
/**
|
|
3648
|
-
* Creates the
|
|
3649
|
-
* Look up via {@link
|
|
3758
|
+
* Creates the User Pool Domain (Cognito hosted UI) and exports domain name to SSM.
|
|
3759
|
+
* Look up via {@link OpenHiAuthService.userPoolDomainFromConstruct}.
|
|
3760
|
+
* Override to customize.
|
|
3650
3761
|
*/
|
|
3651
|
-
|
|
3652
|
-
new
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3762
|
+
createUserPoolDomain() {
|
|
3763
|
+
const domain = new CognitoUserPoolDomain(this, {
|
|
3764
|
+
userPool: this.userPool,
|
|
3765
|
+
cognitoDomain: {
|
|
3766
|
+
domainPrefix: `auth-${this.branchHash}`
|
|
3767
|
+
}
|
|
3657
3768
|
});
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
* just-created {@link staticHosting} bucket (no SSM round-trip within a
|
|
3663
|
-
* single stack); on every other deploy it is imported from the bucket ARN
|
|
3664
|
-
* the release-branch deploy publishes to SSM, addressed against
|
|
3665
|
-
* {@link OpenHiService.releaseBranchHash}. See
|
|
3666
|
-
* {@link resolveStaticHostingBucket}.
|
|
3667
|
-
*
|
|
3668
|
-
* The S3 key prefix is `\<sub-domain\>.\<zone\>/\<contentDest\>` so the
|
|
3669
|
-
* upload location matches the Host-header-derived folder the Lambda@Edge
|
|
3670
|
-
* viewer-request handler prepends. Passing the zone name (rather than
|
|
3671
|
-
* `this.fullDomain`) for the `fullDomain` prop keeps the prefix flat —
|
|
3672
|
-
* `admin-feat-foo.dev.openhi.org/`, not
|
|
3673
|
-
* `admin-feat-foo.admin.dev.openhi.org/`.
|
|
3674
|
-
*/
|
|
3675
|
-
createStaticContent(bucket) {
|
|
3676
|
-
const { contentSourceDirectory, contentDestinationDirectory } = this.props;
|
|
3677
|
-
return new StaticContent(this, "static-content", {
|
|
3678
|
-
bucket,
|
|
3679
|
-
contentSourceDirectory,
|
|
3680
|
-
contentDestinationDirectory,
|
|
3681
|
-
subDomain: this.computeSubDomain(),
|
|
3682
|
-
fullDomain: this.config.zoneName
|
|
3769
|
+
new DiscoverableStringParameter(this, "user-pool-domain-param", {
|
|
3770
|
+
ssmParamName: CognitoUserPoolDomain.SSM_PARAM_NAME,
|
|
3771
|
+
stringValue: domain.domainName,
|
|
3772
|
+
description: "Cognito User Pool Domain (hosted UI) for this Auth stack; cross-stack reference"
|
|
3683
3773
|
});
|
|
3774
|
+
return domain;
|
|
3684
3775
|
}
|
|
3776
|
+
};
|
|
3777
|
+
_OpenHiAuthService.SERVICE_TYPE = "auth";
|
|
3778
|
+
var OpenHiAuthService = _OpenHiAuthService;
|
|
3779
|
+
|
|
3780
|
+
// src/services/open-hi-graphql-service.ts
|
|
3781
|
+
import {
|
|
3782
|
+
AuthorizationType,
|
|
3783
|
+
UserPoolDefaultAction
|
|
3784
|
+
} from "aws-cdk-lib/aws-appsync";
|
|
3785
|
+
var _OpenHiGraphqlService = class _OpenHiGraphqlService extends OpenHiService {
|
|
3685
3786
|
/**
|
|
3686
|
-
*
|
|
3687
|
-
*
|
|
3688
|
-
* `\<domainPrefix\>-\<childZonePrefix\>.\<zone\>` at the release-branch
|
|
3689
|
-
* CloudFront distribution (resolved from SSM against
|
|
3690
|
-
* {@link OpenHiService.releaseBranchHash}).
|
|
3787
|
+
* Returns the GraphQL API by looking up the GraphQL stack's API ID from SSM.
|
|
3788
|
+
* Use from other stacks to obtain an IGraphqlApi reference.
|
|
3691
3789
|
*/
|
|
3692
|
-
|
|
3693
|
-
return
|
|
3694
|
-
hostname: this.fullDomain,
|
|
3695
|
-
hostedZone,
|
|
3696
|
-
serviceType: _OpenHiWebsiteService.SERVICE_TYPE
|
|
3697
|
-
});
|
|
3790
|
+
static graphqlApiFromConstruct(scope) {
|
|
3791
|
+
return RootGraphqlApi.fromConstruct(scope);
|
|
3698
3792
|
}
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3793
|
+
get serviceType() {
|
|
3794
|
+
return _OpenHiGraphqlService.SERVICE_TYPE;
|
|
3795
|
+
}
|
|
3796
|
+
constructor(ohEnv, props = {}) {
|
|
3797
|
+
super(ohEnv, _OpenHiGraphqlService.SERVICE_TYPE, props);
|
|
3798
|
+
this.props = props;
|
|
3799
|
+
this.rootGraphqlApi = this.createRootGraphqlApi();
|
|
3800
|
+
}
|
|
3801
|
+
/** Creates the root GraphQL API with Cognito user pool. */
|
|
3802
|
+
createRootGraphqlApi() {
|
|
3803
|
+
const userPool = OpenHiAuthService.userPoolFromConstruct(this);
|
|
3804
|
+
return new RootGraphqlApi(this, {
|
|
3805
|
+
authorizationConfig: {
|
|
3806
|
+
defaultAuthorization: {
|
|
3807
|
+
authorizationType: AuthorizationType.USER_POOL,
|
|
3808
|
+
userPoolConfig: {
|
|
3809
|
+
userPool,
|
|
3810
|
+
defaultAction: UserPoolDefaultAction.ALLOW
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3714
3814
|
});
|
|
3715
|
-
return Bucket3.fromBucketArn(this, "shared-bucket", bucketArn);
|
|
3716
3815
|
}
|
|
3717
3816
|
};
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
* Default `domainPrefix` for this service when none is supplied.
|
|
3721
|
-
* Release-branch hostname is `www.<zone>`; per-PR preview hostname is
|
|
3722
|
-
* `www-<childZonePrefix>.<zone>`.
|
|
3723
|
-
*/
|
|
3724
|
-
_OpenHiWebsiteService.DEFAULT_DOMAIN_PREFIX = "www";
|
|
3725
|
-
var OpenHiWebsiteService = _OpenHiWebsiteService;
|
|
3817
|
+
_OpenHiGraphqlService.SERVICE_TYPE = "graphql-api";
|
|
3818
|
+
var OpenHiGraphqlService = _OpenHiGraphqlService;
|
|
3726
3819
|
|
|
3727
3820
|
// src/workflows/control-plane/owning-delete-cascade/owning-delete-cascade-lambdas.ts
|
|
3728
3821
|
import fs13 from "fs";
|
|
@@ -4271,6 +4364,8 @@ export {
|
|
|
4271
4364
|
DataStorePostgresReplica,
|
|
4272
4365
|
DiscoverableStringParameter,
|
|
4273
4366
|
DynamoDbDataStore,
|
|
4367
|
+
LOCALHOST_OAUTH_CALLBACK_URLS,
|
|
4368
|
+
LOCALHOST_OAUTH_LOGOUT_URLS,
|
|
4274
4369
|
OPENHI_REPO_TAG_KEY_ENV_VAR,
|
|
4275
4370
|
OPENHI_RESOURCE_URN_SYSTEM,
|
|
4276
4371
|
OPENHI_TAG_KEY_PREFIX_ENV_VAR,
|