101 lines
6.6 KiB
JavaScript
101 lines
6.6 KiB
JavaScript
import { app, safeStorage } from 'electron'
|
|
import { mkdtemp, readFile, writeFile } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { resolve, join } from 'node:path'
|
|
import assert from 'node:assert/strict'
|
|
import { createMobileWindow } from '../../desktop/electron/mobile-host.mjs'
|
|
import { MobileVault } from '../../desktop/electron/mobile-secrets.mjs'
|
|
const directory = await mkdtemp(join(tmpdir(), 'aiturk-mobile-electron-'))
|
|
app.setPath('userData', join(directory, 'electron-profile'))
|
|
void app.whenReady().then(async () => {
|
|
let window
|
|
try {
|
|
assert.ok(safeStorage.isEncryptionAvailable(), 'Windows encryption unavailable')
|
|
window = createMobileWindow({ directory, assets: resolve('apps/desktop/dist'), show: false,
|
|
resolveBackend: async () => { throw new Error('No live backend in vault smoke test') } })
|
|
await new Promise(resolve => window.webContents.once('did-finish-load', resolve))
|
|
const result = await window.webContents.executeJavaScript(`(async () => {
|
|
const before = await mobileHost.call('setup');
|
|
await mobileHost.call('account.save', {token:'local-fake-account-for-encryption-test'});
|
|
const after = await mobileHost.call('setup');
|
|
return {before,after,heading:document.querySelector('h1').textContent};
|
|
})()`)
|
|
assert.equal(result.before.hasAccount, false); assert.equal(result.after.hasAccount, true)
|
|
const blob = await readFile(join(directory, 'mobile', 'mobile-secrets.bin'))
|
|
assert.ok(!blob.includes(Buffer.from('local-fake-account-for-encryption-test')))
|
|
const vault = new MobileVault(join(directory, 'mobile'), safeStorage)
|
|
assert.equal((await vault.load()).accountToken, 'local-fake-account-for-encryption-test')
|
|
await vault.save({ ...await vault.load(), deviceId: '11111111-1111-4111-8111-111111111111' })
|
|
const fingerprint = await window.webContents.executeJavaScript(`(async () => {
|
|
const { createDeviceIdentity } = await import('./mobile/peer-identity.mjs');
|
|
const setup = await mobileHost.call('setup');
|
|
const identity = await createDeviceIdentity(setup.deviceId);
|
|
await mobileHost.call('identity.save', {deviceId:setup.deviceId, fingerprint:identity.fingerprint});
|
|
let rejected = false;
|
|
try { await mobileHost.call('identity.save', {deviceId:setup.deviceId, fingerprint:Array(32).fill('00').join(':')}); }
|
|
catch { rejected = true; }
|
|
if (!rejected) throw new Error('Changed identity silently accepted');
|
|
return identity.fingerprint;
|
|
})()`)
|
|
assert.equal((await vault.load()).localFingerprint, fingerprint)
|
|
const reloaded = new Promise(resolve => window.webContents.once('did-finish-load', resolve))
|
|
window.webContents.reload(); await reloaded
|
|
const restored = await window.webContents.executeJavaScript(`(async () => {
|
|
const { loadDeviceIdentity } = await import('./mobile/peer-identity.mjs');
|
|
const setup = await mobileHost.call('setup');
|
|
const identity = await loadDeviceIdentity(setup.deviceId, setup.localFingerprint);
|
|
return identity.fingerprint;
|
|
})()`)
|
|
assert.equal(restored, fingerprint)
|
|
const originalFetch = globalThis.fetch, phoneId = '33333333-3333-4333-8333-333333333333'
|
|
const pairingId = '44444444-4444-4444-8444-444444444444', phonePin = Array(32).fill('AB').join(':')
|
|
let advertisedPin = phonePin, approvals = 0
|
|
globalThis.fetch = async (url, options) => {
|
|
const path = new URL(url).pathname
|
|
if (path === `/v1/aiturk/pairings/${pairingId}/approve` && options.method === 'POST') {
|
|
approvals++
|
|
return Response.json({ device_id: phoneId, status: 'approved' })
|
|
}
|
|
if (path === '/v1/aiturk/mobile/channels') return Response.json({ channels: [{ id: 'fixture-channel',
|
|
phone_id: phoneId, sdp: `v=0\r\na=fingerprint:sha-256 ${advertisedPin}\r\n` }] })
|
|
throw new Error('Unexpected network request in isolated test')
|
|
}
|
|
try {
|
|
const proof = JSON.stringify({ kind: 'aiturk-phone-identity', version: 1, pairingId, deviceId: phoneId, fingerprint: phonePin })
|
|
const qrImport = await window.webContents.executeJavaScript(`(async () => {
|
|
const { drawQr } = await import('./mobile/qr.mjs');
|
|
const canvas = document.createElement('canvas'); drawQr(canvas, ${JSON.stringify(proof)});
|
|
const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png'));
|
|
const transfer = new DataTransfer(); transfer.items.add(new File([blob], 'phone-identity.png', {type:'image/png'}));
|
|
const input = document.getElementById('phone-identity-file'); input.files = transfer.files;
|
|
await input.onchange({target:input});
|
|
return {pairingId:document.getElementById('pairing').value, proof:document.getElementById('phone-identity').value,
|
|
status:document.getElementById('status').textContent};
|
|
})()`)
|
|
assert.equal(qrImport.pairingId, pairingId)
|
|
assert.equal(qrImport.proof, proof)
|
|
assert.match(qrImport.status, /henüz izin verilmedi/)
|
|
assert.equal(approvals, 0)
|
|
const accepted = await window.webContents.executeJavaScript(`mobileHost.call('phone.approve', ${JSON.stringify({ pairingId, code: 'QRSTUVWX', identity: proof })})`)
|
|
assert.equal(accepted.device_id, phoneId)
|
|
assert.equal((await vault.load()).phoneFingerprints[phoneId], phonePin)
|
|
const inbox = await window.webContents.executeJavaScript(`mobileHost.call('inbox')`)
|
|
assert.equal(inbox.channels[0].trustedPhoneFingerprint, phonePin)
|
|
advertisedPin = Array(32).fill('CD').join(':')
|
|
await assert.rejects(window.webContents.executeJavaScript(`mobileHost.call('inbox')`), /kimliği/)
|
|
await assert.rejects(window.webContents.executeJavaScript(`mobileHost.call('channel.answer', {channelId:'fixture-channel',sdp:'unused'})`), /kimliği/)
|
|
const changed = JSON.stringify({ kind: 'aiturk-phone-identity', version: 1, pairingId, deviceId: phoneId, fingerprint: advertisedPin })
|
|
await assert.rejects(window.webContents.executeJavaScript(`mobileHost.call('phone.approve', ${JSON.stringify({ pairingId, code: 'QRSTUVWX', identity: changed })})`), /kimliği/)
|
|
assert.equal(approvals, 1)
|
|
assert.equal((await vault.load()).phoneFingerprints[phoneId], phonePin)
|
|
} finally { globalThis.fetch = originalFetch }
|
|
await vault.save({})
|
|
const receipt = { realElectronIpc: 'passed', windowsSafeStorage: 'passed', desktopIdentityReload: 'passed',
|
|
silentIdentityReplacement: 'rejected', locallyApprovedPhonePin: 'passed',
|
|
changedPhoneBeforeAnswer: 'rejected', identityQrImportWithoutApproval: 'passed', heading: result.heading, checkedAt: new Date().toISOString() }
|
|
await writeFile('apps/mobile/test-results/electron-smoke.json', JSON.stringify(receipt))
|
|
console.log(JSON.stringify(receipt))
|
|
window.destroy(); app.exit(0)
|
|
} catch (error) { console.error(error); window?.destroy(); app.exit(1) }
|
|
})
|