From b07c406819521ff479bedb500b024b1472c984cf Mon Sep 17 00:00:00 2001 From: Eric Beard Date: Thu, 16 Jan 2025 12:02:00 -0800 Subject: [PATCH] Example web app using local modules --- LocalModules/ExampleWebApp/.gitignore | 4 + LocalModules/ExampleWebApp/README.md | 7 + LocalModules/ExampleWebApp/api/.gitignore | 1 + LocalModules/ExampleWebApp/api/common.go | 22 + .../ExampleWebApp/api/dist/.gitignore | 1 + LocalModules/ExampleWebApp/api/dist/.gitkeep | 2 + .../ExampleWebApp/api/resources/jwt/main.go | 209 ++ .../ExampleWebApp/api/resources/test/main.go | 71 + LocalModules/ExampleWebApp/buildapi.sh | 24 + LocalModules/ExampleWebApp/buildsite.sh | 34 + LocalModules/ExampleWebApp/deploy.sh | 49 + LocalModules/ExampleWebApp/go.mod | 38 + LocalModules/ExampleWebApp/go.sum | 77 + .../ExampleWebApp/modules/api-resource.yaml | 104 + .../ExampleWebApp/modules/bucket-policy.yaml | 21 + .../ExampleWebApp/modules/cognito.yaml | 52 + .../modules/compliant-bucket.yaml | 196 ++ .../modules/encrypted-bucket.yaml | 17 + .../ExampleWebApp/modules/load-balancer.yaml | 103 + .../ExampleWebApp/modules/rest-api.yaml | 37 + .../ExampleWebApp/modules/simple-table.yaml | 52 + .../ExampleWebApp/modules/static-site.yaml | 135 ++ LocalModules/ExampleWebApp/package-lock.json | 6 + LocalModules/ExampleWebApp/site/.gitignore | 4 + LocalModules/ExampleWebApp/site/404.html | 62 + LocalModules/ExampleWebApp/site/css/style.css | 358 +++ .../ExampleWebApp/site/eslint.config.mjs | 21 + LocalModules/ExampleWebApp/site/favicon.ico | Bin 0 -> 766 bytes LocalModules/ExampleWebApp/site/icon.png | Bin 0 -> 4029 bytes LocalModules/ExampleWebApp/site/icon.svg | 1 + LocalModules/ExampleWebApp/site/img/.gitkeep | 0 LocalModules/ExampleWebApp/site/index.html | 83 + LocalModules/ExampleWebApp/site/js/.gitignore | 3 + LocalModules/ExampleWebApp/site/js/app.js | 103 + LocalModules/ExampleWebApp/site/js/auth.js | 95 + .../ExampleWebApp/site/js/config-sample.js | 16 + .../ExampleWebApp/site/js/config-template.js | 15 + .../ExampleWebApp/site/js/containers.js | 12 + LocalModules/ExampleWebApp/site/js/plugins.js | 24 + .../ExampleWebApp/site/js/rest-api.js | 159 ++ .../site/js/vendor/modernizr-3.11.2.min.js | 3 + .../ExampleWebApp/site/js/web-util.js | 34 + .../ExampleWebApp/site/package-lock.json | 2130 +++++++++++++++++ LocalModules/ExampleWebApp/site/package.json | 26 + LocalModules/ExampleWebApp/site/robots.txt | 5 + .../ExampleWebApp/site/site.webmanifest | 12 + .../ExampleWebApp/site/vite.config.js | 5 + LocalModules/ExampleWebApp/webapp-pkg.yaml | 891 +++++++ LocalModules/ExampleWebApp/webapp.yaml | 120 + 49 files changed, 5444 insertions(+) create mode 100644 LocalModules/ExampleWebApp/.gitignore create mode 100644 LocalModules/ExampleWebApp/README.md create mode 100644 LocalModules/ExampleWebApp/api/.gitignore create mode 100644 LocalModules/ExampleWebApp/api/common.go create mode 100644 LocalModules/ExampleWebApp/api/dist/.gitignore create mode 100644 LocalModules/ExampleWebApp/api/dist/.gitkeep create mode 100644 LocalModules/ExampleWebApp/api/resources/jwt/main.go create mode 100644 LocalModules/ExampleWebApp/api/resources/test/main.go create mode 100755 LocalModules/ExampleWebApp/buildapi.sh create mode 100755 LocalModules/ExampleWebApp/buildsite.sh create mode 100755 LocalModules/ExampleWebApp/deploy.sh create mode 100644 LocalModules/ExampleWebApp/go.mod create mode 100644 LocalModules/ExampleWebApp/go.sum create mode 100644 LocalModules/ExampleWebApp/modules/api-resource.yaml create mode 100644 LocalModules/ExampleWebApp/modules/bucket-policy.yaml create mode 100644 LocalModules/ExampleWebApp/modules/cognito.yaml create mode 100644 LocalModules/ExampleWebApp/modules/compliant-bucket.yaml create mode 100644 LocalModules/ExampleWebApp/modules/encrypted-bucket.yaml create mode 100644 LocalModules/ExampleWebApp/modules/load-balancer.yaml create mode 100644 LocalModules/ExampleWebApp/modules/rest-api.yaml create mode 100644 LocalModules/ExampleWebApp/modules/simple-table.yaml create mode 100644 LocalModules/ExampleWebApp/modules/static-site.yaml create mode 100644 LocalModules/ExampleWebApp/package-lock.json create mode 100644 LocalModules/ExampleWebApp/site/.gitignore create mode 100644 LocalModules/ExampleWebApp/site/404.html create mode 100644 LocalModules/ExampleWebApp/site/css/style.css create mode 100644 LocalModules/ExampleWebApp/site/eslint.config.mjs create mode 100644 LocalModules/ExampleWebApp/site/favicon.ico create mode 100644 LocalModules/ExampleWebApp/site/icon.png create mode 100644 LocalModules/ExampleWebApp/site/icon.svg create mode 100644 LocalModules/ExampleWebApp/site/img/.gitkeep create mode 100644 LocalModules/ExampleWebApp/site/index.html create mode 100644 LocalModules/ExampleWebApp/site/js/.gitignore create mode 100644 LocalModules/ExampleWebApp/site/js/app.js create mode 100644 LocalModules/ExampleWebApp/site/js/auth.js create mode 100644 LocalModules/ExampleWebApp/site/js/config-sample.js create mode 100644 LocalModules/ExampleWebApp/site/js/config-template.js create mode 100644 LocalModules/ExampleWebApp/site/js/containers.js create mode 100644 LocalModules/ExampleWebApp/site/js/plugins.js create mode 100644 LocalModules/ExampleWebApp/site/js/rest-api.js create mode 100644 LocalModules/ExampleWebApp/site/js/vendor/modernizr-3.11.2.min.js create mode 100644 LocalModules/ExampleWebApp/site/js/web-util.js create mode 100644 LocalModules/ExampleWebApp/site/package-lock.json create mode 100644 LocalModules/ExampleWebApp/site/package.json create mode 100644 LocalModules/ExampleWebApp/site/robots.txt create mode 100644 LocalModules/ExampleWebApp/site/site.webmanifest create mode 100644 LocalModules/ExampleWebApp/site/vite.config.js create mode 100644 LocalModules/ExampleWebApp/webapp-pkg.yaml create mode 100644 LocalModules/ExampleWebApp/webapp.yaml diff --git a/LocalModules/ExampleWebApp/.gitignore b/LocalModules/ExampleWebApp/.gitignore new file mode 100644 index 00000000..fabd2377 --- /dev/null +++ b/LocalModules/ExampleWebApp/.gitignore @@ -0,0 +1,4 @@ +site/node_modules +DS_Store +local/ + diff --git a/LocalModules/ExampleWebApp/README.md b/LocalModules/ExampleWebApp/README.md new file mode 100644 index 00000000..0c97f660 --- /dev/null +++ b/LocalModules/ExampleWebApp/README.md @@ -0,0 +1,7 @@ +# AWS CloudFormation Web App Sample with Local Modules + +This sample demonstrates how to break down a CloudFormation template into +modules that can be re-used and shared. + + + diff --git a/LocalModules/ExampleWebApp/api/.gitignore b/LocalModules/ExampleWebApp/api/.gitignore new file mode 100644 index 00000000..b7c200ac --- /dev/null +++ b/LocalModules/ExampleWebApp/api/.gitignore @@ -0,0 +1 @@ +bootstrap diff --git a/LocalModules/ExampleWebApp/api/common.go b/LocalModules/ExampleWebApp/api/common.go new file mode 100644 index 00000000..6ea343f0 --- /dev/null +++ b/LocalModules/ExampleWebApp/api/common.go @@ -0,0 +1,22 @@ +package api + +import "github.com/aws/aws-lambda-go/events" + +// Fail returns a failure response +func Fail(code int, msg string) events.APIGatewayProxyResponse { + response := events.APIGatewayProxyResponse{ + StatusCode: code, + Body: "{\"message\": \"" + msg + "\"}", + } + response.Headers = make(map[string]string) + AddCORSHeaders(response) + response.Headers["X-Rain-Webapp-Error"] = msg + return response +} + +// AddCORSHeaders adds the necessary headers to a response to enable cross site requests +func AddCORSHeaders(response events.APIGatewayProxyResponse) { + response.Headers["Access-Control-Allow-Headers"] = "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent,X-KG-Partition" + response.Headers["Access-Control-Allow-Origin"] = "*" + response.Headers["Access-Control-Allow-Methods"] = "OPTIONS, GET, PUT, POST, DELETE, PATCH, HEAD" +} diff --git a/LocalModules/ExampleWebApp/api/dist/.gitignore b/LocalModules/ExampleWebApp/api/dist/.gitignore new file mode 100644 index 00000000..27614bc2 --- /dev/null +++ b/LocalModules/ExampleWebApp/api/dist/.gitignore @@ -0,0 +1 @@ +lambda-handler.zip diff --git a/LocalModules/ExampleWebApp/api/dist/.gitkeep b/LocalModules/ExampleWebApp/api/dist/.gitkeep new file mode 100644 index 00000000..139597f9 --- /dev/null +++ b/LocalModules/ExampleWebApp/api/dist/.gitkeep @@ -0,0 +1,2 @@ + + diff --git a/LocalModules/ExampleWebApp/api/resources/jwt/main.go b/LocalModules/ExampleWebApp/api/resources/jwt/main.go new file mode 100644 index 00000000..26f2d71f --- /dev/null +++ b/LocalModules/ExampleWebApp/api/resources/jwt/main.go @@ -0,0 +1,209 @@ +package main + +import ( + "context" + "fmt" + "log" + + "encoding/json" + "errors" + "net/http" + "net/url" + "os" + "strings" + + "example/api" + + "github.com/aws/aws-lambda-go/events" + "github.com/aws/aws-lambda-go/lambda" + "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/lestrrat-go/jwx/v2/jwt" +) + +func HandleRequest(ctx context.Context, + request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { + + fmt.Printf("request: %+v\n", request) + message := fmt.Sprintf("{\"message\": \"Request Resource: %s, Path: %s, HTTPMethod: %s\"}", request.Resource, request.Path, request.HTTPMethod) + fmt.Printf("message: %s\n", message) + + headers := make(map[string]string) + code := request.QueryStringParameters["code"] + refresh := request.QueryStringParameters["refresh"] + + response := events.APIGatewayProxyResponse{ + StatusCode: 200, + Headers: headers, + Body: "{\"message\": \"Success\"}", + } + + api.AddCORSHeaders(response) + + switch request.HTTPMethod { + case "GET": + jsonData, err := handleAuth(code, refresh) + if err != nil { + fmt.Printf("handleAuth: %v", err) + return api.Fail(401, "Auth Failure"), nil + } + response.Body = jsonData + return response, nil + case "OPTIONS": + response.StatusCode = 204 + response.Body = "{}" + return response, nil + default: + return api.Fail(400, fmt.Sprintf("Unexpected HttpMethod: %s", request.HTTPMethod)), nil + } + +} + +func main() { + lambda.Start(HandleRequest) +} + +// getCognitoIssuer returns the Cognito issuer URL. +func getCognitoIssuer() (string, error) { + region := os.Getenv("COGNITO_REGION") + if region == "" { + return "", errors.New("missing COGNITO_REGION") + } + + cognitoPoolID := os.Getenv("COGNITO_POOL_ID") + if cognitoPoolID == "" { + return "", errors.New("missing COGNITO_POOL_ID") + } + + return fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s", region, cognitoPoolID), nil +} + +// getPublicKeys retrieves the public keys from the Cognito issuer. +func getPublicKeys() (jwk.Set, error) { + + cognitoIssuer, err := getCognitoIssuer() + if err != nil { + return nil, err + } + + url := cognitoIssuer + "/.well-known/jwks.json" + fmt.Printf("JWK URL: %s\n", url) + + set, err := jwk.Fetch(context.Background(), url) + if err != nil { + fmt.Printf("failed to fetch JWK: %s\n", err) + return nil, err + } + + { + jsonbuf, err := json.Marshal(set) + if err != nil { + log.Printf("failed to marshal key set into JSON: %s\n", err) + return nil, err + } + fmt.Printf("json jwk: %s\n", jsonbuf) + } + + return set, nil +} + +func handleAuth(code string, refresh string) (string, error) { + + redirectURI := os.Getenv("COGNITO_REDIRECT_URI") + cognitoDomainPrefix := os.Getenv("COGNITO_DOMAIN_PREFIX") + cognitoDomainPrefix = strings.ReplaceAll(cognitoDomainPrefix, ".", "-") + cognitoClientID := os.Getenv("COGNITO_APP_CLIENT_ID") + cognitoRegion := os.Getenv("COGNITO_REGION") + + tokenEndpoint := fmt.Sprintf("https://%s.auth.%s.amazoncognito.com/oauth2/token", + cognitoDomainPrefix, cognitoRegion) + + var postData url.Values + + if code != "" { + postData = url.Values{ + "grant_type": {"authorization_code"}, + "client_id": {cognitoClientID}, + "code": {code}, + "redirect_uri": {redirectURI}, + } + } else { + if refresh == "" { + return "", errors.New("no refresh token") + } + + postData = url.Values{ + "grant_type": {"refresh_token"}, + "client_id": {cognitoClientID}, + "refresh_token": {refresh}, + } + } + + fmt.Printf("About to post to %s: %+v\n", tokenEndpoint, postData) + + resp, err := http.PostForm(tokenEndpoint, postData) + if err != nil { + fmt.Printf("PostForm error from %s: %v\n", tokenEndpoint, err) + return "", errors.New("token endpoint failed") + } + defer resp.Body.Close() + + fmt.Printf("resp: %+v\n", resp) + + if resp.StatusCode >= 400 { + return "", fmt.Errorf("request to %s failed with Status %d", + tokenEndpoint, resp.StatusCode) + } + + var token struct { + AccessToken string `json:"access_token"` + IDToken string `json:"id_token"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int64 `json:"expires_in"` + } + err = json.NewDecoder(resp.Body).Decode(&token) + if err != nil { + fmt.Printf("json token response error: %v\n", err) + return "", errors.New("failed to decode token response") + } + + fmt.Printf("Got token: %+v\n", token) + + keys, err := getPublicKeys() + if err != nil { + return "", err + } + fmt.Printf("keys: %+v\n", keys) + + parsed, err := jwt.Parse([]byte(token.AccessToken), jwt.WithKeySet(keys)) + if err != nil { + fmt.Printf("failed to verify: %s\n", err) + return "", errors.New("failed to verify token") + } + + fmt.Printf("parsed: %+v", parsed) + + userName, ok := parsed.Get("username") + if !ok { + return "", errors.New("missing username") + } + + retval := struct { + IDToken string `json:"idToken"` + RefreshToken string `json:"refreshToken"` + Username string `json:"username"` + ExpiresIn int64 `json:"expiresIn"` + }{ + IDToken: token.IDToken, + RefreshToken: token.RefreshToken, + Username: strings.TrimPrefix(userName.(string), "AmazonFederate_"), + ExpiresIn: token.ExpiresIn, + } + + jsonData, err := json.Marshal(retval) + if err != nil { + return "", errors.New("failed to encode response") + } + + return string(jsonData), nil + +} diff --git a/LocalModules/ExampleWebApp/api/resources/test/main.go b/LocalModules/ExampleWebApp/api/resources/test/main.go new file mode 100644 index 00000000..15addb09 --- /dev/null +++ b/LocalModules/ExampleWebApp/api/resources/test/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "os" + + "example/api" + + "github.com/aws/aws-lambda-go/events" + "github.com/aws/aws-lambda-go/lambda" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" +) + +func HandleRequest(ctx context.Context, + request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { + + fmt.Printf("request: %+v\n", request) + message := fmt.Sprintf("{\"message\": \"Request Resource: %s, Path: %s, HTTPMethod: %s\"}", request.Resource, request.Path, request.HTTPMethod) + fmt.Printf("message: %s\n", message) + + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + log.Fatal(err) + } + + client := dynamodb.NewFromConfig(cfg) + + response := events.APIGatewayProxyResponse{ + StatusCode: 200, + Body: "{\"message\": \"Success\"}", + Headers: make(map[string]string), + } + + api.AddCORSHeaders(response) + + switch request.HTTPMethod { + case "GET": + input := &dynamodb.ScanInput{ + TableName: aws.String(os.Getenv("TABLE_NAME")), + } + res, err := client.Scan(context.Background(), input) + if err != nil { + fmt.Printf("Scan failed: %v\n", err) + return api.Fail(500, fmt.Sprintf("%v", err)), nil + } + fmt.Printf("Scan result: %+v", res) + jsonData, err := json.Marshal(res.Items) + if err != nil { + fmt.Printf("Marshal failed: %v\n", err) + return api.Fail(500, fmt.Sprintf("%v", err)), nil + } + response.Body = string(jsonData) + case "OPTIONS": + response.StatusCode = 204 + response.Body = "{}" + return response, nil + default: + return api.Fail(400, fmt.Sprintf("Unexpected HttpMethod: %s", request.HTTPMethod)), nil + } + + return response, nil +} + +func main() { + lambda.Start(HandleRequest) +} diff --git a/LocalModules/ExampleWebApp/buildapi.sh b/LocalModules/ExampleWebApp/buildapi.sh new file mode 100755 index 00000000..240659ea --- /dev/null +++ b/LocalModules/ExampleWebApp/buildapi.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -eou pipefail + +SCRIPT_DIR=$(dirname "$0") +echo "SCRIPT_DIR: ${SCRIPT_DIR}" +cd $SCRIPT_DIR + +function build() { + echo "Building $1..." + cd ../$1 + staticcheck . + go vet . + GOOS=linux GOARCH=amd64 go build -o bootstrap main.go + mkdir -p ../../dist/$1 + zip ../../dist/$1/lambda-handler.zip bootstrap +} + +cd api/resources/test + +build test +build jwt + + diff --git a/LocalModules/ExampleWebApp/buildsite.sh b/LocalModules/ExampleWebApp/buildsite.sh new file mode 100755 index 00000000..3c25f160 --- /dev/null +++ b/LocalModules/ExampleWebApp/buildsite.sh @@ -0,0 +1,34 @@ +#!/usr/local/bin/bash +set -eou pipefail + +SCRIPT_DIR=$(dirname "$0") +echo "SCRIPT_DIR: ${SCRIPT_DIR}" + +cd ${SCRIPT_DIR}/site + +if [ "$#" -eq 4 ] +then + echo "Editing config file..." + + APIGW=$1 + REDIRECT=$2 + DOMAIN=$3 + APPCLIENT=$4 + + ESCAPED_APIGW=$(printf '%s\n' "${APIGW}" | sed -e 's/[\/&]/\\&/g') + ESCAPED_REDIRECT=$(printf '%s\n' "${REDIRECT}" | sed -e 's/[\/&]/\\&/g') + + cat js/config-template.js | sed s/__APIGW__/"${ESCAPED_APIGW}"/ | sed s/__REDIRECT__/"${ESCAPED_REDIRECT}"/ | sed s/__DOMAIN__/"${DOMAIN}"/ | sed s/__APPCLIENT__/"$APPCLIENT"/ > js/config.js + + echo "Config file:" + cat js/config.js +else + echo "Number of args was $#" +fi + +echo "Linting..." +npm run lint + +echo "Building site..." +npm run build + diff --git a/LocalModules/ExampleWebApp/deploy.sh b/LocalModules/ExampleWebApp/deploy.sh new file mode 100755 index 00000000..6a8eba4d --- /dev/null +++ b/LocalModules/ExampleWebApp/deploy.sh @@ -0,0 +1,49 @@ +#!/bin/bash +set -eou pipefail + +APPNAME=cli-cfn-webapp +PROFILE="" +if [ -z "$1" ]; then + echo "Using default profile" +else + PROFILE="--profile $1" +fi + +echo "Building API..." +./buildapi.sh + +echo "Building Site..." +./buildsite.sh + +echo "Packaging..." +awscli cloudformation package $PROFILE \ + --s3-bucket ezbeard-rain-lambda \ + --template-file webapp.yaml --output-template-file webapp-pkg.yaml + +echo "Linting..." +cfn-lint webapp-pkg.yaml + +echo "Deploying..." +awscli cloudformation deploy $PROFILE \ + --template-file webapp-pkg.yaml \ + --stack-name $APPNAME \ + --parameter-overrides AppName=$APPNAME \ + --capabilities CAPABILITY_IAM + +echo "Rebuilding site with config..." +# Look up output values from the stack +outs=$(aws cloudformation describe-stacks $PROFILE --stack-name $APPNAME | jq -r ".Stacks[0].Outputs") +APIGW=$(echo $outs | jq -r '.[] | select(.OutputKey == "RestApiInvokeURL") | .OutputValue') +REDIRECT=$(echo $outs | jq -r '.[] | select(.OutputKey == "RedirectURI") | .OutputValue') +DOMAIN=$(echo $outs | jq -r '.[] | select(.OutputKey == "AppName") | .OutputValue') +APPCLIENT=$(echo $outs | jq -r '.[] | select(.OutputKey == "AppClientId") | .OutputValue') +CONTENTBUCKET=$(echo $outs | jq -r '.[] | select(.OutputKey == "ContentBucketName") | .OutputValue') +./buildsite.sh $APIGW $REDIRECT $DOMAIN $APPCLIENT + +echo "Uploading site to s://$CONTENTBUCKET..." +aws s3 cp $PROFILE --recursive site/dist s3://$CONTENTBUCKET + +# TODO: Invalidate CloudFront distribution + +echo "Success!" + diff --git a/LocalModules/ExampleWebApp/go.mod b/LocalModules/ExampleWebApp/go.mod new file mode 100644 index 00000000..04ac0768 --- /dev/null +++ b/LocalModules/ExampleWebApp/go.mod @@ -0,0 +1,38 @@ +module example + +go 1.22.4 + +require ( + github.com/aws-cloudformation/rain v1.21.0 + github.com/aws/aws-lambda-go v1.47.0 + github.com/aws/aws-sdk-go-v2 v1.32.8 + github.com/aws/aws-sdk-go-v2/config v1.28.11 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.39.3 + github.com/lestrrat-go/jwx/v2 v2.1.3 +) + +require ( + github.com/aws/aws-sdk-go-v2/credentials v1.17.52 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.8 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.8 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.9 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.8 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.7 // indirect + github.com/aws/smithy-go v1.22.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect + github.com/goccy/go-json v0.10.4 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.6 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect + github.com/segmentio/asm v1.2.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/sys v0.29.0 // indirect +) diff --git a/LocalModules/ExampleWebApp/go.sum b/LocalModules/ExampleWebApp/go.sum new file mode 100644 index 00000000..9cf4aea5 --- /dev/null +++ b/LocalModules/ExampleWebApp/go.sum @@ -0,0 +1,77 @@ +github.com/aws-cloudformation/rain v1.21.0 h1:VcMthCM/UFojCngAgF3tkbyuG+gP98PN8b53py688Mo= +github.com/aws-cloudformation/rain v1.21.0/go.mod h1:je+IG7S/xX8uA07eJB4mvOc+3dyaWCzb/UMsY7NRHYo= +github.com/aws/aws-lambda-go v1.47.0 h1:0H8s0vumYx/YKs4sE7YM0ktwL2eWse+kfopsRI1sXVI= +github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= +github.com/aws/aws-sdk-go-v2 v1.32.8 h1:cZV+NUS/eGxKXMtmyhtYPJ7Z4YLoI/V8bkTdRZfYhGo= +github.com/aws/aws-sdk-go-v2 v1.32.8/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2/config v1.28.11 h1:7Ekru0IkRHRnSRWGQLnLN6i0o1Jncd0rHo2T130+tEQ= +github.com/aws/aws-sdk-go-v2/config v1.28.11/go.mod h1:x78TpPvBfHH16hi5tE3OCWQ0pzNfyXA349p5/Wp82Yo= +github.com/aws/aws-sdk-go-v2/credentials v1.17.52 h1:I4ymSk35LHogx2Re2Wu6LOHNTRaRWkLVoJgWS5Wd40M= +github.com/aws/aws-sdk-go-v2/credentials v1.17.52/go.mod h1:vAkqKbMNUcher8fDXP2Ge2qFXKMkcD74qvk1lJRMemM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.23 h1:IBAoD/1d8A8/1aA8g4MBVtTRHhXRiNAgwdbo/xRM2DI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.23/go.mod h1:vfENuCM7dofkgKpYzuzf1VT1UKkA/YL3qanfBn7HCaA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 h1:jSJjSBzw8VDIbWv+mmvBSP8ezsztMYJGH+eKqi9AmNs= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27/go.mod h1:/DAhLbFRgwhmvJdOfSm+WwikZrCuUJiA4WgJG0fTNSw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 h1:l+X4K77Dui85pIj5foXDhPlnqcNRG2QUyvca300lXh8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27/go.mod h1:KvZXSFEXm6x84yE8qffKvT3x8J5clWnVFXphpohhzJ8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.39.3 h1:gZ5KNaw6OKL+Z+5wIuONGiSLfvYtBjn/AG7EG7hJEJg= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.39.3/go.mod h1:516U/KQM3zdcahNBjHUZKGWNfNnIYyt7sxLeqOx78b0= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.8 h1:h56mLNgpqWIL7RZOIQO634Xr569bXGTlIE83t/a0LSE= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.8/go.mod h1:kK04550Xx95KI0sNmwoB7ciS9QkRwt9TojhoTMXyJdo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.8 h1:cWno7lefSH6Pp+mSznagKCgfDGeZRin66UvYUqAkyeA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.8/go.mod h1:tPD+VjU3ABTBoEJ3nctu5Nyg4P4yjqSH5bJGGkY4+XE= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.9 h1:YqtxripbjWb2QLyzRK9pByfEDvgg95gpC2AyDq4hFE8= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.9/go.mod h1:lV8iQpg6OLOfBnqbGMBKYjilBlf633qwHnBEiMSPoHY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.8 h1:6dBT1Lz8fK11m22R+AqfRsFn8320K0T5DTGxxOQBSMw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.8/go.mod h1:/kiBvRQXBc6xeJTYzhSdGvJ5vm1tjaDEjH+MSeRJnlY= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.7 h1:qwGa9MA8G7mBq2YphHFaygdPe5t9OA7SvaJdwWTlEds= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.7/go.mod h1:+8h7PZb3yY5ftmVLD7ocEoE98hdc8PoKS0H3wfx1dlc= +github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= +github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= +github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= +github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.1.3 h1:Ud4lb2QuxRClYAmRleF50KrbKIoM1TddXgBrneT5/Jo= +github.com/lestrrat-go/jwx/v2 v2.1.3/go.mod h1:q6uFgbgZfEmQrfJfrCo90QcQOcXFMfbI/fO0NqRtvZo= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/LocalModules/ExampleWebApp/modules/api-resource.yaml b/LocalModules/ExampleWebApp/modules/api-resource.yaml new file mode 100644 index 00000000..0ce8402e --- /dev/null +++ b/LocalModules/ExampleWebApp/modules/api-resource.yaml @@ -0,0 +1,104 @@ +Description: This module contains a lambda handler and the API Gateway resource to proxy requests to the lambda. It is assumed that the lambda function can handle all HTTPMethods sent to the specified path, including the OPTIONS pre-flight request. The lambda function must also return the approppriate CORS headers with each response. + +Parameters: + + Name: + Type: String + Description: This name will be used for resource names and tags + + RestApi: + Type: String + + RestApiDeployment: + Type: String + + BuildScript: + Type: String + Description: The name of the script to run before uploading the lambda handler to S3 + + CodePath: + Type: String + Description: The path of the packaged lambda function created by BuildScript + + ResourcePath: + Type: String + Description: The URI path name for the resource, for example, "user" or "order" + + AuthorizerId: + Type: String + Description: The Id of the APIGateway Authorizer + +Resources: + + Handler: + Type: AWS::Lambda::Function + Properties: + Handler: bootstrap + FunctionName: !Sub ${Name}-handler + Runtime: provided.al2023 + Code: !Ref CodePath + Role: !GetAtt HandlerRole.Arn + + HandlerRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - 'sts:AssumeRole' + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + + Resource: + Type: AWS::ApiGateway::Resource + Properties: + ParentId: !Sub ${RestApi.RootResourceId} + PathPart: !Ref ResourcePath + RestApiId: !Ref RestApi + + Permission: + Type: AWS::Lambda::Permission + Properties: + Action: lambda:InvokeFunction + FunctionName: !GetAtt Handler.Arn + Principal: apigateway.amazonaws.com + SourceArn: !Sub "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*/*" + + RootPermission: + Type: AWS::Lambda::Permission + Properties: + Action: lambda:InvokeFunction + FunctionName: !GetAtt Handler.Arn + Principal: apigateway.amazonaws.com + SourceArn: !Sub "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*/" + + Options: + Type: AWS::ApiGateway::Method + Properties: + HttpMethod: OPTIONS + ResourceId: !Ref Resource + RestApiId: !Ref RestApi + AuthorizationType: NONE + Integration: + IntegrationHttpMethod: POST + Type: AWS_PROXY + Uri: !Sub "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Handler.Arn}/invocations" + + Get: + Type: AWS::ApiGateway::Method + Properties: + HttpMethod: GET + ResourceId: !Ref Resource + RestApiId: !Ref RestApi + AuthorizationType: COGNITO_USER_POOLS + AuthorizerId: !Ref AuthorizerId + Integration: + IntegrationHttpMethod: POST + Type: AWS_PROXY + Uri: !Sub "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Handler.Arn}/invocations" + diff --git a/LocalModules/ExampleWebApp/modules/bucket-policy.yaml b/LocalModules/ExampleWebApp/modules/bucket-policy.yaml new file mode 100644 index 00000000..8dc76a5c --- /dev/null +++ b/LocalModules/ExampleWebApp/modules/bucket-policy.yaml @@ -0,0 +1,21 @@ +Parameters: + PolicyBucketName: + Type: String +Resources: + Policy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: !Ref PolicyBucketName + PolicyDocument: + Statement: + - Action: s3:* + Condition: + Bool: + aws:SecureTransport: false + Effect: Deny + Principal: + AWS: '*' + Resource: + - !Sub "arn:${AWS::Partition}:s3:::${PolicyBucketName}" + - !Sub "arn:${AWS::Partition}:s3:::${PolicyBucketName}/*" + Version: "2012-10-17" diff --git a/LocalModules/ExampleWebApp/modules/cognito.yaml b/LocalModules/ExampleWebApp/modules/cognito.yaml new file mode 100644 index 00000000..5dc84360 --- /dev/null +++ b/LocalModules/ExampleWebApp/modules/cognito.yaml @@ -0,0 +1,52 @@ +Description: This module creates a simple Cognito User Pool, Domain, and App Client. + +Parameters: + + AppName: + Type: String + + CallbackURL: + Type: String + +Resources: + + UserPool: + Type: AWS::Cognito::UserPool + Properties: + UserPoolName: !Ref AppName + AdminCreateUserConfig: + AllowAdminCreateUserOnly: true + AutoVerifiedAttributes: + - email + Schema: + - Name: email + Required: true + - Name: given_name + Required: true + - Name: family_name + Required: true + + Domain: + Type: AWS::Cognito::UserPoolDomain + Properties: + Domain: !Ref AppName + UserPoolId: !Ref UserPool + + Client: + Type: AWS::Cognito::UserPoolClient + Properties: + ClientName: !Ref AppName + GenerateSecret: false + UserPoolId: !Ref UserPool + CallbackURLs: + - !Ref CallbackURL + AllowedOAuthFlows: + - code + AllowedOAuthFlowsUserPoolClient: true + AllowedOAuthScopes: + - phone + - email + - openid + SupportedIdentityProviders: + - COGNITO + diff --git a/LocalModules/ExampleWebApp/modules/compliant-bucket.yaml b/LocalModules/ExampleWebApp/modules/compliant-bucket.yaml new file mode 100644 index 00000000..8c76731d --- /dev/null +++ b/LocalModules/ExampleWebApp/modules/compliant-bucket.yaml @@ -0,0 +1,196 @@ +Description: | + This module creates an S3 bucket that will pass common compliance checks + by default. It also creates an associated log bucket and replica bucket. + +Parameters: + + AppName: + Type: String + Description: | + This string will serve as a prefix for all resource names, which have + the general form of AppName-ResourceName-Region-Account. + +Constants: + S3Arn: "arn:${AWS::Partition}:s3:::" + BucketName: "${AppName}-${AWS::Region}-${AWS::AccountId}" + LogBucketName: "${AppName}-logs-${AWS::Region}-${AWS::AccountId}" + LogBucketArn: "${Constant::S3Arn}${Constant::LogBucketName}" + ReplicaBucketName: "${AppName}-replicas-${AWS::Region}-${AWS::AccountId}" + +Resources: + + LogBucket: + Type: AWS::S3::Bucket + Metadata: + Comment: This bucket records access logs for the main bucket + checkov: + skip: + - comment: This is the log bucket + id: CKV_AWS_18 + guard: + SuppressedRules: + - S3_BUCKET_LOGGING_ENABLED + - S3_BUCKET_REPLICATION_ENABLED + Properties: + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + BucketName: !Sub ${Constant::LogBucketName} + ObjectLockConfiguration: + ObjectLockEnabled: Enabled + Rule: + DefaultRetention: + Mode: COMPLIANCE + Years: 1 + ObjectLockEnabled: true + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + VersioningConfiguration: + Status: Enabled + + LogBucketAccess: + Type: LocalModule + Source: "bucket-policy.yaml" + Properties: + PolicyBucketName: !Sub ${Constant::LogBucketName} + Overrides: + Policy: + Properties: + PolicyDocument: + Statement: + - Action: s3:* + Condition: + Bool: + aws:SecureTransport: false + Effect: Deny + Principal: + AWS: '*' + Resource: + - !Sub ${Constant::LogBucketArn} + - !Sub ${Constant::LogBucketArn}/* + - Action: s3:PutObject + Condition: + ArnLike: + aws:SourceArn: !Sub ${Constant::LogBucketArn}/* + StringEquals: + aws:SourceAccount: !Ref AWS::AccountId + Effect: Allow + Principal: + Service: logging.s3.amazonaws.com + Resource: + - !Sub ${Constant::LogBucketArn}/* + + Bucket: + Type: AWS::S3::Bucket + Metadata: + guard: + SuppressedRules: + - S3_BUCKET_DEFAULT_LOCK_ENABLED + Properties: + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + BucketName: !Sub ${Constant::BucketName} + LoggingConfiguration: + DestinationBucketName: !Ref LogBucket + ObjectLockEnabled: false + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + ReplicationConfiguration: + Role: !GetAtt ReplicationRole.Arn + Rules: + - Destination: + Bucket: !GetAtt ReplicaBucket.Arn + Status: Enabled + VersioningConfiguration: + Status: Enabled + + BucketAccess: + Type: LocalModule + Source: "bucket-policy.yaml" + Properties: + PolicyBucketName: !Sub ${AppName}-${AWS::Region}-${AWS::AccountId} + + ReplicaBucket: + Type: AWS::S3::Bucket + Metadata: + Comment: This bucket is used as a target for replicas from the main bucket + checkov: + skip: + - comment: This is the replica bucket + id: CKV_AWS_18 + guard: + SuppressedRules: + - S3_BUCKET_DEFAULT_LOCK_ENABLED + - S3_BUCKET_REPLICATION_ENABLED + - S3_BUCKET_LOGGING_ENABLED + Properties: + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + BucketName: !Sub ${AppName}-replicas-${AWS::Region}-${AWS::AccountId} + ObjectLockEnabled: false + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + VersioningConfiguration: + Status: Enabled + + ReplicaBucketAccess: + Type: LocalModule + Source: "bucket-policy.yaml" + Properties: + PolicyBucketName: !Sub ${AppName}-replicas-${AWS::Region}-${AWS::AccountId} + + ReplicationPolicy: + Type: AWS::IAM::RolePolicy + Properties: + PolicyDocument: + Statement: + - Action: + - s3:GetReplicationConfiguration + - s3:ListBucket + Effect: Allow + Resource: !Sub arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId} + - Action: + - s3:GetObjectVersionForReplication + - s3:GetObjectVersionAcl + - s3:GetObjectVersionTagging + Effect: Allow + Resource: !Sub arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}/* + - Action: + - s3:ReplicateObject + - s3:ReplicateDelete + - s3:ReplicationTags + Effect: Allow + Resource: !Sub arn:${AWS::Partition}:s3:::${AppName}-replicas-${AWS::Region}-${AWS::AccountId}/* + Version: "2012-10-17" + PolicyName: bucket-replication-policy + RoleName: !Ref ReplicationRole + + ReplicationRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - s3.amazonaws.com + Version: "2012-10-17" + Path: / + + diff --git a/LocalModules/ExampleWebApp/modules/encrypted-bucket.yaml b/LocalModules/ExampleWebApp/modules/encrypted-bucket.yaml new file mode 100644 index 00000000..6d00f126 --- /dev/null +++ b/LocalModules/ExampleWebApp/modules/encrypted-bucket.yaml @@ -0,0 +1,17 @@ +Description: A simple S3 bucket with encryption enabled and public access blocked + +Resources: + + Bucket: + Type: AWS::S3::Bucket + Properties: + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + diff --git a/LocalModules/ExampleWebApp/modules/load-balancer.yaml b/LocalModules/ExampleWebApp/modules/load-balancer.yaml new file mode 100644 index 00000000..b8061966 --- /dev/null +++ b/LocalModules/ExampleWebApp/modules/load-balancer.yaml @@ -0,0 +1,103 @@ +Description: | + This module creates an ELBv2 load balancer + +Parameters: + + CertificateArn: + Type: String + + VPCId: + Type: String + + PublicSubnet1: + Type: String + + PublicSubnet2: + Type: String + + DestinationSecurityGroupId: + Type: String + +Resources: + + LoadBalancer: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Metadata: + checkov: + skip: + - id: CKV_AWS_91 + guard: + SuppressedRules: + - ELB_DELETION_PROTECTION_ENABLED + Properties: + LoadBalancerAttributes: + - Key: deletion_protection.enabled + Value: false + - Key: routing.http.drop_invalid_header_fields.enabled + Value: true + Scheme: internet-facing + SecurityGroups: + - Fn::GetAtt: + - LoadBalancerSecurityGroup + - GroupId + Subnets: + - !Ref PublicSubnet1 + - !Ref PublicSubnet2 + Type: application + + LoadBalancerSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Automatically created Security Group for ELB + SecurityGroupIngress: + - CidrIp: 0.0.0.0/0 + Description: Allow from anyone on port 443 + FromPort: 443 + IpProtocol: tcp + ToPort: 443 + VpcId: !Ref VPCId + + LoadBalancerEgress: + Type: AWS::EC2::SecurityGroupEgress + Properties: + Description: Load balancer to target + DestinationSecurityGroupId: !Ref DestinationSecurityGroupId + FromPort: 80 + GroupId: !GetAtt LoadBalancerSecurityGroup.GroupId + IpProtocol: tcp + ToPort: 80 + + LoadBalancerListener: + Type: AWS::ElasticLoadBalancingV2::Listener + Metadata: + guard: + SuppressedRules: + - ELBV2_ACM_CERTIFICATE_REQUIRED + Properties: + DefaultActions: + - TargetGroupArn: !Ref TargetGroup + Type: forward + LoadBalancerArn: !Ref LoadBalancer + Port: 443 + Protocol: HTTPS + Certificates: + - CertificateArn: !Ref CertificateArn + SslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06 + + TargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + Port: 80 + Protocol: HTTP + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: "10" + - Key: stickiness.enabled + Value: "false" + TargetType: ip + VpcId: !Ref VPCId + +Outputs: + LoadBalancerDNS: + Value: !GetAtt LoadBalancer.DNSName + diff --git a/LocalModules/ExampleWebApp/modules/rest-api.yaml b/LocalModules/ExampleWebApp/modules/rest-api.yaml new file mode 100644 index 00000000..33d16691 --- /dev/null +++ b/LocalModules/ExampleWebApp/modules/rest-api.yaml @@ -0,0 +1,37 @@ +Parameters: + + AppName: + Type: String + + UserPoolArn: + Type: String + +Resources: + + Api: + Type: AWS::ApiGateway::RestApi + Properties: + Name: !Ref AppName + + ApiDeployment: + Type: AWS::ApiGateway::Deployment + Properties: + RestApiId: !Ref Api + + ApiStage: + Type: AWS::ApiGateway::Stage + Properties: + RestApiId: !Ref Api + DeploymentId: !Ref ApiDeployment + StageName: prod + + ApiAuthorizer: + Type: AWS::ApiGateway::Authorizer + Properties: + IdentitySource: method.request.header.authorization + Name: CognitoApiAuthorizer + ProviderARNs: + - !Ref UserPoolArn + RestApiId: !Ref Api + Type: COGNITO_USER_POOLS + diff --git a/LocalModules/ExampleWebApp/modules/simple-table.yaml b/LocalModules/ExampleWebApp/modules/simple-table.yaml new file mode 100644 index 00000000..b04a9245 --- /dev/null +++ b/LocalModules/ExampleWebApp/modules/simple-table.yaml @@ -0,0 +1,52 @@ +Parameters: + + TableName: + Type: String + + LambdaRole: + Type: String + Description: If set, allow the lambda function to access this table + Default: "" + +Conditions: + LambdaRole: + Fn::Not: + - Fn::Equals: + - !Ref LambdaRole + - "" + +Resources: + Table: + Type: AWS::DynamoDB::Table + Properties: + BillingMode: PAY_PER_REQUEST + TableName: !Sub ${TableName} + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + + LambdaPolicy: + Type: AWS::IAM::RolePolicy + Condition: LambdaRole + Metadata: + Comment: This resource is created only if the LambdaRoleArn is set + Properties: + PolicyDocument: + Statement: + - Action: + - dynamodb:BatchGetItem + - dynamodb:GetItem + - dynamodb:Query + - dynamodb:Scan + - dynamodb:BatchWriteItem + - dynamodb:PutItem + - dynamodb:UpdateItem + Effect: Allow + Resource: + - !GetAtt Table.Arn + PolicyName: !Sub ${TableName}-policy + RoleName: !Ref LambdaRole + diff --git a/LocalModules/ExampleWebApp/modules/static-site.yaml b/LocalModules/ExampleWebApp/modules/static-site.yaml new file mode 100644 index 00000000..324cfd63 --- /dev/null +++ b/LocalModules/ExampleWebApp/modules/static-site.yaml @@ -0,0 +1,135 @@ +Description: This module creates a static website consisting of an S3 bucket and a CloudFront distribution. + +Parameters: + + AppName: + Type: String + +Resources: + + OriginAccessControl: + Type: AWS::CloudFront::OriginAccessControl + Properties: + OriginAccessControlConfig: + Name: !Join + - "" + - - !Ref AppName + - !Select + - 2 + - !Split + - / + - !Ref AWS::StackId + OriginAccessControlOriginType: s3 + SigningBehavior: always + SigningProtocol: sigv4 + + Distribution: + Type: AWS::CloudFront::Distribution + Metadata: + checkov: + skip: + - id: CKV_AWS_174 + comment: Using the default cloudfront certificate with no aliases + guard: + SuppressedRules: + - CLOUDFRONT_CUSTOM_SSL_CERTIFICATE + - CLOUDFRONT_ORIGIN_FAILOVER_ENABLED + - CLOUDFRONT_SNI_ENABLED + Properties: + DistributionConfig: + DefaultCacheBehavior: + CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 + Compress: true + TargetOriginId: !Sub ${AppName}-origin-1 + ViewerProtocolPolicy: redirect-to-https + DefaultRootObject: index.html + Enabled: true + HttpVersion: http2 + IPV6Enabled: true + Logging: + Bucket: !GetAtt CloudFrontLogsBucket.RegionalDomainName + Origins: + - DomainName: !GetAtt ContentBucket.RegionalDomainName + Id: !Sub ${AppName}-origin-1 + OriginAccessControlId: !GetAtt OriginAccessControl.Id + S3OriginConfig: + OriginAccessIdentity: "" + ViewerCertificate: + CloudFrontDefaultCertificate: true + WebACLId: !GetAtt WebACL.Arn + + WebACL: + Type: AWS::WAFv2::WebACL + Properties: + Name: WebACLWithAMR + Scope: CLOUDFRONT + Description: Web ACL with AWS Managed Rules + DefaultAction: + Allow: {} + VisibilityConfig: + SampledRequestsEnabled: true + CloudWatchMetricsEnabled: true + MetricName: MetricForWebACLWithAMR + Tags: + - Key: Name + Value: !Ref AppName + Rules: + - Name: AWS-AWSManagedRulesCommonRuleSet + Priority: 0 + OverrideAction: + None: {} + VisibilityConfig: + SampledRequestsEnabled: true + CloudWatchMetricsEnabled: true + MetricName: MetricForAMRCRS + Statement: + ManagedRuleGroupStatement: + VendorName: AWS + Name: AWSManagedRulesCommonRuleSet + ExcludedRules: + - Name: NoUserAgent_HEADER + + Content: + Type: LocalModule + Source: "compliant-bucket.yaml" + Properties: + AppName: !Sub ${AppName}-content + EmptyOnDelete: true + Overrides: + BucketAccessPolicy: + Properties: + PolicyDocument: + Statement: + - Action: s3:GetObject + Effect: Allow + Resource: !Sub arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}/* + Principal: + Service: cloudfront.amazonaws.com + Condition: + StringEquals: + AWS:SourceArn: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${Distribution.Id} + - Action: s3:* + Condition: + Bool: + aws:SecureTransport: false + Effect: Deny + Principal: + AWS: '*' + Resource: + - !Sub arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId} + - !Sub arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}/* + Version: "2012-10-17" + + CloudFrontLogs: + Type: LocalModule + Source: "compliant-bucket.yaml" + Properties: + AppName: !Sub ${AppName}-cflogs + EmptyOnDelete: true + Overrides: + Bucket: + Properties: + OwnershipControls: + Rules: + - ObjectOwnership: BucketOwnerPreferred + diff --git a/LocalModules/ExampleWebApp/package-lock.json b/LocalModules/ExampleWebApp/package-lock.json new file mode 100644 index 00000000..2f1cd951 --- /dev/null +++ b/LocalModules/ExampleWebApp/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "webapp-aws-cli", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/LocalModules/ExampleWebApp/site/.gitignore b/LocalModules/ExampleWebApp/site/.gitignore new file mode 100644 index 00000000..f4f25e04 --- /dev/null +++ b/LocalModules/ExampleWebApp/site/.gitignore @@ -0,0 +1,4 @@ +dist/ +node_modules/ + + diff --git a/LocalModules/ExampleWebApp/site/404.html b/LocalModules/ExampleWebApp/site/404.html new file mode 100644 index 00000000..260cc4c9 --- /dev/null +++ b/LocalModules/ExampleWebApp/site/404.html @@ -0,0 +1,62 @@ + + + + + + Page Not Found + + + + + +

