Coder Social home page Coder Social logo

alexruperez / securepropertystorage Goto Github PK

View Code? Open in Web Editor NEW
470.0 14.0 21.0 30.72 MB

Helps you define secure storages for your properties using Swift property wrappers.

Home Page: https://alexruperez.github.io/SecurePropertyStorage

License: MIT License

Swift 99.28% Shell 0.72%
swift property-wrappers userdefaults keychain singleton dependency-injection

securepropertystorage's Introduction

๐Ÿ” Secure Property Storage

Helps you define secure storages for your properties using Swift property wrappers.

Twitter Swift License Swift Package Manager Carthage Bitrise Maintainability Test Coverage codecov codebeat badge Quality Documentation

๐ŸŒŸ Features

All keys are hashed using SHA512 and all values are encrypted using AES-GCM to keep user information safe, automagically. Symmetric key is stored in Keychain in a totally secure way.

๐Ÿ’ Basic usage

@UserDefault

This property wrapper will store your property in UserDefaults using StoreKey (any String but i recommend you a String typed enum). Optionally, you can assign a default value to the property that will be secure stored at initialization.

@UserDefault(<#StoreKey#>)
var yourProperty: YourType? = yourDefaultValueIfNeeded

UserDefaultsStorage is also available, a subclass of UserDefaults with all the security provided by this library, where you can customize suite name.

@Keychain

This property wrapper will store your property in Keychain using StoreKey.

@Keychain(<#StoreKey#>)
var yourProperty: YourType? = yourDefaultValueIfNeeded

As UserDefaultsStorage, KeychainStorage is also available, where you can customize access, group and synchronize it with iCloud.

@Singleton

This property wrapper will store your property in a memory singleton, every property with the same wrapper and key can access or modify the value from wherever it is.

@Singleton(<#StoreKey#>)
var yourProperty: YourType? = yourDefaultValueIfNeeded

As KeychainStorage, SingletonStorage is also available.

@Inject

This property wrapper is similar to @Singleton but, together with @Register, will inject your dependencies. More details in Dependency Injection usage guide.

@Inject
var yourDependency: YourProtocol?

As SingletonStorage, InjectStorage is also available.

@Store

This is a custom wrapper, you can define your own Storage protocol implementation.

@Store(<#YourStorage#>, <#StoreKey#>)
var yourProperty: YourType? = yourDefaultValueIfNeeded

As InjectStorage, DelegatedStorage is also available with all the magic of this library.

๐Ÿง™โ€โ™‚๏ธ Codable usage

If your property conforms Codable protocol, just add Codable keyword as prefix of your property wrapper.

  • @CodableUserDefault
  • @CodableKeychain
  • @CodableSingleton
  • @CodableStore

๐Ÿฅก Unwrapped usage

To avoid continually unwrapping your property, just add Unwrapped keyword as prefix of your property wrapper, assign a default value (mandatory except for @UnwrappedInject), and it will return stored value or default value, but your property will always be there for you.

  • @UnwrappedUserDefault
  • @UnwrappedKeychain
  • @UnwrappedSingleton
  • @UnwrappedInject
  • @UnwrappedStore

๐Ÿฅก + ๐Ÿง™โ€โ™‚๏ธ Combo usage

You can also combine previous cases in case you need it, unwrapped first please.

  • @UnwrappedCodableUserDefault
  • @UnwrappedCodableKeychain
  • @UnwrappedCodableSingleton
  • @UnwrappedCodableStore

๐Ÿ’‰ Dependency Injection usage

@Register (click to expand)

This property wrapper will register the implementations of your dependencies. Register them wherever you want before inject it, but be sure to do it only once (except if you use qualifiers), for example, in an Injector class. You can register through a protocol or directly using your class implementation.

@Register
var yourDependency: YourProtocol = YourImplementation()

@Register
var yourDependency = YourImplementation()

You can also define a closure that builds your dependency. Just remember cast your dependency if you are going to inject it through a protocol.

@Register
var yourDependency = {
    YourImplementation() as YourProtocol
}

@Register
var yourDependency = {
    YourImplementation()
}

You can also register your dependencies only after the moment someone tries to inject them and you haven't registered them yet, for this you can use the error closure.

InjectStorage.standard.errorClosure = { error in
    if case InjectError.notFound = error {
        YourImplementation.register()
    }
}

You can get this syntactic sugar because you can now use property wrappers in function parameters.

static func register(@Register yourDependency: YourProtocol = YourImplementation()) {}
@Inject and @UnwrappedInject (click to expand)

These property wrappers injects your dependencies @Register implementations.

@Inject
var yourDependency: YourProtocol?

@Inject
var yourDependency: YourImplementation?

@UnwrappedInject
var yourUnwrappedDependency: YourProtocol

@UnwrappedInject
var yourUnwrappedDependency: YourImplementation

Scope

Because these property wrappers works similarly to @Singleton, the default scope is .singleton, but if you use builder closures on @Register, you can modify them to inject a single instance.

@Inject(.instance)
var yourDependency: YourProtocol?

@UnwrappedInject(.instance)
var yourUnwrappedDependency: YourProtocol
@InjectWith and @UnwrappedInjectWith (click to expand)

Your dependency may need parameters when injecting, you can pass them with these property wrappers. Simply define a model with your dependency parameters and pass it. It will inject a new instance built with these parameters.

@Register
var yourDependency = { parameters in
    YourImplementation(parameters) as YourProtocol
}

@Inject(YourParameters())
var yourDependency: YourProtocol?

@UnwrappedInject(YourParameters())
var yourUnwrappedDependency: YourProtocol
Qualifiers (click to expand)

You can use qualifiers to provide various implementations of a particular dependency. A qualifier is just a @objc protocol that you apply to a class.

For example, you could declare Dog and Cat qualifier protocols and apply it to another class that conforms Animal protocol. To declare this qualifier, use the following code:

protocol Animal {
  func sound()
}

@objc protocol Dog {}

@objc protocol Cat {}

You can then define multiple classes that conforms Animal protocol and uses this qualifiers:

class DogImplementation: Animal, Dog {
    func sound() { print("Woof!") }
}

class CatImplementation: Animal, Cat {
    func sound() { print("Meow!") }
}

Both implementations of the class can now be @Register:

@Register
var registerDog: Animal = DogImplementation()

@Register
var registerCat: Animal = CatImplementation()

To inject one or the other implementation, simply add the qualifier(s) to your @Inject:

@UnwrappedInject(Dog.self)
var dog: Animal

@UnwrappedInject(Cat.self)
var cat: Animal

dog.sound() // prints Woof!
cat.sound() // prints Meow!
Testing (click to expand)

One of the advantages of dependency injection is that the code can be easily testable with mock implementation. That is why there is a Mock qualifier that has priority over all, so you can have your dependencies defined in the app and create your mock in the test target simply by adding this qualifier.

// App target

class YourImplementation: YourProtocol {}

@Register
var yourDependency: YourProtocol = YourImplementation()

@Inject
var yourDependency: YourProtocol?
// Test target

class YourMock: YourProtocol, Mock {}

@Register
var yourDependency: YourProtocol = YourMock()
Groups (click to expand)

When you have a lot of dependencies in your app, you may want to optimize dependency resolution.

You can group them using @Register(group:) and a DependencyGroupKey:

@Register(group: <#DependencyGroupKey#>)
var yourDependency: YourProtocol = YourImplementation()

@Inject(group:) will look for those dependencies only in that group:

@Inject(group: <#DependencyGroupKey#>)
var yourDependency: YourProtocol?

๐Ÿ‘€ Examples

Talk is cheap. Show me the code.

    // Securely stored in UserDefaults.
    @UserDefault("username")
    var username: String?

    // Securely stored in Keychain.
    @Keychain("password")
    var password: String?

    // Securely stored in a Singleton storage.
    @Singleton("sessionToken")
    var sessionToken: String?

    // Securely stored in a Singleton storage.
    // Always has a value, the stored or the default.
    @UnwrappedSingleton("refreshToken")
    var refreshToken: String = "B0610306-A33F"

    struct User: Codable {
        let username: String
        let password: String?
        let sessionToken: String?
    }

    // Codable model securely stored in UserDefaults.
    @CodableUserDefault("user")
    var user: User?

๐Ÿ›  Compatibility

  • macOS 10.15+
  • iOS 13.0+
  • iPadOS 13.0+
  • tvOS 13.0+
  • watchOS 6.0+
  • visionOS 1.0+

โš™๏ธ Installation

You can use the Swift Package Manager by declaring SecurePropertyStorage as a dependency in your Package.swift file:

.package(url: "https://github.com/alexruperez/SecurePropertyStorage", from: "0.7.1")

By default, all property wrappers are installed and you can import them, but if you want, you can install only some of them:

  • UserDefault: @*UserDefault property wrappers.
  • Keychain: @*Keychain property wrappers.
  • Singleton: @*Singleton property wrappers.
  • Storage: @*Store property wrappers.
  • Inject: @*Inject property wrappers.

For more information, see the Swift Package Manager documentation.

Or you can use Carthage:

github "alexruperez/SecurePropertyStorage"

๐Ÿป Etc.

  • Featured in Dave Verwer's iOS Dev Weekly - Issue 450, thanks Dave!
  • Contributions are very welcome. Thanks Alberto Garcia and Chen!
  • Attribution is appreciated (let's spread the word!), but not mandatory.

๐Ÿ‘จโ€๐Ÿ’ป Author

Alex Rupรฉrez โ€“ @alexruperez โ€“ [email protected]

๐Ÿ‘ฎโ€โ™‚๏ธ License

SecurePropertyStorage is available under the MIT license. See the LICENSE file for more info.

securepropertystorage's People

Contributors

alexruperez avatar nuomi1 avatar qchenqizhi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

securepropertystorage's Issues

Values not being saved to keychain in release 0.7.0

Starting with version 0.7.0 I see this message printed in the console:
Unable to store item: The specified item already exists in the keychain.

When I try to retrieve an item from the keychain, I get nil. I reverted to 0.6.0 and it's all working again as expected.

Support for Cocoapods?

I'd love to use SPM but we have other dependencies that aren't available there yet, and using multiple managers in the same project isn't a road I'd like to go down. Can you release there as well?

Support suite for @UserDefault

Is your feature request related to a problem? Please describe.
I want to be able to share user defaults data between bundles inside the same app group (i.e App and Widget)

Describe the solution you'd like
The best could be to have an additional field in @userdefault property wrapper to specify the app name and if not provided, use the standard value

Allow register dependencies on demand.

Is your feature request related to a problem? Please describe.
SecurePropertyStorage is designed to register all dependencies at once,
in application(_:didFinishLaunchingWithOptions:) for example.

Describe the solution you'd like
Register dependencies them on demand.

Requested by @fnicola1

Nonce reuse is bad

For some unknown reason this library is using a fixed nonce. This is bad as AES-GCM can be trivially broken if a nonce is ever reused with the same key ( https://www.elttam.com/blog/key-recovery-attacks-on-gcm/ ).

I suggest the following: Remove all the nonce generation and saving logic in the keychain and all the custom nonce management code. AES.GCM.seal() will generate a random nonce if none is provided. The nonce does not need to be saved/generated/handled: AES.GCM.seal will serialize the nonce with the cyphertext in the combined representation and automatically read it back in constructor : AES.GCM.SealedBox(combined: data). It is totally transparant to the user ot the AES.GCM api.

Note: a key can be reused securely up to a certain extent with AES-GCM (IIRC 64Gb max per key), thus to have a really secure storage, a key rotation should be implemented.

Use SPM and install only some property wrappers

Is your feature request related to a problem? Please describe.
The documentation says "By default, all property wrappers are installed and you can import them, but if you want, you can install only some of them... For more information, see the Swift Package Manager documentation." Looking at the linked documentation it's not clear how to do this.

Describe the solution you'd like
Can a bit more guidance be provided or alternatively a link to the specific section of the documentation that contains the explanation?

Support Codable instead of NSCoding

Is your feature request related to a problem? Please describe.
Codable comes almost free of charge where's NSCodable requires implementation of 2 cumbersome methods

Describe the solution you'd like
Change StorageData to conform to codable

Describe alternatives you've considered
Conform all my model objects to NSCodable (NOGO)

`Unwrapped` always return defaultValue after restart app

Describe the bug
Unwrapped always return defaultValue after restart app

To Reproduce
Steps to reproduce the behavior:

    @UnwrappedCodableUserDefault(wrappedValue: "123", "key")
    var string: String

set the value for var string to "234", then restart app, it's value become to "123"

Expected behavior
defaultValue should only returned when never set the value.

Environment

Additional context

Fix concurrency issues in Swift 6

Description:

With the upcoming Swift 6 release, strict concurrency checks will be enforced. Our project, SecurePropertyStorage, currently has several warnings related to concurrency safety that need to be addressed to ensure compatibility and stability. This issue aims to track and resolve all these warnings.

Steps to Reproduce:

  1. Open SecurePropertyStorage.xcodeproj project file.
  2. Set the SWIFT_STRICT_CONCURRENCY flag to complete in the project build settings.
  3. Run SecurePropertyStorage-Package scheme tests.
  4. Observe the warnings related to concurrency safety.

Warnings to Fix:

Associated Value Warnings:

  1. InjectError enum warnings:

Static Property Warnings:

  1. Static property 'shared' is not concurrency-safe:
    • Locations:
      • Sources/Inject/InjectStorage.swift:7:24
      • Sources/Keychain/KeychainStorage.swift:9:24
      • Sources/Singleton/SingletonStorage.swift:10:24
      • Sources/UserDefault/UserDefaultsStorage.swift:10:24

Test Warnings:

  1. Static property 'allTests' is not concurrency-safe:

    • Locations:
      • Tests/SecurePropertyStorageTests/InjectTests.swift:195:16
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:252:16
      • Tests/SecurePropertyStorageTests/SingletonTests.swift:79:16
      • Tests/SecurePropertyStorageTests/UserDefaultTests.swift:243:16
  2. Let properties not concurrency-safe:

    • Locations:
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:10:5
      • Tests/SecurePropertyStorageTests/SingletonTests.swift:10:5
      • Tests/SecurePropertyStorageTests/UserDefaultTests.swift:10:5
  3. Call to main actor-isolated instance method waitForExpectations(timeout:handler:) in a synchronous nonisolated context:

    • Locations:
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:65:9
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:81:9
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:97:9
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:111:9
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:127:9
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:141:9
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:157:9
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:171:9
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:187:9
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:201:9
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:217:9
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:233:9
      • Tests/SecurePropertyStorageTests/KeychainTests.swift:249:9

Proposed Solution:

  • Ensure all associated values in Sendable-conforming enums are of Sendable types.
  • Modify static properties to conform to Sendable or isolate them to a global actor.
  • Ensure all tests and shared mutable states are concurrency-safe or properly isolated.

References:

Default values

Is your feature request related to a problem? Please describe.
I would like to add a default value so that I don't have to unwrap the values every time

Describe the solution you'd like
Default value in the constructor and backing property wrapper

Describe alternatives you've considered
I've tried using backing values but its ugly

@UserDefault should be public and update documentation

In order to unify all property wrappers, why @userdefault is not open? Also update sample usages as it forces to include a default value.

Version 0.3.1

public class DefaultStorage: AnyStorage {
    @UserDefault("sessionName", defaultValue: UIDevice.current.name)
    public var sessionName: String? // Error Property cannot be declared public because its property wrapper type uses an internal type
}

When building a binary stable framework, keychain wrapper seems to not be located

Describe the bug
When providing a binary stable framework, .swiftinterface file raises the following error:

image

To Reproduce
Steps to reproduce the behavior:

  1. Add SecurePropertyStorageDynamic as xcframework dependency
  2. Generate the stable xcframework (disabling SKIP_INSTALL and enabling BUILD_LIBRARY_FOR_DISTRIBUTION)
  3. Integrate into a separate app/framework, it will raise the issue displayed on the screenshot

Expected behavior
Property wrapper can be used without any other issue

Environment

  • Version 0.4.1
  • Xcode: 12.4 to build the xcframework, 12.5 to build the app
  • Swift: 5.3

Additional context
When building the xcframework it raises the following warning:
image

And @_implementationOnly cannot be used as it won't allow to use the property wrapper due to the following error:
image

Clear keychain value ?

Hi,

How can I clear Keychain value ?
Like I want to remove all Data when user logout !

I didn't see any thing in your doc explain how to remove value or all values

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.