Skip to content

Commit 5e381d4

Browse files
authored
fix: ns publish, apple authentication, appstore list (#5820)
1 parent 661b653 commit 5e381d4

10 files changed

+444
-329
lines changed

lib/commands/appstore-upload.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ export class PublishIOS implements ICommand {
5454
const user = await this.$applePortalSessionService.createUserSession(
5555
{ username, password },
5656
{
57-
applicationSpecificPassword: this.$options
58-
.appleApplicationSpecificPassword,
57+
applicationSpecificPassword:
58+
this.$options.appleApplicationSpecificPassword,
5959
sessionBase64: this.$options.appleSessionBase64,
6060
requireInteractiveConsole: true,
6161
requireApplicationSpecificPassword: true,
@@ -91,14 +91,12 @@ export class PublishIOS implements ICommand {
9191
mobileProvisionIdentifier
9292
);
9393

94-
// As we need to build the package for device
95-
this.$options.forDevice = true;
9694
this.$options.provision = mobileProvisionIdentifier;
9795

9896
const buildData = new IOSBuildData(
9997
this.$projectData.projectDir,
10098
platform,
101-
{ ...this.$options.argv, watch: false }
99+
{ ...this.$options.argv, buildForAppStore: true, watch: false }
102100
);
103101
ipaFilePath = await this.$buildController.prepareAndBuild(buildData);
104102
} else {
@@ -118,8 +116,8 @@ export class PublishIOS implements ICommand {
118116
await this.$itmsTransporterService.upload({
119117
credentials: { username, password },
120118
user,
121-
applicationSpecificPassword: this.$options
122-
.appleApplicationSpecificPassword,
119+
applicationSpecificPassword:
120+
this.$options.appleApplicationSpecificPassword,
123121
ipaFilePath,
124122
shouldExtractIpa: !!this.$options.ipa,
125123
verboseLogging: this.$logger.getLevel() === "TRACE",

lib/services/apple-portal/apple-portal-application-service.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
} from "./definitions";
1111

1212
export class ApplePortalApplicationService
13-
implements IApplePortalApplicationService {
13+
implements IApplePortalApplicationService
14+
{
1415
constructor(
1516
private $applePortalSessionService: IApplePortalSessionService,
1617
private $errors: IErrors,
@@ -36,13 +37,12 @@ export class ApplePortalApplicationService
3637
public async getApplicationsByProvider(
3738
contentProviderId: number
3839
): Promise<IApplePortalApplication> {
39-
const webSessionCookie = await this.$applePortalSessionService.createWebSession(
40-
contentProviderId
41-
);
40+
const webSessionCookie =
41+
await this.$applePortalSessionService.createWebSession(contentProviderId);
4242
const summaries: IApplePortalApplicationSummary[] = [];
4343
await this.getApplicationsByUrl(
4444
webSessionCookie,
45-
"https://appstoreconnect.apple.com/iris/v1/apps?include=appStoreVersions,prices",
45+
"https://appstoreconnect.apple.com/iris/v1/apps?include=appStoreVersions",
4646
summaries
4747
);
4848

lib/services/apple-portal/apple-portal-session-service.ts

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
} from "./definitions";
1111
import * as crypto from "crypto";
1212

13+
import { GSASRPAuthenticator } from "./srp/srp-wrapper";
14+
1315
export class ApplePortalSessionService implements IApplePortalSessionService {
1416
private loginConfigEndpoint =
1517
"https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com";
@@ -174,29 +176,53 @@ For more details how to set up your environment, please execute "ns publish ios
174176
}
175177

176178
private async loginCore(credentials: ICredentials): Promise<void> {
179+
const wrapper = new GSASRPAuthenticator(credentials.username);
180+
const initData = await wrapper.getInit();
181+
177182
const loginConfig = await this.getLoginConfig();
178-
const loginUrl = `${loginConfig.authServiceUrl}/auth/signin`;
183+
const loginUrl = `${loginConfig.authServiceUrl}/auth/signin/init`;
179184
const headers = {
180185
"Content-Type": "application/json",
181186
"X-Requested-With": "XMLHttpRequest",
182187
"X-Apple-Widget-Key": loginConfig.authServiceKey,
183188
Accept: "application/json, text/javascript",
184189
};
185-
const body = {
186-
accountName: credentials.username,
187-
password: credentials.password,
188-
rememberMe: true,
189-
};
190190

191-
const loginResponse = await this.$httpClient.httpRequest({
191+
const initResponse = await this.$httpClient.httpRequest({
192192
url: loginUrl,
193193
method: "POST",
194-
body,
194+
body: initData,
195195
headers,
196196
});
197197

198+
const body = JSON.parse(initResponse.response.body);
199+
200+
const completeData = await wrapper.getComplete(credentials.password, body);
201+
202+
const hashcash = await this.fetchHashcash(
203+
loginConfig.authServiceUrl,
204+
loginConfig.authServiceKey
205+
);
206+
207+
const completeUrl = `${loginConfig.authServiceUrl}/auth/signin/complete?isRememberMeEnabled=false`;
208+
const completeHeaders = {
209+
"Content-Type": "application/json",
210+
"X-Requested-With": "XMLHttpRequest",
211+
"X-Apple-Widget-Key": loginConfig.authServiceKey,
212+
Accept: "application/json, text/javascript",
213+
"X-Apple-HC": hashcash || "",
214+
};
215+
216+
const completeResponse = await this.$httpClient.httpRequest({
217+
url: completeUrl,
218+
method: "POST",
219+
completeHeaders,
220+
body: completeData,
221+
headers: completeHeaders,
222+
});
223+
198224
this.$applePortalCookieService.updateUserSessionCookie(
199-
loginResponse.headers["set-cookie"]
225+
completeResponse.headers["set-cookie"]
200226
);
201227
}
202228

@@ -221,6 +247,23 @@ For more details how to set up your environment, please execute "ns publish ios
221247
return config || this.defaultLoginConfig;
222248
}
223249

250+
private async fetchHashcash(
251+
authServiceUrl: string,
252+
authServiceKey: string
253+
): Promise<string> {
254+
const loginUrl = `${authServiceUrl}/auth/signin?widgetKey=${authServiceKey}`;
255+
const response = await this.$httpClient.httpRequest({
256+
url: loginUrl,
257+
method: "GET",
258+
});
259+
260+
const headers = response.headers;
261+
262+
const bits = headers["X-Apple-HC-Bits"];
263+
const challenge = headers["X-Apple-HC-Challenge"];
264+
return makeHashCash(bits, challenge);
265+
}
266+
224267
private async handleTwoFactorAuthentication(
225268
scnt: string,
226269
xAppleIdSessionId: string,
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { Client, Hash, Mode, Srp, util } from "@foxt/js-srp";
2+
import * as crypto from "crypto";
3+
4+
export type SRPProtocol = "s2k" | "s2k_fo";
5+
6+
export interface ServerSRPInitRequest {
7+
a: string;
8+
accountName: string;
9+
protocols: SRPProtocol[];
10+
}
11+
export interface ServerSRPInitResponse {
12+
iteration: number;
13+
salt: string;
14+
protocol: "s2k" | "s2k_fo";
15+
b: string;
16+
c: string;
17+
}
18+
export interface ServerSRPCompleteRequest {
19+
accountName: string;
20+
c: string;
21+
m1: string;
22+
m2: string;
23+
rememberMe: boolean;
24+
trustTokens: string[];
25+
}
26+
27+
let srp = new Srp(Mode.GSA, Hash.SHA256, 2048);
28+
const stringToU8Array = (str: string) => new TextEncoder().encode(str);
29+
const base64ToU8Array = (str: string) =>
30+
Uint8Array.from(Buffer.from(str, "base64"));
31+
export class GSASRPAuthenticator {
32+
constructor(private username: string) {}
33+
private srpClient?: Client = undefined;
34+
35+
private async derivePassword(
36+
protocol: "s2k" | "s2k_fo",
37+
password: string,
38+
salt: Uint8Array,
39+
iterations: number
40+
) {
41+
let passHash = new Uint8Array(
42+
await util.hash(srp.h, stringToU8Array(password))
43+
);
44+
if (protocol == "s2k_fo") {
45+
passHash = stringToU8Array(util.toHex(passHash));
46+
}
47+
48+
let imported = await crypto.subtle.importKey(
49+
"raw",
50+
passHash,
51+
{ name: "PBKDF2" },
52+
false,
53+
["deriveBits"]
54+
);
55+
let derived = await crypto.subtle.deriveBits(
56+
{
57+
name: "PBKDF2",
58+
hash: { name: "SHA-256" },
59+
iterations,
60+
salt,
61+
},
62+
imported,
63+
256
64+
);
65+
66+
return new Uint8Array(derived);
67+
}
68+
69+
async getInit(): Promise<ServerSRPInitRequest> {
70+
if (this.srpClient) throw new Error("Already initialized");
71+
this.srpClient = await srp.newClient(
72+
stringToU8Array(this.username),
73+
// provide fake passsword because we need to get data from server
74+
new Uint8Array()
75+
);
76+
let a = Buffer.from(util.bytesFromBigint(this.srpClient.A)).toString(
77+
"base64"
78+
);
79+
return {
80+
a,
81+
protocols: ["s2k", "s2k_fo"],
82+
accountName: this.username,
83+
};
84+
}
85+
async getComplete(
86+
password: string,
87+
serverData: ServerSRPInitResponse
88+
): Promise<
89+
Pick<ServerSRPCompleteRequest, "m1" | "m2" | "c" | "accountName">
90+
> {
91+
if (!this.srpClient) throw new Error("Not initialized");
92+
if (serverData.protocol != "s2k" && serverData.protocol != "s2k_fo")
93+
throw new Error("Unsupported protocol " + serverData.protocol);
94+
let salt = base64ToU8Array(serverData.salt);
95+
let serverPub = base64ToU8Array(serverData.b);
96+
let iterations = serverData.iteration;
97+
let derived = await this.derivePassword(
98+
serverData.protocol,
99+
password,
100+
salt,
101+
iterations
102+
);
103+
this.srpClient.p = derived;
104+
await this.srpClient.generate(salt, serverPub);
105+
let m1 = Buffer.from(this.srpClient._M).toString("base64");
106+
let M2 = await this.srpClient.generateM2();
107+
let m2 = Buffer.from(M2).toString("base64");
108+
return {
109+
accountName: this.username,
110+
m1,
111+
m2,
112+
c: serverData.c,
113+
};
114+
}
115+
}

lib/services/ios/export-options-plist-service.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,17 @@ export class ExportOptionsPlistService implements IExportOptionsPlistService {
112112
`;
113113
}
114114
if (provision) {
115-
plistTemplate += ` <key>provisioningProfiles</key>
115+
plistTemplate += ` <key>signingStyle</key>
116+
<string>manual</string>
117+
<key>provisioningProfiles</key>
116118
<dict>
117119
<key>${projectData.projectIdentifiers.ios}</key>
118120
<string>${provision}</string>
119121
${this.getExtensionProvisions()}
120122
</dict>`;
121123
}
122124
plistTemplate += ` <key>method</key>
123-
<string>app-store</string>
125+
<string>app-store-connect</string>
124126
<key>uploadBitcode</key>
125127
<false/>
126128
<key>compileBitcode</key>

lib/services/ios/xcodebuild-service.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,12 @@ export class XcodebuildService implements IXcodebuildService {
7979
platformData.getBuildOutputPath(buildConfig),
8080
projectData.projectName + ".xcarchive"
8181
);
82-
const output = await this.$exportOptionsPlistService.createDevelopmentExportOptionsPlist(
83-
archivePath,
84-
projectData,
85-
buildConfig
86-
);
82+
const output =
83+
await this.$exportOptionsPlistService.createDevelopmentExportOptionsPlist(
84+
archivePath,
85+
projectData,
86+
buildConfig
87+
);
8788
const args = [
8889
"-exportArchive",
8990
"-archivePath",
@@ -110,11 +111,14 @@ export class XcodebuildService implements IXcodebuildService {
110111
platformData.getBuildOutputPath(buildConfig),
111112
projectData.projectName + ".xcarchive"
112113
);
113-
const output = await this.$exportOptionsPlistService.createDistributionExportOptionsPlist(
114-
archivePath,
115-
projectData,
116-
buildConfig
117-
);
114+
const output =
115+
await this.$exportOptionsPlistService.createDistributionExportOptionsPlist(
116+
archivePath,
117+
projectData,
118+
buildConfig
119+
);
120+
const provision =
121+
buildConfig.provision || buildConfig.mobileProvisionIdentifier;
118122
const args = [
119123
"-exportArchive",
120124
"-archivePath",
@@ -123,6 +127,7 @@ export class XcodebuildService implements IXcodebuildService {
123127
output.exportFileDir,
124128
"-exportOptionsPlist",
125129
output.exportOptionsPlistFilePath,
130+
provision ? "" : "-allowProvisioningUpdates", // no profiles specificed so let xcode decide.
126131
];
127132

128133
await this.$xcodebuildCommandService.executeCommand(args, {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy