Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions dapp/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.env
node_modules
coverage
tests/_test_outputs_
14,654 changes: 5,072 additions & 9,582 deletions dapp/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-sonarjs": "^0.21.0",
"iexec": "^8.2.1",
"iexec": "^8.22.0",
"jest": "^29.7.0",
"prettier": "^2.8.8"
}
Expand Down
2 changes: 1 addition & 1 deletion dapp/src/app.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const start = require('./sendEmail');
const start = require('./executeTask');

start().catch((error) => {
console.error(`Error: ${error.message}`);

Check warning on line 4 in dapp/src/app.js

View workflow job for this annotation

GitHub Actions / check-code

Unexpected console statement
process.exit(1);
});
43 changes: 31 additions & 12 deletions dapp/src/checkEmailPreviousValidation.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
const { request, gql } = require('graphql-request');

/**
* returns true if valid, false if invalid, undefined if no prior validation or unable to check
*/
async function checkEmailPreviousValidation({
datasetAddress,
dappAddresses,
pocoSubgraphUrl,
}) {
// TODO: add check in bulk task
const query = gql`
query checkSuccessfulTaskQuery($apps: [String!], $dataset: String!) {
tasks(
Expand All @@ -27,23 +31,38 @@
try {
const data = await request(pocoSubgraphUrl, query, variables);
const tasks = data?.tasks || [];

return tasks.some((task) => {
const callback = task.resultsCallback?.toLowerCase();
return (
callback &&
callback.startsWith('0x') &&
callback.endsWith(
'0000000000000000000000000000000000000000000000000000000000000001'
)
);
});
if (
tasks.some((task) => {
const callback = task.resultsCallback?.toLowerCase();
return (
callback ===
'0x0000000000000000000000000000000000000000000000000000000000000001' || // 0b01 legacy format valid
callback ===
'0x0000000000000000000000000000000000000000000000000000000000000003' // 0b11 checked valid
);
})
) {
return true;
}
if (
tasks.some((task) => {
const callback = task.resultsCallback?.toLowerCase();
return (
callback ===
'0x0000000000000000000000000000000000000000000000000000000000000002' // 0b10 checked invalid
);
})
) {
return false;
}
// no prior validation found
return undefined;
} catch (error) {
console.error(

Check warning on line 61 in dapp/src/checkEmailPreviousValidation.js

View workflow job for this annotation

GitHub Actions / check-code

Unexpected console statement
'GraphQL error:',
error.response?.errors || error.message || error
);
return false;
return undefined;
}
}

Expand Down
4 changes: 0 additions & 4 deletions dapp/src/emailService.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,5 @@ async function sendEmail({
.catch(() => {
throw new Error('Failed to send email');
});
return {
message: 'Your email has been sent successfully.',
status: 200,
};
}
module.exports = sendEmail;
229 changes: 229 additions & 0 deletions dapp/src/executeTask.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
const { promises: fs } = require('fs');
const {
IExecDataProtectorDeserializer,
} = require('@iexec/dataprotector-deserializer');
const sendEmail = require('./emailService');
const {
validateWorkerEnv,
validateAppSecret,
validateRequesterSecret,
validateProtectedData,
} = require('./validation');
const {
downloadEncryptedContent,
decryptContent,
} = require('./decryptEmailContent');
const { validateEmailAddress } = require('./validateEmailAddress');
const {
checkEmailPreviousValidation,
} = require('./checkEmailPreviousValidation');

async function processProtectedData({
index,
IEXEC_IN,
appDeveloperSecret,
requesterSecret,
}) {
const datasetFilename =
index > 0
? process.env[`IEXEC_DATASET_${index}_FILENAME`]
: process.env.IEXEC_DATASET_FILENAME;
const result = {
index,
protectedData: datasetFilename,
isEmailValid: undefined,
};
try {
let protectedData;
try {
const deserializerConfig = datasetFilename
? { protectedDataPath: `${IEXEC_IN}/${datasetFilename}` }
: {};
const deserializer = new IExecDataProtectorDeserializer(
deserializerConfig
);
protectedData = {
email: await deserializer.getValue('email', 'string'),
};
} catch (e) {
throw Error(`Failed to parse ProtectedData ${index}: ${e.message}`);
}

// Validate the protected data
validateProtectedData(protectedData);

// Step 1: Check if email was already validated
result.isEmailValid = await checkEmailPreviousValidation({
datasetAddress: protectedData.email,
dappAddresses: appDeveloperSecret.WEB3MAIL_WHITELISTED_APPS,
pocoSubgraphUrl: appDeveloperSecret.POCO_SUBGRAPH_URL,
});

// Step 2: If not, try Mailgun
if (result.isEmailValid === undefined) {
console.log('No prior verification found. Trying Mailgun...');

Check warning on line 64 in dapp/src/executeTask.js

View workflow job for this annotation

GitHub Actions / check-code

Unexpected console statement
result.isEmailValid = await validateEmailAddress({
emailAddress: protectedData.email,
mailgunApiKey: appDeveloperSecret.MAILGUN_APIKEY,
});
} else {
console.log('Email already verified, skipping Mailgun check.');

Check warning on line 70 in dapp/src/executeTask.js

View workflow job for this annotation

GitHub Actions / check-code

Unexpected console statement
}

if (result.isEmailValid === false) {
throw Error('The protected email address seems to be invalid.');
}

// Step 3: Decrypt email content
const encryptedEmailContent = await downloadEncryptedContent(
requesterSecret.emailContentMultiAddr
);
const requesterEmailContent = decryptContent(
encryptedEmailContent,
requesterSecret.emailContentEncryptionKey
);

// Step 4: Send email
await sendEmail({
email: protectedData.email,
mailJetApiKeyPublic: appDeveloperSecret.MJ_APIKEY_PUBLIC,
mailJetApiKeyPrivate: appDeveloperSecret.MJ_APIKEY_PRIVATE,
mailJetSender: appDeveloperSecret.MJ_SENDER,
mailgunApiKey: appDeveloperSecret.MAILGUN_APIKEY,
emailContent: requesterEmailContent,
emailSubject: requesterSecret.emailSubject,
contentType: requesterSecret.contentType,
senderName: requesterSecret.senderName,
});
result.success = true;
} catch (e) {
result.success = false;
result.error = e.message;
}
console.log(`Protected data ${index} processed:`, result);

Check warning on line 103 in dapp/src/executeTask.js

View workflow job for this annotation

GitHub Actions / check-code

Unexpected console statement
return result;
}

async function start() {

Check warning on line 107 in dapp/src/executeTask.js

View workflow job for this annotation

GitHub Actions / check-code

Refactor this function to reduce its Cognitive Complexity from 17 to the 15 allowed
const {
IEXEC_IN,
IEXEC_OUT,
IEXEC_APP_DEVELOPER_SECRET,
IEXEC_REQUESTER_SECRET_1,
IEXEC_BULK_SLICE_SIZE,
} = process.env;

// Check worker env
const workerEnv = validateWorkerEnv({ IEXEC_OUT });

let result; // { success: boolean, error?: string, protectedData?: string, results?: { index: number, protectedData: string, success: boolean, error?: string }[] }
let callbackData;
try {
// Parse the app developer secret environment variable
let appDeveloperSecret;
try {
appDeveloperSecret = JSON.parse(IEXEC_APP_DEVELOPER_SECRET);
appDeveloperSecret.WEB3MAIL_WHITELISTED_APPS =
appDeveloperSecret.WEB3MAIL_WHITELISTED_APPS
? JSON.parse(appDeveloperSecret.WEB3MAIL_WHITELISTED_APPS)
: undefined;
} catch {
throw Error('Failed to parse the developer secret');
}
appDeveloperSecret = validateAppSecret(appDeveloperSecret);

let requesterSecret;
try {
requesterSecret = JSON.parse(IEXEC_REQUESTER_SECRET_1);
} catch {
throw Error('Failed to parse requester secret');
}
requesterSecret = validateRequesterSecret(requesterSecret);

const bulkSize = parseInt(IEXEC_BULK_SLICE_SIZE, 10) || 0;

// Process multiple protected data
if (bulkSize > 0) {
console.log(`Processing ${bulkSize} protected data...`);

Check warning on line 147 in dapp/src/executeTask.js

View workflow job for this annotation

GitHub Actions / check-code

Unexpected console statement
const results = [];
// Process each protected data one by one to avoid rate limiting issues
for (let index = 1; index <= bulkSize; index += 1) {
// eslint-disable-next-line no-await-in-loop
const protectedDataResult = await processProtectedData({
index,
IEXEC_IN,
appDeveloperSecret,
requesterSecret,
});
results.push(protectedDataResult);
// eslint-disable-next-line no-await-in-loop
await new Promise((res) => {
setTimeout(res, 1000);
}); // Add a delay to avoid rate limiting
}
const successCount = results.filter((r) => r.success === true).length;
const errorCount = results.filter((r) => r.success !== true).length;
result = {
success: errorCount === 0,
error: errorCount > 0 ? 'Partial failure' : undefined,
totalCount: results.length,
successCount,
errorCount,
results: results.map((r) => ({
index: r.index,
protectedData: r.protectedData,
success: r.success,
isEmailValid: r.isEmailValid,
error: r.error,
})),
};
} else {
console.log('Processing single protected data...');

Check warning on line 181 in dapp/src/executeTask.js

View workflow job for this annotation

GitHub Actions / check-code

Unexpected console statement
const { protectedData, success, error, isEmailValid } =
await processProtectedData({
index: 0,
IEXEC_IN,
appDeveloperSecret,
requesterSecret,
});
// set result json
result = { protectedData, success, isEmailValid, error };
// Add callback data for single processing if useCallback is enabled
if (requesterSecret.useCallback) {
const bool32Bytes = Buffer.alloc(32);
// Encode 2 bits:
// - Bit 1: Email validation was performed (1 = yes, 0 = no)
// - Bit 0: Email is valid (1 = yes, 0 = no)
if (isEmailValid === true) {
// eslint-disable-next-line no-bitwise
bool32Bytes[31] |= 0b11;
} else if (isEmailValid === false) {
// eslint-disable-next-line no-bitwise
bool32Bytes[31] |= 0b10;
}
callbackData = `0x${bool32Bytes.toString('hex')}`;
}
}
} catch (e) {
console.error('Something went wrong:', e.message);

Check warning on line 208 in dapp/src/executeTask.js

View workflow job for this annotation

GitHub Actions / check-code

Unexpected console statement
result = { success: false, error: e.message };
}

console.log('Writing results:', JSON.stringify(result));

Check warning on line 212 in dapp/src/executeTask.js

View workflow job for this annotation

GitHub Actions / check-code

Unexpected console statement
await fs.writeFile(
`${workerEnv.IEXEC_OUT}/result.json`,
JSON.stringify(result, null, 2)
);

const computedData = {
'deterministic-output-path': `${workerEnv.IEXEC_OUT}/result.json`,
'callback-data': callbackData,
};

await fs.writeFile(
`${workerEnv.IEXEC_OUT}/computed.json`,
JSON.stringify(computedData, null, 2)
);
}

module.exports = start;
Loading