Simplify Secrets module — get rid of protocol. Just reference SecretKey properties as static.

This commit is contained in:
Brent Simmons 2025-01-06 21:02:03 -08:00
parent 1eacebe546
commit 45cd018020
19 changed files with 64 additions and 95 deletions

2
.gitignore vendored
View File

@ -74,4 +74,6 @@ fastlane/test_output
/Frameworks/Secrets/Secrets.swift /Frameworks/Secrets/Secrets.swift
Secrets/Sources/Secrets/Secrets.swift Secrets/Sources/Secrets/Secrets.swift
*.py[cod] *.py[cod]
/Secrets/Sources/Secrets/SecretKey.swift
/Modules/Secrets/Sources/Secrets/SecretKey.swift /Modules/Secrets/Sources/Secrets/SecretKey.swift

View File

@ -15,10 +15,10 @@ extension OAuthAuthorizationClient {
/// Models private NetNewsWire client secrets. /// Models private NetNewsWire client secrets.
/// These placeholders are substituted at build time using a Run Script phase with build settings. /// 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 /// 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", redirectUri: "netnewswire://auth/feedly",
state: nil, state: nil,
secret: SecretsManager.provider.feedlyClientSecret) secret: SecretKey.feedlyClientSecret)
} }
static var feedlySandboxClient: OAuthAuthorizationClient { static var feedlySandboxClient: OAuthAuthorizationClient {

View File

@ -56,7 +56,7 @@ final class NewsBlurAPICaller: NSObject {
let cookies = HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url) let cookies = HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url)
for cookie in cookies where cookie.name == Self.SessionIdCookie { 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)) completion(.success(credentials))
return return
} }

View File

@ -609,7 +609,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
} }
func accountDidInitialize(_ account: Account) { func accountDidInitialize(_ account: Account) {
credentials = try? account.retrieveCredentials(type: .newsBlurSessionId) credentials = try? account.retrieveCredentials(type: .newsBlurSessionID)
} }
func accountWillBeDeleted(_ account: Account) { func accountWillBeDeleted(_ account: Account) {

View File

@ -693,8 +693,8 @@ private extension ReaderAPICaller {
func addVariantHeaders(_ request: inout URLRequest) { func addVariantHeaders(_ request: inout URLRequest) {
if variant == .inoreader { if variant == .inoreader {
request.addValue(SecretsManager.provider.inoreaderAppId, forHTTPHeaderField: "AppId") request.addValue(SecretKey.inoreaderAppID, forHTTPHeaderField: "AppId")
request.addValue(SecretsManager.provider.inoreaderAppKey, forHTTPHeaderField: "AppKey") request.addValue(SecretKey.inoreaderAppKey, forHTTPHeaderField: "AppKey")
} }
} }

View File

@ -35,7 +35,7 @@ public extension URLRequest {
URLQueryItem(name: "password", value: credentials.secret), URLQueryItem(name: "password", value: credentials.secret),
] ]
httpBody = postData.enhancedPercentEncodedQuery?.data(using: .utf8) httpBody = postData.enhancedPercentEncodedQuery?.data(using: .utf8)
case .newsBlurSessionId: case .newsBlurSessionID:
setValue("\(NewsBlurAPICaller.SessionIdCookie)=\(credentials.secret)", forHTTPHeaderField: "Cookie") setValue("\(NewsBlurAPICaller.SessionIdCookie)=\(credentials.secret)", forHTTPHeaderField: "Cookie")
httpShouldHandleCookies = true httpShouldHandleCookies = true
case .readerBasic: case .readerBasic:

View File

@ -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 = ""
}

View File

@ -19,10 +19,6 @@ class FeedlyTestSupport {
var refreshToken = Credentials(type: .oauthRefreshToken, username: "Test", secret: "t3st-refresh-tok3n") var refreshToken = Credentials(type: .oauthRefreshToken, username: "Test", secret: "t3st-refresh-tok3n")
var transport = TestTransport() var transport = TestTransport()
init() {
SecretsManager.provider = FeedlyTestSecrets()
}
func makeMockNetworkStack() -> (TestTransport, FeedlyAPICaller) { func makeMockNetworkStack() -> (TestTransport, FeedlyAPICaller) {
let caller = FeedlyAPICaller(transport: transport, api: .sandbox) let caller = FeedlyAPICaller(transport: transport, api: .sandbox)
caller.credentials = accessToken caller.credentials = accessToken

View File

@ -117,7 +117,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
crashReporter.enable() crashReporter.enable()
#endif #endif
SecretsManager.provider = Secrets()
AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!) AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!)
ArticleThemesManager.shared = ArticleThemesManager(folderPath: Platform.dataSubfolder(forApplication: nil, folderName: "Themes")!) ArticleThemesManager.shared = ArticleThemesManager(folderPath: Platform.dataSubfolder(forApplication: nil, folderName: "Themes")!)

View File

@ -97,7 +97,7 @@ class AccountsNewsBlurWindowController: NSWindowController {
do { do {
try self.account?.removeCredentials(type: .newsBlurBasic) 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(credentials)
try self.account?.storeCredentials(validatedCredentials) try self.account?.storeCredentials(validatedCredentials)

View File

@ -396,7 +396,6 @@
isa = PBXFileSystemSynchronizedBuildFileExceptionSet; isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = ( membershipExceptions = (
Resources/NewsFax.nnwtheme, Resources/NewsFax.nnwtheme,
Secrets.swift.gyb,
ShareExtension/SafariExt.js, ShareExtension/SafariExt.js,
ShareExtension/ShareDefaultContainer.swift, ShareExtension/ShareDefaultContainer.swift,
Widget/WidgetData.swift, Widget/WidgetData.swift,
@ -413,7 +412,6 @@
ExtensionPoints/SendToMarsEditCommand.swift, ExtensionPoints/SendToMarsEditCommand.swift,
ExtensionPoints/SendToMicroBlogCommand.swift, ExtensionPoints/SendToMicroBlogCommand.swift,
"Extensions/NSView-Extensions.swift", "Extensions/NSView-Extensions.swift",
Secrets.swift.gyb,
ShareExtension/SafariExt.js, ShareExtension/SafariExt.js,
ShareExtension/ShareDefaultContainer.swift, ShareExtension/ShareDefaultContainer.swift,
SmartFeeds/SmartFeedPasteboardWriter.swift, SmartFeeds/SmartFeedPasteboardWriter.swift,

24
Package.swift Normal file
View File

@ -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")
]
)
]
)

View File

@ -1,4 +1,4 @@
// swift-tools-version:5.10 // swift-tools-version:6.0
import PackageDescription import PackageDescription
@ -17,7 +17,7 @@ let package = Package(
.target( .target(
name: "Secrets", name: "Secrets",
dependencies: [], dependencies: [],
swiftSettings: [.unsafeFlags(["-warnings-as-errors"])] exclude: ["SecretKey.swift.gyb"]
) )
] ]
) )

View File

