Skip to content

feat: Add OTP authenticator support#149

Open
cbaker6 wants to merge 9 commits into
mainfrom
addOTP
Open

feat: Add OTP authenticator support#149
cbaker6 wants to merge 9 commits into
mainfrom
addOTP

Conversation

@cbaker6
Copy link
Copy Markdown
Member

@cbaker6 cbaker6 commented Jan 14, 2024

New Pull Request Checklist

Issue Description

Approach

TODOs before merging

  • Add tests
  • Add entry to changelog
  • Add changes to documentation (guides, repository pages, in-code descriptions)

@cbaker6
Copy link
Copy Markdown
Member Author

cbaker6 commented Jan 14, 2024

@dblythy I still needs to do a little work on this and test it, but let me know what you think. I won't be for sure without testing, but from parse-community/parse-server#8661 it looks like you might be sending authData.mfa?.pending (I see a check for it in spec/AuthenticationAdapters.spec.js) to the client and it's sent as an object. If this is true, it will break strongly typed SDKs. I might able to add a workaround via Cloud Code that will delete pending before sending the user back to the client. Let me know what you think.

Copilot AI review requested due to automatic review settings January 19, 2026 02:35
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 19, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added OTP authentication support with async/await and Combine framework integration, including login, verification, and enrollment methods.
  • Documentation

    • Updated authentication documentation across GitHub, Google, and LinkedIn modules for clarity.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

The PR introduces OTP-based authentication support for ParseSwift with completion-based, async/await, and Combine APIs. Additionally, documentation across existing GitHub, Google, and LinkedIn authentication providers is standardized by removing "graph API" terminology.

Changes

Cohort / File(s) Summary
Project Configuration
ParseSwift.xcodeproj/project.pbxproj
Added three new OTP source files to the build graph, registered file references, and included them in the Sources build phase for compilation.
Documentation Updates
Sources/ParseSwift/Authentication/3rd Party/ParseGithub/*, ParseGoogle/*, ParseLinkedIn/*
Updated docstrings across nine files to replace "graph API login" with "login" for consistency. No functional or signature changes.
OTP Authentication Core
Sources/ParseSwift/Authentication/3rd Party/ParseOTP/ParseOTP.swift
Introduced new ParseOTP<AuthenticatedUser> struct conforming to ParseAuthentication with login/link/verify methods, nested Status and AuthenticationKeys enums, and extensions on ParseUser for OTP access. Includes authData dictionary builders and key validation.
OTP Async/Await Support
Sources/ParseSwift/Authentication/3rd Party/ParseOTP/ParseOTP+async.swift
Added four async/await wrappers (login/link with id/accessToken and authData variants) that bridge completion-based APIs using withCheckedThrowingContinuation.
OTP Combine Publishers
Sources/ParseSwift/Authentication/3rd Party/ParseOTP/ParseOTP+combine.swift
Added four Combine publisher variants (loginPublisher/linkPublisher) returning Future<AuthenticatedUser, ParseError>. ⚠️ Note: First loginPublisher variant contains redundant preliminary login invocation.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ParseOTP
    participant API as Parse API
    participant AuthUser as AuthenticatedUser

    User->>ParseOTP: verify(token)
    ParseOTP->>AuthUser: Read current user
    AuthUser-->>ParseOTP: Current user + existing token
    ParseOTP->>ParseOTP: Preserve token as oldToken
    ParseOTP->>ParseOTP: Inject new token into authData
    ParseOTP->>ParseOTP: link(authData)
    ParseOTP->>API: Link request with OTP authData
    API-->>ParseOTP: Authentication result
    ParseOTP-->>User: AuthenticatedUser
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • feat: Prepare for Swift 6 compatability #208: Updates authentication helper methods (ParseGitHub, ParseGoogle, ParseLinkedIn) with Sendable callback annotations—touches the same auth provider files modified here for documentation standardization.

Poem

🦸 With OTP's mighty shield so strong,
Plus Ultra auth flows all day long!
Async, Combine—both paths ring true,
Documentation cleaned—I AM HERE TO REVIEW! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description uses the template but is largely incomplete: Issue Description and Approach sections are empty, and all TODOs are unchecked with no explanation of work status or blockers. Complete the Issue Description and Approach sections with details about the OTP feature, and check/explain the status of TODOs (tests, changelog, documentation) or indicate when they will be completed.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: Add OTP authenticator support' clearly and concisely describes the main change: introducing OTP authentication support to the ParseSwift library.
Docstring Coverage ✅ Passed Docstring coverage is 92.59% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch addOTP

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds OTP (One-Time Password) authenticator support to ParseSwift by introducing three new files: ParseOTP.swift, ParseOTP+async.swift, and ParseOTP+combine.swift. The implementation follows the existing pattern used for third-party authenticators like Apple, Facebook, and Google.

Changes:

  • Added ParseOTP authenticator implementation with support for OTP-based multi-factor authentication
  • Provided async/await and Combine variants for the OTP authenticator
  • Updated Xcode project to include the new ParseOTP files and organize 3rd party authenticators alphabetically

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🤖 Fix all issues with AI agents
In `@Sources/ParseSwift/Authentication/3rd` Party/ParseOTP/ParseOTP.swift:
- Around line 216-228: The error message in the guard that calls
AuthenticationKeys.token.verifyMandatoryKeys(authData:) is incorrect (it
mentions "id", "idToken", "accessToken" from Google); update the completion
error message inside that guard to accurately state the required OTP keys
("token" or "mobile") so the failure returned by ParseOTP.verifyMandatoryKeys
reflects the real expected keys; locate the guard in ParseOTP.swift and replace
the message passed to .init(message:) with a concise message saying the authData
must contain "token" or "mobile".
- Around line 210-236: The completion closure parameter in
ParseOTP.link(authData:options:callbackQueue:completion:) must be declared
`@Sendable` to match the `@Sendable` expectation of AuthenticatedUser.link; update
the method signature to mark the completion parameter as `@Sendable` (and ensure
any internal forwarding still passes it unchanged to AuthenticatedUser.link).
Apply the same `@Sendable` annotation to the other ParseOTP.link overloads (the
other link methods in this file) so all link(...) completion handlers match
AuthenticatedUser.link's `@Sendable` requirement.
- Around line 115-153: verify currently invokes completion directly inside the
Task error paths (when AuthenticatedUser.current() fails or when authData is
missing) which ignores the provided callbackQueue; update both failure branches
to dispatch the completion call onto callbackQueue (e.g., callbackQueue.async {
completion(.failure(...)) }) so that all completion calls from verify (including
errors) honor the callbackQueue the caller provided; refer to the verify
function, the completion closure, callbackQueue parameter, and existing
link(...) success path for consistency.
- Around line 86-102: The completion closure in ParseOTP.login should be marked
`@Sendable` to satisfy Swift 6 concurrency checking; update the method signature
for login to declare completion as `@Sendable` `@escaping`
(Result<AuthenticatedUser, ParseError>) -> Void (i.e., change completion:
`@escaping` to completion: `@Sendable` `@escaping`) and adjust any callers if
necessary to match the new closure type (referencing the
login(authData:options:callbackQueue:completion:) function and its completion
parameter).

In `@Sources/ParseSwift/Authentication/3rd` Party/ParseOTP/ParseOTP+async.swift:
- Around line 23-38: The async wrapper login(id:idToken:accessToken:options:) in
ParseOTP+async.swift calls a non-existent completion-based overload; remove this
async wrapper or rewrite it to use the existing API by converting to the
authData variant: call the class's
login(authData:options:callbackQueue:completion:) (or delete the async login
entirely since ParseOTP currently returns "login not supported"); update or
remove the function named login in ParseOTP+async.swift so it no longer calls
self.login(id:idToken:accessToken:options:completion:).
- Around line 46-57: The async wrapper login(authData:options:) must accept and
forward the missing callbackQueue required by the underlying method
login(authData:options:callbackQueue:completion:); update the async function
signature to include a callbackQueue parameter (matching the underlying method's
type and default if present) and forward that parameter in the call to
self.login(..., callbackQueue: callbackQueue, completion: continuation.resume)
so the continuation is invoked on the correct queue.
- Around line 95-106: The async wrapper link(authData:options:) calls the
underlying self.link(...) overload but omits the required callbackQueue
parameter; update the call inside withCheckedThrowingContinuation to pass the
callbackQueue argument expected by the underlying API (e.g., add callbackQueue:
nil or the appropriate DispatchQueue/CallbackQueue used elsewhere) so the
invocation matches the signature used by the other login variant and forwards
completion: continuation.resume.
- Around line 71-86: The async wrapper calls a non-existent link variant;
instead construct an authData dictionary from the provided id, idToken and
accessToken and call the existing
link(authData:options:callbackQueue:completion:) helper. Update the body of
ParseOTP+async.swift's link(id:idToken:accessToken:options:) to build authData
(include only non-nil values), then call self.link(authData: authData, options:
options, callbackQueue: /* same queue used elsewhere, e.g. .main */ ,
completion: continuation.resume) so the continuation gets resumed by the actual
existing implementation.

