|
@@ -10806,6 +10806,11 @@ final class ZoomOAuthTokenStore {
|
|
|
|
|
|
|
|
final class ZoomOAuthService: NSObject {
|
|
final class ZoomOAuthService: NSObject {
|
|
|
static let shared = ZoomOAuthService()
|
|
static let shared = ZoomOAuthService()
|
|
|
|
|
+
|
|
|
|
|
+ /// Production Marketplace reject loopback (4,700). HTTPS callback is intercepted by the in-app WKWebView.
|
|
|
|
|
+ /// Must match Marketplace → Production → OAuth Redirect URL / Allow List exactly.
|
|
|
|
|
+ static let zoomOAuthRedirectURI = "https://www.mqldevelopment.com/oauth/zoom-callback"
|
|
|
|
|
+
|
|
|
private var inAppOAuthWindowController: InAppOAuthWindowController?
|
|
private var inAppOAuthWindowController: InAppOAuthWindowController?
|
|
|
|
|
|
|
|
/// When set, Zoom OAuth runs in this session instead of a separate window (unused; sign-in uses `InAppOAuthWindowController`).
|
|
/// When set, Zoom OAuth runs in this session instead of a separate window (unused; sign-in uses `InAppOAuthWindowController`).
|
|
@@ -10828,7 +10833,7 @@ final class ZoomOAuthService: NSObject {
|
|
|
// Optional: put OAuth app credentials here for local-only testing (do not ship secrets in release builds).
|
|
// Optional: put OAuth app credentials here for local-only testing (do not ship secrets in release builds).
|
|
|
/// Confidential Client ID (token refresh). Production Marketplace value — used by Archive/App Store builds.
|
|
/// Confidential Client ID (token refresh). Production Marketplace value — used by Archive/App Store builds.
|
|
|
private let bundledClientId = "X4fHO95lTdWGvmvQYGeQEA"
|
|
private let bundledClientId = "X4fHO95lTdWGvmvQYGeQEA"
|
|
|
- /// Public Client ID for loopback authorize + PKCE (Production Marketplace value).
|
|
|
|
|
|
|
+ /// Public Client ID for authorize + PKCE (Production Marketplace value).
|
|
|
private let bundledPublicClientId = "bCMJ3KHVR8K1SkZC5ubmXg"
|
|
private let bundledPublicClientId = "bCMJ3KHVR8K1SkZC5ubmXg"
|
|
|
/// Prefer `ZOOM_OAUTH_CLIENT_SECRET` env or UserDefaults when distributing; rotate if this value is ever leaked.
|
|
/// Prefer `ZOOM_OAUTH_CLIENT_SECRET` env or UserDefaults when distributing; rotate if this value is ever leaked.
|
|
|
private let bundledClientSecret = "yG75pEEVsQ94GgqN8j2EBdv3WaD57m5c"
|
|
private let bundledClientSecret = "yG75pEEVsQ94GgqN8j2EBdv3WaD57m5c"
|
|
@@ -10953,10 +10958,11 @@ final class ZoomOAuthService: NSObject {
|
|
|
|
|
|
|
|
private func interactiveSignIn(presentingWindow: NSWindow?) async throws -> ZoomOAuthTokens {
|
|
private func interactiveSignIn(presentingWindow: NSWindow?) async throws -> ZoomOAuthTokens {
|
|
|
guard let presentingWindow else { throw ZoomOAuthError.unableToOpenBrowser }
|
|
guard let presentingWindow else { throw ZoomOAuthError.unableToOpenBrowser }
|
|
|
- // Loopback redirect requires Public Client OAuth + PKCE (not confidential Client ID alone).
|
|
|
|
|
|
|
+ // Production loopback is rejected by Zoom (4,700). Use HTTPS callback + Public Client PKCE.
|
|
|
|
|
+ // The in-app WKWebView intercepts this URL prefix when Zoom redirects with ?code=...
|
|
|
guard let publicClientId = configuredPublicClientId() else { throw ZoomOAuthError.missingPublicClientId }
|
|
guard let publicClientId = configuredPublicClientId() else { throw ZoomOAuthError.missingPublicClientId }
|
|
|
|
|
|
|
|
- let redirectURI = "http://127.0.0.1:8742/oauth2redirect"
|
|
|
|
|
|
|
+ let redirectURI = Self.zoomOAuthRedirectURI
|
|
|
let state = UUID().uuidString
|
|
let state = UUID().uuidString
|
|
|
let codeVerifier = Self.randomURLSafeString(length: 64)
|
|
let codeVerifier = Self.randomURLSafeString(length: 64)
|
|
|
let codeChallenge = Self.pkceChallenge(for: codeVerifier)
|
|
let codeChallenge = Self.pkceChallenge(for: codeVerifier)
|
|
@@ -11569,7 +11575,7 @@ extension ZoomOAuthError: LocalizedError {
|
|
|
case .missingClientSecret:
|
|
case .missingClientSecret:
|
|
|
return "Zoom OAuth Client Secret is not set (ZOOM_OAUTH_CLIENT_SECRET env, UserDefaults, or the setup prompt)."
|
|
return "Zoom OAuth Client Secret is not set (ZOOM_OAUTH_CLIENT_SECRET env, UserDefaults, or the setup prompt)."
|
|
|
case .invalidCallbackURL:
|
|
case .invalidCallbackURL:
|
|
|
- return "The OAuth redirect URL was invalid. In your Zoom app OAuth allow list, add exactly http://127.0.0.1:8742/oauth2redirect. With Public Client OAuth ON, authorize must use the Public Client ID and PKCE."
|
|
|
|
|
|
|
+ return "The OAuth redirect URL was invalid. In Marketplace → Production OAuth Allow List, add exactly \(ZoomOAuthService.zoomOAuthRedirectURI). Sign-in must use the Public Client ID with PKCE (not the confidential Client ID from Marketplace’s generated OAuth URL)."
|
|
|
case .missingAuthorizationCode:
|
|
case .missingAuthorizationCode:
|
|
|
return "Zoom did not return an authorization code."
|
|
return "Zoom did not return an authorization code."
|
|
|
case .tokenExchangeFailed(let details):
|
|
case .tokenExchangeFailed(let details):
|