From 8f0f787b54020ae6899a61d3c9a7ac27da19cf78 Mon Sep 17 00:00:00 2001 From: bodymindarts Date: Mon, 23 Oct 2023 15:35:33 +0200 Subject: [PATCH 01/10] chore: initial Tiltfile --- .github/workflows/tilt.yml | 14 +++ dev/.env | 0 dev/Tiltfile | 29 ++++-- dev/bin/helpers.sh | 15 ++++ dev/bin/init-onchain.sh | 24 +++++ dev/config/bitcoind/bitcoin.conf | 19 ++++ .../bitcoind/bitcoind_signer_descriptors.json | 1 + dev/config/bria.yml | 8 ++ dev/config/fulcrum/fulcrum.conf | 7 ++ dev/config/fulcrum/tls.cert | 19 ++++ dev/config/fulcrum/tls.key | 28 ++++++ dev/config/otel-agent-config.yaml | 68 ++++++++++++++ dev/docker-compose.deps.yml | 89 +++++++++++++++++++ flake.nix | 1 + typos.toml | 1 + 15 files changed, 318 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/tilt.yml create mode 100644 dev/.env create mode 100644 dev/bin/helpers.sh create mode 100755 dev/bin/init-onchain.sh create mode 100644 dev/config/bitcoind/bitcoin.conf create mode 100644 dev/config/bitcoind/bitcoind_signer_descriptors.json create mode 100644 dev/config/bria.yml create mode 100644 dev/config/fulcrum/fulcrum.conf create mode 100644 dev/config/fulcrum/tls.cert create mode 100644 dev/config/fulcrum/tls.key create mode 100644 dev/config/otel-agent-config.yaml create mode 100644 dev/docker-compose.deps.yml diff --git a/.github/workflows/tilt.yml b/.github/workflows/tilt.yml new file mode 100644 index 0000000000..4f23f5d6e5 --- /dev/null +++ b/.github/workflows/tilt.yml @@ -0,0 +1,14 @@ +name: "Tilt CI" +on: + pull_request: + branches: [ main ] +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: cachix/install-nix-action@v22 + with: + github_access_token: ${{ secrets.GITHUB_TOKEN }} + - name: Tilt CI + run: nix develop -c bash -c "cd dev && tilt ci" diff --git a/dev/.env b/dev/.env new file mode 100644 index 0000000000..e69de29bb2 diff --git a/dev/Tiltfile b/dev/Tiltfile index 5de71ffe41..5872ea51b4 100644 --- a/dev/Tiltfile +++ b/dev/Tiltfile @@ -1,7 +1,26 @@ -api_target = "//core/api:api" +docker_compose("./docker-compose.deps.yml", ".env","galoy-dev") + +groups = { + "bitcoin": [ + "bitcoind", + "bitcoind-signer", + "postgres-bria", + "fulcrum", + "bria", + ], + "tracing": [ + "otel-agent", + ], +} +for service in groups["bitcoin"]: + dc_resource(service, labels = ["bitcoin"]) + local_resource( - "api", - labels = ["frontend"], - cmd = "buck2 build {}".format(api_target), - serve_cmd = "buck2 run {}".format(api_target), + name='init-onchain', + labels = ['bitcoin'], + cmd='bin/init-onchain.sh', + resource_deps = [ + "bitcoind", + "bria", + ] ) diff --git a/dev/bin/helpers.sh b/dev/bin/helpers.sh new file mode 100644 index 0000000000..f5d069497d --- /dev/null +++ b/dev/bin/helpers.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-quickstart}" + +bitcoin_cli() { + docker exec "${COMPOSE_PROJECT_NAME}-bitcoind-1" bitcoin-cli "$@" +} + +bitcoin_signer_cli() { + docker exec "${COMPOSE_PROJECT_NAME}-bitcoind-signer-1" bitcoin-cli "$@" +} + +bria_cli() { + docker exec "${COMPOSE_PROJECT_NAME}-bria-1" bria "$@" +} diff --git a/dev/bin/init-onchain.sh b/dev/bin/init-onchain.sh new file mode 100755 index 0000000000..cfe1d99d67 --- /dev/null +++ b/dev/bin/init-onchain.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -e + +DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" +source "${DIR}/helpers.sh" + +echo "Seeding some regtest blocks..." + +bitcoin_cli createwallet "outside" || true +bitcoin_cli -generate 200 > /dev/null 2>&1 + +bitcoin_signer_cli createwallet "dev" || true +bitcoin_signer_cli -rpcwallet=dev importdescriptors "$(cat ./config/bitcoind/bitcoind_signer_descriptors.json)" + +echo "Checking that bria is running..." + +for _ in {1..20}; do + bria_cli wallet-balance -w dev-wallet && break + sleep 1 +done +bria_cli wallet-balance -w dev-wallet || exit 1 + +echo "DONE" diff --git a/dev/config/bitcoind/bitcoin.conf b/dev/config/bitcoind/bitcoin.conf new file mode 100644 index 0000000000..d04fb1b417 --- /dev/null +++ b/dev/config/bitcoind/bitcoin.conf @@ -0,0 +1,19 @@ +rpcuser=rpcuser +rpcpassword=rpcpassword +debug=mempool +debug=rpc +server=1 +txindex=1 +printtoconsole=1 +zmqpubrawtx=tcp://0.0.0.0:28333 +zmqpubrawblock=tcp://0.0.0.0:28332 +blockfilterindex=1 +bind=0.0.0.0 +fallbackfee=0.0002 +rpcallowip=0.0.0.0/0 +regtest=1 +[regtest] +bind=0.0.0.0 +fallbackfee=0.0002 +rpcallowip=0.0.0.0/0 +rpcbind=0.0.0.0 diff --git a/dev/config/bitcoind/bitcoind_signer_descriptors.json b/dev/config/bitcoind/bitcoind_signer_descriptors.json new file mode 100644 index 0000000000..fc0a17989b --- /dev/null +++ b/dev/config/bitcoind/bitcoind_signer_descriptors.json @@ -0,0 +1 @@ +[{"active":true,"desc":"wpkh([6f2fa1b2/84'/0'/0']tprv8gXB88g1VCScmqPp8WcetpJPRxix24fRJJ6FniYCcCUEFMREDrCfwd34zWXPiY5MW2xp8e1Z6EeBrh74zMSgfQQmTorWtE1zyBtv7yxdcoa/0/*)#88k4937c","timestamp":0},{"active":true,"desc":"wpkh([6f2fa1b2/84'/0'/0']tprv8gXB88g1VCScmqPp8WcetpJPRxix24fRJJ6FniYCcCUEFMREDrCfwd34zWXPiY5MW2xp8e1Z6EeBrh74zMSgfQQmTorWtE1zyBtv7yxdcoa/1/*)#knn5cywq","internal":true,"timestamp":0}] diff --git a/dev/config/bria.yml b/dev/config/bria.yml new file mode 100644 index 0000000000..25c27d310a --- /dev/null +++ b/dev/config/bria.yml @@ -0,0 +1,8 @@ +app: + blockchain: + network: regtest + electrum_url: fulcrum:50001 +tracing: + host: "otel-agent" + port: 4318 + service_name: "bria-dev" diff --git a/dev/config/fulcrum/fulcrum.conf b/dev/config/fulcrum/fulcrum.conf new file mode 100644 index 0000000000..8697258865 --- /dev/null +++ b/dev/config/fulcrum/fulcrum.conf @@ -0,0 +1,7 @@ +bitcoind = bitcoind:18443 +rpcuser = rpcuser +rpcpassword = rpcpassword +peering = false +announce = false +tcp = 0.0.0.0:50001 +ssl = 0.0.0.0:50002 diff --git a/dev/config/fulcrum/tls.cert b/dev/config/fulcrum/tls.cert new file mode 100644 index 0000000000..82f5259186 --- /dev/null +++ b/dev/config/fulcrum/tls.cert @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBTCCAe2gAwIBAgIUKCSR4otDtA4+ow5z5zL9zc1CI10wDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UECgwHRnVsY3J1bTAeFw0yMjExMjkxMDUzNDFaFw0zMjExMjYx +MDUzNDFaMBIxEDAOBgNVBAoMB0Z1bGNydW0wggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDasNdsbz47BDEjzKLeG4RxOqMikrBY6fQd0+8pt8Fh/qSaF2lG +NpLbADDcszyddb0GD5tYQe7YBIyrexp9Q1gvejj1zuagXwlSYfrSkiDB3nBYreSl +wq4i87VjCMX5Fr270S3mHfptg5zbPgQvvTvnD0Y7Ur9lriNydWp9Qz6TLWWYVhui +yxLrtsxv6hRDndlkIxDbZt9kr6yzLroBtXFEFSCeROb1sE2ouVXC89eAlcpvWZF6 +MMnlAkECRS5m3QtKyYtTQ0FTGBrAN2xUnirR9z+wpAOu3S/AjEt4AC1ome75tiJv +rtCIaf438QhNerb/sQmyHXIpjw6KZxADKS3RAgMBAAGjUzBRMB0GA1UdDgQWBBS0 +xHEskJjxCg8F1YYT0NkzBVFsTDAfBgNVHSMEGDAWgBS0xHEskJjxCg8F1YYT0Nkz +BVFsTDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCwxhxRCfa1 +DXfFP+BM9OwDldNC9B66d/6hueaY26YJxcLvuKcAm+0MwciNFnWhb9yiY2wnWaZ6 +zA6VKuxCrVU7egg5gG2+lZYjAiQOkFtVNHGEIjVTow4GJAkJGjH/B9J2teHKR1a6 +XDJ5xKLAIM7hnKVjIM7cA/Y3Q/AsR/uzp4GOccqHX3g/AKKLFahLCkVv5CeIrl+y +kPv/3jXbP71iJPhG2j74EVPLeQ0qKAb03vz2Y2HaSBripGQEwnx7MoQnbRhYx00Y ++LG1WGYeNOEXfT8trewl+gfsoLenlDTLukIfjjvQ5dRDA9pD1ij+1+jz/XCyQ7w5 +LzhYnJBwIGpt +-----END CERTIFICATE----- diff --git a/dev/config/fulcrum/tls.key b/dev/config/fulcrum/tls.key new file mode 100644 index 0000000000..d2d9c6affd --- /dev/null +++ b/dev/config/fulcrum/tls.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDasNdsbz47BDEj +zKLeG4RxOqMikrBY6fQd0+8pt8Fh/qSaF2lGNpLbADDcszyddb0GD5tYQe7YBIyr +exp9Q1gvejj1zuagXwlSYfrSkiDB3nBYreSlwq4i87VjCMX5Fr270S3mHfptg5zb +PgQvvTvnD0Y7Ur9lriNydWp9Qz6TLWWYVhuiyxLrtsxv6hRDndlkIxDbZt9kr6yz +LroBtXFEFSCeROb1sE2ouVXC89eAlcpvWZF6MMnlAkECRS5m3QtKyYtTQ0FTGBrA +N2xUnirR9z+wpAOu3S/AjEt4AC1ome75tiJvrtCIaf438QhNerb/sQmyHXIpjw6K +ZxADKS3RAgMBAAECggEBAJB5g9f/Jf2XJ1+fMXnKdBTZyCxu/FHBPaT2/mlxHDDB +Kb0i+kmIhDxqQ92KC+F6hwYaCWalKEVNvCENktJuSzQSas75gSMxpdbnI+043Ks3 +aMEtIIDhvGka2Bo1EZUdkPsjlk8QPwFakaXFsr5XJdvgG2XVpNxnzsMvDsryQcS4 +X+foFXRFPWjTvof9v68WyeIUWVKumVA/285TmS6jMqsYkDHKxg25CiBr95WJKsSo ++o1sBmj9DvJXGxwo47MGX65dZxU4PVTBA9ABq3kA9SnOpzr0Tz27eJl8Er6zOENy +9JmijNLDTuPRwxqDMjW/He5NHz13vnXf5FasGWhfuIECgYEA+isl7jPFDAybPH1b +dL0d8NEwl8CloZ9a0AmsQZ8BGi6gn/uQAtNfZk2Q9vFMulBmppMnYTDrYh7KwZ8u +UjiSg3QIKZgixtZQelDxAv5G8VpR20DeQwn4pxBgvo0WeGKlxV2WdbouwhW1OBoj +fRnTHfzxCkqsS6B+mff8awsKfv8CgYEA38naezhfm4G54wkYUnD8cx4GvuGRHHoR +DcXMyPm2+zub4sYeVDCzh6eoIkNc8TW+YEZNsiYOxYfuYiKLmlw1m2TeZKXwf7Vc +9OkX5Is+7ARlJlG27xU4OLfOMrIdaibPUFLDTWknTryu3hLKoWGHsbfB/7hJoUBq +7np6odIhIy8CgYBgzuu5txzsocA880G99iUs+La2Mt2lorYQxX18U1yo1hxgbIf5 +H/TN8PxFI3GOs+hVU6KskLK14LaToNbbCHtiFTfHaNEkYvD3tHTftKidY1dD4mFg +Te6EL+lQM+bbJUmDC8lau3a2spHTKQJMuf6hny1jzyvHhE8ZpAZxIe+o3wKBgQCt +y0UxjIYzbnu+JcINxFFJ1XfHpcXcJ7A20fZH3iaM1a4qX1IOcAP8xed9NHNpoCNJ +AVA1mfi+zuICtHFWX2YCWEOeRoCPrXmTebJkev9Sq9ch+zPmWMRgx94K4OiXF6zI +5H1oENHGBCKJtG5JBkUrb1nqDmSgJjblHZk/MsEpYQKBgBoI4QpQ0yhn812H0BJV +T1t0SM9+bkJC40ogme0R5/gUz8aDe+A2WHN/HH+q6QKLvkLxBCH8y7RufoKSN2XB +IqtTRZQX/yQyY/AZB7Rse29fmomDGa3/xj1LK33zhYqwMgDmisymu3XER6EwGRV6 +RTv9SXp8+92eNOwSJ63hEZV9 +-----END PRIVATE KEY----- diff --git a/dev/config/otel-agent-config.yaml b/dev/config/otel-agent-config.yaml new file mode 100644 index 0000000000..ed63c32977 --- /dev/null +++ b/dev/config/otel-agent-config.yaml @@ -0,0 +1,68 @@ +receivers: + jaeger: + protocols: + thrift_compact: # on port 6831 + thrift_binary: # on port 6832 + otlp: + protocols: + http: # on port 4318 + +processors: + filter/ottl: + error_mode: ignore + traces: + span: + - 'resource.attributes["service.name"] == "stablesats-dev"' + - 'resource.attributes["service.name"] == "bria-dev"' + - 'name == "grpc.lnrpc.State/GetState"' + - 'name == "app.prices.getCurrentSatPrice"' + - 'name == "app.prices.getCurrentUsdCentPrice"' + - 'name == "grpc.PriceFeed/GetPrice"' + - 'name == "grpc.PriceFeed/ListCurrencies"' + - 'name == "services.cache.local.set"' + - 'name == "services.cache.local.get"' + - 'name == "app.prices.listCurrencies"' + - 'name == "services.cache.local.getOrSet"' + batch: + attributes: + actions: + - key: graphql.variables.input.code + action: update + value: "" + - key: code.function.params.code + action: update + value: "" + - key: code.function.params.authToken + action: update + value: "" + - key: code.function.params.totpCode + action: update + value: "" + - key: graphql.variables.input.totpCode + action: update + value: "" + - key: graphql.variables.input.authToken + action: update + value: "" + +exporters: + logging: + loglevel: debug + otlp: + endpoint: "api.honeycomb.io:443" + headers: + "x-honeycomb-team": ${HONEYCOMB_API_KEY} + "x-honeycomb-dataset": ${HONEYCOMB_DATASET} + +extensions: + health_check: + pprof: + zpages: + +service: + extensions: [health_check, pprof, zpages] + pipelines: + traces: + receivers: [jaeger, otlp] + processors: [filter/ottl, attributes, batch] + exporters: [otlp, logging] diff --git a/dev/docker-compose.deps.yml b/dev/docker-compose.deps.yml new file mode 100644 index 0000000000..9d1f8e58c5 --- /dev/null +++ b/dev/docker-compose.deps.yml @@ -0,0 +1,89 @@ +--- +version: "3" +services: + mongodb: + image: mongo:7.0.2 + ports: + - "27017:27017" + environment: + - MONGO_INITDB_DATABASE=galoy + bria: + image: us.gcr.io/galoy-org/bria:latest + ports: + - "2743:2743" + - "2742:2742" + environment: + - PG_CON=postgres://user:password@postgres-bria:5432/pg + - BITCOIND_SIGNER_ENDPOINT=https://bitcoind-signer:18443 + command: + - bria + - daemon + - --config + - /bria.yml + - dev + - -x + - tpubDDDDGYiFda8HfJRc2AHFJDxVzzEtBPrKsbh35EaW2UGd5qfzrF2G87ewAgeeRyHEz4iB3kvhAYW1sH6dpLepTkFUzAktumBN8AXeXWE9nd1 + - -d + - m/84h/0h/0h + volumes: + - ${HOST_PROJECT_PATH:-.}/config/bria.yml:/bria.yml + depends_on: + bitcoind-signer: + condition: service_started + otel-agent: + condition: service_started + fulcrum: + condition: service_started + postgres-bria: + condition: service_healthy + postgres-bria: + image: postgres:14.1 + environment: + - POSTGRES_USER=user + - POSTGRES_PASSWORD=password + - POSTGRES_DB=pg + healthcheck: + test: ["CMD-SHELL", "pg_isready"] + interval: 5s + timeout: 30s + retries: 5 + fulcrum: + image: cculianu/fulcrum:latest + ports: + - "50001:50001" + depends_on: [bitcoind] + volumes: + - ${HOST_PROJECT_PATH:-.}/config/fulcrum/fulcrum.conf:/fulcrum.conf + - ${HOST_PROJECT_PATH:-.}/config/fulcrum/tls.key:/tls.key + - ${HOST_PROJECT_PATH:-.}/config/fulcrum/tls.cert:/tls.cert + environment: + - DATA_DIR=/db + - SSL_CERTFILE=/tls.cert + - SSL_KEYFILE=/tls.key + command: ["Fulcrum", "/fulcrum.conf"] + bitcoind: + image: lncm/bitcoind:v24.0.1 + ports: + - "18443:18443" + volumes: + - ${HOST_PROJECT_PATH:-.}/config/bitcoind/bitcoin.conf:/data/.bitcoin/bitcoin.conf + bitcoind-signer: + image: lncm/bitcoind:v24.0.1 + ports: [] + volumes: + - ${HOST_PROJECT_PATH:-.}/config/bitcoind/bitcoin.conf:/data/.bitcoin/bitcoin.conf + depends_on: [bitcoind] + entrypoint: ["/bin/sh", "-c"] + command: + - | + bitcoind -connect=bitcoind:18444 + otel-agent: + ports: + - "4318:4318" #! http receiver + image: otel/opentelemetry-collector-contrib:0.84.0 + command: ["--config=/etc/otel-agent-config.yaml"] + environment: + - HONEYCOMB_DATASET=${HONEYCOMB_DATASET} + - HONEYCOMB_API_KEY=${HONEYCOMB_API_KEY} + volumes: + - ${HOST_PROJECT_PATH:-.}/config/otel-agent-config.yaml:/etc/otel-agent-config.yaml diff --git a/flake.nix b/flake.nix index 57b0df46ad..0a94f7b243 100644 --- a/flake.nix +++ b/flake.nix @@ -168,6 +168,7 @@ devShells.default = mkShell { inherit nativeBuildInputs; BUCK2_VERSION = buck2Version; + COMPOSE_PROJECT_NAME = "galoy-dev"; }; formatter = alejandra; diff --git a/typos.toml b/typos.toml index f342bab6b0..ddf7518014 100644 --- a/typos.toml +++ b/typos.toml @@ -1,4 +1,5 @@ files.extend-exclude = [ + "dev", "core/api/dev", "core/api/src/domain/users/languages.ts", "core/api/src/services/loopd/protos", From 86cb4ded4ff35571b755ad2a004ee7f90667c308 Mon Sep 17 00:00:00 2001 From: bodymindarts Date: Mon, 23 Oct 2023 15:52:49 +0200 Subject: [PATCH 02/10] chore: add lnd1 to Tiltfile --- dev/Tiltfile | 7 +-- dev/config/lnd/lnd.conf | 48 ++++++++++++++++++ dev/config/lnd/regtest/lnd1.admin.macaroon | Bin 0 -> 293 bytes .../lnd/regtest/lnd1.admin.macaroon.base64 | 1 + dev/config/lnd/regtest/lnd1.macaroons.db | Bin 0 -> 20480 bytes dev/config/lnd/regtest/lnd1.pubkey | 1 + dev/config/lnd/regtest/lnd1.wallet.db | Bin 0 -> 303104 bytes dev/config/lnd/tls.cert | 15 ++++++ dev/config/lnd/tls.key | 5 ++ dev/docker-compose.deps.yml | 20 ++++++++ 10 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 dev/config/lnd/lnd.conf create mode 100644 dev/config/lnd/regtest/lnd1.admin.macaroon create mode 100644 dev/config/lnd/regtest/lnd1.admin.macaroon.base64 create mode 100644 dev/config/lnd/regtest/lnd1.macaroons.db create mode 100644 dev/config/lnd/regtest/lnd1.pubkey create mode 100644 dev/config/lnd/regtest/lnd1.wallet.db create mode 100644 dev/config/lnd/tls.cert create mode 100644 dev/config/lnd/tls.key diff --git a/dev/Tiltfile b/dev/Tiltfile index 5872ea51b4..4d8e4751cf 100644 --- a/dev/Tiltfile +++ b/dev/Tiltfile @@ -2,11 +2,12 @@ docker_compose("./docker-compose.deps.yml", ".env","galoy-dev") groups = { "bitcoin": [ - "bitcoind", - "bitcoind-signer", + "lnd1", + "bria", "postgres-bria", "fulcrum", - "bria", + "bitcoind-signer", + "bitcoind", ], "tracing": [ "otel-agent", diff --git a/dev/config/lnd/lnd.conf b/dev/config/lnd/lnd.conf new file mode 100644 index 0000000000..f7d9fd4b6e --- /dev/null +++ b/dev/config/lnd/lnd.conf @@ -0,0 +1,48 @@ +[Application Options] + +accept-keysend=1 +allow-circular-route=1 +debuglevel=error +default-remote-max-htlcs=50 +maxchansize=500000000 +noseedbackup=1 +prometheus.enable=1 +prometheus.listen=0.0.0.0:9092 +restlisten=0.0.0.0:8080 +rpclisten=0.0.0.0:10009 +tlsextradomain=lnd-outside-1 +tlsextradomain=lnd-outside-2 +tlsextradomain=lnd1 +tlsextradomain=lnd2 +gc-canceled-invoices-on-the-fly=true +gc-canceled-invoices-on-startup=true +trickledelay=2000 +historicalsyncinterval=2s +tlscertduration=100800h + +[Bitcoin] + +bitcoin.active=true +bitcoin.defaultchanconfs=0 +bitcoin.node=bitcoind +bitcoin.regtest=true +; bitcoin.minhtlc=1 +bitcoin.minhtlcout=1 + +[Bitcoind] + +bitcoind.rpchost=bitcoind:18443 +bitcoind.rpcpass=rpcpassword +bitcoind.rpcuser=rpcuser +bitcoind.zmqpubrawblock=tcp://bitcoind:28332 +bitcoind.zmqpubrawtx=tcp://bitcoind:28333 + +[protocol] + +protocol.wumbo-channels=1 + +[routing] + +; Should only be used with regtest. +; Improves sync speed in slow environments. +routing.assumechanvalid=true diff --git a/dev/config/lnd/regtest/lnd1.admin.macaroon b/dev/config/lnd/regtest/lnd1.admin.macaroon new file mode 100644 index 0000000000000000000000000000000000000000..f8faa86840d06bfd884f422d04472c15881b145b GIT binary patch literal 293 zcmZQ#WX{P;Vfw+y%q74c7~7Jz_gMRe;-DjOnO9te7!9PvxY!d@Qi@WGi-lN%)Kaipi_r4}WYq@wBt zT9ga4B{3aM9B5{KT3T{OVrCwOhWtD%;v!tE1*xe;XfBZ8Vk^!}2U?77KLZ1s!is{s j2NG5tcblLefAii``7>H^Y~r_;r2pOgLDJ@FLH%j~PR(CW literal 0 HcmV?d00001 diff --git a/dev/config/lnd/regtest/lnd1.admin.macaroon.base64 b/dev/config/lnd/regtest/lnd1.admin.macaroon.base64 new file mode 100644 index 0000000000..c06b347a06 --- /dev/null +++ b/dev/config/lnd/regtest/lnd1.admin.macaroon.base64 @@ -0,0 +1 @@ +AgEDbG5kAvgBAwoQB1FdhGa9xoewc1LEXmnURRIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgqHDdwGCqx0aQL1/Z3uUfzCpeBhfapGf9s/AZPOVwf6s= \ No newline at end of file diff --git a/dev/config/lnd/regtest/lnd1.macaroons.db b/dev/config/lnd/regtest/lnd1.macaroons.db new file mode 100644 index 0000000000000000000000000000000000000000..3defb531a6c77dbeb35d2ae2a158f8e0586fa466 GIT binary patch literal 20480 zcmeI&ODIH97{Kv6*Laj?vaqliktrgvkcC)j6xmQlC`~kC$g@03O<|!lp&q?8d z2sy6Ohm1%$5GQk6_ORnMqpC*TM@9ev1Q0*~0R#|0009ILKmdXNBVdtR`EC6lE9WOH z+eb5_3m^Z_KVv5dAbTLybY@5fM6jW3Oz(0_59M=K(OE-K4sM!-;ug>=C9dVEWQn# zrYw|H=4{`+O^!5p_TM`jA}4(F{^9!jtLma@@8(f$(0#a}m5HT9#DtUFrtFXqKmY** z5I_I{1Q0*~0R#|0z>I);zh4#c-|PQ7v4E-iUmYT7noUeGmT-t>gi|zi9*}Q!wLYKg z$-d{(1pW9jv=>ObeW`dDdeXP+G98wg#+%?_y1U4_F_*WJaWd|$zP1-`^>-(34eU0# zz0OvB&2P^w-yd^wu<&Ke1px#QKmY**5I_I{1Q0*~0n-BNz5k!;f16mvRQ+%I*SHY@ X1Q0*~0R#|0009ILKmY**{zBjrlV6it literal 0 HcmV?d00001 diff --git a/dev/config/lnd/regtest/lnd1.pubkey b/dev/config/lnd/regtest/lnd1.pubkey new file mode 100644 index 0000000000..a49cc4d770 --- /dev/null +++ b/dev/config/lnd/regtest/lnd1.pubkey @@ -0,0 +1 @@ +03ca1907342d5d37744cb7038375e1867c24a87564c293157c95b2a9d38dcfb4c2 diff --git a/dev/config/lnd/regtest/lnd1.wallet.db b/dev/config/lnd/regtest/lnd1.wallet.db new file mode 100644 index 0000000000000000000000000000000000000000..c73c638dd4a550166d05c698002923a7df58093d GIT binary patch literal 303104 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 Date: Mon, 23 Oct 2023 16:17:23 +0200 Subject: [PATCH 03/10] chore: add core api to Tiltfile --- dev/Tiltfile | 33 +++++++++++++++++++++++++++++++-- dev/docker-compose.deps.yml | 7 +++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/dev/Tiltfile b/dev/Tiltfile index 4d8e4751cf..728b225ec8 100644 --- a/dev/Tiltfile +++ b/dev/Tiltfile @@ -1,6 +1,8 @@ -docker_compose("./docker-compose.deps.yml", ".env","galoy-dev") - groups = { + "core": [ + "mongodb", + "redis", + ], "bitcoin": [ "lnd1", "bria", @@ -13,8 +15,35 @@ groups = { "otel-agent", ], } + +api_target = "//core/api:api" +local_resource( + "api", + labels = ["core"], + cmd = "buck2 build {}".format(api_target), + serve_cmd = "buck2 run {}".format(api_target), + allow_parallel = True, + readiness_probe = probe( + period_secs = 5, + http_get = http_get_action( + path = "healthz", + port = 4012, + ), + ), + resource_deps = [ + "init-onchain", + "lnd1", + ] +) + +docker_compose("./docker-compose.deps.yml", project_name = "galoy-dev") + for service in groups["bitcoin"]: dc_resource(service, labels = ["bitcoin"]) +for service in groups["tracing"]: + dc_resource(service, labels = ["tracing"]) +for service in groups["core"]: + dc_resource(service, labels = ["core"]) local_resource( name='init-onchain', diff --git a/dev/docker-compose.deps.yml b/dev/docker-compose.deps.yml index 6fdea328e4..c644904b4a 100644 --- a/dev/docker-compose.deps.yml +++ b/dev/docker-compose.deps.yml @@ -1,6 +1,13 @@ --- version: "3" services: + redis: + image: redis:7.0.8 + ports: + - "6379:6379" + environment: + - ALLOW_EMPTY_PASSWORD=yes + - REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL mongodb: image: mongo:7.0.2 ports: From cb32262956722d37dfc394e0dbb8e544cb23564d Mon Sep 17 00:00:00 2001 From: bodymindarts Date: Tue, 24 Oct 2023 14:54:52 +0200 Subject: [PATCH 04/10] chore: add serve_env to api --- core/api/src/config/env.ts | 2 +- dev/Tiltfile | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/core/api/src/config/env.ts b/core/api/src/config/env.ts index 40ce0fc189..ed74c9d5a6 100644 --- a/core/api/src/config/env.ts +++ b/core/api/src/config/env.ts @@ -47,7 +47,7 @@ export const env = createEnv({ KRATOS_CALLBACK_API_KEY: z.string().min(1), BRIA_HOST: z.string().min(1), - BRIA_PORT: z.number().min(1).or(z.string()).pipe(z.coerce.number()), + BRIA_PORT: z.number().min(1).or(z.string()).pipe(z.coerce.number()).default(2742), BRIA_API_KEY: z.string().min(1), GEETEST_ID: z.string().min(1).optional(), diff --git a/dev/Tiltfile b/dev/Tiltfile index 728b225ec8..e5641b8066 100644 --- a/dev/Tiltfile +++ b/dev/Tiltfile @@ -22,6 +22,27 @@ local_resource( labels = ["core"], cmd = "buck2 build {}".format(api_target), serve_cmd = "buck2 run {}".format(api_target), + serve_env = { + "HELMREVISION": "dev", + "NETWORK": "regtest", + "OATHKEEPER_DECISION_ENDPOINT": "http://localhost:4456", + "TWILIO_ACCOUNT_SID": "AC_twilio_id", + "TWILIO_AUTH_TOKEN": "AC_twilio_auth_token", + "TWILIO_VERIFY_SERVICE_ID": "VA_twilio_service", + "KRATOS_PG_CON": "postgres://dbuser:secret@localhost:5433/default?sslmode=disable", + "KRATOS_PUBLIC_API": "http://localhost:4433", + "KRATOS_ADMIN_API": "http://localhost:4434", + "KRATOS_MASTER_USER_PASSWORD": "passwordHardtoFindWithNumber123", + "KRATOS_CALLBACK_API_KEY": "The-Value-of-My-Key", + "BRIA_HOST": "localhost", + "BRIA_API_KEY": "bria_dev_000000000000000000000", + "MONGODB_CON": "mongodb://localhost:27017/galoy", + "REDIS_MASTER_NAME": "mymaster", + "REDIS_PASSWORD": "", + "REDIS_0_DNS": "localhost", + "REDIS_0_PORT": "6379", + "REDIS_TYPE": "standalone", + }, allow_parallel = True, readiness_probe = probe( period_secs = 5, From 6f5ae95b8d6e78f670fd6ac5b5464ad4fbd8d10f Mon Sep 17 00:00:00 2001 From: bodymindarts Date: Wed, 25 Oct 2023 05:54:08 +0200 Subject: [PATCH 05/10] chore: add auth deps to Tiltfile --- dev/Tiltfile | 11 + dev/config/ory/body.jsonnet | 8 + .../email_no_password_v0.identity.schema.json | 33 +++ dev/config/ory/hydra.yml | 22 ++ dev/config/ory/jwks.json | 18 ++ dev/config/ory/kratos.yml | 205 ++++++++++++++++++ dev/config/ory/oathkeeper.yml | 88 ++++++++ dev/config/ory/oathkeeper_rules.yaml | 105 +++++++++ ..._email_no_password_v0.identity.schema.json | 45 ++++ .../phone_no_password_v0.identity.schema.json | 32 +++ ..._password_deviceid_v0.identity.schema.json | 25 +++ dev/docker-compose.deps.yml | 76 +++++++ 12 files changed, 668 insertions(+) create mode 100644 dev/config/ory/body.jsonnet create mode 100644 dev/config/ory/email_no_password_v0.identity.schema.json create mode 100644 dev/config/ory/hydra.yml create mode 100644 dev/config/ory/jwks.json create mode 100644 dev/config/ory/kratos.yml create mode 100644 dev/config/ory/oathkeeper.yml create mode 100644 dev/config/ory/oathkeeper_rules.yaml create mode 100644 dev/config/ory/phone_email_no_password_v0.identity.schema.json create mode 100644 dev/config/ory/phone_no_password_v0.identity.schema.json create mode 100644 dev/config/ory/username_password_deviceid_v0.identity.schema.json diff --git a/dev/Tiltfile b/dev/Tiltfile index e5641b8066..f183af2430 100644 --- a/dev/Tiltfile +++ b/dev/Tiltfile @@ -1,5 +1,14 @@ groups = { + "auth": [ + "oathkeeper", + "hydra", + "hydra-migrate", + "hydra-pg", + "kratos", + "kratos-pg", + ], "core": [ + "apollo-router", "mongodb", "redis", ], @@ -65,6 +74,8 @@ for service in groups["tracing"]: dc_resource(service, labels = ["tracing"]) for service in groups["core"]: dc_resource(service, labels = ["core"]) +for service in groups["auth"]: + dc_resource(service, labels = ["auth"]) local_resource( name='init-onchain', diff --git a/dev/config/ory/body.jsonnet b/dev/config/ory/body.jsonnet new file mode 100644 index 0000000000..2d3090b804 --- /dev/null +++ b/dev/config/ory/body.jsonnet @@ -0,0 +1,8 @@ +function(ctx) { + identity_id: if std.objectHas(ctx, "identity") then ctx.identity.id else null, + phone: if std.objectHas(ctx.identity.traits, "phone") then ctx.identity.traits.phone else null, + transient_payload: if std.objectHas(ctx.flow, "transient_payload") then ctx.flow.transient_payload else null, + schema_id: ctx.identity.schema_id, + flow_id: ctx.flow.id, + flow_type: ctx.flow.type +} diff --git a/dev/config/ory/email_no_password_v0.identity.schema.json b/dev/config/ory/email_no_password_v0.identity.schema.json new file mode 100644 index 0000000000..6b72f9c8b6 --- /dev/null +++ b/dev/config/ory/email_no_password_v0.identity.schema.json @@ -0,0 +1,33 @@ +{ + "$id": "http://mydomain.com/schemas/v2/customer.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "A email user", + "type": "object", + "properties": { + "traits": { + "type": "object", + "properties": { + "email": { + "title": "E-Mail", + "type": "string", + "format": "email", + "ory.sh/kratos": { + "credentials": { + "password": { + "identifier": true + } + }, + "verification": { + "via": "email" + }, + "recovery": { + "via": "email" + } + } + } + }, + "required": ["email"], + "additionalProperties": false + } + } +} diff --git a/dev/config/ory/hydra.yml b/dev/config/ory/hydra.yml new file mode 100644 index 0000000000..8d69cc1d24 --- /dev/null +++ b/dev/config/ory/hydra.yml @@ -0,0 +1,22 @@ +serve: + cookies: + same_site_mode: Lax + +urls: + self: + issuer: http://127.0.0.1:4444 + consent: http://127.0.0.1:3000/consent + login: http://127.0.0.1:3000/login + logout: http://127.0.0.1:3000/logout + +secrets: + system: + - youReallyNeedToChangeThis + +oidc: + subject_identifiers: + supported_types: + - pairwise + - public + pairwise: + salt: youReallyNeedToChangeThis diff --git a/dev/config/ory/jwks.json b/dev/config/ory/jwks.json new file mode 100644 index 0000000000..9a92888331 --- /dev/null +++ b/dev/config/ory/jwks.json @@ -0,0 +1,18 @@ +{ + "keys": [ + { + "use": "sig", + "kty": "RSA", + "kid": "1b97b221-ca08-4eb2-9d09-a5770fcceb37", + "alg": "RS256", + "n": "qlvfGDcdL8kM9L6svAi4DrR8XbIfznVilo0jJVHSzCMe6gW5H_RE-yYg7bWc6t2DRRP7UUcggI4c3m3o85DeWrd-pCWQaKxhJAvMrvzbBqk8Eh9HynwG_5garUf3CWEQJWrl7UAJgBThua9QJdN6RONfTlUFlahju4difLfAIYpJyRNeGZ8oAQSiPflQ6a_Ldl6oNP5KEKB526Bx9wG0ri_m2ZB-6gkenhLwjQ2Pc8dAsb-Y1FZnNxswfnW2VPMAun7aNZKGcUXBXim3uM-H4iLvBKSG7DokfA0icqf4xS4kYkCgoo17D-yU564IuckwUGjB6eziXhBlIj-O3dS4sQ", + "e": "AQAB", + "d": "g-fOvbku6CkL1xUsOgumN_yXrxSUfA933oo5hFPunBE1ho4X4R4Jq0zCgkv0zAXn9jyagzxHdkKTJ8aCcNTi1eruK7rKLcILFRXtzjAKhwXSdVWzCJ0v7gef4Duwk5qMHey_SwwiTwHcJSIRNLiiG2TSDpzWTiKr1i_lmhvMsCpXv1331F1Yp0jVDt098Gj5E5Ppqrp2zQay94fTj07sLbRgZPmVT-rTQwjLwRC_PvWWUpxIGGW9PHjy6640t7JrUOLtBQphXrgitkQ0TRxYuot16BOTmELAStFFOmUJ2NS0DPV0Horu7B6r7Qa_9iH4XelWTlZ-bxSaiz_d9OJ9YQ", + "p": "0lPl2OpzdlbAxI4_UZvcad0MnR2vZUqc84VZyqOiC5LFV557i5-AfBZ1X5m-8CLY4L32N0zJPgsiyH4SKg_iwlYGRZX9gbFGKIEuy2J66LhrMN4N6Gu32YF7NTKjVHIpEBv3esRGeARKw16oT8_Empff1rLw0PYWL9q_xHw4UvM", + "q": "z1obr3gdTPw6R2OtgIGnGXQfz0kCm0gQhUGZDVGBLoMYzQOoiIhncYD539gAXSDMXZHxM7z_jSvbwQ0tV9p6frVw4elxY6En_V3FzBVZO823qOdgwtiz5FENbSvJX3EDpFly6h26K5Iz4Q4S71_wQFIiYnAwrDJnsfNMFYGxxss", + "dp": "xj0BZKQF08IMyrXwBDFNXBJWszepe2UJ7ZUAfrggIZximCTt-TmK1mosksDMcHXHyHwDNzgObYX9cM8yu6Zhah2-p5OpbrXxlHe97UCS7An2Lgb9QNVWYZFi2U8zFoLLJT3W0kVwzenttZrgNOl1Ouhut2PNCocHfm9FVCJoy_U", + "dq": "NYuzcpC2IFdSRXU8LN9OY4hVXpYgEjF98quJ9qKBlZ1NHkQ_lWKENA92d5O0JFh_7fnuK8o2xCH7UdMxTmqhD2-TgwNhwLSxOwCoP5eTv5nUP83gcvC7I866hOK10evBYQOoZUTf-rh6pTeZNC-2PyX2syz9pLovhQirMTKY0hk", + "qi": "NUtIm6QtQKy0j4HFXuXweQTI778pBmGgY5Ob0ZX5brFMKN2TkFLYKgX9pgiw3Lv6o9QLvYi4_YACS2vTMxUyKUIqd5FYaGHU67kwWzFdtuEgM4zcINLPmSo5pYi7n4_eA8AHBUKwz3L-UchdWbCKopYR-BrUPUQdQTLPxnSRC3U" + } + ] +} diff --git a/dev/config/ory/kratos.yml b/dev/config/ory/kratos.yml new file mode 100644 index 0000000000..3af5389f2b --- /dev/null +++ b/dev/config/ory/kratos.yml @@ -0,0 +1,205 @@ +version: v0.13.0 + +dsn: "overrided-by-env" + +serve: + public: + base_url: http://127.0.0.1:4433/ + cors: + enabled: true + allow_credentials: true + allowed_origins: + - http://127.0.0.1:4433 + - http://0.0.0.0:4434 + - http://localhost:3000 + - http://127.0.0.1:3000 + allowed_methods: + - POST + - GET + - PUT + - PATCH + - DELETE + allowed_headers: + - Authorization + - Cookie + - Content-Type + - X-Session-Token + exposed_headers: + - Content-Type + - Set-Cookie + debug: true + + admin: + base_url: http://0.0.0.0:4434/ + +selfservice: + default_browser_return_url: http://localhost:3000/ + allowed_return_urls: + - http://127.0.0.1:4002/ + - http://localhost:3000/ + + methods: + oidc: + enabled: false + webauthn: + enabled: false + totp: + enabled: true + password: + enabled: true + + code: + enabled: true + config: + # Defines how long the verification or the recovery code is valid for (default 1h) + lifespan: 15m + + flows: + error: + ui_url: http://127.0.0.1:4002/error + + settings: + ui_url: http://127.0.0.1:4002/settings + + # very short time frame because this has impact in the totp flow + privileged_session_max_age: 1s + + # to enforce the user to paste his 2fa to deactivate it, this could be set to highest_available + required_aal: aal1 + + after: + profile: + hooks: + - hook: web_hook + config: + # url: http://bats-tests:4002/auth/after_settings_hooks + url: http://invalid-because-we-dont-want-profile-to-be-updated + method: POST + body: file:///home/ory/body.jsonnet + auth: + type: api_key + config: + name: Authorization + value: The-Value-of-My-Key + in: header + + recovery: + enabled: true + ui_url: http://127.0.0.1:4002/recovery + + verification: + use: code + enabled: true + lifespan: 15m + # notify_unknown_recipients: false + + logout: + after: + default_browser_return_url: http://127.0.0.1:4002/login + + login: + ui_url: http://localhost:3000/login + lifespan: 10m + + # this below make phone authentication fails even if there is no email in the schema + # after: + # password: + # hooks: + # - hook: require_verified_address + + registration: + lifespan: 10m + ui_url: http://localhost:3000/register + after: + password: + hooks: + # we are not sure if we need this hook yet. + # this could be used to check if the user is already registered in the backend + # before creating the user in kratos + # otherwise response: parse: false happens after kratos user creation + # + # + # - hook: web_hook + # config: + # url: http://bats-tests:4012/kratos/preregistration + # method: POST + # response: + # parse: true + # body: file:///home/ory/body.jsonnet # TODO: use a base64 encoding instead + # auth: + # type: api_key + # config: + # name: Authorization + # value: The-Value-of-My-Key + # in: header + - hook: web_hook + config: + url: http://bats-tests:4012/kratos/registration + method: POST + response: + parse: false + body: file:///home/ory/body.jsonnet # TODO: use a base64 encoding instead + auth: + type: api_key + config: + name: Authorization + value: The-Value-of-My-Key + in: header + - hook: session + +log: + level: debug + format: json + leak_sensitive_values: true + +secrets: + cookie: + - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE + cipher: + - 32-LONG-SECRET-NOT-SECURE-AT-ALL + +ciphers: + algorithm: xchacha20-poly1305 + +hashers: + algorithm: bcrypt + bcrypt: + cost: 8 + +identity: + default_schema_id: phone_no_password_v0 + schemas: + - id: phone_no_password_v0 + url: file:///home/ory/phone_no_password_v0.identity.schema.json + - id: phone_email_no_password_v0 + url: file:///home/ory/phone_email_no_password_v0.identity.schema.json + - id: email_no_password_v0 + url: file:///home/ory/email_no_password_v0.identity.schema.json + - id: username_password_deviceid_v0 + url: file:///home/ory/username_password_deviceid_v0.identity.schema.json + +courier: + smtp: + connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true + templates: + recovery_code: + valid: + email: + subject: base64://eW91ciBjb2RlCg== + body: + # courier/template/courier/builtin/templates/recovery_code/valid/email.body.plaintext.gotmpl + # Hi, + # You can confirm access to your blink account by entering the following code: + # {{ .RecoveryCode }} + # Don't share this code with anyone. Our employee will never ask for this code + plaintext: base64://SGksCgpZb3UgY2FuIGNvbmZpcm0gYWNjZXNzIHRvIHlvdXIgYmxpbmsgYWNjb3VudCBieSBlbnRlcmluZyB0aGUgZm9sbG93aW5nIGNvZGU6Cgp7eyAuUmVjb3ZlcnlDb2RlIH19CgpEb24ndCBzaGFyZSB0aGlzIGNvZGUgd2l0aCBhbnlvbmUuIE91ciBlbXBsb3llZSB3aWxsIG5ldmVyIGFzayBmb3IgdGhpcyBjb2RlCg== + html: base64://SGksCgpZb3UgY2FuIGNvbmZpcm0gYWNjZXNzIHRvIHlvdXIgYmxpbmsgYWNjb3VudCBieSBlbnRlcmluZyB0aGUgZm9sbG93aW5nIGNvZGU6Cgp7eyAuUmVjb3ZlcnlDb2RlIH19CgpEb24ndCBzaGFyZSB0aGlzIGNvZGUgd2l0aCBhbnlvbmUuIE91ciBlbXBsb3llZSB3aWxsIG5ldmVyIGFzayBmb3IgdGhpcyBjb2RlCg== + +session: + # TODO: check lifespan per schema + # or look how to extend + lifespan: "720h" # 1 month + earliest_possible_extend: "720h" # needed for test. should be shorter in prod + + whoami: + required_aal: highest_available diff --git a/dev/config/ory/oathkeeper.yml b/dev/config/ory/oathkeeper.yml new file mode 100644 index 0000000000..60d91cf059 --- /dev/null +++ b/dev/config/ory/oathkeeper.yml @@ -0,0 +1,88 @@ +log: + level: debug + format: json + +tracing: + provider: otel + providers: + otlp: + server_url: otel-agent:4318 + insecure: true + +authenticators: + jwt: + enabled: true + config: + trusted_issuers: + - https://firebaseappcheck.googleapis.com/72279297366 + target_audience: + - projects/72279297366 + jwks_urls: + - https://firebaseappcheck.googleapis.com/v1beta/jwks + - file:///home/ory/jwks.json # ONLY FOR DEV, DO NOT USE IN PRODUCTION + token_from: + header: Appcheck + + bearer_token: + enabled: true + config: + check_session_url: http://kratos:4433/sessions/whoami + preserve_path: true + subject_from: identity.id + extra_from: identity.traits + + oauth2_introspection: + enabled: true + config: + introspection_url: http://hydra:4445/admin/oauth2/introspect + token_from: + header: Oauth2-Token + + anonymous: + enabled: true + config: + subject: anon + + cookie_session: + enabled: true + config: + # TODO cluster-internal mTLS + check_session_url: http://kratos:4433/sessions/whoami + preserve_path: true + subject_from: identity.id + extra_from: identity.traits + + unauthorized: + enabled: true + + noop: + enabled: true + +authorizers: + allow: + enabled: true + +mutators: + id_token: + enabled: true + config: + jwks_url: file:///home/ory/jwks.json + issuer_url: "galoy.io" + claims: '{"sub": "{{ print .Subject }}" }' + + noop: + enabled: true + +errors: + fallback: + - json + handlers: + json: + enabled: true + config: + verbose: true + +access_rules: + repositories: + - file:///home/ory/oathkeeper_rules.yaml + matching_strategy: regexp diff --git a/dev/config/ory/oathkeeper_rules.yaml b/dev/config/ory/oathkeeper_rules.yaml new file mode 100644 index 0000000000..1017ec20fd --- /dev/null +++ b/dev/config/ory/oathkeeper_rules.yaml @@ -0,0 +1,105 @@ +- id: anonymous-rest-auth + upstream: + url: "http://bats-tests:4012" + match: + url: "<(http|https)>://<[a-zA-Z0-9-.:]+>/auth/<(clearCookies|login|logout|email/code|email/login|totp/validate|email/login/cookie|phone/captcha|phone/code|phone/login)>" + methods: ["GET", "POST", "OPTIONS"] + authenticators: + - handler: anonymous + authorizer: + handler: allow + mutators: + - handler: noop + +- id: device-login + upstream: + url: "http://bats-tests:4012" + match: + url: "<(http|https)>://<[a-zA-Z0-9-.:]+>/auth/create/device-account" + methods: ["POST"] + authenticators: + - handler: jwt + config: + trusted_issuers: + - https://firebaseappcheck.googleapis.com/72279297366 + target_audience: + - projects/72279297366 + jwks_urls: + - https://firebaseappcheck.googleapis.com/v1beta/jwks + - file:///home/ory/jwks.json # ONLY FOR DEV, DO NOT USE IN PRODUCTION + token_from: + header: Appcheck + authorizer: + handler: allow + mutators: + - handler: noop + +- id: galoy-ws + upstream: + url: "http://bats-tests:4000/graphql" + strip_path: /graphqlws # ONLY FOR DEV, in prod should resolve to /graphql, like ws.blink.sv/graphql + match: + url: "<(http|https)>://<[a-zA-Z0-9-.:]+>/graphqlws" # ONLY FOR DEV, in prod should resolve to /graphql + methods: ["POST", "GET"] + authenticators: + - handler: noop + authorizer: + handler: allow + mutators: + - handler: noop + +- id: galoy-backend + upstream: + url: "http://apollo-router:4004" + match: + url: "<(http|https)>://<[a-zA-Z0-9-.:]+>/graphql" + methods: ["POST", "GET", "OPTIONS"] + authenticators: + - handler: oauth2_introspection + config: + introspection_url: http://hydra:4445/admin/oauth2/introspect + token_from: + header: Oauth2-Token + + - handler: bearer_token + config: + check_session_url: http://kratos:4433/sessions/whoami + preserve_path: true + preserve_query: true + subject_from: identity.id + extra_from: "@this" + - handler: anonymous + authorizer: + handler: allow + mutators: + - handler: id_token + config: #! TODO: add aud: {"aud": ["https://api/graphql"] } + claims: '{"sub": "{{ print .Subject }}", "session_id": "{{ print .Extra.id }}", "expires_at": "{{ print .Extra.expires_at }}", "scope": "{{ print .Extra.scope }}", "client_id": "{{ print .Extra.client_id }}" }' + +- id: admin-backend + upstream: + url: "http://bats-tests:4001" + strip_path: /admin + match: + url: "<(http|https)>://<.*><[0-9]+>/admin<.*>" + methods: ["GET", "POST", "OPTIONS"] + authenticators: + - handler: cookie_session + config: + check_session_url: http://host.docker.internal:3002/api/auth/session + preserve_path: true + preserve_query: true + subject_from: user.email + extra_from: "@this" + force_method: "GET" + - handler: oauth2_introspection + config: + introspection_url: http://hydra:4445/admin/oauth2/introspect + token_from: + header: Oauth2-Token + authorizer: + handler: allow + mutators: + - handler: id_token + config: #! TODO: add aud: {"aud": ["https://api/admin/graphql"] } + claims: '{"sub": "{{ print .Subject }}", "scope": "{{ print .Extra.scope }}" }' diff --git a/dev/config/ory/phone_email_no_password_v0.identity.schema.json b/dev/config/ory/phone_email_no_password_v0.identity.schema.json new file mode 100644 index 0000000000..067319a889 --- /dev/null +++ b/dev/config/ory/phone_email_no_password_v0.identity.schema.json @@ -0,0 +1,45 @@ +{ + "$id": "http://mydomain.com/schemas/v2/customer.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "A phone+email user", + "type": "object", + "properties": { + "traits": { + "type": "object", + "properties": { + "email": { + "title": "E-Mail", + "type": "string", + "format": "email", + "ory.sh/kratos": { + "credentials": { + "password": { + "identifier": true + } + }, + "verification": { + "via": "email" + }, + "recovery": { + "via": "email" + } + } + }, + "phone": { + "title": "Phone", + "type": "string", + "format": "string", + "ory.sh/kratos": { + "credentials": { + "password": { + "identifier": true + } + } + } + } + }, + "required": ["email", "phone"], + "additionalProperties": false + } + } +} diff --git a/dev/config/ory/phone_no_password_v0.identity.schema.json b/dev/config/ory/phone_no_password_v0.identity.schema.json new file mode 100644 index 0000000000..34773ec530 --- /dev/null +++ b/dev/config/ory/phone_no_password_v0.identity.schema.json @@ -0,0 +1,32 @@ +{ + "$id": "https://schemas.ory.sh/presets/kratos/quickstart/phone-no-password/identity.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PhoneNoPassword", + "type": "object", + "properties": { + "traits": { + "type": "object", + "properties": { + "phone": { + "type": "string", + "format": "string", + "title": "phone", + "minLength": 3, + "ory.sh/kratos": { + "credentials": { + "password": { + "identifier": true + } + } + } + } + }, + "required": ["phone"], + "additionalProperties": false + }, + "transient_payload": { + "type": "object", + "additionalProperties": true + } + } +} diff --git a/dev/config/ory/username_password_deviceid_v0.identity.schema.json b/dev/config/ory/username_password_deviceid_v0.identity.schema.json new file mode 100644 index 0000000000..03176dbb06 --- /dev/null +++ b/dev/config/ory/username_password_deviceid_v0.identity.schema.json @@ -0,0 +1,25 @@ +{ + "$id": "https://schemas.ory.sh/presets/kratos/quickstart/username-password-deviceid/identity.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UsernamePasswordDeviceId", + "type": "object", + "properties": { + "traits": { + "type": "object", + "properties": { + "username": { + "type": "string", + "minLength": 36, + "maxLength": 36, + "ory.sh/kratos": { + "credentials": { + "password": { + "identifier": true + } + } + } + } + } + } + } +} diff --git a/dev/docker-compose.deps.yml b/dev/docker-compose.deps.yml index c644904b4a..d84da84e1b 100644 --- a/dev/docker-compose.deps.yml +++ b/dev/docker-compose.deps.yml @@ -1,6 +1,82 @@ --- version: "3" services: + oathkeeper: + image: oryd/oathkeeper:v0.40.6-distroless + ports: + - "4455:4455" + - "4456:4456" + extra_hosts: + - "bats-tests:host-gateway" + command: serve -c /home/ory/oathkeeper.yml --sqa-opt-out + volumes: + - ${HOST_PROJECT_PATH:-.}/config/ory:/home/ory + hydra: + image: oryd/hydra:v2.1.2 + ports: + - "4444:4444" #! Public port + - "4445:4445" #! Admin port + command: serve -c /home/ory/hydra.yml all --dev + volumes: + - ${HOST_PROJECT_PATH:-.}/config/ory:/home/ory + environment: + - DSN=postgres://hydra:secret@hydra-pg:5432/hydra?sslmode=disable&max_conns=20&max_idle_conns=4 + restart: unless-stopped + depends_on: + - hydra-migrate + - hydra-pg + + hydra-migrate: + image: oryd/hydra:v2.1.2 + environment: + - DSN=postgres://hydra:secret@hydra-pg:5432/hydra?sslmode=disable&max_conns=20&max_idle_conns=4 + command: migrate -c /home/ory/hydra.yml sql -e --yes + volumes: + - ${HOST_PROJECT_PATH:-.}/config/ory:/home/ory + + restart: on-failure + depends_on: + - hydra-pg + + hydra-pg: + image: postgres:14.1 + environment: + - POSTGRES_USER=hydra + - POSTGRES_PASSWORD=secret + - POSTGRES_DB=hydra + kratos: + image: oryd/kratos:v1.0.0 + extra_hosts: + - "bats-tests:host-gateway" + ports: + - "4433:4433" #! public + - "4434:4434" #! admin + entrypoint: sh -c + command: '"kratos migrate sql up -y -e && kratos serve -c /home/ory/kratos.yml --watch-courier --sqa-opt-out"' + environment: + DSN: postgres://dbuser:secret@kratos-pg:5432/default?sslmode=disable + links: + - kratos-pg:kratos-pg + volumes: + - ${HOST_PROJECT_PATH:-.}/config/ory:/home/ory + kratos-pg: + image: postgres:14.1 + ports: [] + expose: + - "5432" + environment: + - POSTGRES_USER=dbuser + - POSTGRES_PASSWORD=secret + - POSTGRES_DB=default + apollo-router: + image: ghcr.io/apollographql/router:v1.25.0 + ports: + - 4004:4004 + environment: + - APOLLO_ROUTER_SUPERGRAPH_PATH=/repo/dev/apollo-federation/supergraph.graphql + - APOLLO_ROUTER_CONFIG_PATH=/repo/dev/apollo-federation/router.yaml + volumes: + - ${HOST_PROJECT_PATH:-.}/../core/api/dev:/repo/dev redis: image: redis:7.0.8 ports: From de7936939e21792a1ac9c35de5f89d13235a244f Mon Sep 17 00:00:00 2001 From: bodymindarts Date: Wed, 25 Oct 2023 16:18:09 +0200 Subject: [PATCH 06/10] chore: run consent test in tilt --- .env | 120 ------------------ .github/workflows/tilt.yml | 2 +- apps/consent/BUCK | 17 +++ apps/consent/cypress.config.ts | 8 +- apps/consent/cypress/support/commands.ts | 2 +- apps/consent/env.ts | 4 +- dev/.env | 0 dev/.gitignore | 3 + dev/Tiltfile | 80 ++++++++++-- dev/bin/init-onchain.sh | 4 +- dev/bin/init-user.sh | 16 +++ dev/bin/setup-hydra-client.sh | 34 +++++ dev/config/ory/hydra.yml | 8 +- dev/docker-compose.deps.yml | 2 + dev/helpers/auth.sh | 15 +++ dev/{bin/helpers.sh => helpers/cli.sh} | 11 ++ dev/helpers/gql.sh | 58 +++++++++ .../gql/user-email-registration-initiate.gql | 15 +++ .../gql/user-email-registration-validate.gql | 14 ++ toolchains/workspace-pnpm/macros.bzl | 56 ++++++++ 20 files changed, 324 insertions(+), 145 deletions(-) delete mode 100644 .env delete mode 100644 dev/.env create mode 100644 dev/.gitignore create mode 100755 dev/bin/init-user.sh create mode 100755 dev/bin/setup-hydra-client.sh create mode 100644 dev/helpers/auth.sh rename dev/{bin/helpers.sh => helpers/cli.sh} (59%) mode change 100644 => 100755 create mode 100644 dev/helpers/gql.sh create mode 100644 dev/helpers/gql/user-email-registration-initiate.gql create mode 100644 dev/helpers/gql/user-email-registration-validate.gql diff --git a/.env b/.env deleted file mode 100644 index 8d2ed9f144..0000000000 --- a/.env +++ /dev/null @@ -1,120 +0,0 @@ -export DOCKER_HOST_IP=${DOCKER_HOST_IP:-127.0.0.1} -export NETWORK=regtest - -# dev/lnd/tls.cert.base64 -export LND1_TLS="LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNZVENDQWdlZ0F3SUJBZ0lSQU9zZzdYWFR4cnVZYlhkeTY2d3RuN1F3Q2dZSUtvWkl6ajBFQXdJd09ERWYKTUIwR0ExVUVDaE1XYkc1a0lHRjFkRzluWlc1bGNtRjBaV1FnWTJWeWRERVZNQk1HQTFVRUF4TU1PRFl4T1RneApNak5tT0Roak1CNFhEVEl6TURFeE9USXdOREUxTTFvWERUTTBNRGN5TVRJd05ERTFNMW93T0RFZk1CMEdBMVVFCkNoTVdiRzVrSUdGMWRHOW5aVzVsY21GMFpXUWdZMlZ5ZERFVk1CTUdBMVVFQXhNTU9EWXhPVGd4TWpObU9EaGoKTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFM1lieUlKWU1Vcm8zZkl0UFFucysxZ2lpTXI5NQpJUXRmclFDQ2JhOWVtcjI4TENmbk1vYy9VQVFwUlg3QVlvVFRneUdiMFBuZGNUODF5ZVgvYTlPa0RLT0I4VENCCjdqQU9CZ05WSFE4QkFmOEVCQU1DQXFRd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC8KQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVL1AxRHpJUkRzTEhHMU10d3NrZE5nZ0lub1Mwd2daWUdBMVVkRVFTQgpqakNCaTRJTU9EWXhPVGd4TWpObU9EaGpnZ2xzYjJOaGJHaHZjM1NDRFd4dVpDMXZkWFJ6YVdSbExUR0NEV3h1ClpDMXZkWFJ6YVdSbExUS0NEV3h1WkMxdmRYUnphV1JsTFRPQ0JHeHVaREdDQkd4dVpES0NCSFZ1YVhpQ0NuVnUKYVhod1lXTnJaWFNDQjJKMVptTnZibTZIQkg4QUFBR0hFQUFBQUFBQUFBQUFBQUFBQUFBQUFBR0hCS3dUQUJBdwpDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBSU5DNlJWQ3d6SzFYRnFxeVNLY0Y4QzQ5ZFlSOThjemdLNVdkcmNOCkxYYWlBaUJHYmtWeGhaeHdDaDVLQ1o1Z2M1Q2FsQ0RvaGNxVkdiaHNya0hHTFhpdHN3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" -export LND2_TLS=$LND1_TLS - -export TLSOUTSIDE1=$LND1_TLS -export TLSOUTSIDE2=$LND1_TLS - -# dev/lnd/regtest/lnd1.admin.macaroon.base64 -export LND1_MACAROON="AgEDbG5kAvgBAwoQB1FdhGa9xoewc1LEXmnURRIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgqHDdwGCqx0aQL1/Z3uUfzCpeBhfapGf9s/AZPOVwf6s=" -# dev/lnd/regtest/lnd2.admin.macaroon.base64 -export LND2_MACAROON="AgEDbG5kAvgBAwoQX0BxfhQTxLTiqaceBnGnfBIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgMAKlr1HehfBpn2R5RPE2IuY9r/18QBeLZxYgRidpos4=" - -# dev/lnd/regtest/lnd-outside-1.admin.macaroon.base64 -export MACAROONOUTSIDE1="AgEDbG5kAvgBAwoQeE+5exgz7/0ExCn7H6AJlBIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgL7pU+cKOt6zGyWTdWWmAJLP1L3cnbOPb4Rd3QtniyyM=" -# -# dev/lnd/regtest/lnd-outside-2.admin.macaroon.base64 -export MACAROONOUTSIDE2="AgEDbG5kAvgBAwoQfKO82/iPT2zIwWYPrOXvABIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYg2XkV+4Z4inbfXGZivRoY+r7KHNZhgxkCEdKByxbeb/Q=" - -# dev/lnd/loop/regtest/loopd1-1.loop.macaroon.base64 -export LND1_LOOP_MACAROON="AgEEbG9vcAJ3AwoQRGymK6/vfF3wwuVmaTj3RhIBMBoMCgRhdXRoEgRyZWFkGg8KBGxvb3ASAmluEgNvdXQaGgoLc3VnZ2VzdGlvbnMSBHJlYWQSBXdyaXRlGhUKBHN3YXASB2V4ZWN1dGUSBHJlYWQaDQoFdGVybXMSBHJlYWQAAAYgAFS/qTZItZ3ZKksQkfXAKFnsb0JS5Ok3Oi5fAgCaE/k=" -# dev/lnd/loop/regtest/loopd2-1.loop.macaroon.base64 -export LND2_LOOP_MACAROON="AgEEbG9vcAJ3AwoQ6Ntr7+DpuicdMgmVPKvDVxIBMBoMCgRhdXRoEgRyZWFkGg8KBGxvb3ASAmluEgNvdXQaGgoLc3VnZ2VzdGlvbnMSBHJlYWQSBXdyaXRlGhUKBHN3YXASB2V4ZWN1dGUSBHJlYWQaDQoFdGVybXMSBHJlYWQAAAYgU6bTJC50AuYehDtb9U2s4EuH7C2Tf8eAppPPOFeUXds=" - -# dev/lnd/loop/regtest/loopd1-1.tls.cert.base64 -export LND1_LOOP_TLS="LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNKakNDQWMyZ0F3SUJBZ0lSQU1memVXM0J0UWJaaTdxSjZoQk5vMHN3Q2dZSUtvWkl6ajBFQXdJd09URWcKTUI0R0ExVUVDaE1YYkc5dmNDQmhkWFJ2WjJWdVpYSmhkR1ZrSUdObGNuUXhGVEFUQmdOVkJBTVREREpqWkdRMQpOMlptWlRZeFpEQWVGdzB5TWpBNU1EY3lNVE15TWpSYUZ3MHlNekV4TURJeU1UTXlNalJhTURreElEQWVCZ05WCkJBb1RGMnh2YjNBZ1lYVjBiMmRsYm1WeVlYUmxaQ0JqWlhKME1SVXdFd1lEVlFRREV3d3lZMlJrTlRkbVptVTIKTVdRd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFUcU5qcVFMUGNHSURaSmtHenNlL3d2ZWt0TwpRVlhpaFJ6WmVLay9ZMFlTNDFkejB2TjlQdktaM0ZxTmN2eEN5a1cvZ1dKNWhBdEpCZTdDaTZhWitnR0tvNEcxCk1JR3lNQTRHQTFVZER3RUIvd1FFQXdJQ3BEQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBVEFQQmdOVkhSTUIKQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJRZHJscHQzM2lLdlZUZWQyVnV4Y25uQVJMeTlEQmJCZ05WSFJFRQpWREJTZ2d3eVkyUmtOVGRtWm1VMk1XU0NDV3h2WTJGc2FHOXpkSUlFZFc1cGVJSUtkVzVwZUhCaFkydGxkSUlIClluVm1ZMjl1Ym9jRWZ3QUFBWWNRQUFBQUFBQUFBQUFBQUFBQUFBQUFBWWNFckJzQUR6QUtCZ2dxaGtqT1BRUUQKQWdOSEFEQkVBaUJYaFI2VmRzSFYrREhhWGRrV2VRZ0pzMlRxT0pXajBwUXI1ZHFLcFViNjlBSWdTeGtYYTZFRQpWVk9CZ0VhNXR5Z3NBcGM2bDBSak5nVGF2SkF6T2dWT2tIWT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" -# dev/lnd/loop/regtest/loopd2-1.tls.cert.base64 -export LND2_LOOP_TLS="LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNKakNDQWN5Z0F3SUJBZ0lRVjd2dFlUbzJYS1NybWt6N3d0SjNjVEFLQmdncWhrak9QUVFEQWpBNU1TQXcKSGdZRFZRUUtFeGRzYjI5d0lHRjFkRzluWlc1bGNtRjBaV1FnWTJWeWRERVZNQk1HQTFVRUF4TU1aVEZpTkRobQpZbVUwTmpZek1CNFhEVEl5TURrd056SXhNekl5TlZvWERUSXpNVEV3TWpJeE16SXlOVm93T1RFZ01CNEdBMVVFCkNoTVhiRzl2Y0NCaGRYUnZaMlZ1WlhKaGRHVmtJR05sY25ReEZUQVRCZ05WQkFNVERHVXhZalE0Wm1KbE5EWTIKTXpCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQkVYbzlndHpkUnFMTWFhdjJ1VDA4eHlTUUpTKwpoMFNMcUMrdnpzR0RhZ2owOXg3UW9ud3oralo0eHppeklsdWVOY0JlWDYzd3VGZ0dwOTlBMW9mMDQyU2pnYlV3CmdiSXdEZ1lEVlIwUEFRSC9CQVFEQWdLa01CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUJNQThHQTFVZEV3RUIKL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkZOM2E3WkY5Y2FSUGJnNDJXYitOYnVYM2hTYU1Gc0dBMVVkRVFSVQpNRktDREdVeFlqUTRabUpsTkRZMk00SUpiRzlqWVd4b2IzTjBnZ1IxYm1sNGdncDFibWw0Y0dGamEyVjBnZ2RpCmRXWmpiMjV1aHdSL0FBQUJoeEFBQUFBQUFBQUFBQUFBQUFBQUFBQUJod1NzR3dBUU1Bb0dDQ3FHU000OUJBTUMKQTBnQU1FVUNJUURUcUpnVitReks2N3lSN1lGcWtyQkN6aEQ2OExZcjBBM1JMWFF1ckM2d21BSWdZNGdESjA1eQpRTjJCY2YvaEl6VHJwb1ZlK3Y0blBRREg3bXBFQXRyOE96MD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" - -# dev/lnd/regtest/lnd1.pubkey -export LND1_PUBKEY="03ca1907342d5d37744cb7038375e1867c24a87564c293157c95b2a9d38dcfb4c2" -# dev/lnd/regtest/lnd2.pubkey -export LND2_PUBKEY="039341ef13e776dc1611502cf510110d9ac5cdc252141f5997adcfd72cef34c3a7" - -export BITCOINDPORT=18443 - -export BITCOINDADDR=${DOCKER_HOST_IP} -export BITCOIND_SIGNER_PORT=18543 -export BITCOIND_SIGNER_ADDR=${DOCKER_HOST_IP} -export BITCOINDRPCPASS=rpcpassword - -export LND1_DNS=${DOCKER_HOST_IP} -export LND2_DNS=${DOCKER_HOST_IP} -export LNDOUTSIDE1ADDR=${DOCKER_HOST_IP} -export LNDOUTSIDE2ADDR=${DOCKER_HOST_IP} - -export LND1_RPCPORT=10009 -export LND2_RPCPORT=10010 - -export LNDOUTSIDE1RPCPORT=10012 -export LNDOUTSIDE2RPCPORT=10013 - -export LND1_TYPE=offchain,onchain -export LND2_TYPE=offchain - -export LND1_NAME=lnd1 -export LND2_NAME=lnd2 - -export MONGODB_CON=mongodb://${DOCKER_HOST_IP}:27017/galoy - -export REDIS_0_DNS=${DOCKER_HOST_IP} -export REDIS_0_PORT=6379 -export REDIS_MASTER_NAME="mymaster" -export REDIS_PASSWORD="" -export REDIS_TYPE="standalone" - -export BRIA_HOST=${DOCKER_HOST_IP} -export BRIA_PORT=2742 -export BRIA_API_KEY="bria_dev_000000000000000000000" - -export PRICE_HOST=${DOCKER_HOST_IP} -export PRICE_PORT=50051 - -export PRICE_HISTORY_HOST=${DOCKER_HOST_IP} -export PRICE_HISTORY_PORT=50052 - -export OATHKEEPER_DECISION_ENDPOINT=http://${DOCKER_HOST_IP}:4456 - -export WEBSOCKET_PORT=4000 - -export GEETEST_ID="geetest_id" -export GEETEST_KEY="geetest_key" - -export TWILIO_ACCOUNT_SID="AC_twilio_id" -export TWILIO_AUTH_TOKEN="AC_twilio_auth_token" -export TWILIO_VERIFY_SERVICE_ID="VA_twilio_service" - -export COMMITHASH="hash" -export HELMREVISION="1" - -export LOG_LEVEL="info" - -export KRATOS_MASTER_USER_PASSWORD="passwordHardtoFindWithNumber123" -export KRATOS_CALLBACK_API_KEY="The-Value-of-My-Key" -export KRATOS_PG_HOST="localhost" -export KRATOS_PG_PORT="5433" - -export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318" -# TODO: rename to OTEL_SERVICE_NAME -# https://opentelemetry.io/docs/concepts/sdk-configuration/general-sdk-configuration/#otel_service_name -export TRACING_SERVICE_NAME="galoy-dev" - -export MATTERMOST_WEBHOOK_URL="https://chat.galoy.io/hooks/sometoken" - -export KRATOS_PG_CON="postgres://dbuser:secret@localhost:5433/default?sslmode=disable" - -export UNSECURE_DEFAULT_LOGIN_CODE="000000" -export UNSECURE_IP_FROM_REQUEST_OBJECT=true - -export SVIX_SECRET="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTE2NzIwMTQsImV4cCI6MjAwNzAzMjAxNCwibmJmIjoxNjkxNjcyMDE0LCJpc3MiOiJzdml4LXNlcnZlciIsInN1YiI6Im9yZ18yM3JiOFlkR3FNVDBxSXpwZ0d3ZFhmSGlyTXUifQ.b9s0aWSisNdUNki4edabBEToLNSwjC9-AiJQr4J3y4E" -export SVIX_ENDPOINT="http://localhost:8071" -export SVIX_CALLBACK_URL=http://bats-tests:8080/webhook/ - -export KRATOS_PUBLIC_API="http://localhost:4433" -export KRATOS_ADMIN_API="http://localhost:4434" - -export HYDRA_PUBLIC_API="http://localhost:4444" -export HYDRA_ADMIN_API="http://localhost:4445" diff --git a/.github/workflows/tilt.yml b/.github/workflows/tilt.yml index 4f23f5d6e5..42cc32a342 100644 --- a/.github/workflows/tilt.yml +++ b/.github/workflows/tilt.yml @@ -11,4 +11,4 @@ jobs: with: github_access_token: ${{ secrets.GITHUB_TOKEN }} - name: Tilt CI - run: nix develop -c bash -c "cd dev && tilt ci" + run: nix develop -c bash -c "cd dev && tilt ci -- -test consent | tee tilt.log | grep test" diff --git a/apps/consent/BUCK b/apps/consent/BUCK index 8f621dfe4f..2dd0dd0702 100644 --- a/apps/consent/BUCK +++ b/apps/consent/BUCK @@ -1,10 +1,27 @@ load("@toolchains//workspace-pnpm:macros.bzl", +"dev_pnpm_task_binary", +"dev_pnpm_task_test", "build_node_modules", "next_build", "next_build_bin", "eslint" ) +dev_pnpm_task_binary( + name = "dev", + command = "dev", +) + +dev_pnpm_task_binary( + name = "open-cypress", + command = "cypress:open", +) + +dev_pnpm_task_test( + name = "cypress", + command = "cypress:run", +) + export_file( name = "package.json", visibility = ["PUBLIC"], diff --git a/apps/consent/cypress.config.ts b/apps/consent/cypress.config.ts index 4452ebab6b..9143d8168f 100644 --- a/apps/consent/cypress.config.ts +++ b/apps/consent/cypress.config.ts @@ -1,16 +1,14 @@ import { defineConfig } from "cypress" import dotenv from "dotenv" -dotenv.config() -dotenv.config({ path: ".env.test" }) +dotenv.config({ path: "../../dev/.envs/consent.env" }) export default defineConfig({ e2e: { - baseUrl: "http://127.0.0.1:3000", - // setupNodeEvents(on, config) {}, + baseUrl: "http://localhost:3000", }, defaultCommandTimeout: 60000, env: { - ...process.env, + AUTHORIZATION_URL: process.env.AUTHORIZATION_URL, }, component: { devServer: { diff --git a/apps/consent/cypress/support/commands.ts b/apps/consent/cypress/support/commands.ts index 16e0c35dbc..3bacca45f2 100644 --- a/apps/consent/cypress/support/commands.ts +++ b/apps/consent/cypress/support/commands.ts @@ -46,7 +46,7 @@ declare namespace Cypress { } Cypress.Commands.add("getOTP", (email) => { - const query = `docker exec -i api-kratos-pg-1 psql -U dbuser -d default -t -c "SELECT body FROM courier_messages WHERE recipient='${email}' ORDER BY created_at DESC LIMIT 1;"` + const query = `docker exec -i galoy-dev-kratos-pg-1 psql -U dbuser -d default -t -c "SELECT body FROM courier_messages WHERE recipient='${email}' ORDER BY created_at DESC LIMIT 1;"` cy.exec(query).then((result) => { const rawMessage = result.stdout const otpMatch = rawMessage.match(/(\d{6})/) diff --git a/apps/consent/env.ts b/apps/consent/env.ts index 9804b44cdf..becafbc31c 100644 --- a/apps/consent/env.ts +++ b/apps/consent/env.ts @@ -4,10 +4,10 @@ import { z } from "zod" export const env = createEnv({ server: { HYDRA_ADMIN_URL: z.string().default("http://localhost:4445"), - CORE_AUTH_URL: z.string().default("http://localhost:4002/auth"), + CORE_AUTH_URL: z.string().default("http://localhost:4455/auth"), }, shared: { - GRAPHQL_ENDPOINT: z.string().default("http://localhost:4002/graphql"), + GRAPHQL_ENDPOINT: z.string().default("http://localhost:4455/graphql"), }, runtimeEnv: { CORE_AUTH_URL: process.env.CORE_AUTH_URL, diff --git a/dev/.env b/dev/.env deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/dev/.gitignore b/dev/.gitignore new file mode 100644 index 0000000000..ca3d6357e7 --- /dev/null +++ b/dev/.gitignore @@ -0,0 +1,3 @@ +.*.json +.*.env +.envs diff --git a/dev/Tiltfile b/dev/Tiltfile index f183af2430..f03f9b3765 100644 --- a/dev/Tiltfile +++ b/dev/Tiltfile @@ -1,3 +1,8 @@ +is_ci=sys.argv[1] == "ci" + +config.define_string_list("test") +cfg = config.parse() + groups = { "auth": [ "oathkeeper", @@ -25,6 +30,57 @@ groups = { ], } +consent_test_target = "//apps/consent:cypress" +local_resource( + "consent-test", + labels = ["test"], + auto_init = is_ci and "consent" in cfg.get("test", []), + cmd = "buck2 test {}".format(consent_test_target), + resource_deps = [ + "consent", + "init-test-user", + "hydra-consent" + ], +) + +local_resource( + name='init-test-user', + labels = ['test'], + cmd='bin/init-user.sh', + resource_deps = [ + "oathkeeper", + "kratos", + "api", + ] +) + +consent_target = "//apps/consent:dev" +if is_ci: + consent_target = '//apps/consent:consent' +local_resource( + "consent", + labels = ["auth"], + cmd = "buck2 build {}".format(consent_target), + serve_cmd = "buck2 run {}".format(consent_target), + resource_deps = [ + "apollo-router", + "hydra", + "api", + ], + links = [ + link("http://localhost:3000", "consent"), + ], +) + +local_resource( + name='hydra-consent', + labels = ['auth'], + cmd=['bin/setup-hydra-client.sh', 'consent', 'http://localhost:3000'], + resource_deps = [ + "hydra", + ] +) + api_target = "//core/api:api" local_resource( "api", @@ -51,6 +107,10 @@ local_resource( "REDIS_0_DNS": "localhost", "REDIS_0_PORT": "6379", "REDIS_TYPE": "standalone", + "UNSECURE_IP_FROM_REQUEST_OBJECT": "true", + "UNSECURE_DEFAULT_LOGIN_CODE": "000000", + "GEETEST_ID": "geetest_id", + "GEETEST_KEY": "geetest_key" }, allow_parallel = True, readiness_probe = probe( @@ -66,6 +126,16 @@ local_resource( ] ) +local_resource( + name='init-onchain', + labels = ['bitcoin'], + cmd='bin/init-onchain.sh', + resource_deps = [ + "bitcoind", + "bria", + ] +) + docker_compose("./docker-compose.deps.yml", project_name = "galoy-dev") for service in groups["bitcoin"]: @@ -76,13 +146,3 @@ for service in groups["core"]: dc_resource(service, labels = ["core"]) for service in groups["auth"]: dc_resource(service, labels = ["auth"]) - -local_resource( - name='init-onchain', - labels = ['bitcoin'], - cmd='bin/init-onchain.sh', - resource_deps = [ - "bitcoind", - "bria", - ] -) diff --git a/dev/bin/init-onchain.sh b/dev/bin/init-onchain.sh index cfe1d99d67..c10cd6eee6 100755 --- a/dev/bin/init-onchain.sh +++ b/dev/bin/init-onchain.sh @@ -2,8 +2,8 @@ set -e -DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" -source "${DIR}/helpers.sh" +DEV_DIR="$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")")" +source "${DEV_DIR}/helpers/cli.sh" echo "Seeding some regtest blocks..." diff --git a/dev/bin/init-user.sh b/dev/bin/init-user.sh new file mode 100755 index 0000000000..56e1a484c3 --- /dev/null +++ b/dev/bin/init-user.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +set -x + +DEV_DIR="$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")")" +source "${DEV_DIR}/helpers/auth.sh" +source "${DEV_DIR}/helpers/gql.sh" + +user_phone="+16505554350" +email="test@galoy.com" + +auth_token="$(login_user "${user_phone}")" + +register_email_to_user "${auth_token}" "${email}" diff --git a/dev/bin/setup-hydra-client.sh b/dev/bin/setup-hydra-client.sh new file mode 100755 index 0000000000..6113c9109a --- /dev/null +++ b/dev/bin/setup-hydra-client.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -e + +DEV_DIR="$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")")" +source "${DEV_DIR}/helpers/cli.sh" + +hydra_client_name="${1}" +redirect_uri="${2}" + +HYDRA_CLIENT_JSON="${DEV_DIR}/../.${hydra_client_name}-hydra-client.json" +HYDRA_CLIENT_ENV="${DEV_DIR}/../.${hydra_client_name}-hydra-client.env" +HYDRA_ADMIN_API="http://localhost:4445" +HYDRA_PUBLIC_API="http://localhost:4444" + +hydra_cli create client \ + --endpoint "${HYDRA_ADMIN_API}" \ + --grant-type authorization_code,refresh_token \ + --response-type code,id_token \ + --format json \ + --scope offline --scope transactions:read --scope payments:send \ + --redirect-uri "$redirect_uri" > "${HYDRA_CLIENT_JSON}" + +CLIENT_ID=$(jq -r '.client_id' < "${HYDRA_CLIENT_JSON}") +CLIENT_SECRET=$(jq -r '.client_secret' < "${HYDRA_CLIENT_JSON}") + +AUTHORIZATION_URL="${HYDRA_PUBLIC_API}/oauth2/auth?client_id=$CLIENT_ID&scope=offline%20transactions:read&response_type=code&redirect_uri=$redirect_uri&state=kfISr3GhH0rqheByU6A6hqIG_f14pCGkZLSCUTHnvlI" + +echo "export CLIENT_ID=$CLIENT_ID" > "${HYDRA_CLIENT_ENV}" +echo "export CLIENT_SECRET=$CLIENT_SECRET" >> "${HYDRA_CLIENT_ENV}" +echo "export AUTHORIZATION_URL=$AUTHORIZATION_URL" >> "${HYDRA_CLIENT_ENV}" + +mkdir -p "${DEV_DIR}/.envs" +cp "${HYDRA_CLIENT_ENV}" "${DEV_DIR}/.envs/${hydra_client_name}.env" diff --git a/dev/config/ory/hydra.yml b/dev/config/ory/hydra.yml index 8d69cc1d24..eeb3592a3b 100644 --- a/dev/config/ory/hydra.yml +++ b/dev/config/ory/hydra.yml @@ -4,10 +4,10 @@ serve: urls: self: - issuer: http://127.0.0.1:4444 - consent: http://127.0.0.1:3000/consent - login: http://127.0.0.1:3000/login - logout: http://127.0.0.1:3000/logout + issuer: http://localhost:4444 + consent: http://localhost:3000/consent + login: http://localhost:3000/login + logout: http://localhost:3000/logout secrets: system: diff --git a/dev/docker-compose.deps.yml b/dev/docker-compose.deps.yml index d84da84e1b..59719e9021 100644 --- a/dev/docker-compose.deps.yml +++ b/dev/docker-compose.deps.yml @@ -72,6 +72,8 @@ services: image: ghcr.io/apollographql/router:v1.25.0 ports: - 4004:4004 + extra_hosts: + - "bats-tests:host-gateway" environment: - APOLLO_ROUTER_SUPERGRAPH_PATH=/repo/dev/apollo-federation/supergraph.graphql - APOLLO_ROUTER_CONFIG_PATH=/repo/dev/apollo-federation/router.yaml diff --git a/dev/helpers/auth.sh b/dev/helpers/auth.sh new file mode 100644 index 0000000000..f2e78b4c48 --- /dev/null +++ b/dev/helpers/auth.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +AUTH_ENDPOINT="http://localhost:4455/auth/phone/login" + +login_user() { + phone="$1" + + login_response=$(curl \ + -s \ + -X POST $AUTH_ENDPOINT \ + -H "Content-Type: application/json" \ + -d '{"phone": "'$phone'", "code":"000000"}') + + echo "$login_response" | jq -r '.authToken' +} diff --git a/dev/bin/helpers.sh b/dev/helpers/cli.sh old mode 100644 new mode 100755 similarity index 59% rename from dev/bin/helpers.sh rename to dev/helpers/cli.sh index f5d069497d..62b0746ad9 --- a/dev/bin/helpers.sh +++ b/dev/helpers/cli.sh @@ -13,3 +13,14 @@ bitcoin_signer_cli() { bria_cli() { docker exec "${COMPOSE_PROJECT_NAME}-bria-1" bria "$@" } + +hydra_cli() { + docker exec "${COMPOSE_PROJECT_NAME}-hydra-1" hydra "$@" +} + +kratos_pg() { + DB_USER="dbuser" + DB_NAME="default" + + docker exec "${COMPOSE_PROJECT_NAME}-kratos-pg-1" psql -U $DB_USER -d $DB_NAME -t -c "$@" +} diff --git a/dev/helpers/gql.sh b/dev/helpers/gql.sh new file mode 100644 index 0000000000..9608f9522c --- /dev/null +++ b/dev/helpers/gql.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +GQL_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/gql" +HELPERS_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" +source "${HELPERS_DIR}/cli.sh" + +GALOY_ENDPOINT="http://localhost:4455/graphql" + +register_email_to_user() { + local token=$1 + local email=$2 + + variables="{\"input\": {\"email\": \"$email\"}}" + registration_id=$(exec_graphql $token 'user-email-registration-initiate' "${variables}" '.data.userEmailRegistrationInitiate.emailRegistrationId') + + email_code_response=$(kratos_pg "SELECT body FROM courier_messages WHERE recipient='$email' ORDER BY created_at DESC LIMIT 1;") + email_code=$(echo "$email_code_response" | grep -oP '\d{6}') + + variables="{\"input\": {\"code\": \"$email_code\", \"emailRegistrationId\": \"$registration_id\"}}" + exec_graphql $token 'user-email-registration-validate' "${variables}" +} + +gql_file() { + echo "${GQL_DIR}/$1.gql" +} + +gql_query() { + cat "$(gql_file $1)" | tr '\n' ' ' | sed 's/"/\\"/g' +} + +exec_graphql() { + local token=$1 + local query_name=$2 + local variables=${3:-"{}"} + local output=${4:-"."} + + if [[ ${token} == "anon" ]]; then + AUTH_HEADER="" + else + AUTH_HEADER="Authorization: Bearer ${token}" + fi + + curl -s \ + -X POST \ + ${AUTH_HEADER:+ -H "$AUTH_HEADER"} \ + -H "Content-Type: application/json" \ + -H "X-Idempotency-Key: $(random_uuid)" \ + -d "{\"query\": \"$(gql_query $query_name)\", \"variables\": $variables}" \ + "${GALOY_ENDPOINT}" | jq -r "${output}" +} + +random_uuid() { + if [[ -e /proc/sys/kernel/random/uuid ]]; then + cat /proc/sys/kernel/random/uuid + else + uuidgen + fi +} diff --git a/dev/helpers/gql/user-email-registration-initiate.gql b/dev/helpers/gql/user-email-registration-initiate.gql new file mode 100644 index 0000000000..e71d774a3d --- /dev/null +++ b/dev/helpers/gql/user-email-registration-initiate.gql @@ -0,0 +1,15 @@ +mutation userEmailRegistrationInitiate($input: UserEmailRegistrationInitiateInput!) { + userEmailRegistrationInitiate(input: $input) { + errors { + message + } + emailRegistrationId + me { + id + email { + address + verified + } + } + } +} diff --git a/dev/helpers/gql/user-email-registration-validate.gql b/dev/helpers/gql/user-email-registration-validate.gql new file mode 100644 index 0000000000..14fc670c2f --- /dev/null +++ b/dev/helpers/gql/user-email-registration-validate.gql @@ -0,0 +1,14 @@ +mutation userEmailRegistrationValidate($input: UserEmailRegistrationValidateInput!) { + userEmailRegistrationValidate(input: $input) { + errors { + message + } + me { + id + email { + address + verified + } + } + } +} diff --git a/toolchains/workspace-pnpm/macros.bzl b/toolchains/workspace-pnpm/macros.bzl index 3e6bc37b83..ac553dd8b4 100644 --- a/toolchains/workspace-pnpm/macros.bzl +++ b/toolchains/workspace-pnpm/macros.bzl @@ -879,3 +879,59 @@ def madge_check( visibility = visibility, **kwargs, ) + +def pnpm_task_binary_impl(ctx: AnalysisContext) -> list[[DefaultInfo, RunInfo]]: + script = ctx.actions.write("pnpm-run.sh", """\ +#!/usr/bin/env bash +set -euo pipefail + +rootpath="$(git rev-parse --show-toplevel)" +install_node_modules="$1" +npm_package_path="$2" +npm_run_command="$3" + +cd "$rootpath/$npm_package_path" +if [ "$install_node_modules" = "True" ]; then + pnpm install +fi +pnpm run --report-summary "$npm_run_command" +""", is_executable = True) + args = cmd_args([script, str(ctx.attrs.local_node_modules), ctx.label.package, ctx.attrs.command]) + args.hidden([ctx.attrs.deps]) + args.hidden([ctx.attrs.srcs]) + return [DefaultInfo(), RunInfo(args = args)] + +dev_pnpm_task_binary = rule(impl = pnpm_task_binary_impl, attrs = { + "command": attrs.string(doc = """pnpm command to run"""), + "local_node_modules": attrs.bool(default = True, doc = """Need to run pnpm install first?"""), + "srcs": attrs.list(attrs.source(), default = [], doc = """List of sources we require"""), + "deps": attrs.list(attrs.source(), default = [], doc = """List of dependencies we require"""), +}) + +def pnpm_task_test_impl(ctx: AnalysisContext) -> list[[DefaultInfo, ExternalRunnerTestInfo]]: + script = ctx.actions.write("pnpm-run.sh", """\ +#!/usr/bin/env bash +set -euo pipefail + +rootpath="$(git rev-parse --show-toplevel)" +install_node_modules="$1" +npm_package_path="$2" +npm_run_command="$3" + +cd "$rootpath/$npm_package_path" +if [ "$install_node_modules" = "True" ]; then + pnpm install +fi +pnpm run --report-summary "$npm_run_command" +""", is_executable = True) + args = cmd_args([script, str(ctx.attrs.local_node_modules), ctx.label.package, ctx.attrs.command]) + args.hidden([ctx.attrs.deps]) + args.hidden([ctx.attrs.srcs]) + return [DefaultInfo(), ExternalRunnerTestInfo(type = "integration", command = [script, str(ctx.attrs.local_node_modules), ctx.label.package, ctx.attrs.command])] + +dev_pnpm_task_test = rule(impl = pnpm_task_test_impl, attrs = { + "command": attrs.string(default = "start", doc = """pnpm command to run"""), + "local_node_modules": attrs.bool(default = True, doc = """Need to run pnpm install first?"""), + "srcs": attrs.list(attrs.source(), default = [], doc = """List of sources we require"""), + "deps": attrs.list(attrs.source(), default = [], doc = """List of dependencies we require"""), +}) From 2730d3b4b4413e7cba0e73cd57ed57fc36de52f7 Mon Sep 17 00:00:00 2001 From: bodymindarts Date: Wed, 25 Oct 2023 16:21:38 +0200 Subject: [PATCH 07/10] build: add xvfb-run to tilt --- .github/workflows/tilt.yml | 2 +- flake.nix | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tilt.yml b/.github/workflows/tilt.yml index 42cc32a342..c808aee95b 100644 --- a/.github/workflows/tilt.yml +++ b/.github/workflows/tilt.yml @@ -11,4 +11,4 @@ jobs: with: github_access_token: ${{ secrets.GITHUB_TOKEN }} - name: Tilt CI - run: nix develop -c bash -c "cd dev && tilt ci -- -test consent | tee tilt.log | grep test" + run: nix develop -c bash -c "cd dev && xvfb-run tilt ci -- -test consent | tee tilt.log | grep test" diff --git a/flake.nix b/flake.nix index 0a94f7b243..6c311ca9a3 100644 --- a/flake.nix +++ b/flake.nix @@ -54,7 +54,11 @@ jq ytt ] - ++ buck2NativeBuildInputs; + ++ buck2NativeBuildInputs + ++ lib.optionals pkgs.stdenv.isLinux [ + xvfb-run + cypress + ]; buck2Version = pkgs.buck2.version; postPatch = with pkgs; '' From 6c7c422dcb2ff2497fe6dfb7f5db19b4621c464e Mon Sep 17 00:00:00 2001 From: bodymindarts Date: Wed, 25 Oct 2023 16:23:18 +0200 Subject: [PATCH 08/10] fix: bring back .env --- .env | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000000..8d2ed9f144 --- /dev/null +++ b/.env @@ -0,0 +1,120 @@ +export DOCKER_HOST_IP=${DOCKER_HOST_IP:-127.0.0.1} +export NETWORK=regtest + +# dev/lnd/tls.cert.base64 +export LND1_TLS="LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNZVENDQWdlZ0F3SUJBZ0lSQU9zZzdYWFR4cnVZYlhkeTY2d3RuN1F3Q2dZSUtvWkl6ajBFQXdJd09ERWYKTUIwR0ExVUVDaE1XYkc1a0lHRjFkRzluWlc1bGNtRjBaV1FnWTJWeWRERVZNQk1HQTFVRUF4TU1PRFl4T1RneApNak5tT0Roak1CNFhEVEl6TURFeE9USXdOREUxTTFvWERUTTBNRGN5TVRJd05ERTFNMW93T0RFZk1CMEdBMVVFCkNoTVdiRzVrSUdGMWRHOW5aVzVsY21GMFpXUWdZMlZ5ZERFVk1CTUdBMVVFQXhNTU9EWXhPVGd4TWpObU9EaGoKTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFM1lieUlKWU1Vcm8zZkl0UFFucysxZ2lpTXI5NQpJUXRmclFDQ2JhOWVtcjI4TENmbk1vYy9VQVFwUlg3QVlvVFRneUdiMFBuZGNUODF5ZVgvYTlPa0RLT0I4VENCCjdqQU9CZ05WSFE4QkFmOEVCQU1DQXFRd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC8KQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVL1AxRHpJUkRzTEhHMU10d3NrZE5nZ0lub1Mwd2daWUdBMVVkRVFTQgpqakNCaTRJTU9EWXhPVGd4TWpObU9EaGpnZ2xzYjJOaGJHaHZjM1NDRFd4dVpDMXZkWFJ6YVdSbExUR0NEV3h1ClpDMXZkWFJ6YVdSbExUS0NEV3h1WkMxdmRYUnphV1JsTFRPQ0JHeHVaREdDQkd4dVpES0NCSFZ1YVhpQ0NuVnUKYVhod1lXTnJaWFNDQjJKMVptTnZibTZIQkg4QUFBR0hFQUFBQUFBQUFBQUFBQUFBQUFBQUFBR0hCS3dUQUJBdwpDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBSU5DNlJWQ3d6SzFYRnFxeVNLY0Y4QzQ5ZFlSOThjemdLNVdkcmNOCkxYYWlBaUJHYmtWeGhaeHdDaDVLQ1o1Z2M1Q2FsQ0RvaGNxVkdiaHNya0hHTFhpdHN3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" +export LND2_TLS=$LND1_TLS + +export TLSOUTSIDE1=$LND1_TLS +export TLSOUTSIDE2=$LND1_TLS + +# dev/lnd/regtest/lnd1.admin.macaroon.base64 +export LND1_MACAROON="AgEDbG5kAvgBAwoQB1FdhGa9xoewc1LEXmnURRIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgqHDdwGCqx0aQL1/Z3uUfzCpeBhfapGf9s/AZPOVwf6s=" +# dev/lnd/regtest/lnd2.admin.macaroon.base64 +export LND2_MACAROON="AgEDbG5kAvgBAwoQX0BxfhQTxLTiqaceBnGnfBIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgMAKlr1HehfBpn2R5RPE2IuY9r/18QBeLZxYgRidpos4=" + +# dev/lnd/regtest/lnd-outside-1.admin.macaroon.base64 +export MACAROONOUTSIDE1="AgEDbG5kAvgBAwoQeE+5exgz7/0ExCn7H6AJlBIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgL7pU+cKOt6zGyWTdWWmAJLP1L3cnbOPb4Rd3QtniyyM=" +# +# dev/lnd/regtest/lnd-outside-2.admin.macaroon.base64 +export MACAROONOUTSIDE2="AgEDbG5kAvgBAwoQfKO82/iPT2zIwWYPrOXvABIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYg2XkV+4Z4inbfXGZivRoY+r7KHNZhgxkCEdKByxbeb/Q=" + +# dev/lnd/loop/regtest/loopd1-1.loop.macaroon.base64 +export LND1_LOOP_MACAROON="AgEEbG9vcAJ3AwoQRGymK6/vfF3wwuVmaTj3RhIBMBoMCgRhdXRoEgRyZWFkGg8KBGxvb3ASAmluEgNvdXQaGgoLc3VnZ2VzdGlvbnMSBHJlYWQSBXdyaXRlGhUKBHN3YXASB2V4ZWN1dGUSBHJlYWQaDQoFdGVybXMSBHJlYWQAAAYgAFS/qTZItZ3ZKksQkfXAKFnsb0JS5Ok3Oi5fAgCaE/k=" +# dev/lnd/loop/regtest/loopd2-1.loop.macaroon.base64 +export LND2_LOOP_MACAROON="AgEEbG9vcAJ3AwoQ6Ntr7+DpuicdMgmVPKvDVxIBMBoMCgRhdXRoEgRyZWFkGg8KBGxvb3ASAmluEgNvdXQaGgoLc3VnZ2VzdGlvbnMSBHJlYWQSBXdyaXRlGhUKBHN3YXASB2V4ZWN1dGUSBHJlYWQaDQoFdGVybXMSBHJlYWQAAAYgU6bTJC50AuYehDtb9U2s4EuH7C2Tf8eAppPPOFeUXds=" + +# dev/lnd/loop/regtest/loopd1-1.tls.cert.base64 +export LND1_LOOP_TLS="LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNKakNDQWMyZ0F3SUJBZ0lSQU1memVXM0J0UWJaaTdxSjZoQk5vMHN3Q2dZSUtvWkl6ajBFQXdJd09URWcKTUI0R0ExVUVDaE1YYkc5dmNDQmhkWFJ2WjJWdVpYSmhkR1ZrSUdObGNuUXhGVEFUQmdOVkJBTVREREpqWkdRMQpOMlptWlRZeFpEQWVGdzB5TWpBNU1EY3lNVE15TWpSYUZ3MHlNekV4TURJeU1UTXlNalJhTURreElEQWVCZ05WCkJBb1RGMnh2YjNBZ1lYVjBiMmRsYm1WeVlYUmxaQ0JqWlhKME1SVXdFd1lEVlFRREV3d3lZMlJrTlRkbVptVTIKTVdRd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFUcU5qcVFMUGNHSURaSmtHenNlL3d2ZWt0TwpRVlhpaFJ6WmVLay9ZMFlTNDFkejB2TjlQdktaM0ZxTmN2eEN5a1cvZ1dKNWhBdEpCZTdDaTZhWitnR0tvNEcxCk1JR3lNQTRHQTFVZER3RUIvd1FFQXdJQ3BEQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBVEFQQmdOVkhSTUIKQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJRZHJscHQzM2lLdlZUZWQyVnV4Y25uQVJMeTlEQmJCZ05WSFJFRQpWREJTZ2d3eVkyUmtOVGRtWm1VMk1XU0NDV3h2WTJGc2FHOXpkSUlFZFc1cGVJSUtkVzVwZUhCaFkydGxkSUlIClluVm1ZMjl1Ym9jRWZ3QUFBWWNRQUFBQUFBQUFBQUFBQUFBQUFBQUFBWWNFckJzQUR6QUtCZ2dxaGtqT1BRUUQKQWdOSEFEQkVBaUJYaFI2VmRzSFYrREhhWGRrV2VRZ0pzMlRxT0pXajBwUXI1ZHFLcFViNjlBSWdTeGtYYTZFRQpWVk9CZ0VhNXR5Z3NBcGM2bDBSak5nVGF2SkF6T2dWT2tIWT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" +# dev/lnd/loop/regtest/loopd2-1.tls.cert.base64 +export LND2_LOOP_TLS="LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNKakNDQWN5Z0F3SUJBZ0lRVjd2dFlUbzJYS1NybWt6N3d0SjNjVEFLQmdncWhrak9QUVFEQWpBNU1TQXcKSGdZRFZRUUtFeGRzYjI5d0lHRjFkRzluWlc1bGNtRjBaV1FnWTJWeWRERVZNQk1HQTFVRUF4TU1aVEZpTkRobQpZbVUwTmpZek1CNFhEVEl5TURrd056SXhNekl5TlZvWERUSXpNVEV3TWpJeE16SXlOVm93T1RFZ01CNEdBMVVFCkNoTVhiRzl2Y0NCaGRYUnZaMlZ1WlhKaGRHVmtJR05sY25ReEZUQVRCZ05WQkFNVERHVXhZalE0Wm1KbE5EWTIKTXpCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQkVYbzlndHpkUnFMTWFhdjJ1VDA4eHlTUUpTKwpoMFNMcUMrdnpzR0RhZ2owOXg3UW9ud3oralo0eHppeklsdWVOY0JlWDYzd3VGZ0dwOTlBMW9mMDQyU2pnYlV3CmdiSXdEZ1lEVlIwUEFRSC9CQVFEQWdLa01CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUJNQThHQTFVZEV3RUIKL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkZOM2E3WkY5Y2FSUGJnNDJXYitOYnVYM2hTYU1Gc0dBMVVkRVFSVQpNRktDREdVeFlqUTRabUpsTkRZMk00SUpiRzlqWVd4b2IzTjBnZ1IxYm1sNGdncDFibWw0Y0dGamEyVjBnZ2RpCmRXWmpiMjV1aHdSL0FBQUJoeEFBQUFBQUFBQUFBQUFBQUFBQUFBQUJod1NzR3dBUU1Bb0dDQ3FHU000OUJBTUMKQTBnQU1FVUNJUURUcUpnVitReks2N3lSN1lGcWtyQkN6aEQ2OExZcjBBM1JMWFF1ckM2d21BSWdZNGdESjA1eQpRTjJCY2YvaEl6VHJwb1ZlK3Y0blBRREg3bXBFQXRyOE96MD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" + +# dev/lnd/regtest/lnd1.pubkey +export LND1_PUBKEY="03ca1907342d5d37744cb7038375e1867c24a87564c293157c95b2a9d38dcfb4c2" +# dev/lnd/regtest/lnd2.pubkey +export LND2_PUBKEY="039341ef13e776dc1611502cf510110d9ac5cdc252141f5997adcfd72cef34c3a7" + +export BITCOINDPORT=18443 + +export BITCOINDADDR=${DOCKER_HOST_IP} +export BITCOIND_SIGNER_PORT=18543 +export BITCOIND_SIGNER_ADDR=${DOCKER_HOST_IP} +export BITCOINDRPCPASS=rpcpassword + +export LND1_DNS=${DOCKER_HOST_IP} +export LND2_DNS=${DOCKER_HOST_IP} +export LNDOUTSIDE1ADDR=${DOCKER_HOST_IP} +export LNDOUTSIDE2ADDR=${DOCKER_HOST_IP} + +export LND1_RPCPORT=10009 +export LND2_RPCPORT=10010 + +export LNDOUTSIDE1RPCPORT=10012 +export LNDOUTSIDE2RPCPORT=10013 + +export LND1_TYPE=offchain,onchain +export LND2_TYPE=offchain + +export LND1_NAME=lnd1 +export LND2_NAME=lnd2 + +export MONGODB_CON=mongodb://${DOCKER_HOST_IP}:27017/galoy + +export REDIS_0_DNS=${DOCKER_HOST_IP} +export REDIS_0_PORT=6379 +export REDIS_MASTER_NAME="mymaster" +export REDIS_PASSWORD="" +export REDIS_TYPE="standalone" + +export BRIA_HOST=${DOCKER_HOST_IP} +export BRIA_PORT=2742 +export BRIA_API_KEY="bria_dev_000000000000000000000" + +export PRICE_HOST=${DOCKER_HOST_IP} +export PRICE_PORT=50051 + +export PRICE_HISTORY_HOST=${DOCKER_HOST_IP} +export PRICE_HISTORY_PORT=50052 + +export OATHKEEPER_DECISION_ENDPOINT=http://${DOCKER_HOST_IP}:4456 + +export WEBSOCKET_PORT=4000 + +export GEETEST_ID="geetest_id" +export GEETEST_KEY="geetest_key" + +export TWILIO_ACCOUNT_SID="AC_twilio_id" +export TWILIO_AUTH_TOKEN="AC_twilio_auth_token" +export TWILIO_VERIFY_SERVICE_ID="VA_twilio_service" + +export COMMITHASH="hash" +export HELMREVISION="1" + +export LOG_LEVEL="info" + +export KRATOS_MASTER_USER_PASSWORD="passwordHardtoFindWithNumber123" +export KRATOS_CALLBACK_API_KEY="The-Value-of-My-Key" +export KRATOS_PG_HOST="localhost" +export KRATOS_PG_PORT="5433" + +export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318" +# TODO: rename to OTEL_SERVICE_NAME +# https://opentelemetry.io/docs/concepts/sdk-configuration/general-sdk-configuration/#otel_service_name +export TRACING_SERVICE_NAME="galoy-dev" + +export MATTERMOST_WEBHOOK_URL="https://chat.galoy.io/hooks/sometoken" + +export KRATOS_PG_CON="postgres://dbuser:secret@localhost:5433/default?sslmode=disable" + +export UNSECURE_DEFAULT_LOGIN_CODE="000000" +export UNSECURE_IP_FROM_REQUEST_OBJECT=true + +export SVIX_SECRET="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTE2NzIwMTQsImV4cCI6MjAwNzAzMjAxNCwibmJmIjoxNjkxNjcyMDE0LCJpc3MiOiJzdml4LXNlcnZlciIsInN1YiI6Im9yZ18yM3JiOFlkR3FNVDBxSXpwZ0d3ZFhmSGlyTXUifQ.b9s0aWSisNdUNki4edabBEToLNSwjC9-AiJQr4J3y4E" +export SVIX_ENDPOINT="http://localhost:8071" +export SVIX_CALLBACK_URL=http://bats-tests:8080/webhook/ + +export KRATOS_PUBLIC_API="http://localhost:4433" +export KRATOS_ADMIN_API="http://localhost:4434" + +export HYDRA_PUBLIC_API="http://localhost:4444" +export HYDRA_ADMIN_API="http://localhost:4445" From 058bff6319b9b497cc0a4229771ee651b323bcab Mon Sep 17 00:00:00 2001 From: bodymindarts Date: Wed, 25 Oct 2023 16:27:56 +0200 Subject: [PATCH 09/10] ci: fix --test opt in tilt.yml --- .github/workflows/tilt.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tilt.yml b/.github/workflows/tilt.yml index c808aee95b..ad9e17345b 100644 --- a/.github/workflows/tilt.yml +++ b/.github/workflows/tilt.yml @@ -11,4 +11,4 @@ jobs: with: github_access_token: ${{ secrets.GITHUB_TOKEN }} - name: Tilt CI - run: nix develop -c bash -c "cd dev && xvfb-run tilt ci -- -test consent | tee tilt.log | grep test" + run: nix develop -c bash -c "cd dev && xvfb-run tilt ci -- --test consent" From b8293478bdd9766293955f7fdcce868164189de0 Mon Sep 17 00:00:00 2001 From: bodymindarts Date: Wed, 25 Oct 2023 22:29:51 +0200 Subject: [PATCH 10/10] ci: pass labels to tilt --- .github/workflows/tilt.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tilt.yml b/.github/workflows/tilt.yml index ad9e17345b..f8fe135af2 100644 --- a/.github/workflows/tilt.yml +++ b/.github/workflows/tilt.yml @@ -2,6 +2,8 @@ name: "Tilt CI" on: pull_request: branches: [ main ] + types: [opened, synchronize, labeled, unlabeled] + jobs: tests: runs-on: ubuntu-latest @@ -10,5 +12,21 @@ jobs: - uses: cachix/install-nix-action@v22 with: github_access_token: ${{ secrets.GITHUB_TOKEN }} + - name: Prepare tilt args from labels + id: prepare_args + run: | + ARGS="" + cat < labels.json + ${{ toJSON(github.event.pull_request.labels.*.name) }} + EOF + for LABEL in $(jq -r '.[]' < labels.json); do + case "$LABEL" in + dashboard|consent) + ARGS+=" $LABEL" + ;; + esac + done + echo "Prepared args: $ARGS" + echo "args=$ARGS" >> "$GITHUB_OUTPUT" - name: Tilt CI - run: nix develop -c bash -c "cd dev && xvfb-run tilt ci -- --test consent" + run: nix develop -c bash -c "cd dev && xvfb-run tilt ci -- --test ${{ steps.prepare_args.outputs.args }}"