├── func ├── scss_to_css │ ├── BUILD.bazel │ └── index.js ├── css_to_json │ ├── index.js │ └── BUILD.bazel ├── hello_world │ ├── main.go │ └── BUILD.bazel ├── watttime_demo │ ├── BUILD.bazel │ └── main.go ├── readable_profiles │ ├── BUILD.bazel │ └── main.go ├── encrypt_secret_value │ ├── BUILD.bazel │ └── index.js ├── enforce_contract │ ├── BUILD.bazel │ └── index.js ├── azure_hedge │ └── BUILD.bazel ├── grant_access │ ├── BUILD.bazel │ └── AmazonRootCA1.pem ├── renewable_record │ └── BUILD.bazel ├── validate_entity │ └── BUILD.bazel ├── convert_media │ ├── BUILD.bazel │ └── main.go └── index_entity │ ├── BUILD.bazel │ └── main.go ├── app ├── image_feature │ └── BUILD.bazel └── knn_spherical │ └── BUILD.bazel ├── iac ├── precheck-cognito │ ├── .gitignore │ ├── message │ │ ├── README.md │ │ ├── package.json │ │ └── index.js │ ├── tsconfig.json │ ├── Pulumi.yaml │ ├── Pulumi.dev.yaml │ ├── index.ts │ ├── post-confirmation │ │ ├── package.json │ │ └── index.js │ ├── project.json │ ├── stack-reference.ts │ └── eslintrc.json └── precheck-oidc-auth │ ├── .gitignore │ ├── tsconfig.json │ ├── Pulumi.dev.yaml │ ├── user │ ├── package.json │ ├── index.js │ └── package-lock.json │ ├── Pulumi.yaml │ ├── login │ ├── package.json │ └── index.js │ ├── token │ ├── package.json │ └── index.js │ ├── index.ts │ ├── callback │ └── package.json │ ├── project.json │ ├── .eslintrc.json │ ├── stack-reference.ts │ ├── shared │ └── logger.js │ └── README.md ├── architecture ├── plugins.md ├── commands.md └── sites.md ├── .vscode └── settings.json ├── lib ├── user │ ├── user.go │ └── BUILD.bazel ├── utils │ ├── uuid.go │ ├── jwt.go │ ├── BUILD.bazel │ ├── adub.go │ └── compact.go ├── hedge │ ├── package.json │ └── BUILD.bazel ├── attr │ ├── BUILD.bazel │ └── attr.go ├── chat │ ├── BUILD.bazel │ └── chat.go ├── vocab │ ├── BUILD.bazel │ └── vocab.go ├── profiles │ └── BUILD.bazel ├── watttime │ ├── BUILD.bazel │ └── watttime.go ├── cc │ └── BUILD.bazel ├── ads │ ├── BUILD.bazel │ └── ads.go ├── es │ ├── BUILD.bazel │ └── es.go ├── os │ ├── BUILD.bazel │ └── os.go ├── search │ ├── BUILD.bazel │ └── stats.go ├── gov │ ├── BUILD.bazel │ └── gov.go ├── sign │ └── BUILD.bazel ├── repo │ └── BUILD.bazel ├── entity │ └── BUILD.bazel └── shapeshift │ └── BUILD.bazel ├── BUILD.bazel ├── mappings └── renewable-record.json ├── email_templates ├── temp_password.json └── mytemplate.json ├── docker-compose.yml ├── .gitignore ├── api ├── model │ ├── main.go │ └── BUILD.bazel ├── entity │ ├── main.go │ ├── BUILD.bazel │ ├── test_data │ │ └── post_shapeshift.json │ └── types.json.tmpl ├── versait │ └── BUILD.bazel ├── proxy │ └── BUILD.bazel ├── index │ └── BUILD.bazel ├── gov │ ├── BUILD.bazel │ └── main.go ├── authorizer │ ├── BUILD.bazel │ └── main.go ├── authorizer2 │ ├── BUILD.bazel │ └── main.go ├── authorizer3 │ ├── BUILD.bazel │ └── main.go ├── stream │ ├── BUILD.bazel │ └── AmazonRootCA1.pem ├── chat │ ├── BUILD.bazel │ ├── AmazonRootCA1.pem │ └── queries.tmpl ├── user │ └── BUILD.bazel └── media │ └── BUILD.bazel ├── node_deps.bzl ├── edge └── renewable_redirect │ └── BUILD.bazel ├── package.json ├── hooks ├── site_complete │ ├── BUILD.bazel │ └── main.go ├── site_start │ ├── main.go │ └── BUILD.bazel ├── site_staticwebapp │ └── BUILD.bazel ├── site_init │ └── BUILD.bazel ├── site_modify │ └── BUILD.bazel ├── entity_index │ └── BUILD.bazel ├── index_create │ ├── BUILD.bazel │ └── main.go ├── site_write │ ├── BUILD.bazel │ └── main.go ├── site_deploy │ └── BUILD.bazel └── site_environment │ └── BUILD.bazel ├── manifests └── entity-api.yaml ├── cloudform ├── bucket.yml ├── search2.sh ├── search_minimal.sh ├── search_minimal.yaml ├── main.yaml ├── search_distinct.sh ├── cognito_role.sh ├── cognito_role.yaml ├── search.sh ├── users.yaml ├── search.yaml └── search_distinct.yaml ├── job └── renewable_report │ └── BUILD.bazel ├── HelloWorld.yml ├── serverless-watttime-demo.yml ├── go.mod ├── serverless-versait.yml ├── Dockerfile └── serverless-splits.yml /func/scss_to_css/BUILD.bazel: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /func/scss_to_css/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/image_feature/BUILD.bazel: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /iac/precheck-cognito/.gitignore: -------------------------------------------------------------------------------- 1 | Pulumi.local*.yaml -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/.gitignore: -------------------------------------------------------------------------------- 1 | Pulumi.local*.yaml -------------------------------------------------------------------------------- /architecture/plugins.md: -------------------------------------------------------------------------------- 1 | Call functions if they exists at certain points to alter -------------------------------------------------------------------------------- /iac/precheck-cognito/message/README.md: -------------------------------------------------------------------------------- 1 | npm install fails with node 20 best bet use 18. -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json" 3 | } 4 | -------------------------------------------------------------------------------- /iac/precheck-cognito/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json" 3 | } 4 | -------------------------------------------------------------------------------- /architecture/commands.md: -------------------------------------------------------------------------------- 1 | dev: serverless deploy 2 | prod: serverless deploy --aws-profile prod --stage prod -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": null, 3 | "git.ignoreLimitWarning": true 4 | } -------------------------------------------------------------------------------- /lib/user/user.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | type PublicUserProfile struct { 4 | Id string 5 | UserName string 6 | } 7 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_gazelle//:def.bzl", "gazelle") 2 | 3 | gazelle( 4 | name = "gazelle", 5 | prefix = "goclassifieds", 6 | ) 7 | -------------------------------------------------------------------------------- /lib/utils/uuid.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/google/uuid" 4 | 5 | func GenerateId() string { 6 | uuid, _ := uuid.NewUUID() 7 | return uuid.String() 8 | } 9 | -------------------------------------------------------------------------------- /mappings/renewable-record.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "carbon": { 4 | "type": "float" 5 | }, 6 | "electricity": { 7 | "type": "float" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /lib/hedge/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rollthecloudinc/hedge", 3 | "version": "0.0.23", 4 | "main": "hedge_lib.js", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/rollthecloudinc/hedge" 8 | } 9 | } -------------------------------------------------------------------------------- /lib/attr/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "attr", 5 | srcs = ["attr.go"], 6 | importpath = "goclassifieds/lib/attr", 7 | visibility = ["//visibility:public"], 8 | ) 9 | -------------------------------------------------------------------------------- /lib/chat/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "chat", 5 | srcs = ["chat.go"], 6 | importpath = "goclassifieds/lib/chat", 7 | visibility = ["//visibility:public"], 8 | ) 9 | -------------------------------------------------------------------------------- /lib/user/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "user", 5 | srcs = ["user.go"], 6 | importpath = "goclassifieds/lib/user", 7 | visibility = ["//visibility:public"], 8 | ) 9 | -------------------------------------------------------------------------------- /lib/vocab/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "vocab", 5 | srcs = ["vocab.go"], 6 | importpath = "goclassifieds/lib/vocab", 7 | visibility = ["//visibility:public"], 8 | ) 9 | -------------------------------------------------------------------------------- /lib/profiles/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "profiles", 5 | srcs = ["profiles.go"], 6 | importpath = "goclassifieds/lib/profiles", 7 | visibility = ["//visibility:public"], 8 | ) 9 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/Pulumi.dev.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | precheck-oidc-auth:ApiDeploymentStage: dev 3 | precheck-oidc-auth:LoginGovRedirectUri: "https://5bh4unsxta.execute-api.us-east-2.amazonaws.com/dev/callback" 4 | aws:profile: precheck-dev 5 | aws:region: us-east-1 6 | -------------------------------------------------------------------------------- /lib/watttime/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "watttime", 5 | srcs = ["watttime.go"], 6 | importpath = "goclassifieds/lib/watttime", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /iac/precheck-cognito/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: precheck-cognito 2 | description: Authenication system using cognito and identity pools 3 | runtime: 4 | name: nodejs 5 | options: 6 | packagemanager: npm 7 | config: 8 | pulumi:tags: 9 | value: 10 | pulumi:template: aws-typescript 11 | -------------------------------------------------------------------------------- /lib/cc/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "cc", 5 | srcs = ["cc.go"], 6 | importpath = "goclassifieds/lib/cc", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//lib/attr", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /email_templates/temp_password.json: -------------------------------------------------------------------------------- 1 | { 2 | "Template": { 3 | "TemplateName": "TempPassword", 4 | "SubjectPart": "Greetings, {{name}}!", 5 | "HtmlPart": "

Hello {{name}},

Your temp password is {{tempPassword}}

", 6 | "TextPart": "Dear {{name}},\r\nYour temp password is {{tempPassword}}" 7 | } 8 | } -------------------------------------------------------------------------------- /email_templates/mytemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "Template": { 3 | "TemplateName": "MyTemplate", 4 | "SubjectPart": "Greetings, {{name}}!", 5 | "HtmlPart": "

Hello {{name}},

Your favorite animal is {{favoriteanimal}}.

