Skip to content
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
2 changes: 2 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
disabled_rules:
- trailing_whitespace
62 changes: 51 additions & 11 deletions Sources/Entities/Issuance/AuthorizedRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ public extension CanExpire {
if issued >= at {
return true
}

guard let expiresIn = expiresIn else {
return false
}

let expiration = issued + expiresIn
return expiration <= at
return expiration >= at
}
}

Expand All @@ -51,24 +51,24 @@ public enum AuthorizedRequest {
timeStamp: TimeInterval,
dPopNonce: Nonce?
)
public func isAccessTokenExpired(clock: TimeInterval) -> Bool {

public func isAccessTokenExpired(_ from: TimeInterval) -> Bool {
guard let timeStamp = self.timeStamp else {
return true
}
return accessToken?.isExpired(issued: timeStamp, at: clock) ?? false
return accessToken?.isExpired(issued: timeStamp, at: from) ?? false
}

public func isRefreshTokenExpired(clock: TimeInterval) -> Bool {
guard let timeStamp = self.timeStamp else {
return true
}
return accessToken?.isExpired(
issued: timeStamp,
at: clock
) ?? false
) ?? false
}

public var timeStamp: TimeInterval? {
switch self {
case .noProofRequired(_, _, _, let timeStamp, _):
Expand All @@ -86,7 +86,7 @@ public enum AuthorizedRequest {
return dPopNonce
}
}

public var noProofToken: IssuanceAccessToken? {
switch self {
case .noProofRequired(let accessToken, _, _, _, _):
Expand All @@ -95,7 +95,7 @@ public enum AuthorizedRequest {
return nil
}
}

public var proofToken: IssuanceAccessToken? {
switch self {
case .noProofRequired:
Expand All @@ -104,6 +104,15 @@ public enum AuthorizedRequest {
return accessToken
}
}

public var refreshToken: IssuanceRefreshToken? {
switch self {
case .noProofRequired(_, let refreshToken, _, _, _):
return refreshToken
case .proofRequired(_, let refreshToken, _, _, _, _):
return refreshToken
}
}
}

public extension AuthorizedRequest {
Expand Down Expand Up @@ -132,3 +141,34 @@ public extension AuthorizedRequest {
}
}
}

extension AuthorizedRequest {
/// Returns a copy of the current `AuthorizedRequest`, replacing the `accessToken` and `timeStamp`
/// - Parameters:
/// - newAccessToken: The new `IssuanceAccessToken` to use.
/// - newTimeStamp: The new `TimeInterval` to use.
/// - Returns: A new `AuthorizedRequest` instance with the updated values.
func replacing(accessToken newAccessToken: IssuanceAccessToken, timeStamp newTimeStamp: TimeInterval) -> AuthorizedRequest {
switch self {
case let .noProofRequired(_, refreshToken, credentialIdentifiers, _, dPopNonce):
return .noProofRequired(
accessToken: newAccessToken,
refreshToken: refreshToken,
credentialIdentifiers: credentialIdentifiers,
timeStamp: newTimeStamp,
dPopNonce: dPopNonce
)

case let .proofRequired(_, refreshToken, cNonce, credentialIdentifiers, _, dPopNonce):
return .proofRequired(
accessToken: newAccessToken,
refreshToken: refreshToken,
cNonce: cNonce,
credentialIdentifiers: credentialIdentifiers,
timeStamp: newTimeStamp,
dPopNonce: dPopNonce
)
}
}
}

3 changes: 2 additions & 1 deletion Sources/Entities/IssuanceRefreshToken.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ public struct IssuanceRefreshToken: Codable, CanExpire {

public let refreshToken: String?

public init(refreshToken: String?) throws {
public init(refreshToken: String?, expiresIn: TimeInterval? = nil) throws {
self.refreshToken = refreshToken
self.expiresIn = expiresIn
}
}

5 changes: 5 additions & 0 deletions Sources/Extensions/Int+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,9 @@ public extension Int {
func isWithinRange(_ range: ClosedRange<Int>) -> Bool {
return range.contains(self)
}

/// Converts an `Int` to a `TimeInterval` (Double).
var asTimeInterval: TimeInterval {
return TimeInterval(self)
}
}
66 changes: 56 additions & 10 deletions Sources/Issuers/Issuer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ public protocol IssuerType {
notificationId: NotificationObject,
dPopNonce: Nonce?
) async throws -> Result<Void, Error>

func refresh(
clientId: String,
authorizedRequest: AuthorizedRequest,
dPopNonce: Nonce?
) async -> Result<AuthorizedRequest, Error>
}

public actor Issuer: IssuerType {
Expand Down Expand Up @@ -241,17 +247,19 @@ public actor Issuer: IssuerType {

switch response {
case .success(
(let accessToken, let nonce, let identifiers, let expiresIn, let dPopNonce)
(let accessToken, let refreshToken, let nonce, let identifiers, let expiresIn, let dPopNonce)
):
if let cNonce = nonce {
return .success(
.proofRequired(
accessToken: try IssuanceAccessToken(
accessToken: try .init(
accessToken: accessToken.accessToken,
tokenType: accessToken.tokenType,
expiresIn: TimeInterval(expiresIn ?? .zero)
expiresIn: expiresIn?.asTimeInterval ?? .zero
),
refreshToken: try .init(
refreshToken: refreshToken.refreshToken
),
refreshToken: nil,
cNonce: cNonce,
credentialIdentifiers: identifiers,
timeStamp: Date().timeIntervalSinceReferenceDate,
Expand All @@ -264,9 +272,11 @@ public actor Issuer: IssuerType {
accessToken: try IssuanceAccessToken(
accessToken: accessToken.accessToken,
tokenType: accessToken.tokenType,
expiresIn: TimeInterval(expiresIn ?? .zero)
expiresIn: expiresIn?.asTimeInterval ?? .zero
),
refreshToken: try .init(
refreshToken: refreshToken.refreshToken
),
refreshToken: nil,
credentialIdentifiers: identifiers,
timeStamp: Date().timeIntervalSinceReferenceDate,
dPopNonce: dPopNonce
Expand Down Expand Up @@ -307,6 +317,7 @@ public actor Issuer: IssuerType {

let response: (
accessToken: IssuanceAccessToken,
refreshToken: IssuanceRefreshToken,
nonce: CNonce?,
identifiers: AuthorizationDetailsIdentifiers?,
tokenType: TokenType?,
Expand All @@ -323,12 +334,14 @@ public actor Issuer: IssuerType {
if let cNonce = response.nonce {
return .success(
.proofRequired(
accessToken: try IssuanceAccessToken(
accessToken: try .init(
accessToken: response.accessToken.accessToken,
tokenType: response.tokenType,
expiresIn: TimeInterval(response.expiresIn ?? .zero)
),
refreshToken: nil,
refreshToken: try .init(
refreshToken: response.refreshToken.refreshToken
),
cNonce: cNonce,
credentialIdentifiers: response.identifiers,
timeStamp: Date().timeIntervalSinceReferenceDate,
Expand All @@ -338,12 +351,14 @@ public actor Issuer: IssuerType {
} else {
return .success(
.noProofRequired(
accessToken: try IssuanceAccessToken(
accessToken: try .init(
accessToken: response.accessToken.accessToken,
tokenType: response.tokenType,
expiresIn: TimeInterval(response.expiresIn ?? .zero)
),
refreshToken: nil,
refreshToken: try .init(
refreshToken: response.refreshToken.refreshToken
),
credentialIdentifiers: response.identifiers,
timeStamp: Date().timeIntervalSinceReferenceDate,
dPopNonce: response.dPopNonce
Expand Down Expand Up @@ -838,4 +853,35 @@ public extension Issuer {
dPopNonce: dPopNonce
)
}

func refresh(
clientId: String,
authorizedRequest: AuthorizedRequest,
dPopNonce: Nonce? = nil
) async -> Result<AuthorizedRequest, Error> {

if let refreshToken = authorizedRequest.refreshToken {
do {
let token = try await authorizer.refreshAccessToken(
clientId: clientId,
refreshToken: refreshToken,
dpopNonce: dPopNonce,
retry: true
)
switch token {
case .success((let accessToken, _, _, _, let timeStamp, _)):
return .success(authorizedRequest.replacing(
accessToken: accessToken,
timeStamp: timeStamp?.asTimeInterval ?? .zero
)
)
case .failure(let error):
return .failure(error)
}
} catch {
return .failure(error)
}
}
return .success(authorizedRequest)
}
}
Loading