Skip to content

Commit

Permalink
Merge pull request #39 from dallen4/alpha
Browse files Browse the repository at this point in the history
IP Limiting & CORS
  • Loading branch information
dallen4 authored Apr 3, 2023
2 parents 8a38402 + 16ff193 commit dec4813
Show file tree
Hide file tree
Showing 21 changed files with 279 additions and 94 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/web_ci_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
env:
STAGE: ${{ github.ref_name }}
run: |
if [[ $STAGE == "alpha" ]]; then BASE_URI="https://test.drop.nieky.dev"; else BASE_URI="https://drop.nieky.dev"; fi
if [[ $STAGE == "alpha" ]]; then BASE_URI="https://alpha.deadrop.io"; else BASE_URI="https://deadrop.io"; fi
CI=true TEST_URI=$BASE_URI yarn web playwright test --trace on
- uses: actions/upload-artifact@v3
if: always()
Expand Down
19 changes: 19 additions & 0 deletions web/api/drops.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { generateIV } from '@shared/lib/util';
import { getRedis } from 'lib/redis';
import { formatDropKey } from 'lib/util';
import { nanoid } from 'nanoid';

const FIVE_MINS_IN_SEC = 10 * 60;

export const createDrop = async (peerId: string) => {
const client = getRedis();

const dropId = nanoid();
const nonce = generateIV();

const key = formatDropKey(dropId);
await client.hset(key, { peerId, nonce });
await client.expire(key, FIVE_MINS_IN_SEC);

return { dropId, nonce };
};
22 changes: 22 additions & 0 deletions web/api/limiter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { hashRaw } from '@shared/lib/crypto/operations';
import { getRedis } from '../lib/redis';

const DAY_IN_SEC = 60 * 60 * 24;

export const checkAndIncrementDropCount = async (ipAddress: string) => {
const userIpHash = await hashRaw(ipAddress);

const client = getRedis();

const userDropCount = await client.get(userIpHash);

if (!userDropCount) {
await client.setex(userIpHash, DAY_IN_SEC, 1);
} else {
if (parseInt(userDropCount) >= 5)
return false;
else await client.incr(userIpHash);
}

return true;
};
20 changes: 20 additions & 0 deletions web/api/middleware/cors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Cors from 'cors';

export const cors = Cors({
methods: ['POST', 'GET', 'DELETE'],
origin: (origin, callback) => {
console.log(origin);
if (
!origin ||
origin.endsWith('deadrop.io') ||
origin.includes('vscode-webview:')
)
callback(null, true);
else if (
process.env.NODE_ENV !== 'production' &&
origin.startsWith('http://localhost:')
)
callback(null, true);
else callback(new Error('Invalid origin'));
},
});
21 changes: 21 additions & 0 deletions web/api/middleware/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NextApiRequest, NextApiResponse } from 'next/types';

export function runMiddleware(
req: NextApiRequest,
res: NextApiResponse,
fn: (
req: NextApiRequest,
res: NextApiResponse,
cb: (result: any) => void,
) => any,
) {
return new Promise((resolve, reject) => {
fn(req, res, (result) => {
if (result instanceof Error) {
return reject(result);
}

return resolve(result);
});
});
}
89 changes: 48 additions & 41 deletions web/hooks/use-drop.ts → web/hooks/use-drop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ import {
importKey,
} from '@shared/lib/crypto/operations';
import { encryptFile, hashFile } from 'lib/crypto';
import { showNotification } from '@mantine/notifications';
import { IconX } from '@tabler/icons';

export const useDrop = () => {
const logsRef = useRef<Array<string>>([]);
const contextRef = useRef<DropContext>(initDropContext());

const [{ value: state }, send] = useMachine(dropMachine);

console.log('GRAB STATE: ', state);

const pushLog = (message: string) => logsRef.current.push(message);

const onMessage = async (msg: BaseMessage) => {
Expand Down Expand Up @@ -116,40 +116,47 @@ export const useDrop = () => {
const keyPair = await generateKeyPair();

pushLog('Key pair generated...');
console.log('Key pair generated');

const peerId = generateId();
const peer = await initPeer(peerId);

pushLog('Peer instance created successfully...');
console.log(`Peer initialized: ${peerId}`);

peer.on('connection', onConnection);

const { id, nonce } = await post<InitDropResult, { id: string }>(
DROP_API_PATH,
{
id: peer.id,
},
);
try {
const { id, nonce } = await post<InitDropResult, { id: string }>(
DROP_API_PATH,
{
id: peer.id,
},
);

pushLog('Session is ready to begin drop...');
console.log('DROP READY');
pushLog('Session is ready to begin drop...');

contextRef.current.id = id;
contextRef.current.peer = peer;
contextRef.current.keyPair = keyPair;
contextRef.current.nonce = nonce;
contextRef.current.id = id;
contextRef.current.peer = peer;
contextRef.current.keyPair = keyPair;
contextRef.current.nonce = nonce;

const event: InitDropEvent = {
type: DropEventType.Init,
id,
peer,
keyPair,
nonce,
};
const event: InitDropEvent = {
type: DropEventType.Init,
id,
peer,
keyPair,
nonce,
};

send(event);
send(event);
} catch (err) {
console.error(err);
showNotification({
message: (err as Error).message,
color: 'red',
icon: <IconX />,
autoClose: 2000,
});
}
};

const setPayload = async (content: string | File) => {
Expand All @@ -159,13 +166,13 @@ export const useDrop = () => {

const { payload, integrity } = isRaw
? {
payload: content,
integrity: await hashRaw(content),
}
payload: content,
integrity: await hashRaw(content),
}
: {
payload: content,
integrity: await hashFile(content),
};
payload: content,
integrity: await hashFile(content),
};

contextRef.current.integrity = integrity;
contextRef.current.message = payload;
Expand Down Expand Up @@ -209,15 +216,15 @@ export const useDrop = () => {

const payload = isFile
? await encryptFile(
contextRef.current.dropKey!,
contextRef.current.nonce!,
contextRef.current.message as File,
)
contextRef.current.dropKey!,
contextRef.current.nonce!,
contextRef.current.message as File,
)
: await encryptRaw(
contextRef.current.dropKey!,
contextRef.current.nonce!,
contextRef.current.message! as string,
);
contextRef.current.dropKey!,
contextRef.current.nonce!,
contextRef.current.message! as string,
);

pushLog('Payload encrypted, dropping...');

Expand All @@ -227,9 +234,9 @@ export const useDrop = () => {
payload,
meta: isFile
? {
name: (contextRef.current.message! as File).name,
type: (contextRef.current.message! as File).type,
}
name: (contextRef.current.message! as File).name,
type: (contextRef.current.message! as File).type,
}
: undefined,
};

Expand Down
3 changes: 0 additions & 3 deletions web/hooks/use-grab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,11 @@ export const useGrab = () => {
const keyPair = await generateKeyPair();

pushLog('Key pair generated...');
console.log('Key pair generated');

const peerId = generateId();
const peer = await initPeer(peerId);

pushLog('Peer instance created successfully...');
console.log(`Peer initialized: ${peerId}`);

const dropId = router.query.drop as string;

Expand Down Expand Up @@ -165,7 +163,6 @@ export const useGrab = () => {
connection.on('error', console.error);
connection.on('open', () => {
pushLog('Drop connection successful...');
console.log('Connection established');

send({ type: GrabEventType.Connect });
});
Expand Down
9 changes: 9 additions & 0 deletions web/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const BEGIN_DROP_BTN_ID = 'begin-drop-btn';

export const CONFIRM_PAYLOAD_BTN_ID = 'confirm-payload-btn';

export const DROP_LINK_ID = 'drop-link';

export const DROP_SECRET_BTN_ID = 'drop-secret-btn';

export const DROP_SECRET_VALUE_ID = 'drop-secret-value';
8 changes: 6 additions & 2 deletions web/lib/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ErrorBody } from 'types/fetch';

export const get = async <Data>(
path: string,
params?: { [key: string]: any },
Expand Down Expand Up @@ -35,9 +37,11 @@ export const post = async <Data, Body>(
body: body ? JSON.stringify(body) : undefined,
});

const data: Data = await res.json();
const data: Data | ErrorBody = await res.json();

return data;
if (res.status === 500) throw new Error((data as ErrorBody).message);

return data as Data;
};

