Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions assets/markdown/apple-login-notice.md.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Signing in to Apple

ShipThis sends your Apple ID and password **directly to Apple's authentication service**, using the open-source `@expo/apple-utils` library.

- Your password is **never stored** and is **never sent to ShipThis servers**.
- Only the resulting Apple **session cookies** are saved, locally, to your auth file.
- Your password is used once, for this sign-in, and then discarded.

**Read the exact code that handles your password: [<%= sourceURL %>](<%= sourceURL %>)**
26 changes: 0 additions & 26 deletions src/apple/auth.ts

This file was deleted.

42 changes: 19 additions & 23 deletions src/apple/expo.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
// This is just to fix the weird export issue (no export named Auth when running but ok when developing)
import * as expo from '@expo/apple-utils/build/index.js'
/**
* Interop shim for @expo/apple-utils.
*
* That package ships a single CommonJS bundle (built with @vercel/ncc), while
* ShipThis is ESM ("type": "module"). Node cannot statically detect the named
* exports of a bundle like that, so `import {Auth} from '@expo/apple-utils'`
* type-checks but throws at runtime:
*
* SyntaxError: Named export 'Auth' not found. The requested module
* '@expo/apple-utils' is a CommonJS module, which may not support all
* module.exports as named exports.
*
* The fix is the one Node itself suggests: import the CommonJS module as a
* default import and destructure it. We do that once, here, so the rest of the
* codebase can use ordinary named imports.
*/
import appleUtils from '@expo/apple-utils'

// TODO: this is awful
const defaultExport = expo.default
const {
export const {
ApiKey,
ApiKeyType,
App,
Expand All @@ -18,21 +31,4 @@ const {
ProfileType,
Session,
UserRole,
} = defaultExport

export {
ApiKey,
ApiKeyType,
App,
Auth,
BetaGroup,
BundleId,
CapabilityType,
CapabilityTypeOption,
Certificate,
CertificateType,
Profile,
ProfileType,
Session,
UserRole,
}
} = appleUtils
31 changes: 28 additions & 3 deletions src/commands/apple/login.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import appleUtils from '@expo/apple-utils'
import {Flags} from '@oclif/core'

import {getNewAuthState} from '@cli/apple/auth.js'
import {BaseAuthenticatedCommand} from '@cli/baseCommands/index.js'
import {getRenderedMarkdown} from '@cli/components/index.js'
import {getInput, getMaskedInput} from '@cli/utils/index.js'

// @expo/apple-utils is CommonJS, so it has to be imported as a default and destructured -
// see src/apple/expo.ts. We import Auth directly here rather than via that shim, so that
// everything which touches your Apple password is readable in this one file.
const {Auth} = appleUtils
Comment thread
madebydavid marked this conversation as resolved.

const SOURCE_URL = 'https://github.com/shipth-is/cli/blob/main/src/commands/apple/login.ts'

export default class AppleLogin extends BaseAuthenticatedCommand<typeof AppleLogin> {
static override args = {}

static override description = 'Authenticate with Apple - saves the session to the auth file'
static override description = `Authenticate with Apple - saves the session to the auth file.

Your Apple password is sent only to Apple, never to ShipThis. Only the resulting session cookies are saved locally. Read the source: ${SOURCE_URL}`
Comment thread
madebydavid marked this conversation as resolved.

static override examples = [
'<%= config.bin %> <%= command.id %>',
Expand Down Expand Up @@ -40,6 +50,16 @@ export default class AppleLogin extends BaseAuthenticatedCommand<typeof AppleLog
throw new Error('You are already logged in to Apple. Use --force to re-authenticate.')
}

// Shown even when --quiet - the iOS wizard runs this with --quiet
this.log(
getRenderedMarkdown({
filename: 'apple-login-notice.md.ejs',
templateVars: {
Comment thread
madebydavid marked this conversation as resolved.
sourceURL: `https://github.com/shipth-is/cli/blob/v${this.config.version}/src/commands/apple/login.ts`,
},
}),
)

const getAppleEmail = async (): Promise<string> => {
if (flags.appleEmail) return flags.appleEmail
const appleEmail = await getInput('Please enter your Apple Developer account email address: ')
Expand All @@ -57,12 +77,17 @@ export default class AppleLogin extends BaseAuthenticatedCommand<typeof AppleLog
const appleEmail = await getAppleEmail()
const applePassword = await getApplePassword()

const authState = await getNewAuthState(appleEmail, applePassword)
// The password goes to Apple here and is not used anywhere else
const authState = await Auth.loginAsync({
password: applePassword,
username: appleEmail,
})

if (!authState) {
throw new Error('Failed to authenticate with Apple')
}

// Session cookies are saved to the local auth file
await this.setAppleCookies(authState.cookies)

if (!this.flags.quiet) await this.config.runCommand(`apple:status`)
Expand Down