Skip to content

Commit f785628

Browse files
feat: use 2 bits callbacks for verification occurred (0b10) and verification success (0b01)
1 parent 1fc7455 commit f785628

File tree

4 files changed

+179
-103
lines changed

4 files changed

+179
-103
lines changed

dapp/src/checkEmailPreviousValidation.js

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
const { request, gql } = require('graphql-request');
22

3-
// TODO: the method could return true if valid, false if invalid, null if no prior validation or unable to check
3+
/**
4+
* returns true if valid, false if invalid, undefined if no prior validation or unable to check
5+
*/
46
async function checkEmailPreviousValidation({
57
datasetAddress,
68
dappAddresses,
@@ -29,23 +31,38 @@ async function checkEmailPreviousValidation({
2931
try {
3032
const data = await request(pocoSubgraphUrl, query, variables);
3133
const tasks = data?.tasks || [];
32-
33-
return tasks.some((task) => {
34-
const callback = task.resultsCallback?.toLowerCase();
35-
return (
36-
callback &&
37-
callback.startsWith('0x') &&
38-
callback.endsWith(
39-
'0000000000000000000000000000000000000000000000000000000000000001'
40-
)
41-
);
42-
});
34+
if (
35+
tasks.some((task) => {
36+
const callback = task.resultsCallback?.toLowerCase();
37+
return (
38+
callback ===
39+
'0x0000000000000000000000000000000000000000000000000000000000000001' || // 0b01 legacy format valid
40+
callback ===
41+
'0x0000000000000000000000000000000000000000000000000000000000000003' // 0b11 checked valid
42+
);
43+
})
44+
) {
45+
return true;
46+
}
47+
if (
48+
tasks.some((task) => {
49+
const callback = task.resultsCallback?.toLowerCase();
50+
return (
51+
callback ===
52+
'0x0000000000000000000000000000000000000000000000000000000000000002' // 0b10 checked invalid
53+
);
54+
})
55+
) {
56+
return false;
57+
}
58+
// no prior validation found
59+
return undefined;
4360
} catch (error) {
4461
console.error(
4562
'GraphQL error:',
4663
error.response?.errors || error.message || error
4764
);
48-
return false;
65+
return undefined;
4966
}
5067
}
5168

dapp/src/executeTask.js

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,35 +49,27 @@ async function processProtectedData({
4949
validateProtectedData(protectedData);
5050

5151
// Step 1: Check if email was already validated
52-
const isEmailValid = await checkEmailPreviousValidation({
52+
result.isEmailValid = await checkEmailPreviousValidation({
5353
datasetAddress: protectedData.email,
5454
dappAddresses: appDeveloperSecret.WEB3MAIL_WHITELISTED_APPS,
5555
pocoSubgraphUrl: appDeveloperSecret.POCO_SUBGRAPH_URL,
5656
});
5757

5858
// Step 2: If not, try Mailgun
59-
if (!isEmailValid) {
59+
if (result.isEmailValid === undefined) {
6060
console.log('No prior verification found. Trying Mailgun...');
61-
const mailgunResult = await validateEmailAddress({
61+
result.isEmailValid = await validateEmailAddress({
6262
emailAddress: protectedData.email,
6363
mailgunApiKey: appDeveloperSecret.MAILGUN_APIKEY,
6464
});
65-
66-
if (mailgunResult === true) {
67-
result.isEmailValid = true;
68-
} else if (mailgunResult === false) {
69-
result.isEmailValid = false;
70-
throw Error('The protected email address seems to be invalid.');
71-
} else {
72-
console.warn(
73-
'Mailgun verification failed or was unreachable. Proceeding without check.'
74-
);
75-
}
7665
} else {
77-
result.isEmailValid = true;
7866
console.log('Email already verified, skipping Mailgun check.');
7967
}
8068

69+
if (result.isEmailValid === false) {
70+
throw Error('The protected email address seems to be invalid.');
71+
}
72+
8173
// Step 3: Decrypt email content
8274
const encryptedEmailContent = await downloadEncryptedContent(
8375
requesterSecret.emailContentMultiAddr
@@ -184,10 +176,17 @@ async function start() {
184176
});
185177

186178
// Add callback data for single processing if useCallback is enabled
187-
if (requesterSecret.useCallback && isEmailValid !== undefined) {
179+
if (requesterSecret.useCallback) {
188180
const bool32Bytes = Buffer.alloc(32);
189-
if (isEmailValid) {
190-
bool32Bytes[31] = 1; // set last byte to 1 for true
181+
// Encode 2 bits:
182+
// - Bit 1: Email validation was performed (1 = yes, 0 = no)
183+
// - Bit 0: Email is valid (1 = yes, 0 = no)
184+
if (isEmailValid === true) {
185+
// eslint-disable-next-line no-bitwise
186+
bool32Bytes[31] |= 0b11;
187+
} else if (isEmailValid === false) {
188+
// eslint-disable-next-line no-bitwise
189+
bool32Bytes[31] |= 0b10;
191190
}
192191
callbackData = `0x${bool32Bytes.toString('hex')}`;
193192
}

dapp/tests/e2e/app.test.js

Lines changed: 91 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -561,88 +561,114 @@ describe('sendEmail', () => {
561561
});
562562
});
563563

564-
it('should send an email successfully', async () => {
565-
// protected data setup
566-
process.env.IEXEC_DATASET_FILENAME = 'data.zip';
564+
describe('with valid email protectedData', () => {
565+
it('should send an email successfully', async () => {
566+
// protected data setup
567+
process.env.IEXEC_DATASET_FILENAME = 'data.zip';
567568

568-
await expect(start()).resolves.toBeUndefined();
569+
await expect(start()).resolves.toBeUndefined();
569570

570-
const { result, computed, files } = await readOutputs(
571-
process.env.IEXEC_OUT
572-
);
573-
expect(result).toStrictEqual({
574-
success: true,
575-
protectedData: process.env.IEXEC_DATASET_FILENAME,
576-
});
577-
expect(computed).toStrictEqual({
578-
'deterministic-output-path': `${process.env.IEXEC_OUT}/result.json`,
571+
const { result, computed, files } = await readOutputs(
572+
process.env.IEXEC_OUT
573+
);
574+
expect(result).toStrictEqual({
575+
success: true,
576+
protectedData: process.env.IEXEC_DATASET_FILENAME,
577+
});
578+
expect(computed).toStrictEqual({
579+
'deterministic-output-path': `${process.env.IEXEC_OUT}/result.json`,
580+
});
581+
expect(files.length).toBe(2);
579582
});
580-
expect(files.length).toBe(2);
581-
});
582583

583-
it('should send an email successfully and set the callback when requested', async () => {
584-
// protected data setup
585-
process.env.IEXEC_DATASET_FILENAME = 'data.zip';
586-
process.env.IEXEC_REQUESTER_SECRET_1 = JSON.stringify({
587-
...JSON.parse(process.env.IEXEC_REQUESTER_SECRET_1),
588-
useCallback: true,
589-
});
584+
it('should send an email successfully and set the valid email callback when requested', async () => {
585+
// protected data setup
586+
process.env.IEXEC_DATASET_FILENAME = 'data.zip';
587+
process.env.IEXEC_REQUESTER_SECRET_1 = JSON.stringify({
588+
...JSON.parse(process.env.IEXEC_REQUESTER_SECRET_1),
589+
useCallback: true,
590+
});
590591

591-
await expect(start()).resolves.toBeUndefined();
592+
await expect(start()).resolves.toBeUndefined();
592593

593-
const { result, computed, files } = await readOutputs(
594-
process.env.IEXEC_OUT
595-
);
596-
expect(result).toStrictEqual({
597-
success: true,
598-
protectedData: process.env.IEXEC_DATASET_FILENAME,
599-
});
600-
expect(computed).toStrictEqual({
601-
'deterministic-output-path': `${process.env.IEXEC_OUT}/result.json`,
602-
'callback-data':
603-
'0x0000000000000000000000000000000000000000000000000000000000000001',
594+
const { result, computed, files } = await readOutputs(
595+
process.env.IEXEC_OUT
596+
);
597+
expect(result).toStrictEqual({
598+
success: true,
599+
protectedData: process.env.IEXEC_DATASET_FILENAME,
600+
});
601+
expect(computed).toStrictEqual({
602+
'deterministic-output-path': `${process.env.IEXEC_OUT}/result.json`,
603+
'callback-data':
604+
'0x0000000000000000000000000000000000000000000000000000000000000003',
605+
});
606+
expect(files.length).toBe(2);
604607
});
605-
expect(files.length).toBe(2);
606608
});
607609

608-
it('should output an error if email address does not exist', async () => {
609-
// protected data setup
610-
process.env.IEXEC_DATASET_FILENAME = 'dataEmailUserDoesNotExist.zip';
610+
describe('with invalid email protectedData', () => {
611+
it('should output an error if email address does not exist', async () => {
612+
// protected data setup
613+
process.env.IEXEC_DATASET_FILENAME = 'dataEmailUserDoesNotExist.zip';
611614

612-
await expect(start()).resolves.toBeUndefined();
615+
await expect(start()).resolves.toBeUndefined();
613616

614-
const { result, computed, files } = await readOutputs(
615-
process.env.IEXEC_OUT
616-
);
617-
expect(result).toStrictEqual({
618-
success: false,
619-
protectedData: process.env.IEXEC_DATASET_FILENAME,
620-
error: 'The protected email address seems to be invalid.',
621-
});
622-
expect(computed).toStrictEqual({
623-
'deterministic-output-path': `${process.env.IEXEC_OUT}/result.json`,
617+
const { result, computed, files } = await readOutputs(
618+
process.env.IEXEC_OUT
619+
);
620+
expect(result).toStrictEqual({
621+
success: false,
622+
protectedData: process.env.IEXEC_DATASET_FILENAME,
623+
error: 'The protected email address seems to be invalid.',
624+
});
625+
expect(computed).toStrictEqual({
626+
'deterministic-output-path': `${process.env.IEXEC_OUT}/result.json`,
627+
});
628+
expect(files.length).toBe(2);
624629
});
625-
expect(files.length).toBe(2);
626-
});
627630

628-
it('should output an error if email address is disposable', async () => {
629-
// protected data setup
630-
process.env.IEXEC_DATASET_FILENAME = 'dataDisposableEmail.zip';
631+
it('should output an error if email address is disposable', async () => {
632+
// protected data setup
633+
process.env.IEXEC_DATASET_FILENAME = 'dataDisposableEmail.zip';
631634

632-
await expect(start()).resolves.toBeUndefined();
635+
await expect(start()).resolves.toBeUndefined();
633636

634-
const { result, computed, files } = await readOutputs(
635-
process.env.IEXEC_OUT
636-
);
637-
expect(result).toStrictEqual({
638-
success: false,
639-
protectedData: process.env.IEXEC_DATASET_FILENAME,
640-
error: 'The protected email address seems to be invalid.',
637+
const { result, computed, files } = await readOutputs(
638+
process.env.IEXEC_OUT
639+
);
640+
expect(result).toStrictEqual({
641+
success: false,
642+
protectedData: process.env.IEXEC_DATASET_FILENAME,
643+
error: 'The protected email address seems to be invalid.',
644+
});
645+
expect(computed).toStrictEqual({
646+
'deterministic-output-path': `${process.env.IEXEC_OUT}/result.json`,
647+
});
648+
expect(files.length).toBe(2);
641649
});
642-
expect(computed).toStrictEqual({
643-
'deterministic-output-path': `${process.env.IEXEC_OUT}/result.json`,
650+
651+
it('should set the invalid email callback when requested', async () => {
652+
// protected data setup
653+
process.env.IEXEC_DATASET_FILENAME = 'dataEmailUserDoesNotExist.zip';
654+
655+
await expect(start()).resolves.toBeUndefined();
656+
657+
const { result, computed, files } = await readOutputs(
658+
process.env.IEXEC_OUT
659+
);
660+
expect(result).toStrictEqual({
661+
success: false,
662+
protectedData: process.env.IEXEC_DATASET_FILENAME,
663+
error: 'The protected email address seems to be invalid.',
664+
});
665+
expect(computed).toStrictEqual({
666+
'deterministic-output-path': `${process.env.IEXEC_OUT}/result.json`,
667+
'callback-data':
668+
'0x0000000000000000000000000000000000000000000000000000000000000002',
669+
});
670+
expect(files.length).toBe(2);
644671
});
645-
expect(files.length).toBe(2);
646672
});
647673
});
648674
}

dapp/tests/unit/checkEmailPreviousValidation.test.js

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('checkEmailPreviousValidation', () => {
1616
jest.clearAllMocks();
1717
});
1818

19-
it('returns true if a valid email verification callback exists', async () => {
19+
it('returns true if a valid email verification callback exists (1 bit format)', async () => {
2020
request.mockResolvedValue({
2121
tasks: [
2222
{
@@ -33,8 +33,32 @@ describe('checkEmailPreviousValidation', () => {
3333
expect(result).toBe(true);
3434
});
3535

36-
it('returns false if no tasks are returned', async () => {
37-
request.mockResolvedValue({ tasks: [] });
36+
it('returns true if a valid email verification callback exists (2 bits format)', async () => {
37+
request.mockResolvedValue({
38+
tasks: [
39+
{
40+
resultsCallback:
41+
'0x0000000000000000000000000000000000000000000000000000000000000003',
42+
},
43+
],
44+
});
45+
46+
const result = await checkEmailPreviousValidation({
47+
datasetAddress,
48+
dappAddresses,
49+
});
50+
expect(result).toBe(true);
51+
});
52+
53+
it('returns false if a invalid email verification callback exists (2 bits format)', async () => {
54+
request.mockResolvedValue({
55+
tasks: [
56+
{
57+
resultsCallback:
58+
'0x0000000000000000000000000000000000000000000000000000000000000002',
59+
},
60+
],
61+
});
3862

3963
const result = await checkEmailPreviousValidation({
4064
datasetAddress,
@@ -43,7 +67,17 @@ describe('checkEmailPreviousValidation', () => {
4367
expect(result).toBe(false);
4468
});
4569

46-
it('returns false if none of the callbacks indicate a valid verification', async () => {
70+
it('returns undefined if no tasks are returned', async () => {
71+
request.mockResolvedValue({ tasks: [] });
72+
73+
const result = await checkEmailPreviousValidation({
74+
datasetAddress,
75+
dappAddresses,
76+
});
77+
expect(result).toBe(undefined);
78+
});
79+
80+
it('returns undefined if none of the callbacks indicate a valid or invalid verification', async () => {
4781
request.mockResolvedValue({
4882
tasks: [
4983
{
@@ -61,16 +95,16 @@ describe('checkEmailPreviousValidation', () => {
6195
datasetAddress,
6296
dappAddresses,
6397
});
64-
expect(result).toBe(false);
98+
expect(result).toBe(undefined);
6599
});
66100

67-
it('returns false if GraphQL query fails', async () => {
101+
it('returns undefined if GraphQL query fails', async () => {
68102
request.mockRejectedValue(new Error('GraphQL request failed'));
69103

70104
const result = await checkEmailPreviousValidation({
71105
datasetAddress,
72106
dappAddresses,
73107
});
74-
expect(result).toBe(false);
108+
expect(result).toBe(undefined);
75109
});
76110
});

0 commit comments

Comments
 (0)