Skip to content

Conversation

@ignaciosantise
Copy link
Collaborator

@ignaciosantise ignaciosantise commented Dec 30, 2025

This pull request introduces significant improvements to the merchant authentication and payment flow in the POS app, refactoring how merchant credentials are managed and simplifying the settings UI. It also enhances the payment initiation and status APIs for merchant authentication, removes redundant merchant confirmation components, and adds a convenient QR code copy feature for payment URLs.

Merchant Authentication & API Integration

  • Refactored API calls in payment.ts to require merchant-specific headers, including API key and merchant ID, retrieved securely via expo-secure-store. The payment endpoints now use /merchant/payment routes, improving security and traceability. [1] [2] [3]

Settings UI & Merchant Credential Management

  • Reworked the settings screen to split merchant ID and merchant API key into separate, clearly labeled input sections, with independent save actions and validation. Removed merchant address lookup and confirmation flows for a simpler experience. [1] [2] [3] [4] [5]
  • Removed the now-unnecessary MerchantAddressRow and MerchantConfirmModal components, cleaning up unused code. [1] [2]

Payment Initiation & QR Code Improvements

  • Updated the payment initiation logic to use a new request format and generate a payment URL with the new /merchant/payment endpoint. Added a feature to copy the payment URL to the clipboard by tapping the QR code, with a success toast confirmation. [1] [2] [3]

Secure Storage Integration

  • Added configuration for expo-secure-store to app.json to support secure credential storage and biometric authentication.

Minor Codebase Improvements

  • Cleaned up imports and improved code consistency across several components. [1] [2]

Copilot AI review requested due to automatic review settings December 30, 2025 12:12
@vercel
Copy link

vercel bot commented Dec 30, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
react-native-examples Ready Ready Preview, Comment Jan 5, 2026 1:39pm

@claude
Copy link

claude bot commented Dec 30, 2025

Claude finished @ignaciosantise's task in 1m 44s —— View job


Code Review Complete

Reviewed PR #297: Migration to new merchant authentication system


Issue 1: Cryptographically weak PIN hashing algorithm

ID: usesettingsst-weak-pin-hash-a7b3
File: dapps/pos-app/store/useSettingsStore.ts:10
Severity: HIGH
Category: security

The PIN hashing function uses a simple bitwise hash that is not cryptographically secure. This custom hash function can be easily reversed or brute-forced, undermining the security of PIN protection for merchant credentials.

function hashPin(pin: string): string {
  let hash = 0;
  for (let i = 0; i < pin.length; i++) {
    const char = pin.charCodeAt(i);
    hash = (hash << 5) - hash + char;
    hash = hash & hash;
  }
  return hash.toString(16);
}

Recommendation: Use a proper cryptographic hash function like SHA-256 or bcrypt for PIN hashing:

import { Crypto } from 'expo-crypto';

async function hashPin(pin: string): Promise<string> {
  return await Crypto.digestStringAsync(
    Crypto.CryptoDigestAlgorithm.SHA256,
    pin
  );
}

Exploit Scenario: An attacker with access to the device's storage could extract the PIN hash and reverse-engineer the 4-digit PIN through brute force or rainbow tables, gaining unauthorized access to merchant credentials.


Issue 2: Insecure web fallback storage for sensitive credentials

ID: securestorag-localstorage-fallback-c4d2
File: dapps/pos-app/utils/secure-storage.web.ts:10
Severity: HIGH
Category: security

The web implementation stores sensitive data (API keys, PIN hashes) in browser localStorage, which is not encrypted and accessible to any JavaScript running on the page. This completely negates the security benefits of using secure storage on native platforms.

Recommendation: For web platform, implement proper security measures:

// Option 1: Warn users that web platform is not secure for sensitive data
if (isBrowser && key === SECURE_STORAGE_KEYS.MERCHANT_API_KEY) {
  console.warn('Web platform does not support secure credential storage');
}

// Option 2: Use Web Crypto API for encryption before localStorage
// Option 3: Disable merchant API key storage on web entirely

Exploit Scenario: On web platform, XSS attacks or browser extensions can read merchant API keys and PIN hashes directly from localStorage, bypassing all security measures.


Issue 3: Missing API key validation in header construction

ID: payment-missing-apikey-validation-f8a1
File: dapps/pos-app/api/payment.ts:17
Severity: MEDIUM
Category: code_quality

The getApiHeaders function retrieves the API key from secure storage but doesn't validate its format or presence before using it. An empty string could pass the null check and be sent in headers.

Recommendation: Add proper validation:

