Move modules to Modules folder.

This commit is contained in:
Brent Simmons
2025-01-06 21:13:56 -08:00
parent 430871c94a
commit 2933d9aca0
463 changed files with 2 additions and 20 deletions

5
Modules/Secrets/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/

View File

@@ -0,0 +1,23 @@
// swift-tools-version:6.0
import PackageDescription
let package = Package(
name: "Secrets",
platforms: [.macOS(.v14), .iOS(.v17)],
products: [
.library(
name: "Secrets",
type: .dynamic,
targets: ["Secrets"]
)
],
dependencies: [],
targets: [
.target(
name: "Secrets",
dependencies: [],
exclude: ["SecretKey.swift.gyb"]
)
]
)

View File

@@ -0,0 +1,3 @@
# Secrets
A description of this package.

View File

@@ -0,0 +1,37 @@
//
// Credentials.swift
// NetNewsWire
//
// Created by Brent Simmons on 12/9/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
public enum CredentialsError: Error, Sendable {
case incompleteCredentials
case unhandledError(status: OSStatus)
}
public enum CredentialsType: String, Sendable {
case basic = "password"
case newsBlurBasic = "newsBlurBasic"
case newsBlurSessionID = "newsBlurSessionId"
case readerBasic = "readerBasic"
case readerAPIKey = "readerAPIKey"
case oauthAccessToken = "oauthAccessToken"
case oauthAccessTokenSecret = "oauthAccessTokenSecret"
case oauthRefreshToken = "oauthRefreshToken"
}
public struct Credentials: Equatable, Sendable {
public let type: CredentialsType
public let username: String
public let secret: String
public init(type: CredentialsType, username: String, secret: String) {
self.type = type
self.username = username
self.secret = secret
}
}

View File

@@ -0,0 +1,124 @@
//
// CredentialsManager.swift
// NetNewsWire
//
// Created by Maurice Parker on 5/5/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import Foundation
public struct CredentialsManager {
private static let keychainGroup: String? = {
guard let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String else {
return nil
}
let appIdentifierPrefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String
let appGroupSuffix = appGroup.suffix(appGroup.count - 6)
return "\(appIdentifierPrefix)\(appGroupSuffix)"
}()
public static func storeCredentials(_ credentials: Credentials, server: String) throws {
var query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
kSecAttrAccount as String: credentials.username,
kSecAttrServer as String: server]
if credentials.type != .basic {
query[kSecAttrSecurityDomain as String] = credentials.type.rawValue
}
if let securityGroup = keychainGroup {
query[kSecAttrAccessGroup as String] = securityGroup
}
let secretData = credentials.secret.data(using: String.Encoding.utf8)!
query[kSecValueData as String] = secretData
let status = SecItemAdd(query as CFDictionary, nil)
switch status {
case errSecSuccess:
return
case errSecDuplicateItem:
break
default:
throw CredentialsError.unhandledError(status: status)
}
var deleteQuery = query
deleteQuery.removeValue(forKey: kSecAttrAccessible as String)
SecItemDelete(deleteQuery as CFDictionary)
let addStatus = SecItemAdd(query as CFDictionary, nil)
if addStatus != errSecSuccess {
throw CredentialsError.unhandledError(status: status)
}
}
public static func retrieveCredentials(type: CredentialsType, server: String, username: String) throws -> Credentials? {
var query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: username,
kSecAttrServer as String: server,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnAttributes as String: true,
kSecReturnData as String: true]
if type != .basic {
query[kSecAttrSecurityDomain as String] = type.rawValue
}
if let securityGroup = keychainGroup {
query[kSecAttrAccessGroup as String] = securityGroup
}
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status != errSecItemNotFound else {
return nil
}
guard status == errSecSuccess else {
throw CredentialsError.unhandledError(status: status)
}
guard let existingItem = item as? [String : Any],
let secretData = existingItem[kSecValueData as String] as? Data,
let secret = String(data: secretData, encoding: String.Encoding.utf8) else {
return nil
}
return Credentials(type: type, username: username, secret: secret)
}
public static func removeCredentials(type: CredentialsType, server: String, username: String) throws {
var query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: username,
kSecAttrServer as String: server,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnAttributes as String: true,
kSecReturnData as String: true]
if type != .basic {
query[kSecAttrSecurityDomain as String] = type.rawValue
}
if let securityGroup = keychainGroup {
query[kSecAttrAccessGroup as String] = securityGroup
}
let status = SecItemDelete(query as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound else {
throw CredentialsError.unhandledError(status: status)
}
}
}

View File

@@ -0,0 +1,49 @@
// Generated by SecretKey.swift.gyb
%{
import os
secrets = ['MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'INOREADER_APP_ID', 'INOREADER_APP_KEY']
def chunks(seq, size):
return (seq[i:(i + size)] for i in range(0, len(seq), size))
def encode(string, salt):
bytes_ = string.encode("UTF-8")
return [bytes_[i] ^ salt[i % len(salt)] for i in range(0, len(bytes_))]
def snake_to_camel(snake_str):
components = snake_str.split('_')
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 Foundation
public struct SecretKey {
% for secret in secrets:
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)
}()
% end
}
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)
}