Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/common/API/storageAPI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const fetchCsrfToken = async (documentStorageURL: string): Promise<string> => {
method: "get",
url: url,
withCredentials: true,
headers: getHeaders(),
});

const csrfToken = response.data.csrfToken;
Expand Down Expand Up @@ -49,7 +50,7 @@ export const getQueueNumber = async (documentStorageURL: string): Promise<AxiosR
return axios({
method: "get",
url: url,
headers: getHeaders(documentStorageURL),
headers: getHeaders(),
});
};

Expand Down
2 changes: 1 addition & 1 deletion src/common/hooks/useQueue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export const useQueue = ({ formEntry, formTemplate }: UseQueue): UseQueueReturn
try {
setQueueState(QueueState.PENDING);

const documentStorageURL = process.env.DOCUMENT_STORAGE_URL;
const documentStorageURL = "http://localhost:5080/.netlify/functions/storage"; //process.env.DOCUMENT_STORAGE_URL;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Do not hardcode storage URL; gate to preview/local or revert before merge

Merging this will force all environments to localhost. Restore env-based config with a local fallback for dev/test only.

Apply this minimal fix:

-      const documentStorageURL = "http://localhost:5080/.netlify/functions/storage"; //process.env.DOCUMENT_STORAGE_URL;
+      const documentStorageURL =
+        process.env.DOCUMENT_STORAGE_URL ||
+        (window.location.hostname === "localhost"
+          ? "http://localhost:5080/.netlify/functions/storage"
+          : "");

Optionally, fail gracefully in non-local envs by skipping QR/storage when the URL is empty instead of throwing.

Run locally and on preview to confirm:

  • Local: QR shows and uploads hit localhost storage.
  • Preview/Prod: reads DOCUMENT_STORAGE_URL and never points to localhost.

I'll verify the review comment by examining the actual code and checking the codebase for context.


🏁 Script executed:

#!/bin/bash
# Check the actual code at line 133 in useQueue.tsx
fd -e tsx -e ts 'useQueue' | head -5

Length of output: 29


🏁 Script executed:

#!/bin/bash
# Search for the file and inspect around line 133
find . -name "useQueue.tsx" -type f 2>/dev/null | head -1 | xargs -I {} sh -c 'cat -n {} | sed -n "125,140p"'

Length of output: 624


🏁 Script executed:

#!/bin/bash
# Search for DOCUMENT_STORAGE_URL references throughout codebase
rg -n 'DOCUMENT_STORAGE_URL|documentStorageURL' --type=ts --type=tsx -C 3

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check for environment configuration files
fd -e 'env' -e 'env.local' -e 'env.example' | head -10

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for localhost:5080 to understand scope of hardcoding
rg -n 'localhost:5080' --type=ts --type=tsx --type=json -C 2

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check environment setup in package.json or build config
find . -maxdepth 2 -name "package.json" -o -name "*.config.*" | xargs grep -l "DOCUMENT_STORAGE_URL" 2>/dev/null || echo "No env config found"

Length of output: 19


🏁 Script executed:

#!/bin/bash
# Search for DOCUMENT_STORAGE_URL without type flags
rg -n 'DOCUMENT_STORAGE_URL' -C 3

Length of output: 503


🏁 Script executed:

#!/bin/bash
# Search for localhost:5080 without type flags
rg -n 'localhost:5080' -C 2

Length of output: 373


🏁 Script executed:

#!/bin/bash
# Check the entire function context around line 133
cat -n src/common/hooks/useQueue.tsx | sed -n "120,160p"

Length of output: 2017


🏁 Script executed:

#!/bin/bash
# Look for env setup in CI/deployment files
find . -maxdepth 3 \( -name ".github" -o -name ".gitlab-ci*" -o -name "netlify*" -o -name "*workflow*" \) -type d 2>/dev/null | head -5

Length of output: 29


🏁 Script executed:

#!/bin/bash
# Search for any env variable pattern setup
rg -n 'process\.env\.' -C 2 src/common/hooks/useQueue.tsx

Length of output: 489


🏁 Script executed:

#!/bin/bash
# Check if there are deployment or env config files at root
ls -la | grep -E 'env|netlify|deploy|config'

Length of output: 963


🏁 Script executed:

#!/bin/bash
# Check the content of env files to see if DOCUMENT_STORAGE_URL is defined
echo "=== .env.sample ===" && cat .env.sample && echo -e "\n=== .env.development ===" && cat .env.development && echo -e "\n=== .env.production ===" && cat .env.production && echo -e "\n=== .env.test ===" && cat .env.test

Length of output: 977


🏁 Script executed:

#!/bin/bash
# Check netlify.toml for environment variable configuration
cat netlify.toml

Length of output: 583


🏁 Script executed:

#!/bin/bash
# Check GitHub workflows for environment setup
find .github/workflows -name "*.yml" -o -name "*.yaml" | head -3 | xargs -I {} sh -c 'echo "=== {} ===" && cat {}'

Length of output: 3295


🏁 Script executed:

#!/bin/bash
# Check how documentStorageURL is actually used in the function
cat -n src/common/hooks/useQueue.tsx | sed -n "160,200p"

Length of output: 1779


Hardcoded localhost URL will break production deployments; restore environment-based config

Line 133 hardcodes documentStorageURL to localhost, but the required environment variable DOCUMENT_STORAGE_URL is documented in .env.sample and never initialized in .env files or GitHub workflows. In production, the code will either throw an error at line 168 or attempt to reach an inaccessible localhost endpoint.

-      const documentStorageURL = "http://localhost:5080/.netlify/functions/storage"; //process.env.DOCUMENT_STORAGE_URL;
+      const documentStorageURL =
+        process.env.DOCUMENT_STORAGE_URL ||
+        (window.location.hostname === "localhost"
+          ? "http://localhost:5080/.netlify/functions/storage"
+          : "");

Verify locally that QR generation and storage upload work, and confirm that staging/production use the correct DOCUMENT_STORAGE_URL from environment variables.

🤖 Prompt for AI Agents
In src/common/hooks/useQueue.tsx around line 133 the documentStorageURL is
hardcoded to "http://localhost:5080/.netlify/functions/storage"; restore using
the environment variable DOCUMENT_STORAGE_URL (process.env.DOCUMENT_STORAGE_URL)
instead, add a runtime check that throws or logs a clear error if the variable
is missing, and ensure local .env, staging and production GitHub workflow/env
configurations are updated to provide the correct URL; after making the change,
verify locally that QR generation and storage upload work against the configured
endpoint and confirm staging/production pick up the env var.


const mergedCredentialSubject = {
...formTemplate.defaults.credentialSubject,
Expand Down
170 changes: 132 additions & 38 deletions src/test/creator/happy-flow-magiclink.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
validateMagicIframeSelector,
waitForFileDownload,
} from "../helper";
import * as dotenv from "dotenv";
dotenv.config();

let mailslurp: MailSlurp;
const MAILSLURP_API_KEY = process.env.MAILSLURP_API_KEY;
Expand Down Expand Up @@ -67,63 +69,154 @@ test("should complete full create > issue > verify flow for Transferable Documen
return;
}

// console.log("🚀 Starting Magic Link integration test", getLocation());
// console.log(`📧 Using inbox: ${MAILSLURP_INDEX_ID}`);

// create email address for a test user
// console.log("📬 Fetching test inbox...");
const inbox = await mailslurp.getInbox(MAILSLURP_INDEX_ID);
// console.log(`📧 Test email: ${inbox.emailAddress}`);

const magicLinkConnect = async () => {
// Step 1: Navigate to creator page
await navigateToCreator();

// Step 2: Preview "Bill of Lading" form
await t.click(billOfLadingTitle);
await t.expect(previewOverlay.exists).ok("Preview overlay should be visible");

// Step 3: Create document
await t.click(createDocumentButton);

// Step 4: Connect to Blockchain Wallet: Magiclink
await t.expect(connectBlockchainModal.exists).ok("Connect to Blockchain Wallet modal should appear");
await t.click(Selector('[data-testid="connect-magic-header"]'));
await t.expect(connectToMagicLink.exists).ok("Connect to MagicLink should appear");
await t.click(connectToMagicLink);

// Step 5: Sign in to Magic
// console.log("🔐 Initiating Magic Link sign-in...");
await t.wait(2000); // Increased wait for iframe loading

await validateMagicIframeSelector(Selector("p").withText("Sign in to"));
await clickMagicIframeButton(Selector('button[aria-label="Email"]'));
await t.wait(3000); // Wait for email input form to load

// Step 6: Enter email address
// console.log(`📧 Entering email: ${inbox.emailAddress}`);
await inputMagicIframeTexts(emailInput, inbox.emailAddress);
await clickMagicIframeButton(signInButton);
// console.log("✉️ Email submitted, waiting for verification...");
};

await magicLinkConnect();

// console.log("⏳ Waiting for Magic Link response...");
await t.wait(5000); // Increased wait for Magic processing

// Check what Magic is showing
const deviceRegistrationText = Selector("h4").withText(/Please register this device to continue/);

if (await deviceRegistrationText.exists) {
console.log("🔐 Device registration required - waiting for registration email...");
// Switch to iframe to check what screen we're on
await t.switchToIframe(Selector(".magic-iframe"));
try {
// Wait for device registration email with longer timeout for CI
const registrationEmail = await mailslurp.waitForLatestEmail(inbox.id, 60000, true);
console.log("📧 Registration email received");

// Extract the registration link from the email
const registrationLinkMatch = /https:\/\/[^\s<>"]+/.exec(registrationEmail!.body!);
const registrationLink = registrationLinkMatch?.[0];

if (!registrationLink) {
throw new Error("Registration link not found in email");
}

console.log("🔗 Navigating to registration link...");
await t.switchToMainWindow();
await t.navigateTo(registrationLink);
await t.wait(5000); // Increased wait for page load

const approveButton = Selector("button").withText("Approve");
await t.expect(approveButton.exists).ok("Approve button should be visible");
await t.click(approveButton);
console.log("✅ Device registration approved");

await t.wait(2000);
} catch (error) {
console.error("❌ Device registration failed:", error);
throw error;
}
// Step 1: Navigate to creator page
await t.navigateTo(`${location}`);

await magicLinkConnect();
}

// Step 1: Navigate to creator page
await navigateToCreator();

// Step 2: Preview "Bill of Lading" form
await t.click(billOfLadingTitle);
await t.expect(previewOverlay.exists).ok("Preview overlay should be visible");

// Step 3: Create document
await t.click(createDocumentButton);

// Step 4: Connect to Blockchain Wallet: Magiclink
await t.expect(connectBlockchainModal.exists).ok("Connect to Blockchain Wallet modal should appear");
await t.click(Selector('[data-testid="connect-magic-header"]'));
await t.expect(connectToMagicLink.exists).ok("Connect to MagicLink should appear");
await t.click(connectToMagicLink);

// Step 5: Sign in to Magic
await t.wait(1000);
await validateMagicIframeSelector(Selector("p").withText("Sign in to"));
await inputMagicIframeTexts(emailInput, inbox.emailAddress);
await clickMagicIframeButton(signInButton);

// wait for verification code to arrive to email then extract code
// console.log("📧 Waiting for verification code email...");
// wait for verification code to arrive to email then extract code with longer timeout for CI
const email = await mailslurp.waitForLatestEmail(inbox.id, 30000, true);
// console.log("📧 Verification email received");
// use regex to extract the confirmation code which is 6 digits
const code = /[^#]([0-9]{6})/.exec(email!.body!)?.[1];

if (!code) {
console.error("❌ Failed to extract verification code from email:", email.body);
throw new Error("Verification code not found in email");
}

Comment on lines 160 to +169
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Make verification-code regex robust

The current /^#/ fails at string start and is brittle. Use word boundaries.

-  const code = /[^#]([0-9]{6})/.exec(email!.body!)?.[1];
+  const code = email?.body?.match(/\b(\d{6})\b/)?.[1];

Also consider trimming and normalizing email body if HTML.

🤖 Prompt for AI Agents
In src/test/creator/happy-flow-magiclink.spec.ts around lines 160-169, the
current regex /[^#]([0-9]{6})/ is brittle and fails at string start; replace it
with a word-boundary based pattern such as /\b(\d{6})\b/ to reliably capture a
6-digit code, and before applying the regex normalize the email body by trimming
and converting HTML emails to plain text (strip tags or use the email's
textContent) and collapse whitespace so the code can be consistently matched.

// console.log(`🔢 Extracted verification code: ${code}`);

// Step 6: Enter verification code
await t.wait(1000);
// console.log("🔢 Entering verification code...");
await t.wait(2000); // Wait for code input form to load
await validateMagicIframeSelector(Selector("h4").withText(/Please enter the code sent to/));
// console.log("🔢 Validating iframe code input...");
await inputMagicIframeTexts(codeInput, code!);
await t.wait(5000);
// console.log("✅ Verification code entered, waiting for validation...");
await t.wait(2000); // Increased wait for code validation

// Step 7: Get wallet address
// console.log("💰 Retrieving wallet address...");
await t.expect(walletAddressDiv.exists).ok("Wallet address should be visible");
const walletAddress = await walletAddressDiv.innerText;
// console.log(`💰 Wallet address: ${walletAddress}`);

// Step 8: Transfer funds to wallet
const wallet = new ethers.Wallet("0xe82294532bcfcd8e0763ee5cef194f36f00396be59b94fb418f5f8d83140d9a7");
const provider = new ethers.providers.JsonRpcProvider("http://localhost:8545");
const signer = wallet.connect(provider);
const balance = await signer.getBalance();
if (!balance.gt(BigNumber.from("10000000000000000000"))) {
// 10 ethers
throw new Error("Insufficient balance");
// console.log("💸 Transferring funds to Magic wallet...");
try {
const wallet = new ethers.Wallet("0xe82294532bcfcd8e0763ee5cef194f36f00396be59b94fb418f5f8d83140d9a7");
const provider = new ethers.providers.JsonRpcProvider("http://localhost:8545");
const signer = wallet.connect(provider);

const balance = await signer.getBalance();
// console.log(`💰 Funder wallet balance: ${ethers.utils.formatEther(balance)} ETH`);

if (!balance.gt(BigNumber.from("10000000000000000000"))) {
throw new Error(`Insufficient balance: ${ethers.utils.formatEther(balance)} ETH`);
}
Comment on lines +188 to +199
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Avoid committing a hardcoded private key; read from env

Use an env var for the funder key; fail with a clear error if missing.

-    const wallet = new ethers.Wallet("0xe82294532bcfcd8e0763ee5cef194f36f00396be59b94fb418f5f8d83140d9a7");
+    const FUNDER_PRIVATE_KEY = process.env.FUNDER_PRIVATE_KEY;
+    if (!FUNDER_PRIVATE_KEY) {
+      throw new Error("Missing FUNDER_PRIVATE_KEY for test funding");
+    }
+    const wallet = new ethers.Wallet(FUNDER_PRIVATE_KEY);

Ensure .env/.env.ci define FUNDER_PRIVATE_KEY for CI runs.


I'll verify the review comment by examining the codebase. Let me check if the hardcoded private key exists, current environment variable handling, and the state of .env files.


---

🏁 Script executed:

```shell
#!/bin/bash
# Check if file exists and show the problematic code section
if [ -f "src/test/creator/happy-flow-magiclink.spec.ts" ]; then
  echo "=== File found. Showing lines 185-205 ==="
  sed -n '185,205p' "src/test/creator/happy-flow-magiclink.spec.ts"
else
  echo "File not found"
fi

Length of output: 888


🏁 Script executed:

#!/bin/bash
# Search for FUNDER_PRIVATE_KEY usage across the codebase
echo "=== Searching for FUNDER_PRIVATE_KEY ==="
rg -n "FUNDER_PRIVATE_KEY" --type ts --type js

echo ""
echo "=== Searching for hardcoded private keys (0x pattern with 64 hex chars) ==="
rg -nP '0x[a-fA-F0-9]{64}' --type ts --type js src/test/

Length of output: 4298


🏁 Script executed:

#!/bin/bash
# Check for .env and .env.ci files
echo "=== Checking for .env files ==="
find . -maxdepth 2 -name ".env*" -type f | head -20

echo ""
echo "=== Checking for .env.example or similar ==="
fd -e "env" -e "example" . -H | grep -i env | head -20

Length of output: 140


🏁 Script executed:

#!/bin/bash
# Check if process.env is used for secrets in test setup
echo "=== Checking for process.env usage in test files ==="
rg -n "process\.env" src/test/ --type ts --type js -C 2 | head -50

Length of output: 561


Let me check the .env files to see if FUNDER_PRIVATE_KEY is already defined there:


---

🏁 Script executed:

```shell
#!/bin/bash
# Check contents of .env files
echo "=== .env.test ==="
cat .env.test

echo ""
echo "=== .env.sample ==="
cat .env.sample

echo ""
echo "=== .env.development ==="
cat .env.development

Length of output: 921


🏁 Script executed:

#!/bin/bash
# Check if there's any documentation or comments about the hardcoded key
echo "=== Context around the hardcoded key in happy-flow-magiclink.spec.ts ==="
sed -n '180,210p' "src/test/creator/happy-flow-magiclink.spec.ts"

echo ""
echo "=== Check if this is a test-only file ==="
head -20 "src/test/creator/happy-flow-magiclink.spec.ts"

Length of output: 1902


Remove hardcoded private key and read from environment variable

The hardcoded key at line 190 is confirmed. Move it to process.env.FUNDER_PRIVATE_KEY with validation, matching the pattern already used in this file for MAILSLURP_API_KEY:

-    const wallet = new ethers.Wallet("0xe82294532bcfcd8e0763ee5cef194f36f00396be59b94fb418f5f8d83140d9a7");
+    const FUNDER_PRIVATE_KEY = process.env.FUNDER_PRIVATE_KEY;
+    if (!FUNDER_PRIVATE_KEY) {
+      throw new Error("Missing FUNDER_PRIVATE_KEY for test funding");
+    }
+    const wallet = new ethers.Wallet(FUNDER_PRIVATE_KEY);

Add FUNDER_PRIVATE_KEY= to .env.sample and define it in .env.test for local test runs and in CI environment variables for CI runs.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// console.log("💸 Transferring funds to Magic wallet...");
try {
const wallet = new ethers.Wallet("0xe82294532bcfcd8e0763ee5cef194f36f00396be59b94fb418f5f8d83140d9a7");
const provider = new ethers.providers.JsonRpcProvider("http://localhost:8545");
const signer = wallet.connect(provider);
const balance = await signer.getBalance();
// console.log(`💰 Funder wallet balance: ${ethers.utils.formatEther(balance)} ETH`);
if (!balance.gt(BigNumber.from("10000000000000000000"))) {
throw new Error(`Insufficient balance: ${ethers.utils.formatEther(balance)} ETH`);
}
// console.log("💸 Transferring funds to Magic wallet...");
try {
const FUNDER_PRIVATE_KEY = process.env.FUNDER_PRIVATE_KEY;
if (!FUNDER_PRIVATE_KEY) {
throw new Error("Missing FUNDER_PRIVATE_KEY for test funding");
}
const wallet = new ethers.Wallet(FUNDER_PRIVATE_KEY);
const provider = new ethers.providers.JsonRpcProvider("http://localhost:8545");
const signer = wallet.connect(provider);
const balance = await signer.getBalance();
// console.log(`💰 Funder wallet balance: ${ethers.utils.formatEther(balance)} ETH`);
if (!balance.gt(BigNumber.from("10000000000000000000"))) {
throw new Error(`Insufficient balance: ${ethers.utils.formatEther(balance)} ETH`);
}
🤖 Prompt for AI Agents
In src/test/creator/happy-flow-magiclink.spec.ts around lines 188 to 199,
replace the hardcoded private key with process.env.FUNDER_PRIVATE_KEY and
validate it the same way MAILSLURP_API_KEY is validated: check the env var
exists, matches expected hex/private-key pattern (0x + 64 hex chars) and throw a
clear error if missing/invalid; use that value to create the ethers.Wallet and
keep the rest of the logic unchanged. Also add FUNDER_PRIVATE_KEY= to
.env.sample and set a test key in .env.test and ensure CI secrets include
FUNDER_PRIVATE_KEY.


const tx = await signer.sendTransaction({
to: walletAddress,
value: BigNumber.from("10000000000000000000"), // 10 ethers
});

// console.log(`🔗 Transaction hash: ${tx.hash}`);
await tx.wait();
// console.log(`✅ Transaction confirmed in block: ${receipt.blockNumber}`);

// Clean up email
await mailslurp.deleteEmail(email.id);
// console.log("🗑️ Verification email deleted");
} catch (error) {
// console.error("❌ Fund transfer failed:", error);
throw error;
}
const tx = await signer.sendTransaction({
to: walletAddress,
value: BigNumber.from("10000000000000000000"), // 10 ethers
});
await tx.wait();
await mailslurp.deleteEmail(email.id);

// Step 9: Network Selector
// console.log(" 🌐 Selecting network...");
await t.expect(networkSelector.exists).ok("Network selector should appear");
await t.click(continueConnectBlockchainModal);

Expand Down Expand Up @@ -154,6 +247,7 @@ test("should complete full create > issue > verify flow for Transferable Documen
await t.click(formNextButton);
await t.expect(getLocation()).contains("/creator/publish", "Should navigate to publish page");
await t.expect(processTitle.exists).ok("Issuance success title should be visible");
await t.wait(5000);
await t.expect(processTitle.innerText).eql("Document issued successfully");

// Step 15: Download issued document
Expand Down
Loading