Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): add isExpired variable to session type #399

Merged
merged 1 commit into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions Sources/Auth/AuthClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -599,17 +599,17 @@ public final class AuthClient: Sendable {
/// Gets the session data from a OAuth2 callback URL.
@discardableResult
public func session(from url: URL) async throws -> Session {
if configuration.flowType == .implicit, !isImplicitGrantFlow(url: url) {
let params = extractParams(from: url)

if configuration.flowType == .implicit, !isImplicitGrantFlow(params: params) {
throw AuthError.invalidImplicitGrantFlowURL
}

if configuration.flowType == .pkce, !isPKCEFlow(url: url) {
if configuration.flowType == .pkce, !isPKCEFlow(params: params) {
throw AuthError.pkce(.invalidPKCEFlowURL)
}

let params = extractParams(from: url)

if isPKCEFlow(url: url) {
if isPKCEFlow(params: params) {
guard let code = params["code"] else {
throw AuthError.pkce(.codeVerifierNotFound)
}
Expand Down Expand Up @@ -1120,15 +1120,13 @@ public final class AuthClient: Sendable {
return (codeChallenge, codeChallengeMethod)
}

private func isImplicitGrantFlow(url: URL) -> Bool {
let fragments = extractParams(from: url)
return fragments["access_token"] != nil || fragments["error_description"] != nil
private func isImplicitGrantFlow(params: [String: String]) -> Bool {
params["access_token"] != nil || params["error_description"] != nil
}

private func isPKCEFlow(url: URL) -> Bool {
let fragments = extractParams(from: url)
private func isPKCEFlow(params: [String: String]) -> Bool {
let currentCodeVerifier = codeVerifierStorage.get()
return fragments["code"] != nil && currentCodeVerifier != nil
return params["code"] != nil && currentCodeVerifier != nil
}

private func getURLForProvider(
Expand Down
21 changes: 18 additions & 3 deletions Sources/Auth/AuthError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ public enum AuthError: LocalizedError, Sendable, Equatable {
case malformedJWT
case sessionNotFound
case api(APIError)

/// Error thrown during PKCE flow.
case pkce(PKCEFailureReason)

case invalidImplicitGrantFlowURL
case missingURL
case invalidRedirectScheme

/// An error returned by the API.
public struct APIError: Error, Decodable, Sendable, Equatable {
/// A basic message describing the problem with the request. Usually missing if
/// ``AuthError/APIError/error`` is present.
Expand Down Expand Up @@ -39,21 +43,32 @@ public enum AuthError: LocalizedError, Sendable, Equatable {
}

public enum PKCEFailureReason: Sendable {
/// Code verifier not found in the URL.
case codeVerifierNotFound

/// Not a valid PKCE flow URL.
case invalidPKCEFlowURL
}

public var errorDescription: String? {
switch self {
case let .api(error): error.errorDescription ?? error.msg ?? error.error
case .missingExpClaim: "Missing expiration claim on access token."
case .missingExpClaim: "Missing expiration claim in the access token."
case .malformedJWT: "A malformed JWT received."
case .sessionNotFound: "Unable to get a valid session."
case .pkce(.codeVerifierNotFound): "A code verifier wasn't found in PKCE flow."
case .pkce(.invalidPKCEFlowURL): "Not a valid PKCE flow url."
case let .pkce(reason): reason.errorDescription
case .invalidImplicitGrantFlowURL: "Not a valid implicit grant flow url."
case .missingURL: "Missing URL."
case .invalidRedirectScheme: "Invalid redirect scheme."
}
}
}

extension AuthError.PKCEFailureReason {
var errorDescription: String {
switch self {
case .codeVerifierNotFound: "A code verifier wasn't found in PKCE flow."
case .invalidPKCEFlowURL: "Not a valid PKCE flow url."
}
}
}
10 changes: 10 additions & 0 deletions Sources/Auth/Internal/Contants.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// Contants.swift
//
//
// Created by Guilherme Souza on 22/05/24.
//

import Foundation

let EXPIRY_MARGIN: TimeInterval = 30
2 changes: 1 addition & 1 deletion Sources/Auth/Internal/SessionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private actor LiveSessionManager {
throw AuthError.sessionNotFound
}

if currentSession.isValid {
if !currentSession.isExpired {
scheduleNextTokenRefresh(currentSession)

return currentSession
Expand Down
6 changes: 0 additions & 6 deletions Sources/Auth/Internal/SessionStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,6 @@ struct StoredSession: Codable {
}
}

extension Session {
var isValid: Bool {
expiresAt - Date().timeIntervalSince1970 > 60
}
}

extension AuthLocalStorage {
func getSession() throws -> Session? {
try retrieve(key: "supabase.session").flatMap {
Expand Down
32 changes: 14 additions & 18 deletions Sources/Auth/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public enum AuthChangeEvent: String, Sendable {
case mfaChallengeVerified = "MFA_CHALLENGE_VERIFIED"
}

@available(
*,
deprecated,
message: "Access to UserCredentials will be removed on the next major release."
)
public struct UserCredentials: Codable, Hashable, Sendable {
public var email: String?
public var password: String?
Expand Down Expand Up @@ -104,21 +109,13 @@ public struct Session: Codable, Hashable, Sendable {
self.user = user
}

static let empty = Session(
accessToken: "",
tokenType: "",
expiresIn: 0,
expiresAt: 0,
refreshToken: "",
user: User(
id: UUID(),
appMetadata: [:],
userMetadata: [:],
aud: "",
createdAt: Date(),
updatedAt: Date()
)
)
/// Returns `true` if the token is expired or will expire in the next 30 seconds.
///
/// The 30 second buffer is to account for latency issues.
public var isExpired: Bool {
let expiresAt = Date(timeIntervalSince1970: expiresAt)
return expiresAt.timeIntervalSinceNow < EXPIRY_MARGIN
}
}

public struct User: Codable, Hashable, Identifiable, Sendable {
Expand Down Expand Up @@ -513,7 +510,7 @@ public struct Factor: Identifiable, Codable, Hashable, Sendable {
public let friendlyName: String?

/// Type of factor. Only `totp` supported with this version but may change in future versions.
public let factorType: String
public let factorType: FactorType

/// Factor's status.
public let status: FactorStatus
Expand Down Expand Up @@ -718,8 +715,7 @@ public struct ResendMobileResponse: Decodable, Hashable, Sendable {
}

public struct WeakPassword: Codable, Hashable, Sendable {
/// List of reasons the password is too weak, could be any of `length`, `characters`, or
/// `pwned`.
/// List of reasons the password is too weak, could be any of `length`, `characters`, or `pwned`.
public let reasons: [String]
}

Expand Down
3 changes: 1 addition & 2 deletions Sources/_Helpers/AnyJSON/AnyJSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ public enum AnyJSON: Sendable, Codable, Hashable {

/// Returns the underlying Swift value corresponding to the `AnyJSON` instance.
///
/// - Note: For `.object` and `.array` cases, the returned value contains recursively transformed
/// `AnyJSON` instances.
/// - Note: For `.object` and `.array` cases, the returned value contains recursively transformed `AnyJSON` instances.
public var value: Any {
switch self {
case .null: NSNull()
Expand Down
4 changes: 2 additions & 2 deletions Tests/AuthTests/Mocks/Mocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ extension Session {
static let expiredSession = Session(
accessToken: "accesstoken",
tokenType: "bearer",
expiresIn: 60,
expiresAt: Date().addingTimeInterval(60).timeIntervalSince1970,
expiresIn: 30,
expiresAt: Date().addingTimeInterval(30).timeIntervalSince1970,
refreshToken: "refreshtoken",
user: User(fromMockNamed: "user")
)
Expand Down
Loading