In `@Sources/ParseSwift/Authentication/3rd` Party/ParseOTP/ParseOTP+combine.swift:
- Around line 50-61: The loginPublisher is calling
login(authData:options:completion:) but the real API requires
login(authData:options:callbackQueue:completion:); update the call inside
loginPublisher to pass the callbackQueue through (accept or default a
DispatchQueue parameter if needed) so it calls
login(authData:options:callbackQueue:completion:). Also, to satisfy Sendable
requirements, wrap the promise with nonisolated(unsafe) as done in
ParseGitHub+combine.swift when passing the completion closure to login. Ensure
you reference the loginPublisher function and the
login(authData:options:callbackQueue:completion:) signature when making the
change.
- Around line 29-43: Remove the stray duplicate self.login(...) call in
loginPublisher (the incomplete call at lines 30-34) and replace the remaining
call so it matches an existing completion-based API, or implement the missing
completion-based method in ParseOTP.swift; specifically ensure either
ParseOTP+combine.swift invokes an existing method like
login(id:idToken:accessToken:options:completion:) that actually exists, or add
that method (ParseOTP.login(id:idToken:accessToken:options:completion:)) to
ParseOTP.swift with the expected signature and completion handling so the
Future's promise can be passed as the completion.
- Around line 97-108: linkPublisher is missing the callbackQueue parameter when
calling link; update the call inside func linkPublisher(authData:options:) to
pass callbackQueue: .main to the self.link(...) invocation so the completion is
invoked on the main queue (refer to function linkPublisher and the
link(authData:options:completion:) overload).
- Around line 74-89: The linkPublisher currently calls a non-existent
self.link(id:idToken:accessToken:options:completion:); update linkPublisher (in
ParseOTP+combine.swift) to call the actual ParseOTP link overloads instead — use
self.link(authData:options:completion:) when you have token(s) (build an
authData dictionary from id, idToken, accessToken), use
self.link(mobile:options:completion:) when the id represents a mobile number, or
use self.link(secret:token:options:completion:) when the intended API is the
secret/token pair; implement a small conditional branch inside linkPublisher to
pick the correct existing method and pass the provided options and the promise
as completion.

Comment on lines +86 to +102
func login(
authData: [String: String],
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void
) {
callbackQueue.async {
completion(
.failure(
.init(
code: .otherCause,
message: "Login is not supported. Please use \"link(...)\"."
)
)
)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

The completion handler needs @Sendable annotation, young hero!

To be a Symbol of Peace in Swift 6 concurrency, the completion handler should be marked @Sendable. The static analysis is warning about capturing non-Sendable types in @Sendable closures.

♻️ Proposed fix
     func login(
         authData: [String: String],
         options: API.Options = [],
         callbackQueue: DispatchQueue = .main,
-        completion: `@escaping` (Result<AuthenticatedUser, ParseError>) -> Void
+        completion: `@escaping` `@Sendable` (Result<AuthenticatedUser, ParseError>) -> Void
     ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func login(
authData: [String: String],
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void
) {
callbackQueue.async {
completion(
.failure(
.init(
code: .otherCause,
message: "Login is not supported. Please use \"link(...)\"."
)
)
)
}
}
func login(
authData: [String: String],
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
completion: `@escaping` `@Sendable` (Result<AuthenticatedUser, ParseError>) -> Void
) {
callbackQueue.async {
completion(
.failure(
.init(
code: .otherCause,
message: "Login is not supported. Please use \"link(...)\"."
)
)
)
}
}
🧰 Tools
🪛 GitHub Check: spm-test

[warning] 93-93:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]


[warning] 93-93:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]


[warning] 93-93:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]


[warning] 93-93:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]


[warning] 93-93:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]

🤖 Prompt for AI Agents
In `@Sources/ParseSwift/Authentication/3rd` Party/ParseOTP/ParseOTP.swift around
lines 86 - 102, The completion closure in ParseOTP.login should be marked
`@Sendable` to satisfy Swift 6 concurrency checking; update the method signature
for login to declare completion as `@Sendable` `@escaping`
(Result<AuthenticatedUser, ParseError>) -> Void (i.e., change completion:
`@escaping` to completion: `@Sendable` `@escaping`) and adjust any callers if
necessary to match the new closure type (referencing the
login(authData:options:callbackQueue:completion:) function and its completion
parameter).