export const deleteReq = async <Data, Body>(path: string, body?: Body) => {
Expand Down
2 changes: 1 addition & 1 deletion web/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ export const generateGrabUrl = (id: string) => {
return `${baseUrl.toString()}?${params.toString()}`;
};

export const generateDropKey = (id: string) => `drop:${id}`;
export const formatDropKey = (id: string) => `drop:${id}`;

export const getBrowserInfo = () => new UAParser().getResult();
20 changes: 15 additions & 5 deletions web/molecules/steps/SecretInputCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useDropContext } from 'contexts/DropContext';
import type { PayloadInputMode } from '@shared/types/common';
import { Captcha } from 'atoms/Captcha';
import { ACCEPTED_FILE_TYPES, MAX_PAYLOAD_SIZE } from '@shared/config/files';
import { CONFIRM_PAYLOAD_BTN_ID } from 'lib/constants';

export const SecretInputCard = () => {
const [mode, setMode] = useState<PayloadInputMode>('text');
Expand All @@ -37,9 +38,12 @@ export const SecretInputCard = () => {
}
};

const validateOnChange = (inputMode: PayloadInputMode, value: string | File | null) => {
const validateOnChange = (
inputMode: PayloadInputMode,
value: string | File | null,
) => {
if (inputMode === 'file') setIsValid(!!value);
else if (inputMode === 'json') setIsValid(isValidJson(value as string))
else if (inputMode === 'json') setIsValid(isValidJson(value as string));
else setIsValid((value as string).length > 0);
};

Expand Down Expand Up @@ -82,7 +86,9 @@ export const SecretInputCard = () => {
ref={textRef}
size={'md'}
placeholder={'Your secret'}
onChange={(event) => validateOnChange('text', event.target.value)}
onChange={(event) =>
validateOnChange('text', event.target.value)
}
/>
) : mode === 'json' ? (
<JsonInput
Expand All @@ -98,7 +104,7 @@ export const SecretInputCard = () => {
) : mode === 'file' ? (
<Group position={'center'}>
<FileButton
onChange={file => validateOnChange('file', file)}
onChange={(file) => validateOnChange('file', file)}
accept={ACCEPTED_FILE_TYPES.join(',')}
>
{(props) => (
Expand All @@ -116,7 +122,11 @@ export const SecretInputCard = () => {
onSuccess={() => setCanConfirm(true)}
onExpire={() => setCanConfirm(false)}
/>
<Button onClick={confirmPayload} disabled={!canConfirm || !isValid}>
<Button
id={CONFIRM_PAYLOAD_BTN_ID}
onClick={confirmPayload}
disabled={!canConfirm || !isValid}
>
Confirm Payload
</Button>
</StepCard>
Expand Down
9 changes: 7 additions & 2 deletions web/organisms/DropFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import DropLog from 'molecules/DropLog';
import StepCard from 'molecules/steps/StepCard';
import { SharePane } from 'molecules/SharePane';
import { SecretInputCard } from 'molecules/steps/SecretInputCard';
import { BEGIN_DROP_BTN_ID, DROP_SECRET_BTN_ID } from 'lib/constants';

const DropFlow = () => {
const theme = useMantineTheme();
Expand Down Expand Up @@ -52,7 +53,9 @@ const DropFlow = () => {
>
<StepCard title={'starting a session'}>
<Text>ready to start a drop?</Text>
<Button onClick={init}>Begin</Button>
<Button id={BEGIN_DROP_BTN_ID} onClick={init}>
Begin
</Button>
</StepCard>
</Stepper.Step>
<Stepper.Step
Expand All @@ -74,7 +77,9 @@ const DropFlow = () => {
description={isMobile && 'Drop your message'}
>
<StepCard title={'finish your deadrop'}>
<Button id={'drop-secret-btn'} onClick={drop}>Drop</Button>
<Button id={DROP_SECRET_BTN_ID} onClick={drop}>
Drop
</Button>
</StepCard>
</Stepper.Step>
<Stepper.Completed>
Expand Down
Loading

1 comment on commit dec4813

@vercel
Copy link

@vercel vercel bot commented on dec4813 Apr 3, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.