mirror of
https://github.com/sudoxnym/audiobookshelf-atv.git
synced 2026-05-19 20:28:28 +00:00
116 lines
No EOL
4.1 KiB
Swift
116 lines
No EOL
4.1 KiB
Swift
//
|
|
// SecureStorage.swift
|
|
// App
|
|
//
|
|
// Created by advplyr on 2025-07-05
|
|
//
|
|
|
|
import Foundation
|
|
import Security
|
|
|
|
class SecureStorage {
|
|
private static let tag = "SecureStorage"
|
|
private static let serviceName = "AudiobookshelfRefreshTokens"
|
|
|
|
/**
|
|
* Encrypts and stores a refresh token for a specific server connection
|
|
*/
|
|
func storeRefreshToken(serverConnectionConfigId: String, refreshToken: String) -> Bool {
|
|
let key = "refresh_token_\(serverConnectionConfigId)"
|
|
|
|
guard let data = refreshToken.data(using: .utf8) else {
|
|
print("\(Self.tag): Failed to convert refresh token to data")
|
|
return false
|
|
}
|
|
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: Self.serviceName,
|
|
kSecAttrAccount as String: key,
|
|
kSecValueData as String: data,
|
|
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
|
|
]
|
|
|
|
// First, try to delete any existing item
|
|
SecItemDelete(query as CFDictionary)
|
|
|
|
// Then add the new item
|
|
let status = SecItemAdd(query as CFDictionary, nil)
|
|
|
|
if status == errSecSuccess {
|
|
print("\(Self.tag): Successfully stored encrypted refresh token for server: \(serverConnectionConfigId)")
|
|
return true
|
|
} else {
|
|
print("\(Self.tag): Failed to store refresh token for server: \(serverConnectionConfigId), status: \(status)")
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves and decrypts a refresh token for a specific server connection
|
|
*/
|
|
func getRefreshToken(serverConnectionConfigId: String) -> String? {
|
|
let key = "refresh_token_\(serverConnectionConfigId)"
|
|
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: Self.serviceName,
|
|
kSecAttrAccount as String: key,
|
|
kSecReturnData as String: true,
|
|
kSecMatchLimit as String: kSecMatchLimitOne
|
|
]
|
|
|
|
var result: AnyObject?
|
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
|
|
if status == errSecSuccess,
|
|
let data = result as? Data,
|
|
let refreshToken = String(data: data, encoding: .utf8) {
|
|
return refreshToken
|
|
} else {
|
|
print("\(Self.tag): Failed to retrieve refresh token for server: \(serverConnectionConfigId), status: \(status)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes a refresh token for a specific server connection
|
|
*/
|
|
func removeRefreshToken(serverConnectionConfigId: String) -> Bool {
|
|
let key = "refresh_token_\(serverConnectionConfigId)"
|
|
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: Self.serviceName,
|
|
kSecAttrAccount as String: key
|
|
]
|
|
|
|
let status = SecItemDelete(query as CFDictionary)
|
|
|
|
if status == errSecSuccess || status == errSecItemNotFound {
|
|
print("\(Self.tag): Successfully removed refresh token for server: \(serverConnectionConfigId)")
|
|
return true
|
|
} else {
|
|
print("\(Self.tag): Failed to remove refresh token for server: \(serverConnectionConfigId), status: \(status)")
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if a refresh token exists for a specific server connection
|
|
*/
|
|
func hasRefreshToken(serverConnectionConfigId: String) -> Bool {
|
|
let key = "refresh_token_\(serverConnectionConfigId)"
|
|
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: Self.serviceName,
|
|
kSecAttrAccount as String: key,
|
|
kSecReturnData as String: false,
|
|
kSecMatchLimit as String: kSecMatchLimitOne
|
|
]
|
|
|
|
let status = SecItemCopyMatching(query as CFDictionary, nil)
|
|
return status == errSecSuccess
|
|
}
|
|
} |