Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/dependency-security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ jobs:
uses: dtolnay/rust-toolchain@stable

- name: Install cargo-audit
run: cargo install cargo-audit --locked
uses: taiki-e/install-action@cargo-audit

- name: Run Cargo Audit
run: cargo audit --deny warnings
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/load-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ jobs:
- name: Run k6 load test
uses: grafana/k6-action@v0.3.1
env:
BASE_URL: ${{ secrets.STAGING_BACKEND_URL || 'https://yieldvault-backend-staging.vercel.app' }}
BASE_URL: ${{ vars.STAGING_BACKEND_URL || secrets.STAGING_BACKEND_URL || 'https://yieldvault-backend-staging.vercel.app' }}
WALLET_ADDRESS: ${{ secrets.LOAD_TEST_WALLET_ADDRESS }}
ASSET: ${{ secrets.LOAD_TEST_ASSET || 'USDC' }}
AMOUNT: ${{ secrets.LOAD_TEST_AMOUNT || '100.0' }}
METRICS_PATH: ${{ secrets.LOAD_TEST_METRICS_PATH || '/metrics' }}
ASSET: ${{ vars.LOAD_TEST_ASSET || secrets.LOAD_TEST_ASSET || 'USDC' }}
AMOUNT: ${{ vars.LOAD_TEST_AMOUNT || secrets.LOAD_TEST_AMOUNT || '100.0' }}
METRICS_PATH: ${{ vars.LOAD_TEST_METRICS_PATH || secrets.LOAD_TEST_METRICS_PATH || '/metrics' }}
with:
filename: tests/load/vault-load.test.js

Expand Down
18 changes: 11 additions & 7 deletions .github/workflows/production-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ jobs:

- name: Build (production mode)
env:
VITE_SOROBAN_RPC_URL: ${{ secrets.PROD_SOROBAN_RPC_URL }}
VITE_STELLAR_NETWORK_PASSPHRASE: ${{ secrets.PROD_STELLAR_NETWORK_PASSPHRASE }}
VITE_VAULT_CONTRACT_ID: ${{ inputs.vault_contract_id || secrets.PROD_VAULT_CONTRACT_ID }}
VITE_API_BASE_URL: ${{ secrets.PROD_API_BASE_URL }}
VITE_SENTRY_DSN: ${{ secrets.PROD_SENTRY_DSN }}
VITE_SOROBAN_RPC_URL: ${{ vars.PROD_SOROBAN_RPC_URL || secrets.PROD_SOROBAN_RPC_URL }}
VITE_STELLAR_NETWORK_PASSPHRASE: ${{ vars.PROD_STELLAR_NETWORK_PASSPHRASE || secrets.PROD_STELLAR_NETWORK_PASSPHRASE }}
VITE_VAULT_CONTRACT_ID: ${{ inputs.vault_contract_id || vars.PROD_VAULT_CONTRACT_ID || secrets.PROD_VAULT_CONTRACT_ID }}
VITE_API_BASE_URL: ${{ vars.PROD_API_BASE_URL || secrets.PROD_API_BASE_URL }}
VITE_SENTRY_DSN: ${{ vars.PROD_SENTRY_DSN || secrets.PROD_SENTRY_DSN }}
VITE_SENTRY_ENVIRONMENT: production
VITE_SENTRY_TRACES_SAMPLE_RATE: '0.1'
VITE_SENTRY_REPLAYS_SESSION_SAMPLE_RATE: '0.1'
Expand Down Expand Up @@ -167,13 +167,17 @@ jobs:

- name: Pull Vercel environment info
working-directory: frontend
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
run: vercel pull --yes --environment=production

- name: Deploy to Vercel (production)
id: deploy
working-directory: frontend
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
run: |
DEPLOY_URL=$(vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} 2>&1 | tail -1)
DEPLOY_URL=$(vercel deploy --prebuilt --prod 2>&1 | tail -1)
echo "production_url=$DEPLOY_URL" >> $GITHUB_OUTPUT
echo "Deployed to: $DEPLOY_URL"

Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/rust-security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ jobs:
working-directory: frontend

