Skip to content

Commit b15718d

Browse files
authored
Add support for external signing strategies to the SDK (#43)
* Initial draft of external signature generation * Updated README.md for signature generation * Added test for signature generation * Podspec and changelog
1 parent 0606c35 commit b15718d

File tree

6 files changed

+293
-186
lines changed

6 files changed

+293
-186
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# TransloaditKit Changelog
22

3-
## 3.4
3+
## 3.5.0
4+
5+
* Allow clients to inject only an api key and provide a signature generator closure to calculate signatures for signing requests instead of injecting a key and secret. ([#42](https://github.com/transloadit/TransloaditKit/issues/42))
6+
7+
## 3.4.0
48

59
* Updated Package to depend on exact TUSKit version and removed call to removed method in TUSKit ([#41](https://github.com/transloadit/TransloaditKit/issues/41))
610

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,53 @@ dependencies: [
2222

2323
Start by initializing `Transloadit`.
2424

25+
### Simple initialization; pass key and secret to the SDK
26+
2527
```swift
2628
let credentials = Transloadit.Credentials(key: "SomeKey", secret: "SomeSecret")
2729
let transloadit = Transloadit(credentials: credentials, session: URLSession.shared)
2830
```
2931

32+
Certain transloadit endpoints (can) require signatures to be included in their requests. The SDK can automatically generate signatures on your behalf but this requires you to pass both your Transloadit key _and_ secret to the SDK.
33+
34+
The SDK does not persist your secret locally beyond the SDK's lifetime.
35+
36+
This means that you're free to obtain your SDK secret in a secure manner from an external host or that you can include it in your app binary. It's up to you.
37+
38+
It's also possible to initialize the SDK with a `nil` secret and manage signing yourself.
39+
40+
### Advanced initialization; omit secret for manual request signing
41+
42+
If, for security reasons, you choose to not expose your API secret to the app in any way, shape, or form, you can manage signature generation yourself. This allows you to generate signatures on your server and provide them to Transloadit as needed.
43+
44+
To do this, use the `Transloadit` initializer that takes an api key and a `signatureGenerator`
45+
46+
```swift
47+
let transloadit = Transloadit(
48+
apiKey: "YOUR-API-KEY",
49+
sessionConfiguration: .default,
50+
signatureGenerator: { stringToSign, onSignatureGenerated in
51+
mySigningService.sign(stringToSign) { result in
52+
onSignatureGenerated(result)
53+
}
54+
})
55+
```
56+
57+
The signature generator is defined as follows:
58+
59+
```swift
60+
public typealias SignatureCompletion = (Result<String, Error>) -> Void
61+
public typealias SignatureGenerator = (String, SignatureCompletion) -> Void
62+
```
63+
64+
The generator itself is passed a string that needs to be signed (a JSON representation of the request parameters that you're generating a signature for) and a closure that you _must_ call to inform the SDK when you're done generating the signature (whether it's successful or failed).
65+
66+
**Important** if you don't call the completion handler, your requests will never be sent. The SDK does not implement a fallback or timeout.
67+
68+
The SDK will invoke the signature generator for every request that requires a signature. It will pass a parameter string for each request to your closure which you can then send to your service (local or external) for signature generation.
69+
70+
To learn more about signature generation see this page: https://transloadit.com/docs/api/authentication/
71+
3072
### Create an Assembly
3173

3274
To create an `Assembly` you invoke `createAssembly(steps:andUpload:completion)` on `Transloadit`.

Sources/TransloaditKit/Transloadit.swift

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ import Foundation
33

44
/// The errors that `Transloadit` can return
55
public enum TransloaditError: Error {
6-
76
case couldNotFetchStatus(underlyingError: Error)
87
case couldNotCreateAssembly(underlyingError: Error)
98
case couldNotUploadFile(underlyingError: Error)
109
case couldNotClearCache(underlyingError: Error)
1110
}
1211

12+
public enum SDKConfigurationError: Error {
13+
case missingClientSecret
14+
}
15+
1316
public protocol TransloaditFileDelegate: AnyObject {
1417

1518
func didStartUpload(assembly: Assembly, client: Transloadit)
@@ -31,6 +34,9 @@ public protocol TransloaditFileDelegate: AnyObject {
3134
func didError(error: Error, client: Transloadit)
3235
}
3336

37+
public typealias SignatureCompletion = (Result<String, Error>) -> Void
38+
public typealias SignatureGenerator = (String, SignatureCompletion) -> Void
39+
3440
/// Use the `Transloadit` class to upload files using the underlying TUS protocol.
3541
/// You can either create an Assembly by itself, or create an Assembly and upload files to it right away.
3642
///
@@ -42,9 +48,9 @@ public final class Transloadit {
4248

4349
public struct Credentials {
4450
let key: String
45-
let secret: String
51+
let secret: String?
4652

47-
public init(key: String, secret: String) {
53+
public init(key: String, secret: String?) {
4854
self.key = key
4955
self.secret = secret
5056
}
@@ -84,7 +90,18 @@ public final class Transloadit {
8490
/// then TUS will make a directory, whether one you specify or a default one in the documents directory.
8591
@available(*, deprecated, message: "Use the new init(credentials:sessionConfig:storageDir:) instead.")
8692
public init(credentials: Transloadit.Credentials, session: URLSession, storageDir: URL? = nil) {
87-
self.api = TransloaditAPI(credentials: credentials, session: session)
93+
self.api = TransloaditAPI(
94+
credentials: credentials,
95+
session: session,
96+
signatureGenerator: { parameterString, generationComplete in
97+
guard let secret = credentials.secret, !secret.isEmpty else {
98+
generationComplete(.failure(SDKConfigurationError.missingClientSecret))
99+
return
100+
}
101+
102+
generationComplete(.success("sha384:" + parameterString.hmac(key: secret)))
103+
}
104+
)
88105
self.storageDir = storageDir
89106
self.tusSessionConfig = session.configuration.copy(withIdentifier: "com.transloadit.tus.bg")
90107
}
@@ -97,7 +114,47 @@ public final class Transloadit {
97114
/// If left empty, no directory will be made when performing non-file related tasks, such as creating assemblies. However, if you start uploading files,
98115
/// then TUS will make a directory, whether one you specify or a default one in the documents directory.
99116
public init(credentials: Transloadit.Credentials, sessionConfiguration: URLSessionConfiguration, storageDir: URL? = nil) {
100-
self.api = TransloaditAPI(credentials: credentials, sessionConfiguration: sessionConfiguration)
117+
self.api = TransloaditAPI(
118+
credentials: credentials,
119+
sessionConfiguration: sessionConfiguration,
120+
signatureGenerator: { parameterString, generationComplete in
121+
guard let secret = credentials.secret, !secret.isEmpty else {
122+
generationComplete(.failure(SDKConfigurationError.missingClientSecret))
123+
return
124+
}
125+
126+
generationComplete(.success("sha384:" + parameterString.hmac(key: secret)))
127+
}
128+
)
129+
self.storageDir = storageDir
130+
self.tusSessionConfig = sessionConfiguration.copy(withIdentifier: "com.transloadit.tus.bg")
131+
}
132+
133+
/// Initialize Transloadit without a secret, providing a signature generator.
134+
/// - Parameters:
135+
/// - apiKey: Transloadit API key.
136+
/// - sessionConfiguration: A URLSessionConfiguration to use.
137+
/// - storageDir: A storagedirectory to use. Used by underlying TUSKit mechanism to store files.
138+
/// If left empty, no directory will be made when performing non-file related tasks, such as creating assemblies. However, if you start uploading files,
139+
/// then TUS will make a directory, whether one you specify or a default one in the documents directory.
140+
/// - signatureGenerator: A closure that's invoked to generate the signature for the API request. Implement your own logic to generate a valid
141+
/// signature. Call the provided completion handler with your signed string or an error as needed.
142+
///
143+
/// For example, you can make a request to your backend to generate the signature for you. The closure is passed a string that holds all request params
144+
/// that need to be signed. See https://transloadit.com/docs/api/authentication/ for more information on signature authentication.
145+
/// The closure is invoked by the TransloaditAPI when needed.
146+
///
147+
/// ** Important:** It's up to the caller to ensure that all codepaths (eventually) call the completion handler. The SDK does not implement any timeouts or fallbacks.
148+
public init(
149+
apiKey: String, sessionConfiguration: URLSessionConfiguration,
150+
storageDir: URL? = nil, signatureGenerator: @escaping SignatureGenerator
151+
) {
152+
let credentials = Transloadit.Credentials(key: apiKey, secret: nil)
153+
self.api = TransloaditAPI(
154+
credentials: credentials,
155+
sessionConfiguration: sessionConfiguration,
156+
signatureGenerator: signatureGenerator
157+
)
101158
self.storageDir = storageDir
102159
self.tusSessionConfig = sessionConfiguration.copy(withIdentifier: "com.transloadit.tus.bg")
103160
}

0 commit comments

Comments
 (0)