", 6 | "TextPart": "Dear {{name}},\r\nYour favorite animal is {{favoriteanimal}}." 7 | } 8 | } -------------------------------------------------------------------------------- /lib/ads/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "ads", 5 | srcs = ["ads.go"], 6 | importpath = "goclassifieds/lib/ads", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//lib/attr", 10 | "//lib/vocab", 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /func/css_to_json/index.js: -------------------------------------------------------------------------------- 1 | var cssjson = require("cssjson"); 2 | 3 | module.exports.handler = function(event, context, callback) { 4 | console.log('hello world'); 5 | console.log(event); 6 | console.log(context); 7 | var json = cssjson.toJSON(event.Content, { comments: true }); 8 | console.log(json); 9 | callback(null, json); 10 | } -------------------------------------------------------------------------------- /lib/es/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "es", 5 | srcs = ["es.go"], 6 | importpath = "goclassifieds/lib/es", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_elastic_go_elasticsearch_v7//:go-elasticsearch", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /lib/os/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "os", 5 | srcs = ["os.go"], 6 | importpath = "goclassifieds/lib/os", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_opensearch_project_opensearch_go//:opensearch-go", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | bazel-environment: 5 | build: . 6 | image: bazel6-go17-nodejs 7 | volumes: 8 | - /var/run/docker.sock:/var/run/docker.sock 9 | - ~/projects/hedge:/workspace 10 | - ~/.aws:/root/.aws:ro 11 | working_dir: /workspace 12 | stdin_open: true 13 | tty: true 14 | privileged: true -------------------------------------------------------------------------------- /lib/search/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "search", 5 | srcs = ["dialect.go", "analyzers.go","engine.go","loader.go"], 6 | importpath = "goclassifieds/lib/search", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_google_go_github_v46//github", 10 | ], 11 | ) 12 | 13 | 14 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/user/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "node-fetch": "^2.7.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/gov/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "gov", 5 | srcs = ["gov.go"], 6 | importpath = "goclassifieds/lib/gov", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//lib/utils", 10 | "@com_github_google_go_github_v46//github", 11 | "@com_github_gocql_gocql//:gocql", 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: precheck-oidc-auth 2 | description: Centralized OIDC providing infinite integration capabilities unrestricted by vendor-to-vendor incompatibilities since this can be used as a middle man to satisfy gaps. 3 | runtime: 4 | name: nodejs 5 | options: 6 | packagemanager: npm 7 | config: 8 | pulumi:tags: 9 | value: 10 | pulumi:template: aws-typescript 11 | -------------------------------------------------------------------------------- /iac/precheck-cognito/Pulumi.dev.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | precheck-cognito:SesVerifiedEmail: "VERIFIED email address to dispatch transactional emails" 3 | precheck-cognito:OidcDomain: "ykpt3qxd62.execute-api.us-east-1.amazonaws.com" 4 | precheck-cognito:OidcStage: "dev" 5 | precheck-cognito:LoginGovClientId: "urn:gov:gsa:openidconnect.profiles:sp:sso:dhs:precheck-dev-pkce" 6 | aws:region: "us-east-1" 7 | aws:profile: "precheck-dev" -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/login/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "login", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "aws-sdk": "^2.1692.0", 14 | "crypto": "^1.0.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/token/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "token", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "aws-sdk": "^2.1692.0", 14 | "querystring": "^0.2.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /iac/precheck-cognito/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import { loadConfig, register } from 'tsconfig-paths' 4 | 5 | const config = loadConfig('.') 6 | if (config.resultType === 'failed') { 7 | console.log('Could not load tsconfig to map paths, aborting.') 8 | process.exit(1) 9 | } 10 | 11 | register({ 12 | baseUrl: config.absoluteBaseUrl, 13 | paths: config.paths 14 | }) 15 | 16 | import './pulumi' 17 | export * from './pulumi' 18 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import { loadConfig, register } from 'tsconfig-paths' 4 | 5 | const config = loadConfig('.') 6 | if (config.resultType === 'failed') { 7 | console.log('Could not load tsconfig to map paths, aborting.') 8 | process.exit(1) 9 | } 10 | 11 | register({ 12 | baseUrl: config.absoluteBaseUrl, 13 | paths: config.paths 14 | }) 15 | 16 | import './pulumi' 17 | export * from './pulumi' 18 | -------------------------------------------------------------------------------- /lib/utils/jwt.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strings" 5 | 6 | jwt "github.com/dgrijalva/jwt-go" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func GetSubject(context *gin.Context) string { 11 | authPieces := strings.Split(context.GetHeader("Authorization"), " ") 12 | if len(authPieces) == 2 { 13 | claims := jwt.StandardClaims{} 14 | jwt.ParseWithClaims(authPieces[1], &claims, nil) 15 | return claims.Subject 16 | } 17 | return "" 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .serverless 2 | bin 3 | node_modules 4 | 5 | bazel-bin 6 | bazel-goclassifieds 7 | bazel-out 8 | bazel-testlogs 9 | bazel-vertigo 10 | bazel-hedge 11 | 12 | private.dev.json 13 | private.prod.json 14 | 15 | api/entity/rtc-vertigo-dev.private-key.pem 16 | api/entity/rtc-vertigo-prod.private-key.pem 17 | 18 | api/media/rtc-vertigo-dev.private-key.pem 19 | api/media/rtc-vertigo-prod.private-key.pem 20 | 21 | .npmrc 22 | azure_credentials.json 23 | 24 | go/* -------------------------------------------------------------------------------- /func/hello_world/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/aws/aws-lambda-go/events" 5 | "github.com/aws/aws-lambda-go/lambda" 6 | ) 7 | 8 | func HandleRequest(req *events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { 9 | body := "{\"works\": true}" 10 | return events.APIGatewayProxyResponse{StatusCode: 200, Body: body, Headers: map[string]string{"Content-Type": "application/json"}}, nil 11 | } 12 | 13 | func main() { 14 | lambda.Start(HandleRequest) 15 | } 16 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/callback/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "callback", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "aws-sdk": "^2.1692.0", 14 | "jsonwebtoken": "^9.0.2", 15 | "jwks-rsa": "^3.1.0", 16 | "node-fetch": "^2.7.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /api/model/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "context" 6 | "net/http" 7 | 8 | "github.com/aws/aws-lambda-go/events" 9 | "github.com/aws/aws-lambda-go/lambda" 10 | ) 11 | 12 | func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { 13 | return events.APIGatewayProxyResponse{ 14 | StatusCode: http.StatusOK, 15 | Body: "Hello World", 16 | }, nil 17 | } 18 | 19 | func main() { 20 | log.SetFlags(0) 21 | lambda.Start(handler) 22 | } -------------------------------------------------------------------------------- /iac/precheck-cognito/post-confirmation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "precheck-cognito-post-confirmation", 3 | "version": "1.0.0", 4 | "description": "A Lambda function to do things after user confirmation of account.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "echo \"No test specified\" && exit 0" 9 | }, 10 | "author": "Telos", 11 | "license": "MIT", 12 | "dependencies": { 13 | "aws-sdk": "^2.1390.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /node_deps.bzl: -------------------------------------------------------------------------------- 1 | def setup_node_dependencies(): 2 | # Load Node.js rules 3 | load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories", "npm_install") 4 | 5 | # Declare Node.js repositories 6 | node_repositories( 7 | node_version = "16.6.2", 8 | package_json = ["//:package.json"], 9 | ) 10 | 11 | # Install NPM packages 12 | npm_install( 13 | name = "npm", 14 | package_json = "//:package.json", 15 | package_lock_json = "//:package-lock.json", 16 | ) -------------------------------------------------------------------------------- /api/entity/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "goclassifieds/lib/shapeshift" 7 | 8 | "github.com/aws/aws-lambda-go/events" 9 | "github.com/aws/aws-lambda-go/lambda" 10 | ) 11 | 12 | var handler shapeshift.Handler 13 | 14 | type Handler func(req *events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) 15 | 16 | func init() { 17 | ac := shapeshift.ShapeshiftActionContext() 18 | handler = shapeshift.InitializeHandler(ac) 19 | } 20 | 21 | func main() { 22 | log.SetFlags(0) 23 | lambda.Start(handler) 24 | } 25 | -------------------------------------------------------------------------------- /func/css_to_json/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm//@bazel/esbuild:index.bzl", "esbuild") 2 | 3 | esbuild( 4 | name = "css_to_json", 5 | entry_point = "index.js", 6 | platform = "node", 7 | target = "node10", 8 | tool = select({ 9 | "@bazel_tools//src/conditions:darwin": "@esbuild_darwin//:bin/esbuild", 10 | "@bazel_tools//src/conditions:windows": "@esbuild_windows//:esbuild.exe", 11 | "@bazel_tools//src/conditions:linux_x86_64": "@esbuild_linux//:bin/esbuild", 12 | }), 13 | deps = [ 14 | "@npm//cssjson", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /edge/renewable_redirect/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm//@bazel/esbuild:index.bzl", "esbuild") 2 | 3 | esbuild( 4 | name = "renewable_redirect", 5 | entry_point = "index.js", 6 | platform = "node", 7 | target = "node10", 8 | tool = select({ 9 | "@bazel_tools//src/conditions:darwin": "@esbuild_darwin//:bin/esbuild", 10 | "@bazel_tools//src/conditions:windows": "@esbuild_windows//:esbuild.exe", 11 | "@bazel_tools//src/conditions:linux_x86_64": "@esbuild_linux//:bin/esbuild", 12 | }), 13 | deps = [ 14 | "@npm//matcher", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /app/knn_spherical/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "knn_spherical", 5 | embed = [":knn_spherical_app"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/app/knn_spherical", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "knn_spherical_app", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/app/knn_spherical", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | ], 20 | ) 21 | -------------------------------------------------------------------------------- /iac/precheck-cognito/message/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "precheck-cognito-message", 3 | "version": "1.0.0", 4 | "description": "A Lambda function to handle custom Cognito messages with SMS and email fallback.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "echo \"No test specified\" && exit 0" 9 | }, 10 | "author": "Telos", 11 | "license": "MIT", 12 | "dependencies": { 13 | "@aws-crypto/client-node": "^4.2.0", 14 | "aws-sdk": "^2.1390.0", 15 | "base64-js": "^1.5.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/utils/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "utils", 5 | srcs = [ 6 | "jwt.go", 7 | "uuid.go", 8 | "log.go", 9 | "compact.go", 10 | "adub.go" 11 | ], 12 | importpath = "goclassifieds/lib/utils", 13 | visibility = ["//visibility:public"], 14 | deps = [ 15 | "@com_github_dgrijalva_jwt_go//:jwt-go", 16 | "@com_github_gin_gonic_gin//:gin", 17 | "@com_github_google_uuid//:uuid", 18 | "@com_github_aws_aws_lambda_go//events", 19 | ], 20 | ) 21 | -------------------------------------------------------------------------------- /lib/sign/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "sign", 5 | srcs = ["sign.go"], 6 | importpath = "goclassifieds/lib/sign", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_aws_aws_sdk_go//aws/credentials", 10 | "@com_github_aws_aws_sdk_go//aws/signer/v4:go_default_library", 11 | "@com_github_aws_aws_sdk_go//aws/session", 12 | "@com_github_aws_aws_sdk_go//service/cognitoidentity", 13 | "@com_github_aws_aws_sdk_go//service/cognitoidentityprovider", 14 | "@com_github_aws_aws_sdk_go//aws", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goclassifieds", 3 | "devDependencies": { 4 | "@bazel/bazelisk": "^1.9.0", 5 | "@bazel/esbuild": "^3.6.0", 6 | "@bazel/ibazel": "^0.15.10", 7 | "@bazel/typescript": "3.8.0", 8 | "ajv": "^8.17.1", 9 | "ajv-formats": "^3.0.1", 10 | "cssjson": "^2.1.3", 11 | "libsodium-wrappers": "^0.7.10", 12 | "matcher": "^5.0.0", 13 | "serverless-custom-packaging-plugin": "^0.1.2", 14 | "serverless-knative": "^0.6.0", 15 | "serverless-prune-plugin": "^2.0.1" 16 | }, 17 | "dependencies": { 18 | "serverless": "^3.40.0", 19 | "serverless-step-functions": "^3.23.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /api/model/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "model", 5 | embed = [":model_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/model", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "model_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/api/model", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "@com_github_aws_aws_lambda_go//events", 20 | "@com_github_aws_aws_lambda_go//lambda", 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /func/watttime_demo/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "watttime_demo", 5 | embed = [":watttime_demo_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/watttime_demo", 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | go_library( 13 | name = "watttime_demo_lib", 14 | srcs = ["main.go"], 15 | importpath = "goclassifieds/func/watttime_demo", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "//lib/watttime", 19 | "@com_github_aws_aws_lambda_go//lambda", 20 | ], 21 | ) 22 | -------------------------------------------------------------------------------- /api/versait/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "versait", 5 | embed = [":versait_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/versait", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "versait_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/api/versait", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "@com_github_aws_aws_lambda_go//events", 20 | "@com_github_aws_aws_lambda_go//lambda", 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /func/hello_world/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "hello_world", 5 | embed = [":hello_world_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/hello_world", 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | go_library( 13 | name = "hello_world_lib", 14 | srcs = ["main.go"], 15 | importpath = "goclassifieds/func/hello_world", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "@com_github_aws_aws_lambda_go//events", 19 | "@com_github_aws_aws_lambda_go//lambda", 20 | ], 21 | ) 22 | -------------------------------------------------------------------------------- /func/readable_profiles/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "readable_profiles", 5 | embed = [":readable_profiles_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/readable_profiles", 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | go_library( 13 | name = "readable_profiles_lib", 14 | srcs = ["main.go"], 15 | importpath = "goclassifieds/func/readable_profiles", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "//lib/entity", 19 | "@com_github_aws_aws_lambda_go//lambda", 20 | ], 21 | ) 22 | -------------------------------------------------------------------------------- /hooks/site_complete/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "site_complete", 5 | embed = [":site_complete_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/hooks/site_complete", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "site_complete_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/hooks/site_complete", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/entity", 20 | "@com_github_aws_aws_lambda_go//lambda", 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /func/encrypt_secret_value/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm//@bazel/esbuild:index.bzl", "esbuild") 2 | # load("//:node_deps.bzl", "setup_node_repositories") 3 | 4 | # setup_node_repositories() 5 | 6 | esbuild( 7 | name = "encrypt_secret_value", 8 | entry_point = "index.js", 9 | platform = "node", 10 | target = "node10", 11 | tool = select({ 12 | "@bazel_tools//src/conditions:darwin": "@esbuild_darwin//:bin/esbuild", 13 | "@bazel_tools//src/conditions:windows": "@esbuild_windows//:esbuild.exe", 14 | "@bazel_tools//src/conditions:linux_x86_64": "@esbuild_linux//:bin/esbuild", 15 | }), 16 | deps = [ 17 | "@npm//libsodium-wrappers" 18 | ], 19 | ) -------------------------------------------------------------------------------- /hooks/site_start/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "goclassifieds/lib/entity" 6 | "log" 7 | 8 | "github.com/aws/aws-lambda-go/lambda" 9 | ) 10 | 11 | func handler(ctx context.Context, event map[string]interface{}) (entity.AfterSaveExecEntityResponse, error) { 12 | 13 | /** 14 | * This is where all the code goes to create action SECRETS 15 | * for a site. Both for repo and enviironment. 16 | */ 17 | log.Print("Start site workflow") 18 | 19 | return entity.AfterSaveExecEntityResponse{}, nil 20 | } 21 | 22 | func main() { 23 | log.SetFlags(0) 24 | // Make the handler available for Remote Procedure Call by AWS Lambda 25 | lambda.Start(handler) 26 | } 27 | -------------------------------------------------------------------------------- /architecture/sites.md: -------------------------------------------------------------------------------- 1 | Add Site key to top level entities like panel page. 2 | 3 | This could be an array allowing opne piece of content to belong to multiple sites. 4 | 5 | Include site id in query string when applicable. 6 | 7 | Limit result set to items in that site. 8 | 9 | configs 10 | -------- 11 | 12 | does this matter not - not really 13 | 14 | all sites share same resources for now. 15 | 16 | 17 | 18 | ----------- 19 | 20 | environments (limited - lambda limit) 21 | 22 | - dev 23 | - prod 24 | 25 | ------ 26 | 27 | restrict write on entities (pages) user is not allowed to write 0- entity permissions 28 | 29 | * New Authorizer that can be used with entity permissions on entities -------------------------------------------------------------------------------- /hooks/site_complete/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "goclassifieds/lib/entity" 6 | "log" 7 | 8 | "github.com/aws/aws-lambda-go/lambda" 9 | ) 10 | 11 | func handler(ctx context.Context, event map[string]interface{}) (entity.AfterSaveExecEntityResponse, error) { 12 | 13 | /** 14 | * This is where all the code goes to create action SECRETS 15 | * for a site. Both for repo and enviironment. 16 | */ 17 | log.Print("Complete site workflow") 18 | 19 | return entity.AfterSaveExecEntityResponse{}, nil 20 | } 21 | 22 | func main() { 23 | log.SetFlags(0) 24 | // Make the handler available for Remote Procedure Call by AWS Lambda 25 | lambda.Start(handler) 26 | } 27 | -------------------------------------------------------------------------------- /func/enforce_contract/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm//@bazel/esbuild:index.bzl", "esbuild") 2 | # load("//:node_deps.bzl", "setup_node_repositories") 3 | 4 | # setup_node_repositories() 5 | 6 | esbuild( 7 | name = "enforce_contract", 8 | entry_point = "index.js", 9 | platform = "node", 10 | target = "node10", 11 | tool = select({ 12 | "@bazel_tools//src/conditions:darwin": "@esbuild_darwin//:bin/esbuild", 13 | "@bazel_tools//src/conditions:windows": "@esbuild_windows//:esbuild.exe", 14 | "@bazel_tools//src/conditions:linux_x86_64": "@esbuild_linux//:bin/esbuild", 15 | }), 16 | deps = [ 17 | "@npm//ajv", 18 | "@npm//ajv-formats", 19 | ], 20 | ) -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "precheck-oidc-auth", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "sourceRoot": "apps/precheck-oidc-auth", 6 | "targets": { 7 | "destroy": { 8 | "executor": "@telos-iac/nx-pulumi:destroy", 9 | "options": {} 10 | }, 11 | "up": { 12 | "executor": "@telos-iac/nx-pulumi:up", 13 | "options": {} 14 | }, 15 | "preview": { 16 | "executor": "@telos-iac/nx-pulumi:preview", 17 | "options": {} 18 | }, 19 | "refresh": { 20 | "executor": "@telos-iac/nx-pulumi:refresh", 21 | "options": {} 22 | } 23 | }, 24 | "tags": [] 25 | } 26 | -------------------------------------------------------------------------------- /manifests/entity-api.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: serving.knative.dev/v1 2 | kind: Service 3 | metadata: 4 | name: entity-api 5 | namespace: default 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - image: gcr.io//entity-api:latest # Replace with your actual image URL 11 | ports: 12 | - containerPort: 8080 13 | env: 14 | - name: ELASTIC_URL 15 | value: # Replace with your actual Elastic URL 16 | - name: BUCKET_NAME 17 | value: # Replace with your actual bucket name 18 | - name: STAGE 19 | value: dev # Replace with your desired stage (e.g., dev, prod) 20 | -------------------------------------------------------------------------------- /hooks/site_staticwebapp/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm//@bazel/esbuild:index.bzl", "esbuild") 2 | # load("//:node_deps.bzl", "setup_node_repositories") 3 | 4 | # setup_node_repositories() 5 | 6 | esbuild( 7 | name = "site_staticwebapp", 8 | entry_point = "index.js", 9 | platform = "node", 10 | target = "node18", 11 | tool = select({ 12 | "@bazel_tools//src/conditions:darwin": "@esbuild_darwin//:bin/esbuild", 13 | "@bazel_tools//src/conditions:windows": "@esbuild_windows//:esbuild.exe", 14 | "@bazel_tools//src/conditions:linux_x86_64": "@esbuild_linux//:bin/esbuild", 15 | }), 16 | deps = [ 17 | "@npm//@azure/arm-appservice", 18 | "@npm//@azure/identity", 19 | ], 20 | ) -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "../../.eslintrc.json" 4 | ], 5 | "ignorePatterns": [ 6 | "!**/*" 7 | ], 8 | "overrides": [ 9 | { 10 | "files": [ 11 | "*.ts", 12 | "*.tsx", 13 | "*.js", 14 | "*.jsx" 15 | ], 16 | "parserOptions": { 17 | "project": [ 18 | "apps/precheck-oidc-auth/tsconfig.json" 19 | ] 20 | }, 21 | "rules": {} 22 | }, 23 | { 24 | "files": [ 25 | "*.ts", 26 | "*.tsx" 27 | ], 28 | "rules": {} 29 | }, 30 | { 31 | "files": [ 32 | "*.js", 33 | "*.jsx" 34 | ], 35 | "rules": {} 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /hooks/site_start/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "site_start", 5 | embed = [":site_start_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/hooks/site_start", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "site_start_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/hooks/site_start", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/entity", 20 | "//lib/repo", 21 | "@com_github_google_go_github_v46//github", 22 | "@com_github_aws_aws_lambda_go//lambda", 23 | ], 24 | ) 25 | -------------------------------------------------------------------------------- /iac/precheck-cognito/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "precheck-cognito", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "sourceRoot": "apps/precheck-cognito", 6 | "targets": { 7 | "destroy": { 8 | "executor": "@telos-iac/nx-pulumi:destroy", 9 | "options": {} 10 | }, 11 | "up": { 12 | "executor": "@telos-iac/nx-pulumi:up", 13 | "options": {} 14 | }, 15 | "preview": { 16 | "executor": "@telos-iac/nx-pulumi:preview", 17 | "options": {} 18 | }, 19 | "refresh": { 20 | "executor": "@telos-iac/nx-pulumi:refresh", 21 | "options": {} 22 | } 23 | }, 24 | "tags": [] 25 | } 26 | -------------------------------------------------------------------------------- /api/proxy/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "proxy", 5 | embed = [":proxy_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/proxy", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "proxy_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/proxy", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/utils", 24 | "@com_github_aws_aws_lambda_go//events", 25 | "@com_github_aws_aws_lambda_go//lambda", 26 | ], 27 | ) 28 | -------------------------------------------------------------------------------- /cloudform/bucket.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: 'This template sets up bucket to host automation resources.' 3 | 4 | Parameters: 5 | EnvironmentName: 6 | Description: The name of the environment (e.g., dev, prod) 7 | Type: String 8 | VendorSuffix: 9 | Description: Unique vendor suffix for the bucket. 10 | Type: String 11 | 12 | Resources: 13 | CloudformationTemplatesBucket: 14 | Type: 'AWS::S3::Bucket' 15 | Properties: 16 | BucketName: !Sub 'rtc-classifieds-cloudformation-templates-${EnvironmentName}-${VendorSuffix}' 17 | AccessControl: Private 18 | BucketEncryption: 19 | ServerSideEncryptionConfiguration: 20 | - ServerSideEncryptionByDefault: 21 | SSEAlgorithm: AES256 -------------------------------------------------------------------------------- /func/azure_hedge/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "azure_hedge", 5 | embed = [":azure_hedge_lib"], 6 | goarch = "amd64", 7 | goos = "windows", 8 | importpath = "goclassifieds/func/azure_hedge", 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | go_library( 13 | name = "azure_hedge_lib", 14 | srcs = ["main.go"], 15 | importpath = "goclassifieds/func/azure_hedge", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "//lib/shapeshift", 19 | "@com_github_aws_aws_lambda_go//events", 20 | "@com_github_golang_jwt_jwt_v4//:go_default_library", 21 | "@com_github_micahparks_keyfunc//:go_default_library" 22 | ], 23 | ) 24 | -------------------------------------------------------------------------------- /iac/precheck-cognito/stack-reference.ts: -------------------------------------------------------------------------------- 1 | import * as pulumi from '@pulumi/pulumi'; 2 | 3 | type ExportTypes = typeof import('./pulumi'); 4 | type ExportTypesKey = keyof ExportTypes; 5 | type ExportTypesValue = ExportTypes[TKey]; 6 | 7 | type StrongTypedStackReference = Omit & { 8 | getOutput(name: pulumi.Input): ExportTypesValue; 9 | requireOutput(name: pulumi.Input): ExportTypesValue; 10 | }; 11 | 12 | export function getStackReference() { 13 | const org = pulumi.getOrganization(); 14 | const stack = pulumi.getStack(); 15 | return new pulumi.StackReference(`${org}/precheck-cognito/${stack}`) as StrongTypedStackReference; 16 | } 17 | -------------------------------------------------------------------------------- /lib/hedge/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm//@bazel/esbuild:index.bzl", "esbuild") 2 | 3 | esbuild( 4 | name = "hedge_lib", 5 | entry_point = "index.js", 6 | platform = "node", 7 | target = "node10", 8 | tool = select({ 9 | "@bazel_tools//src/conditions:darwin": "@esbuild_darwin//:bin/esbuild", 10 | "@bazel_tools//src/conditions:windows": "@esbuild_windows//:esbuild.exe", 11 | "@bazel_tools//src/conditions:linux_x86_64": "@esbuild_linux//:bin/esbuild", 12 | }), 13 | deps = [ 14 | "@npm//matcher", 15 | ], 16 | ) 17 | 18 | load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") 19 | 20 | pkg_npm( 21 | name = "hedge", 22 | srcs = ["package.json"], 23 | deps = [":hedge_lib"], 24 | substitutions = {"//internal/": "//"}, 25 | ) -------------------------------------------------------------------------------- /hooks/site_init/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "site_init", 5 | embed = [":site_init_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/hooks/site_init", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "site_init_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/hooks/site_init", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/entity", 20 | "//lib/repo", 21 | "@org_golang_x_oauth2//:go_default_library", 22 | "@com_github_google_go_github_v46//github", 23 | "@com_github_aws_aws_lambda_go//lambda", 24 | ], 25 | ) 26 | -------------------------------------------------------------------------------- /iac/precheck-cognito/eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "../../.eslintrc.json" 4 | ], 5 | "ignorePatterns": [ 6 | "!**/*" 7 | ], 8 | "overrides": [ 9 | { 10 | "files": [ 11 | "*.ts", 12 | "*.tsx", 13 | "*.js", 14 | "*.jsx" 15 | ], 16 | "parserOptions": { 17 | "project": [ 18 | "apps/precheck-cognito/tsconfig.json" 19 | ] 20 | }, 21 | "rules": {} 22 | }, 23 | { 24 | "files": [ 25 | "*.ts", 26 | "*.tsx" 27 | ], 28 | "rules": {} 29 | }, 30 | { 31 | "files": [ 32 | "*.js", 33 | "*.jsx" 34 | ], 35 | "rules": {} 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /api/index/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "index", 5 | embed = [":index_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/index", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "index_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/api/index", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/repo", 20 | "//lib/search", 21 | "@com_github_aws_aws_lambda_go//events", 22 | "@com_github_aws_aws_lambda_go//lambda", 23 | "@org_golang_x_oauth2//:go_default_library", 24 | "@com_github_google_go_github_v46//github", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /hooks/site_modify/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "site_modify", 5 | embed = [":site_modify_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/hooks/site_modify", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "site_modify_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/hooks/site_modify", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/entity", 20 | "//lib/repo", 21 | "@org_golang_x_oauth2//:go_default_library", 22 | "@com_github_aws_aws_lambda_go//lambda", 23 | "@com_github_google_go_github_v46//github", 24 | ], 25 | ) 26 | -------------------------------------------------------------------------------- /api/entity/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | #load("@rules_oci//oci:oci.bzl", "oci_image") 3 | 4 | # Build the Go library 5 | go_library( 6 | name = "entity_lib", 7 | srcs = ["main.go"], 8 | importpath = "goclassifieds/api/entity", 9 | visibility = ["//visibility:private"], 10 | deps = [ 11 | "//lib/shapeshift", 12 | "@com_github_aws_aws_lambda_go//events", 13 | "@com_github_aws_aws_lambda_go//lambda", 14 | ], 15 | ) 16 | 17 | # Build the Go binary 18 | go_binary( 19 | name = "entity", 20 | embed = [":entity_lib"], 21 | goarch = "amd64", 22 | goos = "linux", 23 | importpath = "goclassifieds/api/entity", 24 | visibility = ["//visibility:public"], 25 | out = "bootstrap", # Output binary name 26 | ) -------------------------------------------------------------------------------- /hooks/entity_index/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "entity_index", 5 | embed = [":entity_index_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/hooks/entity_index", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "entity_index_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/hooks/entity_index", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/entity", 20 | "//lib/repo", 21 | "@org_golang_x_oauth2//:go_default_library", 22 | "@com_github_google_go_github_v46//github", 23 | "@com_github_aws_aws_lambda_go//lambda", 24 | ], 25 | ) 26 | -------------------------------------------------------------------------------- /hooks/index_create/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "index_create", 5 | embed = [":index_create_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/hooks/index_create", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "index_create_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/hooks/index_create", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/entity", 20 | "//lib/repo", 21 | "@org_golang_x_oauth2//:go_default_library", 22 | "@com_github_google_go_github_v46//github", 23 | "@com_github_aws_aws_lambda_go//lambda", 24 | ], 25 | ) 26 | -------------------------------------------------------------------------------- /func/grant_access/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "grant_access", 5 | embed = [":grant_access_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/grant_access", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "grant_access_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/func/grant_access", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/gov", 20 | "//lib/entity", 21 | "//lib/utils", 22 | "@com_github_gocql_gocql//:gocql", 23 | "@com_github_tangzero_inflector//:inflector", 24 | "@com_github_aws_aws_lambda_go//lambda", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /api/gov/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "gov", 5 | embed = [":gov_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/gov", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "gov_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/api/gov", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/gov", 20 | "//lib/utils", 21 | "@com_github_aws_aws_lambda_go//events", 22 | "@com_github_aws_aws_lambda_go//lambda", 23 | "@com_github_aws_aws_sdk_go//aws", 24 | "@com_github_aws_aws_sdk_go//aws/session", 25 | "@com_github_aws_aws_sdk_go//service/lambda", 26 | ], 27 | ) 28 | -------------------------------------------------------------------------------- /hooks/site_write/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "site_write", 5 | embed = [":site_write_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/hooks/site_write", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "site_write_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/hooks/site_write", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/entity", 20 | "//lib/repo", 21 | "@com_github_aws_aws_sdk_go//aws/session", 22 | "@com_github_aws_aws_sdk_go//service/lambda", 23 | "@org_golang_x_oauth2//:go_default_library", 24 | "@com_github_aws_aws_lambda_go//lambda", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /hooks/site_deploy/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "site_deploy", 5 | embed = [":site_deploy_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/hooks/site_deploy", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "site_deploy_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/hooks/site_deploy", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/entity", 20 | "//lib/repo", 21 | "@com_github_aws_aws_sdk_go//aws/session", 22 | "@com_github_aws_aws_sdk_go//service/lambda", 23 | "@org_golang_x_oauth2//:go_default_library", 24 | "@com_github_aws_aws_lambda_go//lambda", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/stack-reference.ts: -------------------------------------------------------------------------------- 1 | import * as pulumi from '@pulumi/pulumi'; 2 | 3 | type ExportTypes = typeof import('./pulumi'); 4 | type ExportTypesKey = keyof ExportTypes; 5 | type ExportTypesValue = ExportTypes[TKey]; 6 | 7 | type StrongTypedStackReference = Omit & { 8 | // eslint-disable-next-line no-unused-vars 9 | getOutput(name: pulumi.Input): ExportTypesValue; 10 | // eslint-disable-next-line no-unused-vars 11 | requireOutput(name: pulumi.Input): ExportTypesValue; 12 | }; 13 | 14 | export function getStackReference() { 15 | const org = pulumi.getOrganization(); 16 | const stack = pulumi.getStack(); 17 | return new pulumi.StackReference(`${org}/precheck-oidc-auth/${stack}`) as StrongTypedStackReference; 18 | } 19 | -------------------------------------------------------------------------------- /hooks/site_environment/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "site_environment", 5 | embed = [":site_environment_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/hooks/site_environment", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "site_environment_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/hooks/site_environment", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/entity", 20 | "//lib/repo", 21 | "@com_github_aws_aws_sdk_go//aws/session", 22 | "@com_github_aws_aws_sdk_go//service/lambda", 23 | "@org_golang_x_oauth2//:go_default_library", 24 | "@com_github_aws_aws_lambda_go//lambda", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /lib/repo/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "repo", 5 | srcs = ["repo.go"], 6 | importpath = "goclassifieds/lib/repo", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//lib/utils", 10 | "@com_github_shurcool_githubv4//:go_default_library", 11 | "@com_github_google_go_github_v46//github", 12 | "@org_golang_x_oauth2//:go_default_library", 13 | "@com_github_golang_jwt_jwt_v4//:go_default_library", 14 | # "@org_golang_x_crypto//nacl/box", # Add this line for golang.org/x/crypto 15 | "@com_github_kevinburke_nacl//box:go_default_library", # THIS IS THE KEY CHANGE 16 | "@com_github_aws_aws_sdk_go//service/lambda", 17 | "@com_github_aws_aws_sdk_go//aws", 18 | "@org_golang_x_crypto//ssh:go_default_library", # Add ssh library dependency 19 | ], 20 | ) -------------------------------------------------------------------------------- /func/renewable_record/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "renewable_record", 5 | embed = [":renewable_record_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/renewable_record", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "renewable_record_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/func/renewable_record", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/entity", 20 | "//lib/sign", 21 | "//lib/utils", 22 | "@com_github_aws_aws_lambda_go//lambda", 23 | "@com_github_aws_aws_lambda_go//events", 24 | "@com_github_aws_aws_sdk_go//aws/session", 25 | "@com_github_opensearch_project_opensearch_go//:opensearch-go", 26 | ], 27 | ) 28 | -------------------------------------------------------------------------------- /func/validate_entity/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "validate_entity", 5 | embed = [":validate_entity_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/validate_entity", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "validate_entity_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/func/validate_entity", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/ads", 20 | "//lib/cc", 21 | "//lib/chat", 22 | "//lib/entity", 23 | "//lib/profiles", 24 | "//lib/utils", 25 | "//lib/vocab", 26 | "@com_github_aws_aws_lambda_go//lambda", 27 | "@com_github_go_playground_validator_v10//:validator", 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /api/authorizer/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "authorizer", 5 | embed = [":authorizer_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/authorizer", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "authorizer_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/authorizer", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/utils", 24 | "@com_github_aws_aws_lambda_go//events", 25 | "@com_github_aws_aws_lambda_go//lambda", 26 | "@com_github_golang_jwt_jwt_v4//:go_default_library", 27 | "@com_github_micahparks_keyfunc//:go_default_library" 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /api/authorizer2/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "authorizer2", 5 | embed = [":authorizer2_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/authorizer2", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "authorizer2_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/authorizer2", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/utils", 24 | "@com_github_aws_aws_lambda_go//events", 25 | "@com_github_aws_aws_lambda_go//lambda", 26 | "@com_github_golang_jwt_jwt_v4//:go_default_library", 27 | "@com_github_micahparks_keyfunc//:go_default_library" 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /api/authorizer3/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "authorizer3", 5 | embed = [":authorizer3_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/authorizer3", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "authorizer3_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/authorizer3", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/utils", 24 | "@com_github_aws_aws_lambda_go//events", 25 | "@com_github_aws_aws_lambda_go//lambda", 26 | "@com_github_golang_jwt_jwt_v4//:go_default_library", 27 | "@com_github_micahparks_keyfunc//:go_default_library" 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /func/convert_media/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "convert_media", 5 | embed = [":convert_media_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/convert_media", 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | go_library( 13 | name = "convert_media_lib", 14 | srcs = ["main.go"], 15 | importpath = "goclassifieds/func/convert_media", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "@com_github_aws_aws_lambda_go//events", 19 | "@com_github_aws_aws_lambda_go//lambda", 20 | "@com_github_aws_aws_sdk_go//aws", 21 | "@com_github_aws_aws_sdk_go//aws/session", 22 | "@com_github_aws_aws_sdk_go//service/lambda", 23 | "@com_github_aws_aws_sdk_go//service/s3", 24 | "@com_github_aws_aws_sdk_go//service/s3/s3manager", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /api/stream/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "stream", 5 | embed = [":stream_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/stream", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "stream_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/stream", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/entity", 24 | "//lib/utils", 25 | "@com_github_aws_aws_lambda_go//events", 26 | "@com_github_aws_aws_lambda_go//lambda", 27 | "@com_github_aws_aws_sdk_go//aws/session", 28 | "@com_github_aws_aws_sdk_go//service/lambda", 29 | "@com_github_gocql_gocql//:gocql", 30 | ], 31 | ) 32 | -------------------------------------------------------------------------------- /api/entity/test_data/post_shapeshift.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource": "/{owner}/{repo}/shapeshifter/{proxy+}", 3 | "path": "/my-owner/my-repo/shapeshifter/example/550e8400-e29b-41d4-a716-446655440000", 4 | "httpMethod": "POST", 5 | "headers": { 6 | "Content-Type": "application/json", 7 | "Authorization": "Bearer eyJraWQiOiJVTlRrNGhWaFVycTBrOHVBR3ZEWXNcL3cwZ1wvaVcrYjlQd2VPYk1iTTZTXC93PSIsImFsZyI6IlJTMjU2In0..." 8 | }, 9 | "pathParameters": { 10 | "owner": "my-owner", 11 | "repo": "my-repo", 12 | "proxy": "example/550e8400-e29b-41d4-a716-446655440000" 13 | }, 14 | "queryStringParameters": {}, 15 | "requestContext": { 16 | "authorizer": { 17 | "claims": { 18 | "sub": "mock-user-id", 19 | "username": "mock-username" 20 | } 21 | }, 22 | "requestId": "mock-request-id" 23 | }, 24 | "body": "{\"id\": \"550e8400-e29b-41d4-a716-446655440000\", \"name\": \"GenericEntity\", \"type\": \"example\", \"attributes\": {\"key\": \"value\", \"key2\": \"value2\"}}" 25 | } -------------------------------------------------------------------------------- /job/renewable_report/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "renewable_report", 5 | embed = [":renewable_report_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/job/renewable_report", 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | go_library( 13 | name = "renewable_report_lib", 14 | srcs = ["main.go"], 15 | importpath = "goclassifieds/job/renewable_report", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "//lib/entity", 19 | "//lib/sign", 20 | "//lib/utils", 21 | "//lib/repo", 22 | "//lib/watttime", 23 | "@com_github_aws_aws_lambda_go//lambda", 24 | "@com_github_aws_aws_lambda_go//events", 25 | "@com_github_aws_aws_sdk_go//aws/session", 26 | "@org_golang_x_oauth2//:go_default_library", 27 | "@com_github_shurcool_githubv4//:go_default_library", 28 | "@com_github_opensearch_project_opensearch_go//:opensearch-go", 29 | ], 30 | ) 31 | -------------------------------------------------------------------------------- /api/chat/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "chat", 5 | embed = [":chat_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/chat", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "chat_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/chat", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/entity", 24 | "//lib/utils", 25 | "@com_github_aws_aws_lambda_go//events", 26 | "@com_github_aws_aws_lambda_go//lambda", 27 | "@com_github_aws_aws_sdk_go//aws", 28 | "@com_github_aws_aws_sdk_go//aws/session", 29 | "@com_github_aws_aws_sdk_go//service/apigatewaymanagementapi", 30 | "@com_github_aws_aws_sdk_go//service/lambda", 31 | "@com_github_gocql_gocql//:gocql", 32 | "@com_github_tangzero_inflector//:inflector", 33 | ], 34 | ) 35 | -------------------------------------------------------------------------------- /func/index_entity/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "index_entity", 5 | embed = [":index_entity_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/index_entity", 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | go_library( 13 | name = "index_entity_lib", 14 | srcs = ["main.go"], 15 | importpath = "goclassifieds/func/index_entity", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "//lib/ads", 19 | "//lib/attr", 20 | "//lib/cc", 21 | "//lib/entity", 22 | "//lib/vocab", 23 | "//lib/sign", 24 | "@com_github_aws_aws_lambda_go//events", 25 | "@com_github_aws_aws_lambda_go//lambda", 26 | "@com_github_aws_aws_sdk_go//aws/session", 27 | "@com_github_elastic_go_elasticsearch_v7//:go-elasticsearch", 28 | "@com_github_opensearch_project_opensearch_go//:opensearch-go", 29 | "@com_github_mitchellh_mapstructure//:mapstructure", 30 | "@com_github_tangzero_inflector//:inflector", 31 | ], 32 | ) 33 | -------------------------------------------------------------------------------- /lib/search/stats.go: -------------------------------------------------------------------------------- 1 | package search 2 | 3 | // IndexStats holds pre-calculated global statistics for IDF scoring. 4 | type IndexStats struct { 5 | TotalDocuments uint64 // N: Total number of documents in the index. 6 | 7 | // DocumentFrequency[token] stores the number of documents (df) 8 | // that contain the given analyzed token. 9 | DocumentFrequency map[string]uint64 10 | } 11 | 12 | // GetDocumentFrequency returns the document count for a token, or 1 if unknown. 13 | func (i *IndexStats) GetDocumentFrequency(token string) uint64 { 14 | if df, ok := i.DocumentFrequency[token]; ok && df > 0 { 15 | return df 16 | } 17 | // Default to 1 (or a small smoothing value) to prevent division by zero/log(1). 18 | return 1 19 | } 20 | 21 | // CalculateIDF returns the Inverse Document Frequency score for a given token. 22 | // Formula: log(1 + (N - df + 0.5) / (df + 0.5)) (a common variation for better stability) 23 | func (i *IndexStats) CalculateIDF(token string) float64 { 24 | N := float64(i.TotalDocuments) 25 | df := float64(i.GetDocumentFrequency(token)) 26 | 27 | return math.Log(1 + (N - df + 0.5) / (df + 0.5)) 28 | } -------------------------------------------------------------------------------- /api/user/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "user", 5 | embed = [":user_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/user", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "user_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/user", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/entity", 24 | "//lib/utils", 25 | "@com_github_aws_aws_lambda_go//events", 26 | "@com_github_aws_aws_lambda_go//lambda", 27 | "@com_github_aws_aws_sdk_go//aws/session", 28 | "@com_github_aws_aws_sdk_go//service/cognitoidentityprovider", 29 | "@com_github_aws_aws_sdk_go//service/ses", 30 | "@org_golang_x_oauth2//:go_default_library", 31 | "@com_github_google_go_github_v46//github", 32 | "@com_github_aws_aws_sdk_go//aws", 33 | "@com_github_sethvargo_go_password//password", 34 | ], 35 | ) 36 | -------------------------------------------------------------------------------- /api/media/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "media", 5 | embed = [":media_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/media", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "media_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/media", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/utils", 24 | "//lib/repo", 25 | "//lib/gov", 26 | "@com_github_aws_aws_lambda_go//events", 27 | "@com_github_aws_aws_lambda_go//lambda", 28 | "@com_github_aws_aws_sdk_go//aws", 29 | "@com_github_aws_aws_sdk_go//aws/session", 30 | "@com_github_aws_aws_sdk_go//service/s3", 31 | "@com_github_aws_aws_sdk_go//service/s3/s3manager", 32 | "@org_golang_x_oauth2//:go_default_library", 33 | "@com_github_shurcool_githubv4//:go_default_library", 34 | "@com_github_google_go_github_v46//github", 35 | "@com_github_aws_aws_sdk_go//service/lambda", 36 | ], 37 | ) 38 | -------------------------------------------------------------------------------- /api/chat/AmazonRootCA1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF 3 | ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 4 | b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL 5 | MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv 6 | b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj 7 | ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM 8 | 9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw 9 | IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 10 | VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L 11 | 93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm 12 | jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC 13 | AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA 14 | A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI 15 | U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs 16 | N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv 17 | o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU 18 | 5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy 19 | rqXRfboQnoZsG4q5WTP468SQvvG5 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /api/stream/AmazonRootCA1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF 3 | ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 4 | b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL 5 | MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv 6 | b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj 7 | ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM 8 | 9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw 9 | IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 10 | VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L 11 | 93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm 12 | jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC 13 | AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA 14 | A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI 15 | U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs 16 | N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv 17 | o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU 18 | 5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy 19 | rqXRfboQnoZsG4q5WTP468SQvvG5 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /func/grant_access/AmazonRootCA1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF 3 | ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 4 | b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL 5 | MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv 6 | b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj 7 | ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM 8 | 9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw 9 | IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 10 | VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L 11 | 93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm 12 | jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC 13 | AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA 14 | A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI 15 | U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs 16 | N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv 17 | o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU 18 | 5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy 19 | rqXRfboQnoZsG4q5WTP468SQvvG5 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /func/readable_profiles/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "goclassifieds/lib/entity" 7 | "log" 8 | 9 | "github.com/aws/aws-lambda-go/lambda" 10 | ) 11 | 12 | func handler(ctx context.Context, payload *entity.EntityDataRequest) (entity.EntityDataResponse, error) { 13 | log.Print("Inside readable_profiles") 14 | log.Printf("Entity: %s", payload.EntityName) 15 | 16 | rootIds := make(map[string]bool) 17 | for _, p := range payload.Data { 18 | id := fmt.Sprint(p["id"]) 19 | parentId := fmt.Sprint(p["parentId"]) 20 | _, ok := rootIds[id] 21 | if !ok && (parentId == "" || parentId == "") { 22 | rootIds[id] = true 23 | } 24 | } 25 | for _, p := range payload.Data { 26 | parentId := fmt.Sprint(p["parentId"]) 27 | _, ok := rootIds[parentId] 28 | if !ok && parentId != "" && parentId != "" { 29 | rootIds[parentId] = true 30 | } 31 | } 32 | i := 0 33 | ids := make([]map[string]interface{}, len(rootIds)) 34 | for key := range rootIds { 35 | ids[i] = map[string]interface{}{"value": key} 36 | i++ 37 | } 38 | 39 | return entity.EntityDataResponse{ 40 | Data: ids, 41 | }, nil 42 | } 43 | 44 | func main() { 45 | // Make the handler available for Remote Procedure Call by AWS Lambda 46 | lambda.Start(handler) 47 | } 48 | -------------------------------------------------------------------------------- /iac/precheck-cognito/post-confirmation/index.js: -------------------------------------------------------------------------------- 1 | const AWS = require("aws-sdk"); 2 | 3 | exports.handler = async (event) => { 4 | console.log("Event:", JSON.stringify(event, null, 2)); 5 | 6 | const userPoolId = event.userPoolId; 7 | const userName = event.userName; 8 | const email = event.request.userAttributes.email; 9 | const phoneNumberVerified = event.request.userAttributes.phone_number_verified; 10 | 11 | // Only proceed if phone number is verified 12 | if (phoneNumberVerified === "true" && email) { 13 | const cognito = new AWS.CognitoIdentityServiceProvider(); 14 | 15 | try { 16 | // Update the email_verified attribute to true 17 | await cognito.adminUpdateUserAttributes({ 18 | UserPoolId: userPoolId, 19 | Username: userName, 20 | UserAttributes: [ 21 | { 22 | Name: "email_verified", 23 | Value: "true", 24 | }, 25 | ], 26 | }).promise(); 27 | 28 | console.log(`Email verified for user: ${userName}`); 29 | } catch (error) { 30 | console.error("Error updating email_verified:", error); 31 | throw error; 32 | } 33 | } 34 | 35 | return event; 36 | }; -------------------------------------------------------------------------------- /func/enforce_contract/index.js: -------------------------------------------------------------------------------- 1 | const Ajv = require("ajv") 2 | const addFormats = require("ajv-formats") 3 | 4 | const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} 5 | addFormats(ajv) 6 | 7 | module.exports.handler = function(event, context, callback) { 8 | console.log('hello world'); 9 | console.log(event); 10 | console.log(context); 11 | 12 | /*const schema = { 13 | type: "object", 14 | properties: { 15 | foo: {type: "integer"}, 16 | bar: {type: "string"} 17 | }, 18 | required: ["foo"], 19 | additionalProperties: false 20 | }*/ 21 | 22 | const contract = event.Contract; 23 | const schema = contract.schema; 24 | if (typeof(schema['$schema']) !== 'undefined') { 25 | delete schema['$schema']; 26 | } 27 | 28 | console.log('schema', schema); 29 | 30 | const validate = ajv.compile(schema) 31 | 32 | /*const data = { 33 | foo: "blah", 34 | bar: "abc" 35 | }*/ 36 | 37 | const data = event.Entity; 38 | console.log('entity', data); 39 | 40 | const valid = validate(data) 41 | console.log('errors', validate.errors) 42 | 43 | const res = { 44 | Entity: { ...data, userId: event.UserId }, 45 | Valid: validate.errors == null, 46 | Unauthorized: false, 47 | Errors: validate.errors 48 | }; 49 | 50 | //var json = cssjson.toJSON(event.Content, { comments: true }); 51 | //console.log(json); 52 | 53 | callback(null, res); 54 | } -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/shared/logger.js: -------------------------------------------------------------------------------- 1 | // Helper function to format log messages with context variables 2 | const format = (level, contextVars, message, additionalData) => { 3 | const timestamp = new Date().toISOString(); 4 | const contextString = Object.entries(contextVars) 5 | .map(([key, value]) => `${key}=${value}`) 6 | .join(" "); 7 | const additionalString = additionalData ? JSON.stringify(additionalData) : ""; 8 | return `${timestamp} [${level}] ${contextString} ${message} ${additionalString}`; 9 | }; 10 | 11 | // Functional logger setup to override console methods 12 | const logger = (contextVars) => { 13 | const log = console.log; 14 | const error = console.error; 15 | const warn = console.warn; 16 | const debug = console.debug; 17 | 18 | // Wrap each console method with custom formatting 19 | console.log = (message, additionalData) => 20 | log(format("INFO", contextVars, message, additionalData)); 21 | 22 | console.error = (message, additionalData) => 23 | error(format("ERROR", contextVars, message, additionalData)); 24 | 25 | console.warn = (message, additionalData) => 26 | warn(format("WARN", contextVars, message, additionalData)); 27 | 28 | console.debug = (message, additionalData) => 29 | debug(format("DEBUG", contextVars, message, additionalData)); 30 | }; 31 | 32 | // Export the setup function 33 | module.exports = logger; -------------------------------------------------------------------------------- /cloudform/search2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | STACK_NAME="opensearch-cognito-stack2" 5 | TEMPLATE_FILE="search2.yaml" # Name of the CloudFormation template file 6 | REGION="us-east-1" # Change to your preferred AWS Region 7 | 8 | # Replace these with your existing Cognito resource IDs 9 | COGNITO_USER_POOL_ID="us-east-1_sWRV0kAgS" 10 | COGNITO_IDENTITY_POOL_ID="us-east-1:026caf18-c852-451b-a93e-fb431c4eee6d" 11 | MASTER_USER_ARN="arn:aws:iam::032425924121:user/tzmijewski" # Replace with the actual master user's ARN 12 | 13 | # Deploy the stack 14 | echo "Deploying CloudFormation stack: $STACK_NAME" 15 | 16 | aws cloudformation deploy \ 17 | --template-file "$TEMPLATE_FILE" \ 18 | --stack-name "$STACK_NAME" \ 19 | --region "$REGION" \ 20 | --capabilities CAPABILITY_NAMED_IAM \ 21 | --parameter-overrides "CognitoUserPoolId=$COGNITO_USER_POOL_ID" "CognitoIdentityPoolId=$COGNITO_IDENTITY_POOL_ID" "MasterUserARN=$MASTER_USER_ARN" 22 | 23 | # Check if the stack creation/update was successful 24 | if [ $? -eq 0 ]; then 25 | echo "CloudFormation stack deployed successfully!" 26 | 27 | # Retrieve the OpenSearch domain endpoint as an output 28 | OPENSEARCH_ENDPOINT=$(aws cloudformation describe-stacks \ 29 | --stack-name "$STACK_NAME" \ 30 | --region "$REGION" \ 31 | --query "Stacks[0].Outputs[?OutputKey=='OpenSearchEndpoint'].OutputValue" \ 32 | --output text) 33 | 34 | echo "OpenSearch Domain Endpoint: $OPENSEARCH_ENDPOINT" 35 | else 36 | echo "Failed to deploy CloudFormation stack." 37 | exit 1 38 | fi -------------------------------------------------------------------------------- /lib/entity/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "entity", 5 | srcs = ["entity.go"], 6 | importpath = "goclassifieds/lib/entity", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//lib/attr", 10 | "//lib/es", 11 | "//lib/os", 12 | "//lib/utils", 13 | "//lib/repo", 14 | "//lib/gov", 15 | "@com_github_aws_aws_lambda_go//events", 16 | "@com_github_aws_aws_sdk_go//aws", 17 | "@com_github_aws_aws_sdk_go//aws/session", 18 | "@com_github_aws_aws_sdk_go//service/cognitoidentityprovider", 19 | "@com_github_aws_aws_sdk_go//service/lambda", 20 | "@com_github_aws_aws_sdk_go//service/sfn", 21 | "@com_github_aws_aws_sdk_go//service/s3", 22 | "@com_github_aws_aws_sdk_go//service/s3/s3manager", 23 | "@com_github_elastic_go_elasticsearch//esapi", 24 | "@com_github_elastic_go_elasticsearch_v7//:go-elasticsearch", 25 | "@com_github_opensearch_project_opensearch_go//:opensearch-go", 26 | "@com_github_opensearch_project_opensearch_go//opensearchapi", 27 | "@com_github_go_playground_validator_v10//:validator", 28 | "@com_github_gocql_gocql//:gocql", 29 | "@com_github_mitchellh_mapstructure//:mapstructure", 30 | "@com_github_tangzero_inflector//:inflector", 31 | "@com_github_shurcool_githubv4//:go_default_library", 32 | "@org_golang_x_oauth2//:go_default_library", 33 | "@com_github_google_go_github_v46//github", 34 | ], 35 | ) 36 | -------------------------------------------------------------------------------- /lib/shapeshift/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | #go_test( 3 | # name = "mainTest", 4 | # srcs = ["main_test.go"], 5 | #) 6 | 7 | go_library( 8 | name = "shapeshift", 9 | srcs = ["shapeshift.go"], 10 | importpath = "goclassifieds/lib/shapeshift", 11 | visibility = ["//visibility:public"], 12 | deps = [ 13 | "//lib/entity", 14 | "//lib/sign", 15 | "//lib/gov", 16 | "//lib/repo", 17 | "//lib/utils", 18 | "@com_github_aws_aws_lambda_go//events", 19 | "@com_github_aws_aws_lambda_go//lambda", 20 | "@com_github_aws_aws_sdk_go//aws/session", 21 | "@com_github_aws_aws_sdk_go//service/lambda", 22 | "@com_github_aws_aws_sdk_go//service/sfn", 23 | "@com_github_aws_aws_sdk_go//aws/credentials", 24 | "@com_github_aws_aws_sdk_go//aws/signer/v4:go_default_library", 25 | "@com_github_elastic_go_elasticsearch_v7//:go-elasticsearch", 26 | "@com_github_opensearch_project_opensearch_go//:opensearch-go", 27 | "@com_github_mitchellh_mapstructure//:mapstructure", 28 | "@com_github_tangzero_inflector//:inflector", 29 | "@com_github_shurcool_githubv4//:go_default_library", 30 | "@org_golang_x_oauth2//:go_default_library", 31 | "@com_github_aws_aws_sdk_go//aws", 32 | "@com_github_aws_aws_sdk_go//service/cognitoidentityprovider", 33 | "@com_github_google_go_github_v46//github", 34 | "@com_github_golang_jwt_jwt_v4//:go_default_library", 35 | "@com_github_gocql_gocql//:gocql", 36 | ], 37 | ) 38 | -------------------------------------------------------------------------------- /cloudform/search_minimal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Shell script to deploy an AWS CloudFormation template for OpenSearch. 4 | 5 | # Set variables 6 | STACK_NAME="MinimalOpenSearchStack" 7 | TEMPLATE_FILE="search_minimal.yaml" # Ensure the YAML template file is in the same directory as this script 8 | REGION="us-east-1" # Change this to your target AWS region 9 | MASTER_PASSWORD="-Password1!" # Replace with your desired master password 10 | 11 | # Create the CloudFormation Stack 12 | echo "Deploying the CloudFormation stack: ${STACK_NAME} in region: ${REGION}..." 13 | aws cloudformation create-stack \ 14 | --stack-name ${STACK_NAME} \ 15 | --template-body file://${TEMPLATE_FILE} \ 16 | --parameters ParameterKey=MasterPassword,ParameterValue=${MASTER_PASSWORD} \ 17 | --capabilities CAPABILITY_NAMED_IAM \ 18 | --region ${REGION} 19 | 20 | # Wait for the deployment to complete 21 | if [ $? -eq 0 ]; then 22 | echo "Stack creation initiated successfully. Waiting for completion..." 23 | aws cloudformation wait stack-create-complete --stack-name ${STACK_NAME} --region ${REGION} 24 | 25 | if [ $? -eq 0 ]; then 26 | echo "Stack created successfully!" 27 | 28 | # Fetch and display the OpenSearch endpoint 29 | OPENSEARCH_ENDPOINT=$(aws cloudformation describe-stacks \ 30 | --stack-name ${STACK_NAME} \ 31 | --query "Stacks[0].Outputs[?OutputKey=='OpenSearchEndpoint'].OutputValue" \ 32 | --output text \ 33 | --region ${REGION}) 34 | 35 | echo "OpenSearch Endpoint: ${OPENSEARCH_ENDPOINT}" 36 | else 37 | echo "Error: Stack creation failed!" 38 | fi 39 | else 40 | echo "Error: Failed to initiate stack creation!" 41 | fi -------------------------------------------------------------------------------- /lib/attr/attr.go: -------------------------------------------------------------------------------- 1 | package attr 2 | 3 | import "strconv" 4 | 5 | type AttributeTypes int32 6 | 7 | const ( 8 | Number AttributeTypes = iota 9 | Text 10 | Complex 11 | Float 12 | Array 13 | Bool 14 | ) 15 | 16 | type AttributeValue struct { 17 | Name string `form:"name" json:"name" binding:"required"` 18 | DisplayName string `form:"displayName" json:"displayName" binding:"required"` 19 | Type AttributeTypes `form:"type" json:"type" binding:"required"` 20 | Value string `form:"value" json:"value" binding:"required"` 21 | IntValue int32 `json:"intValue"` 22 | FloatValue float64 `json:"floatValue"` 23 | ComputedValue string `form:"computedValue" json:"computedValue" binding:"required"` 24 | Attributes []AttributeValue `form:"attributes[]" json:"attributes"` 25 | } 26 | 27 | func FlattenAttributeValue(value AttributeValue) []AttributeValue { 28 | leafNodes := make([]AttributeValue, 0) 29 | if value.Attributes == nil || len(value.Attributes) == 0 { 30 | leafNodes = append(leafNodes, value) 31 | } else { 32 | for _, attr := range value.Attributes { 33 | flatChildren := FlattenAttributeValue(attr) 34 | for _, flatChild := range flatChildren { 35 | leafNodes = append(leafNodes, flatChild) 36 | } 37 | } 38 | } 39 | return leafNodes 40 | } 41 | 42 | func FinalizeAttributeValue(value *AttributeValue) { 43 | if value.Type == Number { 44 | computedValue, _ := strconv.ParseInt(value.ComputedValue, 10, 32) 45 | value.IntValue = int32(computedValue) 46 | } else if value.Type == Float { 47 | computedValue, _ := strconv.ParseFloat(value.ComputedValue, 64) 48 | value.FloatValue = computedValue 49 | } else { 50 | value.IntValue = 0 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /func/watttime_demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "log" 7 | "os" 8 | 9 | "goclassifieds/lib/watttime" 10 | 11 | "github.com/aws/aws-lambda-go/lambda" 12 | ) 13 | 14 | type RegionIntensity struct { 15 | Name string 16 | Ba string 17 | Intensity int 18 | } 19 | 20 | func handler(ctx context.Context, b json.RawMessage) { 21 | log.Print("top handler") 22 | 23 | loginInput := &watttime.LoginInput{ 24 | Username: os.Getenv("WATTTIME_USERNAME"), 25 | Password: os.Getenv("WATTTIME_PASSWORD"), 26 | } 27 | 28 | loginRes, err := watttime.Login(loginInput) 29 | if err != nil { 30 | log.Print(err.Error()) 31 | } 32 | 33 | // log.Printf("token = %s", loginRes.Token) 34 | 35 | regionsInput := &watttime.GridRegionsInput{ 36 | Token: loginRes.Token, 37 | } 38 | 39 | gridRegionsRes, err := watttime.GridRegions(regionsInput) 40 | if err != nil { 41 | log.Print(err.Error()) 42 | } 43 | 44 | intensities := make([]*RegionIntensity, len(gridRegionsRes.GridRegions)) 45 | 46 | for index, region := range gridRegionsRes.GridRegions { 47 | log.Print("region: " + region.Name + " | Ba: " + region.Ba) 48 | indexInput := &watttime.IndexInput{ 49 | Token: loginRes.Token, 50 | Ba: region.Ba, 51 | } 52 | intensity := &RegionIntensity{ 53 | Name: region.Name, 54 | Ba: region.Ba, 55 | } 56 | intensities[index] = intensity 57 | indexRes, err := watttime.GetIndex(indexInput) 58 | if err != nil { 59 | log.Print(err.Error()) 60 | } else { 61 | intensity.Intensity = indexRes.Percent 62 | log.Printf("Grid Intensity: %d", indexRes.Percent) 63 | log.Printf("Grid MOER: %f", indexRes.Moer) 64 | } 65 | } 66 | } 67 | 68 | func main() { 69 | log.SetFlags(0) 70 | lambda.Start(handler) 71 | } 72 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/user/index.js: -------------------------------------------------------------------------------- 1 | const fetch = require("node-fetch"); 2 | const logger = require("./logger"); 3 | 4 | exports.handler = async (event) => { 5 | logger({}); 6 | 7 | console.log("Received event:", JSON.stringify(event, null, 2)); 8 | 9 | // Extract the Authorization header 10 | const authorizationHeader = event.headers?.Authorization || event.headers?.authorization; 11 | if (!authorizationHeader || !authorizationHeader.startsWith("Bearer ")) { 12 | return { 13 | statusCode: 401, 14 | body: JSON.stringify({ error: "Unauthorized: Missing or invalid Authorization header" }), 15 | }; 16 | } 17 | 18 | const accessToken = authorizationHeader.split(" ")[1]; 19 | 20 | try { 21 | // Fetch user information from Login.gov `/userinfo` endpoint 22 | const userInfoResponse = await fetch(`${process.env.LOGIN_GOV_ISSUER}/api/openid_connect/userinfo`, { 23 | method: "GET", 24 | headers: { 25 | Authorization: `Bearer ${accessToken}`, 26 | }, 27 | }); 28 | 29 | if (!userInfoResponse.ok) { 30 | console.error("Failed to fetch user info from Login.gov:", userInfoResponse.statusText); 31 | return { 32 | statusCode: userInfoResponse.status, 33 | body: await userInfoResponse.text(), 34 | }; 35 | } 36 | 37 | const userInfo = await userInfoResponse.json(); 38 | 39 | // Return the user information 40 | return { 41 | statusCode: 200, 42 | body: JSON.stringify(userInfo), 43 | }; 44 | } catch (error) { 45 | console.error("Error fetching user info from Login.gov:", error); 46 | return { 47 | statusCode: 500, 48 | body: JSON.stringify({ error: "Failed to fetch user info", details: error.message }), 49 | }; 50 | } 51 | }; -------------------------------------------------------------------------------- /cloudform/search_minimal.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Minimal OpenSearch instance using the smallest node type (t3.small.search) with fine-grained access control. 3 | 4 | Resources: 5 | OpenSearchDomain: 6 | Type: AWS::OpenSearchService::Domain 7 | Properties: 8 | DomainName: minimal-opensearch 9 | EngineVersion: OpenSearch_2.5 10 | ClusterConfig: 11 | InstanceType: t3.small.search 12 | InstanceCount: 1 13 | DedicatedMasterEnabled: false 14 | ZoneAwarenessEnabled: false 15 | EBSOptions: 16 | EBSEnabled: true 17 | VolumeType: gp2 18 | VolumeSize: 10 19 | AdvancedSecurityOptions: 20 | Enabled: true 21 | InternalUserDatabaseEnabled: true 22 | MasterUserOptions: 23 | MasterUserName: admin 24 | MasterUserPassword: !Sub '${MasterPassword}' 25 | EncryptionAtRestOptions: 26 | Enabled: true # Enable encryption at rest 27 | NodeToNodeEncryptionOptions: 28 | Enabled: true # Enable node-to-node encryption 29 | DomainEndpointOptions: 30 | EnforceHTTPS: true # Enable HTTPS enforcement 31 | AccessPolicies: 32 | Version: '2012-10-17' 33 | Statement: 34 | - Effect: Allow 35 | Principal: 36 | AWS: '*' 37 | Action: 'es:*' 38 | Resource: '*' 39 | 40 | Parameters: 41 | MasterPassword: 42 | Type: String 43 | Description: Master user password for OpenSearch fine-grained access control 44 | NoEcho: true 45 | MinLength: 8 46 | MaxLength: 32 47 | AllowedPattern: '[A-Za-z0-9@!?*#_+=^.-]+' 48 | ConstraintDescription: Password must be 8-32 characters long and can include letters, numbers, and special characters. 49 | 50 | Outputs: 51 | OpenSearchEndpoint: 52 | Description: Endpoint of the OpenSearch domain 53 | Value: !GetAtt OpenSearchDomain.Endpoint -------------------------------------------------------------------------------- /func/encrypt_secret_value/index.js: -------------------------------------------------------------------------------- 1 | const sodium = require("libsodium-wrappers"); 2 | 3 | module.exports.handler = async (event) => { 4 | try { 5 | await sodium.ready; 6 | 7 | const { publicKey: publicKeyBase64, secretValue } = event; 8 | 9 | // Validate the inputs 10 | if (!publicKeyBase64 || typeof publicKeyBase64 !== "string") { 11 | throw new Error(`Invalid or missing publicKey: ${publicKeyBase64}`); 12 | } 13 | if (!secretValue || typeof secretValue !== "string") { 14 | throw new Error(`Invalid or missing secretValue: ${secretValue}`); 15 | } 16 | 17 | // Decode public key 18 | const publicKey = Buffer.from(publicKeyBase64, "base64"); 19 | if (publicKey.length !== sodium.crypto_box_PUBLICKEYBYTES) { 20 | throw new Error( 21 | `Invalid publicKey length: Expected ${sodium.crypto_box_PUBLICKEYBYTES}, got ${publicKey.length}` 22 | ); 23 | } 24 | 25 | // Encrypt the message 26 | const message = Buffer.from(secretValue, "utf8"); 27 | const sealedBox = sodium.crypto_box_seal(message, publicKey); 28 | 29 | // Convert the sealed box to Base64 30 | const encryptedValue = Buffer.from(sealedBox).toString("base64"); 31 | 32 | // Ensure the encryptedValue is not empty 33 | if (!encryptedValue) { 34 | throw new Error("Failed to encrypt secretValue; Empty encryptedValue."); 35 | } 36 | 37 | console.log("Encrypted Value:", encryptedValue); 38 | return { 39 | statusCode: 200, 40 | body: JSON.stringify({ encryptedValue }), 41 | }; 42 | } catch (error) { 43 | console.error("Lambda error:", error.message); 44 | return { 45 | statusCode: 500, 46 | body: JSON.stringify({ message: `Error processing request: ${error.message}` }), 47 | }; 48 | } 49 | }; -------------------------------------------------------------------------------- /cloudform/main.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: 'AWS CloudFormation Sample Template: This template automates ase infrastructure.' 3 | 4 | Parameters: 5 | EnvironmentName: 6 | Description: The name of the environment (e.g., dev, prod) 7 | Type: String 8 | EnvironmentNameCamelCase: 9 | Description: The name of the environment (e.g., dev, prod) in CamelCase for supporting multiple environments in same account without conflicts. 10 | Type: String 11 | VendorSuffix: 12 | Description: Unique vendor suffix for the bucket. 13 | Type: String 14 | VendorSuffixCamelCase: 15 | Description: Unique vendor suffix in CamelCase for supporting multiple vendors under the same account. 16 | Type: String 17 | SSODomain: 18 | Description: The domain for hosting sso page. 19 | Type: String 20 | 21 | Resources: 22 | UsersStack: 23 | Type: AWS::CloudFormation::Stack 24 | Properties: 25 | TemplateURL: !Sub 'https://s3.amazonaws.com/rtc-classifieds-cloudformation-templates-${EnvironmentName}-${VendorSuffix}/users.yaml' 26 | Parameters: 27 | EnvironmentName: !Ref EnvironmentName 28 | EnvironmentNamCamelCase: !Ref EnvironmentNameCamelCase 29 | VendorSuffix: !Ref VendorSuffix 30 | VendorSuffixCamelCase: !Ref VendorSuffixCamelCase 31 | SSODomain: !Ref SSODomain 32 | 33 | SearchStack: 34 | Type: AWS::CloudFormation::Stack 35 | Properties: 36 | TemplateURL: !Sub 'https://s3.amazonaws.com/rtc-classifieds-cloudformation-templates-${EnvironmentName}-${VendorSuffix}/search.yaml' 37 | Parameters: 38 | EnvironmentName: !Ref EnvironmentName 39 | EnvironmentNamCamelCase: !Ref EnvironmentNameCamelCase 40 | VendorSuffix: !Ref VendorSuffix 41 | VendorSuffixCamelCase: !Ref VendorSuffixCamelCase 42 | UserPoolId: !GetAtt UsersStack.Outputs.UserPoolId 43 | IdentityPoolId: !GetAtt UsersStack.Outputs.IdentityPoolId -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/README.md: -------------------------------------------------------------------------------- 1 | implementation of an OIDC adapter (pkce:prononciation of PKCE) to bridge compatibility gaps between Login.gov and AWS Cognito and centralize external auth and/or support internal legacy user databases without requiring explicit migrations. The following changes are included: 2 | 3 | **Pulumi Infrastructure:** 4 | 5 | Created DynamoDB tables for state, code verifier, and redirect URI tracking. 6 | Deployed API Gateway with Lambda-backed endpoints ( 7 | 8 | - /login 9 | - /callback 10 | - /token 11 | - /user 12 | 13 | Configured Lambda functions with required IAM permissions and environment variables. 14 | 15 | **Lambda Functions:** 16 | 17 | - loginLambda: Handles the /login endpoint to initiate the OIDC flow with PKCE (Proof Key for Code Exchange) and redirects users to Login.gov. 18 | - callbackLambda: Processes Login.gov responses at the /callback endpoint, exchanges authorization codes for tokens, and redirects users back to Cognito. 19 | - tokenLambda: Implements the /token endpoint for exchanging authorization codes for tokens. 20 | - userLambda: Retrieves user profile information from Cognito's /userinfo endpoint. 21 | 22 | **Features:** 23 | 24 | - Secure state management with short-lived TTL in DynamoDB for mitigating CSRF and replay attacks. 25 | - PKCE-based implementation for enhanced security in OIDC flows. 26 | - Dynamic discovery of Cognito endpoints using the OpenID Connect discovery document. 27 | - Comprehensive error handling and logging for debugging. 28 | 29 | **Testing Notes:** 30 | 31 | - Deployed and tested the full OIDC flow end-to-end, including Login.gov authentication and Cognito redirection. 32 | - Verified DynamoDB entries (state, authorization codes) and their TTL behavior. 33 | - Ensured error propagation for invalid or expired tokens. 34 | - Impact: This implementation enables seamless integration with Login.gov while maintaining compatibility with AWS Cognito, providing a robust and secure authentication solution for our monorepo. -------------------------------------------------------------------------------- /cloudform/search_distinct.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Shell script to deploy an AWS CloudFormation template for OpenSearch. 4 | 5 | # Set variables 6 | STACK_NAME="DistinctOpenSearchStack" # Updated the stack name to avoid conflicts 7 | TEMPLATE_FILE="search_distinct.yaml" # Ensure the updated YAML template file is in the same directory as this script 8 | REGION="us-east-1" # Change this to your target AWS region 9 | MASTER_PASSWORD="-Password1!" # Replace with your desired master password 10 | COGNITO_USER_POOL_ID="us-east-1_sWRV0kAgS" # Replace with the existing Cognito User Pool ID 11 | COGNITO_IDENTITY_POOL_ID="us-east-1:026caf18-c852-451b-a93e-fb431c4eee6d" # Replace with the existing Cognito Identity Pool ID 12 | 13 | # Create the CloudFormation Stack 14 | echo "Deploying the CloudFormation stack: ${STACK_NAME} in region: ${REGION}..." 15 | aws cloudformation create-stack \ 16 | --stack-name ${STACK_NAME} \ 17 | --template-body file://${TEMPLATE_FILE} \ 18 | --parameters \ 19 | ParameterKey=MasterPassword,ParameterValue=${MASTER_PASSWORD} \ 20 | ParameterKey=CognitoUserPoolId,ParameterValue=${COGNITO_USER_POOL_ID} \ 21 | ParameterKey=CognitoIdentityPoolId,ParameterValue=${COGNITO_IDENTITY_POOL_ID} \ 22 | --capabilities CAPABILITY_NAMED_IAM \ 23 | --region ${REGION} 24 | 25 | # Wait for the deployment to complete 26 | if [ $? -eq 0 ]; then 27 | echo "Stack creation initiated successfully. Waiting for completion..." 28 | aws cloudformation wait stack-create-complete --stack-name ${STACK_NAME} --region ${REGION} 29 | 30 | if [ $? -eq 0 ]; then 31 | echo "Stack created successfully!" 32 | 33 | # Fetch and display the OpenSearch endpoint 34 | OPENSEARCH_ENDPOINT=$(aws cloudformation describe-stacks \ 35 | --stack-name ${STACK_NAME} \ 36 | --query "Stacks[0].Outputs[?OutputKey=='DistinctOpenSearchEndpoint'].OutputValue" \ 37 | --output text \ 38 | --region ${REGION}) 39 | 40 | echo "OpenSearch Endpoint: ${OPENSEARCH_ENDPOINT}" 41 | else 42 | echo "Error: Stack creation failed!" 43 | fi 44 | else 45 | echo "Error: Failed to initiate stack creation!" 46 | fi -------------------------------------------------------------------------------- /cloudform/cognito_role.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | STACK_NAME="CognitoAuthenticatedRoleStack" 5 | TEMPLATE_FILE="cognito_role.yaml" # Ensure the YAML template file is in the same directory 6 | REGION="us-east-1" # Change to your AWS target region 7 | IDENTITY_POOL_ID="us-east-1:026caf18-c852-451b-a93e-fb431c4eee6d" # Replace with your Cognito Identity Pool ID 8 | OPENSEARCH_DOMAIN_NAME="rtc-classifieds-dev-rtc" # Replace with your OpenSearch domain name 9 | ENVIRONMENT_NAME_CAMEL_CASE="Dev" # Replace with your environment name in camel case 10 | VENDOR_SUFFIX_CAMEL_CASE="Rtc" # Replace with your vendor suffix in camel case 11 | 12 | # Deploy the CloudFormation stack 13 | echo "Deploying the CloudFormation stack: ${STACK_NAME} in region: ${REGION}..." 14 | aws cloudformation create-stack \ 15 | --stack-name ${STACK_NAME} \ 16 | --template-body file://${TEMPLATE_FILE} \ 17 | --parameters ParameterKey=IdentityPoolId,ParameterValue=${IDENTITY_POOL_ID} \ 18 | ParameterKey=OpenSearchDomainName,ParameterValue=${OPENSEARCH_DOMAIN_NAME} \ 19 | ParameterKey=EnvironmentNameCamelCase,ParameterValue=${ENVIRONMENT_NAME_CAMEL_CASE} \ 20 | ParameterKey=VendorSuffixCamelCase,ParameterValue=${VENDOR_SUFFIX_CAMEL_CASE} \ 21 | --capabilities CAPABILITY_NAMED_IAM \ 22 | --region ${REGION} 23 | 24 | # Wait for stack creation to complete 25 | if [ $? -eq 0 ]; then 26 | echo "Stack creation initiated successfully. Waiting for completion..." 27 | aws cloudformation wait stack-create-complete --stack-name ${STACK_NAME} --region ${REGION} 28 | 29 | if [ $? -eq 0 ]; then 30 | echo "Stack created successfully!" 31 | 32 | # Fetch and display the role ARN 33 | COGNITO_ROLE_ARN=$(aws cloudformation describe-stacks \ 34 | --stack-name ${STACK_NAME} \ 35 | --query "Stacks[0].Outputs[?OutputKey=='CognitoRoleArn'].OutputValue" \ 36 | --output text \ 37 | --region ${REGION}) 38 | 39 | echo "Cognito Authenticated Role ARN: ${COGNITO_ROLE_ARN}" 40 | else 41 | echo "Error: Stack creation failed!" 42 | fi 43 | else 44 | echo "Error: Failed to initiate stack creation!" 45 | fi -------------------------------------------------------------------------------- /cloudform/cognito_role.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: CloudFormation template to create an IAM role for Cognito-authenticated users to access OpenSearch and OpenSearch Dashboards. 3 | 4 | Resources: 5 | CognitoAuthenticatedRole: 6 | Type: AWS::IAM::Role 7 | Properties: 8 | RoleName: !Sub 'CognitoAuthenticatedRole${EnvironmentNameCamelCase}${VendorSuffixCamelCase}' 9 | AssumeRolePolicyDocument: 10 | Version: '2012-10-17' 11 | Statement: 12 | - Effect: Allow 13 | Principal: 14 | Federated: "cognito-identity.amazonaws.com" 15 | Action: "sts:AssumeRoleWithWebIdentity" 16 | Condition: 17 | StringEquals: 18 | "cognito-identity.amazonaws.com:aud": !Ref IdentityPoolId 19 | ForAnyValue:StringLike: 20 | "cognito-identity.amazonaws.com:amr": "authenticated" 21 | 22 | Policies: 23 | - PolicyName: OpenSearchAccess 24 | PolicyDocument: 25 | Version: "2012-10-17" 26 | Statement: 27 | - Effect: Allow 28 | Action: 29 | - "es:ESHttpGet" 30 | - "es:ESHttpPost" 31 | - "es:ESHttpPut" 32 | - "es:ESHttpDelete" 33 | Resource: !Sub 'arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}/*' 34 | 35 | Parameters: 36 | IdentityPoolId: 37 | Type: String 38 | Description: The ID of the Cognito Identity Pool (e.g., us-east-1:xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx) 39 | OpenSearchDomainName: 40 | Type: String 41 | Description: Name of the OpenSearch domain (e.g., rtc-classifieds-dev-rtc) 42 | EnvironmentNameCamelCase: 43 | Type: String 44 | Description: The name of the environment in camel case (e.g., Dev, Prod) 45 | VendorSuffixCamelCase: 46 | Type: String 47 | Description: Unique vendor suffix in camel case for multiple vendors under the same account 48 | 49 | Outputs: 50 | CognitoRoleArn: 51 | Description: ARN of the IAM Role created for Cognito-authenticated users 52 | Value: !GetAtt CognitoAuthenticatedRole.Arn -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/user/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "user", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "node-fetch": "^2.7.0" 13 | } 14 | }, 15 | "node_modules/node-fetch": { 16 | "version": "2.7.0", 17 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 18 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 19 | "dependencies": { 20 | "whatwg-url": "^5.0.0" 21 | }, 22 | "engines": { 23 | "node": "4.x || >=6.0.0" 24 | }, 25 | "peerDependencies": { 26 | "encoding": "^0.1.0" 27 | }, 28 | "peerDependenciesMeta": { 29 | "encoding": { 30 | "optional": true 31 | } 32 | } 33 | }, 34 | "node_modules/tr46": { 35 | "version": "0.0.3", 36 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 37 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 38 | }, 39 | "node_modules/webidl-conversions": { 40 | "version": "3.0.1", 41 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 42 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 43 | }, 44 | "node_modules/whatwg-url": { 45 | "version": "5.0.0", 46 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 47 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 48 | "dependencies": { 49 | "tr46": "~0.0.3", 50 | "webidl-conversions": "^3.0.0" 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/gov/gov.go: -------------------------------------------------------------------------------- 1 | package gov 2 | 3 | import ( 4 | "goclassifieds/lib/utils" 5 | "text/template" 6 | 7 | "github.com/gocql/gocql" 8 | ) 9 | 10 | const ( 11 | User ResourceUserTypes = iota 12 | Site 13 | ) 14 | 15 | var UserTypeMap = map[string]ResourceUserTypes{ 16 | "user": User, 17 | "site": Site, 18 | } 19 | 20 | type ResourceUserTypes int32 21 | 22 | const ( 23 | GithubRepo ResourceTypes = iota 24 | DruidSite 25 | CognitoUserPool 26 | CassandraTable 27 | ) 28 | 29 | var ResourceTypeMap = map[string]ResourceTypes{ 30 | "githubrepo": GithubRepo, 31 | "druidsite": DruidSite, 32 | "cognitouserpool": CognitoUserPool, 33 | "cassandratable": CassandraTable, 34 | } 35 | 36 | type ResourceTypes int32 37 | 38 | const ( 39 | Read ResourceOperations = iota 40 | Write 41 | Delete 42 | ) 43 | 44 | var OperationMap = map[string]ResourceOperations{ 45 | "read": Read, 46 | "write": Write, 47 | "delete": Delete, 48 | } 49 | 50 | type ResourceOperations int32 51 | 52 | type GrantAccessRequest struct { 53 | User string 54 | Type ResourceUserTypes 55 | Resource ResourceTypes 56 | Asset string 57 | Operation ResourceOperations 58 | AdditionalResources []Resource 59 | LogUsageLambdaInput *utils.LogUsageLambdaInput 60 | } 61 | 62 | type Resource struct { 63 | User string 64 | Type ResourceUserTypes 65 | Resource ResourceTypes 66 | Asset string 67 | Operation ResourceOperations 68 | } 69 | 70 | type GrantAccessResponse struct { 71 | Grant bool `json:"grant"` 72 | } 73 | 74 | type ResourceManagerParams struct { 75 | Session *gocql.Session 76 | Request *GrantAccessRequest 77 | Template *template.Template 78 | // Resource string 79 | // Operation string 80 | } 81 | 82 | func Query() string { 83 | return ` 84 | {{ define "grant_access" }} 85 | SELECT 86 | op 87 | FROM 88 | resources2 89 | WHERE 90 | user = {{ bindValue (index .Metadata "user" ) }} 91 | AND 92 | type = {{ bindValue (index .Metadata "type" ) }} 93 | AND 94 | resource = {{ bindValue (index .Metadata "resource" ) }} 95 | AND 96 | asset = {{ bindValue (index .Metadata "asset" ) }} 97 | AND 98 | op = {{ bindValue (index .Metadata "op" ) }} 99 | {{end}} 100 | ` 101 | } 102 | -------------------------------------------------------------------------------- /lib/vocab/vocab.go: -------------------------------------------------------------------------------- 1 | package vocab 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "log" 7 | ) 8 | 9 | type Vocabulary struct { 10 | Id string `form:"id" json:"id"` 11 | UserId string `form:"userId" json:"userId"` 12 | MachineName string `form:"machineName" json:"machineName" binding:"required" validate:"required"` 13 | HumanName string `form:"humanName" json:"humanName" binding:"required" validate:"required"` 14 | Terms []Term `form:"terms[]" json:"terms" binding:"required" validate:"required"` 15 | } 16 | 17 | type Term struct { 18 | Id string `form:"id" json:"id" binding:"required" validate:"required"` 19 | VocabularyId string `form:"vocabularyId" json:"vocabularyId" binding:"required" validate:"required"` 20 | ParentId string `form:"parentId" json:"parentId"` 21 | MachineName string `form:"machineName" json:"machineName" binding:"required" validate:"required"` 22 | HumanName string `form:"humanName" json:"humanName" binding:"required" validate:"required"` 23 | Weight int `form:"weight" json:"weight" binding:"required" validate:"required"` 24 | Group bool `form:"group" json:"group" binding:"required" validate:"required"` 25 | Selected bool `form:"selected" json:"selected" binding:"required" validate:"required"` 26 | Level int `form:"level" json:"level" binding:"required" validate:"required"` 27 | Children []Term `form:"children[]" json:"children"` 28 | } 29 | 30 | func ToEntity(vocab *Vocabulary) (map[string]interface{}, error) { 31 | var buf bytes.Buffer 32 | if err := json.NewEncoder(&buf).Encode(vocab); err != nil { 33 | log.Fatalf("Error encoding query: %s", err) 34 | } 35 | jsonData, err := json.Marshal(vocab) 36 | if err != nil { 37 | return nil, err 38 | } 39 | var entity map[string]interface{} 40 | err = json.Unmarshal(jsonData, &entity) 41 | return entity, nil 42 | } 43 | 44 | func FlattenTerm(term Term, selectedOnly bool) []Term { 45 | leafNodes := make([]Term, 0) 46 | if term.Children == nil || len(term.Children) == 0 { 47 | if !selectedOnly || term.Selected { 48 | leafNodes = append(leafNodes, term) 49 | } 50 | } else { 51 | for _, childTerm := range term.Children { 52 | flatChildren := FlattenTerm(childTerm, selectedOnly) 53 | for _, flatChild := range flatChildren { 54 | leafNodes = append(leafNodes, flatChild) 55 | } 56 | } 57 | } 58 | return leafNodes 59 | } 60 | -------------------------------------------------------------------------------- /api/chat/queries.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "chatconversations" }} 2 | SELECT 3 | recipientid AS recipientId, 4 | recipientlabel AS recipientLabel, 5 | userid AS userId 6 | FROM 7 | chatconversations 8 | WHERE 9 | userid = {{ bindValue .Req.RequestContext.Authorizer.claims.sub }} 10 | {{end}} 11 | 12 | {{ define "chatmessages" }} 13 | SELECT 14 | senderid, 15 | recipientid, 16 | createdat, 17 | message 18 | FROM 19 | chatmessages 20 | WHERE 21 | senderid = {{ bindValue .Req.RequestContext.Authorizer.claims.sub }} 22 | AND 23 | recipientid = {{ bindValue (index .Req.QueryStringParameters "recipientId") }} 24 | {{end}} 25 | 26 | {{ define "_chatmessages_inverse" }} 27 | SELECT 28 | senderid, 29 | recipientid, 30 | createdat, 31 | message 32 | FROM 33 | chatmessages 34 | WHERE 35 | recipientid = {{ bindValue .Req.RequestContext.Authorizer.claims.sub }} 36 | AND 37 | senderid = {{ bindValue (index .Req.QueryStringParameters "recipientId") }} 38 | {{end}} 39 | 40 | {{ define "_chatconnections" }} 41 | SELECT 42 | connid, 43 | userid, 44 | createdat 45 | FROM 46 | chatconnections 47 | WHERE 48 | userid = {{ bindValue .Req.RequestContext.Authorizer.claims.sub }} 49 | {{ end }} 50 | 51 | {{ define "_chatconnections_inverse" }} 52 | SELECT 53 | connid, 54 | userid, 55 | createdat 56 | FROM 57 | chatconnections 58 | WHERE 59 | userid = {{ bindValue (index .Metadata "recipientId" ) }} 60 | {{ end }} 61 | 62 | {{define "leads" }} 63 | SELECT 64 | adid, 65 | profileid, 66 | senderid, 67 | phone, 68 | email, 69 | createdat 70 | FROM 71 | leads 72 | WHERE 73 | profileid = {{ bindValue (index .Req.QueryStringParameters "profileId") }} 74 | {{ end }} 75 | 76 | {{ define "pages" }} 77 | SELECT 78 | site, 79 | path, 80 | title, 81 | body, 82 | published, 83 | createdAt 84 | FROM 85 | pages 86 | WHERE 87 | site = {{ bindValue (index .Req.QueryStringParameters "site") }} 88 | {{ end }} 89 | 90 | {{ define "gridlayouts"}} 91 | SELECT 92 | id, 93 | site, 94 | weight, 95 | cols, 96 | rows, 97 | x, 98 | y 99 | FROM 100 | gridlayouts 101 | WHERE 102 | site = {{ bindValue (index .Req.QueryStringParameters "site") }} 103 | {{ end }} -------------------------------------------------------------------------------- /lib/chat/chat.go: -------------------------------------------------------------------------------- 1 | package chat 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "log" 7 | "time" 8 | ) 9 | 10 | type ChatConnection struct { 11 | ConnId string `form:"connId" json:"connId" binding:"required" validate:"required"` 12 | CreatedAt time.Time `form:"createdAt" json:"createdAt" binding:"required" validate:"required"` 13 | UserId string `form:"userId" json:"userId" binding:"required" validate:"required"` 14 | } 15 | 16 | type ChatConversation struct { 17 | UserId string `form:"userId" json:"userId" binding:"required" validate:"required"` 18 | RecipientId string `form:"recipientId" json:"recipientId" binding:"required" validate:"required"` 19 | RecipientLabel string `form:"recipientLabel" json:"recipientLabel" binding:"required" validate:"required"` 20 | } 21 | 22 | type ChatMessage struct { 23 | SenderId string `form:"senderId" json:"senderId" binding:"required" validate:"required"` 24 | RecipientId string `form:"recipientId" json:"recipientId" binding:"required" validate:"required"` 25 | Message string `form:"message" json:"message" binding:"required" validate:"required"` 26 | CreatedAt time.Time `form:"createdAt" json:"createdAt" binding:"required" validate:"required"` 27 | } 28 | 29 | func ToConnectionEntity(conn *ChatConnection) (map[string]interface{}, error) { 30 | var buf bytes.Buffer 31 | if err := json.NewEncoder(&buf).Encode(conn); err != nil { 32 | log.Fatalf("Error encoding query: %s", err) 33 | } 34 | jsonData, err := json.Marshal(conn) 35 | if err != nil { 36 | return nil, err 37 | } 38 | var entity map[string]interface{} 39 | err = json.Unmarshal(jsonData, &entity) 40 | return entity, nil 41 | } 42 | 43 | func ToConversationEntity(conv *ChatConversation) (map[string]interface{}, error) { 44 | var buf bytes.Buffer 45 | if err := json.NewEncoder(&buf).Encode(conv); err != nil { 46 | log.Fatalf("Error encoding query: %s", err) 47 | } 48 | jsonData, err := json.Marshal(conv) 49 | if err != nil { 50 | return nil, err 51 | } 52 | var entity map[string]interface{} 53 | err = json.Unmarshal(jsonData, &entity) 54 | return entity, nil 55 | } 56 | 57 | func ToMessageEntity(message *ChatMessage) (map[string]interface{}, error) { 58 | var buf bytes.Buffer 59 | if err := json.NewEncoder(&buf).Encode(message); err != nil { 60 | log.Fatalf("Error encoding query: %s", err) 61 | } 62 | jsonData, err := json.Marshal(message) 63 | if err != nil { 64 | return nil, err 65 | } 66 | var entity map[string]interface{} 67 | err = json.Unmarshal(jsonData, &entity) 68 | return entity, nil 69 | } 70 | -------------------------------------------------------------------------------- /cloudform/search.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Shell script to deploy an AWS CloudFormation template for OpenSearch. 4 | 5 | # Set variables 6 | STACK_NAME="BaseOpenSearchStack" 7 | TEMPLATE_FILE="search.yaml" # Ensure the YAML template file is in the same directory as this script 8 | REGION="us-east-1" # Change this to your target AWS region 9 | MASTER_PASSWORD="" # Replace with your desired master password 10 | ENVIRONMENT_NAME="dev" # Replace with your environment name (e.g., dev, prod) 11 | ENVIRONMENT_NAME_CAMEL_CASE="Dev" # Replace with the CamelCase environment name (e.g., Dev, Prod) 12 | VENDOR_SUFFIX="rtc" # Replace with your vendor suffix 13 | VENDOR_SUFFIX_CAMEL_CASE="Rtc" # Replace with the CamelCase vendor suffix 14 | COGNITO_USER_POOL_ID="us-east-1_sWRV0kAgS" 15 | COGNITO_IDENTITY_POOL_ID="us-east-1:026caf18-c852-451b-a93e-fb431c4eee6d" 16 | 17 | # Create the CloudFormation Stack 18 | echo "Deploying the CloudFormation stack: ${STACK_NAME} in region: ${REGION}..." 19 | aws cloudformation create-stack \ 20 | --stack-name ${STACK_NAME} \ 21 | --template-body file://${TEMPLATE_FILE} \ 22 | --parameters ParameterKey=MasterPassword,ParameterValue=${MASTER_PASSWORD} \ 23 | ParameterKey=EnvironmentName,ParameterValue=${ENVIRONMENT_NAME} \ 24 | ParameterKey=EnvironmentNameCamelCase,ParameterValue=${ENVIRONMENT_NAME_CAMEL_CASE} \ 25 | ParameterKey=VendorSuffix,ParameterValue=${VENDOR_SUFFIX} \ 26 | ParameterKey=VendorSuffixCamelCase,ParameterValue=${VENDOR_SUFFIX_CAMEL_CASE} \ 27 | ParameterKey=UserPoolId,ParameterValue=${USER_POOL_ID} \ 28 | ParameterKey=IdentityPoolId,ParameterValue=${IDENTITY_POOL_ID} \ 29 | --capabilities CAPABILITY_NAMED_IAM \ 30 | --region ${REGION} 31 | 32 | # Wait for the deployment to complete 33 | if [ $? -eq 0 ]; then 34 | echo "Stack creation initiated successfully. Waiting for completion..." 35 | aws cloudformation wait stack-create-complete --stack-name ${STACK_NAME} --region ${REGION} 36 | 37 | if [ $? -eq 0 ]; then 38 | echo "Stack created successfully!" 39 | 40 | # Fetch and display the OpenSearch endpoint 41 | OPENSEARCH_ENDPOINT=$(aws cloudformation describe-stacks \ 42 | --stack-name ${STACK_NAME} \ 43 | --query "Stacks[0].Outputs[?OutputKey=='OpenSearchEndpoint'].OutputValue" \ 44 | --output text \ 45 | --region ${REGION}) 46 | 47 | echo "OpenSearch Endpoint: ${OPENSEARCH_ENDPOINT}" 48 | else 49 | echo "Error: Stack creation failed!" 50 | fi 51 | else 52 | echo "Error: Failed to initiate stack creation!" 53 | fi -------------------------------------------------------------------------------- /HelloWorld.yml: -------------------------------------------------------------------------------- 1 | service: goclassifieds-hellworld 2 | provider: 3 | name: aws 4 | runtime: go1.x 5 | memorySize: 256 6 | timeout: 10 7 | httpApi: 8 | payload: '1.0' 9 | cors: true 10 | package: 11 | individually: false 12 | include: 13 | - bazel-out/darwin_arm64-fastbuild-ST-5fa8105c1248/bin/func/hello_world/hello_world_/hello_world 14 | exclude: 15 | - ./** 16 | - bazel-out/darwin_arm64-fastbuild-ST-5fa8105c1248/bin/func/hello_world/hello_world_/** 17 | resources: 18 | Resources: 19 | HelloWorldRole: 20 | Type: AWS::IAM::Role 21 | Properties: 22 | Path: "/" 23 | RoleName: "goclassifieds-helloworld-us-east-1-lambdaRole" 24 | AssumeRolePolicyDocument: 25 | Version: '2012-10-17' 26 | Statement: 27 | - Effect: Allow 28 | Principal: 29 | Service: 30 | - lambda.amazonaws.com 31 | Action: sts:AssumeRole 32 | ManagedPolicyArns: 33 | - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole 34 | Policies: 35 | - PolicyName: goclassifieds-helloworld-lambda 36 | PolicyDocument: 37 | Version: '2012-10-17' 38 | Statement: 39 | - Effect: Allow 40 | Action: 41 | - logs:CreateLogGroup 42 | - logs:CreateLogStream 43 | - logs:PutLogEvents 44 | - logs:DescribeLogGroups 45 | - logs:DescribeLogStreams 46 | - logs:GetLogEvents 47 | - logs:FilterLogEvents 48 | Resource: 49 | - 'Fn::Join': 50 | - ':' 51 | - 52 | - 'arn:aws:logs' 53 | - Ref: 'AWS::Region' 54 | - Ref: 'AWS::AccountId' 55 | - 'log-group:/aws/lambda/*:*:*' 56 | - Effect: "Allow" 57 | Action: 58 | - "lambda:InvokeFunction" 59 | Resource: "*" 60 | - Effect: "Allow" 61 | Action: 62 | - "execute-api:Invoke" 63 | - "execute-api:ManageConnections" 64 | Resource: "*" 65 | functions: 66 | HelloWorld: 67 | handler: bazel-out/darwin_arm64-fastbuild-ST-5fa8105c1248/bin/func/hello_world/hello_world_/hello_world 68 | role: HelloWorldRole 69 | events: 70 | - httpApi: 71 | path: /helloworld 72 | method: GET -------------------------------------------------------------------------------- /cloudform/users.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: 'AWS CloudFormation Sample Template: This template demonstrates the creation of Cognito User Pools' 3 | 4 | Parameters: 5 | EnvironmentName: 6 | Description: The name of the environment (e.g., dev, prod) 7 | Type: String 8 | EnvironmentNameCamelCase: 9 | Description: The name of the environment (e.g., dev, prod) in CamelCase for supporting multiple environments in same account without conflicts. 10 | Type: String 11 | VendorSuffix: 12 | Description: Unique vendor suffix for the bucket. 13 | Type: String 14 | VendorSuffixCamelCase: 15 | Description: Unique vendor suffix in CamelCase for supporting multiple vendors under the same account. 16 | Type: String 17 | SSODomain: 18 | Description: The domain for hosting sso page. 19 | Type: String 20 | 21 | Resources: 22 | UserPool: 23 | Type: "AWS::Cognito::UserPool" 24 | Properties: 25 | UserPoolName: !Sub 'RtcClassifiedsUserPool${EnvironmentNameCamelCase}${VendorSuffixCamelCase}' 26 | Policies: 27 | PasswordPolicy: 28 | MinimumLength: 8 29 | RequireLowercase: True 30 | RequireNumbers: True 31 | RequireSymbols: True 32 | RequireUppercase: True 33 | 34 | UserPoolClient: 35 | Type: "AWS::Cognito::UserPoolClient" 36 | Properties: 37 | ClientName: !Sub 'RtcClassifiedsUserPoolClient${EnvironmentNameCamelCase}${VendorSuffixCamelCase}' 38 | GenerateSecret: False 39 | UserPoolId: 40 | Ref: UserPool 41 | SupportedIdentityProviders: ["COGNITO"] 42 | CallbackURLs: [!Sub 'https://${SSODomain}/auth-callback'] 43 | LogoutURLs: [!Sub 'https://${SSODomain}/logout'] 44 | AllowedOAuthFlows: ["code"] 45 | AllowedOAuthScopes: ["email", "openid"] 46 | AllowedOAuthFlowsUserPoolClient: true 47 | 48 | UserPoolDomain: 49 | Type: "AWS::Cognito::UserPoolDomain" 50 | Properties: 51 | UserPoolId: 52 | Ref: UserPool 53 | Domain: !Ref SSODomain 54 | 55 | UserIdentityPool: 56 | Type: "AWS::Cognito::IdentityPool" 57 | Properties: 58 | IdentityPoolName: !Sub 'ClassifiedsIdentityPool${EnvironmentNameCamelCase}${VendorSuffixCamelCase}' 59 | AllowUnauthenticatedIdentities: false 60 | CognitoIdentityProviders: 61 | - ClientId: 62 | Ref: UserPoolClient 63 | ProviderName: 64 | Fn::GetAtt: 65 | - UserPool 66 | - ProviderName 67 | 68 | Outputs: 69 | UserPoolId: 70 | Description: The ID of the user pool 71 | Value: !Ref UserPool 72 | IdentityPoolId: 73 | Description: The ID of the identity pool 74 | Value: !Ref UserIdentityPool -------------------------------------------------------------------------------- /lib/utils/adub.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" // For working with JSON (e.g., unmarshaling request bodies) 5 | "fmt" // For string formatting and creating errors 6 | "github.com/aws/aws-lambda-go/events" // For APIGatewayProxyRequest & APIGatewayProxyResponse 7 | ) 8 | 9 | func CopyRequest(req *events.APIGatewayProxyRequest, modifications func(*events.APIGatewayProxyRequest)) *events.APIGatewayProxyRequest { 10 | // Create a deep copy of the original request 11 | newReq := &events.APIGatewayProxyRequest{ 12 | Resource: req.Resource, 13 | Path: req.Path, 14 | HTTPMethod: req.HTTPMethod, 15 | Headers: copyMap(req.Headers), 16 | QueryStringParameters: copyMap(req.QueryStringParameters), 17 | PathParameters: copyMap(req.PathParameters), 18 | StageVariables: copyMap(req.StageVariables), 19 | RequestContext: req.RequestContext, 20 | Body: req.Body, 21 | IsBase64Encoded: req.IsBase64Encoded, 22 | MultiValueHeaders: copyMultiMap(req.MultiValueHeaders), 23 | MultiValueQueryStringParameters: copyMultiMap(req.MultiValueQueryStringParameters), 24 | } 25 | 26 | // Apply modifications 27 | if modifications != nil { 28 | modifications(newReq) 29 | } 30 | 31 | return newReq 32 | } 33 | 34 | // Helper functions to copy maps to maintain immutability 35 | func copyMap(original map[string]string) map[string]string { 36 | if original == nil { 37 | return nil 38 | } 39 | copied := make(map[string]string) 40 | for k, v := range original { 41 | copied[k] = v 42 | } 43 | return copied 44 | } 45 | 46 | func copyMultiMap(original map[string][]string) map[string][]string { 47 | if original == nil { 48 | return nil 49 | } 50 | copied := make(map[string][]string) 51 | for k, v := range original { 52 | copied[k] = append([]string{}, v...) 53 | } 54 | return copied 55 | } 56 | 57 | func PluckPropertyFromJSON(body string, propertyName string) (string, error) { 58 | if body == "" { 59 | return "", fmt.Errorf("request body is empty") 60 | } 61 | 62 | // Parse the JSON body into a map 63 | var data map[string]interface{} 64 | err := json.Unmarshal([]byte(body), &data) 65 | if err != nil { 66 | return "", fmt.Errorf("failed to parse JSON: %v", err) 67 | } 68 | 69 | // Extract the desired property 70 | value, ok := data[propertyName] 71 | if !ok { 72 | return "", fmt.Errorf("property %s not found in JSON body", propertyName) 73 | } 74 | 75 | // Ensure the value is a string 76 | strValue, ok := value.(string) 77 | if !ok { 78 | return "", fmt.Errorf("property %s is not a string", propertyName) 79 | } 80 | 81 | return strValue, nil 82 | } -------------------------------------------------------------------------------- /serverless-watttime-demo.yml: -------------------------------------------------------------------------------- 1 | service: watttime-demo 2 | frameworkVersion: '3' 3 | plugins: 4 | - serverless-prune-plugin 5 | custom: 6 | wattTimeUsername: ${file(./private.${opt:stage, 'dev'}.json):wattTimeUsername} 7 | wattTimePassword: ${file(./private.${opt:stage, 'dev'}.json):wattTimePassword} 8 | provider: 9 | name: aws 10 | runtime: go1.x 11 | memorySize: 256 12 | timeout: 10 13 | package: 14 | individually: false 15 | patterns: 16 | - '!./**' 17 | - bazel-out/darwin_arm64-fastbuild-ST-5fa8105c1248/bin/func/watttime_demo/watttime_demo_/watttime_demo 18 | resources: 19 | Resources: 20 | WattTimeDemoRole: 21 | Type: AWS::IAM::Role 22 | Properties: 23 | Path: "/" 24 | RoleName: watttime-demo-${opt:region, 'us-east-1'}-lambdaRole 25 | AssumeRolePolicyDocument: 26 | Version: '2012-10-17' 27 | Statement: 28 | - Effect: Allow 29 | Principal: 30 | Service: 31 | - lambda.amazonaws.com 32 | - apigateway.amazonaws.com 33 | Action: sts:AssumeRole 34 | ManagedPolicyArns: 35 | - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole 36 | Policies: 37 | - PolicyName: watttime-demo-lambda 38 | PolicyDocument: 39 | Version: '2012-10-17' 40 | Statement: 41 | - Effect: Allow 42 | Action: 43 | - logs:CreateLogGroup 44 | - logs:CreateLogStream 45 | - logs:PutLogEvents 46 | - logs:DescribeLogGroups 47 | - logs:DescribeLogStreams 48 | - logs:GetLogEvents 49 | - logs:FilterLogEvents 50 | Resource: 51 | - 'Fn::Join': 52 | - ':' 53 | - 54 | - 'arn:aws:logs' 55 | - Ref: 'AWS::Region' 56 | - Ref: 'AWS::AccountId' 57 | - 'log-group:/aws/lambda/*:*:*' 58 | - Effect: "Allow" 59 | Action: 60 | - "lambda:InvokeFunction" 61 | Resource: "*" 62 | - Effect: "Allow" 63 | Action: 64 | - "execute-api:Invoke" 65 | - "execute-api:ManageConnections" 66 | Resource: "*" 67 | functions: 68 | WattTimeDemo: 69 | handler: bazel-out/darwin_arm64-fastbuild-ST-5fa8105c1248/bin/func/watttime_demo/watttime_demo_/watttime_demo 70 | role: WattTimeDemoRole 71 | environment: 72 | WATTTIME_USERNAME: ${self:custom.wattTimeUsername} 73 | WATTTIME_PASSWORD: ${self:custom.wattTimePassword} 74 | STAGE: ${opt:stage, 'dev'} -------------------------------------------------------------------------------- /func/convert_media/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "log" 9 | "os" 10 | 11 | "github.com/aws/aws-lambda-go/events" 12 | "github.com/aws/aws-lambda-go/lambda" 13 | "github.com/aws/aws-sdk-go/aws" 14 | "github.com/aws/aws-sdk-go/aws/session" 15 | lambda2 "github.com/aws/aws-sdk-go/service/lambda" 16 | "github.com/aws/aws-sdk-go/service/s3" 17 | "github.com/aws/aws-sdk-go/service/s3/s3manager" 18 | ) 19 | 20 | var stage string 21 | 22 | type CssToJsonRequest struct { 23 | Content string 24 | } 25 | 26 | func handler(ctx context.Context, s3Event events.S3Event) { 27 | 28 | sess := session.Must(session.NewSession()) 29 | lClient := lambda2.New(sess) 30 | 31 | for _, record := range s3Event.Records { 32 | 33 | log.Printf("%+v", record) 34 | 35 | content, _ := download(sess, &record) 36 | payload, _ := request(content) 37 | 38 | log.Printf("%s", payload) 39 | 40 | res, err := lClient.Invoke(&lambda2.InvokeInput{FunctionName: aws.String("goclassifieds-api-" + stage + "-CssToJson"), Payload: payload}) 41 | if err != nil { 42 | log.Printf("error invoking entity validation: %s", err.Error()) 43 | log.Printf("response: %s", res) 44 | } 45 | 46 | log.Printf("json: %s", res.Payload) 47 | write(sess, res.Payload, &record) 48 | 49 | } 50 | } 51 | 52 | func request(content []byte) ([]byte, error) { 53 | 54 | request := CssToJsonRequest{ 55 | Content: string(content), 56 | } 57 | 58 | payload, err := json.Marshal(request) 59 | if err != nil { 60 | log.Printf("Error marshalling css to json request: %s", err.Error()) 61 | return nil, errors.New("Error marshalling css to json request") 62 | } 63 | 64 | return payload, nil 65 | } 66 | 67 | func download(sess *session.Session, record *events.S3EventRecord) ([]byte, error) { 68 | 69 | buf := aws.NewWriteAtBuffer([]byte{}) 70 | 71 | downloader := s3manager.NewDownloader(sess) 72 | 73 | _, err := downloader.Download(buf, &s3.GetObjectInput{ 74 | Bucket: aws.String(record.S3.Bucket.Name), 75 | Key: aws.String(record.S3.Object.Key), 76 | }) 77 | 78 | if err != nil { 79 | return []byte(""), err 80 | } 81 | 82 | return buf.Bytes(), nil 83 | 84 | } 85 | 86 | func write(sess *session.Session, content []byte, record *events.S3EventRecord) error { 87 | 88 | uploader := s3manager.NewUploader(sess) 89 | 90 | _, err := uploader.Upload(&s3manager.UploadInput{ 91 | Bucket: aws.String(record.S3.Bucket.Name), 92 | Key: aws.String(record.S3.Object.Key + ".json"), 93 | Body: bytes.NewReader(content), 94 | ContentType: aws.String("application/json"), 95 | }) 96 | if err != nil { 97 | return err 98 | } 99 | return nil 100 | } 101 | 102 | func main() { 103 | stage = os.Getenv("STAGE") 104 | lambda.Start(handler) 105 | } 106 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module goclassifieds 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/aws/aws-lambda-go v1.26.0 7 | github.com/aws/aws-sdk-go v1.42.27 8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 9 | github.com/elastic/go-elasticsearch v0.0.0 10 | github.com/elastic/go-elasticsearch/v7 v7.14.0 11 | github.com/gin-gonic/gin v1.7.4 12 | github.com/go-playground/validator/v10 v10.9.0 13 | github.com/gocql/gocql v0.0.0-20210817081954-bc256bbb90de 14 | github.com/golang/protobuf v1.5.2 // indirect 15 | github.com/google/uuid v1.3.0 16 | github.com/lestrrat-go/jwx v1.2.6 17 | github.com/mitchellh/mapstructure v1.4.1 18 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 19 | github.com/modern-go/reflect2 v1.0.1 // indirect 20 | github.com/tangzero/inflector v1.0.0 21 | golang.org/x/sys v0.34.0 // indirect 22 | gopkg.in/yaml.v2 v2.4.0 // indirect 23 | ) 24 | 25 | require ( 26 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect 27 | github.com/gin-contrib/sse v0.1.0 // indirect 28 | github.com/go-playground/locales v0.14.0 // indirect 29 | github.com/go-playground/universal-translator v0.18.0 // indirect 30 | github.com/goccy/go-json v0.7.6 // indirect 31 | github.com/golang/snappy v0.0.3 // indirect 32 | github.com/google/go-cmp v0.7.0 // indirect 33 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect 34 | github.com/jmespath/go-jmespath v0.4.0 // indirect 35 | github.com/json-iterator/go v1.1.10 // indirect 36 | github.com/leodido/go-urn v1.2.1 // indirect 37 | github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect 38 | github.com/lestrrat-go/blackmagic v1.0.0 // indirect 39 | github.com/lestrrat-go/httpcc v1.0.0 // indirect 40 | github.com/lestrrat-go/iter v1.0.1 // indirect 41 | github.com/lestrrat-go/option v1.0.0 // indirect 42 | github.com/mattn/go-isatty v0.0.12 // indirect 43 | github.com/opensearch-project/opensearch-go v1.1.0 // indirect 44 | github.com/pkg/errors v0.9.1 // indirect 45 | github.com/shurcooL/githubv4 v0.0.0-20220520033151-0b4e3294ff00 46 | github.com/ugorji/go/codec v1.1.7 // indirect 47 | golang.org/x/crypto v0.40.0 // indirect 48 | //golang.org/x/crypto/curve25519 v0.0.0-20210817164053-32db794688a5 // indirect 49 | golang.org/x/text v0.27.0 // indirect 50 | google.golang.org/protobuf v1.26.0 // indirect 51 | gopkg.in/inf.v0 v0.9.1 // indirect 52 | ) 53 | 54 | require ( 55 | github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 // indirect 56 | golang.org/x/net v0.41.0 // indirect 57 | golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb 58 | ) 59 | 60 | require ( 61 | github.com/GoKillers/libsodium-go v0.0.0-20171022220152-dd733721c3cb // indirect 62 | github.com/MicahParks/keyfunc v1.5.1 // indirect 63 | github.com/golang-jwt/jwt/v4 v4.4.2 // indirect 64 | github.com/google/go-github/v46 v46.0.0 // indirect 65 | github.com/kevinburke/nacl v0.0.0-20250518034207-4fa338b68f84 // indirect 66 | github.com/sethvargo/go-password v0.2.0 // indirect 67 | google.golang.org/appengine v1.6.7 // indirect 68 | ) 69 | -------------------------------------------------------------------------------- /api/authorizer2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "goclassifieds/lib/utils" 5 | "log" 6 | "os" 7 | 8 | "github.com/MicahParks/keyfunc" 9 | "github.com/aws/aws-lambda-go/events" 10 | "github.com/aws/aws-lambda-go/lambda" 11 | "github.com/golang-jwt/jwt/v4" 12 | ) 13 | 14 | var handler Handler 15 | 16 | type Handler func(req *events.APIGatewayProxyRequest) (events.APIGatewayCustomAuthorizerResponse, error) 17 | 18 | type ActionContext struct { 19 | UserPoolId string 20 | } 21 | 22 | // Authorizer custom api authorizer 23 | func Authorizer(request *events.APIGatewayProxyRequest, ac *ActionContext) (events.APIGatewayCustomAuthorizerResponse, error) { 24 | token1 := request.Headers["authorization"] 25 | 26 | utils.LogUsageForHttpRequest(request) 27 | 28 | log.Printf("%v", request) 29 | log.Printf("token is %s", token1) 30 | 31 | if token1 == "" { 32 | log.Print("unauthorized request pass thru") 33 | return events.APIGatewayCustomAuthorizerResponse{ 34 | PrincipalID: "me", 35 | PolicyDocument: events.APIGatewayCustomAuthorizerPolicy{ 36 | Version: "2012-10-17", 37 | Statement: []events.IAMPolicyStatement{ 38 | { 39 | Action: []string{"execute-api:*"}, 40 | Effect: "Allow", 41 | Resource: []string{"*"}, 42 | // Resource: []string{request.Re}, 43 | }, 44 | }, 45 | }, 46 | }, nil 47 | } 48 | 49 | token := token1[7:] 50 | log.Printf("token after is %s", token) 51 | 52 | jwks, err := keyfunc.Get("https://cognito-idp.us-east-1.amazonaws.com/"+ac.UserPoolId+"/.well-known/jwks.json", keyfunc.Options{}) 53 | if err != nil { 54 | log.Fatalln("Unable to fetch keys") 55 | } 56 | 57 | log.Print("fectehd keys") 58 | 59 | // Verify 60 | t, err := jwt.Parse(token, jwks.Keyfunc) 61 | if err != nil || !t.Valid { 62 | log.Print(err) 63 | log.Fatalln("Unauthorized") 64 | } 65 | 66 | log.Print("authorized") 67 | 68 | claims := t.Claims.(jwt.MapClaims) 69 | 70 | log.Print("got claims") 71 | 72 | claims["cognito:groups"] = nil //claims["cognito:groups"] 73 | claims["cognito:roles"] = nil //claims["cognito:groups"] 74 | 75 | return events.APIGatewayCustomAuthorizerResponse{ 76 | PrincipalID: "me", 77 | PolicyDocument: events.APIGatewayCustomAuthorizerPolicy{ 78 | Version: "2012-10-17", 79 | Statement: []events.IAMPolicyStatement{ 80 | { 81 | Action: []string{"execute-api:*"}, 82 | Effect: "Allow", 83 | Resource: []string{"*"}, 84 | // Resource: []string{request.Re}, 85 | }, 86 | }, 87 | }, 88 | Context: claims, 89 | }, nil 90 | } 91 | 92 | func InitializeHandler(ac ActionContext) Handler { 93 | return func(req *events.APIGatewayProxyRequest) (events.APIGatewayCustomAuthorizerResponse, error) { 94 | return Authorizer(req, &ac) 95 | } 96 | } 97 | 98 | func init() { 99 | log.Printf("Gin cold start") 100 | actionContext := ActionContext{ 101 | UserPoolId: os.Getenv("USER_POOL_ID"), 102 | } 103 | handler = InitializeHandler(actionContext) 104 | } 105 | 106 | func main() { 107 | log.SetFlags(0) 108 | lambda.Start(handler) 109 | } 110 | -------------------------------------------------------------------------------- /serverless-versait.yml: -------------------------------------------------------------------------------- 1 | service: goclassifieds-versait-api 2 | frameworkVersion: '3' 3 | plugins: 4 | - serverless-prune-plugin 5 | - serverless-custom-packaging-plugin 6 | custom: 7 | githubToken: ${file(./private.${opt:stage, 'dev'}.json):githubToken} 8 | versaitUsername: ${file(./private.${opt:stage, 'dev'}.json):versaitUsername} 9 | webhookSecret: ${file(./private.${opt:stage, 'dev'}.json):webhookSecret} 10 | openAiApiKey: ${file(./private.${opt:stage, 'dev'}.json):openAiApiKey} 11 | provider: 12 | name: aws 13 | runtime: provided.al2023 14 | memorySize: 512 15 | timeout: 45 16 | #logs: 17 | #websocket: true 18 | httpApi: 19 | payload: '1.0' 20 | cors: true 21 | package: 22 | individually: true 23 | resources: 24 | Resources: 25 | VersaitRole: 26 | Type: AWS::IAM::Role 27 | Properties: 28 | Path: "/" 29 | RoleName: verait-${opt:region, 'us-east-1'}-lambdaRole 30 | AssumeRolePolicyDocument: 31 | Version: '2012-10-17' 32 | Statement: 33 | - Effect: Allow 34 | Principal: 35 | Service: 36 | - lambda.amazonaws.com 37 | - apigateway.amazonaws.com 38 | Action: sts:AssumeRole 39 | ManagedPolicyArns: 40 | - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole 41 | Policies: 42 | - PolicyName: goclassifieds-versait-api-lambda 43 | PolicyDocument: 44 | Version: '2012-10-17' 45 | Statement: 46 | - Effect: Allow 47 | Action: 48 | - logs:CreateLogGroup 49 | - logs:CreateLogStream 50 | - logs:PutLogEvents 51 | - logs:DescribeLogGroups 52 | - logs:DescribeLogStreams 53 | - logs:GetLogEvents 54 | - logs:FilterLogEvents 55 | Resource: 56 | - 'Fn::Join': 57 | - ':' 58 | - 59 | - 'arn:aws:logs' 60 | - Ref: 'AWS::Region' 61 | - Ref: 'AWS::AccountId' 62 | - 'log-group:/aws/lambda/*:*:*' 63 | - Effect: "Allow" 64 | Action: 65 | - "lambda:InvokeFunction" 66 | Resource: "*" 67 | - Effect: "Allow" 68 | Action: 69 | - "execute-api:Invoke" 70 | - "execute-api:ManageConnections" 71 | Resource: "*" 72 | functions: 73 | VersaitApi: 74 | handler: bootstrap 75 | role: VersaitRole 76 | package: 77 | path: bazel-bin/api/versait 78 | artifact: .serverless/VersaitApi.zip 79 | libs: api/entity 80 | include_globs: 81 | - "**/bootstrap" 82 | - "**/*.json.tmpl" 83 | - "**/*.pem" 84 | environment: 85 | GITHUB_TOKEN: ${self:custom.githubToken} 86 | VERSAIT_USERNAME: ${self:custom.versaitUsername} 87 | OPENAI_API_KEY: ${self:custom.openAiApiKey} 88 | #WEBHOOK_SECRET: ${self:custom.webhookSecret} 89 | STAGE: ${opt:stage, 'dev'} 90 | events: 91 | - httpApi: 92 | path: /versait/{proxy+} 93 | method: POST -------------------------------------------------------------------------------- /lib/ads/ads.go: -------------------------------------------------------------------------------- 1 | package ads 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "goclassifieds/lib/attr" 7 | "goclassifieds/lib/vocab" 8 | "log" 9 | "time" 10 | ) 11 | 12 | type AdStatuses int32 13 | 14 | const ( 15 | Submitted AdStatuses = iota 16 | Approved 17 | Rejected 18 | Expired 19 | Deleted 20 | ) 21 | 22 | type AdListitemsRequest struct { 23 | TypeId string `form:"typeId" binding:"required"` 24 | SearchString string `form:"searchString"` 25 | Location string `form:"location"` 26 | Features []string `form:"features[]"` 27 | Page int `form:"page"` 28 | } 29 | 30 | type Ad struct { 31 | Id string `json:"id" validate:"required"` 32 | TypeId string `form:"typeId" json:"typeId" binding:"typeId" validate:"required"` 33 | Status *AdStatuses `form:"status" json:"status" validate:"required"` 34 | Title string `form:"title" json:"title" binding:"required" validate:"required"` 35 | Description string `form:"description" json:"description" binding:"required" validate:"required"` 36 | Location [2]float64 `form:"location[]" json:"location" binding:"required" validate:"required"` 37 | UserId string `form:"userId" json:"userId" validate:"required"` 38 | ProfileId string `form:"profileId" json:"profileId"` 39 | CityDisplay string `form:"cityDisplay" json:"cityDisplay" binding:"required" validate:"required"` 40 | Images []AdImage `form:"images[]" json:"images" validate:"dive"` 41 | Attributes []attr.AttributeValue `form:"attributes[]" json:"attributes" validate:"dive"` 42 | FeatureSets []vocab.Vocabulary `form:"featureSets[]" json:"featureSets" validate:"dive"` 43 | } 44 | 45 | type AdImage struct { 46 | Id string `form:"id" json:"id" binding:"required" validate:"required"` 47 | Path string `form:"path" json:"path" binding:"required" validate:"required"` 48 | Weight int `form:"weight" json:"weight" binding:"required" validate:"required"` 49 | } 50 | 51 | type AdLead struct { 52 | ProfileId string `form:"profileId" json:"profileId" binding:"required" validate:"required"` 53 | AdId string `form:"adId" json:"adId" binding:"required" validate:"required"` 54 | SenderId string `form:"senderId" json:"senderId"` 55 | Email string `form:"email" json:"email" binding:"required" validate:"required,email"` 56 | Phone string `form:"phone" json:"phone" binding:"required" validate:"required"` 57 | Message string `form:"message" json:"message" binding:"required" validate:"required"` 58 | CreatedAt time.Time `form:"createdAt" json:"createdAt" binding:"required" validate:"required"` 59 | } 60 | 61 | func ToEntity(ad *Ad) (map[string]interface{}, error) { 62 | var buf bytes.Buffer 63 | if err := json.NewEncoder(&buf).Encode(ad); err != nil { 64 | log.Fatalf("Error encoding query: %s", err) 65 | } 66 | jsonData, err := json.Marshal(ad) 67 | if err != nil { 68 | return nil, err 69 | } 70 | var entity map[string]interface{} 71 | err = json.Unmarshal(jsonData, &entity) 72 | return entity, nil 73 | } 74 | 75 | func ToLeadEntity(lead *AdLead) (map[string]interface{}, error) { 76 | jsonData, err := json.Marshal(lead) 77 | if err != nil { 78 | return nil, err 79 | } 80 | var entity map[string]interface{} 81 | err = json.Unmarshal(jsonData, &entity) 82 | return entity, nil 83 | } 84 | -------------------------------------------------------------------------------- /api/authorizer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "goclassifieds/lib/utils" 8 | 9 | "github.com/MicahParks/keyfunc" 10 | "github.com/aws/aws-lambda-go/events" 11 | "github.com/aws/aws-lambda-go/lambda" 12 | "github.com/golang-jwt/jwt/v4" 13 | ) 14 | 15 | var handler Handler 16 | 17 | type Handler func(req *events.APIGatewayWebsocketProxyRequest) (events.APIGatewayCustomAuthorizerResponse, error) 18 | 19 | type ActionContext struct { 20 | UserPoolId string 21 | } 22 | 23 | // Authorizer custom api authorizer 24 | func Authorizer(request *events.APIGatewayWebsocketProxyRequest, ac *ActionContext) (events.APIGatewayCustomAuthorizerResponse, error) { 25 | token := request.QueryStringParameters["token"] 26 | 27 | usageLog := &utils.LogUsageLambdaInput{ 28 | // UserId: GetUserId(req), 29 | //Username: GetUsername(req), 30 | UserId: "null", 31 | Username: "null", 32 | Resource: request.RequestContext.RouteKey, 33 | Path: request.RequestContext.EventType, 34 | RequestId: request.RequestContext.RequestID, 35 | Intensities: "null", 36 | Regions: "null", 37 | Region: "null", 38 | Service: "null", 39 | Repository: "null", 40 | Organization: "null", 41 | } 42 | _, hedged := request.Headers["x-hedge-region"] 43 | if hedged { 44 | usageLog.Intensities = request.Headers["x-hedge-intensities"] 45 | usageLog.Regions = request.Headers["x-hedge-regions"] 46 | usageLog.Region = request.Headers["x-hedge-region"] 47 | usageLog.Service = request.Headers["x-hedge-service"] 48 | } 49 | 50 | utils.LogUsageForLambdaWithInput(usageLog) 51 | 52 | // ctx := context.Background() 53 | // Fetch all keys 54 | // jwkSet, err := jwk.Fetch(ctx, "https://cognito-idp.us-east-1.amazonaws.com/"+ac.UserPoolId+"/.well-known/jwks.json") 55 | jwks, err := keyfunc.Get("https://cognito-idp.us-east-1.amazonaws.com/"+ac.UserPoolId+"/.well-known/jwks.json", keyfunc.Options{}) 56 | if err != nil { 57 | log.Fatalln("Unable to fetch keys") 58 | } 59 | 60 | log.Print("fectehd keys") 61 | 62 | // Verify 63 | t, err := jwt.Parse(token, jwks.Keyfunc) 64 | if err != nil || !t.Valid { 65 | log.Print(err) 66 | log.Fatalln("Unauthorized") 67 | } 68 | 69 | log.Print("authorized") 70 | 71 | claims := t.Claims.(jwt.MapClaims) 72 | 73 | log.Print("got claims") 74 | 75 | claims["cognito:groups"] = nil //claims["cognito:groups"] 76 | claims["cognito:roles"] = nil //claims["cognito:groups"] 77 | 78 | return events.APIGatewayCustomAuthorizerResponse{ 79 | PrincipalID: "me", 80 | PolicyDocument: events.APIGatewayCustomAuthorizerPolicy{ 81 | Version: "2012-10-17", 82 | Statement: []events.IAMPolicyStatement{ 83 | { 84 | Action: []string{"execute-api:Invoke"}, 85 | Effect: "Allow", 86 | Resource: []string{"*"}, 87 | // Resource: []string{request.Resource}, 88 | }, 89 | }, 90 | }, 91 | Context: claims, 92 | }, nil 93 | } 94 | 95 | func InitializeHandler(ac ActionContext) Handler { 96 | return func(req *events.APIGatewayWebsocketProxyRequest) (events.APIGatewayCustomAuthorizerResponse, error) { 97 | return Authorizer(req, &ac) 98 | } 99 | } 100 | 101 | func init() { 102 | log.Printf("Gin cold start") 103 | actionContext := ActionContext{ 104 | UserPoolId: os.Getenv("USER_POOL_ID"), 105 | } 106 | handler = InitializeHandler(actionContext) 107 | } 108 | 109 | func main() { 110 | log.SetFlags(0) 111 | lambda.Start(handler) 112 | } 113 | -------------------------------------------------------------------------------- /lib/es/es.go: -------------------------------------------------------------------------------- 1 | package es 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "log" 8 | "strings" 9 | "text/template" 10 | 11 | elasticsearch7 "github.com/elastic/go-elasticsearch/v7" 12 | ) 13 | 14 | type SearchQueryBuilder interface { 15 | Build() map[string]interface{} 16 | GetIndex() string 17 | GetCollectionKey() string 18 | } 19 | 20 | type TemplateBuilder struct { 21 | Index string 22 | Data interface{} 23 | Template *template.Template 24 | Name string 25 | CollectionKey string 26 | } 27 | 28 | func (t TemplateBuilder) GetIndex() string { 29 | return t.Index 30 | } 31 | 32 | func (t TemplateBuilder) GetCollectionKey() string { 33 | return t.CollectionKey 34 | } 35 | 36 | func (t TemplateBuilder) Build() map[string]interface{} { 37 | var tb bytes.Buffer 38 | err := t.Template.ExecuteTemplate(&tb, t.Name, t.Data) 39 | if err != nil { 40 | log.Printf("Build Query Error: %s", err.Error()) 41 | } 42 | 43 | var query map[string]interface{} 44 | err = json.Unmarshal(tb.Bytes(), &query) 45 | if err != nil { 46 | log.Printf("Unmarshall Query Error: %s", err.Error()) 47 | } 48 | 49 | return query 50 | } 51 | 52 | func ExecuteQuery(esClient *elasticsearch7.Client, builder SearchQueryBuilder) []interface{} { 53 | query := builder.Build() 54 | 55 | var qb bytes.Buffer 56 | if err := json.NewEncoder(&qb).Encode(query); err != nil { 57 | log.Fatalf("Error encoding search query: %s", err) 58 | } 59 | log.Printf("Search Query: %s", qb.String()) 60 | 61 | return ExecuteSearch(esClient, &query, builder.GetIndex(), builder.GetCollectionKey()) 62 | } 63 | 64 | func ExecuteSearch(esClient *elasticsearch7.Client, query *map[string]interface{}, index string, collectionKey string) []interface{} { 65 | var buf bytes.Buffer 66 | if err := json.NewEncoder(&buf).Encode(query); err != nil { 67 | log.Fatalf("Error encoding query: %s", err) 68 | } 69 | res, err := esClient.Search( 70 | esClient.Search.WithContext(context.Background()), 71 | esClient.Search.WithIndex(index), 72 | esClient.Search.WithBody(&buf), 73 | // esClient.Search.WithTrackTotalHits(true), 74 | esClient.Search.WithPretty(), 75 | ) 76 | if err != nil { 77 | log.Fatalf("Error getting response: %s", err) 78 | } 79 | if res.IsError() { 80 | var e map[string]interface{} 81 | if err := json.NewDecoder(res.Body).Decode(&e); err != nil { 82 | log.Printf("Response: %s", res.Body) 83 | log.Fatalf("Error parsing the response body: %s", err) 84 | } else { 85 | // Print the response status and error information. 86 | log.Fatalf("[%s] %s: %s", 87 | res.Status(), 88 | e["error"].(map[string]interface{})["type"], 89 | e["error"].(map[string]interface{})["reason"], 90 | ) 91 | } 92 | } 93 | defer res.Body.Close() 94 | var r map[string]interface{} 95 | if err := json.NewDecoder(res.Body).Decode(&r); err != nil { 96 | log.Printf("Response: %s", res.Body) 97 | log.Fatalf("Error parsing the response body: %s", err) 98 | } 99 | pieces := strings.Split(collectionKey, ".") 100 | target := r[pieces[0]] 101 | for i, piece := range pieces { 102 | if i > 0 { 103 | target = target.(map[string]interface{})[piece] 104 | } 105 | } 106 | return target.([]interface{}) 107 | // return r["hits"].(map[string]interface{})["hits"].([]interface{}) 108 | /*var docs []interface{} 109 | for _, hit := range r["hits"].(map[string]interface{})["hits"].([]interface{}) { 110 | docs = append(docs, hit) 111 | } 112 | return docs*/ 113 | } 114 | -------------------------------------------------------------------------------- /lib/utils/compact.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "errors" 7 | "os" 8 | ) 9 | 10 | // EncodeStringToFixedBytes encodes a string into a fixed number of bytes. 11 | func EncodeStringToFixedBytes(input string, fixedSize int) ([]byte, error) { 12 | if fixedSize <= 0 { 13 | return nil, errors.New("fixed size must be greater than zero") 14 | } 15 | 16 | encoded := []byte(input) 17 | 18 | // If the string is longer than the fixed size, truncate it. 19 | if len(encoded) > fixedSize { 20 | return encoded[:fixedSize], nil 21 | } 22 | 23 | // If the string is shorter, pad it with zero bytes. 24 | padding := make([]byte, fixedSize-len(encoded)) 25 | return append(encoded, padding...), nil 26 | } 27 | 28 | // DecodeFixedBytesToString decodes a fixed-size byte array back into a string. 29 | func DecodeFixedBytesToString(data []byte) string { 30 | return string(bytes.TrimRight(data, "\x00")) // Remove trailing zero bytes (padding) 31 | } 32 | 33 | // WriteFixedLengthStrings writes an array of strings to a binary file with fixed-length encoding. 34 | func WriteFixedLengthStrings(filename string, strings []string, fixedSize int) error { 35 | file, err := os.Create(filename) 36 | if err != nil { 37 | return err 38 | } 39 | defer file.Close() 40 | 41 | for _, s := range strings { 42 | encoded, err := EncodeStringToFixedBytes(s, fixedSize) 43 | if err != nil { 44 | return err 45 | } 46 | _, err = file.Write(encoded) 47 | if err != nil { 48 | return err 49 | } 50 | } 51 | return nil 52 | } 53 | 54 | // ReadFixedLengthString reads a single fixed-length string from a binary file at a given index. 55 | func ReadFixedLengthString(filename string, index int, fixedSize int) (string, error) { 56 | file, err := os.Open(filename) 57 | if err != nil { 58 | return "", err 59 | } 60 | defer file.Close() 61 | 62 | offset := index * fixedSize 63 | data := make([]byte, fixedSize) 64 | 65 | // Seek to the correct position in the file 66 | _, err = file.Seek(int64(offset), 0) 67 | if err != nil { 68 | return "", err 69 | } 70 | 71 | // Read the fixed-length string 72 | _, err = file.Read(data) 73 | if err != nil { 74 | return "", err 75 | } 76 | 77 | // Decode and return the string 78 | return DecodeFixedBytesToString(data), nil 79 | } 80 | 81 | func main() { 82 | // Fixed size for each string (128 bytes) 83 | fixedSize := 128 84 | 85 | // Strings to store in the file 86 | strings := []string{"Hello", "World", "Golang", "This is a longer string!"} 87 | 88 | // File to store the fixed-length encoded strings 89 | filename := "fixed_length_strings_128.bin" 90 | 91 | // Write the strings to the file 92 | err := WriteFixedLengthStrings(filename, strings, fixedSize) 93 | if err != nil { 94 | fmt.Printf("Error writing strings to file: %v\n", err) 95 | return 96 | } 97 | fmt.Println("Strings written to file successfully.") 98 | 99 | // Read specific strings from the file 100 | for i := 0; i < len(strings); i++ { 101 | result, err := ReadFixedLengthString(filename, i, fixedSize) 102 | if err != nil { 103 | fmt.Printf("Error reading string at index %d: %v\n", i, err) 104 | continue 105 | } 106 | fmt.Printf("String at index %d: %s\n", i, result) 107 | } 108 | 109 | // Random access: Read the 2nd string (index 1, zero-based) 110 | index := 1 111 | randomResult, err := ReadFixedLengthString(filename, index, fixedSize) 112 | if err != nil { 113 | fmt.Printf("Error reading string at index %d: %v\n", index, err) 114 | return 115 | } 116 | fmt.Printf("Random access result (index %d): %s\n", index, randomResult) 117 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use a base image with Debian/Ubuntu for compatibility 2 | FROM ubuntu:20.04 3 | 4 | # Set environment variables for non-interactive installation 5 | ENV DEBIAN_FRONTEND=noninteractive 6 | ENV NVM_DIR=/usr/local/nvm 7 | 8 | # Install dependencies required for Bazel, Go, Node.js, AWS CLI, Serverless Framework, and additional tools 9 | RUN apt-get update && \ 10 | apt-get install -y \ 11 | curl \ 12 | wget \ 13 | gnupg \ 14 | unzip \ 15 | git \ 16 | build-essential \ 17 | software-properties-common \ 18 | apt-transport-https \ 19 | python3 \ 20 | python3-pip \ 21 | libssl-dev 22 | 23 | # --- NEW BLOCK START: INSTALL DOCKER CLI --- 24 | # This installs only the 'docker' command line client, allowing it to talk 25 | # to the host's daemon via the socket defined in docker-compose.yml. 26 | RUN apt-get update && \ 27 | # 1. Add Docker's official GPG key 28 | install -m 0755 -d /etc/apt/keyrings && \ 29 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \ 30 | chmod a+r /etc/apt/keyrings/docker.gpg && \ 31 | \ 32 | # 2. Add the Docker repository to Apt sources 33 | echo \ 34 | "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ 35 | $(. /etc/os-release && echo "$UBUNTU_CODENAME") stable" | \ 36 | tee /etc/apt/sources.list.d/docker.list > /dev/null && \ 37 | \ 38 | # 3. Install only the CLI package 39 | apt-get update && \ 40 | apt-get install -y docker-ce-cli 41 | # --- NEW BLOCK END --- 42 | 43 | # Clean up package lists after all installations 44 | RUN rm -rf /var/lib/apt/lists/* 45 | 46 | # Install Bazel 5.4.0 47 | RUN wget https://github.com/bazelbuild/bazel/releases/download/5.4.0/bazel-5.4.0-linux-x86_64 -O /usr/local/bin/bazel && \ 48 | chmod +x /usr/local/bin/bazel && \ 49 | bazel --version 50 | 51 | # Install Go 1.18 instead of Go 1.7 52 | RUN wget https://go.dev/dl/go1.18.10.linux-amd64.tar.gz && \ 53 | tar -C /usr/local -xzf go1.18.10.linux-amd64.tar.gz && \ 54 | rm go1.18.10.linux-amd64.tar.gz 55 | ENV PATH="/usr/local/go/bin:${PATH}" 56 | RUN go version 57 | 58 | 59 | # Set GOPATH environment variable 60 | ENV GOPATH=/workspace/go 61 | ENV PATH="$GOPATH/bin:${PATH}" 62 | 63 | # Create GOPATH directory 64 | RUN mkdir -p $GOPATH/src $GOPATH/bin $GOPATH/pkg 65 | 66 | # Install Node.js using nvm (Node Version Manager) 67 | ENV NODE_VERSION=20 68 | RUN mkdir -p $NVM_DIR && \ 69 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash && \ 70 | . "$NVM_DIR/nvm.sh" && \ 71 | nvm install $NODE_VERSION && \ 72 | nvm alias default $NODE_VERSION && \ 73 | ln -s "$NVM_DIR/versions/node/$(nvm current)/bin/node" /usr/local/bin/node && \ 74 | ln -s "$NVM_DIR/versions/node/$(nvm current)/bin/npm" /usr/local/bin/npm && \ 75 | ln -s "$NVM_DIR/versions/node/$(nvm current)/bin/npx" /usr/local/bin/npx 76 | ENV PATH="$NVM_DIR/versions/node/$(nvm current)/bin:$PATH" 77 | RUN . "$NVM_DIR/nvm.sh" && npm install -g npm@latest 78 | RUN node -v && npm -v 79 | 80 | # Install AWS CLI v2 81 | RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \ 82 | unzip awscliv2.zip && \ 83 | ./aws/install && \ 84 | rm -rf awscliv2.zip aws 85 | RUN aws --version 86 | 87 | # Install Serverless Framework version 3 88 | RUN . "$NVM_DIR/nvm.sh" && \ 89 | npm install -g serverless@3 --unsafe-perm --verbose && \ 90 | echo "Serverless installed to: $(which serverless)" && \ 91 | serverless --version 92 | 93 | # Set default workdir inside the container 94 | WORKDIR /workspace 95 | 96 | # Default command to keep the container running 97 | CMD ["bash"] -------------------------------------------------------------------------------- /func/index_entity/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "goclassifieds/lib/ads" 6 | "goclassifieds/lib/attr" 7 | "goclassifieds/lib/cc" 8 | "goclassifieds/lib/entity" 9 | "goclassifieds/lib/sign" 10 | "goclassifieds/lib/vocab" 11 | "os" 12 | "strings" 13 | 14 | "github.com/aws/aws-lambda-go/events" 15 | "github.com/aws/aws-lambda-go/lambda" 16 | session "github.com/aws/aws-sdk-go/aws/session" 17 | elasticsearch7 "github.com/elastic/go-elasticsearch/v7" 18 | opensearch "github.com/opensearch-project/opensearch-go" 19 | 20 | "github.com/mitchellh/mapstructure" 21 | "github.com/tangzero/inflector" 22 | ) 23 | 24 | func handler(ctx context.Context, s3Event events.S3Event) { 25 | 26 | elasticCfg := elasticsearch7.Config{ 27 | Addresses: []string{os.Getenv("ELASTIC_URL")}, 28 | } 29 | 30 | esClient, err := elasticsearch7.NewClient(elasticCfg) 31 | if err != nil { 32 | 33 | } 34 | 35 | awsSigner := sign.AwsSigner{ 36 | Service: "es", 37 | Region: "us-east-1", 38 | } 39 | 40 | opensearchCfg := opensearch.Config{ 41 | Addresses: []string{os.Getenv("ELASTIC_URL")}, 42 | Signer: awsSigner, 43 | } 44 | 45 | osClient, err := opensearch.NewClient(opensearchCfg) 46 | if err != nil { 47 | 48 | } 49 | 50 | sess := session.Must(session.NewSession()) 51 | 52 | for _, record := range s3Event.Records { 53 | 54 | pieces := strings.Split(record.S3.Object.Key, "/") 55 | 56 | pluralName := inflector.Pluralize(pieces[0]) 57 | singularName := inflector.Singularize(pieces[0]) 58 | 59 | entityManager := entity.NewDefaultManager(entity.DefaultManagerConfig{ 60 | SingularName: singularName, 61 | PluralName: pluralName, 62 | Index: "classified_" + pluralName, 63 | EsClient: esClient, 64 | OsClient: osClient, 65 | Session: sess, 66 | UserId: "", 67 | Stage: os.Getenv("STAGE"), 68 | BucketName: os.Getenv("BUCKET_NAME"), 69 | }) 70 | 71 | id := pieces[1][0 : len(pieces[1])-8] 72 | ent := entityManager.Load(id, "default") 73 | 74 | if singularName == "ad" { 75 | ent = IndexAd(ent) 76 | } else if singularName == "panelpage" { 77 | ent = IndexPanelPage(ent) 78 | } 79 | 80 | entityManager.Save(ent, "elastic") 81 | } 82 | } 83 | 84 | func IndexAd(obj map[string]interface{}) map[string]interface{} { 85 | 86 | var item ads.Ad 87 | mapstructure.Decode(obj, &item) 88 | 89 | allAttrValues := make([]attr.AttributeValue, 0) 90 | for _, attrValue := range item.Attributes { 91 | attributesFlattened := attr.FlattenAttributeValue(attrValue) 92 | for _, flatAttr := range attributesFlattened { 93 | attr.FinalizeAttributeValue(&flatAttr) 94 | allAttrValues = append(allAttrValues, flatAttr) 95 | } 96 | } 97 | item.Attributes = allAttrValues 98 | 99 | for index, featureSet := range item.FeatureSets { 100 | allFeatureTerms := make([]vocab.Term, 0) 101 | for _, term := range featureSet.Terms { 102 | flatTerms := vocab.FlattenTerm(term, true) 103 | for _, flatTerm := range flatTerms { 104 | allFeatureTerms = append(allFeatureTerms, flatTerm) 105 | } 106 | } 107 | item.FeatureSets[index].Terms = allFeatureTerms 108 | } 109 | 110 | ent, _ := ads.ToEntity(&item) 111 | return ent 112 | 113 | } 114 | 115 | func IndexPanelPage(obj map[string]interface{}) map[string]interface{} { 116 | 117 | var item cc.PanelPage 118 | mapstructure.Decode(obj, &item) 119 | 120 | item.GridItems = make([]cc.GridItem, 0) 121 | item.Contexts = make([]cc.InlineContext, 0) 122 | item.Panels = make([]cc.Panel, 0) 123 | item.RowSettings = make([]cc.LayoutSetting, 0) 124 | 125 | ent, _ := cc.ToPanelPageEntity(&item) 126 | return ent 127 | 128 | } 129 | 130 | func main() { 131 | lambda.Start(handler) 132 | } 133 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/token/index.js: -------------------------------------------------------------------------------- 1 | const AWS = require("aws-sdk"); 2 | const querystring = require("querystring"); // Used to parse form-encoded data 3 | const logger = require("./logger"); 4 | 5 | const dynamodb = new AWS.DynamoDB.DocumentClient(); 6 | 7 | exports.handler = async (event) => { 8 | try { 9 | // Parse the form-encoded body 10 | const body = querystring.parse(event.body); 11 | const { code /*, state , redirect_uri, client_id*/ } = body; 12 | 13 | logger({}); 14 | 15 | // Validate the input parameters 16 | if (!code) { 17 | console.error("Missing authorization code"); 18 | return { 19 | statusCode: 400, 20 | body: JSON.stringify({ error: "Missing authorization code" }), 21 | }; 22 | } 23 | 24 | // Retrieve the token data from DynamoDB using the authorization code 25 | const tokenData = await dynamodb 26 | .get({ 27 | TableName: process.env.AUTH_CODE_TABLE_NAME, 28 | Key: { auth_code: code }, 29 | }) 30 | .promise(); 31 | 32 | // Check if token data exists 33 | if (!tokenData.Item) { 34 | console.error("Invalid or expired authorization code"); 35 | return { 36 | statusCode: 400, 37 | body: JSON.stringify({ error: "Invalid or expired authorization code" }), 38 | }; 39 | } 40 | 41 | // No state provided in request for cognito but maybe others so leave it here 42 | /*if (tokenData.Item.state !== state) { 43 | console.error("State mismatch"); 44 | return { 45 | statusCode: 400, 46 | body: JSON.stringify({ error: "State mismatch" }), 47 | }; 48 | }*/ 49 | 50 | // Optional: Validate redirect_uri 51 | /*if (redirect_uri && tokenData.Item.redirect_uri !== redirect_uri) { 52 | console.error("Redirect URI mismatch"); 53 | return { 54 | statusCode: 400, 55 | body: JSON.stringify({ error: "Redirect URI mismatch" }), 56 | }; 57 | }*/ 58 | 59 | // Optional: Validate client_id 60 | /*if (client_id && process.env.COGNITO_APP_CLIENT_ID !== client_id) { 61 | console.error("Client ID mismatch"); 62 | return { 63 | statusCode: 400, 64 | body: JSON.stringify({ error: "Client ID mismatch" }), 65 | }; 66 | }*/ 67 | 68 | // Extract the tokens from the retrieved data 69 | const { id_token, access_token, refresh_token, expiration } = tokenData.Item; 70 | 71 | // Optional: Check if the tokens are expired 72 | const currentTimestamp = Math.floor(Date.now() / 1000); 73 | if (expiration < currentTimestamp) { 74 | console.error("Authorization code has expired"); 75 | return { 76 | statusCode: 400, 77 | body: JSON.stringify({ error: "Authorization code has expired" }), 78 | }; 79 | } 80 | 81 | // Return the tokens to the client 82 | return { 83 | statusCode: 200, 84 | body: JSON.stringify({ 85 | id_token, 86 | access_token, 87 | refresh_token, 88 | expires_in: expiration - currentTimestamp, // Remaining time until expiration 89 | token_type: 'Bearer' 90 | }), 91 | }; 92 | } catch (error) { 93 | console.error("Error retrieving token:", error); 94 | return { 95 | statusCode: 500, 96 | body: JSON.stringify({ error: "Internal server error", details: error.message }), 97 | }; 98 | } 99 | }; -------------------------------------------------------------------------------- /lib/os/os.go: -------------------------------------------------------------------------------- 1 | package os 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "log" 8 | "strings" 9 | "text/template" 10 | 11 | opensearch "github.com/opensearch-project/opensearch-go" 12 | ) 13 | 14 | type SearchQueryBuilder interface { 15 | Build() map[string]interface{} 16 | GetIndex() string 17 | GetCollectionKey() string 18 | } 19 | 20 | type TemplateBuilder struct { 21 | Index string 22 | Data interface{} 23 | Template *template.Template 24 | Name string 25 | CollectionKey string 26 | } 27 | 28 | func (t TemplateBuilder) GetIndex() string { 29 | return t.Index 30 | } 31 | 32 | func (t TemplateBuilder) GetCollectionKey() string { 33 | return t.CollectionKey 34 | } 35 | 36 | func (t TemplateBuilder) Build() map[string]interface{} { 37 | var tb bytes.Buffer 38 | err := t.Template.ExecuteTemplate(&tb, t.Name, t.Data) 39 | if err != nil { 40 | log.Printf("Build Query Error: %s", err.Error()) 41 | } 42 | 43 | var query map[string]interface{} 44 | err = json.Unmarshal(tb.Bytes(), &query) 45 | if err != nil { 46 | log.Printf("Unmarshall Query Error: %s", err.Error()) 47 | } 48 | 49 | return query 50 | } 51 | 52 | func ExecuteQuery(esClient *opensearch.Client, builder SearchQueryBuilder) []interface{} { 53 | query := builder.Build() 54 | 55 | var qb bytes.Buffer 56 | if err := json.NewEncoder(&qb).Encode(query); err != nil { 57 | log.Fatalf("Error encoding search query: %s", err) 58 | } 59 | log.Printf("Search Query: %s", qb.String()) 60 | 61 | return ExecuteSearch(esClient, &query, builder.GetIndex(), builder.GetCollectionKey()) 62 | } 63 | 64 | func ExecuteSearch(esClient *opensearch.Client, query *map[string]interface{}, index string, collectionKey string) []interface{} { 65 | var buf bytes.Buffer 66 | if err := json.NewEncoder(&buf).Encode(query); err != nil { 67 | log.Fatalf("Error encoding query: %s", err) 68 | } 69 | res, err := esClient.Search( 70 | esClient.Search.WithContext(context.Background()), 71 | esClient.Search.WithIndex(index), 72 | esClient.Search.WithBody(&buf), 73 | // esClient.Search.WithTrackTotalHits(true), 74 | esClient.Search.WithPretty(), 75 | ) 76 | if err != nil { 77 | log.Fatalf("Error getting response: %s", err) 78 | } 79 | if res.IsError() { 80 | var e map[string]interface{} 81 | if err := json.NewDecoder(res.Body).Decode(&e); err != nil { 82 | buf := new(bytes.Buffer) 83 | buf.ReadFrom(res.Body) 84 | newStr := buf.String() 85 | log.Printf("Response: %s", newStr) 86 | log.Fatalf("Error parsing the response body: %s", err) 87 | } else { 88 | // Print the response status and error information. 89 | buf := new(bytes.Buffer) 90 | buf.ReadFrom(res.Body) 91 | msg := buf.String() 92 | log.Printf("open search request failure statuc: %s", res.Status()) 93 | log.Printf("open search request failure message: %s", msg) 94 | log.Fatalf("[%s] %s: %s", 95 | res.Status(), 96 | e["error"].(map[string]interface{})["type"], 97 | e["error"].(map[string]interface{})["reason"], 98 | ) 99 | } 100 | } 101 | defer res.Body.Close() 102 | var r map[string]interface{} 103 | if err := json.NewDecoder(res.Body).Decode(&r); err != nil { 104 | buf := new(bytes.Buffer) 105 | buf.ReadFrom(res.Body) 106 | newStr := buf.String() 107 | log.Printf("Response: %s", newStr) 108 | log.Fatalf("Error parsing the response body: %s", err) 109 | } 110 | pieces := strings.Split(collectionKey, ".") 111 | target := r[pieces[0]] 112 | for i, piece := range pieces { 113 | if i > 0 { 114 | target = target.(map[string]interface{})[piece] 115 | } 116 | } 117 | return target.([]interface{}) 118 | // return r["hits"].(map[string]interface{})["hits"].([]interface{}) 119 | /*var docs []interface{} 120 | for _, hit := range r["hits"].(map[string]interface{})["hits"].([]interface{}) { 121 | docs = append(docs, hit) 122 | } 123 | return docs*/ 124 | } 125 | -------------------------------------------------------------------------------- /hooks/index_create/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "goclassifieds/lib/entity" 6 | "log" 7 | "encoding/json" 8 | "os" 9 | "fmt" 10 | 11 | "goclassifieds/lib/repo" 12 | 13 | "github.com/aws/aws-lambda-go/lambda" 14 | "golang.org/x/oauth2" 15 | "github.com/google/go-github/v46/github" 16 | ) 17 | 18 | func handler(ctx context.Context, event entity.AfterSaveExecEntityRequest) (entity.AfterSaveExecEntityResponse, error) { 19 | 20 | /** 21 | * This is where all the code goes to create action SECRETS 22 | * for a site. Both for repo and enviironment. 23 | */ 24 | log.Printf("Create index for entity %s in repo %s and owner %s", event.Contract, event.Repo, event.Owner) 25 | 26 | // Log the entire content of event.Entity 27 | entityJSON, err := json.Marshal(event.Entity) // Convert map[string]interface{} to JSON string for readable logging 28 | if err != nil { 29 | log.Printf("Error marshalling event.Entity: %s", err) 30 | } else { 31 | log.Printf("Entity content: %s", entityJSON) 32 | } 33 | 34 | repoName, ok := event.Entity["repoName"].(string) 35 | if ok { 36 | log.Printf("The new repo name will be %s", repoName) 37 | } 38 | 39 | githubAppID := os.Getenv("GITHUB_APP_ID") 40 | if githubAppID == "" { 41 | err := fmt.Errorf("environment variable GITHUB_APP_ID is missing") 42 | log.Print(err) 43 | return entity.AfterSaveExecEntityResponse{}, err 44 | } 45 | 46 | // Load GitHub app PEM file 47 | pemFilePath := fmt.Sprintf("rtc-vertigo-%s.private-key.pem", os.Getenv("STAGE")) 48 | pem, err := os.ReadFile(pemFilePath) 49 | if err != nil { 50 | log.Printf("Failed to read PEM file '%s': %v", pemFilePath, err) 51 | return entity.AfterSaveExecEntityResponse{}, fmt.Errorf("failed to load GitHub app PEM file: %w", err) 52 | } 53 | log.Print("GitHub app PEM file loaded successfully.") 54 | 55 | // Generate GitHub Installation Token 56 | getTokenInput := &repo.GetInstallationTokenInput{ 57 | GithubAppPem: pem, 58 | Owner: event.Owner, 59 | GithubAppId: githubAppID, 60 | } 61 | installationToken, err := repo.GetInstallationToken(getTokenInput) 62 | if err != nil { 63 | log.Printf("Error generating GitHub installation token for owner '%s': %v", event.Owner, err) 64 | return entity.AfterSaveExecEntityResponse{}, fmt.Errorf("failed to generate GitHub installation token: %w", err) 65 | } 66 | log.Print("GitHub installation token generated successfully.") 67 | 68 | // Create OAuth2 HTTP client 69 | srcToken := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: *installationToken.Token}) 70 | httpClient := oauth2.NewClient(ctx, srcToken) 71 | githubRestClient := github.NewClient(httpClient) 72 | 73 | // Step 1: Create repository from template 74 | templateOwner := "rollthecloudinc" 75 | templateRepo := "spearhead-index" 76 | description := "index repository" 77 | private := false // index repositories are private by default content should only be accessed via searches. Ah... make it public for now 78 | err = repo.CreateFromTemplate(ctx, githubRestClient.Client(), templateOwner, templateRepo, event.Owner, repoName, description, private) 79 | if err != nil { 80 | log.Printf("Error creating index repository: %s", err.Error()) 81 | return entity.AfterSaveExecEntityResponse{}, fmt.Errorf("failed to create repository from template: %w", err) 82 | } 83 | 84 | log.Printf("Repository '%s/%s' successfully created from template. Proceeding with dev branch creation...", event.Owner, repoName) 85 | 86 | // Step 2: Create the "dev" branch 87 | err = repo.CreateDevBranch(githubRestClient, event.Owner, repoName) 88 | if err != nil { 89 | log.Printf("Error provisioning dev branch for index repository.") 90 | return entity.AfterSaveExecEntityResponse{}, fmt.Errorf("failed to create dev branch: %w", err) 91 | } 92 | 93 | return entity.AfterSaveExecEntityResponse{}, nil 94 | } 95 | 96 | func main() { 97 | log.SetFlags(0) 98 | // Make the handler available for Remote Procedure Call by AWS Lambda 99 | lambda.Start(handler) 100 | } -------------------------------------------------------------------------------- /api/entity/types.json.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "all" }} 2 | [ 3 | { 4 | "id": "27b8e62c-fcec-450c-9774-579a9549917f", 5 | "owner": "system", 6 | "overlay": false, 7 | "name": "general", 8 | "target": "ad", 9 | "attributes": [], 10 | "filters": [] 11 | }, 12 | { 13 | "id": "5665f5f8-5533-4aa1-96f4-b78e02714de0", 14 | "owner": "system", 15 | "overlay": false, 16 | "name": "realestate", 17 | "target": "ad", 18 | "attributes": [ 19 | { 20 | "name": "price", 21 | "type": 0, 22 | "label": "Asking Price", 23 | "required": true, 24 | "widget": "text", 25 | "attributes": [] 26 | }, 27 | { 28 | "name": "beds", 29 | "type": 0, 30 | "label": "Beds", 31 | "required": true, 32 | "widget": "text", 33 | "attributes": [] 34 | }, 35 | { 36 | "name": "baths", 37 | "type": 0, 38 | "label": "Baths", 39 | "required": true, 40 | "widget": "text", 41 | "attributes": [] 42 | }, 43 | { 44 | "name": "sqft", 45 | "type": 0, 46 | "label": "Sqft", 47 | "required": true, 48 | "widget": "text", 49 | "attributes": [] 50 | } 51 | ], 52 | "filters": [ 53 | { 54 | "name": "beds", 55 | "type": 0, 56 | "label": "Beds", 57 | "required": false, 58 | "widget": "text", 59 | "attributes": [] 60 | }, 61 | { 62 | "name": "baths", 63 | "type": 0, 64 | "label": "Baths", 65 | "required": false, 66 | "widget": "text", 67 | "attributes": [] 68 | } 69 | ] 70 | }, 71 | { 72 | "id": "d9bf054c-2a43-4976-a35e-25bd4c58417e", 73 | "owner": "system", 74 | "overlay": false, 75 | "name": "rentals", 76 | "target": "ad", 77 | "attributes": [], 78 | "filters": [] 79 | }, 80 | { 81 | "id": "ef5c1cdb-95b3-45be-ba01-b6745d9744f9", 82 | "owner": "system", 83 | "overlay": false, 84 | "name": "autos", 85 | "target": "ad", 86 | "attributes": [ 87 | { 88 | "name": "ymm", 89 | "type": 2, 90 | "label": "YMM", 91 | "required": false, 92 | "widget": "ymm_selector", 93 | "attributes": [ 94 | { 95 | "name": "year", 96 | "type": 0, 97 | "label": "Year", 98 | "required": false, 99 | "widget": "text", 100 | "attributes": [] 101 | }, 102 | { 103 | "name": "make", 104 | "type": 1, 105 | "label": "Make", 106 | "required": false, 107 | "widget": "text", 108 | "attributes": [] 109 | }, 110 | { 111 | "name": "model", 112 | "type": 1, 113 | "label": "Model", 114 | "required": false, 115 | "widget": "text", 116 | "attributes": [] 117 | } 118 | ] 119 | } 120 | ], 121 | "filters": [ 122 | { 123 | "name": "ymm", 124 | "label": "YMM", 125 | "type": 2, 126 | "required": false, 127 | "attributes": [ 128 | { 129 | "name": "year", 130 | "label": "Year", 131 | "type": 0, 132 | "required": false, 133 | "attributes": [] 134 | }, 135 | { 136 | "name": "make", 137 | "label": "Make", 138 | "type": 1, 139 | "required": false, 140 | "attributes": [] 141 | }, 142 | { 143 | "name":"model", 144 | "label":"model", 145 | "type": 1, 146 | "required": false, 147 | "attributes": [] 148 | } 149 | ], 150 | "widget": "ymm_selector" 151 | } 152 | ] 153 | } 154 | ] 155 | {{end}} -------------------------------------------------------------------------------- /iac/precheck-cognito/message/index.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | const b64 = require('base64-js'); 3 | const encryptionSdk = require('@aws-crypto/client-node'); 4 | 5 | // Configure the encryption SDK client with the KMS key from the environment variables 6 | const { encrypt, decrypt } = encryptionSdk.buildClient(encryptionSdk.CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT); 7 | const generatorKeyId = process.env.KEY_ALIAS; // KMS Key Alias (e.g., alias/your-key) 8 | const keyIds = [process.env.KEY_ARN]; // KMS Key ARN (e.g., arn:aws:kms:region:account-id:key/key-id) 9 | const keyring = new encryptionSdk.KmsKeyringNode({ generatorKeyId, keyIds }); 10 | 11 | const sns = new AWS.SNS({ region: process.env.REGION }); // Default SNS region 12 | const ses = new AWS.SES({ region: process.env.REGION }); // Default SES region 13 | 14 | exports.handler = async (event) => { 15 | console.log('Received event:', JSON.stringify(event, null, 2)); 16 | 17 | const phoneNumber = event.request.userAttributes.phone_number; // E.164 format 18 | const encryptedCode = event.request.code; // Encrypted verification code 19 | const fallbackEmail = event.request.userAttributes.email; // Fallback email, if needed 20 | 21 | // Ensure required attributes are present 22 | if (!phoneNumber || !encryptedCode) { 23 | console.error('Missing required parameters: phone number or encrypted code'); 24 | throw new Error('Missing required parameters'); 25 | } 26 | 27 | let plainTextCode; 28 | try { 29 | // Decrypt the secret code using the encryption SDK 30 | console.log('Decrypting verification code...'); 31 | const { plaintext, messageHeader } = await decrypt(keyring, b64.toByteArray(encryptedCode)); 32 | plainTextCode = plaintext.toString('utf-8'); // Convert plaintext buffer to a string 33 | console.log('Decrypted verification code:', plainTextCode); 34 | } catch (decryptError) { 35 | console.error('Failed to decrypt verification code:', decryptError); 36 | throw new Error('Failed to decrypt verification code'); 37 | } 38 | 39 | try { 40 | // Step 1: Attempt to send SMS via SNS 41 | console.log('Attempting to send SMS via SNS...'); 42 | const smsResult = await sns 43 | .publish({ 44 | Message: `Your verification code is: ${plainTextCode}`, // The decrypted code 45 | PhoneNumber: phoneNumber, 46 | }) 47 | .promise(); 48 | 49 | console.log('SMS sent successfully:', smsResult); 50 | return event; // Return the unmodified event object to indicate success 51 | } catch (snsError) { 52 | console.error('Failed to send SMS via SNS:', snsError); 53 | 54 | // Step 2: Fallback to Email via SES 55 | if (fallbackEmail) { 56 | console.log('Falling back to email...'); 57 | try { 58 | const emailResult = await ses 59 | .sendEmail({ 60 | Source: process.env.SES_VERIFIED_EMAIL, // SES verified sender email 61 | Destination: { 62 | ToAddresses: [fallbackEmail], 63 | }, 64 | Message: { 65 | Subject: { 66 | Data: 'Your Verification Code', 67 | }, 68 | Body: { 69 | Text: { 70 | Data: `We couldn't send an SMS to your phone number. Your verification code is: ${plainTextCode}`, 71 | }, 72 | }, 73 | }, 74 | }) 75 | .promise(); 76 | console.log('Fallback email sent successfully:', emailResult); 77 | return event; // Return the unmodified event object to indicate success 78 | } catch (emailError) { 79 | console.error('Failed to send fallback email:', emailError); 80 | throw new Error('Failed to send SMS and fallback email'); 81 | } 82 | } 83 | 84 | throw new Error('Failed to send SMS and no fallback email is available'); 85 | } 86 | }; -------------------------------------------------------------------------------- /api/gov/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "goclassifieds/lib/gov" 6 | "goclassifieds/lib/utils" 7 | "log" 8 | "os" 9 | 10 | "github.com/aws/aws-lambda-go/events" 11 | "github.com/aws/aws-lambda-go/lambda" 12 | "github.com/aws/aws-sdk-go/aws" 13 | "github.com/aws/aws-sdk-go/aws/session" 14 | lambda2 "github.com/aws/aws-sdk-go/service/lambda" 15 | ) 16 | 17 | var handler Handler 18 | 19 | type Handler func(req *events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) 20 | 21 | type ActionContext struct { 22 | Stage string 23 | Lambda *lambda2.Lambda 24 | } 25 | 26 | func GetGrant(req *events.APIGatewayProxyRequest, ac *ActionContext) (events.APIGatewayProxyResponse, error) { 27 | var res events.APIGatewayProxyResponse 28 | 29 | grantAccessRequest := gov.GrantAccessRequest{ 30 | User: req.PathParameters["user"], 31 | Type: gov.UserTypeMap[req.PathParameters["type"]], 32 | Resource: gov.ResourceTypeMap[req.PathParameters["resource"]], 33 | Operation: gov.OperationMap[req.PathParameters["op"]], 34 | Asset: req.PathParameters["proxy"], 35 | } 36 | 37 | log.Print(req) 38 | 39 | payload, err := json.Marshal(grantAccessRequest) 40 | if err != nil { 41 | log.Printf("Error marshalling grant access request: %s", err.Error()) 42 | res.StatusCode = 500 43 | return res, err 44 | } 45 | 46 | lambdaRes, err := ac.Lambda.Invoke(&lambda2.InvokeInput{FunctionName: aws.String("goclassifieds-api-" + ac.Stage + "-GrantAccess"), Payload: payload}) 47 | if err != nil { 48 | log.Printf("error invoking grant_access: %s", err.Error()) 49 | res.StatusCode = 500 50 | return res, err 51 | } 52 | 53 | //var grantRes gov.GrantAccessResponse 54 | //json.Unmarshal(lambdaRes.Payload, &grantRes) 55 | 56 | res.Body = string(lambdaRes.Payload) 57 | res.StatusCode = 200 58 | return res, nil 59 | } 60 | 61 | func InitializeHandler(c *ActionContext) Handler { 62 | return func(req *events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { 63 | 64 | usageLog := &utils.LogUsageLambdaInput{ 65 | UserId: req.PathParameters["user"], 66 | Username: "null", 67 | Resource: req.Resource, 68 | Path: req.Path, 69 | RequestId: req.RequestContext.RequestID, 70 | Intensities: "null", 71 | Regions: "null", 72 | Region: "null", 73 | Service: "null", 74 | Repository: "null", 75 | Organization: "null", 76 | } 77 | _, hedged := req.Headers["x-hedge-region"] 78 | if hedged { 79 | usageLog.Intensities = req.Headers["x-hedge-intensities"] 80 | usageLog.Regions = req.Headers["x-hedge-regions"] 81 | usageLog.Region = req.Headers["x-hedge-region"] 82 | usageLog.Service = req.Headers["x-hedge-service"] 83 | } 84 | _, hasOwner := req.PathParameters["owner"] 85 | if hasOwner { 86 | usageLog.Organization = req.PathParameters["owner"] 87 | } 88 | _, hasRepo := req.PathParameters["repo"] 89 | if hasRepo { 90 | usageLog.Repository = req.PathParameters["repo"] 91 | } 92 | 93 | utils.LogUsageForLambdaWithInput(usageLog) 94 | 95 | ac := RequestActionContext(c) 96 | 97 | //ac.UserId = GetUserId(req) 98 | 99 | if req.HTTPMethod == "GET" { 100 | return GetGrant(req, ac) 101 | } /*else if entityName == pluralName && req.HTTPMethod == "GET" { 102 | return GetEntities(req, ac) 103 | }*/ 104 | 105 | return events.APIGatewayProxyResponse{StatusCode: 400}, nil 106 | } 107 | } 108 | 109 | func RequestActionContext(c *ActionContext) *ActionContext { 110 | 111 | ac := &ActionContext{ 112 | Stage: c.Stage, 113 | Lambda: c.Lambda, 114 | } 115 | 116 | return ac 117 | 118 | } 119 | 120 | func init() { 121 | log.Printf("gov start") 122 | 123 | sess := session.Must(session.NewSession()) 124 | lClient := lambda2.New(sess) 125 | //gateway := apigatewaymanagementapi.New(sess, aws.NewConfig().WithEndpoint(os.Getenv("APIGATEWAY_ENDPOINT"))) 126 | 127 | actionContext := ActionContext{ 128 | Stage: os.Getenv("STAGE"), 129 | Lambda: lClient, 130 | } 131 | 132 | handler = InitializeHandler(&actionContext) 133 | 134 | log.Print("gov started") 135 | } 136 | 137 | func main() { 138 | log.SetFlags(0) 139 | lambda.Start(handler) 140 | } 141 | -------------------------------------------------------------------------------- /lib/watttime/watttime.go: -------------------------------------------------------------------------------- 1 | package watttime 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io/ioutil" 7 | "net/http" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | type LoginInput struct { 13 | Username string 14 | Password string 15 | } 16 | 17 | type LoginOutput struct { 18 | Token string 19 | } 20 | 21 | type GridRegionsInput struct { 22 | Token string 23 | All bool 24 | } 25 | 26 | type GridRegionsOutput struct { 27 | GridRegions []GridRegion 28 | } 29 | 30 | type IndexInput struct { 31 | Token string 32 | Ba string 33 | } 34 | 35 | type IndexOutput struct { 36 | Freq string `json:"freq"` 37 | Ba string `json:"ba"` 38 | Percent int `json:"percent"` 39 | Moer float64 `json:"moer"` 40 | PointTime time.Time `json:"point_time"` 41 | } 42 | 43 | type IndexResponse struct { 44 | Freq string `json:"freq"` 45 | Ba string `json:"ba"` 46 | Percent string `json:"percent"` 47 | Moer string `json:"moer"` 48 | PointTime string `json:"point_time"` 49 | } 50 | 51 | type GridRegion struct { 52 | Ba string `json:"ba"` 53 | Name string `json:"name"` 54 | Access string `json:"access"` 55 | Datatype string `json:"datatype"` 56 | } 57 | 58 | type Result []interface{} 59 | 60 | type WattLoginResponse struct { 61 | Token string `json:"token"` 62 | } 63 | 64 | func Login(input *LoginInput) (*LoginOutput, error) { 65 | output := &LoginOutput{} 66 | 67 | h := http.Client{} 68 | req, _ := http.NewRequest("GET", "https://api2.watttime.org/v2/login", nil) 69 | req.SetBasicAuth(input.Username, input.Password) 70 | r, err := h.Do(req) 71 | if err != nil { 72 | return nil, err 73 | } else if r.Status != "200 OK" { 74 | return nil, errors.New("Status: " + r.Status) 75 | } 76 | 77 | defer r.Body.Close() 78 | body, err := ioutil.ReadAll(r.Body) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | wattRes := &WattLoginResponse{} 84 | err = json.Unmarshal(body, wattRes) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | output.Token = wattRes.Token 90 | 91 | return output, nil 92 | } 93 | 94 | func GridRegions(input *GridRegionsInput) (*GridRegionsOutput, error) { 95 | output := &GridRegionsOutput{} 96 | url := "https://api2.watttime.org/v2/ba-access" 97 | if input.All { 98 | url += "?all=true" 99 | } 100 | 101 | h := http.Client{} 102 | req, _ := http.NewRequest("GET", url, nil) 103 | req.Header.Set("Authorization", "Bearer "+input.Token) 104 | r, err := h.Do(req) 105 | if err != nil { 106 | return nil, err 107 | } else if r.Status != "200 OK" { 108 | return nil, errors.New("Status: " + r.Status) 109 | } 110 | 111 | defer r.Body.Close() 112 | body, err := ioutil.ReadAll(r.Body) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | var wattRes Result 118 | err = json.Unmarshal(body, &wattRes) 119 | if err != nil { 120 | return nil, err 121 | } 122 | 123 | typedResult := make([]GridRegion, len(wattRes)) 124 | for index, item := range wattRes { 125 | var gridRegion GridRegion 126 | b, _ := json.Marshal(item) 127 | json.Unmarshal(b, &gridRegion) 128 | typedResult[index] = gridRegion 129 | } 130 | 131 | output.GridRegions = typedResult 132 | 133 | return output, nil 134 | } 135 | 136 | func GetIndex(input *IndexInput) (*IndexOutput, error) { 137 | 138 | output := &IndexOutput{} 139 | url := "https://api2.watttime.org/index?ba=" + input.Ba 140 | 141 | h := http.Client{} 142 | req, _ := http.NewRequest("GET", url, nil) 143 | req.Header.Set("Authorization", "Bearer "+input.Token) 144 | r, err := h.Do(req) 145 | if err != nil { 146 | return nil, err 147 | } else if r.Status != "200 OK" { 148 | return nil, errors.New("Status: " + r.Status) 149 | } 150 | 151 | defer r.Body.Close() 152 | body, err := ioutil.ReadAll(r.Body) 153 | if err != nil { 154 | return nil, err 155 | } 156 | 157 | indexRes := &IndexResponse{} 158 | err = json.Unmarshal(body, indexRes) 159 | if err != nil { 160 | return nil, err 161 | } 162 | 163 | output.Freq = indexRes.Freq 164 | output.Ba = indexRes.Ba 165 | output.Percent, _ = strconv.Atoi(indexRes.Percent) 166 | output.Moer, _ = strconv.ParseFloat(indexRes.Moer, 64) 167 | output.PointTime, _ = time.Parse(time.RFC3339, indexRes.PointTime) 168 | 169 | return output, nil 170 | 171 | } 172 | -------------------------------------------------------------------------------- /cloudform/search.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Hedge OpenSearch instance using the smallest node type (t3.small.search) with fine-grained access control. 3 | 4 | Resources: 5 | CognitoAuthRole: 6 | Type: AWS::IAM::Role 7 | Properties: 8 | RoleName: !Sub 'RtcCognitoOpenSearchRole${EnvironmentNameCamelCase}${VendorSuffixCamelCase}' 9 | AssumeRolePolicyDocument: 10 | Version: '2012-10-17' 11 | Statement: 12 | - Effect: "Allow" 13 | Principal: 14 | Service: "opensearchservice.amazonaws.com" 15 | Action: "sts:AssumeRole" 16 | 17 | Policies: 18 | - PolicyName: !Sub 'RtcCognitoOpenSearchAccess${EnvironmentNameCamelCase}${VendorSuffixCamelCase}' 19 | PolicyDocument: 20 | Version: "2012-10-17" 21 | Statement: 22 | - Effect: "Allow" 23 | Action: 24 | - "cognito-idp:DescribeUserPool" 25 | - "cognito-idp:CreateUserPoolClient" 26 | - "cognito-idp:DescribeUserPoolClient" 27 | - "cognito-identity:DescribeIdentityPool" 28 | - "cognito-identity:ListIdentityPools" 29 | Resource: !Sub 'arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/rtc-classifieds-${EnvironmentName}-${VendorSuffix}/*' 30 | 31 | - PolicyName: AllowCognitoActions 32 | PolicyDocument: 33 | Version: '2012-10-17' 34 | Statement: 35 | - Effect: Allow 36 | Action: 37 | - 'es:ESHttp*' 38 | Resource: 39 | - !Sub 'arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${UserPoolId}' 40 | - !Sub 'arn:aws:cognito-identity:${AWS::Region}:${AWS::AccountId}:identitypool/${IdentityPoolId}' 41 | OpenSearchDomain: 42 | Type: AWS::OpenSearchService::Domain 43 | Properties: 44 | DomainName: !Sub 'rtc-classifieds-${EnvironmentName}-${VendorSuffix}' 45 | EngineVersion: OpenSearch_2.19 46 | ClusterConfig: 47 | InstanceType: t3.small.search 48 | InstanceCount: 1 49 | DedicatedMasterEnabled: false 50 | ZoneAwarenessEnabled: false 51 | EBSOptions: 52 | EBSEnabled: true 53 | VolumeType: gp2 54 | VolumeSize: 10 55 | AdvancedSecurityOptions: 56 | Enabled: true 57 | InternalUserDatabaseEnabled: true 58 | MasterUserOptions: 59 | MasterUserName: admin 60 | MasterUserPassword: !Sub '${MasterPassword}' 61 | EncryptionAtRestOptions: 62 | Enabled: true # Enable encryption at rest 63 | NodeToNodeEncryptionOptions: 64 | Enabled: true # Enable node-to-node encryption 65 | DomainEndpointOptions: 66 | EnforceHTTPS: true # Enable HTTPS enforcement 67 | AccessPolicies: 68 | Version: '2012-10-17' 69 | Statement: 70 | - Effect: Allow 71 | Principal: 72 | AWS: '*' 73 | Action: 'es:*' 74 | Resource: '*' 75 | 76 | Parameters: 77 | EnvironmentName: 78 | Description: The name of the environment (e.g., dev, prod) 79 | Type: String 80 | EnvironmentNameCamelCase: 81 | Description: The name of the environment (e.g., dev, prod) in CamelCase for supporting multiple environments in same account without conflicts. 82 | Type: String 83 | VendorSuffix: 84 | Description: Unique vendor suffix for the bucket. 85 | Type: String 86 | VendorSuffixCamelCase: 87 | Description: Unique vendor suffix in CamelCase for supporting multiple vendors under the same account. 88 | Type: String 89 | UserPoolId: 90 | Description: The ID of the Cognito User Pool 91 | Type: String 92 | IdentityPoolId: 93 | Description: The ID of the Cognito Identity Pool 94 | Type: String 95 | MasterPassword: 96 | Type: String 97 | Description: Master user password for OpenSearch fine-grained access control 98 | NoEcho: true 99 | MinLength: 8 100 | MaxLength: 32 101 | AllowedPattern: '[A-Za-z0-9@!?*#_+=^.-]+' 102 | ConstraintDescription: Password must be 8-32 characters long and can include letters, numbers, and special characters. 103 | 104 | Outputs: 105 | OpenSearchEndpoint: 106 | Description: Endpoint of the OpenSearch domain 107 | Value: !GetAtt OpenSearchDomain.DomainEndpoint -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/login/index.js: -------------------------------------------------------------------------------- 1 | const crypto = require("crypto"); 2 | const AWS = require("aws-sdk"); 3 | const logger = require("./logger"); 4 | 5 | const loginGovBaseUrl = process.env.LOGIN_GOV_BASE_URL; 6 | const loginGovClientId = process.env.LOGIN_GOV_CLIENT_ID; 7 | const loginGovRedirectUri = process.env.LOGIN_GOV_REDIRECT_URI; 8 | const stateTableName = process.env.STATE_TABLE_NAME; 9 | const redirectTableName = process.env.REDIRECT_TABLE_NAME; // New table for storing redirect URLs 10 | 11 | /** 12 | * Generates a random string for the PKCE code verifier. 13 | */ 14 | function generateCodeVerifier() { 15 | return crypto.randomBytes(32).toString("hex"); 16 | } 17 | 18 | function generateNonce() { 19 | return crypto.randomBytes(16).toString("hex"); // Generate a random 16-byte nonce 20 | } 21 | 22 | /** 23 | * Generates a code challenge from the code verifier using SHA256. 24 | */ 25 | function generateCodeChallenge(codeVerifier) { 26 | return crypto 27 | .createHash("sha256") 28 | .update(codeVerifier) 29 | .digest("base64") 30 | .replace(/\+/g, "-") 31 | .replace(/\//g, "_") 32 | .replace(/=/g, ""); 33 | } 34 | 35 | exports.handler = async (event) => { 36 | try { 37 | 38 | // Parse the query string to extract the original redirect URL 39 | const { queryStringParameters } = event; 40 | const originalRedirectUri = queryStringParameters?.redirect_uri; 41 | const state = queryStringParameters?.state; 42 | 43 | logger({ state }); 44 | 45 | if (!state || state == '') { 46 | console.error("Missing state in query string"); 47 | return { 48 | statusCode: 400, 49 | body: JSON.stringify({ error: "Missing state in query string" }), 50 | }; 51 | } 52 | 53 | if (!originalRedirectUri || originalRedirectUri == '') { 54 | console.error("Missing redirect_uri in query string"); 55 | return { 56 | statusCode: 400, 57 | body: JSON.stringify({ error: "Missing redirect_uri in query string" }), 58 | }; 59 | } 60 | 61 | 62 | // Generate a nonce 63 | const nonce = generateNonce(); 64 | 65 | // const state = crypto.randomBytes(16).toString("hex"); // Generate a random state 66 | const codeVerifier = generateCodeVerifier(); 67 | const codeChallenge = generateCodeChallenge(codeVerifier); 68 | 69 | // Store the state and code verifier in DynamoDB 70 | const dynamodb = new AWS.DynamoDB.DocumentClient(); 71 | 72 | await dynamodb 73 | .put({ 74 | TableName: stateTableName, 75 | Item: { 76 | state, 77 | codeVerifier, 78 | nonce, 79 | ttl: Math.floor(Date.now() / 1000) + 300, // 5-minute TTL 80 | }, 81 | }) 82 | .promise(); 83 | 84 | 85 | // Store the original redirect URL in the redirect table 86 | await dynamodb 87 | .put({ 88 | TableName: redirectTableName, 89 | Item: { 90 | state, 91 | redirectUri: originalRedirectUri, 92 | ttl: Math.floor(Date.now() / 1000) + 300, // 5-minute TTL 93 | }, 94 | }) 95 | .promise(); 96 | 97 | // Build the Login.gov authorization URL 98 | const redirectUrl = `${loginGovBaseUrl}?client_id=${loginGovClientId}&response_type=code&redirect_uri=${encodeURIComponent( 99 | loginGovRedirectUri 100 | )}&scope=openid+email+verified_at+x509_presented+x509_subject&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256&acr_values=${encodeURIComponent('urn:acr.login.gov:auth-only urn:gov:gsa:ac:classes:sp:PasswordProtectedTransport:duo')}&nonce=${nonce}`; 101 | 102 | // Return a redirect response 103 | return { 104 | statusCode: 302, 105 | headers: { 106 | Location: redirectUrl, 107 | }, 108 | }; 109 | } catch (error) { 110 | console.error("Error in Login Lambda:", error); 111 | return { 112 | statusCode: 500, 113 | body: JSON.stringify({ error: "Internal Server Error" }), 114 | }; 115 | } 116 | }; -------------------------------------------------------------------------------- /hooks/site_write/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "goclassifieds/lib/entity" 10 | "goclassifieds/lib/repo" 11 | 12 | "golang.org/x/oauth2" 13 | "github.com/aws/aws-lambda-go/lambda" // Used for defining the Lambda handler 14 | lambdaSvc "github.com/aws/aws-sdk-go/service/lambda" // AWS SDK for interacting with Lambda 15 | "github.com/aws/aws-sdk-go/aws/session" // AWS session for SDK service creation 16 | ) 17 | 18 | func handler(ctx context.Context, event map[string]interface{}) (entity.AfterSaveExecEntityResponse, error) { 19 | 20 | // Navigate through the map to get the 'repoName' 21 | input, ok := event["Input"].(map[string]interface{}) 22 | if !ok { 23 | return entity.AfterSaveExecEntityResponse{}, fmt.Errorf("failed to parse 'Input' field from event") 24 | } 25 | 26 | ent, ok := input["entity"].(map[string]interface{}) 27 | if !ok { 28 | return entity.AfterSaveExecEntityResponse{}, fmt.Errorf("failed to parse 'entity' field from Input") 29 | } 30 | 31 | repoName, ok := ent["repoName"].(string) 32 | if !ok { 33 | return entity.AfterSaveExecEntityResponse{}, fmt.Errorf("failed to parse 'repoName' from entity") 34 | } 35 | 36 | // Log or use the 'repoName' 37 | log.Printf("Repository Name: %s", repoName) 38 | 39 | owner := "rollthecloudinc" // Hardcoded owner (to be replaced by payload.Entity in later iterations) 40 | // repoName := "site12" 41 | repoBuildName := repoName + "-build" // Hardcoded repo name (to be replaced by payload.Entity) 42 | 43 | // Validate environment variables 44 | stage := os.Getenv("STAGE") 45 | if stage == "" { 46 | return entity.AfterSaveExecEntityResponse{}, fmt.Errorf("environment variable STAGE is missing") 47 | } 48 | 49 | githubAppID := os.Getenv("GITHUB_APP_ID") 50 | if githubAppID == "" { 51 | return entity.AfterSaveExecEntityResponse{}, fmt.Errorf("environment variable GITHUB_APP_ID is missing") 52 | } 53 | 54 | // Create an AWS session for Lambda client 55 | sess := session.Must(session.NewSession()) 56 | lClient := lambdaSvc.New(sess) 57 | 58 | // Load GitHub app PEM file 59 | pemFilePath := fmt.Sprintf("rtc-vertigo-%s.private-key.pem", stage) 60 | pem, err := os.ReadFile(pemFilePath) 61 | if err != nil { 62 | log.Printf("Failed to read PEM file '%s': %v", pemFilePath, err) 63 | return entity.AfterSaveExecEntityResponse{}, fmt.Errorf("failed to load GitHub app PEM file: %w", err) 64 | } 65 | log.Print("GitHub app PEM file loaded successfully.") 66 | 67 | // Generate GitHub Installation Token 68 | getTokenInput := &repo.GetInstallationTokenInput{ 69 | GithubAppPem: pem, 70 | Owner: owner, 71 | GithubAppId: githubAppID, 72 | } 73 | installationToken, err := repo.GetInstallationToken(getTokenInput) 74 | if err != nil { 75 | log.Printf("Error generating GitHub installation token for owner '%s': %v", owner, err) 76 | return entity.AfterSaveExecEntityResponse{}, fmt.Errorf("failed to generate GitHub installation token: %w", err) 77 | } 78 | 79 | // Create OAuth2 HTTP client 80 | srcToken := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: *installationToken.Token}) 81 | httpClient := oauth2.NewClient(ctx, srcToken) 82 | 83 | privateKey, publicKey, err := repo.GenerateSSHKeyPair() 84 | if err != nil { 85 | log.Print("Failed to ssh key pair") 86 | return entity.AfterSaveExecEntityResponse{}, err 87 | } 88 | 89 | log.Printf("ssh private key: %s", privateKey) 90 | log.Printf("ssh public key: %s", publicKey) 91 | 92 | err = repo.CreateGithubDeployKey(context.Background(), httpClient, owner, repoBuildName, "Automated SSH Deploy Key", publicKey, false) 93 | if err != nil { 94 | log.Print("Failed to create deploy deploy") 95 | return entity.AfterSaveExecEntityResponse{}, err 96 | } 97 | 98 | // Now add the private key to the source code repo as a repo secret. 99 | // Step: Create environment secrets 100 | err = repo.CreateGithubRepositorySecret(context.Background(), httpClient, lClient, owner, repoName, "SSH_DEPLOY_KEY", privateKey, stage) 101 | if err != nil { 102 | log.Print("Failed to create SSH_DEPLOY_KEY repository secret") 103 | return entity.AfterSaveExecEntityResponse{}, err 104 | } 105 | 106 | log.Printf("Repository secret %s created successfullly.", "SSH_DEPLOY_KEY") 107 | 108 | return entity.AfterSaveExecEntityResponse{}, nil 109 | } 110 | 111 | func main() { 112 | log.SetFlags(0) // Output logs without timestamps 113 | // Make the handler available for invocation by AWS Lambda 114 | lambda.Start(handler) 115 | } -------------------------------------------------------------------------------- /serverless-splits.yml: -------------------------------------------------------------------------------- 1 | service: goclassifieds-splits-api 2 | frameworkVersion: '3' 3 | plugins: 4 | - serverless-prune-plugin 5 | - serverless-custom-packaging-plugin 6 | custom: 7 | githubToken: ${file(./private.${opt:stage, 'dev'}.json):githubToken} 8 | githubAppId: ${file(./private.${opt:stage, 'dev'}.json):githubAppId} 9 | provider: 10 | name: aws 11 | runtime: provided.al2023 12 | memorySize: 512 13 | timeout: 45 14 | #logs: 15 | #websocket: true 16 | httpApi: 17 | payload: '1.0' 18 | cors: true 19 | package: 20 | individually: true 21 | resources: 22 | Resources: 23 | SplitsRole: 24 | Type: AWS::IAM::Role 25 | Properties: 26 | Path: "/" 27 | RoleName: splits-${opt:region, 'us-east-1'}-lambdaRole 28 | AssumeRolePolicyDocument: 29 | Version: '2012-10-17' 30 | Statement: 31 | - Effect: Allow 32 | Principal: 33 | Service: 34 | - lambda.amazonaws.com 35 | - apigateway.amazonaws.com 36 | Action: sts:AssumeRole 37 | ManagedPolicyArns: 38 | - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole 39 | Policies: 40 | - PolicyName: goclassifieds-splits-api-lambda 41 | PolicyDocument: 42 | Version: '2012-10-17' 43 | Statement: 44 | - Effect: Allow 45 | Action: 46 | - logs:CreateLogGroup 47 | - logs:CreateLogStream 48 | - logs:PutLogEvents 49 | - logs:DescribeLogGroups 50 | - logs:DescribeLogStreams 51 | - logs:GetLogEvents 52 | - logs:FilterLogEvents 53 | Resource: 54 | - 'Fn::Join': 55 | - ':' 56 | - 57 | - 'arn:aws:logs' 58 | - Ref: 'AWS::Region' 59 | - Ref: 'AWS::AccountId' 60 | - 'log-group:/aws/lambda/*:*:*' 61 | - Effect: "Allow" 62 | Action: 63 | - "lambda:InvokeFunction" 64 | Resource: "*" 65 | - Effect: "Allow" 66 | Action: 67 | - "execute-api:Invoke" 68 | - "execute-api:ManageConnections" 69 | Resource: "*" 70 | functions: 71 | ModelApi: 72 | handler: bootstrap 73 | role: SplitsRole 74 | package: 75 | path: bazel-bin/api/model 76 | artifact: .serverless/ModelApi.zip 77 | libs: api/entity 78 | include_globs: 79 | - "**/bootstrap" 80 | - "**/*.json.tmpl" 81 | - "**/*.pem" 82 | environment: 83 | GITHUB_TOKEN: ${self:custom.githubToken} 84 | STAGE: ${opt:stage, 'dev'} 85 | events: 86 | - httpApi: 87 | path: /model/{proxy+} 88 | method: POST 89 | IndexApi: 90 | handler: bootstrap 91 | role: SplitsRole 92 | package: 93 | path: bazel-bin/api/index 94 | artifact: .serverless/IndexApi.zip 95 | libs: api/entity 96 | include_globs: 97 | - "**/bootstrap" 98 | - "**/*.json.tmpl" 99 | - "**/*.pem" 100 | environment: 101 | GITHUB_APP_ID: ${self:custom.githubAppId} 102 | STAGE: ${opt:stage, 'dev'} 103 | events: 104 | - httpApi: 105 | path: /{owner}/{repo}/index/{index} 106 | method: GET 107 | - httpApi: 108 | path: /{owner}/{repo}/search 109 | method: POST 110 | IndexCreateHook: 111 | handler: bootstrap 112 | role: SplitsRole 113 | package: 114 | path: bazel-bin/hooks/index_create 115 | artifact: .serverless/Index_Create.zip 116 | libs: api/entity 117 | include_globs: 118 | - "**/bootstrap" 119 | - "**/*.json.tmpl" 120 | - "**/*.pem" 121 | environment: 122 | GITHUB_APP_ID: ${self:custom.githubAppId} 123 | STAGE: ${opt:stage, 'dev'} 124 | EntityIndexHook: 125 | handler: bootstrap 126 | role: SplitsRole 127 | package: 128 | path: bazel-bin/hooks/entity_index 129 | artifact: .serverless/Entity_Index.zip 130 | libs: api/entity 131 | include_globs: 132 | - "**/bootstrap" 133 | - "**/*.json.tmpl" 134 | - "**/*.pem" 135 | environment: 136 | GITHUB_APP_ID: ${self:custom.githubAppId} 137 | STAGE: ${opt:stage, 'dev'} -------------------------------------------------------------------------------- /cloudform/search_distinct.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Creates a Distinct OpenSearch instance with Cognito authentication and fine-grained access control. 3 | 4 | Resources: 5 | # IAM Role for Cognito and OpenSearch Authentication 6 | DistinctCognitoAuthRole: 7 | Type: AWS::IAM::Role 8 | Properties: 9 | RoleName: DistinctOpenSearchAuthRole 10 | AssumeRolePolicyDocument: 11 | Version: '2012-10-17' 12 | Statement: 13 | - Effect: Allow 14 | Principal: 15 | Federated: "cognito-identity.amazonaws.com" 16 | Action: 'sts:AssumeRoleWithWebIdentity' 17 | Condition: 18 | StringEquals: 19 | 'cognito-identity.amazonaws.com:aud': !Ref CognitoIdentityPoolId 20 | ForAnyValue:StringLike: 21 | 'cognito-identity.amazonaws.com:amr': authenticated 22 | - Effect: Allow 23 | Principal: 24 | Service: "es.amazonaws.com" # Allow OpenSearch Service to assume the role 25 | Action: 'sts:AssumeRole' 26 | Policies: 27 | - PolicyName: DistinctOpenSearchAccessPolicy 28 | PolicyDocument: 29 | Version: '2012-10-17' 30 | Statement: 31 | - Effect: Allow 32 | Action: 33 | - 'es:ESHttp*' 34 | Resource: '*' # Further restriction is recommended based on access control requirements 35 | - Effect: Allow 36 | Action: 37 | - 'cognito-idp:DescribeUserPool' # Permission to describe the Cognito User Pool 38 | - 'cognito-identity:ListIdentityPools' # Permission to list identity pools 39 | - 'cognito-identity:GetIdentityPoolRoles' # Permission to get identity pool roles 40 | - 'cognito-identity:DescribeIdentityPool' # Permission to describe the Cognito Identity Pool 41 | Resource: 42 | - !Sub 'arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${CognitoUserPoolId}' # Restrict to the Cognito User Pool 43 | - !Sub 'arn:aws:cognito-identity:${AWS::Region}:${AWS::AccountId}:identitypool/${CognitoIdentityPoolId}' # Restrict to the Cognito Identity Pool 44 | 45 | # OpenSearch Domain with Cognito Authentication Enabled 46 | DistinctOpenSearchDomain: 47 | Type: AWS::OpenSearchService::Domain 48 | Properties: 49 | DomainName: distinct-opensearch 50 | EngineVersion: OpenSearch_2.5 51 | ClusterConfig: 52 | InstanceType: t3.small.search 53 | InstanceCount: 1 54 | DedicatedMasterEnabled: false 55 | ZoneAwarenessEnabled: false 56 | EBSOptions: 57 | EBSEnabled: true 58 | VolumeType: gp2 59 | VolumeSize: 10 60 | AdvancedSecurityOptions: 61 | Enabled: true 62 | InternalUserDatabaseEnabled: true 63 | MasterUserOptions: 64 | MasterUserName: admin 65 | MasterUserPassword: !Sub '${MasterPassword}' 66 | CognitoOptions: 67 | Enabled: true 68 | IdentityPoolId: !Ref CognitoIdentityPoolId 69 | UserPoolId: !Ref CognitoUserPoolId 70 | RoleArn: !GetAtt DistinctCognitoAuthRole.Arn # Reference the corrected IAM Role here 71 | EncryptionAtRestOptions: 72 | Enabled: true # Enable encryption at rest 73 | NodeToNodeEncryptionOptions: 74 | Enabled: true # Enable node-to-node encryption 75 | DomainEndpointOptions: 76 | EnforceHTTPS: true # Enforce HTTPS 77 | AccessPolicies: 78 | Version: '2012-10-17' 79 | Statement: 80 | - Effect: Allow 81 | Principal: 82 | AWS: '*' 83 | Action: 'es:*' 84 | Resource: '*' 85 | 86 | Parameters: 87 | MasterPassword: 88 | Type: String 89 | Description: Master user password for OpenSearch fine-grained access control 90 | NoEcho: true 91 | MinLength: 8 92 | MaxLength: 32 93 | AllowedPattern: '[A-Za-z0-9@!?*#_+=^.-]+' 94 | ConstraintDescription: Password must be 8-32 characters long and can include letters, numbers, and special characters. 95 | 96 | CognitoUserPoolId: 97 | Type: String 98 | Description: ID of the existing Cognito User Pool 99 | 100 | CognitoIdentityPoolId: 101 | Type: String 102 | Description: ID of the existing Cognito Identity Pool 103 | 104 | Outputs: 105 | DistinctOpenSearchEndpoint: 106 | Description: Endpoint of the Distinct OpenSearch domain 107 | Value: !GetAtt DistinctOpenSearchDomain.Endpoint -------------------------------------------------------------------------------- /api/authorizer3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "goclassifieds/lib/utils" 5 | "log" 6 | "os" 7 | "errors" 8 | 9 | "github.com/MicahParks/keyfunc" 10 | "github.com/aws/aws-lambda-go/events" 11 | "github.com/aws/aws-lambda-go/lambda" 12 | "github.com/golang-jwt/jwt/v4" 13 | ) 14 | 15 | var handler Handler 16 | 17 | type Handler func(req *events.APIGatewayProxyRequest) (events.APIGatewayCustomAuthorizerResponse, error) 18 | 19 | type ActionContext struct { 20 | UserPoolId string 21 | } 22 | 23 | // Authorizer custom api authorizer 24 | func Authorizer(request *events.APIGatewayProxyRequest, ac *ActionContext) (events.APIGatewayCustomAuthorizerResponse, error) { 25 | token1 := request.Headers["authorization"] 26 | 27 | utils.LogUsageForHttpRequest(request) 28 | 29 | log.Printf("%v", request) 30 | log.Printf("token is %s", token1) 31 | 32 | if token1 == "" { 33 | // For now lock it down but eventually needs to support anonymous users. 34 | /*log.Print("unauthorized request pass thru") 35 | return events.APIGatewayCustomAuthorizerResponse{ 36 | PrincipalID: "me", 37 | PolicyDocument: events.APIGatewayCustomAuthorizerPolicy{ 38 | Version: "2012-10-17", 39 | Statement: []events.IAMPolicyStatement{ 40 | { 41 | Action: []string{"execute-api:*"}, 42 | Effect: "Allow", 43 | Resource: []string{"*"}, 44 | // Resource: []string{request.Re}, 45 | }, 46 | }, 47 | }, 48 | }, nil*/ 49 | log.Print("Unauthorized requires token") 50 | return events.APIGatewayCustomAuthorizerResponse{}, errors.New("Unauthorized requires token") 51 | } 52 | 53 | token := token1[7:] 54 | log.Printf("token after is %s", token) 55 | 56 | // jwks, err := keyfunc.Get("https://cognito-idp.us-east-1.amazonaws.com/"+ac.UserPoolId+"/.well-known/jwks.json", keyfunc.Options{}) 57 | 58 | // --------------------- Canva integration for jwks ---------------------- 59 | 60 | appId := "AAGJBJc7pZs" 61 | jwks, err := keyfunc.Get("https://api.canva.com/rest/v1/apps/" + appId + "/jwks", keyfunc.Options{}) 62 | 63 | // --------------------- End Canva integration for jwks ---------------------- 64 | 65 | if err != nil { 66 | log.Print("Unable to fetch keys") 67 | } 68 | 69 | log.Print("fectehd keys") 70 | 71 | // Verify 72 | t, err := jwt.Parse(token, jwks.Keyfunc) 73 | if err != nil || !t.Valid { 74 | log.Print(err) 75 | log.Print("Unauthorized") 76 | return events.APIGatewayCustomAuthorizerResponse{}, err 77 | } 78 | 79 | log.Print("authorized") 80 | 81 | claims := t.Claims.(jwt.MapClaims) 82 | 83 | log.Print("got claims") 84 | 85 | // -------- Canva Integration for Claims Compatibility --------------------------------- 86 | // remap to source of truth when shared between providers like: user id/sub, user name, etc. 87 | // Otherwise overload with claims that can be pulled out once request identified as a specific auth host ie.cognito, canva, etc. 88 | 89 | // log expected claims for canva 90 | log.Printf("Canva userId: %s", claims["userId"]) 91 | log.Printf("Canva designId: %s", claims["brandId"]) 92 | log.Printf("Canva teamId: %s", claims["aud"]) 93 | 94 | // Cognito is the source of truth so map Canva userId to sub which means the same thing. 95 | claims["sub"] = claims["userId"] 96 | 97 | // Canva does not have usernames just optional display names. So for now just reuse the userId. 98 | // We could use the connect api to grab the display name if it exists... but why really... 99 | claims["cognito:username"] = claims["userId"] 100 | 101 | // --------- End Canva Integration for Claims Compatibility ------------------- 102 | 103 | claims["cognito:groups"] = nil //claims["cognito:groups"] 104 | claims["cognito:roles"] = nil //claims["cognito:groups"] 105 | 106 | return events.APIGatewayCustomAuthorizerResponse{ 107 | PrincipalID: "me", 108 | PolicyDocument: events.APIGatewayCustomAuthorizerPolicy{ 109 | Version: "2012-10-17", 110 | Statement: []events.IAMPolicyStatement{ 111 | { 112 | Action: []string{"execute-api:*"}, 113 | Effect: "Allow", 114 | Resource: []string{"*"}, 115 | // Resource: []string{request.Re}, 116 | }, 117 | }, 118 | }, 119 | Context: claims, 120 | }, nil 121 | } 122 | 123 | func InitializeHandler(ac ActionContext) Handler { 124 | return func(req *events.APIGatewayProxyRequest) (events.APIGatewayCustomAuthorizerResponse, error) { 125 | return Authorizer(req, &ac) 126 | } 127 | } 128 | 129 | func init() { 130 | log.Printf("Gin cold start") 131 | actionContext := ActionContext{ 132 | UserPoolId: os.Getenv("USER_POOL_ID"), 133 | } 134 | handler = InitializeHandler(actionContext) 135 | } 136 | 137 | func main() { 138 | log.SetFlags(0) 139 | lambda.Start(handler) 140 | } 141 | --------------------------------------------------------------------------------