Skip to content
Draft
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
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,15 @@ xcodebuild \
Some test targets (e.g. `WordPressDataTests`) have their own scheme and are not part of the main `WordPress` scheme's test plan.
When the `WordPress` scheme build fails due to an unrelated target, try using the target's dedicated scheme instead.

### Simulator Sign-In

To automatically sign in to the app on an iOS simulator, see @docs/simulator-sign-in.md.

## Coding Standards
- Follow Swift API Design Guidelines
- Use strict access control modifiers where possible
- Use four spaces (not tabs)
- Lines should not have trailing whitespace
- Lines should not have trailing whitespace
- Follow the standard formatting practices enforced by SwiftLint
- Don't create `body` for `View` that are too long
- Use semantics text sizes like `.headline`
Expand Down
8 changes: 4 additions & 4 deletions Modules/Package.resolved

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

2 changes: 1 addition & 1 deletion Modules/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ let package = Package(
.package(url: "https://github.com/zendesk/support_sdk_ios", from: "8.0.3"),
.package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "v0.14.0-alpha.0"),
// We can't use wordpress-rs branches nor commits here. Only tags work.
.package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-20260226"),
.package(url: "https://github.com/automattic/wordpress-rs", branch: "pr-build/1226"),
.package(
url: "https://github.com/Automattic/color-studio",
revision: "bf141adc75e2769eb469a3e095bdc93dc30be8de"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,6 @@ class StaticTokenProvider: ApplicationTokenListDataProvider {

#Preview {
NavigationView {
ApplicationTokenListView(error: WpApiError.WpError(errorCode: .ApplicationPasswordsDisabledForUser, errorMessage: "Not available for the current user", statusCode: 400, response: "{}"))
ApplicationTokenListView(error: WpApiError.WpError(errorCode: .ApplicationPasswordsDisabledForUser, errorMessage: "Not available for the current user", statusCode: 400, response: "{}", requestUrl: "http://example.com", requestMethod: .get))
}
}
72 changes: 25 additions & 47 deletions WordPress/Classes/Login/LoginWithUrlView.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import SwiftUI
import AuthenticationServices
import WordPressAPI
import WordPressAuthenticator
import WordPressAPIInternal
import WordPressData
import DesignSystem
import WordPressShared
Expand Down Expand Up @@ -112,47 +112,36 @@ struct LoginWithUrlView: View {
isLoading = true
defer { isLoading = false }

let details: AutoDiscoveryAttemptSuccess
do {
let blog = try await SelfHostedSiteAuthenticator()
.signIn(site: urlField, from: presenter, context: .default)

dismiss()
self.loginCompleted(blog)
let loginClient = WordPressLoginClient(urlSession: URLSession(configuration: .ephemeral))
details = try await loginClient.details(ofSite: urlField)
} catch is CancellationError {
return
} catch {
if await shouldRedirectToDotComLogin(error: error) {
// We need to chain the dismissing and presenting,
// which is not supported by SwiftUI's `dismiss` variable.
presenter.dismiss(animated: true) {
Notice(title: Strings.wpcomSiteRedirect).post()
presentDotComLogin()
}
} else {
errorMessage = error.localizedDescription
}
errorMessage = error.localizedDescription
return
}
}

// If the error is "API root (wp-json) not found", it's possible that the user typed
// a WP.com simple site address. We should redirect to WP.com login if that's
// the case.
private func shouldRedirectToDotComLogin(
error: SelfHostedSiteAuthenticator.SignInError
) async -> Bool {
guard case let .authentication(error) = error,
let error = error as? AutoDiscoveryAttemptFailure,
error.shouldAttemptDotComLogin else { return false}

let api = WordPressComRestApi.anonymousApi(userAgent: WPUserAgent.defaultUserAgent())
let remote = BlogServiceRemoteREST(wordPressComRestApi: api, siteID: 0)
let url = WordPressAuthenticator.baseSiteURL(string: urlField)
let response: [AnyHashable: Any]? = await withCheckedContinuation { continuation in
remote.fetchUnauthenticatedSiteInfo(forAddress: url) {
continuation.resume(returning: $0)
} failure: { _ in
continuation.resume(returning: nil)
if Task.isCancelled { return }

if case let .oAuth2(endpoints) = details.authentication,
endpoints.authorizationUrl.contains("public-api.wordpress.com") {
presenter.dismiss(animated: true) {
Notice(title: Strings.wpcomSiteRedirect).post()
presentDotComLogin()
}
return
}

do {
let blog = try await SelfHostedSiteAuthenticator()
.signIn(details: details, from: presenter, context: .default)
dismiss()
self.loginCompleted(blog)
} catch {
errorMessage = error.localizedDescription
}
return (response?["isWordPressDotCom"] as? Bool) == true
}
}

Expand All @@ -176,17 +165,6 @@ private enum Strings {
)
}

private extension AutoDiscoveryAttemptFailure {
var shouldAttemptDotComLogin: Bool {
switch self {
case .ParseSiteUrl:
false
case .FindApiRoot, .FetchAndParseApiRoot:
true
}
}
}

// MARK: - SwiftUI Preview

#Preview {
Expand Down
17 changes: 16 additions & 1 deletion WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,26 @@ struct SelfHostedSiteAuthenticator {

@MainActor
private func authenticate(details: AutoDiscoveryAttemptSuccess, from viewController: UIViewController) async throws(SignInError) -> WpApiApplicationPasswordDetails {
guard case let .applicationPasswords(authURL) = details.authentication else {
let failure = AutoDiscoveryAttemptFailure.FetchAndParseApiRoot(
parsedSiteUrl: details.parsedSiteUrl,
apiRootUrl: details.apiRootUrl,
fetchAndParseApiRootFailure: .applicationPasswordsNotSupported(apiDetails: details.apiDetails, reason: nil)
)
throw .authentication(failure)
}

let appId = Self.wordPressAppId
let appName = Self.wordPressAppName

do {
let loginURL = details.loginURL(for: .init(id: appId, name: appName, callbackUrl: SelfHostedSiteAuthenticator.callbackURL.absoluteString))
let loginURL = createApplicationPasswordAuthenticationUrl(
loginUrl: authURL,
appName: appName,
appId: appId,
successUrl: SelfHostedSiteAuthenticator.callbackURL.absoluteString,
rejectUrl: SelfHostedSiteAuthenticator.callbackURL.absoluteString
).asURL()
let callback = try await authorize(url: loginURL, callbackURL: SelfHostedSiteAuthenticator.callbackURL, from: viewController)
return try internalClient.credentials(from: callback)
} catch {
Expand Down
6 changes: 5 additions & 1 deletion WordPress/Classes/Login/WordPressDotComAuthenticator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ struct WordPressDotComAuthenticator {

let token: String
do {
token = try await authenticate(from: viewController, prefersEphemeralWebBrowserSession: hasAlreadySignedIn, accountEmail: context.accountEmail(in: coreDataStack.mainContext))
if let tokenLaunchArgument = UserDefaults.standard.string(forKey: "ui-test-wpcom-token") {
token = tokenLaunchArgument
} else {
token = try await authenticate(from: viewController, prefersEphemeralWebBrowserSession: hasAlreadySignedIn, accountEmail: context.accountEmail(in: coreDataStack.mainContext))
}
} catch {
throw .authentication(error)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ private extension ApplicationPasswordRepository {
_ = try await api.applicationPasswords.retrieveCurrentWithViewContext()
validPasswords.append(password)
} catch let error as WpApiError {
if case let .WpError(errorCode, _, _, _) = error {
if case let .WpError(errorCode, _, _, _, _, _) = error {
if errorCode == .Unauthorized {
invalidPasswords.append(password)
}
Expand Down
39 changes: 39 additions & 0 deletions docs/simulator-sign-in.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Simulator Sign-In

Launch the app with credentials to enable automatic sign-in on the simulator.

## Step 1: Providing Credentials

### Self-hosted site

```bash
xcrun simctl launch --terminate-running-process booted org.wordpress \
-ui-test-reset-everything \
-ui-test-site-url https://example.com \
-ui-test-site-user <username> \
-ui-test-site-pass <application-password>
```

### WordPress.com account

```bash
xcrun simctl launch --terminate-running-process booted org.wordpress \
-ui-test-reset-everything \
-ui-test-wpcom-token <bearer-token>
```

## Step 2: Signing In

After launching with credentials, the app displays a sign-in page with two buttons: **"Continue with WordPress.com"** and **"Enter your existing site address"**.

### WordPress.com account

Tap **"Continue with WordPress.com"**. You will be automatically signed in.

### Self-hosted site

1. Tap **"Enter your existing site address"**
2. Enter the site address in the text field
3. Tap **"Continue"**

You will be automatically signed in.