diff --git a/.gitignore b/.gitignore index 272f953ef..2c573ad74 100644 --- a/.gitignore +++ b/.gitignore @@ -74,4 +74,6 @@ fastlane/test_output /Frameworks/Secrets/Secrets.swift Secrets/Sources/Secrets/Secrets.swift *.py[cod] +/Secrets/Sources/Secrets/SecretKey.swift /Modules/Secrets/Sources/Secrets/SecretKey.swift + diff --git a/Account/Sources/Account/Feedly/OAuthAuthorizationClient+Feedly.swift b/Account/Sources/Account/Feedly/OAuthAuthorizationClient+Feedly.swift index 627736317..5300b0cf1 100644 --- a/Account/Sources/Account/Feedly/OAuthAuthorizationClient+Feedly.swift +++ b/Account/Sources/Account/Feedly/OAuthAuthorizationClient+Feedly.swift @@ -15,10 +15,10 @@ extension OAuthAuthorizationClient { /// Models private NetNewsWire client secrets. /// These placeholders are substituted at build time using a Run Script phase with build settings. /// https://developer.feedly.com/v3/auth/#authenticating-a-user-and-obtaining-an-auth-code - return OAuthAuthorizationClient(id: SecretsManager.provider.feedlyClientId, + return OAuthAuthorizationClient(id: SecretKey.feedlyClientID, redirectUri: "netnewswire://auth/feedly", state: nil, - secret: SecretsManager.provider.feedlyClientSecret) + secret: SecretKey.feedlyClientSecret) } static var feedlySandboxClient: OAuthAuthorizationClient { diff --git a/Account/Sources/Account/NewsBlur/NewsBlurAPICaller.swift b/Account/Sources/Account/NewsBlur/NewsBlurAPICaller.swift index 58849c6cd..77e4521e1 100644 --- a/Account/Sources/Account/NewsBlur/NewsBlurAPICaller.swift +++ b/Account/Sources/Account/NewsBlur/NewsBlurAPICaller.swift @@ -56,7 +56,7 @@ final class NewsBlurAPICaller: NSObject { let cookies = HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url) for cookie in cookies where cookie.name == Self.SessionIdCookie { - let credentials = Credentials(type: .newsBlurSessionId, username: username, secret: cookie.value) + let credentials = Credentials(type: .newsBlurSessionID, username: username, secret: cookie.value) completion(.success(credentials)) return } diff --git a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift index d24764327..0e0ed5775 100644 --- a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift +++ b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift @@ -609,7 +609,7 @@ final class NewsBlurAccountDelegate: AccountDelegate { } func accountDidInitialize(_ account: Account) { - credentials = try? account.retrieveCredentials(type: .newsBlurSessionId) + credentials = try? account.retrieveCredentials(type: .newsBlurSessionID) } func accountWillBeDeleted(_ account: Account) { diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPICaller.swift b/Account/Sources/Account/ReaderAPI/ReaderAPICaller.swift index fd2995c25..45ac4fb44 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPICaller.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPICaller.swift @@ -693,8 +693,8 @@ private extension ReaderAPICaller { func addVariantHeaders(_ request: inout URLRequest) { if variant == .inoreader { - request.addValue(SecretsManager.provider.inoreaderAppId, forHTTPHeaderField: "AppId") - request.addValue(SecretsManager.provider.inoreaderAppKey, forHTTPHeaderField: "AppKey") + request.addValue(SecretKey.inoreaderAppID, forHTTPHeaderField: "AppId") + request.addValue(SecretKey.inoreaderAppKey, forHTTPHeaderField: "AppKey") } } diff --git a/Account/Sources/Account/URLRequest+Account.swift b/Account/Sources/Account/URLRequest+Account.swift index 035106a7c..57026c5c7 100755 --- a/Account/Sources/Account/URLRequest+Account.swift +++ b/Account/Sources/Account/URLRequest+Account.swift @@ -35,7 +35,7 @@ public extension URLRequest { URLQueryItem(name: "password", value: credentials.secret), ] httpBody = postData.enhancedPercentEncodedQuery?.data(using: .utf8) - case .newsBlurSessionId: + case .newsBlurSessionID: setValue("\(NewsBlurAPICaller.SessionIdCookie)=\(credentials.secret)", forHTTPHeaderField: "Cookie") httpShouldHandleCookies = true case .readerBasic: diff --git a/Account/Tests/AccountTests/Feedly/FeedlyTestSecrets.swift b/Account/Tests/AccountTests/Feedly/FeedlyTestSecrets.swift deleted file mode 100644 index 9b92c02af..000000000 --- a/Account/Tests/AccountTests/Feedly/FeedlyTestSecrets.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// FeedlyTestSecrets.swift -// -// -// Created by Maurice Parker on 8/4/20. -// - -import Foundation -import Secrets - -struct FeedlyTestSecrets: SecretsProvider { - var mercuryClientId = "" - var mercuryClientSecret = "" - var feedlyClientId = "" - var feedlyClientSecret = "" - var inoreaderAppId = "" - var inoreaderAppKey = "" -} diff --git a/Account/Tests/AccountTests/Feedly/FeedlyTestSupport.swift b/Account/Tests/AccountTests/Feedly/FeedlyTestSupport.swift index 4a7b898a2..b722ba032 100644 --- a/Account/Tests/AccountTests/Feedly/FeedlyTestSupport.swift +++ b/Account/Tests/AccountTests/Feedly/FeedlyTestSupport.swift @@ -19,10 +19,6 @@ class FeedlyTestSupport { var refreshToken = Credentials(type: .oauthRefreshToken, username: "Test", secret: "t3st-refresh-tok3n") var transport = TestTransport() - init() { - SecretsManager.provider = FeedlyTestSecrets() - } - func makeMockNetworkStack() -> (TestTransport, FeedlyAPICaller) { let caller = FeedlyAPICaller(transport: transport, api: .sandbox) caller.credentials = accessToken diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index f5bfc38c3..150affc05 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -117,7 +117,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, crashReporter.enable() #endif - SecretsManager.provider = Secrets() AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!) ArticleThemesManager.shared = ArticleThemesManager(folderPath: Platform.dataSubfolder(forApplication: nil, folderName: "Themes")!) diff --git a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift index 9d8a4788c..66c23e02d 100644 --- a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift @@ -97,7 +97,7 @@ class AccountsNewsBlurWindowController: NSWindowController { do { try self.account?.removeCredentials(type: .newsBlurBasic) - try self.account?.removeCredentials(type: .newsBlurSessionId) + try self.account?.removeCredentials(type: .newsBlurSessionID) try self.account?.storeCredentials(credentials) try self.account?.storeCredentials(validatedCredentials) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 4e922b5d9..0b0ceb953 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -396,7 +396,6 @@ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( Resources/NewsFax.nnwtheme, - Secrets.swift.gyb, ShareExtension/SafariExt.js, ShareExtension/ShareDefaultContainer.swift, Widget/WidgetData.swift, @@ -413,7 +412,6 @@ ExtensionPoints/SendToMarsEditCommand.swift, ExtensionPoints/SendToMicroBlogCommand.swift, "Extensions/NSView-Extensions.swift", - Secrets.swift.gyb, ShareExtension/SafariExt.js, ShareExtension/ShareDefaultContainer.swift, SmartFeeds/SmartFeedPasteboardWriter.swift, diff --git a/Package.swift b/Package.swift new file mode 100644 index 000000000..2d92a462b --- /dev/null +++ b/Package.swift @@ -0,0 +1,24 @@ +// swift-tools-version: 5.10 +import PackageDescription + +let package = Package( + name: "Secrets", + platforms: [.macOS(.v14), .iOS(.v17)], + products: [ + .library( + name: "Secrets", + targets: ["Secrets"] + ) + ], + dependencies: [], + targets: [ + .target( + name: "Secrets", + dependencies: [], + exclude: ["SecretKey.swift.gyb"], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency") + ] + ) + ] +) diff --git a/Secrets/Package.swift b/Secrets/Package.swift index da31d80c3..655aa78b9 100644 --- a/Secrets/Package.swift +++ b/Secrets/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.10 +// swift-tools-version:6.0 import PackageDescription @@ -17,7 +17,7 @@ let package = Package( .target( name: "Secrets", dependencies: [], - swiftSettings: [.unsafeFlags(["-warnings-as-errors"])] + exclude: ["SecretKey.swift.gyb"] ) ] ) diff --git a/Secrets/Sources/Secrets/Credentials.swift b/Secrets/Sources/Secrets/Credentials.swift index e7d0f0262..47a746e4a 100644 --- a/Secrets/Sources/Secrets/Credentials.swift +++ b/Secrets/Sources/Secrets/Credentials.swift @@ -8,15 +8,15 @@ import Foundation -public enum CredentialsError: Error { +public enum CredentialsError: Error, Sendable { case incompleteCredentials case unhandledError(status: OSStatus) } -public enum CredentialsType: String { +public enum CredentialsType: String, Sendable { case basic = "password" case newsBlurBasic = "newsBlurBasic" - case newsBlurSessionId = "newsBlurSessionId" + case newsBlurSessionID = "newsBlurSessionId" case readerBasic = "readerBasic" case readerAPIKey = "readerAPIKey" case oauthAccessToken = "oauthAccessToken" @@ -24,7 +24,7 @@ public enum CredentialsType: String { case oauthRefreshToken = "oauthRefreshToken" } -public struct Credentials: Equatable { +public struct Credentials: Equatable, Sendable { public let type: CredentialsType public let username: String public let secret: String diff --git a/Secrets/Sources/Secrets/CredentialsManager.swift b/Secrets/Sources/Secrets/CredentialsManager.swift index 1043d9f6d..9fa6d14d9 100644 --- a/Secrets/Sources/Secrets/CredentialsManager.swift +++ b/Secrets/Sources/Secrets/CredentialsManager.swift @@ -10,7 +10,7 @@ import Foundation public struct CredentialsManager { - private static var keychainGroup: String? = { + private static let keychainGroup: String? = { guard let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String else { return nil } diff --git a/Shared/Secrets.swift.gyb b/Secrets/Sources/Secrets/SecretKey.swift.gyb similarity index 50% rename from Shared/Secrets.swift.gyb rename to Secrets/Sources/Secrets/SecretKey.swift.gyb index aa17c57e7..ee54b024f 100644 --- a/Shared/Secrets.swift.gyb +++ b/Secrets/Sources/Secrets/SecretKey.swift.gyb @@ -1,4 +1,4 @@ -// Generated by Secrets.swift.gyb +// Generated by SecretKey.swift.gyb %{ import os @@ -13,40 +13,37 @@ def encode(string, salt): def snake_to_camel(snake_str): components = snake_str.split('_') - return components[0].lower() + ''.join(x.title() for x in components[1:]) + components = [components[0].lower()] + [x.title() if x != 'ID' else x for x in components[1:]] + camel_case_str = ''.join(components) + return camel_case_str salt = [byte for byte in os.urandom(64)] }% -import Secrets +import Foundation -public struct Secrets: SecretsProvider { +public struct SecretKey { % for secret in secrets: - public var ${snake_to_camel(secret)}: String { + public static let ${snake_to_camel(secret)}: String = { let encoded: [UInt8] = [ % for chunk in chunks(encode(os.environ.get(secret) or "", salt), 8): ${"".join(["0x%02x, " % byte for byte in chunk])} % end ] - return decode(encoded, salt: salt) - } + return decode(encoded) + }() % end - - %{ - # custom example: static let myVariable = "${os.environ.get('MY_CUSTOM_VARIABLE')}" - }% - - private let salt: [UInt8] = [ - % for chunk in chunks(salt, 8): - ${"".join(["0x%02x, " % byte for byte in chunk])} - % end - ] - - private func decode(_ encoded: [UInt8], salt: [UInt8]) -> String { - String(decoding: encoded.enumerated().map { (offset, element) in - element ^ salt[offset % salt.count] - }, as: UTF8.self) - } - +} + +private let salt: [UInt8] = [ +% for chunk in chunks(salt, 8): + ${"".join(["0x%02x, " % byte for byte in chunk])} +% end +] + +private func decode(_ encoded: [UInt8]) -> String { + String(decoding: encoded.enumerated().map { (offset, element) in + element ^ salt[offset % salt.count] + }, as: UTF8.self) } diff --git a/Secrets/Sources/Secrets/SecretsManager.swift b/Secrets/Sources/Secrets/SecretsManager.swift deleted file mode 100644 index c53f5615f..000000000 --- a/Secrets/Sources/Secrets/SecretsManager.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// SecretsManager.swift -// -// -// Created by Maurice Parker on 7/30/20. -// - -import Foundation - -public class SecretsManager { - public static var provider: SecretsProvider! -} diff --git a/Secrets/Sources/Secrets/SecretsProvider.swift b/Secrets/Sources/Secrets/SecretsProvider.swift deleted file mode 100644 index 26ed84410..000000000 --- a/Secrets/Sources/Secrets/SecretsProvider.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// SecretsProvider.swift -// -// -// Created by Maurice Parker on 7/30/20. -// - -import Foundation - -public protocol SecretsProvider { - var mercuryClientId: String { get } - var mercuryClientSecret: String { get } - var feedlyClientId: String { get } - var feedlyClientSecret: String { get } - var inoreaderAppId: String { get } - var inoreaderAppKey: String { get } -} diff --git a/Shared/Article Extractor/ArticleExtractor.swift b/Shared/Article Extractor/ArticleExtractor.swift index efff97cdc..0eb09bc5f 100644 --- a/Shared/Article Extractor/ArticleExtractor.swift +++ b/Shared/Article Extractor/ArticleExtractor.swift @@ -38,8 +38,8 @@ class ArticleExtractor { self.articleLink = articleLink let clientURL = "https://extract.feedbin.com/parser" - let username = SecretsManager.provider.mercuryClientId - let signature = articleLink.hmacUsingSHA1(key: SecretsManager.provider.mercuryClientSecret) + let username = SecretKey.mercuryClientID + let signature = articleLink.hmacUsingSHA1(key: SecretKey.mercuryClientSecret) if let base64URL = articleLink.data(using: .utf8)?.base64EncodedString() { let fullURL = "\(clientURL)/\(username)/\(signature)?base64_url=\(base64URL)"