Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/location spoofing use case demo #136

Merged
merged 110 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
ee28724
feat: add sms fraud content, add more existing case studies
JuroUhlar Mar 21, 2024
f9f7530
feat: use case skeleton
JuroUhlar Mar 21, 2024
d5a9b2b
chore: improve and organize utils
JuroUhlar Mar 25, 2024
1ee4162
chore: improve and organize utils
JuroUhlar Mar 25, 2024
4a2bd82
feat: verify number functionality
JuroUhlar Mar 25, 2024
d4633b2
feat: add db model to reset
JuroUhlar Mar 25, 2024
1bcfcbb
chore: clean up
JuroUhlar Mar 25, 2024
9b50634
feat: make code checking work
JuroUhlar Mar 26, 2024
685bb31
feat: integrate twilio
JuroUhlar Mar 26, 2024
d05c6f7
feat: display 500 errors
JuroUhlar Mar 26, 2024
ca1849a
refactor: extract hooks
JuroUhlar Mar 26, 2024
dd7eec5
refactor: extract phoneNumber form
JuroUhlar Mar 28, 2024
60660c2
refactor: extract submitCode form
JuroUhlar Mar 28, 2024
02cce56
refactor: extract send button
JuroUhlar Mar 28, 2024
36b2362
refactor: fix form
JuroUhlar Mar 28, 2024
b4fd755
feat: make test number work on screen only
JuroUhlar Mar 28, 2024
6b6b2a4
feat: copy code to clipboard
JuroUhlar Mar 28, 2024
5338d32
feat: back button, style fixes
JuroUhlar Mar 29, 2024
8d97803
feat: use real timeouts
JuroUhlar Mar 29, 2024
a273250
feat: enable bot detection
JuroUhlar Mar 29, 2024
0f75227
review: fix copy
JuroUhlar Mar 29, 2024
adfa3d5
review: fix off by one 1 copy error
JuroUhlar Mar 29, 2024
94be980
review: hash phone numbers
JuroUhlar Apr 2, 2024
b6e11c0
review: check tor and bot before confidence score
JuroUhlar Apr 2, 2024
2b34f39
feat: disableBotDetection for tests
JuroUhlar Apr 2, 2024
d4cc251
feat: display remaining attempts
JuroUhlar Apr 2, 2024
d4453a2
test: bot protected
JuroUhlar Apr 3, 2024
d3dd00d
feat: apply hard limit
JuroUhlar Apr 3, 2024
985866d
test: bot unprotected
JuroUhlar Apr 3, 2024
fd32784
test: submit code and create account
JuroUhlar Apr 3, 2024
91ab2ee
test: refactor alert and snackbar utils
JuroUhlar Apr 3, 2024
790b8f9
test: refactor bot protected
JuroUhlar Apr 3, 2024
9b26458
test: firefox. webkit don't need clipboard permissions
JuroUhlar Apr 3, 2024
a8e88d8
test: fix e2e tests
JuroUhlar Apr 4, 2024
6f28224
test: fix e2e tests with safari hack
JuroUhlar Apr 4, 2024
526421b
test: fix e2e tests with avoiding safari
JuroUhlar Apr 4, 2024
6ea3330
test: fix e2e tests, remove only()
JuroUhlar Apr 4, 2024
f53f2f5
test: fix isClipboardAvailable condition
JuroUhlar Apr 4, 2024
7f07641
test: more logs
JuroUhlar Apr 4, 2024
b4062bb
test: copy
JuroUhlar Apr 4, 2024
afc7073
test: check final block
JuroUhlar Apr 4, 2024
67d6647
test: fix tests
JuroUhlar Apr 4, 2024
bc80ea3
test: simplify ids
JuroUhlar Apr 4, 2024
cbb4ebc
test: fix undefined
JuroUhlar Apr 4, 2024
8cd7ab0
chore: self-review fixes
JuroUhlar Apr 4, 2024
c309c1f
chore: refactor ui into separate components
JuroUhlar Apr 4, 2024
03594c0
chore: refactor more for readability
JuroUhlar Apr 4, 2024
ac8ecdf
chore: add readme, hide links
JuroUhlar Apr 5, 2024
ca34d83
chore: adjust meta copy
JuroUhlar Apr 5, 2024
7a70df3
chore: use test phone number constant
JuroUhlar Apr 5, 2024
218fc51
review: fixes
JuroUhlar Apr 5, 2024
c51e655
review: add sms icon
JuroUhlar Apr 5, 2024
8c25f75
chore: `app` setup
JuroUhlar Apr 9, 2024
862c26e
chore: make server checks work with app router
JuroUhlar Apr 9, 2024
f7e17c2
feat: return success
JuroUhlar Apr 9, 2024
e3ef274
feat: decrypt result
JuroUhlar Apr 10, 2024
d252669
refactor getAndValidateFingerprintResult
JuroUhlar Apr 10, 2024
09168bb
feat: show country in callout
JuroUhlar Apr 10, 2024
f454802
feat: calculate ppp table
JuroUhlar Apr 11, 2024
b5f0abf
feat: use real regional discount
JuroUhlar Apr 11, 2024
c1bbfca
review fix: rename database model
JuroUhlar Apr 12, 2024
4919dbb
feat: fix error handling
JuroUhlar Apr 14, 2024
8bc4d6c
feat: rename
JuroUhlar Apr 14, 2024
f11e324
feat: display potential discount
JuroUhlar Apr 14, 2024
69e5960
feat: cart improvements
JuroUhlar Apr 14, 2024
d55bd1e
feat: menu items split order
JuroUhlar Apr 14, 2024
653099e
feat: course icon
JuroUhlar Apr 14, 2024
bcf52e6
feat: tweak copy
JuroUhlar Apr 14, 2024
688064c
feat: use case icon
JuroUhlar Apr 14, 2024
89637fa
feat: styles cleanup
JuroUhlar Apr 14, 2024
6b066e0
feat: fix app metadata
JuroUhlar Apr 14, 2024
20374f8
feat: improve comment
JuroUhlar Apr 14, 2024
d431f54
feat: self-review fixes
JuroUhlar Apr 14, 2024
d605828
feat: custom discount labels
JuroUhlar Apr 15, 2024
d8834cb
Merge branch 'feat/sms-fraud-use-case-demo' into feat/location-spoofi…
JuroUhlar Apr 15, 2024
32e1a88
merge fixes
JuroUhlar Apr 15, 2024
e593b1a
feat: introduce t3-env
JuroUhlar Apr 16, 2024
8ee8085
chore: rename SMS fraud to SMS pumping
JuroUhlar Apr 16, 2024
b73e2a4
chore: rename SMS fraud to SMS pumping in Readme
JuroUhlar Apr 16, 2024
fd584aa
feat: use proxy, manage varables in t3-env
JuroUhlar Apr 16, 2024
f932a38
feat: tsconfig
JuroUhlar Apr 16, 2024
3cc2462
fix tests
JuroUhlar Apr 16, 2024
15517e4
Merge branch 'feat/sms-fraud-use-case-demo' into feat/location-spoofi…
JuroUhlar Apr 16, 2024
c56b9c9
chore: use restricted twilio api key, more secure
JuroUhlar Apr 17, 2024
fe93630
feat: put everything into env.ts
JuroUhlar Apr 18, 2024
ca6edc7
Merge branch 'feat/sms-fraud-use-case-demo' into feat/location-spoofi…
JuroUhlar Apr 18, 2024
f0613c3
chore: put everything in env
JuroUhlar Apr 19, 2024
88cb205
chore: rename env
JuroUhlar Apr 19, 2024
65ec89b
chore: fix unit test
JuroUhlar Apr 19, 2024
d0d5a1a
test: add e2e tests
JuroUhlar Apr 19, 2024
60e8cea
fix: use unsealed result should fail if request fails
JuroUhlar Apr 20, 2024
5e3158e
fix: self-review cleanup
JuroUhlar Apr 21, 2024
a066a73
feat: add embed to location spoofing
JuroUhlar Apr 22, 2024
d58e4bd
fix typo
JuroUhlar Apr 22, 2024
0ad7082
Merge branch 'main' into feat/location-spoofing-use-case-demo
JuroUhlar Apr 24, 2024
825839a
Merge branch 'main' into feat/location-spoofing-use-case-demo
JuroUhlar Apr 24, 2024
1d11194
fix: playground proxy test
JuroUhlar Apr 24, 2024
669e9b0
fix: move deploymentUtils to layour
JuroUhlar Apr 24, 2024
88506cd
fix: remove test only
JuroUhlar Apr 24, 2024
39315e4
fix: reorganize _app
JuroUhlar Apr 24, 2024
3cc9be1
fix: location mismatch
JuroUhlar Apr 25, 2024
49ce46c
fix: getIpLocation
JuroUhlar Apr 25, 2024
39754c3
feat: add use case to readme
JuroUhlar Apr 29, 2024
6e93be2
chore: rename use case to VPN detection
JuroUhlar Apr 29, 2024
08184a4
add vpn link
JuroUhlar Apr 29, 2024
4fa1d4c
review fix: only display country if you actually have it
JuroUhlar Apr 29, 2024
2c742e0
review fix: show city, country in VPN error
JuroUhlar Apr 29, 2024
e4e652b
chore: rename file
JuroUhlar Apr 29, 2024
6a833ff
chore: add bg color to callout
JuroUhlar Apr 29, 2024
8a89c43
chore: fix typo
JuroUhlar Apr 29, 2024
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
6 changes: 2 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
PRIVATE_API_KEY=<SECRET_API_KEY>
SERVER_API_KEY=<SECRET_API_KEY>
NEXT_PUBLIC_API_KEY=<PUBLIC_API_KEY>
# "eu" or "ap", "us" is default
BACKEND_REGION=<REGION>
# "eu" or "ap", "us" is default
NEXT_PUBLIC_FRONTEND_REGION=<REGION>
NEXT_PUBLIC_REGION=<REGION>
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ Prevent financial losses from SMS pumping. Link every verification text message

