mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-03-04 06:25:11 +01:00
237 lines
9.4 KiB
TypeScript
237 lines
9.4 KiB
TypeScript
import fetch from 'cross-fetch';
|
|
import fs from 'fs-extra';
|
|
import path from 'path';
|
|
import crypto from 'crypto';
|
|
import { comment, commit } from './helpers';
|
|
|
|
const { exec } = require('./helpers');
|
|
|
|
const AUTHENTICITY_BASE_URL = 'https://data.trezor.io';
|
|
const ROOT = path.join(__dirname, '..', '..');
|
|
const CONFIG_FILE_PATH = path.join(ROOT, 'packages/connect/src/data/deviceAuthenticityConfig.ts');
|
|
const DEVICES_SUPPORTING_DEVICE_AUTHENTICITY_CHECK = ['T2B1', 'T3B1', 'T3T1', 'T3W1'] as const;
|
|
|
|
type DeviceModel = (typeof DEVICES_SUPPORTING_DEVICE_AUTHENTICITY_CHECK)[number];
|
|
|
|
const authenticityPaths = DEVICES_SUPPORTING_DEVICE_AUTHENTICITY_CHECK.reduce(
|
|
(acc, device) => {
|
|
acc[device] = {
|
|
authenticity: `firmware/${device.toLowerCase()}/authenticity.json`,
|
|
authenticityDev: `firmware/${device.toLowerCase()}/authenticity-dev.json`,
|
|
};
|
|
return acc;
|
|
},
|
|
{} as Record<DeviceModel, { authenticity: string; authenticityDev: string }>,
|
|
);
|
|
|
|
const fetchJSON = async (url: string) => {
|
|
const response = await fetch(url);
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
|
|
}
|
|
return response.json();
|
|
};
|
|
|
|
const getLatestTimestamp = (timestamps: string[]): string | null => {
|
|
if (timestamps.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const latestTime = Math.max(...timestamps.map(Date.parse));
|
|
|
|
return new Date(latestTime).toISOString();
|
|
};
|
|
|
|
const emptyAuthenticityConfig = {
|
|
root_pubkeys: [],
|
|
ca_pubkeys: [],
|
|
timestamp: 0,
|
|
};
|
|
|
|
const updateConfigFromJSON = async () => {
|
|
try {
|
|
const devicesKeys = Object.keys(authenticityPaths) as DeviceModel[];
|
|
|
|
// Import the current configuration object
|
|
let { deviceAuthenticityConfig } = require(CONFIG_FILE_PATH);
|
|
|
|
const timestamps: string[] = [];
|
|
|
|
for (const deviceKey of devicesKeys) {
|
|
const { authenticity, authenticityDev } = authenticityPaths[deviceKey];
|
|
const authenticityUrl = `${AUTHENTICITY_BASE_URL}/${authenticity}`;
|
|
// TODO: for now we do not have production keys for `T3W1` so we include it empty.
|
|
const authenticityData =
|
|
deviceKey === 'T3W1' ? emptyAuthenticityConfig : await fetchJSON(authenticityUrl);
|
|
timestamps.push(authenticityData.timestamp);
|
|
const authenticityDevUrl = `${AUTHENTICITY_BASE_URL}/${authenticityDev}`;
|
|
const authenticityDevData = await fetchJSON(authenticityDevUrl);
|
|
|
|
deviceAuthenticityConfig[deviceKey] = {
|
|
rootPubKeys: authenticityData.root_pubkeys,
|
|
caPubKeys: authenticityData.ca_pubkeys,
|
|
debug: {
|
|
rootPubKeys: authenticityDevData.root_pubkeys,
|
|
caPubKeys: authenticityDevData.ca_pubkeys,
|
|
},
|
|
};
|
|
}
|
|
|
|
const latestTimestamp = getLatestTimestamp(timestamps);
|
|
|
|
if (!latestTimestamp) {
|
|
// Sanity check and type safety.
|
|
throw new Error('Timestamp should always be present.');
|
|
}
|
|
|
|
deviceAuthenticityConfig['timestamp'] = latestTimestamp;
|
|
|
|
const updatedConfigString = `
|
|
/** THIS FILE IS AUTOMATICALLY UPDATED by script ci/scripts/check-connect-data.ts */
|
|
import { DeviceAuthenticityConfig } from './deviceAuthenticityConfigTypes';
|
|
|
|
/**
|
|
* How to update this config or check Sentry "Device authenticity invalid!" error? Please read this internal description:
|
|
* https://www.notion.so/satoshilabs/Device-authenticity-check-b8656a0fe3ab4a0d84c61534a73de462?pvs=4
|
|
*/
|
|
export const deviceAuthenticityConfig: DeviceAuthenticityConfig = ${JSON.stringify(deviceAuthenticityConfig, null, 4)};
|
|
`;
|
|
|
|
await fs.writeFile(CONFIG_FILE_PATH, updatedConfigString);
|
|
|
|
await exec('yarn', ['prettier', '--write', CONFIG_FILE_PATH]);
|
|
|
|
console.log('Configuration updated successfully.');
|
|
|
|
console.log('Checking if there were changes.');
|
|
const changes = await exec('git', ['diff', CONFIG_FILE_PATH]);
|
|
// Use the content to generate the hash in the branch so it is the same with same content.
|
|
// If we would use the hash provided by Git it would be different because it contains date as well.
|
|
const fileContent = await fs.readFile(CONFIG_FILE_PATH, 'utf8');
|
|
const hash = crypto.createHash('sha256').update(fileContent).digest('hex');
|
|
|
|
// Use the hash to create branch name to avoid using a branch that already exists.
|
|
const branchName = `chore/update-device-authenticity-config-${hash}`;
|
|
|
|
// Check if PR with same hash, so no new changes is already open, if so, ignore it, no new changes to add.
|
|
const prAlreadyOpen = await exec('gh', [
|
|
'search',
|
|
'prs',
|
|
'--repo=trezor/trezor-suite',
|
|
`--head=${branchName}`,
|
|
'--state=open',
|
|
]);
|
|
|
|
console.log('prAlreadyOpen', prAlreadyOpen);
|
|
|
|
const isSamePrAlreadyOpen = prAlreadyOpen.stdout !== '';
|
|
const isChangesFromRemote = changes.stdout !== '';
|
|
|
|
if (isSamePrAlreadyOpen) {
|
|
console.log('There is already a PR open with same changes.');
|
|
}
|
|
|
|
if (!isSamePrAlreadyOpen && isChangesFromRemote) {
|
|
console.log('There were changes in keys.');
|
|
|
|
// Before creating the new PR with new keys we check if there was already previous one.
|
|
// We can delete the previous one since latest one will contain all new changes.
|
|
// If you need to update search query you can test in GH: https://github.com/search and
|
|
// use the documentation https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests
|
|
const prList = await exec('gh', [
|
|
'search',
|
|
'prs',
|
|
'--repo=trezor/trezor-suite',
|
|
'--head=chore/update-device-authenticity-config',
|
|
'--state=open',
|
|
]);
|
|
console.log('prList', prList);
|
|
|
|
if (prList.stdout !== '') {
|
|
const prNumbers = prList.stdout.match(/(?<=\t)(\d+)(?=\t)/g);
|
|
|
|
console.log(`Found open pull requests ${prNumbers}. Closing...`);
|
|
for (const prNumber of prNumbers) {
|
|
try {
|
|
console.log(`Commenting on PR ${prNumber}`);
|
|
comment({
|
|
prNumber,
|
|
body: `Closing this PR since we are going to create new one with latest changes in ${AUTHENTICITY_BASE_URL}`,
|
|
});
|
|
|
|
console.log(`Closing PR ${prNumber}`);
|
|
await exec('gh', [
|
|
'pr',
|
|
'close',
|
|
prNumber,
|
|
'--repo',
|
|
'trezor/trezor-suite',
|
|
]);
|
|
console.log(`Closed PR #${prNumber}`);
|
|
} catch (error) {
|
|
console.error(`Failed to close PR #${prNumber}:`, error.message);
|
|
}
|
|
}
|
|
try {
|
|
console.log(`Deleting branch ${branchName}`);
|
|
await exec('gh', ['branch', '-d', branchName, '--repo', 'trezor/trezor-suite']);
|
|
console.log(`Deleted branch ${branchName}`);
|
|
} catch (error) {
|
|
console.error(`Failed to delete branch ${branchName}:`, error.message``);
|
|
}
|
|
} else {
|
|
console.log(`No open pull requests found.`);
|
|
}
|
|
|
|
const commitMessage = 'chore(connect): update device authenticity config';
|
|
await exec('git', ['checkout', '-b', branchName]);
|
|
await commit({
|
|
path: ROOT,
|
|
message: commitMessage,
|
|
});
|
|
// If the branch was already created this will fail, and this is desired feature because we
|
|
// do not want to create 2 branches and PRs with same content.
|
|
await exec('git', ['push', 'origin', branchName]);
|
|
|
|
const pr = await exec('gh', [
|
|
'pr',
|
|
'create',
|
|
'--repo',
|
|
'trezor/trezor-suite',
|
|
'--title',
|
|
`${commitMessage}`,
|
|
'--body-file',
|
|
'docs/packages/connect/check-connect-data.md',
|
|
'--base',
|
|
'develop',
|
|
'--head',
|
|
branchName,
|
|
]);
|
|
|
|
console.log('pr', pr);
|
|
|
|
const prUrlMatch = pr.stdout.match(/(https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+)/);
|
|
console.log('prUrlMatch', prUrlMatch);
|
|
if (!prUrlMatch) {
|
|
throw new Error('Failed to extract PR URL');
|
|
}
|
|
const prUrl = prUrlMatch[0];
|
|
|
|
// We get the PR number from the PR URL provided by the `gh pr create ...`.
|
|
const prNumber = prUrl.split('/').pop();
|
|
|
|
console.log(`Created PR: ${prUrl}`);
|
|
|
|
// Adding label to the created PR.
|
|
await exec('gh', ['pr', 'edit', `${prNumber}`, '--add-label', 'no-project']);
|
|
|
|
console.log(`Added label to PR #${prNumber}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error updating configuration: ${error.message}`);
|
|
throw new Error(error.message);
|
|
}
|
|
};
|
|
|
|
updateConfigFromJSON();
|