Comment on lines +115 to +153
func verify(
token: String,
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void
) {
Task {
do {
let currentUser = try await AuthenticatedUser.current()
guard let potentialOTPAuthData = currentUser.authData?[Self.__type],
var otpAuthData = potentialOTPAuthData else {
let error = ParseError(
code: .otherCause,
message: "Logged in user is missing authData, did you link MFA before attempting to call \"verify()\"."
)
completion(.failure(error))
return
}
if let oldToken = otpAuthData[AuthenticationKeys.token.rawValue] {
otpAuthData[AuthenticationKeys.oldToken.rawValue] = oldToken
}
otpAuthData[AuthenticationKeys.token.rawValue] = token
link(
authData: otpAuthData,
options: options,
callbackQueue: callbackQueue,
completion: completion
)
} catch {
let defaultError = ParseError(
code: .otherCause,
message: "Could not retrieve logged in user from Keychain",
swift: error
)
let parseError = error as? ParseError ?? defaultError
completion(.failure(parseError))
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

The verify method doesn't consistently use callbackQueue, hero!

The verify method accepts a callbackQueue parameter but when errors occur inside the Task block (lines 130 and 150), completion is called directly without dispatching to callbackQueue. This could cause unexpected threading behavior!

The successful path at line 137-142 does dispatch via link(...) which respects the queue, but the error paths don't.

💥 Proposed fix
             do {
                 let currentUser = try await AuthenticatedUser.current()
                 guard let potentialOTPAuthData = currentUser.authData?[Self.__type],
                     var otpAuthData = potentialOTPAuthData else {
                     let error = ParseError(
                         code: .otherCause,
                         message: "Logged in user is missing authData, did you link MFA before attempting to call \"verify()\"."
                     )
-                    completion(.failure(error))
+                    callbackQueue.async {
+                        completion(.failure(error))
+                    }
                     return
                 }
                 // ... success path ...
             } catch {
                 let defaultError = ParseError(
                     code: .otherCause,
                     message: "Could not retrieve logged in user from Keychain",
                     swift: error
                 )
                 let parseError = error as? ParseError ?? defaultError
-                completion(.failure(parseError))
+                callbackQueue.async {
+                    completion(.failure(parseError))
+                }
             }
🤖 Prompt for AI Agents
In `@Sources/ParseSwift/Authentication/3rd` Party/ParseOTP/ParseOTP.swift around
lines 115 - 153, verify currently invokes completion directly inside the Task
error paths (when AuthenticatedUser.current() fails or when authData is missing)
which ignores the provided callbackQueue; update both failure branches to
dispatch the completion call onto callbackQueue (e.g., callbackQueue.async {
completion(.failure(...)) }) so that all completion calls from verify (including
errors) honor the callbackQueue the caller provided; refer to the verify
function, the completion closure, callbackQueue parameter, and existing
link(...) success path for consistency.

Comment on lines +210 to +236
func link(
authData: [String: String],
options: API.Options = [],
callbackQueue: DispatchQueue = .main,
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void
) {
guard AuthenticationKeys.token.verifyMandatoryKeys(authData: authData) else {
callbackQueue.async {
completion(
.failure(
.init(
code: .otherCause,
message: "Should have \"authData\" in consisting of keys \"id\", \"idToken\" or \"accessToken\"."
)
)
)
}
return
}
AuthenticatedUser.link(
Self.__type,
authData: authData,
options: options,
callbackQueue: callbackQueue,
completion: completion
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

This link method also needs @Sendable on the completion handler!

Static analysis reports at line 234: "passing non-Sendable parameter 'completion' to function expecting a '@sendable' closure". The underlying AuthenticatedUser.link expects a @Sendable completion.

♻️ Proposed fix
     func link(
         authData: [String: String],
         options: API.Options = [],
         callbackQueue: DispatchQueue = .main,
-        completion: `@escaping` (Result<AuthenticatedUser, ParseError>) -> Void
+        completion: `@escaping` `@Sendable` (Result<AuthenticatedUser, ParseError>) -> Void
     ) {

Apply the same fix to the other link method signatures at lines 168 and 195.

🧰 Tools
🪛 GitHub Check: spm-test

[warning] 218-218:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]


[warning] 218-218:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]


[warning] 218-218:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]


[warning] 218-218:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]


[warning] 218-218:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]


[failure] 234-234:
passing non-Sendable parameter 'completion' to function expecting a '@sendable' closure

🤖 Prompt for AI Agents
In `@Sources/ParseSwift/Authentication/3rd` Party/ParseOTP/ParseOTP.swift around
lines 210 - 236, The completion closure parameter in
ParseOTP.link(authData:options:callbackQueue:completion:) must be declared
`@Sendable` to match the `@Sendable` expectation of AuthenticatedUser.link; update
the method signature to mark the completion parameter as `@Sendable` (and ensure
any internal forwarding still passes it unchanged to AuthenticatedUser.link).
Apply the same `@Sendable` annotation to the other ParseOTP.link overloads (the
other link methods in this file) so all link(...) completion handlers match
AuthenticatedUser.link's `@Sendable` requirement.

Comment on lines +216 to +228
guard AuthenticationKeys.token.verifyMandatoryKeys(authData: authData) else {
callbackQueue.async {
completion(
.failure(
.init(
code: .otherCause,
message: "Should have \"authData\" in consisting of keys \"id\", \"idToken\" or \"accessToken\"."
)
)
)
}
return
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

YOUNG HERO! The error message doesn't match the actual OTP keys!

The error message at line 222 states:

"Should have "authData" in consisting of keys "id", "idToken" or "accessToken"."

But according to AuthenticationKeys enum and verifyMandatoryKeys, OTP actually requires "token" or "mobile" keys! This appears to be copy-pasted from ParseGoogle.swift. A true hero always provides accurate guidance!

📝 Proposed fix
             completion(
                 .failure(
                     .init(
                         code: .otherCause,
-                        message: "Should have \"authData\" in consisting of keys \"id\", \"idToken\" or \"accessToken\"."
+                        message: "Should have \"authData\" consisting of key \"token\" or \"mobile\"."
                     )
                 )
             )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
guard AuthenticationKeys.token.verifyMandatoryKeys(authData: authData) else {
callbackQueue.async {
completion(
.failure(
.init(
code: .otherCause,
message: "Should have \"authData\" in consisting of keys \"id\", \"idToken\" or \"accessToken\"."
)
)
)
}
return
}
guard AuthenticationKeys.token.verifyMandatoryKeys(authData: authData) else {
callbackQueue.async {
completion(
.failure(
.init(
code: .otherCause,
message: "Should have \"authData\" consisting of key \"token\" or \"mobile\"."
)
)
)
}
return
}
🧰 Tools
🪛 GitHub Check: spm-test

[warning] 218-218:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]


[warning] 218-218:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]


[warning] 218-218:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]


[warning] 218-218:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]


[warning] 218-218:
capture of 'completion' with non-Sendable type '(Result<AuthenticatedUser, ParseError>) -> Void' in a '@sendable' closure [#SendableClosureCaptures]

🤖 Prompt for AI Agents
In `@Sources/ParseSwift/Authentication/3rd` Party/ParseOTP/ParseOTP.swift around
lines 216 - 228, The error message in the guard that calls
AuthenticationKeys.token.verifyMandatoryKeys(authData:) is incorrect (it
mentions "id", "idToken", "accessToken" from Google); update the completion
error message inside that guard to accurately state the required OTP keys
("token" or "mobile") so the failure returned by ParseOTP.verifyMandatoryKeys
reflects the real expected keys; locate the guard in ParseOTP.swift and replace
the message passed to .init(message:) with a concise message saying the authData
must contain "token" or "mobile".

Comment on lines +23 to +38
func login(
id: String,
idToken: String? = nil,
accessToken: String? = nil,
options: API.Options = []
) async throws -> AuthenticatedUser {
try await withCheckedThrowingContinuation { continuation in
self.login(
id: id,
idToken: idToken,
accessToken: accessToken,
options: options,
completion: continuation.resume
)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

YOUNG HERO! This method calls a non-existent function - compilation will fail!

The async wrapper is calling self.login(id:idToken:accessToken:options:completion:) but this method signature does not exist in ParseOTP.swift! The base ParseOTP class only provides login(authData:options:callbackQueue:completion:) which returns an error stating login is not supported.

This appears to be copied from ParseGoogle+async.swift but OTP has a fundamentally different API design. The static analysis confirming "extra argument 'completion' in call" validates this issue!

💥 Proposed fix - Remove this method or implement the underlying completion-based method

Since OTP login is explicitly not supported (per line 97 in ParseOTP.swift), consider removing these login async wrappers entirely, or only keeping the authData variant:

-    func login(
-        id: String,
-        idToken: String? = nil,
-        accessToken: String? = nil,
-        options: API.Options = []
-    ) async throws -> AuthenticatedUser {
-        try await withCheckedThrowingContinuation { continuation in
-            self.login(
-                id: id,
-                idToken: idToken,
-                accessToken: accessToken,
-                options: options,
-                completion: continuation.resume
-            )
-        }
-    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func login(
id: String,
idToken: String? = nil,
accessToken: String? = nil,
options: API.Options = []
) async throws -> AuthenticatedUser {
try await withCheckedThrowingContinuation { continuation in
self.login(
id: id,
idToken: idToken,
accessToken: accessToken,
options: options,
completion: continuation.resume
)
}
}
🧰 Tools
🪛 GitHub Check: spm-test

[failure] 35-35:
extra argument 'completion' in call


[failure] 35-35:
extra argument 'completion' in call

🪛 GitHub Check: test (platform=watchOS\ Simulator,name=Apple\ Watch\ Series\ 11\ \(46mm\), build)

[failure] 35-35:
extra argument 'completion' in call

🤖 Prompt for AI Agents
In `@Sources/ParseSwift/Authentication/3rd` Party/ParseOTP/ParseOTP+async.swift
around lines 23 - 38, The async wrapper login(id:idToken:accessToken:options:)
in ParseOTP+async.swift calls a non-existent completion-based overload; remove
this async wrapper or rewrite it to use the existing API by converting to the
authData variant: call the class's
login(authData:options:callbackQueue:completion:) (or delete the async login
entirely since ParseOTP currently returns "login not supported"); update or
remove the function named login in ParseOTP+async.swift so it no longer calls
self.login(id:idToken:accessToken:options:completion:).

Comment on lines +95 to +106
func link(
authData: [String: String],
options: API.Options = []
) async throws -> AuthenticatedUser {
try await withCheckedThrowingContinuation { continuation in
self.link(
authData: authData,
options: options,
completion: continuation.resume
)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing callbackQueue parameter here too, young hero!

Same issue as the login variant - the underlying method requires callbackQueue parameter.

💥 Proposed fix
     func link(
         authData: [String: String],
         options: API.Options = []
     ) async throws -> AuthenticatedUser {
         try await withCheckedThrowingContinuation { continuation in
             self.link(
                 authData: authData,
                 options: options,
+                callbackQueue: .main,
                 completion: continuation.resume
             )
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func link(
authData: [String: String],
options: API.Options = []
) async throws -> AuthenticatedUser {
try await withCheckedThrowingContinuation { continuation in
self.link(
authData: authData,
options: options,
completion: continuation.resume
)
}
}
func link(
authData: [String: String],
options: API.Options = []
) async throws -> AuthenticatedUser {
try await withCheckedThrowingContinuation { continuation in
self.link(
authData: authData,
options: options,
callbackQueue: .main,
completion: continuation.resume
)
}
}
🧰 Tools
🪛 GitHub Check: spm-test

[failure] 103-103:
converting a value of type '@sendable (__shared sending Result<AuthenticatedUser, ParseError>) -> ()' to type '(Result<AuthenticatedUser, ParseError>) -> Void' risks causing data races

🪛 GitHub Check: test (platform=watchOS\ Simulator,name=Apple\ Watch\ Series\ 11\ \(46mm\), build)

[failure] 103-103:
converting a value of type '@sendable (__shared sending Result<AuthenticatedUser, ParseError>) -> ()' to type '(Result<AuthenticatedUser, ParseError>) -> Void' risks causing data races

🤖 Prompt for AI Agents
In `@Sources/ParseSwift/Authentication/3rd` Party/ParseOTP/ParseOTP+async.swift
around lines 95 - 106, The async wrapper link(authData:options:) calls the
underlying self.link(...) overload but omits the required callbackQueue
parameter; update the call inside withCheckedThrowingContinuation to pass the
callbackQueue argument expected by the underlying API (e.g., add callbackQueue:
nil or the appropriate DispatchQueue/CallbackQueue used elsewhere) so the
invocation matches the signature used by the other login variant and forwards
completion: continuation.resume.

Comment on lines +29 to +43
Future { promise in
self.login(
id: id,
accessToken: accessToken,

)
self.login(
id: id,
idToken: idToken,
accessToken: accessToken,
options: options,
completion: promise
)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

YOUNG HERO! There's duplicate code AND a missing method here!

I see TWO issues with this loginPublisher, and they're blocking your path to becoming a true hero:

  1. Lines 30-34: This is an incomplete/duplicate self.login() call with no completion handler - it appears to be leftover code that should be removed!

  2. Lines 35-41: The intended call uses self.login(id:idToken:accessToken:options:completion:) which does NOT exist in ParseOTP.swift!

Static analysis confirms: "extra argument 'completion' in call" - PLUS ULTRA compilation failure incoming!

💥 Proposed fix - Remove duplicate and fix method signature
     func loginPublisher(
         id: String,
         idToken: String? = nil,
         accessToken: String? = nil,
         options: API.Options = []
     ) -> Future<AuthenticatedUser, ParseError> {
         Future { promise in
-            self.login(
-                id: id,
-                accessToken: accessToken,
-                
-            )
-            self.login(
-                id: id,
-                idToken: idToken,
-                accessToken: accessToken,
-                options: options,
-                completion: promise
-            )
+            // Note: OTP login is not supported per ParseOTP.swift
+            // Consider removing this publisher or implementing underlying method
+            promise(.failure(ParseError(code: .otherCause, message: "Login is not supported for OTP. Please use link...")))
         }
     }

Or if login should be supported, the underlying completion-based method needs to be added to ParseOTP.swift first!

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Future { promise in
self.login(
id: id,
accessToken: accessToken,
)
self.login(
id: id,
idToken: idToken,
accessToken: accessToken,
options: options,
completion: promise
)
}
}
Future { promise in
// Note: OTP login is not supported per ParseOTP.swift
// Consider removing this publisher or implementing underlying method
promise(.failure(ParseError(code: .otherCause, message: "Login is not supported for OTP. Please use link...")))
}
}
🧰 Tools
🪛 GitHub Check: Codacy Static Code Analysis

[notice] 33-33: Sources/ParseSwift/Authentication/3rd Party/ParseOTP/ParseOTP+combine.swift#L33
Lines should not have trailing whitespace

🪛 GitHub Check: spm-test

[failure] 40-40:
extra argument 'completion' in call

🤖 Prompt for AI Agents
In `@Sources/ParseSwift/Authentication/3rd` Party/ParseOTP/ParseOTP+combine.swift
around lines 29 - 43, Remove the stray duplicate self.login(...) call in
loginPublisher (the incomplete call at lines 30-34) and replace the remaining
call so it matches an existing completion-based API, or implement the missing
completion-based method in ParseOTP.swift; specifically ensure either
ParseOTP+combine.swift invokes an existing method like
login(id:idToken:accessToken:options:completion:) that actually exists, or add
that method (ParseOTP.login(id:idToken:accessToken:options:completion:)) to
ParseOTP.swift with the expected signature and completion handling so the
Future's promise can be passed as the completion.

Comment on lines +50 to +61
func loginPublisher(
authData: [String: String],
options: API.Options = []
) -> Future<AuthenticatedUser, ParseError> {
Future { promise in
self.login(
authData: authData,
options: options,
completion: promise
)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

This loginPublisher is also missing the callbackQueue parameter!

The underlying method signature is login(authData:options:callbackQueue:completion:) but you're calling it without callbackQueue.

💥 Proposed fix
     func loginPublisher(
         authData: [String: String],
         options: API.Options = []
     ) -> Future<AuthenticatedUser, ParseError> {
         Future { promise in
             self.login(
                 authData: authData,
                 options: options,
+                callbackQueue: .main,
                 completion: promise
             )
         }
     }

Note: You may also need to wrap promise with nonisolated(unsafe) like in ParseGitHub+combine.swift to handle Sendable requirements.

🤖 Prompt for AI Agents
In `@Sources/ParseSwift/Authentication/3rd` Party/ParseOTP/ParseOTP+combine.swift
around lines 50 - 61, The loginPublisher is calling
login(authData:options:completion:) but the real API requires
login(authData:options:callbackQueue:completion:); update the call inside
loginPublisher to pass the callbackQueue through (accept or default a
DispatchQueue parameter if needed) so it calls
login(authData:options:callbackQueue:completion:). Also, to satisfy Sendable
requirements, wrap the promise with nonisolated(unsafe) as done in
ParseGitHub+combine.swift when passing the completion closure to login. Ensure
you reference the loginPublisher function and the
login(authData:options:callbackQueue:completion:) signature when making the
change.

Comment on lines +74 to +89
func linkPublisher(
id: String,
idToken: String? = nil,
accessToken: String? = nil,
options: API.Options = []
) -> Future<AuthenticatedUser, ParseError> {
Future { promise in
self.link(
id: id,
idToken: idToken,
accessToken: accessToken,
options: options,
completion: promise
)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

This linkPublisher calls a non-existent method too!

self.link(id:idToken:accessToken:options:completion:) does not exist in ParseOTP.swift. The available link methods use different signatures (secret:token:, mobile:, or authData:).

💥 Proposed fix - Match actual ParseOTP link signatures
-    func linkPublisher(
-        id: String,
-        idToken: String? = nil,
-        accessToken: String? = nil,
-        options: API.Options = []
-    ) -> Future<AuthenticatedUser, ParseError> {
-        Future { promise in
-            self.link(
-                id: id,
-                idToken: idToken,
-                accessToken: accessToken,
-                options: options,
-                completion: promise
-            )
-        }
-    }
+    func linkPublisher(
+        secret: String,
+        token: String,
+        options: API.Options = []
+    ) -> Future<AuthenticatedUser, ParseError> {
+        Future { promise in
+            nonisolated(unsafe) let promise = promise
+            self.link(
+                secret: secret,
+                token: token,
+                options: options,
+                callbackQueue: .main,
+                completion: promise
+            )
+        }
+    }
+
+    func linkPublisher(
+        mobile: String,
+        options: API.Options = []
+    ) -> Future<AuthenticatedUser, ParseError> {
+        Future { promise in
+            nonisolated(unsafe) let promise = promise
+            self.link(
+                mobile: mobile,
+                options: options,
+                callbackQueue: .main,
+                completion: promise
+            )
+        }
+    }
🧰 Tools
🪛 GitHub Check: spm-test

[failure] 86-86:
extra argument 'completion' in call

🤖 Prompt for AI Agents
In `@Sources/ParseSwift/Authentication/3rd` Party/ParseOTP/ParseOTP+combine.swift
around lines 74 - 89, The linkPublisher currently calls a non-existent
self.link(id:idToken:accessToken:options:completion:); update linkPublisher (in
ParseOTP+combine.swift) to call the actual ParseOTP link overloads instead — use
self.link(authData:options:completion:) when you have token(s) (build an
authData dictionary from id, idToken, accessToken), use
self.link(mobile:options:completion:) when the id represents a mobile number, or
use self.link(secret:token:options:completion:) when the intended API is the
secret/token pair; implement a small conditional branch inside linkPublisher to
pick the correct existing method and pass the provided options and the promise
as completion.

Comment on lines +97 to +108
func linkPublisher(
authData: [String: String],
options: API.Options = []
) -> Future<AuthenticatedUser, ParseError> {
Future { promise in
self.link(
authData: authData,
options: options,
completion: promise
)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing callbackQueue parameter again, hero!

Same pattern - needs callbackQueue: .main parameter.

💥 Proposed fix
     func linkPublisher(
         authData: [String: String],
         options: API.Options = []
     ) -> Future<AuthenticatedUser, ParseError> {
         Future { promise in
+            nonisolated(unsafe) let promise = promise
             self.link(
                 authData: authData,
                 options: options,
+                callbackQueue: .main,
                 completion: promise
             )
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func linkPublisher(
authData: [String: String],
options: API.Options = []
) -> Future<AuthenticatedUser, ParseError> {
Future { promise in
self.link(
authData: authData,
options: options,
completion: promise
)
}
}
func linkPublisher(
authData: [String: String],
options: API.Options = []
) -> Future<AuthenticatedUser, ParseError> {
Future { promise in
nonisolated(unsafe) let promise = promise
self.link(
authData: authData,
options: options,
callbackQueue: .main,
completion: promise
)
}
}
🤖 Prompt for AI Agents
In `@Sources/ParseSwift/Authentication/3rd` Party/ParseOTP/ParseOTP+combine.swift
around lines 97 - 108, linkPublisher is missing the callbackQueue parameter when
calling link; update the call inside func linkPublisher(authData:options:) to
pass callbackQueue: .main to the self.link(...) invocation so the completion is
invoked on the main queue (refer to function linkPublisher and the
link(authData:options:completion:) overload).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants