mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-26 05:14:09 +02:00
Add SecureStorage to iOS and implement refresh token methods
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user