- name: Install cargo-audit
run: cargo install cargo-audit --version 0.22.1 --locked
uses: taiki-e/install-action@v2
with:
tool: cargo-audit@0.22.1

- name: Run cargo audit
run: |
Expand All @@ -78,8 +80,15 @@ jobs:
run: cargo clippy -p share-price-math --all-targets -- -D warnings
working-directory: contracts

- name: Install cargo-deny
uses: taiki-e/install-action@v2
with:
tool: cargo-deny@0.18.3

- name: Run cargo-deny (supply chain security)
run: |
cargo deny check advisories
cargo deny check licenses
cargo install cargo-deny --version 0.18.3 --locked
cargo deny check advisories bans licenses sources

Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/rust-wasm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ jobs:
${{ runner.os }}-cargo-fuzz-

- name: Install cargo-fuzz
run: cargo install cargo-fuzz --locked
uses: taiki-e/install-action@cargo-fuzz

- name: Run share_price_math fuzz target
run: |
Expand Down Expand Up @@ -184,6 +184,9 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: package-lock.json
node-version-file: '.nvmrc'

- name: Install root dependencies
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/staging-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ jobs:
sudo apt-get install -y pkg-config libdbus-1-dev

- name: Install Soroban CLI
uses: taiki-e/install-action@v2
with:
tool: soroban-cli
run: |
cargo install --locked soroban-cli --version 21.0.0

Expand All @@ -54,6 +57,7 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: 'npm'
cache-dependency-path: backend/package-lock.json

- name: Deploy Backend
if: env.DATABASE_URL != '' && env.VERCEL_TOKEN != ''
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/test-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: backend/package-lock.json
node-version-file: '.nvmrc'

- name: Install Dependencies
Expand Down Expand Up @@ -52,6 +55,9 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: frontend/package-lock.json
node-version-file: '.nvmrc'

- name: Install Dependencies
Expand Down Expand Up @@ -80,6 +86,9 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: package-lock.json
node-version-file: '.nvmrc'

- name: Download Backend Coverage Artifact
Expand Down
31 changes: 25 additions & 6 deletions backend/src/middleware/tenantGuard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export interface TenantGuardOptions {
* Defaults to Permission.ADMIN_READ.
*/
adminBypassPermission?: Permission;
/**
* Whether anonymous requests should be rejected with 401 Unauthorized.
*/
requireAuth?: boolean;
}

/**
Expand Down Expand Up @@ -84,23 +88,29 @@ export function tenantGuard(options: TenantGuardOptions) {
walletParamPath,
allowAdminBypass = false,
adminBypassPermission = Permission.ADMIN_READ,
requireAuth = false,
} = options;

return (req: Request, res: Response, next: NextFunction): void => {
// 1. Extract the requested wallet address from the request
const rawRequestedWallet = extractFromRequest(req, walletParamPath);
if (!rawRequestedWallet || typeof rawRequestedWallet !== 'string') {
if (requireAuth) {
res.status(401).json({
error: 'Unauthorized',
status: 401,
message: 'Authentication required',
});
return;
}
// If no wallet is requested, this might be a public endpoint or
// a different kind of resource – let it pass through for now
next();
return;
}
const requestedWallet = normalizeWalletAddress(rawRequestedWallet);

// 2. Get the authenticated wallet address
const authenticatedWallet = getAuthenticatedWalletAddress(req);

// 3. Check for admin bypass
// 2. Check for admin bypass
const authHeader = req.headers.authorization || '';
const isApiKey = authHeader.match(/^ApiKey\s+(.+)$/i);
if (isApiKey && allowAdminBypass && hasPermission(req, adminBypassPermission)) {
Expand All @@ -109,10 +119,19 @@ export function tenantGuard(options: TenantGuardOptions) {
return;
}

// 3. Get the authenticated wallet address
const authenticatedWallet = getAuthenticatedWalletAddress(req);

// 4. Check if authenticated wallet matches requested wallet.
// Anonymous requests should be allowed to view public wallet data
// (for example, transaction history or portfolio listings).
if (!authenticatedWallet) {
if (requireAuth) {
res.status(401).json({
error: 'Unauthorized',
status: 401,
message: 'Authentication required',
});
return;
}
next();
return;
}
Expand Down
Loading
Loading