Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[eas-json] validate EAS Submit inputs better #2198

Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This is the log of notable changes to EAS CLI and related packages.
### 🧹 Chores

- Remove support for classic updates release channel in 50+. ([#2189](https://github.com/expo/eas-cli/pull/2189) by [@wschurman](https://github.com/wschurman))
- Validate EAS Submit inputs better. ([#2198](https://github.com/expo/eas-cli/pull/2198) by [@szdziedzic](https://github.com/szdziedzic))

## [7.0.0](https://github.com/expo/eas-cli/releases/tag/v7.0.0) - 2024-01-19

Expand Down
4 changes: 3 additions & 1 deletion packages/eas-json/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dependencies": {
"@babel/code-frame": "7.23.5",
"@expo/eas-build-job": "1.0.56",
"@hapi/address": "5.1.1",
"chalk": "4.1.2",
"env-string": "1.0.1",
"fs-extra": "11.2.0",
Expand All @@ -15,7 +16,8 @@
"log-symbols": "4.1.0",
"semver": "7.5.2",
"terminal-link": "2.1.1",
"tslib": "2.4.1"
"tslib": "2.4.1",
"uuid": "9.0.1"
},
"devDependencies": {
"@types/babel__code-frame": "7.0.3",
Expand Down
155 changes: 135 additions & 20 deletions packages/eas-json/src/__tests__/submitProfiles-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,10 @@ test('ios config with all required values', async () => {
ios: {
appleId: '[email protected]',
ascAppId: '1223423523',
appleTeamId: 'QWERTY',
appleTeamId: 'AB32CDE81F',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: 'abc-123-def-456',
ascApiKeyId: 'ABCD',
ascApiKeyIssuerId: '123e4567-e89b-12d3-a456-426614174000',
ascApiKeyId: 'AB32CDE81F',
},
},
},
Expand All @@ -121,11 +121,11 @@ test('ios config with all required values', async () => {

expect(iosProfile).toEqual({
appleId: '[email protected]',
appleTeamId: 'QWERTY',
appleTeamId: 'AB32CDE81F',
ascAppId: '1223423523',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: 'abc-123-def-456',
ascApiKeyId: 'ABCD',
ascApiKeyIssuerId: '123e4567-e89b-12d3-a456-426614174000',
ascApiKeyId: 'AB32CDE81F',
language: 'en-US',
});
});
Expand All @@ -137,7 +137,7 @@ test('ios config with ascApiKey fields set to env var', async () => {
ios: {
appleId: '[email protected]',
ascAppId: '1223423523',
appleTeamId: 'QWERTY',
appleTeamId: 'AB32CDE81F',
ascApiKeyPath: '$ASC_API_KEY_PATH',
ascApiKeyIssuerId: '$ASC_API_KEY_ISSUER_ID',
ascApiKeyId: '$ASC_API_KEY_ID',
Expand All @@ -148,18 +148,18 @@ test('ios config with ascApiKey fields set to env var', async () => {

try {
process.env.ASC_API_KEY_PATH = './path-ABCD.p8';
process.env.ASC_API_KEY_ISSUER_ID = 'abc-123-def-456';
process.env.ASC_API_KEY_ID = 'ABCD';
process.env.ASC_API_KEY_ISSUER_ID = '123e4567-e89b-12d3-a456-426614174000';
process.env.ASC_API_KEY_ID = 'AB32CDE81F';
const accessor = EasJsonAccessor.fromProjectPath('/project');
const iosProfile = await EasJsonUtils.getSubmitProfileAsync(accessor, Platform.IOS, 'release');

expect(iosProfile).toEqual({
appleId: '[email protected]',
ascAppId: '1223423523',
appleTeamId: 'QWERTY',
appleTeamId: 'AB32CDE81F',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: 'abc-123-def-456',
ascApiKeyId: 'ABCD',
ascApiKeyIssuerId: '123e4567-e89b-12d3-a456-426614174000',
ascApiKeyId: 'AB32CDE81F',
language: 'en-US',
});
} finally {
Expand All @@ -176,16 +176,16 @@ test('valid profile extending other profile', async () => {
ios: {
appleId: '[email protected]',
ascAppId: '1223423523',
appleTeamId: 'QWERTY',
appleTeamId: 'AB32CDE81F',
},
},
extension: {
extends: 'base',
ios: {
appleTeamId: 'ABCDEF',
appleTeamId: 'AB32CDE81F',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: 'abc-123-def-456',
ascApiKeyId: 'ABCD',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CDE81F',
},
},
},
Expand All @@ -202,19 +202,134 @@ test('valid profile extending other profile', async () => {
language: 'en-US',
appleId: '[email protected]',
ascAppId: '1223423523',
appleTeamId: 'QWERTY',
appleTeamId: 'AB32CDE81F',
});
expect(extendedProfile).toEqual({
language: 'en-US',
appleId: '[email protected]',
ascAppId: '1223423523',
appleTeamId: 'ABCDEF',
appleTeamId: 'AB32CDE81F',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: 'abc-123-def-456',
ascApiKeyId: 'ABCD',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CDE81F',
});
});

test('ios config with with invalid appleId', async () => {
await fs.writeJson('/project/eas.json', {
submit: {
release: {
ios: {
appleId: '| /bin/bash echo "hello"',
ascAppId: '1223423523',
appleTeamId: 'AB32CDE81F',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CDE81F',
},
},
},
});

const accessor = EasJsonAccessor.fromProjectPath('/project');
const promise = EasJsonUtils.getSubmitProfileAsync(accessor, Platform.IOS, 'release');
await expect(promise).rejects.toThrow(
'Invalid Apple ID was specified. It should be a valid email address. Example: "[email protected]".'
);
});

test('ios config with with invalid ascAppId', async () => {
await fs.writeJson('/project/eas.json', {
submit: {
release: {
ios: {
appleId: '[email protected]',
ascAppId: 'othervalue',
appleTeamId: 'AB32CDE81F',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CDE81F',
},
},
},
});

const accessor = EasJsonAccessor.fromProjectPath('/project');
const promise = EasJsonUtils.getSubmitProfileAsync(accessor, Platform.IOS, 'release');
await expect(promise).rejects.toThrow(
'Invalid Apple App Store Connect App ID was specified. It should contain 10 digits. Example: "1234567891". Learn more: https://expo.fyi/asc-app-id.md'
);
});

test('ios config with with invalid appleTeamId', async () => {
await fs.writeJson('/project/eas.json', {
submit: {
release: {
ios: {
appleId: '[email protected]',
ascAppId: '1223423523',
appleTeamId: 'ls -la',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '2af70a7a-2ac5-44d4-924e-ae97a7ca9333',
ascApiKeyId: 'AB32CDE81F',
},
},
},
});

const accessor = EasJsonAccessor.fromProjectPath('/project');
const promise = EasJsonUtils.getSubmitProfileAsync(accessor, Platform.IOS, 'release');
await expect(promise).rejects.toThrow(
'Invalid Apple Team ID was specified. It should contain 10 letters or digits. Example: "AB32CDE81F".'
);
});

test('ios config with with invalid ascApiKeyIssuerId', async () => {
await fs.writeJson('/project/eas.json', {
submit: {
release: {
ios: {
appleId: '[email protected]',
ascAppId: '1223423523',
appleTeamId: 'AB32CDE81F',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: 'notanuuid',
ascApiKeyId: 'AB32CDE81F',
},
},
},
});

const accessor = EasJsonAccessor.fromProjectPath('/project');
const promise = EasJsonUtils.getSubmitProfileAsync(accessor, Platform.IOS, 'release');
await expect(promise).rejects.toThrow(
'Invalid Apple App Store Connect API Key Issuer ID was specified. It should be a valid UUID. Example: "123e4567-e89b-12d3-a456-426614174000". Learn more: https://expo.fyi/creating-asc-api-key.'
);
});

test('ios config with with invalid ascApiKeyId', async () => {
await fs.writeJson('/project/eas.json', {
submit: {
release: {
ios: {
appleId: '[email protected]',
ascAppId: '1223423523',
appleTeamId: 'AB32CDE81F',
ascApiKeyPath: './path-ABCD.p8',
ascApiKeyIssuerId: '123e4567-e89b-12d3-a456-426614174000',
ascApiKeyId: 'wrong value',
},
},
},
});

const accessor = EasJsonAccessor.fromProjectPath('/project');
const promise = EasJsonUtils.getSubmitProfileAsync(accessor, Platform.IOS, 'release');
await expect(promise).rejects.toThrow(
`Invalid Apple App Store Connect API Key ID was specified. It should contain 10 letters or digits. Example: "AB32CDE81F". Learn more: https://expo.fyi/creating-asc-api-key.`
);
});

test('get profile names', async () => {
await fs.writeJson('/project/eas.json', {
submit: {
Expand Down
45 changes: 44 additions & 1 deletion packages/eas-json/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Platform } from '@expo/eas-build-job';
import { isEmailValid } from '@hapi/address';
import { validate } from 'uuid';
szdziedzic marked this conversation as resolved.
Show resolved Hide resolved

import { EasJsonAccessor } from './accessor';
import { resolveBuildProfile } from './build/resolver';
Expand All @@ -8,6 +10,10 @@ import { resolveSubmitProfile } from './submit/resolver';
import { SubmitProfile } from './submit/types';
import { EasJson } from './types';

const ASC_API_KEY_ID_REGEX = /^[\dA-Z]{10}$/;
const APPLE_TEAM_ID_REGEX = /^[\dA-Z]{10}$/;
const ASC_APP_ID_REGEX = /^\d{10}$/;

interface EasJsonDeprecationWarning {
message: string[];
docsUrl?: string;
Expand Down Expand Up @@ -96,12 +102,49 @@ export class EasJsonUtils {
return Object.keys(easJson?.submit ?? {});
}

public static validateSubmitProfile<T extends Platform>(
profile: SubmitProfile<T>,
platform: T
): void {
if (platform === Platform.IOS) {
const iosProfile = profile as SubmitProfile<Platform.IOS>;

if (iosProfile.ascApiKeyId && !ASC_API_KEY_ID_REGEX.test(iosProfile.ascApiKeyId)) {
throw new Error(
`Invalid Apple App Store Connect API Key ID was specified. It should contain 10 letters or digits. Example: "AB32CDE81F". Learn more: https://expo.fyi/creating-asc-api-key.`
szdziedzic marked this conversation as resolved.
Show resolved Hide resolved
);
}
if (iosProfile.appleTeamId && !APPLE_TEAM_ID_REGEX.test(iosProfile.appleTeamId)) {
throw new Error(
`Invalid Apple Team ID was specified. It should contain 10 letters or digits. Example: "AB32CDE81F".`
szdziedzic marked this conversation as resolved.
Show resolved Hide resolved
);
}
if (iosProfile.ascAppId && !ASC_APP_ID_REGEX.test(iosProfile.ascAppId)) {
throw new Error(
`Invalid Apple App Store Connect App ID was specified. It should contain 10 digits. Example: "1234567891". Learn more: https://expo.fyi/asc-app-id.md.`
szdziedzic marked this conversation as resolved.
Show resolved Hide resolved
);
}
if (iosProfile.ascApiKeyIssuerId && !validate(iosProfile.ascApiKeyIssuerId)) {
throw new Error(
`Invalid Apple App Store Connect API Key Issuer ID was specified. It should be a valid UUID. Example: "123e4567-e89b-12d3-a456-426614174000". Learn more: https://expo.fyi/creating-asc-api-key.`
);
}
if (iosProfile.appleId && !isEmailValid(iosProfile.appleId)) {
throw new Error(
`Invalid Apple ID was specified. It should be a valid email address. Example: "[email protected]".`
);
}
}
}

public static async getSubmitProfileAsync<T extends Platform>(
accessor: EasJsonAccessor,
platform: T,
profileName?: string
): Promise<SubmitProfile<T>> {
const easJson = await accessor.readAsync();
return resolveSubmitProfile({ easJson, platform, profileName });
const profile = resolveSubmitProfile({ easJson, platform, profileName });
this.validateSubmitProfile(profile, platform);
return profile;
}
}
12 changes: 12 additions & 0 deletions yarn.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading