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