@ -8,15 +8,15 @@
import Foundation import Foundation
public enum CredentialsError: Error { public enum CredentialsError: Error, Sendable {
case incompleteCredentials case incompleteCredentials
case unhandledError(status: OSStatus) case unhandledError(status: OSStatus)
} }
public enum CredentialsType: String { public enum CredentialsType: String, Sendable {
case basic = "password" case basic = "password"
case newsBlurBasic = "newsBlurBasic" case newsBlurBasic = "newsBlurBasic"
case newsBlurSessionId = "newsBlurSessionId" case newsBlurSessionID = "newsBlurSessionId"
case readerBasic = "readerBasic" case readerBasic = "readerBasic"
case readerAPIKey = "readerAPIKey" case readerAPIKey = "readerAPIKey"
case oauthAccessToken = "oauthAccessToken" case oauthAccessToken = "oauthAccessToken"
@ -24,7 +24,7 @@ public enum CredentialsType: String {
case oauthRefreshToken = "oauthRefreshToken" case oauthRefreshToken = "oauthRefreshToken"
} }
public struct Credentials: Equatable { public struct Credentials: Equatable, Sendable {
public let type: CredentialsType public let type: CredentialsType
public let username: String public let username: String
public let secret: String public let secret: String

View File

@ -10,7 +10,7 @@ import Foundation
public struct CredentialsManager { public struct CredentialsManager {
private static var keychainGroup: String? = { private static let keychainGroup: String? = {
guard let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String else { guard let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String else {
return nil return nil
} }

View File

@ -1,4 +1,4 @@
// Generated by Secrets.swift.gyb // Generated by SecretKey.swift.gyb
%{ %{
import os import os
@ -13,40 +13,37 @@ def encode(string, salt):
def snake_to_camel(snake_str): def snake_to_camel(snake_str):
components = snake_str.split('_') 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)] salt = [byte for byte in os.urandom(64)]
}% }%
import Secrets import Foundation
public struct Secrets: SecretsProvider { public struct SecretKey {
% for secret in secrets: % for secret in secrets:
public var ${snake_to_camel(secret)}: String { public static let ${snake_to_camel(secret)}: String = {
let encoded: [UInt8] = [ let encoded: [UInt8] = [
% for chunk in chunks(encode(os.environ.get(secret) or "", salt), 8): % for chunk in chunks(encode(os.environ.get(secret) or "", salt), 8):
${"".join(["0x%02x, " % byte for byte in chunk])} ${"".join(["0x%02x, " % byte for byte in chunk])}
% end % end
] ]
return decode(encoded, salt: salt) return decode(encoded)
} }()
% end % end
}
%{ private let salt: [UInt8] = [
# custom example: static let myVariable = "${os.environ.get('MY_CUSTOM_VARIABLE')}" % for chunk in chunks(salt, 8):
}%
private let salt: [UInt8] = [
% for chunk in chunks(salt, 8):
${"".join(["0x%02x, " % byte for byte in chunk])} ${"".join(["0x%02x, " % byte for byte in chunk])}
% end % end
] ]
private func decode(_ encoded: [UInt8], salt: [UInt8]) -> String { private func decode(_ encoded: [UInt8]) -> String {
String(decoding: encoded.enumerated().map { (offset, element) in String(decoding: encoded.enumerated().map { (offset, element) in
element ^ salt[offset % salt.count] element ^ salt[offset % salt.count]
}, as: UTF8.self) }, as: UTF8.self)
}
} }

View File

@ -1,12 +0,0 @@
//
// SecretsManager.swift
//
//
// Created by Maurice Parker on 7/30/20.
//
import Foundation
public class SecretsManager {
public static var provider: SecretsProvider!
}

View File

@ -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 }
}

View File

@ -38,8 +38,8 @@ class ArticleExtractor {
self.articleLink = articleLink self.articleLink = articleLink
let clientURL = "https://extract.feedbin.com/parser" let clientURL = "https://extract.feedbin.com/parser"
let username = SecretsManager.provider.mercuryClientId let username = SecretKey.mercuryClientID
let signature = articleLink.hmacUsingSHA1(key: SecretsManager.provider.mercuryClientSecret) let signature = articleLink.hmacUsingSHA1(key: SecretKey.mercuryClientSecret)
if let base64URL = articleLink.data(using: .utf8)?.base64EncodedString() { if let base64URL = articleLink.data(using: .utf8)?.base64EncodedString() {
let fullURL = "\(clientURL)/\(username)/\(signature)?base64_url=\(base64URL)" let fullURL = "\(clientURL)/\(username)/\(signature)?base64_url=\(base64URL)"