Page Not Found

+

Sorry, but the page you were trying to view does not exist.

+ + + + diff --git a/LocalModules/ExampleWebApp/site/css/style.css b/LocalModules/ExampleWebApp/site/css/style.css new file mode 100644 index 00000000..e20d3276 --- /dev/null +++ b/LocalModules/ExampleWebApp/site/css/style.css @@ -0,0 +1,358 @@ +/*! HTML5 Boilerplate v9.0.0-RC1 | MIT License | https://html5boilerplate.com/ */ + +/* main.css 3.0.0 | MIT License | https://github.com/h5bp/main.css#readme */ +/* + * What follows is the result of much research on cross-browser styling. + * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, + * Kroc Camen, and the H5BP dev community and team. + */ + +/* ========================================================================== + Base styles: opinionated defaults + ========================================================================== */ + +html { + color: #222; + font-size: 1em; + line-height: 1.4; +} + +body { + font-family: 'Courier New', monospace; +} + +/* + * Remove text-shadow in selection highlight: + * https://twitter.com/miketaylr/status/12228805301 + * + * Customize the background color to match your design. + */ + +::-moz-selection { + background: #b3d4fc; + text-shadow: none; +} + +::selection { + background: #b3d4fc; + text-shadow: none; +} + +/* + * A better looking default horizontal rule + */ + +hr { + display: block; + height: 1px; + border: 0; + border-top: 1px solid #ccc; + margin: 1em 0; + padding: 0; +} + +/* + * Remove the gap between audio, canvas, iframes, + * images, videos and the bottom of their containers: + * https://github.com/h5bp/html5-boilerplate/issues/440 + */ + +audio, +canvas, +iframe, +img, +svg, +video { + vertical-align: middle; +} + +/* + * Remove default fieldset styles. + */ + +fieldset { + border: 0; + margin: 0; + padding: 0; +} + +/* + * Allow only vertical resizing of textareas. + */ + +textarea { + resize: vertical; +} + +/* ========================================================================== + Author's custom styles + ========================================================================== */ + +/* ========================================================================== + Helper classes + ========================================================================== */ + +/* + * Hide visually and from screen readers + */ + +.hidden, +[hidden] { + display: none !important; +} + +/* + * Hide only visually, but have it available for screen readers: + * https://snook.ca/archives/html_and_css/hiding-content-for-accessibility + * + * 1. For long content, line feeds are not interpreted as spaces and small width + * causes content to wrap 1 word per line: + * https://medium.com/@jessebeach/beware-smushed-off-screen-accessible-text-5952a4c2cbfe + */ + +.visually-hidden { + border: 0; + clip: rect(0, 0, 0, 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + white-space: nowrap; + width: 1px; + /* 1 */ +} + +/* + * Extends the .visually-hidden class to allow the element + * to be focusable when navigated to via the keyboard: + * https://www.drupal.org/node/897638 + */ + +.visually-hidden.focusable:active, +.visually-hidden.focusable:focus { + clip: auto; + height: auto; + margin: 0; + overflow: visible; + position: static; + white-space: inherit; + width: auto; +} + +/* + * Hide visually and from screen readers, but maintain layout + */ + +.invisible { + visibility: hidden; +} + +/* + * Clearfix: contain floats + * + * The use of `table` rather than `block` is only necessary if using + * `::before` to contain the top-margins of child elements. + */ + +.clearfix::before, +.clearfix::after { + content: ""; + display: table; +} + +.clearfix::after { + clear: both; +} + +/* ========================================================================== + EXAMPLE Media Queries for Responsive Design. + These examples override the primary ('mobile first') styles. + Modify as content requires. + ========================================================================== */ + +@media only screen and (min-width: 35em) { + /* Style adjustments for viewports that meet the condition */ +} + +@media print, + (-webkit-min-device-pixel-ratio: 1.25), + (min-resolution: 1.25dppx), + (min-resolution: 120dpi) { + /* Style adjustments for high resolution devices */ +} + +/* ========================================================================== + Print styles. + Inlined to avoid the additional HTTP request: + https://www.phpied.com/delay-loading-your-print-css/ + ========================================================================== */ + +@media print { + *, + *::before, + *::after { + background: #fff !important; + color: #000 !important; + /* Black prints faster */ + box-shadow: none !important; + text-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]::after { + content: " (" attr(href) ")"; + } + + abbr[title]::after { + content: " (" attr(title) ")"; + } + + /* + * Don't show links that are fragment identifiers, + * or use the `javascript:` pseudo protocol + */ + a[href^="#"]::after, + a[href^="javascript:"]::after { + content: ""; + } + + pre { + white-space: pre-wrap !important; + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + + tr, + img { + page-break-inside: avoid; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } +} + + +#test-table { + overflow: auto; + width: 100%; +} + +#test-table table { + border: 1px solid #dededf; + height: 100%; + width: 100%; + table-layout: fixed; + border-collapse: collapse; + border-spacing: 1px; + text-align: left; +} + +#test-table caption { + caption-side: top; + text-align: left; +} + +#test-table th { + border: 1px solid #dededf; + background-color: #eceff1; + color: #000000; + padding: 5px; +} + +#test-table td { + border: 1px solid #dededf; + background-color: #ffffff; + color: #000000; + padding: 5px; +} + + +.topnav { + overflow: hidden; + background-color: #333; + display:flex; + justify-content: space-between; + width: 100%; +} + +.topnav-left { + flex-grow: 1; +} + +.topnav-center { + flex-grow: 4; + color: #f2f2f2; + text-align: left; + padding: 12px 16px; + font-size: 36; + font-weight: bold; +} + +.topnav-right { + flex-grow: 1; +} + +.topnav a { + float: left; + color: #f2f2f2; + text-align: center; + padding: 12px 16px; + text-decoration: none; + font-size: 17px; +} + +.topnav a:hover { + background-color: #ddd; + color: black; +} + +.topnav a.active { + background-color: #04AA6D; + color: white; +} + +.title { + overflow: hidden; + background-color: #ccc; +} + +button { + font-family: 'Courier New', monospace; +} + +.topnav button { + color: white; + background-color: green; + border-radius: 12px; + border: 1px solid gray; + padding: 10px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + margin: 4px 2px; + cursor: pointer; +} + +#test-data { + padding: 10px; +} + diff --git a/LocalModules/ExampleWebApp/site/eslint.config.mjs b/LocalModules/ExampleWebApp/site/eslint.config.mjs new file mode 100644 index 00000000..3516dd33 --- /dev/null +++ b/LocalModules/ExampleWebApp/site/eslint.config.mjs @@ -0,0 +1,21 @@ +import globals from "globals"; +import pluginJs from "@eslint/js"; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; + + +export default [ + { + languageOptions: { + globals: globals.browser + } + }, + pluginJs.configs.recommended, + { + ignores: ["dist", "config-sample.js", "js/vendor"] + }, + { + plugins: { + "prettier": eslintPluginPrettierRecommended + } + } +]; diff --git a/LocalModules/ExampleWebApp/site/favicon.ico b/LocalModules/ExampleWebApp/site/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..be74abd69ad6a32de7375df13cab9354798e328f GIT binary patch literal 766 zcmc(dze~eV5XUd2fg&jH87YDYDQKxq1{4b-_ydP-wqS9vgGh17QXQQAwOE{VaBvi* zmu^z9t({y-&1ey8Y_x;?x+`BviV9;aRjMgZ8M*!jgkRrFqSI9;F zHyfX@Az|AvVmn~YWWZP`0&JWEY~BFm?*Vq}VD7&_%x%MP$p`D`4JMC!K|B7pt?Mmp zUJAB7rxMXS6=!P+AtLU9V)J#61WPxwipRXCHO{BJ`l{m53#=t97a!znv~vfmr|AaP zRGIT7#0FyJy3Z*hL{GQp-0TRhX8UzZ)+>%?mK0^goaX4Q;xkoG_P!lF_+$k;;moo~MP{oen-d(OT0oZo$ZzjM$1-E*GjiAI^|v4I7_3=9lx2Kq=d`u^#E zCkQ}aCz}pY3=AC44Uk%QpD?bZhX)E73`U%~7cQl85yvVXk(g;IUxeEq-gAn`O#OGr z&lO(^T_yYT*6U?-xaqF>yQNy04owaZTcEWi85lr72tIo@UX7J#rUIBw~lJs={CYso;p)qnluna#RzIviF z@XZ9aXw3jLGsYiRA9QXSBQ0g8nvapf3GwJckKb+73+ey{f{A?pDZc1rRRp8J`eE{{ z12JxDMe;-X+2{~b8vxQ@V~^p|pRo&j-hK3?0HH28U;1{w2ngwys?NrTti=FA`$T!T zWQv0~2?Ezt()hh=Kb*P7^9=EYtP-u>vPa~wP}kMQgxcDviA`8}&)>U$C)PUNAT?UbMk-poy=~^hjqHKnI3X=cZp-P;XD9*pla$REW99w&It*Gp-~k}4%869y z&75l9QBIC77(y%a7F0CpRI&mOI)cS|2G^(9c}R(dLg1mseUuF!Fip+R_aKwl4%%uj zlX2n}oG-heo>Vvj?eAXyA{Zm*=ok=ZaL!#;5M1SRAVx>y)(5UJAJiEcBVWyKd1#y?aD-LnG zMC`33$8jx0dMU7WPc|M#u(wXi0@GOU?K}P^?X3|lR7|v=Vg^wZDpj2x*3|TGKydpR zU&=<7K9K64`B2||j*v}!6l4rM7?yl-rb=#F2`gn$il9VYQWGV$TuoDx$aZ)EpYE|T5oqmIZ63g_SCfJl zigFx_JCR8xHRqU%GC!Uph;`N2VafmeCiuy_~JBx;+d|yA`?-``$ zhxc*=UqF&=VcN!=!6Mvdc`vdMB}S@H6mb6}R5=v^$0bKcrSg|eD>sgOqy1k2yyl%& zUe>(103E_Fj1uq#@8g4s41<|rHi>NOHhSwSA7`dOQP{oc`Xw0Pc*N+%8U68yES-U; zZD2x$?}_*Qlbf#CvoPL?P#r>%tuqvbd{Uvle~>rZ)hFUM?dw5rky$kpLCpTdLll3^ zXZr$pIZO43yL03_r_{_`U&f3_di1vvm2=Dq5u%(@TMZvOn#L}zr;nyajZs%_#BoTb z{)KobkGI6J!cuc{b+j>#?I?=FV`jjuhNE%wA1jIRvPo>r3A$G~rM`w>C!7g58si{? zLQ8)ZXH9z;0)LdYVFei{_T?E!?Y#W{_?@Q?CH@sWk+`(*B>21SVO;MNlO-4sPAqHs zqy^eyDN`i1?}%)k<>Cj?FX!;lSi5Ohfw!PKR1^^YQsn-LFe0R<_2FS$%ELqUkH_)w z3xM!w@1`%GS+_XWq>V_Z?`qzA;z{m-5L9hvCet0g_B=k0x`a{VGG#ftNCb50q?<5t z33^P3jw0V1Yb^JnB^W$+Iv<9Zer(Qsr5}&WRqX3w0VTzegy62fX72hEiI(=sa26q= z)wefEn@1-cNoK+T!UpR>VX$V%IZ*T9T{ne`3EmK=1ULUlS=CxZ3jZ6J2X(B__#H}u zR%riaMEc0t8(8%S(rt8m2$$>%ydzm(N4`JVyEXDAgp`_p2h!NOPYAuf^)Yh_+mH@7`kD{Hn!h2huDs=X zG~T&{anJTiR^{xs*_pZ^Bw5JQaHvkMSh`%)x#y2KN~lGxhZQ066kuiDyDczU=) z&fBobH^_VT$}MzY+-cvejN{j`=Bom;LC^dg61vtWGqz?bUbO0IUbMfUiov zhNP$Hm~$v4zeNX~gU3bVpP1hFP!##}?w8}veUZnmEhf$_RwyTh&?jk`lS)iod{%nN zCDwMY1g2L=AyvH$;l_|j)f4ZjHa``o@d{p7hmQ_{n8C-vuwZ0&mb`0^NU32E-B>SILeRoh9rqKOJZQ{fy^efaIdh8pI0Ccek}X1~4_dfdSAXl^`xW&0 zRLQiIbI$6*qCn{BZ;QMRp;mu3iCD9aa7zZ)4Drx)Q5(XR-wyg>Pj|((eyp6WbVgF$ z=f4MUa-G+&y!!%YktS=44wUQ180A0xVH0AVf?cT3u_f>9t`$|XiE4P}Ev?YOBuK&q@s$TeB3c?&tYER|*Y-)x?(ci?lb^UT`!w*o<%7=&3B4=_t_f z)U}wHZp`VGeJN}EitL8{%XAY#(Z8E_{F}+bnAB$YR`Gsj@|E#tT`IzpYT1_L9eCzT z+iJ^DdaFItX#@mUk>O!+%@FPACreI`7sOT6VT)x$MROOPuQ|SVL*7@#L{NrI|LXLF z1ED|(BYOfO9CjOjWdPk!xbW(usP75SZf1!e$u zS5lD(B2ID&qstc|OmUOAcxZh}V%`mU4=KyXLhdlw!tV<6* z!{({uC}VTS)PvsluA)2QJxqiYg9I5Uw?1unxjts~B&sILmy54e?>c9C*p0nT$JI4FWg+m7gHWD^F3*qM$hS1!6qWsQ6%huv8 zqayPU6s0#dn9h1fzuMO#EGT8y+m#?RTDO%#2U-UL=jlH^$pRN5HoiS)w;sB@IGf#2 z#ZEZ7$jC=^nM*y>UE+BGJk;cO7O>w|W5-s5@b*+-(K_K=ae?I+rj7d-MqL-{cMpB8K z3%N%-Mg;hNFnbRM_p3Ua(Nb2hVjc`*CKg3fhd zK0taD|LFDphqeZHlgbJLoj&Oj{xOlIwJB_cCJP2V8-W^Qg`xFT@lrNhDZn$Tbf)ck~d=i9dg z%e8`DpHvS-%kx_fo<6%YroouuIX2L*)NSzLht1l4SnMnf$z^)`tYwIZ#5+B#RZXuT z7$zh^N0=V#BToN#l4CAT$OpSz8(Vt6vNu(|{=wXs>Bqg6mR7g6aKzMJV`8k`F_B}q zSSYfdE6_?(0C>48wb@fBuYT>b;nG&DIkk0_ey>j<5hmC3OGww&bQiLQ{bc%@kGV7d z&|pHlZxX!hYS{@l|7@t=4p}RiuUaH5H7NxD!nV|KG|U?jA82`&_Bmv6FB zr8HvTl(3*bmo#xpQz>&EWjeIWt|BIi%!CZ5Du?ArFJBd}4D`+lMfAvSGk;B^DcCeW z*N6s~OA`TMK}MnLeuvWm(3I0d%w|{+rBQ6JZ)(pNqE=|*R>1#wcU1^{7w&%nAmUKp z*0RsNE$jO0#B8SvDk$sL^!@PUxKDVZWFVd{&U@r7ue3u!x%(Ub@0{0{kNwJ3m&~t@ zhK$v$xyvmjVJGQ7zl4#X`&tTEjUJ)DPuAre+obOLcP{GUp1WRf6<&SO00boVGwx{} z$C6~jZvWsSU$33wN`3ih6q%X8-Y?!AYXag}(!?_Mdxv(fwVLIDfr4SO4?DuE;Vc18 zV4;LrlF2rm%SWoA{ny?n6h=44=3@RL8=AWe2#QR08P}QUKu(pZEZZQ?u3B>J{Q;MX zh#gLS_au{=AR5bd6qga)JjV=w9`KY}fWNy~Ka0YWymJO{=Yn>mfg2I&fpZ=h9EvF5 zt9qEEYa-1=kf6Hwy8c diff --git a/LocalModules/ExampleWebApp/site/img/.gitkeep b/LocalModules/ExampleWebApp/site/img/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/LocalModules/ExampleWebApp/site/index.html b/LocalModules/ExampleWebApp/site/index.html new file mode 100644 index 00000000..54f4a2ac --- /dev/null +++ b/LocalModules/ExampleWebApp/site/index.html @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ Home +
+
+ Web App Sample +
+
+ + +
+
+ +
+ + +

This app demonstrates the creation of a complete serverless web + application using the AWS ClI and the aws cloudformation + package and deploy commands. + +

The CloudFormation template, webapp.yaml, makes use of + the new local modules functionality to break down the template into + re-usable components

+ +
+ +
+ + + + + + + + + + + diff --git a/LocalModules/ExampleWebApp/site/js/.gitignore b/LocalModules/ExampleWebApp/site/js/.gitignore new file mode 100644 index 00000000..e9550ef7 --- /dev/null +++ b/LocalModules/ExampleWebApp/site/js/.gitignore @@ -0,0 +1,3 @@ +config.js + + diff --git a/LocalModules/ExampleWebApp/site/js/app.js b/LocalModules/ExampleWebApp/site/js/app.js new file mode 100644 index 00000000..44d0e3bf --- /dev/null +++ b/LocalModules/ExampleWebApp/site/js/app.js @@ -0,0 +1,103 @@ +// This is a minimal example of how to handle the auth flow with Cognito and +// then interact with DynamoDB once the user is logged in. + +import { checkAuthCode } from "./auth" +import { LOGIN_URL, LOGOUT_URL } from "./config" +import * as restApi from "./rest-api" + +(async function main() { + + const loginBtn = get("login-btn") + loginBtn.onclick = function() { + location.href = LOGIN_URL; + } + + const logoutBtn = get("logout-btn") + logoutBtn.onclick = function() { + location.href = LOGOUT_URL + } + + const testBtn = get("test-btn") + testBtn.onclick = async function() { + hide("about") + show("test-data") + show("loading") + const data = await restApi.get("test", null, null, true) + console.log("test data: " + JSON.stringify(data, null, 0)) + hide("loading") + /* + * [ + * { + * "foo": { + * "Value":"1" + * }, + * "id": { + * "Value":"abc" + * } + * }, + * { + * "foo": { + * "Value":"2" + * }, + * "id": { + * "Value":"def" + * } + * } + * ] + */ + const tbl = get("test-table") + // Clear prior data + for (let i = tbl.rows.length - 1; i > 0; i--) { + tbl.deleteRow(i) + } + for (let i = 0; i < data.length; i++) { + const d = data[i] + const id = d.id.Value + let foo = "" + if (d.Foo) { + foo = d.Foo.Value + } + let bar = "" + if (d.Bar) { + bar = d.Bar.Value + } + // Create a new table row + const row = tbl.insertRow() + const idCell = row.insertCell(0) + idCell.innerHTML = id + const fooCell = row.insertCell(1) + fooCell.innerHTML = foo + const barCell = row.insertCell(2) + barCell.innerHTML = bar + } + } + + // Check to see if we're logged in + const isCognitoRedirect = await checkAuthCode() + if (isCognitoRedirect) { + console.log("Cognito redirect") + return // checkAuthCode does a redirect to / + } else { + console.log("Not a Cognito redirect") + + } + +})() + +function get(id) { + return document.getElementById(id) +} + +function show(id) { + const elem = get(id) + if (elem) { + elem.style.display = "block"; + } +} + +function hide(id) { + const elem = get(id) + if (elem) { + elem.style.display = "none"; + } +} diff --git a/LocalModules/ExampleWebApp/site/js/auth.js b/LocalModules/ExampleWebApp/site/js/auth.js new file mode 100644 index 00000000..7ccae4ff --- /dev/null +++ b/LocalModules/ExampleWebApp/site/js/auth.js @@ -0,0 +1,95 @@ +import { getParameterByName } from "./web-util" +import * as restApi from "./rest-api" +import Cookies from "js-cookie" + +/** + * Set a secure cookie. + * + * @param {*} name + * @param {*} value + */ +function setCookie(name, value) { + console.log(`Setting cookie ${name} ${value}`) + + if (window.location.hostname === "localhost") { + Cookies.set(name, value) + } else { + Cookies.set(name, value, { secure: true, sameSite: "strict" }) + } +} + +/** + * Get the value of a cookie. + * + * @param {*} name + * @returns + */ +function getCookie(name) { + return Cookies.get(name) +} + +/** + * Delete a cookie. + * + * @param {*} name + */ +function removeCookie(name) { + Cookies.remove(name) +} + +/** + * Save data from the JWT token. + */ +function setAuthCookies(data) { + + const idToken = data.idToken + const refreshToken = data.refreshToken + const expiresIn = data.expiresIn + + console.info(expiresIn) + + setCookie("jwt.id", idToken) + + // Put the jwt in a link so it's easy to copy out for local dev + const targetEnvDiv = document.getElementById("target-env") + if (targetEnvDiv) { + targetEnvDiv.innerHTML = `jwt` + document.getElementById("jwt-modal-content").innerHTML = refreshToken + } + + setCookie("jwt.refresh", refreshToken) + const exp = new Date() + const totalSeconds = exp.getSeconds() + expiresIn + + console.info(totalSeconds) + + exp.setSeconds(totalSeconds) + setCookie("jwt.expires", exp.toISOString()) + setCookie("username", data.username) + + console.log("JWT cookies set") +} + +/** + * Check to see if this request is Cognito sending us the auth code redirect. + */ +async function checkAuthCode() { + const code = getParameterByName("code") + + if (code) { + console.log("Found code in query string: " + code) + + const data = await restApi.get(`jwt?code=${code}`, null, null, false) + console.log("jwt response: " + JSON.stringify(data, null, 0)) + + setAuthCookies(data) + + // Redirect to the bare URL without code= + window.location = "/" + return true + } + + return false +} + +export { checkAuthCode, setAuthCookies, setCookie, getCookie, removeCookie } diff --git a/LocalModules/ExampleWebApp/site/js/config-sample.js b/LocalModules/ExampleWebApp/site/js/config-sample.js new file mode 100644 index 00000000..e1678231 --- /dev/null +++ b/LocalModules/ExampleWebApp/site/js/config-sample.js @@ -0,0 +1,16 @@ +// Populate these values for local development +const APIGATEWAY_URL = "" // Get this after deploying +const REDIRECT_URI = "" // https://your-custom-domain.something +const COGNITO_DOMAIN = "" // your-custom-domain-replace-dots-with-dashes +const REGION = "" +const APP_CLIENT_ID = "" // Get this after deploying + +// Leave these alone +const COGNITO_URL = `https://${COGNITO_DOMAIN}.auth.${REGION}.amazoncognito.com` +const PARAMS = `?response_type=code&client_id=${APP_CLIENT_ID}&redirect_uri=${REDIRECT_URI}` +const LOGIN_URL = `${COGNITO_URL}/login${PARAMS}` +const LOGOUT_URL = `${COGNITO_URL}/logout${PARAMS}` + +const LOCAL_JWT = "" // Only populate this for local dev, copy jwt.id from a deployed instance + +export { APIGATEWAY_URL, LOGIN_URL, LOGOUT_URL, LOCAL_JWT } diff --git a/LocalModules/ExampleWebApp/site/js/config-template.js b/LocalModules/ExampleWebApp/site/js/config-template.js new file mode 100644 index 00000000..8fb750b5 --- /dev/null +++ b/LocalModules/ExampleWebApp/site/js/config-template.js @@ -0,0 +1,15 @@ +// Template variables will be replaced by buildsite.sh +const APIGATEWAY_URL = "__APIGW__" +const REDIRECT_URI = "__REDIRECT__" +const COGNITO_DOMAIN = "__DOMAIN__" +const APP_CLIENT_ID = "__APPCLIENT__" + +const REGION = "us-east-1" +const COGNITO_URL = `https://${COGNITO_DOMAIN}.auth.${REGION}.amazoncognito.com` +const PARAMS = `?response_type=code&client_id=${APP_CLIENT_ID}&redirect_uri=${REDIRECT_URI}` +const LOGIN_URL = `${COGNITO_URL}/login${PARAMS}` +const LOGOUT_URL = `${COGNITO_URL}/logout${PARAMS}` + +const LOCAL_JWT = "" + +export { APIGATEWAY_URL, LOGIN_URL, LOGOUT_URL, LOCAL_JWT } diff --git a/LocalModules/ExampleWebApp/site/js/containers.js b/LocalModules/ExampleWebApp/site/js/containers.js new file mode 100644 index 00000000..2fb35f58 --- /dev/null +++ b/LocalModules/ExampleWebApp/site/js/containers.js @@ -0,0 +1,12 @@ +/** + * Show and hide top level containers. + * + * @param name - The container to show. Hide the others. + */ +export function showHide(name) { + const containers = ["grid", "canvas", "expand", "parts"] + for (const c of containers) { + document.getElementById(c + "-container").style.display = (name === c) ? "block" : "none" + } + document.getElementById("props").style.display = "none" +} diff --git a/LocalModules/ExampleWebApp/site/js/plugins.js b/LocalModules/ExampleWebApp/site/js/plugins.js new file mode 100644 index 00000000..feb7d19e --- /dev/null +++ b/LocalModules/ExampleWebApp/site/js/plugins.js @@ -0,0 +1,24 @@ +// Avoid `console` errors in browsers that lack a console. +(function() { + var method; + var noop = function () {}; + var methods = [ + 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', + 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', + 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', + 'timeline', 'timelineEnd', 'timeStamp', 'trace', 'warn' + ]; + var length = methods.length; + var console = (window.console = window.console || {}); + + while (length--) { + method = methods[length]; + + // Only stub undefined methods. + if (!console[method]) { + console[method] = noop; + } + } +}()); + +// Place any jQuery/helper plugins in here. diff --git a/LocalModules/ExampleWebApp/site/js/rest-api.js b/LocalModules/ExampleWebApp/site/js/rest-api.js new file mode 100644 index 00000000..0327e08b --- /dev/null +++ b/LocalModules/ExampleWebApp/site/js/rest-api.js @@ -0,0 +1,159 @@ +import { APIGATEWAY_URL, LOGIN_URL, LOCAL_JWT } from "./config" +import Cookies from "js-cookie" +import { setAuthCookies } from "./auth" + +/** + * Common options for fetch requests + */ +const fetchOptions = { + mode: "cors", + cache: "no-cache", + credentials: "same-origin", + headers: { + "Content-Type": "application/json", + }, +} + +/** + * Check to see if the response has an error. + * + * @param {*} response + */ +async function checkError(response) { + /* + if (response.status === 401) { + // User is not logged in, send them to Cognito + console.log("Got a 401 from API Gateway, redirecting to Cognito") + if (window.location.hostname === "localhost") { + console.error("Not redirecting to Cognito from localhost") + } else { + window.location = LOGIN_URL + } + return + } + */ + if (response.status >= 400 && response.status < 600) { + console.info("response error", response) + const txt = await response.text() + throw new Error("Request failed: " + txt) + } +} + +/** + * Get the JWT from cookies and add it to headers. + * + * @param {*} options + */ +async function addAuthHeader(options) { + + let jwt = Cookies.get("jwt.id") + if (!jwt) { + // User is not logged in + console.log("Not logged in") + + if (window.location.hostname === "localhost") { + if (LOCAL_JWT) { + // Set an old expiration so that we force a refresh of the token + Cookies.set("jwt.expires", "2022-01-01T17:51:37.639Z") + } else { + // We can't do anything else here, since we need a real refresh token + console.log("Not redirecting to Cognito from localhost, populate LOCAL_JWT with a refresh token") + return + } + } else { + // Redirect to Cognito + window.location = LOGIN_URL + return + } + } + + // Refresh the token if it is expired + const expiration = Cookies.get("jwt.expires") + if (expiration) { + const expires = new Date(expiration) + if (expires < new Date()) { + let refresh = Cookies.get("jwt.refresh") + + if (window.location.hostname === "localhost") { + refresh = LOCAL_JWT + Cookies.set("jwt.refresh", jwt) + } + + console.log("Refreshing jwt token: " + refresh) + + // Refresh the token + const data = await get(`jwt?refresh=${refresh}`, null, false) + console.log("jwt refresh response: " + JSON.stringify(data, null, 0)) + + setAuthCookies(data) + jwt = Cookies.get("jwt.id") + } + } + + options.headers.Authorization = "Bearer " + jwt +} + +const PARTITION_HEADER = "X-KG-Partition" + +/** + * POST data to a REST API. + */ +async function post(resource, data = {}, partition) { + const slash = APIGATEWAY_URL.endsWith("/") ? "" : "/" + const url = APIGATEWAY_URL + slash + resource + const options = Object.assign({}, fetchOptions) + options.body = JSON.stringify(data) + options.method = "POST" + await addAuthHeader(options) + options.headers[PARTITION_HEADER] = partition + const response = await fetch(url, options) + await checkError(response) + return response.json() +} + +/** + * Delete a resource by id + * + * @param {*} resource + * @param {*} id + * @returns + */ +async function del(resource, id, partition) { + const slash = APIGATEWAY_URL.endsWith("/") ? "" : "/" + const url = APIGATEWAY_URL + slash + resource + "/" + id + const options = Object.assign({}, fetchOptions) + options.method = "DELETE" + await addAuthHeader(options) + options.headers[PARTITION_HEADER] = partition + const response = await fetch(url, options) + await checkError(response) + return response.json() +} + +/** + * Get a resource from the rest api. + * + * @param {*} resource + * @param {*} id + * @returns + */ +async function get(resource, id, partition, authorize = false) { + const slash = APIGATEWAY_URL.endsWith("/") ? "" : "/" + let url = APIGATEWAY_URL + slash + resource + if (id) { + url += ("/" + id) + } + const options = Object.assign({}, fetchOptions) + options.method = "GET" + if (authorize) { + await addAuthHeader(options) + } + if (partition) { + options.headers[PARTITION_HEADER] = partition + } + const response = await fetch(url, options) + await checkError(response) + return response.json() +} + +export { post, del, get } diff --git a/LocalModules/ExampleWebApp/site/js/vendor/modernizr-3.11.2.min.js b/LocalModules/ExampleWebApp/site/js/vendor/modernizr-3.11.2.min.js new file mode 100644 index 00000000..feada515 --- /dev/null +++ b/LocalModules/ExampleWebApp/site/js/vendor/modernizr-3.11.2.min.js @@ -0,0 +1,3 @@ +/*! modernizr 3.11.2 (Custom Build) | MIT * + * https://modernizr.com/download/?-cssanimations-csscolumns-customelements-flexbox-history-picture-pointerevents-postmessage-sizes-srcset-webgl-websockets-webworkers-addtest-domprefixes-hasevent-mq-prefixedcssvalue-prefixes-setclasses-testallprops-testprop-teststyles !*/ +!function(e,t,n,r){function o(e,t){return typeof e===t}function i(e){var t=_.className,n=Modernizr._config.classPrefix||"";if(S&&(t=t.baseVal),Modernizr._config.enableJSClass){var r=new RegExp("(^|\\s)"+n+"no-js(\\s|$)");t=t.replace(r,"$1"+n+"js$2")}Modernizr._config.enableClasses&&(e.length>0&&(t+=" "+n+e.join(" "+n)),S?_.className.baseVal=t:_.className=t)}function s(e,t){if("object"==typeof e)for(var n in e)k(e,n)&&s(n,e[n]);else{e=e.toLowerCase();var r=e.split("."),o=Modernizr[r[0]];if(2===r.length&&(o=o[r[1]]),void 0!==o)return Modernizr;t="function"==typeof t?t():t,1===r.length?Modernizr[r[0]]=t:(!Modernizr[r[0]]||Modernizr[r[0]]instanceof Boolean||(Modernizr[r[0]]=new Boolean(Modernizr[r[0]])),Modernizr[r[0]][r[1]]=t),i([(t&&!1!==t?"":"no-")+r.join("-")]),Modernizr._trigger(e,t)}return Modernizr}function a(){return"function"!=typeof n.createElement?n.createElement(arguments[0]):S?n.createElementNS.call(n,"http://www.w3.org/2000/svg",arguments[0]):n.createElement.apply(n,arguments)}function l(){var e=n.body;return e||(e=a(S?"svg":"body"),e.fake=!0),e}function u(e,t,r,o){var i,s,u,f,c="modernizr",d=a("div"),p=l();if(parseInt(r,10))for(;r--;)u=a("div"),u.id=o?o[r]:c+(r+1),d.appendChild(u);return i=a("style"),i.type="text/css",i.id="s"+c,(p.fake?p:d).appendChild(i),p.appendChild(d),i.styleSheet?i.styleSheet.cssText=e:i.appendChild(n.createTextNode(e)),d.id=c,p.fake&&(p.style.background="",p.style.overflow="hidden",f=_.style.overflow,_.style.overflow="hidden",_.appendChild(p)),s=t(d,e),p.fake?(p.parentNode.removeChild(p),_.style.overflow=f,_.offsetHeight):d.parentNode.removeChild(d),!!s}function f(e,n,r){var o;if("getComputedStyle"in t){o=getComputedStyle.call(t,e,n);var i=t.console;if(null!==o)r&&(o=o.getPropertyValue(r));else if(i){var s=i.error?"error":"log";i[s].call(i,"getComputedStyle returning null, its possible modernizr test results are inaccurate")}}else o=!n&&e.currentStyle&&e.currentStyle[r];return o}function c(e,t){return!!~(""+e).indexOf(t)}function d(e){return e.replace(/([A-Z])/g,function(e,t){return"-"+t.toLowerCase()}).replace(/^ms-/,"-ms-")}function p(e,n){var o=e.length;if("CSS"in t&&"supports"in t.CSS){for(;o--;)if(t.CSS.supports(d(e[o]),n))return!0;return!1}if("CSSSupportsRule"in t){for(var i=[];o--;)i.push("("+d(e[o])+":"+n+")");return i=i.join(" or "),u("@supports ("+i+") { #modernizr { position: absolute; } }",function(e){return"absolute"===f(e,null,"position")})}return r}function m(e){return e.replace(/([a-z])-([a-z])/g,function(e,t,n){return t+n.toUpperCase()}).replace(/^-/,"")}function h(e,t,n,i){function s(){u&&(delete N.style,delete N.modElem)}if(i=!o(i,"undefined")&&i,!o(n,"undefined")){var l=p(e,n);if(!o(l,"undefined"))return l}for(var u,f,d,h,A,v=["modernizr","tspan","samp"];!N.style&&v.length;)u=!0,N.modElem=a(v.shift()),N.style=N.modElem.style;for(d=e.length,f=0;f { + console.log(`About to clear timeout ${timer}`) + clearTimeout(timer) + timer = setTimeout(() => { + console.log("Calling debounce timeout function") + f.apply(this, args) + }, t) + } +} + +export { getParameterByName, debounce } diff --git a/LocalModules/ExampleWebApp/site/package-lock.json b/LocalModules/ExampleWebApp/site/package-lock.json new file mode 100644 index 00000000..38654653 --- /dev/null +++ b/LocalModules/ExampleWebApp/site/package-lock.json @@ -0,0 +1,2130 @@ +{ + "name": "js", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "js", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "axios": "^1.7.7", + "js-cookie": "^3.0.5", + "vite": "^5.4.6" + }, + "devDependencies": { + "@eslint/js": "^9.10.0", + "eslint": "^9.10.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "globals": "^15.9.0", + "prettier": "^3.3.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", + "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz", + "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz", + "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.10.0", + "@eslint/plugin-kit": "^0.1.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", + "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", + "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/LocalModules/ExampleWebApp/site/package.json b/LocalModules/ExampleWebApp/site/package.json new file mode 100644 index 00000000..1b4dd046 --- /dev/null +++ b/LocalModules/ExampleWebApp/site/package.json @@ -0,0 +1,26 @@ +{ + "name": "js", + "version": "1.0.0", + "description": "", + "main": "app.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "lint": "eslint --fix", + "build": "vite build" + }, + "author": "", + "license": "ISC", + "dependencies": { + "axios": "^1.7.7", + "js-cookie": "^3.0.5", + "vite": "^5.4.6" + }, + "devDependencies": { + "@eslint/js": "^9.10.0", + "eslint": "^9.10.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "globals": "^15.9.0", + "prettier": "^3.3.3" + } +} diff --git a/LocalModules/ExampleWebApp/site/robots.txt b/LocalModules/ExampleWebApp/site/robots.txt new file mode 100644 index 00000000..d0e5f1be --- /dev/null +++ b/LocalModules/ExampleWebApp/site/robots.txt @@ -0,0 +1,5 @@ +# www.robotstxt.org/ + +# Allow crawling of all content +User-agent: * +Disallow: diff --git a/LocalModules/ExampleWebApp/site/site.webmanifest b/LocalModules/ExampleWebApp/site/site.webmanifest new file mode 100644 index 00000000..222ae169 --- /dev/null +++ b/LocalModules/ExampleWebApp/site/site.webmanifest @@ -0,0 +1,12 @@ +{ + "short_name": "", + "name": "", + "icons": [{ + "src": "icon.png", + "type": "image/png", + "sizes": "192x192" + }], + "start_url": "/?utm_source=homescreen", + "background_color": "#fafafa", + "theme_color": "#fafafa" +} diff --git a/LocalModules/ExampleWebApp/site/vite.config.js b/LocalModules/ExampleWebApp/site/vite.config.js new file mode 100644 index 00000000..84479d30 --- /dev/null +++ b/LocalModules/ExampleWebApp/site/vite.config.js @@ -0,0 +1,5 @@ +export default { + build: { + sourcemap: true + } +} diff --git a/LocalModules/ExampleWebApp/webapp-pkg.yaml b/LocalModules/ExampleWebApp/webapp-pkg.yaml new file mode 100644 index 00000000..f31404ee --- /dev/null +++ b/LocalModules/ExampleWebApp/webapp-pkg.yaml @@ -0,0 +1,891 @@ +Description: Creates a web application with a static website using S3 and CloudFront, + an API Gateway REST API, and a DynamoDB table, with Cognito authentication. Apache-2.0 + License. Adapt this template to your needs and thoruoughly test it before introducing + it in a production environment. **WARNING** This template will create resources + in your account that may incur billing charges. +Parameters: + AppName: + Type: String + Description: This name is used as a prefix for resource names + Default: awscli-cfn-webapp +Resources: + SiteOriginAccessControl: + Type: AWS::CloudFront::OriginAccessControl + Properties: + OriginAccessControlConfig: + Name: + Fn::Join: + - '' + - - Ref: AppName + - Fn::Select: + - 2 + - Fn::Split: + - / + - Ref: AWS::StackId + OriginAccessControlOriginType: s3 + SigningBehavior: always + SigningProtocol: sigv4 + SiteDistribution: + Type: AWS::CloudFront::Distribution + Metadata: + checkov: + skip: + - id: CKV_AWS_174 + comment: Using the default cloudfront certificate with no aliases + guard: + SuppressedRules: + - CLOUDFRONT_CUSTOM_SSL_CERTIFICATE + - CLOUDFRONT_ORIGIN_FAILOVER_ENABLED + - CLOUDFRONT_SNI_ENABLED + Properties: + DistributionConfig: + DefaultCacheBehavior: + CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 + Compress: true + TargetOriginId: + Fn::Sub: ${AppName}-origin-1 + ViewerProtocolPolicy: redirect-to-https + DefaultRootObject: index.html + Enabled: true + HttpVersion: http2 + IPV6Enabled: true + Logging: + Bucket: + Fn::GetAtt: + - SiteCloudFrontLogsBucket + - RegionalDomainName + Origins: + - DomainName: + Fn::GetAtt: + - SiteContentBucket + - RegionalDomainName + Id: + Fn::Sub: ${AppName}-origin-1 + OriginAccessControlId: + Fn::GetAtt: + - SiteOriginAccessControl + - Id + S3OriginConfig: + OriginAccessIdentity: '' + ViewerCertificate: + CloudFrontDefaultCertificate: true + WebACLId: + Fn::GetAtt: + - SiteWebACL + - Arn + SiteWebACL: + Type: AWS::WAFv2::WebACL + Properties: + Name: WebACLWithAMR + Scope: CLOUDFRONT + Description: Web ACL with AWS Managed Rules + DefaultAction: + Allow: {} + VisibilityConfig: + SampledRequestsEnabled: true + CloudWatchMetricsEnabled: true + MetricName: MetricForWebACLWithAMR + Tags: + - Key: Name + Value: + Ref: AppName + Rules: + - Name: AWS-AWSManagedRulesCommonRuleSet + Priority: 0 + OverrideAction: + None: {} + VisibilityConfig: + SampledRequestsEnabled: true + CloudWatchMetricsEnabled: true + MetricName: MetricForAMRCRS + Statement: + ManagedRuleGroupStatement: + VendorName: AWS + Name: AWSManagedRulesCommonRuleSet + ExcludedRules: + - Name: NoUserAgent_HEADER + SiteContentLogBucket: + Type: AWS::S3::Bucket + Metadata: + Comment: This bucket records access logs for the main bucket + checkov: + skip: + - comment: This is the log bucket + id: CKV_AWS_18 + guard: + SuppressedRules: + - S3_BUCKET_LOGGING_ENABLED + - S3_BUCKET_REPLICATION_ENABLED + Properties: + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + BucketName: + Fn::Sub: ${AppName}-content-logs-${AWS::Region}-${AWS::AccountId} + ObjectLockConfiguration: + ObjectLockEnabled: Enabled + Rule: + DefaultRetention: + Mode: COMPLIANCE + Years: 1 + ObjectLockEnabled: true + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + VersioningConfiguration: + Status: Enabled + SiteContentBucket: + Type: AWS::S3::Bucket + Metadata: + guard: + SuppressedRules: + - S3_BUCKET_DEFAULT_LOCK_ENABLED + Properties: + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + BucketName: + Fn::Sub: ${AppName}-content-${AWS::Region}-${AWS::AccountId} + LoggingConfiguration: + DestinationBucketName: + Ref: SiteContentLogBucket + ObjectLockEnabled: false + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + ReplicationConfiguration: + Role: + Fn::GetAtt: + - SiteContentReplicationRole + - Arn + Rules: + - Destination: + Bucket: + Fn::GetAtt: + - SiteContentReplicaBucket + - Arn + Status: Enabled + VersioningConfiguration: + Status: Enabled + SiteContentReplicaBucket: + Type: AWS::S3::Bucket + Metadata: + Comment: This bucket is used as a target for replicas from the main bucket + checkov: + skip: + - comment: This is the replica bucket + id: CKV_AWS_18 + guard: + SuppressedRules: + - S3_BUCKET_DEFAULT_LOCK_ENABLED + - S3_BUCKET_REPLICATION_ENABLED + - S3_BUCKET_LOGGING_ENABLED + Properties: + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + BucketName: + Fn::Sub: ${AppName}-content-replicas-${AWS::Region}-${AWS::AccountId} + ObjectLockEnabled: false + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + VersioningConfiguration: + Status: Enabled + SiteContentReplicationPolicy: + Type: AWS::IAM::RolePolicy + Properties: + PolicyDocument: + Statement: + - Action: + - s3:GetReplicationConfiguration + - s3:ListBucket + Effect: Allow + Resource: + Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-${AWS::Region}-${AWS::AccountId} + - Action: + - s3:GetObjectVersionForReplication + - s3:GetObjectVersionAcl + - s3:GetObjectVersionTagging + Effect: Allow + Resource: + Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-${AWS::Region}-${AWS::AccountId}/* + - Action: + - s3:ReplicateObject + - s3:ReplicateDelete + - s3:ReplicationTags + Effect: Allow + Resource: + Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-replicas-${AWS::Region}-${AWS::AccountId}/* + Version: '2012-10-17' + PolicyName: bucket-replication-policy + RoleName: + Ref: SiteContentReplicationRole + SiteContentReplicationRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - s3.amazonaws.com + Version: '2012-10-17' + Path: / + SiteContentLogBucketAccessPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: + Fn::Sub: ${AppName}-content-logs-${AWS::Region}-${AWS::AccountId} + PolicyDocument: + Statement: + - Action: s3:* + Condition: + Bool: + aws:SecureTransport: false + Effect: Deny + Principal: + AWS: '*' + Resource: + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-logs-${AWS::Region}-${AWS::AccountId} + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-logs-${AWS::Region}-${AWS::AccountId}/* + - Action: s3:* + Condition: + Bool: + aws:SecureTransport: false + Effect: Deny + Principal: + AWS: '*' + Resource: + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-logs-${AWS::Region}-${AWS::AccountId} + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-logs-${AWS::Region}-${AWS::AccountId}/* + - Action: s3:PutObject + Condition: + ArnLike: + aws:SourceArn: + Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-logs-${AWS::Region}-${AWS::AccountId}/* + StringEquals: + aws:SourceAccount: + Ref: AWS::AccountId + Effect: Allow + Principal: + Service: logging.s3.amazonaws.com + Resource: + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-logs-${AWS::Region}-${AWS::AccountId}/* + Version: '2012-10-17' + SiteContentBucketAccessPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: + Fn::Sub: ${AppName}-content-${AWS::Region}-${AWS::AccountId} + PolicyDocument: + Statement: + - Action: s3:* + Condition: + Bool: + aws:SecureTransport: false + Effect: Deny + Principal: + AWS: '*' + Resource: + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-${AWS::Region}-${AWS::AccountId} + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-${AWS::Region}-${AWS::AccountId}/* + - Action: s3:GetObject + Effect: Allow + Resource: + Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-${AWS::Region}-${AWS::AccountId}/* + Principal: + Service: cloudfront.amazonaws.com + Condition: + StringEquals: + AWS:SourceArn: + Fn::Sub: arn:aws:cloudfront::${AWS::AccountId}:distribution/${SiteDistribution.Id} + - Action: s3:* + Condition: + Bool: + aws:SecureTransport: false + Effect: Deny + Principal: + AWS: '*' + Resource: + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-${AWS::Region}-${AWS::AccountId} + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-${AWS::Region}-${AWS::AccountId}/* + Version: '2012-10-17' + SiteContentReplicaBucketAccessPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: + Fn::Sub: ${AppName}-content-replicas-${AWS::Region}-${AWS::AccountId} + PolicyDocument: + Statement: + - Action: s3:* + Condition: + Bool: + aws:SecureTransport: false + Effect: Deny + Principal: + AWS: '*' + Resource: + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-replicas-${AWS::Region}-${AWS::AccountId} + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-content-replicas-${AWS::Region}-${AWS::AccountId}/* + Version: '2012-10-17' + SiteCloudFrontLogsLogBucket: + Type: AWS::S3::Bucket + Metadata: + Comment: This bucket records access logs for the main bucket + checkov: + skip: + - comment: This is the log bucket + id: CKV_AWS_18 + guard: + SuppressedRules: + - S3_BUCKET_LOGGING_ENABLED + - S3_BUCKET_REPLICATION_ENABLED + Properties: + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + BucketName: + Fn::Sub: ${AppName}-cflogs-logs-${AWS::Region}-${AWS::AccountId} + ObjectLockConfiguration: + ObjectLockEnabled: Enabled + Rule: + DefaultRetention: + Mode: COMPLIANCE + Years: 1 + ObjectLockEnabled: true + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + VersioningConfiguration: + Status: Enabled + SiteCloudFrontLogsBucket: + Type: AWS::S3::Bucket + Metadata: + guard: + SuppressedRules: + - S3_BUCKET_DEFAULT_LOCK_ENABLED + Properties: + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + BucketName: + Fn::Sub: ${AppName}-cflogs-${AWS::Region}-${AWS::AccountId} + LoggingConfiguration: + DestinationBucketName: + Ref: SiteCloudFrontLogsLogBucket + ObjectLockEnabled: false + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + ReplicationConfiguration: + Role: + Fn::GetAtt: + - SiteCloudFrontLogsReplicationRole + - Arn + Rules: + - Destination: + Bucket: + Fn::GetAtt: + - SiteCloudFrontLogsReplicaBucket + - Arn + Status: Enabled + VersioningConfiguration: + Status: Enabled + OwnershipControls: + Rules: + - ObjectOwnership: BucketOwnerPreferred + SiteCloudFrontLogsReplicaBucket: + Type: AWS::S3::Bucket + Metadata: + Comment: This bucket is used as a target for replicas from the main bucket + checkov: + skip: + - comment: This is the replica bucket + id: CKV_AWS_18 + guard: + SuppressedRules: + - S3_BUCKET_DEFAULT_LOCK_ENABLED + - S3_BUCKET_REPLICATION_ENABLED + - S3_BUCKET_LOGGING_ENABLED + Properties: + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + BucketName: + Fn::Sub: ${AppName}-cflogs-replicas-${AWS::Region}-${AWS::AccountId} + ObjectLockEnabled: false + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + VersioningConfiguration: + Status: Enabled + SiteCloudFrontLogsReplicationPolicy: + Type: AWS::IAM::RolePolicy + Properties: + PolicyDocument: + Statement: + - Action: + - s3:GetReplicationConfiguration + - s3:ListBucket + Effect: Allow + Resource: + Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-cflogs-${AWS::Region}-${AWS::AccountId} + - Action: + - s3:GetObjectVersionForReplication + - s3:GetObjectVersionAcl + - s3:GetObjectVersionTagging + Effect: Allow + Resource: + Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-cflogs-${AWS::Region}-${AWS::AccountId}/* + - Action: + - s3:ReplicateObject + - s3:ReplicateDelete + - s3:ReplicationTags + Effect: Allow + Resource: + Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-cflogs-replicas-${AWS::Region}-${AWS::AccountId}/* + Version: '2012-10-17' + PolicyName: bucket-replication-policy + RoleName: + Ref: SiteCloudFrontLogsReplicationRole + SiteCloudFrontLogsReplicationRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - s3.amazonaws.com + Version: '2012-10-17' + Path: / + SiteCloudFrontLogsLogBucketAccessPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: + Fn::Sub: ${AppName}-cflogs-logs-${AWS::Region}-${AWS::AccountId} + PolicyDocument: + Statement: + - Action: s3:* + Condition: + Bool: + aws:SecureTransport: false + Effect: Deny + Principal: + AWS: '*' + Resource: + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-cflogs-logs-${AWS::Region}-${AWS::AccountId} + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-cflogs-logs-${AWS::Region}-${AWS::AccountId}/* + - Action: s3:* + Condition: + Bool: + aws:SecureTransport: false + Effect: Deny + Principal: + AWS: '*' + Resource: + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-cflogs-logs-${AWS::Region}-${AWS::AccountId} + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-cflogs-logs-${AWS::Region}-${AWS::AccountId}/* + - Action: s3:PutObject + Condition: + ArnLike: + aws:SourceArn: + Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-cflogs-logs-${AWS::Region}-${AWS::AccountId}/* + StringEquals: + aws:SourceAccount: + Ref: AWS::AccountId + Effect: Allow + Principal: + Service: logging.s3.amazonaws.com + Resource: + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-cflogs-logs-${AWS::Region}-${AWS::AccountId}/* + Version: '2012-10-17' + SiteCloudFrontLogsBucketAccessPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: + Fn::Sub: ${AppName}-cflogs-${AWS::Region}-${AWS::AccountId} + PolicyDocument: + Statement: + - Action: s3:* + Condition: + Bool: + aws:SecureTransport: false + Effect: Deny + Principal: + AWS: '*' + Resource: + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-cflogs-${AWS::Region}-${AWS::AccountId} + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-cflogs-${AWS::Region}-${AWS::AccountId}/* + Version: '2012-10-17' + SiteCloudFrontLogsReplicaBucketAccessPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: + Fn::Sub: ${AppName}-cflogs-replicas-${AWS::Region}-${AWS::AccountId} + PolicyDocument: + Statement: + - Action: s3:* + Condition: + Bool: + aws:SecureTransport: false + Effect: Deny + Principal: + AWS: '*' + Resource: + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-cflogs-replicas-${AWS::Region}-${AWS::AccountId} + - Fn::Sub: arn:${AWS::Partition}:s3:::${AppName}-cflogs-replicas-${AWS::Region}-${AWS::AccountId}/* + Version: '2012-10-17' + CognitoUserPool: + Type: AWS::Cognito::UserPool + Properties: + UserPoolName: + Ref: AppName + AdminCreateUserConfig: + AllowAdminCreateUserOnly: true + AutoVerifiedAttributes: + - email + Schema: + - Name: email + Required: true + - Name: given_name + Required: true + - Name: family_name + Required: true + DependsOn: SiteDistribution + CognitoDomain: + Type: AWS::Cognito::UserPoolDomain + Properties: + Domain: + Ref: AppName + UserPoolId: + Ref: CognitoUserPool + CognitoClient: + Type: AWS::Cognito::UserPoolClient + Properties: + ClientName: + Ref: AppName + GenerateSecret: false + UserPoolId: + Ref: CognitoUserPool + CallbackURLs: + - Fn::Sub: https://${SiteDistribution.DomainName}/index.html + AllowedOAuthFlows: + - code + AllowedOAuthFlowsUserPoolClient: true + AllowedOAuthScopes: + - phone + - email + - openid + SupportedIdentityProviders: + - COGNITO + RestApi: + Type: AWS::ApiGateway::RestApi + Properties: + Name: + Ref: AppName + RestApiDeployment: + Type: AWS::ApiGateway::Deployment + Properties: + RestApiId: + Ref: RestApi + DependsOn: + - TestResourceGet + - TestResourceOptions + - JwtResourceGet + - JwtResourceOptions + - TestResourceGet + - TestResourceOptions + - JwtResourceGet + - JwtResourceOptions + RestApiStage: + Type: AWS::ApiGateway::Stage + Properties: + RestApiId: + Ref: RestApi + DeploymentId: + Ref: RestApiDeployment + StageName: prod + RestApiAuthorizer: + Type: AWS::ApiGateway::Authorizer + Properties: + IdentitySource: method.request.header.authorization + Name: CognitoApiAuthorizer + ProviderARNs: + - Fn::GetAtt: + - CognitoUserPool + - Arn + RestApiId: + Ref: RestApi + Type: COGNITO_USER_POOLS + TestResourceHandler: + Type: AWS::Lambda::Function + Properties: + Handler: bootstrap + FunctionName: + Fn::Sub: ${AppName}-test-handler + Runtime: provided.al2023 + Code: + S3Bucket: ezbeard-rain-lambda + S3Key: 3ec7e9070c744aa03894d28946df416e + Role: + Fn::GetAtt: + - TestResourceHandlerRole + - Arn + Environment: + Variables: + TABLE_NAME: + Ref: TestDataTable + TestResourceHandlerRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + TestResourceResource: + Type: AWS::ApiGateway::Resource + Properties: + ParentId: + Fn::Sub: ${RestApi.RootResourceId} + PathPart: test + RestApiId: + Ref: RestApi + TestResourcePermission: + Type: AWS::Lambda::Permission + Properties: + Action: lambda:InvokeFunction + FunctionName: + Fn::GetAtt: + - TestResourceHandler + - Arn + Principal: apigateway.amazonaws.com + SourceArn: + Fn::Sub: arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*/* + TestResourceRootPermission: + Type: AWS::Lambda::Permission + Properties: + Action: lambda:InvokeFunction + FunctionName: + Fn::GetAtt: + - TestResourceHandler + - Arn + Principal: apigateway.amazonaws.com + SourceArn: + Fn::Sub: arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*/ + TestResourceOptions: + Type: AWS::ApiGateway::Method + Properties: + HttpMethod: OPTIONS + ResourceId: + Ref: TestResourceResource + RestApiId: + Ref: RestApi + AuthorizationType: NONE + Integration: + IntegrationHttpMethod: POST + Type: AWS_PROXY + Uri: + Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${TestResourceHandler.Arn}/invocations + TestResourceGet: + Type: AWS::ApiGateway::Method + Properties: + HttpMethod: GET + ResourceId: + Ref: TestResourceResource + RestApiId: + Ref: RestApi + AuthorizationType: COGNITO_USER_POOLS + AuthorizerId: + Ref: RestApiAuthorizer + Integration: + IntegrationHttpMethod: POST + Type: AWS_PROXY + Uri: + Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${TestResourceHandler.Arn}/invocations + TestDataTable: + Type: AWS::DynamoDB::Table + Properties: + BillingMode: PAY_PER_REQUEST + TableName: + Fn::Sub: ${AppName}-test + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + TestDataLambdaPolicy: + Type: AWS::IAM::RolePolicy + Metadata: + Comment: This resource is created only if the LambdaRoleArn is set + Properties: + PolicyDocument: + Statement: + - Action: + - dynamodb:BatchGetItem + - dynamodb:GetItem + - dynamodb:Query + - dynamodb:Scan + - dynamodb:BatchWriteItem + - dynamodb:PutItem + - dynamodb:UpdateItem + Effect: Allow + Resource: + - Fn::GetAtt: + - TestDataTable + - Arn + PolicyName: + Fn::Sub: ${AppName}-test-policy + RoleName: + Ref: TestResourceHandlerRole + JwtResourceHandler: + Type: AWS::Lambda::Function + Properties: + Handler: bootstrap + FunctionName: + Fn::Sub: ${AppName}-jwt-handler + Runtime: provided.al2023 + Code: + S3Bucket: ezbeard-rain-lambda + S3Key: ae97202801e07f18ef8393e5660e70f2 + Role: + Fn::GetAtt: + - JwtResourceHandlerRole + - Arn + Environment: + Variables: + COGNITO_REGION: us-east-1 + COGNITO_POOL_ID: + Ref: CognitoUserPool + COGNITO_REDIRECT_URI: + Fn::Sub: https://${SiteDistribution.DomainName}/index.html + COGNITO_DOMAIN_PREFIX: + Ref: AppName + COGNITO_APP_CLIENT_ID: + Ref: CognitoClient + JwtResourceHandlerRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + JwtResourceResource: + Type: AWS::ApiGateway::Resource + Properties: + ParentId: + Fn::Sub: ${RestApi.RootResourceId} + PathPart: jwt + RestApiId: + Ref: RestApi + JwtResourcePermission: + Type: AWS::Lambda::Permission + Properties: + Action: lambda:InvokeFunction + FunctionName: + Fn::GetAtt: + - JwtResourceHandler + - Arn + Principal: apigateway.amazonaws.com + SourceArn: + Fn::Sub: arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*/* + JwtResourceRootPermission: + Type: AWS::Lambda::Permission + Properties: + Action: lambda:InvokeFunction + FunctionName: + Fn::GetAtt: + - JwtResourceHandler + - Arn + Principal: apigateway.amazonaws.com + SourceArn: + Fn::Sub: arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*/ + JwtResourceOptions: + Type: AWS::ApiGateway::Method + Properties: + HttpMethod: OPTIONS + ResourceId: + Ref: JwtResourceResource + RestApiId: + Ref: RestApi + AuthorizationType: NONE + Integration: + IntegrationHttpMethod: POST + Type: AWS_PROXY + Uri: + Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${JwtResourceHandler.Arn}/invocations + JwtResourceGet: + Type: AWS::ApiGateway::Method + Properties: + HttpMethod: GET + ResourceId: + Ref: JwtResourceResource + RestApiId: + Ref: RestApi + AuthorizationType: NONE + AuthorizerId: AWS::NoValue + Integration: + IntegrationHttpMethod: POST + Type: AWS_PROXY + Uri: + Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${JwtResourceHandler.Arn}/invocations +Outputs: + SiteURL: + Value: + Fn::Sub: https://${SiteDistribution.DomainName} + RedirectURI: + Value: + Fn::Sub: https://${SiteDistribution.DomainName}/index.html + AppName: + Value: + Ref: AppName + RestApiInvokeURL: + Value: + Fn::Sub: https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/${RestApiStage} + AppClientId: + Value: + Ref: CognitoClient + CognitoDomainPrefix: + Value: + Ref: AppName + ContentBucketName: + Value: + Ref: SiteContentBucket diff --git a/LocalModules/ExampleWebApp/webapp.yaml b/LocalModules/ExampleWebApp/webapp.yaml new file mode 100644 index 00000000..c1124e93 --- /dev/null +++ b/LocalModules/ExampleWebApp/webapp.yaml @@ -0,0 +1,120 @@ +Description: Creates a web application with a static website using S3 and CloudFront, an API Gateway REST API, and a DynamoDB table, with Cognito authentication. Apache-2.0 License. Adapt this template to your needs and thoruoughly test it before introducing it in a production environment. **WARNING** This template will create resources in your account that may incur billing charges. + +Parameters: + + AppName: + Type: String + Description: This name is used as a prefix for resource names + Default: awscli-cfn-webapp + +Resources: + + Site: + Type: LocalModule + Source: "modules/static-site.yaml" + Properties: + AppName: !Ref AppName + Overrides: + ContentBucket: + + Cognito: + Type: LocalModule + Source: "modules/cognito.yaml" + Properties: + AppName: !Ref AppName + CallbackURL: !Sub "https://${SiteDistribution.DomainName}/index.html" + Overrides: + UserPool: + DependsOn: SiteDistribution + + Rest: + Type: LocalModule + Source: "modules/rest-api.yaml" + Properties: + AppName: !Ref AppName + UserPoolArn: !GetAtt CognitoUserPool.Arn + Overrides: + ApiDeployment: + DependsOn: + - TestResourceGet + - TestResourceOptions + - JwtResourceGet + - JwtResourceOptions + + TestResource: + Type: LocalModule + Source: "modules/api-resource.yaml" + Metadata: + Comment: This module handles all methods on the /test path on the API. The lambda function code is located in api/resources/test. + Properties: + Name: !Sub ${AppName}-test + RestApi: !Ref RestApi + RestApiDeployment: !Ref RestApiDeployment + CodePath: api/dist/test/lambda-handler.zip + ResourcePath: test + StageName: staging + AuthorizerId: !Ref RestApiAuthorizer + Overrides: + Handler: + Properties: + Environment: + Variables: + TABLE_NAME: !Ref TestDataTable + + TestData: + Type: LocalModule + Source: "modules/simple-table.yaml" + Properties: + TableName: !Sub ${AppName}-test + LambdaRole: !Ref TestResourceHandlerRole + + JwtResource: + Type: LocalModule + Source: "modules/api-resource.yaml" + Metadata: + Comment: This module handles all methods on the /jwt path on the API. The lambda function code is located in api/resources/jwt + Properties: + Name: !Sub ${AppName}-jwt + RestApi: !Ref RestApi + RestApiDeployment: !Ref RestApiDeployment + CodePath: api/dist/jwt/lambda-handler.zip + ResourcePath: jwt + StageName: staging + AuthorizerId: AWS::NoValue + Overrides: + Handler: + Properties: + Environment: + Variables: + COGNITO_REGION: us-east-1 + COGNITO_POOL_ID: !Ref CognitoUserPool + COGNITO_REDIRECT_URI: !Sub "https://${SiteDistribution.DomainName}/index.html" + COGNITO_DOMAIN_PREFIX: !Ref AppName + COGNITO_APP_CLIENT_ID: !Ref CognitoClient + Get: + Properties: + AuthorizationType: NONE + +Outputs: + + SiteURL: + Value: !Sub "https://${SiteDistribution.DomainName}" + + RedirectURI: + Value: !Sub "https://${SiteDistribution.DomainName}/index.html" + + AppName: + Value: !Ref AppName + + RestApiInvokeURL: + Value: !Sub https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/${RestApiStage} + + AppClientId: + Value: !Ref CognitoClient + + CognitoDomainPrefix: + Value: !Ref AppName + + ContentBucketName: + Value: !Ref SiteContentBucket +