mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-06 00:07:28 +00:00
Compare commits
4
Commits
rel-ios-e2e
...
0.1.89
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a4ed501d7 | ||
|
|
af580e9f17 | ||
|
|
91046725cc | ||
|
|
0376512dd5 |
+7
-6
@@ -57,12 +57,13 @@ module.exports = {
|
|||||||
supportsTablet: true,
|
supportsTablet: true,
|
||||||
bundleIdentifier: 'org.bulwarkmail.mobile',
|
bundleIdentifier: 'org.bulwarkmail.mobile',
|
||||||
buildNumber: IOS_BUILD_NUMBER,
|
buildNumber: IOS_BUILD_NUMBER,
|
||||||
config: {
|
// No usesNonExemptEncryption here. PGP and S/MIME (#58) are non-exempt
|
||||||
// PGP and S/MIME (#58) add end-to-end encryption beyond HTTPS/TLS,
|
// encryption, but only standard algorithms from open-source libraries
|
||||||
// so the app declares non-exempt encryption; App Store Connect asks
|
// and the app is not on the French store, so App Store Connect keeps no
|
||||||
// for the export compliance documentation on upload.
|
// declaration and has no ITSEncryptionExportComplianceCode to give. An
|
||||||
usesNonExemptEncryption: true,
|
// Info.plist saying YES without that code fails upload validation
|
||||||
},
|
// (90592), so the key stays out and scripts/testflight-public.mjs
|
||||||
|
// answers the question for each build through the API.
|
||||||
},
|
},
|
||||||
android: {
|
android: {
|
||||||
adaptiveIcon: {
|
adaptiveIcon: {
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "bulwark-mobile",
|
"name": "bulwark-mobile",
|
||||||
"version": "0.1.87",
|
"version": "0.1.89",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "bulwark-mobile",
|
"name": "bulwark-mobile",
|
||||||
"version": "0.1.87",
|
"version": "0.1.89",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bulwark-mobile",
|
"name": "bulwark-mobile",
|
||||||
"version": "0.1.87",
|
"version": "0.1.89",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ if (!group) {
|
|||||||
// ── Wait for the build ─────────────────────────────────────────
|
// ── Wait for the build ─────────────────────────────────────────
|
||||||
const query =
|
const query =
|
||||||
`/v1/builds?filter[app]=${app.id}&filter[version]=${encodeURIComponent(buildNumber)}` +
|
`/v1/builds?filter[app]=${app.id}&filter[version]=${encodeURIComponent(buildNumber)}` +
|
||||||
`&filter[preReleaseVersion.version]=${encodeURIComponent(version)}&fields[builds]=version,processingState`;
|
`&filter[preReleaseVersion.version]=${encodeURIComponent(version)}&fields[builds]=version,processingState,usesNonExemptEncryption`;
|
||||||
const started = Date.now();
|
const started = Date.now();
|
||||||
let build;
|
let build;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@@ -121,6 +121,18 @@ for (;;) {
|
|||||||
}
|
}
|
||||||
console.log(`Build ${version} (${buildNumber}) is processed.`);
|
console.log(`Build ${version} (${buildNumber}) is processed.`);
|
||||||
|
|
||||||
|
// ── Export compliance ──────────────────────────────────────────
|
||||||
|
// The app uses non-exempt encryption (PGP and S/MIME, #58) with standard
|
||||||
|
// algorithms only and is not on the French store: App Store Connect needs no
|
||||||
|
// documentation, so Info.plist has no key and the answer is given here.
|
||||||
|
if (build.attributes.usesNonExemptEncryption == null) {
|
||||||
|
const answer = await api('PATCH', `/v1/builds/${build.id}`, {
|
||||||
|
data: { type: 'builds', id: build.id, attributes: { usesNonExemptEncryption: true } },
|
||||||
|
});
|
||||||
|
if (!answer.ok) fail('Answering export compliance', answer);
|
||||||
|
console.log('Export compliance answered: standard encryption, no documentation needed.');
|
||||||
|
}
|
||||||
|
|
||||||
// ── Add it to the group ────────────────────────────────────────
|
// ── Add it to the group ────────────────────────────────────────
|
||||||
const add = await api('POST', `/v1/betaGroups/${group.id}/relationships/builds`, {
|
const add = await api('POST', `/v1/betaGroups/${group.id}/relationships/builds`, {
|
||||||
data: [{ type: 'builds', id: build.id }],
|
data: [{ type: 'builds', id: build.id }],
|
||||||
@@ -150,7 +162,7 @@ const detail = await api('GET', `/v1/builds/${build.id}/buildBetaDetail`);
|
|||||||
if (!detail.ok) fail('Reading the build state', detail);
|
if (!detail.ok) fail('Reading the build state', detail);
|
||||||
const external = detail.json.data.attributes.externalBuildState;
|
const external = detail.json.data.attributes.externalBuildState;
|
||||||
if (external === 'MISSING_EXPORT_COMPLIANCE') {
|
if (external === 'MISSING_EXPORT_COMPLIANCE') {
|
||||||
console.error('The build has no export compliance answer; set usesNonExemptEncryption in app.config.js.');
|
console.error('The build still has no export compliance answer; answer it in App Store Connect > TestFlight.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
if (external === 'READY_FOR_BETA_SUBMISSION') {
|
if (external === 'READY_FOR_BETA_SUBMISSION') {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ vi.mock('../booking-calendar-actions', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
import type { Email } from '../../../../api/types';
|
import type { Email } from '../../../../api/types';
|
||||||
|
import { usePolicyStore } from '../../../../stores/policy-store';
|
||||||
import { useSettingsStore } from '../../../../stores/settings-store';
|
import { useSettingsStore } from '../../../../stores/settings-store';
|
||||||
import { StructuredMailBanner } from '../StructuredMailBanner';
|
import { StructuredMailBanner } from '../StructuredMailBanner';
|
||||||
import { byTestId, render, textOf } from './render-native';
|
import { byTestId, render, textOf } from './render-native';
|
||||||
@@ -37,7 +38,11 @@ function mail(ar: string | null, subject = 'Your order'): Email {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initialSettings = useSettingsStore.getState();
|
const initialSettings = useSettingsStore.getState();
|
||||||
afterEach(() => useSettingsStore.setState(initialSettings, true));
|
const initialPolicy = usePolicyStore.getState();
|
||||||
|
afterEach(() => {
|
||||||
|
useSettingsStore.setState(initialSettings, true);
|
||||||
|
usePolicyStore.setState(initialPolicy, true);
|
||||||
|
});
|
||||||
|
|
||||||
describe('StructuredMailBanner', () => {
|
describe('StructuredMailBanner', () => {
|
||||||
it('shows the order card of a verified mail', () => {
|
it('shows the order card of a verified mail', () => {
|
||||||
@@ -67,6 +72,8 @@ describe('StructuredMailBanner', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('loads product images only when remote content would load', () => {
|
it('loads product images only when remote content would load', () => {
|
||||||
|
// The webmail's config is known and allows third-party traffic.
|
||||||
|
usePolicyStore.setState({ configPending: false, links: {} });
|
||||||
useSettingsStore.setState({ externalContentPolicy: 'block', trustedSenders: [] });
|
useSettingsStore.setState({ externalContentPolicy: 'block', trustedSenders: [] });
|
||||||
expect(byTestId(render(createElement(StructuredMailBanner, { email: mail(PASS) })), 'structured-mail-item-image')).toHaveLength(0);
|
expect(byTestId(render(createElement(StructuredMailBanner, { email: mail(PASS) })), 'structured-mail-item-image')).toHaveLength(0);
|
||||||
useSettingsStore.setState({ trustedSenders: ['orders@shop.example'] });
|
useSettingsStore.setState({ trustedSenders: ['orders@shop.example'] });
|
||||||
@@ -74,4 +81,12 @@ describe('StructuredMailBanner', () => {
|
|||||||
useSettingsStore.setState({ externalContentPolicy: 'allow', trustedSenders: [] });
|
useSettingsStore.setState({ externalContentPolicy: 'allow', trustedSenders: [] });
|
||||||
expect(byTestId(render(createElement(StructuredMailBanner, { email: mail(PASS) })), 'structured-mail-item-image')).toHaveLength(1);
|
expect(byTestId(render(createElement(StructuredMailBanner, { email: mail(PASS) })), 'structured-mail-item-image')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('loads no product images while the webmail config is unknown or forbids third-party traffic', () => {
|
||||||
|
useSettingsStore.setState({ externalContentPolicy: 'allow', trustedSenders: [] });
|
||||||
|
usePolicyStore.setState({ configPending: true, links: {} });
|
||||||
|
expect(byTestId(render(createElement(StructuredMailBanner, { email: mail(PASS) })), 'structured-mail-item-image')).toHaveLength(0);
|
||||||
|
usePolicyStore.setState({ configPending: false, links: { noThirdPartyTraffic: true } });
|
||||||
|
expect(byTestId(render(createElement(StructuredMailBanner, { email: mail(PASS) })), 'structured-mail-item-image')).toHaveLength(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,13 +2,22 @@ import { describe, expect, it } from 'vitest';
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
// With PGP and S/MIME in the app (#58) the encryption is no longer only
|
// PGP and S/MIME (#58) are non-exempt encryption with standard algorithms, and
|
||||||
// HTTPS/TLS, so the App Store export compliance answer must say so.
|
// the app is not on the French store, so App Store Connect has no declaration
|
||||||
|
// and no compliance code. Info.plist must not claim YES (upload validation
|
||||||
|
// rejects it without the code, 90592) nor NO (that would be untrue); the
|
||||||
|
// TestFlight script answers per build instead.
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, '../../..');
|
||||||
|
|
||||||
describe('export compliance', () => {
|
describe('export compliance', () => {
|
||||||
it('declares non-exempt encryption for iOS', () => {
|
it('leaves the encryption key out of the iOS config', () => {
|
||||||
const src = fs.readFileSync(path.resolve(__dirname, '../../../app.config.js'), 'utf8');
|
const src = fs.readFileSync(path.join(root, 'app.config.js'), 'utf8');
|
||||||
expect(src).toMatch(/usesNonExemptEncryption: true/);
|
expect(src).not.toMatch(/usesNonExemptEncryption\s*:/);
|
||||||
expect(src).not.toMatch(/usesNonExemptEncryption: false/);
|
});
|
||||||
|
|
||||||
|
it('answers non-exempt encryption for each TestFlight build', () => {
|
||||||
|
const src = fs.readFileSync(path.join(root, 'scripts/testflight-public.mjs'), 'utf8');
|
||||||
|
expect(src).toMatch(/attributes: \{ usesNonExemptEncryption: true \}/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user