mirror of
https://github.com/selfxyz/self.git
synced 2026-01-09 22:58:20 -05:00
* update CI
* bump iOS version
* update readme
* update mobile-deploy ci
* bump version iOS
* update workflow to use workload identity federation (#933)
* update workflow to use workload identity federation
* add token permissions
* correct provider name
* chore: incrementing android build version for version 2.6.4 [github action]
---------
Co-authored-by: Self GitHub Actions <action@github.com>
* update ci
* update ci
* update ci
* update ci
* update ci
* fix ci
* fix ci
* fix ci
* remove fastlane use for android
* bump iOS build version
* update CI python script
* iterate on CI
* iterate on CI
* iterate on CI
* Dev (#941)
* SDK Go version (#920)
* feat: helper functions and constant for go-sdk
* feat: formatRevealedDataPacked in go
* chore: refactor
* feat: define struct for selfBackendVerifier
* feat: verify function for selfBackendVerifier
* feat(wip): custom hasher
* feat: SelfVerifierBacked in go
* test(wip): scope and userContextHash is failing
* test: zk proof verified
* fix: MockConfigStore getactionId function
* chore: refactor
* chore: remove abi duplicate files
* chore: move configStore to utils
* chore: modified VcAndDiscloseProof struct
* chore: more review changes
* feat: impl DefaultConfig and InMemoryConfigStore
* chore: refactor and export functions
* fix: module import and README
* chore: remove example folder
* chore: remove pointers from VerificationConfig
* chore: coderabbit review fixes
* chore: more coderabbit review fix
* chore: add license
* fix: convert attestationIdd to int
* chore: remove duplicate code
---------
Co-authored-by: ayman <aymanshaik1015@gmail.com>
* Moving proving Utils to common (#935)
* remove react dom
* moves proving utils to the common
* need to use rn components
* fix imports
* add proving-utils and dedeuplicate entry configs for esm and cjs.
* must wrap in text component
* fix metro bundling
* fix mock import
* fix builds and tests
* please save me
* solution?
* fix test
* Move proving inputs to the common package (#937)
* create ofactTree type to share
* move proving inputs from app to register inputs in common
* missed reexport
* ok
* add some validations as suggested by our ai overlords
* Fix mock passport flow (#942)
* fix dev screens
* add hint
* rename
* fix path
* fix mobile-ci path
* fix: extractMRZ (#938)
* fix: extractMRZ
* yarn nice && yarn types
* fix test: remove unused
* fix mobile ci
* add script
---------
Co-authored-by: Justin Hernandez <transphorm@gmail.com>
* Move Proving attest and cose (#950)
* moved attest and cose utils to common
with cursor converted tests in common to use vitest and converted coseVerify.test to vitest after moving from app to common
what does cryptoLoader do?
* moved away
* get buff
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* SELF-253 feat: add user email feedback (#889)
* feat: add sentry feedback
* add sentry feedback to web
* feat: add custom feedback modal & fix freeze on IOS
* yarn nice
* update lock
* feat: show feedback widget on NFC scan issues (#948)
* feat: show feedback widget on NFC scan issues
* fix ref
* clean up
* fix report issue screen
* abstract send user feedback email logic
* fixes
* change text to Report Issue
* sanitize email and track event messge
* remove unnecessary sanitization
* add sanitize error message tests
* fix tests
* save wip. almost done
* fix screen test
* fix screen test
* remove non working test
---------
Co-authored-by: Justin Hernandez <transphorm@gmail.com>
Co-authored-by: Justin Hernandez <justin.hernandez@self.xyz>
* chore: centralize license header checks (#952)
* chore: centralize license header scripts
* chore: run license header checks from root
* add header to other files
* add header to bundle
* add migration script and update check license headers
* convert license to mobile sdk
* migrate license headers
* remove headers from common; convert remaining
* fix headers
* add license header checks
* update unsupported passport screen (#953)
* update unsupported passport screen
* yarn nice
---------
Co-authored-by: Vishalkulkarni45 <109329073+Vishalkulkarni45@users.noreply.github.com>
Co-authored-by: ayman <aymanshaik1015@gmail.com>
Co-authored-by: Aaron DeRuvo <aaron.deruvo@clabs.co>
Co-authored-by: Justin Hernandez <justin.hernandez@self.xyz>
Co-authored-by: Seshanth.S🐺 <35675963+seshanthS@users.noreply.github.com>
Co-authored-by: Justin Hernandez <transphorm@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* bump version
* bump yarn.lock
* update ci (#966)
* chore: Manually bump and release v2.6.4 (#961)
* update lock files
* bump and build android
* update build artifacts
* show generate mock document button
* update lock
* fix formatting and update failing e2e test
* revert podfile
* fixes
* fix cold start of the app with deeplink
* update ci
* update ci
* Sync MARKETING_VERSION to iOS project files after version bump
* chore: incrementing android build version for version 2.6.4 [github action] (#976)
Co-authored-by: remicolin <98749896+remicolin@users.noreply.github.com>
* chore: add build dependencies step for iOS and Android in mobile deploy workflow
* chore: enhance mobile deploy workflow by adding CMake installation step
* bump android build version
* chore: incrementing android build version for version 2.6.4 [github action] (#985)
Co-authored-by: remicolin <98749896+remicolin@users.noreply.github.com>
* chore: configure Metro bundler for production compatibility in mobile deploy workflow
* chore: incrementing android build version for version 2.6.4 [github action] (#987)
Co-authored-by: remicolin <98749896+remicolin@users.noreply.github.com>
* Revert "chore: configure Metro bundler for production compatibility in mobile deploy workflow"
This reverts commit 60fc1f2580.
* reduce max old space size in mobile-deploy ci
* fix android french id card (#957)
* fix android french id card
* fix common ci cache
* feat: log apdu (#988)
---------
Co-authored-by: Justin Hernandez <transphorm@gmail.com>
Co-authored-by: Seshanth.S🐺 <35675963+seshanthS@users.noreply.github.com>
* unblock ci
* fix merge
* merge fixes
* fix tests
* make ci happy
---------
Co-authored-by: turnoffthiscomputer <colin.remi07@gmail.com>
Co-authored-by: pputman-clabs <99900942+pputman-clabs@users.noreply.github.com>
Co-authored-by: Self GitHub Actions <action@github.com>
Co-authored-by: turnoffthiscomputer <98749896+remicolin@users.noreply.github.com>
Co-authored-by: Vishalkulkarni45 <109329073+Vishalkulkarni45@users.noreply.github.com>
Co-authored-by: ayman <aymanshaik1015@gmail.com>
Co-authored-by: Aaron DeRuvo <aaron.deruvo@clabs.co>
Co-authored-by: Seshanth.S🐺 <35675963+seshanthS@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
379 lines
12 KiB
TypeScript
379 lines
12 KiB
TypeScript
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
|
|
|
import { Linking } from 'react-native';
|
|
|
|
jest.mock('@/navigation', () => ({
|
|
navigationRef: {
|
|
navigate: jest.fn(),
|
|
isReady: jest.fn(() => true),
|
|
reset: jest.fn(),
|
|
},
|
|
}));
|
|
|
|
const mockSelfAppStore = { useSelfAppStore: { getState: jest.fn() } };
|
|
jest.mock('@/stores/selfAppStore', () => mockSelfAppStore);
|
|
|
|
const mockUserStore = { default: { getState: jest.fn() } };
|
|
jest.mock('@/stores/userStore', () => ({
|
|
__esModule: true,
|
|
...mockUserStore,
|
|
}));
|
|
|
|
let setSelfApp: jest.Mock, startAppListener: jest.Mock, cleanSelfApp: jest.Mock;
|
|
let setDeepLinkUserDetails: jest.Mock;
|
|
|
|
let handleUrl: (url: string) => void;
|
|
let parseAndValidateUrlParams: (uri: string) => any;
|
|
let setupUniversalLinkListenerInNavigation: () => () => void;
|
|
|
|
describe('deeplinks', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
jest.resetModules();
|
|
({
|
|
handleUrl,
|
|
parseAndValidateUrlParams,
|
|
setupUniversalLinkListenerInNavigation,
|
|
} = require('@/utils/deeplinks'));
|
|
setSelfApp = jest.fn();
|
|
startAppListener = jest.fn();
|
|
cleanSelfApp = jest.fn();
|
|
setDeepLinkUserDetails = jest.fn();
|
|
jest.spyOn(Linking, 'getInitialURL').mockResolvedValue(null as any);
|
|
jest
|
|
.spyOn(Linking, 'addEventListener')
|
|
.mockReturnValue({ remove: jest.fn() } as any);
|
|
mockSelfAppStore.useSelfAppStore.getState.mockReturnValue({
|
|
setSelfApp,
|
|
startAppListener,
|
|
cleanSelfApp,
|
|
});
|
|
mockUserStore.default.getState.mockReturnValue({
|
|
setDeepLinkUserDetails,
|
|
});
|
|
});
|
|
|
|
describe('handleUrl', () => {
|
|
it('handles selfApp parameter', () => {
|
|
const selfApp = { sessionId: 'abc' };
|
|
const url = `scheme://open?selfApp=${encodeURIComponent(JSON.stringify(selfApp))}`;
|
|
handleUrl(url);
|
|
|
|
expect(setSelfApp).toHaveBeenCalledWith(selfApp);
|
|
expect(startAppListener).toHaveBeenCalledWith('abc');
|
|
const { navigationRef } = require('@/navigation');
|
|
expect(navigationRef.reset).toHaveBeenCalledWith({
|
|
index: 1,
|
|
routes: [{ name: 'Home' }, { name: 'ProveScreen' }],
|
|
});
|
|
});
|
|
|
|
it('handles sessionId parameter', () => {
|
|
const url = 'scheme://open?sessionId=123';
|
|
handleUrl(url);
|
|
|
|
expect(cleanSelfApp).toHaveBeenCalled();
|
|
expect(startAppListener).toHaveBeenCalledWith('123');
|
|
const { navigationRef } = require('@/navigation');
|
|
expect(navigationRef.reset).toHaveBeenCalledWith({
|
|
index: 1,
|
|
routes: [{ name: 'Home' }, { name: 'ProveScreen' }],
|
|
});
|
|
});
|
|
|
|
it('handles mock_passport parameter', () => {
|
|
const mockData = { name: 'John', surname: 'Doe' };
|
|
const url = `scheme://open?mock_passport=${encodeURIComponent(JSON.stringify(mockData))}`;
|
|
handleUrl(url);
|
|
|
|
expect(setDeepLinkUserDetails).toHaveBeenCalledWith({
|
|
name: 'John',
|
|
surname: 'Doe',
|
|
nationality: undefined,
|
|
birthDate: undefined,
|
|
gender: undefined,
|
|
});
|
|
const { navigationRef } = require('@/navigation');
|
|
expect(navigationRef.reset).toHaveBeenCalledWith({
|
|
index: 1,
|
|
routes: [{ name: 'Home' }, { name: 'MockDataDeepLink' }],
|
|
});
|
|
});
|
|
|
|
it('navigates to QRCodeTrouble for invalid data', () => {
|
|
const consoleErrorSpy = jest
|
|
.spyOn(console, 'error')
|
|
.mockImplementation(() => {});
|
|
|
|
const url = 'scheme://open?selfApp=%7Binvalid';
|
|
handleUrl(url);
|
|
|
|
const { navigationRef } = require('@/navigation');
|
|
expect(navigationRef.reset).toHaveBeenCalledWith({
|
|
index: 1,
|
|
routes: [{ name: 'Home' }, { name: 'QRCodeTrouble' }],
|
|
});
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
'Error parsing selfApp:',
|
|
expect.any(Error),
|
|
);
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it('handles sessionId with invalid characters', () => {
|
|
const consoleWarnSpy = jest
|
|
.spyOn(console, 'warn')
|
|
.mockImplementation(() => {});
|
|
const consoleErrorSpy = jest
|
|
.spyOn(console, 'error')
|
|
.mockImplementation(() => {});
|
|
|
|
const url = 'scheme://open?sessionId=abc<script>alert("xss")</script>';
|
|
handleUrl(url);
|
|
|
|
const { navigationRef } = require('@/navigation');
|
|
expect(navigationRef.reset).toHaveBeenCalledWith({
|
|
index: 1,
|
|
routes: [{ name: 'Home' }, { name: 'QRCodeTrouble' }],
|
|
});
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
'No sessionId or selfApp found in the data',
|
|
);
|
|
|
|
consoleWarnSpy.mockRestore();
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it('rejects URLs with malformed parameters', () => {
|
|
const consoleErrorSpy = jest
|
|
.spyOn(console, 'error')
|
|
.mockImplementation(() => {});
|
|
|
|
const url = 'scheme://open?sessionId=%ZZ'; // Invalid URL encoding
|
|
handleUrl(url);
|
|
|
|
const { navigationRef } = require('@/navigation');
|
|
expect(navigationRef.reset).toHaveBeenCalledWith({
|
|
index: 1,
|
|
routes: [{ name: 'Home' }, { name: 'QRCodeTrouble' }],
|
|
});
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe('parseAndValidateUrlParams', () => {
|
|
it('returns valid sessionId parameter', () => {
|
|
const url = 'scheme://open?sessionId=abc123';
|
|
const result = parseAndValidateUrlParams(url);
|
|
expect(result).toEqual({ sessionId: 'abc123' });
|
|
});
|
|
|
|
it('returns valid selfApp parameter', () => {
|
|
const selfApp = { sessionId: 'abc' };
|
|
const url = `scheme://open?selfApp=${encodeURIComponent(JSON.stringify(selfApp))}`;
|
|
const result = parseAndValidateUrlParams(url);
|
|
expect(result).toEqual({ selfApp: JSON.stringify(selfApp) });
|
|
});
|
|
|
|
it('returns valid mock_passport parameter', () => {
|
|
const mockData = { name: 'John', surname: 'Doe' };
|
|
const url = `scheme://open?mock_passport=${encodeURIComponent(JSON.stringify(mockData))}`;
|
|
const result = parseAndValidateUrlParams(url);
|
|
expect(result).toEqual({ mock_passport: JSON.stringify(mockData) });
|
|
});
|
|
|
|
it('filters out unexpected parameters', () => {
|
|
const consoleWarnSpy = jest
|
|
.spyOn(console, 'warn')
|
|
.mockImplementation(() => {});
|
|
|
|
const url =
|
|
'scheme://open?sessionId=abc123&maliciousParam=evil&anotherBad=param';
|
|
const result = parseAndValidateUrlParams(url);
|
|
|
|
expect(result).toEqual({ sessionId: 'abc123' });
|
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
'Unexpected or invalid parameter ignored: maliciousParam',
|
|
);
|
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
'Unexpected or invalid parameter ignored: anotherBad',
|
|
);
|
|
|
|
consoleWarnSpy.mockRestore();
|
|
});
|
|
|
|
it('rejects sessionId with invalid characters', () => {
|
|
const consoleErrorSpy = jest
|
|
.spyOn(console, 'error')
|
|
.mockImplementation(() => {});
|
|
|
|
const url = 'scheme://open?sessionId=abc<script>alert("xss")</script>';
|
|
const result = parseAndValidateUrlParams(url);
|
|
|
|
expect(result).toEqual({});
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
'Parameter sessionId failed validation:',
|
|
'abc<script>alert("xss")</script>',
|
|
);
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it('handles URL-encoded characters correctly', () => {
|
|
const sessionId = 'abc-123_TEST';
|
|
const url = `scheme://open?sessionId=${encodeURIComponent(sessionId)}`;
|
|
const result = parseAndValidateUrlParams(url);
|
|
expect(result).toEqual({ sessionId });
|
|
});
|
|
|
|
it('handles complex JSON in selfApp parameter', () => {
|
|
const complexSelfApp = {
|
|
sessionId: 'abc123',
|
|
nested: { data: 'value', numbers: [1, 2, 3] },
|
|
special: 'chars with spaces and symbols',
|
|
};
|
|
const url = `scheme://open?selfApp=${encodeURIComponent(JSON.stringify(complexSelfApp))}`;
|
|
const result = parseAndValidateUrlParams(url);
|
|
expect(result).toEqual({ selfApp: JSON.stringify(complexSelfApp) });
|
|
});
|
|
|
|
it('handles malformed URL encoding gracefully', () => {
|
|
const consoleErrorSpy = jest
|
|
.spyOn(console, 'error')
|
|
.mockImplementation(() => {});
|
|
|
|
const url = 'scheme://open?sessionId=%ZZ'; // Invalid URL encoding
|
|
const result = parseAndValidateUrlParams(url);
|
|
|
|
expect(result).toEqual({});
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
'Error decoding parameter sessionId:',
|
|
expect.any(Error),
|
|
);
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it('ignores empty parameter values', () => {
|
|
const url = 'scheme://open?sessionId=&selfApp=validValue';
|
|
const result = parseAndValidateUrlParams(url);
|
|
expect(result).toEqual({ selfApp: 'validValue' });
|
|
});
|
|
|
|
it('handles duplicate keys correctly', () => {
|
|
// Test what actually happens with duplicate keys in query-string library
|
|
const url = 'scheme://open?sessionId=valid1&sessionId=valid2';
|
|
const result = parseAndValidateUrlParams(url);
|
|
// query-string typically handles duplicates by taking the last value or creating an array
|
|
// We'll accept either a valid sessionId or empty object if it creates an array
|
|
expect(
|
|
result.sessionId === undefined || typeof result.sessionId === 'string',
|
|
).toBe(true);
|
|
});
|
|
|
|
it('handles completely malformed URLs', () => {
|
|
const consoleErrorSpy = jest
|
|
.spyOn(console, 'error')
|
|
.mockImplementation(() => {});
|
|
|
|
const url = 'not-a-valid-url-at-all';
|
|
const result = parseAndValidateUrlParams(url);
|
|
|
|
expect(result).toEqual({});
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it('handles URLs with no query parameters', () => {
|
|
const url = 'scheme://open';
|
|
const result = parseAndValidateUrlParams(url);
|
|
expect(result).toEqual({});
|
|
});
|
|
|
|
it('handles URLs with empty query string', () => {
|
|
const url = 'scheme://open?';
|
|
const result = parseAndValidateUrlParams(url);
|
|
expect(result).toEqual({});
|
|
});
|
|
|
|
it('validates sessionId with allowed characters', () => {
|
|
const validSessionIds = [
|
|
'abc123',
|
|
'ABC_123',
|
|
'test-value',
|
|
'123456789',
|
|
'a_b-c_123',
|
|
];
|
|
|
|
validSessionIds.forEach(sessionId => {
|
|
const url = `scheme://open?sessionId=${sessionId}`;
|
|
const result = parseAndValidateUrlParams(url);
|
|
expect(result).toEqual({ sessionId });
|
|
});
|
|
});
|
|
|
|
it('rejects sessionId with disallowed characters', () => {
|
|
const consoleErrorSpy = jest
|
|
.spyOn(console, 'error')
|
|
.mockImplementation(() => {});
|
|
|
|
const invalidSessionIds = [
|
|
'abc@123',
|
|
'test value',
|
|
'test#value',
|
|
'test$%^&*()',
|
|
];
|
|
|
|
invalidSessionIds.forEach(sessionId => {
|
|
const url = `scheme://open?sessionId=${encodeURIComponent(sessionId)}`;
|
|
const result = parseAndValidateUrlParams(url);
|
|
expect(result).toEqual({});
|
|
});
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it('handles non-string parameter values', () => {
|
|
const consoleWarnSpy = jest
|
|
.spyOn(console, 'warn')
|
|
.mockImplementation(() => {});
|
|
|
|
// This might happen if query-string returns an array for duplicate keys
|
|
const mockParseUrl = jest.fn().mockReturnValue({
|
|
query: { sessionId: ['value1', 'value2'] },
|
|
});
|
|
|
|
// Temporarily mock the parseUrl import
|
|
jest.doMock('query-string', () => ({ parseUrl: mockParseUrl }));
|
|
|
|
// Re-require to get the mocked version
|
|
jest.resetModules();
|
|
const {
|
|
parseAndValidateUrlParams: mockedParser,
|
|
} = require('@/utils/deeplinks');
|
|
|
|
const url = 'scheme://open?sessionId=duplicate&sessionId=values';
|
|
const result = mockedParser(url);
|
|
|
|
expect(result).toEqual({});
|
|
|
|
consoleWarnSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
it('setup listener registers and cleans up', () => {
|
|
const remove = jest.fn();
|
|
(Linking.getInitialURL as jest.Mock).mockResolvedValue(undefined);
|
|
(Linking.addEventListener as jest.Mock).mockReturnValue({ remove });
|
|
|
|
const cleanup = setupUniversalLinkListenerInNavigation();
|
|
expect(Linking.addEventListener).toHaveBeenCalled();
|
|
cleanup();
|
|
expect(remove).toHaveBeenCalled();
|
|
});
|
|
});
|