diff --git a/Package.swift b/Package.swift index 6105e5a6..bff948f7 100644 --- a/Package.swift +++ b/Package.swift @@ -55,6 +55,7 @@ let package = Package( dependencies: [ .product(name: "CustomDump", package: "swift-custom-dump"), .product(name: "SnapshotTesting", package: "swift-snapshot-testing"), + .product(name: "InlineSnapshotTesting", package: "swift-snapshot-testing"), .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), "Helpers", "Auth", diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index d7289345..d775eb55 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -449,8 +449,10 @@ public final class AuthClient: Sendable { /// Log in an existing user by exchanging an Auth Code issued during the PKCE flow. public func exchangeCodeForSession(authCode: String) async throws -> Session { - guard let codeVerifier = codeVerifierStorage.get() else { - throw AuthError.pkce(.codeVerifierNotFound) + let codeVerifier = codeVerifierStorage.get() + + if codeVerifier == nil { + logger?.error("code verifier not found, a code verifier should exist when calling this method.") } let session: Session = try await api.execute( @@ -519,14 +521,10 @@ public final class AuthClient: Sendable { queryParams: [(name: String, value: String?)] = [], launchFlow: @MainActor @Sendable (_ url: URL) async throws -> URL ) async throws -> Session { - guard let redirectTo = (redirectTo ?? configuration.redirectToURL) else { - throw AuthError.invalidRedirectScheme - } - let url = try getOAuthSignInURL( provider: provider, scopes: scopes, - redirectTo: redirectTo, + redirectTo: redirectTo ?? configuration.redirectToURL, queryParams: queryParams ) @@ -566,8 +564,9 @@ public final class AuthClient: Sendable { ) { @MainActor url in try await withCheckedThrowingContinuation { continuation in guard let callbackScheme = (configuration.redirectToURL ?? redirectTo)?.scheme else { - continuation.resume(throwing: AuthError.invalidRedirectScheme) - return + preconditionFailure( + "Please, provide a valid redirect URL, either thorugh `redirectTo` param, or globally thorugh `AuthClient.Configuration.redirectToURL`." + ) } #if !os(tvOS) && !os(watchOS) @@ -583,7 +582,7 @@ public final class AuthClient: Sendable { } else if let url { continuation.resume(returning: url) } else { - continuation.resume(throwing: AuthError.missingURL) + fatalError("Expected url or error, but got none.") } #if !os(tvOS) && !os(watchOS) @@ -674,24 +673,28 @@ public final class AuthClient: Sendable { let params = extractParams(from: url) if configuration.flowType == .implicit, !isImplicitGrantFlow(params: params) { - throw AuthError.invalidImplicitGrantFlowURL + throw AuthError.implicitGrantRedirect(message: "Not a valid implicit grant flow url: \(url)") } if configuration.flowType == .pkce, !isPKCEFlow(params: params) { - throw AuthError.pkce(.invalidPKCEFlowURL) + throw AuthError.pkceGrantCodeExchange(message: "Not a valid PKCE flow url: \(url)") } if isPKCEFlow(params: params) { guard let code = params["code"] else { - throw AuthError.pkce(.codeVerifierNotFound) + throw AuthError.pkceGrantCodeExchange(message: "No code detected.") } let session = try await exchangeCodeForSession(authCode: code) return session } - if let errorDescription = params["error_description"] { - throw AuthError.api(.init(errorDescription: errorDescription)) + if params["error"] != nil || params["error_description"] != nil || params["error_code"] != nil { + throw AuthError.pkceGrantCodeExchange( + message: params["error_description"] ?? "Error in URL with unspecified error_description.", + error: params["error"] ?? "unspecified_error", + code: params["error_code"] ?? "unspecified_code" + ) } guard @@ -700,7 +703,7 @@ public final class AuthClient: Sendable { let refreshToken = params["refresh_token"], let tokenType = params["token_type"] else { - throw URLError(.badURL) + throw AuthError.implicitGrantRedirect(message: "No session defined in URL") } let expiresAt = params["expires_at"].flatMap(TimeInterval.init) @@ -753,11 +756,9 @@ public final class AuthClient: Sendable { var session: Session let jwt = try decode(jwt: accessToken) - if let exp = jwt["exp"] as? TimeInterval { + if let exp = jwt?["exp"] as? TimeInterval { expiresAt = Date(timeIntervalSince1970: exp) hasExpired = expiresAt <= now - } else { - throw AuthError.missingExpClaim } if hasExpired { @@ -803,16 +804,9 @@ public final class AuthClient: Sendable { headers: [.init(name: "Authorization", value: "Bearer \(accessToken)")] ) ) - } catch { + } catch let AuthError.api(_, _, _, response) where [404, 403, 401].contains(response.statusCode) { // ignore 404s since user might not exist anymore // ignore 401s, and 403s since an invalid or expired JWT should sign out the current session. - let ignoredCodes = Set([404, 403, 401]) - - if case let AuthError.api(apiError) = error, let code = apiError.code, - !ignoredCodes.contains(code) - { - throw error - } } } @@ -1169,7 +1163,7 @@ public final class AuthClient: Sendable { @discardableResult public func refreshSession(refreshToken: String? = nil) async throws -> Session { guard let refreshToken = refreshToken ?? currentSession?.refreshToken else { - throw AuthError.sessionNotFound + throw AuthError.sessionMissing } return try await sessionManager.refreshSession(refreshToken) diff --git a/Sources/Auth/AuthError.swift b/Sources/Auth/AuthError.swift index f6424c6f..8430f8fd 100644 --- a/Sources/Auth/AuthError.swift +++ b/Sources/Auth/AuthError.swift @@ -1,20 +1,202 @@ import Foundation -public enum AuthError: LocalizedError, Sendable, Equatable { +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +/// An error code thrown by the server. +public struct ErrorCode: Decodable, RawRepresentable, Sendable, Hashable { + public var rawValue: String + + public init(rawValue: String) { + self.rawValue = rawValue + } + + public init(_ rawValue: String) { + self.init(rawValue: rawValue) + } +} + +// Known error codes. Note that the server may also return other error codes +// not included in this list (if the client library is older than the version +// on the server). +extension ErrorCode { + /// ErrorCodeUnknown should not be used directly, it only indicates a failure in the error handling system in such a way that an error code was not assigned properly. + public static let unknown = ErrorCode("unknown") + + /// ErrorCodeUnexpectedFailure signals an unexpected failure such as a 500 Internal Server Error. + public static let unexpectedFailure = ErrorCode("unexpected_failure") + + public static let validationFailed = ErrorCode("validation_failed") + public static let badJSON = ErrorCode("bad_json") + public static let emailExists = ErrorCode("email_exists") + public static let phoneExists = ErrorCode("phone_exists") + public static let badJWT = ErrorCode("bad_jwt") + public static let notAdmin = ErrorCode("not_admin") + public static let noAuthorization = ErrorCode("no_authorization") + public static let userNotFound = ErrorCode("user_not_found") + public static let sessionNotFound = ErrorCode("session_not_found") + public static let flowStateNotFound = ErrorCode("flow_state_not_found") + public static let flowStateExpired = ErrorCode("flow_state_expired") + public static let signupDisabled = ErrorCode("signup_disabled") + public static let userBanned = ErrorCode("user_banned") + public static let providerEmailNeedsVerification = ErrorCode("provider_email_needs_verification") + public static let inviteNotFound = ErrorCode("invite_not_found") + public static let badOAuthState = ErrorCode("bad_oauth_state") + public static let badOAuthCallback = ErrorCode("bad_oauth_callback") + public static let oauthProviderNotSupported = ErrorCode("oauth_provider_not_supported") + public static let unexpectedAudience = ErrorCode("unexpected_audience") + public static let singleIdentityNotDeletable = ErrorCode("single_identity_not_deletable") + public static let emailConflictIdentityNotDeletable = ErrorCode("email_conflict_identity_not_deletable") + public static let identityAlreadyExists = ErrorCode("identity_already_exists") + public static let emailProviderDisabled = ErrorCode("email_provider_disabled") + public static let phoneProviderDisabled = ErrorCode("phone_provider_disabled") + public static let tooManyEnrolledMFAFactors = ErrorCode("too_many_enrolled_mfa_factors") + public static let mfaFactorNameConflict = ErrorCode("mfa_factor_name_conflict") + public static let mfaFactorNotFound = ErrorCode("mfa_factor_not_found") + public static let mfaIPAddressMismatch = ErrorCode("mfa_ip_address_mismatch") + public static let mfaChallengeExpired = ErrorCode("mfa_challenge_expired") + public static let mfaVerificationFailed = ErrorCode("mfa_verification_failed") + public static let mfaVerificationRejected = ErrorCode("mfa_verification_rejected") + public static let insufficientAAL = ErrorCode("insufficient_aal") + public static let captchaFailed = ErrorCode("captcha_failed") + public static let samlProviderDisabled = ErrorCode("saml_provider_disabled") + public static let manualLinkingDisabled = ErrorCode("manual_linking_disabled") + public static let smsSendFailed = ErrorCode("sms_send_failed") + public static let emailNotConfirmed = ErrorCode("email_not_confirmed") + public static let phoneNotConfirmed = ErrorCode("phone_not_confirmed") + public static let samlRelayStateNotFound = ErrorCode("saml_relay_state_not_found") + public static let samlRelayStateExpired = ErrorCode("saml_relay_state_expired") + public static let samlIdPNotFound = ErrorCode("saml_idp_not_found") + public static let samlAssertionNoUserID = ErrorCode("saml_assertion_no_user_id") + public static let samlAssertionNoEmail = ErrorCode("saml_assertion_no_email") + public static let userAlreadyExists = ErrorCode("user_already_exists") + public static let ssoProviderNotFound = ErrorCode("sso_provider_not_found") + public static let samlMetadataFetchFailed = ErrorCode("saml_metadata_fetch_failed") + public static let samlIdPAlreadyExists = ErrorCode("saml_idp_already_exists") + public static let ssoDomainAlreadyExists = ErrorCode("sso_domain_already_exists") + public static let samlEntityIDMismatch = ErrorCode("saml_entity_id_mismatch") + public static let conflict = ErrorCode("conflict") + public static let providerDisabled = ErrorCode("provider_disabled") + public static let userSSOManaged = ErrorCode("user_sso_managed") + public static let reauthenticationNeeded = ErrorCode("reauthentication_needed") + public static let samePassword = ErrorCode("same_password") + public static let reauthenticationNotValid = ErrorCode("reauthentication_not_valid") + public static let otpExpired = ErrorCode("otp_expired") + public static let otpDisabled = ErrorCode("otp_disabled") + public static let identityNotFound = ErrorCode("identity_not_found") + public static let weakPassword = ErrorCode("weak_password") + public static let overRequestRateLimit = ErrorCode("over_request_rate_limit") + public static let overEmailSendRateLimit = ErrorCode("over_email_send_rate_limit") + public static let overSMSSendRateLimit = ErrorCode("over_sms_send_rate_limit") + public static let badCodeVerifier = ErrorCode("bad_code_verifier") + public static let anonymousProviderDisabled = ErrorCode("anonymous_provider_disabled") + public static let hookTimeout = ErrorCode("hook_timeout") + public static let hookTimeoutAfterRetry = ErrorCode("hook_timeout_after_retry") + public static let hookPayloadOverSizeLimit = ErrorCode("hook_payload_over_size_limit") + public static let requestTimeout = ErrorCode("request_timeout") + public static let mfaPhoneEnrollDisabled = ErrorCode("mfa_phone_enroll_not_enabled") + public static let mfaPhoneVerifyDisabled = ErrorCode("mfa_phone_verify_not_enabled") + public static let mfaTOTPEnrollDisabled = ErrorCode("mfa_totp_enroll_not_enabled") + public static let mfaTOTPVerifyDisabled = ErrorCode("mfa_totp_verify_not_enabled") + public static let mfaVerifiedFactorExists = ErrorCode("mfa_verified_factor_exists") + // #nosec G101 -- Not a secret value. + public static let invalidCredentials = ErrorCode("invalid_credentials") +} + +public enum AuthError: LocalizedError, Equatable { + @available( + *, + deprecated, + message: "Error used to be thrown when no exp claim was found in JWT during setSession(accessToken:refreshToken:) method." + ) case missingExpClaim + + @available( + *, + deprecated, + message: "Error used to be thrown when provided JWT wasn't valid during setSession(accessToken:refreshToken:) method." + ) case malformedJWT - case sessionNotFound - case api(APIError) + + @available(*, deprecated, renamed: "sessionMissing") + public static var sessionNotFound: AuthError { .sessionMissing } /// Error thrown during PKCE flow. - case pkce(PKCEFailureReason) + @available( + *, + deprecated, + renamed: "pkceGrantCodeExchange", + message: "Error was grouped in `pkceGrantCodeExchange`, please use it instead of `pkce`." + ) + public static func pkce(_ reason: PKCEFailureReason) -> AuthError { + switch reason { + case .codeVerifierNotFound: + .pkceGrantCodeExchange(message: "A code verifier wasn't found in PKCE flow.") + case .invalidPKCEFlowURL: + .pkceGrantCodeExchange(message: "Not a valid PKCE flow url.") + } + } + + @available(*, deprecated, message: "Use `pkceGrantCodeExchange` instead.") + public enum PKCEFailureReason: Sendable { + /// Code verifier not found in the URL. + case codeVerifierNotFound + + /// Not a valid PKCE flow URL. + case invalidPKCEFlowURL + } + + @available(*, deprecated, renamed: "implicitGrantRedirect") + public static var invalidImplicitGrantFlowURL: AuthError { + .implicitGrantRedirect(message: "Not a valid implicit grant flow url.") + } - case invalidImplicitGrantFlowURL + @available( + *, + deprecated, + message: "This error is never thrown, if you depend on it, you can remove the logic as it never happens." + ) case missingURL + + @available( + *, + deprecated, + message: "Error used to be thrown on methods which required a valid redirect scheme, such as signInWithOAuth. This is now considered a programming error an a assertion is triggered in case redirect scheme isn't provided." + ) case invalidRedirectScheme + @available( + *, + deprecated, + renamed: "api(message:errorCode:underlyingData:underlyingResponse:)" + ) + public static func api(_ error: APIError) -> AuthError { + let message = error.msg ?? error.error ?? error.errorDescription ?? "Unexpected API error." + if let weakPassword = error.weakPassword { + return .weakPassword(message: message, reasons: weakPassword.reasons) + } + + return .api( + message: message, + errorCode: .unknown, + underlyingData: (try? AuthClient.Configuration.jsonEncoder.encode(error)) ?? Data(), + underlyingResponse: HTTPURLResponse( + url: URL(string: "http://localhost")!, + statusCode: error.code ?? 500, + httpVersion: nil, + headerFields: nil + )! + ) + } + /// An error returned by the API. - public struct APIError: Error, Decodable, Sendable, Equatable { + @available( + *, + deprecated, + renamed: "api(message:errorCode:underlyingData:underlyingResponse:)" + ) + public struct APIError: Error, Codable, Sendable, Equatable { /// A basic message describing the problem with the request. Usually missing if /// ``AuthError/APIError/error`` is present. public var msg: String? @@ -42,33 +224,60 @@ public enum AuthError: LocalizedError, Sendable, Equatable { public var weakPassword: WeakPassword? } - public enum PKCEFailureReason: Sendable { - /// Code verifier not found in the URL. - case codeVerifierNotFound + /// Error thrown when a session is required to proceed, but none was found, either thrown by the client, or returned by the server. + case sessionMissing - /// Not a valid PKCE flow URL. - case invalidPKCEFlowURL - } + /// Error thrown when password is deemed weak, check associated reasons to know why. + case weakPassword(message: String, reasons: [String]) - public var errorDescription: String? { + /// Error thrown by API when an error occurs, check `errorCode` to know more, + /// or use `underlyingData` or `underlyingResponse` for access to the response which originated this error. + case api( + message: String, + errorCode: ErrorCode, + underlyingData: Data, + underlyingResponse: HTTPURLResponse + ) + + /// Error thrown when an error happens during PKCE grant flow. + case pkceGrantCodeExchange(message: String, error: String? = nil, code: String? = nil) + + /// Error thrown when an error happens during implicit grant flow. + case implicitGrantRedirect(message: String) + + public var message: String { switch self { - case let .api(error): error.errorDescription ?? error.msg ?? error.error + case .sessionMissing: "Auth session missing." + case let .weakPassword(message, _), + let .api(message, _, _, _), + let .pkceGrantCodeExchange(message, _, _), + let .implicitGrantRedirect(message): + message + // Deprecated cases case .missingExpClaim: "Missing expiration claim in the access token." case .malformedJWT: "A malformed JWT received." - case .sessionNotFound: "Unable to get a valid session." - case let .pkce(reason): reason.errorDescription - case .invalidImplicitGrantFlowURL: "Not a valid implicit grant flow url." - case .missingURL: "Missing URL." case .invalidRedirectScheme: "Invalid redirect scheme." + case .missingURL: "Missing URL." } } -} -extension AuthError.PKCEFailureReason { - var errorDescription: String { + public var errorCode: ErrorCode { switch self { - case .codeVerifierNotFound: "A code verifier wasn't found in PKCE flow." - case .invalidPKCEFlowURL: "Not a valid PKCE flow url." + case .sessionMissing: .sessionNotFound + case .weakPassword: .weakPassword + case let .api(_, errorCode, _, _): errorCode + case .pkceGrantCodeExchange, .implicitGrantRedirect: .unknown + // Deprecated cases + case .missingExpClaim, .malformedJWT, .invalidRedirectScheme, .missingURL: .unknown } } + + public var errorDescription: String? { + message + } + + public static func ~= (lhs: AuthError, rhs: any Error) -> Bool { + guard let rhs = rhs as? AuthError else { return false } + return lhs == rhs + } } diff --git a/Sources/Auth/AuthMFA.swift b/Sources/Auth/AuthMFA.swift index 701b3159..1c329e4d 100644 --- a/Sources/Auth/AuthMFA.swift +++ b/Sources/Auth/AuthMFA.swift @@ -129,7 +129,7 @@ public struct AuthMFA: Sendable { var currentLevel: AuthenticatorAssuranceLevels? - if let aal = payload["aal"] as? AuthenticatorAssuranceLevels { + if let aal = payload?["aal"] as? AuthenticatorAssuranceLevels { currentLevel = aal } @@ -142,7 +142,7 @@ public struct AuthMFA: Sendable { var currentAuthenticationMethods: [AMREntry] = [] - if let amr = payload["amr"] as? [Any] { + if let amr = payload?["amr"] as? [Any] { currentAuthenticationMethods = amr.compactMap(AMREntry.init(value:)) } @@ -151,14 +151,12 @@ public struct AuthMFA: Sendable { nextLevel: nextLevel, currentAuthenticationMethods: currentAuthenticationMethods ) - } catch AuthError.sessionNotFound { + } catch AuthError.sessionMissing { return AuthMFAGetAuthenticatorAssuranceLevelResponse( currentLevel: nil, nextLevel: nil, currentAuthenticationMethods: [] ) - } catch { - throw error } } } diff --git a/Sources/Auth/Internal/APIClient.swift b/Sources/Auth/Internal/APIClient.swift index 07a7190f..bab98fe0 100644 --- a/Sources/Auth/Internal/APIClient.swift +++ b/Sources/Auth/Internal/APIClient.swift @@ -35,27 +35,14 @@ struct APIClient: Sendable { var request = request request.headers = HTTPHeaders(configuration.headers).merged(with: request.headers) + if request.headers[API_VERSION_HEADER_NAME] == nil { + request.headers[API_VERSION_HEADER_NAME] = API_VERSIONS[._20240101]!.name.rawValue + } + let response = try await http.send(request) - guard (200 ..< 300).contains(response.statusCode) else { - if let apiError = try? configuration.decoder.decode( - AuthError.APIError.self, - from: response.data - ) { - throw AuthError.api(apiError) - } - - /// There are some GoTrue endpoints that can return a `PostgrestError`, for example the - /// ``AuthAdmin/deleteUser(id:shouldSoftDelete:)`` that could return an error in case the - /// user is referenced by other schemas. - if let postgrestError = try? configuration.decoder.decode( - PostgrestError.self, - from: response.data - ) { - throw postgrestError - } - - throw HTTPError(data: response.data, response: response.underlyingResponse) + guard 200 ..< 300 ~= response.statusCode else { + throw handleError(response: response) } return response @@ -74,4 +61,74 @@ struct APIClient: Sendable { return try await execute(request) } + + func handleError(response: HTTPResponse) -> AuthError { + guard let error = try? response.decoded( + as: _RawAPIErrorResponse.self, + decoder: configuration.decoder + ) else { + return .api( + message: "Unexpected error", + errorCode: .unexpectedFailure, + underlyingData: response.data, + underlyingResponse: response.underlyingResponse + ) + } + + let responseAPIVersion = parseResponseAPIVersion(response) + + let errorCode: ErrorCode? = if let responseAPIVersion, responseAPIVersion >= API_VERSIONS[._20240101]!.timestamp, let code = error.code { + ErrorCode(code) + } else { + error.errorCode + } + + if errorCode == nil, let weakPassword = error.weakPassword { + return .weakPassword( + message: error._getErrorMessage(), + reasons: weakPassword.reasons ?? [] + ) + } else if errorCode == .weakPassword { + return .weakPassword( + message: error._getErrorMessage(), + reasons: error.weakPassword?.reasons ?? [] + ) + } else if errorCode == .sessionNotFound { + return .sessionMissing + } else { + return .api( + message: error._getErrorMessage(), + errorCode: errorCode ?? .unknown, + underlyingData: response.data, + underlyingResponse: response.underlyingResponse + ) + } + } + + private func parseResponseAPIVersion(_ response: HTTPResponse) -> Date? { + guard let apiVersion = response.headers[API_VERSION_HEADER_NAME] else { return nil } + + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + return formatter.date(from: "\(apiVersion)T00:00:00.0Z") + } +} + +// Struct for mapping all fields possibly returned by API. +struct _RawAPIErrorResponse: Decodable { + let msg: String? + let message: String? + let errorDescription: String? + let error: String? + let code: String? + let errorCode: ErrorCode? + let weakPassword: _WeakPassword? + + struct _WeakPassword: Decodable { + let reasons: [String]? + } + + func _getErrorMessage() -> String { + msg ?? message ?? errorDescription ?? error ?? "Unknown" + } } diff --git a/Sources/Auth/Internal/Contants.swift b/Sources/Auth/Internal/Contants.swift index ae456410..de388197 100644 --- a/Sources/Auth/Internal/Contants.swift +++ b/Sources/Auth/Internal/Contants.swift @@ -9,3 +9,30 @@ import Foundation let EXPIRY_MARGIN: TimeInterval = 30 let STORAGE_KEY = "supabase.auth.token" + +let API_VERSION_HEADER_NAME = "X-Supabase-Api-Version" +let API_VERSIONS: [APIVersion.Name: APIVersion] = [ + ._20240101: ._20240101, +] + +struct APIVersion { + let timestamp: Date + let name: Name + + enum Name: String { + case _20240101 = "2024-01-01" + } + + static func date(for name: Name) -> Date { + let formattar = ISO8601DateFormatter() + formattar.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + return formattar.date(from: "\(name.rawValue)T00:00:00.0Z")! + } +} + +extension APIVersion { + static let _20240101 = APIVersion( + timestamp: APIVersion.date(for: ._20240101), + name: ._20240101 + ) +} diff --git a/Sources/Auth/Internal/Helpers.swift b/Sources/Auth/Internal/Helpers.swift index c227d3c5..07d2a02a 100644 --- a/Sources/Auth/Internal/Helpers.swift +++ b/Sources/Auth/Internal/Helpers.swift @@ -40,19 +40,19 @@ private func extractParams(from fragment: String) -> [URLQueryItem] { } } -func decode(jwt: String) throws -> [String: Any] { +func decode(jwt: String) throws -> [String: Any]? { let parts = jwt.split(separator: ".") guard parts.count == 3 else { - throw AuthError.malformedJWT + return nil } let payload = String(parts[1]) guard let data = base64URLDecode(payload) else { - throw AuthError.malformedJWT + return nil } let json = try JSONSerialization.jsonObject(with: data, options: []) guard let decodedPayload = json as? [String: Any] else { - throw AuthError.malformedJWT + return nil } return decodedPayload } diff --git a/Sources/Auth/Internal/SessionManager.swift b/Sources/Auth/Internal/SessionManager.swift index 361e1980..2f07f47c 100644 --- a/Sources/Auth/Internal/SessionManager.swift +++ b/Sources/Auth/Internal/SessionManager.swift @@ -40,7 +40,7 @@ private actor LiveSessionManager { func session() async throws -> Session { try await trace(using: logger) { guard let currentSession = try sessionStorage.get() else { - throw AuthError.sessionNotFound + throw AuthError.sessionMissing } if !currentSession.isExpired { diff --git a/Tests/AuthTests/AuthClientTests.swift b/Tests/AuthTests/AuthClientTests.swift index 6fd30be0..b2e8a446 100644 --- a/Tests/AuthTests/AuthClientTests.swift +++ b/Tests/AuthTests/AuthClientTests.swift @@ -9,6 +9,7 @@ import ConcurrencyExtras import CustomDump @testable import Helpers +import InlineSnapshotTesting import TestHelpers import XCTest @@ -93,9 +94,13 @@ final class AuthClientTests: XCTestCase { do { _ = try await sut.session - } catch AuthError.sessionNotFound { } catch { - XCTFail("Unexpected error.") + assertInlineSnapshot(of: error, as: .dump) { + """ + - AuthError.sessionMissing + + """ + } } let events = await eventsTask.value.map(\.event) @@ -117,7 +122,12 @@ final class AuthClientTests: XCTestCase { func testSignOutShouldRemoveSessionIfUserIsNotFound() async throws { sut = makeSUT { _ in - throw AuthError.api(AuthError.APIError(code: 404)) + throw AuthError.api( + message: "", + errorCode: .unknown, + underlyingData: Data(), + underlyingResponse: HTTPURLResponse(url: URL(string: "http://localhost")!, statusCode: 404, httpVersion: nil, headerFields: nil)! + ) } let validSession = Session.validSession @@ -129,12 +139,7 @@ final class AuthClientTests: XCTestCase { await Task.megaYield() - do { - try await sut.signOut() - } catch AuthError.api { - } catch { - XCTFail("Unexpected error: \(error)") - } + try await sut.signOut() let events = await eventsTask.value.map(\.event) let sessions = await eventsTask.value.map(\.session) @@ -148,7 +153,12 @@ final class AuthClientTests: XCTestCase { func testSignOutShouldRemoveSessionIfJWTIsInvalid() async throws { sut = makeSUT { _ in - throw AuthError.api(AuthError.APIError(code: 401)) + throw AuthError.api( + message: "", + errorCode: .invalidCredentials, + underlyingData: Data(), + underlyingResponse: HTTPURLResponse(url: URL(string: "http://localhost")!, statusCode: 401, httpVersion: nil, headerFields: nil)! + ) } let validSession = Session.validSession @@ -160,12 +170,7 @@ final class AuthClientTests: XCTestCase { await Task.megaYield() - do { - try await sut.signOut() - } catch AuthError.api { - } catch { - XCTFail("Unexpected error: \(error)") - } + try await sut.signOut() let events = await eventsTask.value.map(\.event) let sessions = await eventsTask.value.map(\.session) @@ -179,7 +184,12 @@ final class AuthClientTests: XCTestCase { func testSignOutShouldRemoveSessionIf403Returned() async throws { sut = makeSUT { _ in - throw AuthError.api(AuthError.APIError(code: 403)) + throw AuthError.api( + message: "", + errorCode: .invalidCredentials, + underlyingData: Data(), + underlyingResponse: HTTPURLResponse(url: URL(string: "http://localhost")!, statusCode: 403, httpVersion: nil, headerFields: nil)! + ) } let validSession = Session.validSession @@ -191,12 +201,7 @@ final class AuthClientTests: XCTestCase { await Task.megaYield() - do { - try await sut.signOut() - } catch AuthError.api { - } catch { - XCTFail("Unexpected error: \(error)") - } + try await sut.signOut() let events = await eventsTask.value.map(\.event) let sessions = await eventsTask.value.map(\.session) @@ -253,25 +258,6 @@ final class AuthClientTests: XCTestCase { XCTAssertEqual(events, [.initialSession, .signedIn]) } - func testSignInWithOAuthWithInvalidRedirecTo() async { - let sut = makeSUT() - - do { - try await sut.signInWithOAuth( - provider: .google, - redirectTo: nil, - launchFlow: { _ in - XCTFail("Should not call launchFlow.") - return URL(string: "https://supabase.com")! - } - ) - } catch let error as AuthError { - XCTAssertEqual(error, .invalidRedirectScheme) - } catch { - XCTFail("Unexcpted error: \(error)") - } - } - func testGetLinkIdentityURL() async throws { let sut = makeSUT { _ in .stub( diff --git a/Tests/AuthTests/AuthErrorTests.swift b/Tests/AuthTests/AuthErrorTests.swift new file mode 100644 index 00000000..66695263 --- /dev/null +++ b/Tests/AuthTests/AuthErrorTests.swift @@ -0,0 +1,42 @@ +// +// AuthErrorTests.swift +// Supabase +// +// Created by Guilherme Souza on 29/08/24. +// + +@testable import Auth +import XCTest + +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +final class AuthErrorTests: XCTestCase { + func testErrors() { + let sessionMissing = AuthError.sessionMissing + XCTAssertEqual(sessionMissing.errorCode, .sessionNotFound) + XCTAssertEqual(sessionMissing.message, "Auth session missing.") + + let weakPassword = AuthError.weakPassword(message: "Weak password", reasons: []) + XCTAssertEqual(weakPassword.errorCode, .weakPassword) + XCTAssertEqual(weakPassword.message, "Weak password") + + let api = AuthError.api( + message: "API Error", + errorCode: .emailConflictIdentityNotDeletable, + underlyingData: Data(), + underlyingResponse: HTTPURLResponse(url: URL(string: "http://localhost")!, statusCode: 400, httpVersion: nil, headerFields: nil)! + ) + XCTAssertEqual(api.errorCode, .emailConflictIdentityNotDeletable) + XCTAssertEqual(api.message, "API Error") + + let pkceGrantCodeExchange = AuthError.pkceGrantCodeExchange(message: "PKCE failure", error: nil, code: nil) + XCTAssertEqual(pkceGrantCodeExchange.errorCode, .unknown) + XCTAssertEqual(pkceGrantCodeExchange.message, "PKCE failure") + + let implicitGrantRedirect = AuthError.implicitGrantRedirect(message: "Implicit grant failure") + XCTAssertEqual(implicitGrantRedirect.errorCode, .unknown) + XCTAssertEqual(implicitGrantRedirect.message, "Implicit grant failure") + } +} diff --git a/Tests/AuthTests/JWTTests.swift b/Tests/AuthTests/JWTTests.swift index e8157d54..f505a05d 100644 --- a/Tests/AuthTests/JWTTests.swift +++ b/Tests/AuthTests/JWTTests.swift @@ -7,7 +7,7 @@ final class JWTTests: XCTestCase { let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjQ4NjQwMDIxLCJzdWIiOiJmMzNkM2VjOS1hMmVlLTQ3YzQtODBlMS01YmQ5MTlmM2Q4YjgiLCJlbWFpbCI6ImhpQGJpbmFyeXNjcmFwaW5nLmNvIiwicGhvbmUiOiIiLCJhcHBfbWV0YWRhdGEiOnsicHJvdmlkZXIiOiJlbWFpbCIsInByb3ZpZGVycyI6WyJlbWFpbCJdfSwidXNlcl9tZXRhZGF0YSI6e30sInJvbGUiOiJhdXRoZW50aWNhdGVkIn0.CGr5zNE5Yltlbn_3Ms2cjSLs_AW9RKM3lxh7cTQrg0w" let jwt = try decode(jwt: token) - let exp = try XCTUnwrap(jwt["exp"] as? TimeInterval) + let exp = try XCTUnwrap(jwt?["exp"] as? TimeInterval) XCTAssertEqual(exp, 1648640021) } } diff --git a/Tests/AuthTests/RequestsTests.swift b/Tests/AuthTests/RequestsTests.swift index ef7b0483..c109c5de 100644 --- a/Tests/AuthTests/RequestsTests.swift +++ b/Tests/AuthTests/RequestsTests.swift @@ -7,6 +7,7 @@ @testable import Auth import Helpers +import InlineSnapshotTesting import SnapshotTesting import TestHelpers import XCTest @@ -177,10 +178,15 @@ final class RequestsTests: XCTestCase { do { _ = try await sut.session(from: url) - } catch let error as URLError { - XCTAssertEqual(error.code, .badURL) } catch { - XCTFail("Unexpected error thrown: \(error.localizedDescription)") + assertInlineSnapshot(of: error, as: .dump) { + """ + ▿ AuthError + ▿ implicitGrantRedirect: (1 element) + - message: "No session defined in URL" + + """ + } } } diff --git a/Tests/AuthTests/SessionManagerTests.swift b/Tests/AuthTests/SessionManagerTests.swift index 0d56d123..73baf9e1 100644 --- a/Tests/AuthTests/SessionManagerTests.swift +++ b/Tests/AuthTests/SessionManagerTests.swift @@ -9,6 +9,7 @@ import ConcurrencyExtras import CustomDump import Helpers +import InlineSnapshotTesting import TestHelpers import XCTest import XCTestDynamicOverlay @@ -45,10 +46,14 @@ final class SessionManagerTests: XCTestCase { func testSession_shouldFailWithSessionNotFound() async { do { _ = try await sut.session() - XCTFail("Expected a \(AuthError.sessionNotFound) failure") - } catch AuthError.sessionNotFound { + XCTFail("Expected a \(AuthError.sessionMissing) failure") } catch { - XCTFail("Unexpected error \(error)") + assertInlineSnapshot(of: error, as: .dump) { + """ + - AuthError.sessionMissing + + """ + } } } diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testDeleteUser.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testDeleteUser.1.txt index dc66fd68..2e6dd0e4 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testDeleteUser.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testDeleteUser.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"should_soft_delete\":false}" \ "http://localhost:54321/auth/v1/admin/users/E621E1F8-C36C-495A-93FC-0C247A3E6E5F" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testGetLinkIdentityURL.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testGetLinkIdentityURL.1.txt index c7ac5bf1..44ad6259 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testGetLinkIdentityURL.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testGetLinkIdentityURL.1.txt @@ -2,4 +2,5 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Authorization: Bearer accesstoken" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ "http://localhost:54321/auth/v1/user/identities/authorize?extra_key=extra_value&provider=github&redirect_to=https://supabase.com&scopes=user:email&skip_http_redirect=true" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAChallenge.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAChallenge.1.txt index da75a782..e0263d44 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAChallenge.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAChallenge.1.txt @@ -3,4 +3,5 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Authorization: Bearer accesstoken" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ "http://localhost:54321/auth/v1/factors/123/challenge" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAChallengePhone.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAChallengePhone.1.txt index d41a5107..296f1940 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAChallengePhone.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAChallengePhone.1.txt @@ -4,5 +4,6 @@ curl \ --header "Authorization: Bearer accesstoken" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"channel\":\"whatsapp\"}" \ "http://localhost:54321/auth/v1/factors/123/challenge" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAEnrollLegacy.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAEnrollLegacy.1.txt index 9a6d6aff..51778779 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAEnrollLegacy.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAEnrollLegacy.1.txt @@ -4,5 +4,6 @@ curl \ --header "Authorization: Bearer accesstoken" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"factor_type\":\"totp\",\"friendly_name\":\"test\",\"issuer\":\"supabase.com\"}" \ "http://localhost:54321/auth/v1/factors" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAEnrollPhone.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAEnrollPhone.1.txt index a23176e6..5ae9482b 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAEnrollPhone.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAEnrollPhone.1.txt @@ -4,5 +4,6 @@ curl \ --header "Authorization: Bearer accesstoken" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"factor_type\":\"phone\",\"friendly_name\":\"test\",\"phone\":\"+1 202-918-2132\"}" \ "http://localhost:54321/auth/v1/factors" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAEnrollTotp.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAEnrollTotp.1.txt index 9a6d6aff..51778779 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAEnrollTotp.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAEnrollTotp.1.txt @@ -4,5 +4,6 @@ curl \ --header "Authorization: Bearer accesstoken" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"factor_type\":\"totp\",\"friendly_name\":\"test\",\"issuer\":\"supabase.com\"}" \ "http://localhost:54321/auth/v1/factors" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAUnenroll.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAUnenroll.1.txt index b3f057ce..75c28574 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAUnenroll.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAUnenroll.1.txt @@ -3,4 +3,5 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Authorization: Bearer accesstoken" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ "http://localhost:54321/auth/v1/factors/123" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAVerify.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAVerify.1.txt index f4db83cc..b336ccaf 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAVerify.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testMFAVerify.1.txt @@ -4,5 +4,6 @@ curl \ --header "Authorization: Bearer accesstoken" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"challenge_id\":\"123\",\"code\":\"123456\",\"factor_id\":\"123\"}" \ "http://localhost:54321/auth/v1/factors/123/verify" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testReauthenticate.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testReauthenticate.1.txt index 6a2f6bd8..d132361a 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testReauthenticate.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testReauthenticate.1.txt @@ -2,4 +2,5 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Authorization: Bearer accesstoken" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ "http://localhost:54321/auth/v1/reauthenticate" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testRefreshSession.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testRefreshSession.1.txt index 75958f98..69f5f61c 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testRefreshSession.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testRefreshSession.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"refresh_token\":\"refresh-token\"}" \ "http://localhost:54321/auth/v1/token?grant_type=refresh_token" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testResendEmail.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testResendEmail.1.txt index 4809ee9b..8f195886 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testResendEmail.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testResendEmail.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"email\":\"example@mail.com\",\"gotrue_meta_security\":{\"captcha_token\":\"captcha-token\"},\"type\":\"email_change\"}" \ "http://localhost:54321/auth/v1/resend?redirect_to=https://supabase.com" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testResendPhone.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testResendPhone.1.txt index 7972ac67..16f203b3 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testResendPhone.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testResendPhone.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"gotrue_meta_security\":{\"captcha_token\":\"captcha-token\"},\"phone\":\"+1 202-918-2132\",\"type\":\"phone_change\"}" \ "http://localhost:54321/auth/v1/resend" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testResetPasswordForEmail.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testResetPasswordForEmail.1.txt index f5eca058..c77f72d0 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testResetPasswordForEmail.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testResetPasswordForEmail.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"email\":\"example@mail.com\",\"gotrue_meta_security\":{\"captcha_token\":\"captcha-token\"}}" \ "http://localhost:54321/auth/v1/recover?redirect_to=https://supabase.com" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSessionFromURL.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSessionFromURL.1.txt index 390c2455..ca2aed24 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSessionFromURL.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSessionFromURL.1.txt @@ -2,4 +2,5 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Authorization: bearer accesstoken" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ "http://localhost:54321/auth/v1/user" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSetSessionWithAExpiredToken.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSetSessionWithAExpiredToken.1.txt index 0e4fd094..70612d26 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSetSessionWithAExpiredToken.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSetSessionWithAExpiredToken.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"refresh_token\":\"dummy-refresh-token\"}" \ "http://localhost:54321/auth/v1/token?grant_type=refresh_token" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSetSessionWithAFutureExpirationDate.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSetSessionWithAFutureExpirationDate.1.txt index 053a9f94..d8d24073 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSetSessionWithAFutureExpirationDate.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSetSessionWithAFutureExpirationDate.1.txt @@ -2,4 +2,5 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjo0ODUyMTYzNTkzLCJzdWIiOiJmMzNkM2VjOS1hMmVlLTQ3YzQtODBlMS01YmQ5MTlmM2Q4YjgiLCJlbWFpbCI6ImhpQGJpbmFyeXNjcmFwaW5nLmNvIiwicGhvbmUiOiIiLCJhcHBfbWV0YWRhdGEiOnsicHJvdmlkZXIiOiJlbWFpbCIsInByb3ZpZGVycyI6WyJlbWFpbCJdfSwidXNlcl9tZXRhZGF0YSI6e30sInJvbGUiOiJhdXRoZW50aWNhdGVkIn0.UiEhoahP9GNrBKw_OHBWyqYudtoIlZGkrjs7Qa8hU7I" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ "http://localhost:54321/auth/v1/user" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInAnonymously.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInAnonymously.1.txt index 68595574..5fa3cf34 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInAnonymously.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInAnonymously.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"data\":{\"custom_key\":\"custom_value\"},\"gotrue_meta_security\":{\"captcha_token\":\"captcha-token\"}}" \ "http://localhost:54321/auth/v1/signup" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithEmailAndPassword.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithEmailAndPassword.1.txt index e733a890..df2dfbbf 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithEmailAndPassword.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithEmailAndPassword.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"email\":\"example@mail.com\",\"gotrue_meta_security\":{\"captcha_token\":\"dummy-captcha\"},\"password\":\"the.pass\"}" \ "http://localhost:54321/auth/v1/token?grant_type=password" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithIdToken.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithIdToken.1.txt index a2b652d9..37477c44 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithIdToken.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithIdToken.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"access_token\":\"access-token\",\"gotrue_meta_security\":{\"captcha_token\":\"captcha-token\"},\"id_token\":\"id-token\",\"nonce\":\"nonce\",\"provider\":\"apple\"}" \ "http://localhost:54321/auth/v1/token?grant_type=id_token" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithOTPUsingEmail.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithOTPUsingEmail.1.txt index 1e737984..19c1ceba 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithOTPUsingEmail.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithOTPUsingEmail.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"create_user\":true,\"data\":{\"custom_key\":\"custom_value\"},\"email\":\"example@mail.com\",\"gotrue_meta_security\":{\"captcha_token\":\"dummy-captcha\"}}" \ "http://localhost:54321/auth/v1/otp?redirect_to=https://supabase.com" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithOTPUsingPhone.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithOTPUsingPhone.1.txt index 4c63bdfb..512cbccc 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithOTPUsingPhone.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithOTPUsingPhone.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"channel\":\"sms\",\"create_user\":true,\"data\":{\"custom_key\":\"custom_value\"},\"gotrue_meta_security\":{\"captcha_token\":\"dummy-captcha\"},\"phone\":\"+1 202-918-2132\"}" \ "http://localhost:54321/auth/v1/otp" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithPhoneAndPassword.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithPhoneAndPassword.1.txt index 04cbb203..c29e1e69 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithPhoneAndPassword.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithPhoneAndPassword.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"gotrue_meta_security\":{\"captcha_token\":\"dummy-captcha\"},\"password\":\"the.pass\",\"phone\":\"+1 202-918-2132\"}" \ "http://localhost:54321/auth/v1/token?grant_type=password" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithSSOUsingDomain.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithSSOUsingDomain.1.txt index 5c0e1139..1726c3ce 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithSSOUsingDomain.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithSSOUsingDomain.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"domain\":\"supabase.com\",\"gotrue_meta_security\":{\"captcha_token\":\"captcha-token\"},\"redirect_to\":\"https:\/\/supabase.com\"}" \ "http://localhost:54321/auth/v1/sso" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithSSOUsingProviderId.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithSSOUsingProviderId.1.txt index 8fbbf196..8674ed09 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithSSOUsingProviderId.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInWithSSOUsingProviderId.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"gotrue_meta_security\":{\"captcha_token\":\"captcha-token\"},\"provider_id\":\"E621E1F8-C36C-495A-93FC-0C247A3E6E5F\",\"redirect_to\":\"https:\/\/supabase.com\"}" \ "http://localhost:54321/auth/v1/sso" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignOut.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignOut.1.txt index af98f7e3..1868f0c5 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignOut.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignOut.1.txt @@ -3,4 +3,5 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Authorization: Bearer accesstoken" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ "http://localhost:54321/auth/v1/logout?scope=global" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignOutWithLocalScope.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignOutWithLocalScope.1.txt index 87aca3fc..151ef1ce 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignOutWithLocalScope.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignOutWithLocalScope.1.txt @@ -3,4 +3,5 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Authorization: Bearer accesstoken" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ "http://localhost:54321/auth/v1/logout?scope=local" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignOutWithOthersScope.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignOutWithOthersScope.1.txt index 8f912abe..44cf10c9 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignOutWithOthersScope.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignOutWithOthersScope.1.txt @@ -3,4 +3,5 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Authorization: Bearer accesstoken" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ "http://localhost:54321/auth/v1/logout?scope=others" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignUpWithEmailAndPassword.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignUpWithEmailAndPassword.1.txt index 1d615682..1f3cf59e 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignUpWithEmailAndPassword.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignUpWithEmailAndPassword.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"data\":{\"custom_key\":\"custom_value\"},\"email\":\"example@mail.com\",\"gotrue_meta_security\":{\"captcha_token\":\"dummy-captcha\"},\"password\":\"the.pass\"}" \ "http://localhost:54321/auth/v1/signup?redirect_to=https://supabase.com" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignUpWithPhoneAndPassword.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignUpWithPhoneAndPassword.1.txt index aaaf01d8..7bcbc227 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignUpWithPhoneAndPassword.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignUpWithPhoneAndPassword.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"data\":{\"custom_key\":\"custom_value\"},\"gotrue_meta_security\":{\"captcha_token\":\"dummy-captcha\"},\"password\":\"the.pass\",\"phone\":\"+1 202-918-2132\"}" \ "http://localhost:54321/auth/v1/signup" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testUnlinkIdentity.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testUnlinkIdentity.1.txt index ed7ae860..cc81e321 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testUnlinkIdentity.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testUnlinkIdentity.1.txt @@ -3,4 +3,5 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Authorization: Bearer accesstoken" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ "http://localhost:54321/auth/v1/user/identities/E621E1F8-C36C-495A-93FC-0C247A3E6E5F" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testUpdateUser.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testUpdateUser.1.txt index a87cf430..45eaa5f0 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testUpdateUser.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testUpdateUser.1.txt @@ -4,5 +4,6 @@ curl \ --header "Authorization: Bearer accesstoken" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"data\":{\"custom_key\":\"custom_value\"},\"email\":\"example@mail.com\",\"email_change_token\":\"123456\",\"nonce\":\"abcdef\",\"password\":\"another.pass\",\"phone\":\"+1 202-918-2132\"}" \ "http://localhost:54321/auth/v1/user" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testVerifyOTPUsingEmail.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testVerifyOTPUsingEmail.1.txt index 487034c9..885935d6 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testVerifyOTPUsingEmail.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testVerifyOTPUsingEmail.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"email\":\"example@mail.com\",\"gotrue_meta_security\":{\"captcha_token\":\"captcha-token\"},\"token\":\"123456\",\"type\":\"magiclink\"}" \ "http://localhost:54321/auth/v1/verify?redirect_to=https://supabase.com" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testVerifyOTPUsingPhone.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testVerifyOTPUsingPhone.1.txt index bb069709..38304842 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testVerifyOTPUsingPhone.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testVerifyOTPUsingPhone.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"gotrue_meta_security\":{\"captcha_token\":\"captcha-token\"},\"phone\":\"+1 202-918-2132\",\"token\":\"123456\",\"type\":\"sms\"}" \ "http://localhost:54321/auth/v1/verify" \ No newline at end of file diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testVerifyOTPUsingTokenHash.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testVerifyOTPUsingTokenHash.1.txt index 138d6fe0..2488d0c2 100644 --- a/Tests/AuthTests/__Snapshots__/RequestsTests/testVerifyOTPUsingTokenHash.1.txt +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testVerifyOTPUsingTokenHash.1.txt @@ -3,5 +3,6 @@ curl \ --header "Apikey: dummy.api.key" \ --header "Content-Type: application/json" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "X-Supabase-Api-Version: 2024-01-01" \ --data "{\"token_hash\":\"abc-def\",\"type\":\"email\"}" \ "http://localhost:54321/auth/v1/verify" \ No newline at end of file