[📱 SMS Pumping Protection Live Demo](https://demo.fingerprint.com/sms-pumping)

### VPN Detection and Location Spoofing Protection

Detect when visitors are using VPN to access your application. Prevent people spoofing their location from accessing geo-restricted content or pricing.

[🌍 VPN Detection Live Demo](https://demo.fingerprint.com/vpn-detection)

## Documentation and Support

To dive deeper into Fingerprint Pro, see our [Documentation](https://dev.fingerprint.com/docs). For questions or suggestions specific to this repository, you can [create an issue](https://github.com/fingerprintjs/fingerprintjs-pro-use-cases/issues/new). For general questions and community vibes, visit our [Discord server](https://discord.gg/39EpE2neBg). If you require private support, you can email us at [[email protected]](mailto:[email protected]).
Expand Down
2 changes: 1 addition & 1 deletion e2e/coupon-fraud.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Page, test, expect } from '@playwright/test';
import { blockGoogleTagManager, resetScenarios } from './e2eTestUtils';
import { TEST_IDS } from '../src/client/testIDs';
import { COUPON_FRAUD_COPY } from '../src/pages/api/coupon-fraud/claim';
import { COUPON_FRAUD_COPY } from '../src/server/coupon-fraud/copy';

const insertCoupon = async (page: Page, coupon: string) => {
await page.getByTestId(TEST_IDS.couponFraud.couponCode).fill(coupon);
Expand Down
2 changes: 1 addition & 1 deletion e2e/credential-stuffing.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Page, test } from '@playwright/test';
import { blockGoogleTagManager, resetScenarios } from './e2eTestUtils';
import { TEST_IDS } from '../src/client/testIDs';
import { CREDENTIAL_STUFFING_COPY } from '../src/pages/api/credential-stuffing/authenticate';
import { CREDENTIAL_STUFFING_COPY } from '../src/server/credentialStuffing/copy';

const submitForm = async (page: Page) => {
// Waits for the button to be clickable out of the box
Expand Down
2 changes: 1 addition & 1 deletion e2e/loan-risk.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Page, expect, test } from '@playwright/test';
import { blockGoogleTagManager, resetScenarios } from './e2eTestUtils';
import { TEST_IDS } from '../src/client/testIDs';
import { LOAN_RISK_COPY } from '../src/pages/api/loan-risk/request-loan';
import { LOAN_RISK_COPY } from '../src/server/loan-risk/copy';

const testIds = TEST_IDS.loanRisk;

Expand Down
2 changes: 1 addition & 1 deletion e2e/payment-fraud.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Page, test } from '@playwright/test';
import { blockGoogleTagManager, resetScenarios } from './e2eTestUtils';
import { PAYMENT_FRAUD_COPY } from '../src/pages/api/payment-fraud/place-order';
import { TEST_IDS } from '../src/client/testIDs';
import { PAYMENT_FRAUD_COPY } from '../src/server/paymentFraud/copy';

const submit = (page: Page) => page.getByTestId(TEST_IDS.paymentFraud.submitPayment).click();

Expand Down
34 changes: 19 additions & 15 deletions e2e/playground.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { SCRIPT_URL_PATTERN } from './../src/server/const';
import { Page, expect, test } from '@playwright/test';
import { PLAYGROUND_TAG } from '../src/client/components/playground/playgroundTags';
import { isAgentResponse, isServerResponse } from './zodUtils';
import { ENDPOINT } from '../src/server/const';
import { blockGoogleTagManager } from './e2eTestUtils';

const getAgentResponse = async (page: Page) => {
Expand Down Expand Up @@ -86,23 +84,29 @@ test.describe('Playground page', () => {
});

test.describe('Proxy integration', () => {
const proxyIntegrations = [
'https://metrics.fingerprinthub.com',
'https://demo.fingerprint.com/DBqbMN7zXxwl4Ei8',
process.env.NEXT_PUBLIC_ENDPOINT ?? 'NO_CUSTOM_PROXY_IN_ENV',
];

/**
* If any JS agent network request fails, fail the test.
* This captures proxy integration failures that would otherwise go unnoticed thanks to default endpoint fallbacks.
*/
test('Proxy integration works on Playground, no network errors', async ({ page }) => {
// If any JS agent network request fails, fails the test
// This captures proxy integration failures that would otherwise go unnoticed thanks to default endpoint fallbacks
const endpointOrigin = new URL(ENDPOINT).origin;
const scriptUrlPatternOrigin = new URL(SCRIPT_URL_PATTERN).origin;

page.on('requestfailed', (request) => {
console.error(request.url(), request.failure()?.errorText);
const requestOrigin = new URL(request.url()).origin;

if (requestOrigin === endpointOrigin || requestOrigin === scriptUrlPatternOrigin) {
// This fails the test
expect(request.failure()).toBeUndefined();
}
const url = request.url();
const failure = request.failure()?.errorText;

proxyIntegrations.forEach((proxy) => {
if (url.includes(proxy)) {
// This fails the test and prints the relevant info in test result
expect(url + ' ' + failure).toBeUndefined();
}
});
});

await page.goto('/playground');
await clickPlaygroundRefreshButton(page);
});
});
2 changes: 1 addition & 1 deletion e2e/sms-pumping/bot-unprotected.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import {
MAX_SMS_ATTEMPTS,
SMS_ATTEMPT_TIMEOUT_MAP,
SMS_FRAUD_COPY,
TEST_BUILD,
TEST_PHONE_NUMBER,
} from '../../src/server/sms-pumping/smsPumpingConst';
import { assertAlert, assertSnackbar, blockGoogleTagManager, resetScenarios } from '../e2eTestUtils';
import { ONE_MINUTE_MS } from '../../src/shared/timeUtils';
import { TEST_BUILD } from '../../src/envShared';

const TEST_ID = TEST_IDS.smsFraud;

Expand Down
43 changes: 43 additions & 0 deletions e2e/vpn-detection.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { test, expect, Page } from '@playwright/test';
import { TEST_IDS } from '../src/client/testIDs';
import { VPN_DETECTION_COPY } from '../src/app/vpn-detection/copy';
import { assertAlert } from './e2eTestUtils';

const getActivateButton = (page: Page) => page.getByTestId(TEST_IDS.vpnDetection.activateRegionalPricing);

test.describe('VPN Detection demo', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/vpn-detection');
});

test('should personalize UI copy based on user location', async ({ page }) => {
await expect(page.getByTestId(TEST_IDS.vpnDetection.callout)).toContainText(VPN_DETECTION_COPY.personalizedCallout);

const button = await page.getByTestId(TEST_IDS.vpnDetection.activateRegionalPricing);
await expect(button).toContainText(/\d+% off with/);
});

test('should allow to activate regional pricing without VPN', async ({ page }) => {
await getActivateButton(page).click();
await assertAlert({
page,
severity: 'success',
text: VPN_DETECTION_COPY.success({ discount: 20, country: 'Test Country' }).substring(0, 20),
});

const discountLineItem = await page.getByTestId(TEST_IDS.common.cart.discount);
await expect(discountLineItem).toContainText(VPN_DETECTION_COPY.discountName);
});

test('should not allow to activate regional pricing with VPN', async ({ page }) => {
// Mock positive VPN detection result
const vpnError = 'You are using a VPN.';
await page.route('/vpn-detection/api/activate-ppp', (route) =>
route.fulfill({ status: 403, body: JSON.stringify({ severity: 'error', message: vpnError }) }),
);

await getActivateButton(page).click();
await assertAlert({ page, severity: 'error', text: vpnError });
await expect(page.getByTestId(TEST_IDS.common.cart.discount)).toBeAttached({ attached: false });
});
});
1 change: 1 addition & 0 deletions next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slider": "^1.1.2",
"@t3-oss/env-nextjs": "^0.9.2",
"classnames": "^2.5.1",
"cors": "^2.8.5",
"framer-motion": "^11.0.8",
Expand Down
Binary file removed public/iphone14.png
Binary file not shown.
17 changes: 17 additions & 0 deletions src/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import './styles/global-styles.scss';
import { FunctionComponent, PropsWithChildren } from 'react';
import Footer from './client/components/common/Footer/Footer';
import Header from './client/components/common/Header/Header';
import styles from './styles/layout.module.scss';
import DeploymentUtils from './client/DeploymentUtils';

export const Layout: FunctionComponent<PropsWithChildren<{ embed: boolean }>> = ({ children, embed }) => {
return (
<div className={styles.layout}>
{embed ? null : <Header />}
<DeploymentUtils />
<div>{children}</div>
{embed ? null : <Footer />}
</div>
);
};
41 changes: 41 additions & 0 deletions src/Providers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use client';

import { QueryClient, QueryClientProvider } from 'react-query';
import { SnackbarProvider } from 'notistack';
import { PropsWithChildren } from 'react';
import { CloseSnackbarButton, CustomSnackbar } from './client/components/common/Alert/Alert';

const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
});

function Providers({ children }: PropsWithChildren) {
return (
<QueryClientProvider client={queryClient}>
<SnackbarProvider
action={(snackbarId) => <CloseSnackbarButton snackbarId={snackbarId} />}
maxSnack={4}
autoHideDuration={5000}
anchorOrigin={{
horizontal: 'left',
vertical: 'bottom',
}}
Components={{
default: CustomSnackbar,
success: CustomSnackbar,
error: CustomSnackbar,
warning: CustomSnackbar,
info: CustomSnackbar,
}}
>
{children}
</SnackbarProvider>
</QueryClientProvider>
);
}

export default Providers;
19 changes: 19 additions & 0 deletions src/app/api/decrypt/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { EventResponse } from '@fingerprintjs/fingerprintjs-pro-server-api';
import { decryptSealedResult } from '../../../server/decryptSealedResult';

export type DecryptPayload = {
sealedResult: string;
};

export type DecryptResponse = EventResponse;

export async function POST(request: Request) {
try {
const sealedData = ((await request?.json()) as DecryptPayload).sealedResult;
const data = await decryptSealedResult(sealedData);
return Response.json(data);
} catch (e) {
console.error(e);
return Response.json({ error: e }, { status: 500, statusText: String(e) });
}
}
9 changes: 9 additions & 0 deletions src/app/appLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use client';

import { useSelectedLayoutSegments } from 'next/navigation';
import { Layout } from '../Layout';

export default function LayoutUiInsideApp({ children }: { children: React.ReactNode }) {
const segments = useSelectedLayoutSegments();
return <Layout embed={segments.includes('embed')}>{children}</Layout>;
}
20 changes: 20 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Providers from '../Providers';
import LayoutUiInsideApp from './appLayout';

export const metadata = {
title: 'Fingerprint Pro Use Cases',
description:
'Explore the wide range of major use cases supported by Fingerprint, including a comprehensive demo that showcases both frontend and backend sample implementations with a persistent data layer for each use case.',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang='en'>
<body>
<Providers>
<LayoutUiInsideApp>{children}</LayoutUiInsideApp>
</Providers>
</body>
</html>
);
}
Loading
Loading