SEL-269: Update ESLint rules & lock prettier config (#781)

* Update ESLint config and lock prettier config

* Refine ESLint config and fix lint issues

* Apply eslint fixes

* Use socketIo alias (#782)

* move gesture handler

* save wip updates

* fix svg imports

* update tsconfig

* eslint updates

* eslint fixes

* improve ignore folders

* coderabbit feedback

* Fix style prop shorthands (#787)

* Expand view style props

* Expand remaining style props

* update types

* fix pipeline

* fix test env check

* nicer casting

* fix booleans

* update deeplink url handling and make it more robust

* add socket error handler
This commit is contained in:
Justin Hernandez
2025-07-24 21:17:54 -07:00
committed by GitHub
parent f739fedd82
commit bf3ef98c9d
70 changed files with 1504 additions and 591 deletions

View File

@@ -8,10 +8,20 @@ import {
AuthorizeResult,
} from 'react-native-app-auth';
// Ensure the client ID is available at runtime (skip in test environment)
const isTestEnvironment =
process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID;
if (!isTestEnvironment && !GOOGLE_SIGNIN_ANDROID_CLIENT_ID) {
throw new Error(
'GOOGLE_SIGNIN_ANDROID_CLIENT_ID environment variable is not set',
);
}
const config: AuthConfiguration = {
// DEBUG: log config for Auth
// ensure this prints the correct values before calling authorize
clientId: GOOGLE_SIGNIN_ANDROID_CLIENT_ID,
clientId: GOOGLE_SIGNIN_ANDROID_CLIENT_ID || 'mock-client-id',
redirectUrl: 'com.proofofpassportapp:/oauth2redirect',
scopes: ['https://www.googleapis.com/auth/drive.appdata'],
serviceConfiguration: {

View File

@@ -1,34 +1,94 @@
// SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11
import queryString from 'query-string';
import { parseUrl } from 'query-string';
import { Linking, Platform } from 'react-native';
import { navigationRef } from '../navigation';
import { useSelfAppStore } from '../stores/selfAppStore';
import useUserStore from '../stores/userStore';
// Validation patterns for each expected parameter
const VALIDATION_PATTERNS = {
sessionId: /^[a-zA-Z0-9_-]+$/,
selfApp: /^[\s\S]*$/, // JSON strings can contain any characters, we'll validate JSON parsing separately
mock_passport: /^[\s\S]*$/, // JSON strings can contain any characters, we'll validate JSON parsing separately
} as const;
type ValidatedParams = {
sessionId?: string;
selfApp?: string;
mock_passport?: string;
};
/**
* Decodes a URL-encoded string.
* @param {string} encodedUrl
* @returns {string}
* Validates and sanitizes a query parameter value
* @param key - The parameter key
* @param value - The parameter value to validate
* @returns The sanitized value or undefined if invalid
*/
const decodeUrl = (encodedUrl: string): string => {
const validateAndSanitizeParam = (
key: string,
value: string,
): string | undefined => {
if (!value) return undefined;
// Decode the value first
let decodedValue: string;
try {
return decodeURIComponent(encodedUrl);
decodedValue = decodeURIComponent(value);
} catch (error) {
if (typeof __DEV__ !== 'undefined' && __DEV__) {
console.error('Error decoding URL:', error);
console.error(`Error decoding parameter ${key}:`, error);
}
return encodedUrl;
return undefined;
}
// Validate against pattern if we have one for this key
if (key in VALIDATION_PATTERNS) {
const pattern =
VALIDATION_PATTERNS[key as keyof typeof VALIDATION_PATTERNS];
if (!pattern.test(decodedValue)) {
if (typeof __DEV__ !== 'undefined' && __DEV__) {
console.error(`Parameter ${key} failed validation:`, decodedValue);
}
return undefined;
}
}
return decodedValue;
};
/**
* Parses and validates query parameters from a URL
* @param uri - The URL to parse
* @returns Validated and sanitized parameters
*/
export const parseAndValidateUrlParams = (uri: string): ValidatedParams => {
// Parse the URL directly without pre-decoding to avoid issues with fragment separators
const parsed = parseUrl(uri);
const query = parsed.query || {};
const validatedParams: ValidatedParams = {};
// Only process expected parameters and validate them
for (const [key, value] of Object.entries(query)) {
if (key in VALIDATION_PATTERNS && typeof value === 'string') {
const sanitizedValue = validateAndSanitizeParam(key, value);
if (sanitizedValue !== undefined) {
validatedParams[key as keyof ValidatedParams] = sanitizedValue;
}
} else if (typeof __DEV__ !== 'undefined' && __DEV__) {
// Log unexpected parameters in development
console.warn(`Unexpected or invalid parameter ignored: ${key}`);
}
}
return validatedParams;
};
export const handleUrl = (uri: string) => {
const decodedUri = decodeUrl(uri);
const encodedData = queryString.parseUrl(decodedUri).query;
const sessionId = encodedData.sessionId;
const selfAppStr = encodedData.selfApp as string | undefined;
const mock_passport = encodedData.mock_passport as string | undefined;
const validatedParams = parseAndValidateUrlParams(uri);
const { sessionId, selfApp: selfAppStr, mock_passport } = validatedParams;
if (selfAppStr) {
try {

View File

@@ -22,7 +22,7 @@ interface Inputs {
export const scan = async (inputs: Inputs) => {
if (MIXPANEL_NFC_PROJECT_TOKEN) {
if (Platform.OS === 'ios') {
const enableDebugLogs = ENABLE_DEBUG_LOGS === 'true';
const enableDebugLogs = JSON.parse(String(ENABLE_DEBUG_LOGS));
NativeModules.PassportReader.configure(
MIXPANEL_NFC_PROJECT_TOKEN,
enableDebugLogs,

View File

@@ -5,7 +5,7 @@ import { PCR0_MANAGER_ADDRESS, RPC_URL } from '@selfxyz/common';
import { decode } from '@stablelib/cbor';
import { fromBER } from 'asn1js';
import { Buffer } from 'buffer';
import elliptic from 'elliptic';
import { ec as ellipticEc } from 'elliptic';
import { ethers } from 'ethers';
import { sha384 } from 'js-sha512';
import { Certificate } from 'pkijs';
@@ -289,7 +289,7 @@ function getPublicKeyFromPem(pem: string) {
const curve = 'p384';
const publicKeyBuffer =
cert.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView;
const ec = new elliptic.ec(curve);
const ec = new ellipticEc(curve);
const key = ec.keyFromPublic(publicKeyBuffer);
const x_point = key.getPublic().getX().toString('hex');
const y_point = key.getPublic().getY().toString('hex');
@@ -337,7 +337,7 @@ function verifyCertificateSignature(child: string, parent: string): boolean {
const publicKeyBuffer_csca =
publicKeyInfo_csca.subjectPublicKey.valueBlock.valueHexView;
const curve = 'p384';
const ec_csca = new elliptic.ec(curve);
const ec_csca = new ellipticEc(curve);
const key_csca = ec_csca.keyFromPublic(publicKeyBuffer_csca);
const tbsHash = getTBSHash(child);

View File

@@ -19,7 +19,7 @@ import { sha384 } from 'js-sha512';
* to ensure that the TEE's COSE_Sign1 attestation document has not been tampered with.
* @see https://docs.aws.amazon.com/enclaves/latest/user/set-up-attestation.html for p384 sha384 usage
*/
export const cose = {
const cose = {
sign: {
verify: async (
data: Buffer,

View File

@@ -5,22 +5,20 @@ import { SMT } from '@openpassport/zk-kit-smt';
import {
attributeToPosition,
attributeToPosition_ID,
calculateUserIdentifierHash,
DEFAULT_MAJORITY,
DocumentCategory,
ID_CARD_ATTESTATION_ID,
PASSPORT_ATTESTATION_ID,
SelfAppDisclosureConfig,
} from '@selfxyz/common';
import { SelfApp } from '@selfxyz/common';
import { getCircuitNameFromPassportData } from '@selfxyz/common';
import {
generateCircuitInputsDSC,
generateCircuitInputsRegister,
generateCircuitInputsVCandDisclose,
getCircuitNameFromPassportData,
hashEndpointWithScope,
ID_CARD_ATTESTATION_ID,
PASSPORT_ATTESTATION_ID,
PassportData,
SelfApp,
SelfAppDisclosureConfig,
} from '@selfxyz/common';
import { hashEndpointWithScope } from '@selfxyz/common';
import { calculateUserIdentifierHash } from '@selfxyz/common';
import { PassportData } from '@selfxyz/common';
import { poseidon2 } from 'poseidon-lite';
import { useProtocolStore } from '../../stores/protocolStore';
@@ -79,14 +77,13 @@ export function generateTEEInputsDisclose(
throw new Error('OFAC trees not loaded');
}
let passportNoAndNationalitySMT: SMT | null = null;
let nameAndDobSMT, nameAndYobSMT;
const nameAndDobSMT = new SMT(poseidon2, true);
const nameAndYobSMT = new SMT(poseidon2, true);
if (document === 'passport') {
passportNoAndNationalitySMT = new SMT(poseidon2, true);
passportNoAndNationalitySMT.import(ofac_trees.passportNoAndNationality);
}
nameAndDobSMT = new SMT(poseidon2, true);
nameAndDobSMT.import(ofac_trees.nameAndDob);
nameAndYobSMT = new SMT(poseidon2, true);
nameAndYobSMT.import(ofac_trees.nameAndYob);
const serialized_tree = useProtocolStore.getState()[document].commitment_tree;

View File

@@ -9,7 +9,7 @@ import {
SelfApp,
} from '@selfxyz/common';
import forge from 'node-forge';
import io, { Socket } from 'socket.io-client';
import socketIo, { Socket } from 'socket.io-client';
import { v4 } from 'uuid';
import { AnyActorRef, createActor, createMachine } from 'xstate';
import { create } from 'zustand';
@@ -405,7 +405,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
}
const url = getWSDbRelayerUrl(endpointType);
let socket: Socket | null = io(url, {
const socket: Socket = socketIo(url, {
path: '/',
transports: ['websocket'],
});
@@ -417,6 +417,37 @@ export const useProvingStore = create<ProvingState>((set, get) => {
trackEvent(ProofEvents.SOCKETIO_SUBSCRIBED);
});
socket.on('connect_error', error => {
console.error('SocketIO connection error:', error);
trackEvent(ProofEvents.SOCKETIO_CONNECT_ERROR, {
message: (error as any).message,
});
trackEvent(ProofEvents.PROOF_FAILED, {
circuitType: get().circuitType,
error: get().error_code ?? 'unknown',
});
actor!.send({ type: 'PROVE_ERROR' });
set({ socketConnection: null });
});
socket.on('disconnect', (reason: string) => {
console.log(`SocketIO disconnected. Reason: ${reason}`);
const currentActor = actor;
if (get().currentState === 'ready_to_prove' && currentActor) {
console.error(
'SocketIO disconnected unexpectedly during proof listening.',
);
trackEvent(ProofEvents.SOCKETIO_DISCONNECT_UNEXPECTED);
trackEvent(ProofEvents.PROOF_FAILED, {
circuitType: get().circuitType,
error: get().error_code ?? 'unknown',
});
currentActor.send({ type: 'PROVE_ERROR' });
}
set({ socketConnection: null });
});
socket.on('status', (message: any) => {
const data =
typeof message === 'string' ? JSON.parse(message) : message;
@@ -451,37 +482,6 @@ export const useProvingStore = create<ProvingState>((set, get) => {
actor!.send({ type: 'PROVE_SUCCESS' });
}
});
socket.on('disconnect', (reason: string) => {
console.log(`SocketIO disconnected. Reason: ${reason}`);
const currentActor = actor;
if (get().currentState === 'ready_to_prove' && currentActor) {
console.error(
'SocketIO disconnected unexpectedly during proof listening.',
);
trackEvent(ProofEvents.SOCKETIO_DISCONNECT_UNEXPECTED);
trackEvent(ProofEvents.PROOF_FAILED, {
circuitType: get().circuitType,
error: get().error_code ?? 'unknown',
});
currentActor.send({ type: 'PROVE_ERROR' });
}
set({ socketConnection: null });
});
socket.on('connect_error', error => {
console.error('SocketIO connection error:', error);
trackEvent(ProofEvents.SOCKETIO_CONNECT_ERROR, {
message: (error as any).message,
});
trackEvent(ProofEvents.PROOF_FAILED, {
circuitType: get().circuitType,
error: get().error_code ?? 'unknown',
});
actor!.send({ type: 'PROVE_ERROR' });
set({ socketConnection: null });
});
},
_handleWsOpen: () => {