if (!merchantApiKey || merchantApiKey.trim().length === 0) {
  throw new Error("Merchant API key is not configured");
}

Issue 4: Async hash function not awaited in PIN verification

ID: usesettingsst-async-hash-timing-2b9f
File: dapps/pos-app/store/useSettingsStore.ts:127
Severity: MEDIUM
Category: code_quality

If the PIN hashing is changed to use async cryptographic functions (as recommended), the current synchronous hashPin calls will break. The verification logic assumes synchronous hashing.

Recommendation: When implementing crypto hashing, ensure all hash calls are properly awaited:

const hashedPin = await hashPin(pin);
const isValid = storedPinHash !== null && hashedPin === storedPinHash;

Issue 5: Hard-coded SDK version in API headers

ID: payment-hardcoded-sdk-version-3e7c
File: dapps/pos-app/api/payment.ts:33
Severity: LOW
Category: code_quality

SDK version "1.0.0" is hard-coded in API headers, requiring code changes for version updates.

Recommendation: Import version from package.json or app config:

import { version } from '../../package.json';

// In headers:
"Sdk-Version": version,

Issue 6: Missing input sanitization for merchant ID

ID: settings-merchant-id-no-sanitization-9a2c
File: dapps/pos-app/app/settings.tsx:201
Severity: LOW
Category: security

Merchant ID input is trimmed but not sanitized. Special characters or injection attempts are not validated.

Recommendation: Add input validation pattern:

const MERCHANT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;

if (!MERCHANT_ID_PATTERN.test(trimmedMerchantId)) {
  showErrorToast("Merchant ID contains invalid characters");
  return;
}

Issue 7: Clipboard operation without error handling

ID: scan-clipboard-no-error-handling-5d1f
File: dapps/pos-app/app/scan.tsx:91
Severity: LOW
Category: code_quality

The QR code copy-to-clipboard operation doesn't handle potential failures.

Recommendation: Add try-catch for clipboard operations:

const handleCopyPaymentUrl = async () => {
  try {
    await Clipboard.setStringAsync(qrUri);
    showSuccessToast("Payment URL copied");
  } catch (error) {
    showErrorToast("Failed to copy to clipboard");
  }
};

Automated Checks

✅ No workflow security issues detected
✅ No external domain URLs detected
✅ No cache-control issues detected


Summary

7 issues found: 2 HIGH severity (weak PIN hashing, insecure web storage), 2 MEDIUM severity (missing validation, async timing), 3 LOW severity.

Critical security concerns with PIN hashing and web storage fallback need immediate attention before merchant credentials are stored in production.

</ThemedText>
<View style={styles.merchantInputRow}>
<TextInput
value={merchantIdInput}

Choose a reason for hiding this comment

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

🤖 Auto Review Issue: Missing input sanitization for merchant ID

Severity: LOW
Category: security
Tool: Claude Auto Review

Recommendation: Add input validation pattern:

const MERCHANT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;

if (!MERCHANT_ID_PATTERN.test(trimmedMerchantId)) {
  showErrorToast("Merchant ID contains invalid characters");
  return;
}

};

const handleCopyPaymentUrl = async () => {
await Clipboard.setStringAsync(qrUri);

Choose a reason for hiding this comment

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

🤖 Auto Review Issue: Clipboard operation without error handling

Severity: LOW
Category: code_quality
Tool: Claude Auto Review

Recommendation: Add try-catch for clipboard operations:

const handleCopyPaymentUrl = async () => {
  try {
    await Clipboard.setStringAsync(qrUri);
    showSuccessToast("Payment URL copied");
  } catch (error) {
    showErrorToast("Failed to copy to clipboard");
  }
};

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR migrates the POS app to a new merchant authentication system, transitioning from public payment endpoints to authenticated merchant-specific APIs. The refactor introduces secure credential storage, simplifies the settings UI by removing merchant address verification, and enhances the payment flow with new API request formats.

Key Changes:

  • Merchant authentication now requires both a merchant ID and API key, stored securely via expo-secure-store with credentials passed as headers to new /merchant/payment endpoints
  • Settings UI redesigned with separate inputs for merchant ID and API key, removing the previous merchant address lookup and confirmation flow
  • PIN and API key credentials migrated from Zustand storage to platform-specific secure storage (expo-secure-store for native, localStorage for web)

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
dapps/pos-app/utils/types.ts Updated payment request structure to use referenceId and structured amount object with unit
dapps/pos-app/utils/secure-storage.web.ts Added web implementation of secure storage using localStorage with prefix
dapps/pos-app/utils/secure-storage.ts Added native implementation using expo-secure-store for encrypted credential storage
dapps/pos-app/store/useSettingsStore.ts Refactored to move PIN and API key to secure storage; added migration logic from v6 to v8
dapps/pos-app/package.json Added expo-secure-store dependency
dapps/pos-app/package-lock.json Lockfile updates for expo-secure-store and type dependencies
dapps/pos-app/hooks/use-merchant-flow.ts Completely refactored to handle separate merchant ID and API key flows with PIN protection
dapps/pos-app/hooks/use-biometric-auth.ts Updated to use async isPinSet method from settings store
dapps/pos-app/components/qr-code.tsx Made QR code pressable with optional onPress handler
dapps/pos-app/components/pin-modal.tsx Minor cleanup of imports and biometric icon styling
dapps/pos-app/components/merchant-confirm-modal.tsx Removed unused merchant confirmation modal component
dapps/pos-app/components/merchant-address-row.tsx Removed unused merchant address display component
dapps/pos-app/app/settings.tsx Redesigned with separate merchant ID and API key sections, removed address verification
dapps/pos-app/app/scan.tsx Updated to use new payment request format and added QR code copy-to-clipboard feature
dapps/pos-app/app.json Added expo-secure-store configuration with biometric permissions
dapps/pos-app/api/payment.ts Added merchant authentication headers function and updated endpoints to /merchant/payment routes
Files not reviewed (1)
  • dapps/pos-app/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ignaciosantise
Copy link
Collaborator Author

@claude review

@claude
Copy link

claude bot commented Dec 30, 2025

Claude finished @ignaciosantise's task in 2m 1s —— View job


Code Review Complete

Reviewed PR #297: Migration to new merchant authentication system


Issues Found

Issue 1: Cryptographically weak PIN hashing algorithm

ID: usesettingsst-weak-pin-hash-a7b3
File: dapps/pos-app/store/useSettingsStore.ts:10
Severity: HIGH
Category: security

The PIN hashing function (line 10-18) uses a simple bitwise hash ((hash << 5) - hash + char) that is not cryptographically secure. This custom hash function can be easily reversed or brute-forced, undermining the security of PIN protection for merchant credentials.

While the comment acknowledges "not cryptographically secure, but sufficient for local protection", the PIN protects access to merchant API keys stored in secure storage. An attacker with device access could extract the PIN hash and reverse-engineer the 4-digit PIN through brute force.

Recommendation: Use a proper cryptographic hash function:

import * as Crypto from 'expo-crypto';

async function hashPin(pin: string): Promise<string> {
  return await Crypto.digestStringAsync(
    Crypto.CryptoDigestAlgorithm.SHA256,
    pin
  );
}

Note: This requires updating all hashPin() calls to handle async operations.

Exploit Scenario: An attacker with access to device storage extracts the PIN hash from secure storage and uses a rainbow table or brute force to recover the 4-digit PIN, gaining unauthorized access to merchant credentials.


Issue 2: Sensitive data logged in console output

ID: securestorag-console-log-sensitive-8f2a
File: dapps/pos-app/utils/secure-storage.ts:13
Severity: MEDIUM
Category: security

Error handlers in secure storage log errors to console (lines 13, 22, 31), which may expose sensitive data in error messages or stack traces. This is also flagged by GitHub Advanced Security (code-scanning alerts #22, #23, #24).

Recommendation: Remove or sanitize console.error calls:

console.error(`Error getting secure item for key type`);
// Don't log the actual error object which may contain sensitive data

Issue 3: Missing error handling for secure storage operations in settings store

ID: usesettingsst-setapikey-no-errorhandling-9c4f
File: dapps/pos-app/store/useSettingsStore.ts:82
Severity: MEDIUM
Category: code_quality

The setMerchantApiKey function (lines 82-91) doesn't handle errors from secureStorage.setItem() or removeItem(). If secure storage fails (which can happen due to device permissions, storage limits, or platform issues), the operation silently fails or crashes.

Copilot reviewer also flagged this at line 89.

Recommendation: Add try-catch with user feedback:

setMerchantApiKey: async (apiKey: string | null) => {
  try {
    if (apiKey) {
      await secureStorage.setItem(
        SECURE_STORAGE_KEYS.MERCHANT_API_KEY,
        apiKey,
      );
    } else {
      await secureStorage.removeItem(SECURE_STORAGE_KEYS.MERCHANT_API_KEY);
    }
  } catch (error) {
    console.error("Failed to store merchant API key");
    throw new Error("Failed to save credentials securely");
  }
},

Issue 4: Hard-coded SDK version in API headers

ID: payment-hardcoded-sdk-version-3e7c
File: dapps/pos-app/services/payment.ts:33
Severity: LOW
Category: code_quality

SDK version "1.0.0" is hard-coded, requiring code changes for version updates.

Recommendation: Import from package.json:

import packageJson from '../../package.json';

// In headers:
"Sdk-Version": packageJson.version,

Issue 5: Inconsistent prefix usage in web secure storage fallback

ID: securestorag-prefix-inconsistency-2d8b
File: dapps/pos-app/utils/secure-storage.web.ts:41
Severity: LOW
Category: code_quality

The fallback storage (lines 41, 55, 70) uses prefixed keys via getKey(), but this creates inconsistency where browser environments use prefixed keys while SSR/Node.js environments also use prefixes. This is already noted by Copilot.

Recommendation: Ensure consistent key usage across both storage mechanisms.


Response to Previous Reviews

Validating issues from previous reviews:

GitHub Advanced Security (Clear-text logging): ✅ Confirmed - Issue #2 above addresses this

Copilot Review:

  • setMerchantApiKey error handling: ✅ Confirmed - Issue Feat/expo wallet #3 above
  • Web localStorage security: ✅ Author added security warning at top of file (lines 1-11) noting "For development purposes only"
  • Prefix inconsistency: ✅ Confirmed - Issue Add example using react-native <0.70.x (pre-Hermes engine) #5 above
  • Hard-coded SDK version: ✅ Confirmed - Issue Expo Wallet #4 above
  • UUID hyphen removal: ✅ Author clarified backend needs IDs ≤35 chars
  • QR code visual feedback: ❌ Author declined ("no")
  • Currency hardcoding: ❌ Author deferred ("not for now")
  • Merchant ID loss on v4→v8 migration: ✅ Author clarified v4 users don't have merchantId set
  • API key visibility toggle: ❌ Author deferred ("not for now")

Previous Claude Review:

  • Weak PIN hashing: ✅ Still present - Issue feat: init rn native wallet #1 above (author dismissed as "not needed" but security risk remains)
  • Web storage security: ✅ Author acknowledged with warning comment
  • Missing API key validation: ✅ Author noted validation exists below (line 25-27 in payment.ts)
  • Async hash timing: ❌ Author dismissed as "not needed" (correct - only relevant if Issue feat: init rn native wallet #1 is fixed)

Automated Checks

✅ No workflow security issues detected (no changes to .github/workflows/)
✅ No external domain URLs detected (only approved domains: sentry.io, walletconnect.org, reactnative.dev, base64.guru, npmjs.org, solscan.io)
✅ No cache-control issues detected


Summary

5 issues found: 1 HIGH severity (weak PIN hashing), 2 MEDIUM severity (console logging sensitive data, missing error handling), 2 LOW severity (hard-coded version, prefix inconsistency).

Key Security Concern: The weak PIN hashing algorithm (Issue #1) is the most critical finding. While the author dismissed this in a previous review, it represents a genuine security vulnerability that allows PIN recovery through brute force. The 4-digit PIN space (10,000 combinations) can be exhausted in milliseconds with the current hash function.

Web Platform Note: The author appropriately added a security warning to the web storage implementation, noting it's for development/testing only.

Author Response Context: The author has addressed several previous review comments and provided clarifications on design decisions (UUID formatting, feature deferrals). Some security recommendations were dismissed but warrant reconsideration.


try {
return await SecureStore.getItemAsync(key);
} catch (error) {
console.error(`Error getting secure item`, error);

Choose a reason for hiding this comment

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

🤖 Auto Review Issue: Sensitive data logged in console output

Severity: MEDIUM
Category: security
Tool: Claude Auto Review

Recommendation: Remove or sanitize console.error calls:

console.error(`Error getting secure item for key type`);
// Don't log the actual error object which may contain sensitive data

const value = storage.getItem(prefixedKey);
return value ?? null;
}
return fallbackStorage.get(prefixedKey) ?? null;

Choose a reason for hiding this comment

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

🤖 Auto Review Issue: Inconsistent prefix usage in web secure storage fallback

Severity: LOW
Category: code_quality
Tool: Claude Auto Review

Recommendation: Ensure consistent key usage across both storage mechanisms.

@ignaciosantise ignaciosantise merged commit d82ccd2 into main Jan 5, 2026
6 checks passed
@ignaciosantise ignaciosantise deleted the chore/new-core branch January 5, 2026 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants