Skip to content

Commit

Permalink
FEATURE: Request authentication token
Browse files Browse the repository at this point in the history
  • Loading branch information
adamayoung committed Jan 24, 2024
1 parent ff9ae08 commit 4182e6d
Show file tree
Hide file tree
Showing 82 changed files with 293 additions and 13 deletions.
4 changes: 4 additions & 0 deletions Sources/TMDb/Extensions/URL+TMDb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ extension URL {
URL(string: "https://api.themoviedb.org/3")!
}

static var tmdbWebSiteURL: URL {
URL(string: "https://www.themoviedb.org")!
}

}
46 changes: 46 additions & 0 deletions Sources/TMDb/Helpers/AuthenticateURLBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// AuthenticateURLBuilder.swift
// TMDb
//
// Copyright © 2024 Adam Young.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an AS IS BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

final class AuthenticateURLBuilder: AuthenticateURLBuilding {

private let baseURL: URL

init(baseURL: URL) {
self.baseURL = baseURL
}

func authenticateURL(with requestToken: String) -> URL {
authenticateURL(with: requestToken, redirectURL: nil)
}

func authenticateURL(with requestToken: String, redirectURL: URL?) -> URL {
var url = baseURL
.appendingPathComponent("authenticate")
.appendingPathComponent(requestToken)

if let redirectURL {
url = url.appendingQueryItem(name: "redirect_to", value: redirectURL.absoluteString)
}

return url
}

}
16 changes: 15 additions & 1 deletion Sources/TMDb/Models/Token.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import Foundation

///
/// A model representing a token request.
/// A model representing an internediate request token.
///
public struct Token: Codable, Equatable, Hashable {

Expand All @@ -39,4 +39,18 @@ public struct Token: Codable, Equatable, Hashable {
///
public let expiresAt: Date

///
/// Creates an internediate request token.
///
/// - Parameters:
/// - success: Was token creation successful.
/// - requestToken: An intermediate request token.
/// - expiresAt: Date of token expiry.
///
public init(success: Bool, requestToken: String, expiresAt: Date) {
self.success = success
self.requestToken = requestToken
self.expiresAt = expiresAt
}

}
File renamed without changes.
28 changes: 28 additions & 0 deletions Sources/TMDb/Services/Authentication/AuthenticateURLBuilding.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// AuthenticateURLBuilding.swift
// TMDb
//
// Copyright © 2024 Adam Young.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an AS IS BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

protocol AuthenticateURLBuilding {

func authenticateURL(with requestToken: String) -> URL

func authenticateURL(with requestToken: String, redirectURL: URL?) -> URL

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,33 @@
import Foundation

///
/// Provides an interface for authenticating with TMDb.
/// Provides an interface for authenticating and generating session IDs with TMDb.
///
/// Details of generating session IDs for TMDb can be found at
/// [TMDb API - How do I generate a session ID?](https://developer.themoviedb.org/reference/authentication-how-do-i-generate-a-session-id)
///
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
public final class AuthenticationService {

private let apiClient: any APIClient
private let authenticateURLBuilder: any AuthenticateURLBuilding

///
/// Creates an authentication service object.
///
public convenience init() {
self.init(
apiClient: TMDbFactory.authAPIClient
apiClient: TMDbFactory.authAPIClient,
authenticateURLBuilder: TMDbFactory.authenticateURLBuilder
)
}

init(apiClient: some APIClient) {
init(
apiClient: some APIClient,
authenticateURLBuilder: some AuthenticateURLBuilding
) {
self.apiClient = apiClient
self.authenticateURLBuilder = authenticateURLBuilder
}

///
Expand All @@ -49,7 +58,7 @@ public final class AuthenticationService {
///
/// Guest sessions will automatically be deleted if they are not used within 60 minutes of it being issued.
///
/// [TMDb API - Authentication: Create Guest Session](https://developer.themoviedb.org/reference/certifications-tv-list)
/// [TMDb API - Authentication: Create Guest Session](https://developer.themoviedb.org/reference/authentication-create-guest-session)
///
/// - Throws: TMDb error ``TMDbError``.
///
Expand All @@ -72,6 +81,8 @@ public final class AuthenticationService {
/// This is a temporary token that is required to ask the user for permission to access their account. This token
/// will auto expire after 60 minutes if it's not used.
///
/// [TMDb API - Authentication: Create Request Token](https://developer.themoviedb.org/reference/authentication-create-request-token)
///
/// - Returns: An intermediate request token.
///
public func requestToken() async throws -> Token {
Expand All @@ -85,4 +96,21 @@ public final class AuthenticationService {
return token
}

///
/// Builds the URL used for the user to authenticate with after requesting an intermediate request token.
///
/// An internediate request token can be generated by calling ``requestToken()``.
///
/// - Parameters:
/// - token: An intermediate request token.
/// - redirectURL: Optional URL to redirect to once the user has authenticated.
///
/// - Returns: An authenticate URL.
///
public func authenticateURL(for token: Token, redirectURL: URL? = nil) -> URL {
let url = authenticateURLBuilder.authenticateURL(with: token.requestToken, redirectURL: redirectURL)

return url
}

}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 3 additions & 1 deletion Sources/TMDb/TMDb.docc/Extensions/AuthenticationService.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@

### Creating Sessions

- ``createGuestSession()``
- ``guestSession()``
- ``requestToken()``
- ``authenticateURL(for:redirectURL:)``
34 changes: 29 additions & 5 deletions Sources/TMDb/TMDbFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,28 @@ extension TMDbFactory {

static var apiClient: some APIClient {
TMDbAPIClient(
apiKey: TMDb.configuration.apiKey(),
baseURL: .tmdbAPIBaseURL,
httpClient: TMDb.configuration.httpClient(),
apiKey: apiKey,
baseURL: tmdbAPIBaseURL,
httpClient: httpClient,
serialiser: serialiser,
localeProvider: localeProvider()
)
}

static var authAPIClient: some APIClient {
TMDbAPIClient(
apiKey: TMDb.configuration.apiKey(),
apiKey: apiKey,
baseURL: .tmdbAPIBaseURL,
httpClient: TMDb.configuration.httpClient(),
httpClient: httpClient,
serialiser: authSerialiser,
localeProvider: localeProvider()
)
}

static var authenticateURLBuilder: some AuthenticateURLBuilding {
AuthenticateURLBuilder(baseURL: tmdbWebSiteURL)
}

static func localeProvider() -> some LocaleProviding {
LocaleProvider(locale: .current)
}
Expand Down Expand Up @@ -96,3 +100,23 @@ extension TMDbFactory {
}

}

extension TMDbFactory {

private static var tmdbAPIBaseURL: URL {
URL.tmdbAPIBaseURL
}

private static var tmdbWebSiteURL: URL {
URL.tmdbWebSiteURL
}

private static var apiKey: String {
TMDb.configuration.apiKey()
}

private static var httpClient: any HTTPClient {
TMDb.configuration.httpClient()
}

}
64 changes: 64 additions & 0 deletions Tests/TMDbTests/Helpers/AuthenticateURLBuilderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// AuthenticateURLBuilderTests.swift
// TMDb
//
// Copyright © 2024 Adam Young.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an AS IS BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

@testable import TMDb
import XCTest

final class AuthenticateURLBuilderTests: XCTestCase {

var builder: AuthenticateURLBuilder!
var baseURL: URL!

override func setUp() {
super.setUp()
baseURL = URL(string: "https://some.domain.com")
builder = AuthenticateURLBuilder(baseURL: baseURL)
}

override func tearDown() {
builder = nil
baseURL = nil
super.tearDown()
}

func testAuthenticateURLReturnsURL() {
let requestToken = "qwertyuiop"
let expectedURL = baseURL
.appendingPathComponent("authenticate")
.appendingPathComponent(requestToken)

let url = builder.authenticateURL(with: requestToken)

XCTAssertEqual(url, expectedURL)
}

func testAuthenticateURLWithRedirectURLReturnsURL() throws {
let requestToken = "qwertyuiop"
let redirectURL = try XCTUnwrap(URL(string: "https://my.domain.com/auth/callback"))
let expectedURL = baseURL
.appendingPathComponent("authenticate")
.appendingPathComponent(requestToken)
.appendingQueryItem(name: "redirect_to", value: redirectURL.absoluteString)

let url = builder.authenticateURL(with: requestToken, redirectURL: redirectURL)

XCTAssertEqual(url, expectedURL)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// AuthenticateURLMockBuilder.swift
// TMDb
//
// Copyright © 2024 Adam Young.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an AS IS BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation
@testable import TMDb

final class AuthenticateURLMockBuilder: AuthenticateURLBuilding {

var authenticateURLResult: URL = .init(string: "https://some.domain.com/authenticate")!
private(set) var lastRequestToken: String?
private(set) var lastRedirectURL: URL?

func authenticateURL(with requestToken: String) -> URL {
authenticateURL(with: requestToken, redirectURL: nil)
}

func authenticateURL(with requestToken: String, redirectURL: URL?) -> URL {
lastRequestToken = requestToken
lastRedirectURL = redirectURL

return authenticateURLResult
}

}
File renamed without changes.
Loading

0 comments on commit 4182e6d

Please sign in to comment.