Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/cely-tools/Cely

Plug-n-Play login system for iOS written in Swift
https://github.com/cely-tools/Cely

keychain login swift

Last synced: about 1 month ago
JSON representation

Plug-n-Play login system for iOS written in Swift

Awesome Lists containing this project

README

        

![Swift 4.0](https://img.shields.io/badge/Swift-3.0-orange.svg?style=flat)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![CocoaPods compatible](https://img.shields.io/cocoapods/v/Cely.svg)](https://cocoapods.org/pods/Cely)
[![Cookiecutter-Swift](https://img.shields.io/badge/cookiecutter--swift-framework-red.svg)](http://github.com/cookiecutter-swift/Framework)
[![Build Status](https://app.bitrise.io/app/aff729145cb46dfe/status.svg?token=YUV0bymd7P_w2tdiKw2xOQ&branch=master)](https://app.bitrise.io/app/aff729145cb46dfe)
[![codecov](https://codecov.io/gh/initFabian/Cely/branch/master/graph/badge.svg)](https://codecov.io/gh/initFabian/Cely)
> Prounounced Cell-Lee

Cely’s goal is to add a login system into your app in under 30 seconds!

- [Overview](#overview)
- [Requirements](#requirements)
- [Usage](#usage)
- [API](#api)
- [Installation](#installation)
- [License](#license)

## Overview
Cely's goal is to add a login system into your app in under 30 seconds!

### Background
Whether you're building an app for a client or for a hackathon, building a login system, no matter how basic it is, can be very tedious and time-consuming. Cely's architecture has been battle tested on countless apps, and Cely guarantees you a fully functional login system in a fraction of the time. You can trust Cely is handling login credentials correctly as well since Cely is built on top of [Locksmith](https://github.com/matthewpalmer/Locksmith), swift's most popular Keychain wrapper.

#### Details:
What does Cely does for you?

1. Simple API to store user creditials and information **securely**
- `Cely.save("SUPER_SECRET_STRING", forKey: "token", securely: true)`
2. Manages switching between your app's Home Screen and Login Screen with:
- `Cely.changeStatus(to: .loggedIn) // or .loggedOut`
3. Customizable starter Login screen(or you can use your login screen)

What Cely **does not do** for you?

1. Network requests
2. Handle Network errors
3. Anything with the network

## Requirements

- iOS 11.0+
- Xcode 9.0+
- Swift 4.0+

## Usage

### Setup(30 seconds)

#### User Model (`User.swift`)
Let's start by creating a `User` model that conforms to the [`CelyUser`](#Cely.CelyUser) Protocol:

```swift
// User.swift

import Cely

struct User: CelyUser {

enum Property: CelyProperty {
case token = "token"
}
}

```
#### Login redirect(`AppDelegate.swift`)

Cely's **Simple Setup** function will get you up and running in a matter of seconds. Inside of your `AppDelegate.swift` simply `import Cely` and call the [`setup(_:)`](#Cely.setup) function inside of your `didFinishLaunchingWithOptions` method.

```swift
// AppDelegate.swift

import Cely

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: Any]?) -> Bool {

Cely.setup(with: window, forModel: User(), requiredProperties: [.token])

...
}
```

Hit RUN!!

### CelyOptions
#### Handle Login Credentials
Now how do we get the `username` and `password` from Cely's default LoginViewController? It's easy, just pass in a completion block for the `.loginCompletionBlock`. Check out [`CelyOptions`](#Cely.CelyOptions) for more info.

```swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

Cely.setup(with: window!, forModel: User(), requiredProperties: [.token], withOptions: [
.loginCompletionBlock: { (username: String, password: String) in
if username == "asdf" && password == "asdf" {
Cely.save(username, forKey: "username")
Cely.save("FAKETOKEN:\(username)\(password)", forKey: "token", securely: true)
Cely.changeStatus(to: .loggedIn)
}
}
])

return true
}
```
#### Customize Default Login Screen

Create an object that conforms to the [`CelyStyle`](#Cely.CelyStyle) protocol and set it to `.loginStyle` inside of the [`CelyOptions`](#Cely.CelyOptions) dictionary when calling Cely's [`setup(_:)`](#Cely.setup) method.

```swift
// LoginStyles.swift
struct CottonCandy: CelyStyle {
func backgroundColor() -> UIColor {
return UIColor(red: 86/255, green: 203/255, blue: 249/255, alpha: 1) // Changing Color
}
func buttonTextColor() -> UIColor {
return .white
}
func buttonBackgroundColor() -> UIColor {
return UIColor(red: 253/255, green: 108/255, blue: 179/255, alpha: 1) // Changing Color
}
func textFieldBackgroundColor() -> UIColor {
return UIColor.white.withAlphaComponent(0.4)
}
func appLogo() -> UIImage? {
return UIImage(named: "CelyLogo")
}
}

...

// AppDelegate.swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

Cely.setup(with: window!, forModel: User.ref, requiredProperties: [.token], withOptions: [
.loginStyle: CottonCandy(), // <--- HERE!!
.loginCompletionBlock: { ... }
])

return true
}

```

#### Customize Transitions
In order to create a custom transition, create an object that conforms to [`CelyAnimator`](#Cely.CelyAnimator) protocol and set it to `.celyAnimator` inside of the [`CelyOption`](#Cely.CelyOptions) dictionary when calling Cely's [`setup(_:)`](#Cely.setup) method.

```swift
struct DefaultAnimator: CelyAnimator {
func loginTransition(to destinationVC: UIViewController?, with celyWindow: UIWindow) {
guard let snapshot = celyWindow.snapshotView(afterScreenUpdates: true) else {
return
}

destinationVC?.view.addSubview(snapshot)
// Set the rootViewController of the `celyWindow` object
celyWindow.setCurrentViewController(to: destinationVC)

// Below here is where you can create your own animations
UIView.animate(withDuration: 0.5, animations: {
// Slide login screen to left
snapshot.transform = CGAffineTransform(translationX: 600.0, y: 0.0)
}, completion: {
(value: Bool) in
snapshot.removeFromSuperview()
})
}

func logoutTransition(to destinationVC: UIViewController?, with celyWindow: UIWindow) {
guard let snapshot = celyWindow.snapshotView(afterScreenUpdates: true) else {
return
}

destinationVC?.view.addSubview(snapshot)
// Set the rootViewController of the `celyWindow` object
celyWindow.setCurrentViewController(to: destinationVC)

// Below here is where you can create your own animations
UIView.animate(withDuration: 0.5, animations: {
// Slide home screen to right
snapshot.transform = CGAffineTransform(translationX: -600.0, y: 0.0)
}, completion: {
(value: Bool) in
snapshot.removeFromSuperview()
})
}
}

...

// AppDelegate.swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

Cely.setup(with: window!, forModel: User.ref, requiredProperties: [.token], withOptions: [
.celyAnimator: CustomAnimator(), // <--- HERE!!
.loginCompletionBlock: { ... }
])

return true
}

```

#### Use your own Screens
To use your own login screen, simply create a storyboard that contains your login screen and pass that in as `.loginStoryboard` inside of the [`CelyOptions`](#Cely.CelyOptions) dictionary when calling Cely's [`setup(_:)`](#Cely.setup) method.

Lastly, if your app uses a different storyboard other than `Main.storyboard`, you can pass that in as `.homeStoryboard`.

**⚠️⚠️⚠️⚠️ Be sure to make your Login screen as the `InitialViewController` inside of your storyboard!⚠️⚠️⚠️⚠️**

```swift

// AppDelegate.swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

Cely.setup(with: window!, forModel: User.ref, requiredProperties: [.token], withOptions: [
.loginStoryboard: UIStoryboard(name: "MyCustomLogin", bundle: nil),
.homeStoryboard: UIStoryboard(name: "NonMain", bundle: nil)
])

return true
}

```

### Recommended User Pattern

```swift
import Cely

struct User: CelyUser {

enum Property: CelyProperty {
case username = "username"
case email = "email"
case token = "token"

func securely() -> Bool {
switch self {
case .token:
return true
default:
return false
}
}

func persisted() -> Bool {
switch self {
case .username:
return true
default:
return false
}
}

func save(_ value: Any) {
Cely.save(value, forKey: rawValue, securely: securely(), persisted: persisted())
}

func get() -> Any? {
return Cely.get(key: rawValue)
}
}
}

// MARK: - Save/Get User Properties

extension User {

static func save(_ value: Any, as property: Property) {
property.save(value: value)
}

static func save(_ data: [Property : Any]) {
data.forEach { property, value in
property.save(value)
}
}

static func get(_ property: Property) -> Any? {
return property.get()
}
}

```

The reason for this pattern is to make saving data as easy as:

```swift
// Pseudo network code, NOT REAL CODE!!!
ApiManager.logUserIn("username", "password") { json in
let apiToken = json["token"].string

// REAL CODE!!!
User.save(apiToken, as: .token)
}

```
and getting data as simple as:

```swift
let token = User.get(.token)
```

## API

### Cely
Cely was made to help handle user credentials and handling login with ease. Below you will find documentation for Cely's Framework. **Please do not hesitate to open an issue if something is unclear or is missing.**
#### Variables

##### `store`

A class that conforms to the [`CelyStorageProtocol`](#Cely.CelyStorageProtocol) protocol. By default is set to a singleton instance of `CelyStorage`.

#### Methods

##### `setup(with:forModel:requiredProperties:withOptions:)`

Sets up Cely within your application

Example

```swift
Cely.setup(with: window, forModel: User(), requiredProperties:[.token])

// or

Cely.setup(with: window, forModel: User(), requiredProperties:[.token], withOptions:[
.loginStoryboard: UIStoryboard(name: "MyCustomLogin", bundle: nil),
.HomeStoryboard: UIStoryboard(name: "My_NonMain_Storyboard", bundle: nil),
.loginCompletionBlock: { (username: String, password: String) in
if username == "asdf" && password == "asdf" {
print("username: \(username): password: \(password)")
}
}
])
```

Parameters

Key | Type| Required? | Description
----|------|----------|--------
`window` | `UIWindow` | ✅ | window of your application.
`forModel` | [`CelyUser`](#Cely.CelyUser) | ✅ | The model Cely will be using to store data.
`requiredProperties` | `[CelyProperty]` | no | The properties that cely tests against to determine if a user is logged in.
**Default value**: empty array.
`options` | `[CelyOption]` | no | An array of [`CelyOptions`](#Cely.CelyOptions) to pass in additional customizations to cely.

##### `currentLoginStatus(requiredProperties:fromStorage:)`

Will return the `CelyStatus` of the current user.

Example

```swift
let status = Cely.currentLoginStatus()
```

Parameters

Key | Type| Required? | Description
----|------|----------|--------
`properties` | [`CelyProperty`] | no | Array of required properties that need to be in store.
`store` | `CelyStorage` | no | Storage `Cely` will be using. Defaulted to `CelyStorage`

Returns

Type| Description
----|------
[`CelyStatus`](#Cely.CelyStatus) | If `requiredProperties` are all in store, it will return `.loggedIn`, else `.loggedOut`

##### `get(_:fromStorage:)`

Returns stored data for key.

Example

```swift
let username = Cely.get(key: "username")
```

Parameters

Key | Type| Required? | Description
----|------|----------|--------
`key` | String | ✅ | The key to the value you want to retrieve.
`store` | CelyStorage | no | Storage `Cely` will be using. Defaulted to `CelyStorage`

Returns

Type| Description
----|------
`Any?` | Returns an `Any?` object in the case the value nil(not found).

##### `save(_:forKey:toStorage:securely:persisted:)`

Saves data in store

Example

```swift
Cely.save("[email protected]", forKey: "email")
Cely.save("testUsername", forKey: "username", persisted: true)
Cely.save("token123", forKey: "token", securely: true)
```

Parameters

Key | Type| Required? | Description
----|------|----------|--------
`value` | **Any?** | ✅ | The value you want to save to storage.
`key` | **String** | ✅ | The key to the value you want to save.
`store` | **CelyStorage** | no | Storage `Cely` will be using. Defaulted to `CelyStorage`.
`secure` | **Boolean** | no | If you want to store the value securely.
`persisted` | **Boolean** | no | Keep data after logout.

Returns

Type| Description
----|------
`StorageResult` | Whether or not your value was successfully set.

##### `changeStatus(to:)`

Perform action like `LoggedIn` or `LoggedOut`.

Example

```swift
changeStatus(to: .loggedOut)
```

Parameters

Key | Type| Required? | Description
----|------|----------|--------
`status` | **CelyStatus** | ✅ | enum value

##### `logout(usesStorage:)`

Convenience method to logout user. Is equivalent to `changeStatus(to: .loggedOut)`

Example

```swift
Cely.logout()
```

Parameters

Key | Type| Required? | Description
----|------|----------|--------
`store` | **CelyStorage** | no | Storage `Cely` will be using. Defaulted to `CelyStorage`.

##### `isLoggedIn()`

Returns whether or not the user is logged in

Example

```swift
Cely.isLoggedIn()
```

Returns

Type| Description
----|------
`Boolean` | Returns whether or not the user is logged in

### Constants
#### Protocols

##### `CelyUser `

`protocol` for model class to implements

Example

```swift
import Cely

struct User: CelyUser {

enum Property: CelyProperty {
case token = "token"
}
}
```

Required

value | Type| Description
----|------|---
`Property ` | `associatedtype` | Enum of all the properties you would like to save for a model

##### `CelyStorageProtocol `

`protocol` a storage class must abide by in order for Cely to use it

Required

```swift
func set(_ value: Any?, forKey key: String, securely secure: Bool, persisted: Bool) -> StorageResult
func get(_ key: String) -> Any?
func removeAllData()

```

##### `CelyStyle`

The `protocol` an object must conform to in order to customize Cely's default login screen. Since all methods are optional, Cely will use the default value for any unimplemented methods.

Methods

```swift
func backgroundColor() -> UIColor
func textFieldBackgroundColor() -> UIColor
func buttonBackgroundColor() -> UIColor
func buttonTextColor() -> UIColor
func appLogo() -> UIImage?

```

##### `CelyAnimator`

The `protocol` an object must conform to in order to customize transitions between home and login screens.

Methods

```swift
func loginTransition(to destinationVC: UIViewController?, with celyWindow: UIWindow)
func logoutTransition(to destinationVC: UIViewController?, with celyWindow: UIWindow)

```

#### Typealias

##### `CelyProperty `

`String` type alias. Is used in User model

##### `CelyCommands `

`String` type alias. Command for cely to execute

#### enums

##### `CelyOptions`

`enum` Options that you can pass into Cely on [`setup(with:forModel:requiredProperties:withOptions:)`](#Cely.setup)

Options

Case | Description
----|------
`storage ` | Pass in you're own storage class if you wish not to use Cely's default storage. Class must conform to the `CelyStorage` protocol.
`homeStoryboard ` | Pass in your app's default storyboard if it is not named "Main"
`loginStoryboard ` | Pass in your own login storyboard.
`loginStyle` | Pass in an object that conforms to [`CelyStyle`](#Cely.CelyStyle) to customize the default login screen.
`loginCompletionBlock ` | `(String,String) -> Void` block of code that will run once the Login button is pressed on Cely's default login Controller
`celyAnimator` | Pass in an object that conforms to [`CelyAnimator`](#Cely.CelyAnimator) to customize the default login screen.

##### `CelyStatus`

`enum` Statuses for Cely to perform actions on

Statuses

Case | Description
----|------
`loggedIn ` | Indicates user is now logged in.
`loggedOut ` | Indicates user is now logged out.

##### `StorageResult `

`enum` result on whether or not Cely successfully saved your data.

Results

Case| Description
----|------
`success ` | Successfully saved your data
`fail(error) ` | Failed to save data along with a `LocksmithError`.

## Installation

### CocoaPods

[CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command:

```bash
$ gem install cocoapods
```

> CocoaPods 1.1.0+ is required to build Cely 2.0.0+.

To integrate Cely into your Xcode project using CocoaPods, specify it in your `Podfile`:

```ruby
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!

pod 'Cely', '~> 2.1'
```

Then, run the following command:

```bash
$ pod install
```

### Carthage

[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application.

You can install Carthage with [Homebrew](http://brew.sh/) using the following command:

```bash
$ brew update
$ brew install carthage
```

To integrate Cely into your Xcode project using Carthage, specify it in your `Cartfile`:

```ogdl
github "initFabian/Cely" ~> 2.1
```

### Manually

If you prefer not to use either of the aforementioned dependency managers, you can integrate Cely into your project manually.

#### Git Submodules

- Open up Terminal, `cd` into your top-level project directory, and run the following command "if" your project is not initialized as a git repository:

```bash
$ git init
```

- Add Cely as a git [submodule](http://git-scm.com/docs/git-submodule) by running the following command:

```bash
$ git submodule add https://github.com/initFabian/Cely.git
$ git submodule update --init --recursive
```

- Open the new `Cely` folder, and drag the `Cely.xcodeproj` into the Project Navigator of your application's Xcode project.

> It should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter.

- Select the `Cely.xcodeproj` in the Project Navigator and verify the deployment target matches that of your application target.
- Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar.
- In the tab bar at the top of that window, open the "General" panel.
- Click on the `+` button under the "Embedded Binaries" section.
- You will see two different `Cely.xcodeproj` folders each with two different versions of the `Cely.framework` nested inside a `Products` folder.

> It does not matter which `Products` folder you choose from.

- Select the `Cely.framework`.

- And that's it!

> The `Cely.framework` is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device.

#### Embeded Binaries

- Download the latest release from https://github.com/initFabian/Cely/releases
- Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar.
- In the tab bar at the top of that window, open the "General" panel.
- Click on the `+` button under the "Embedded Binaries" section.
- Add the downloaded `Cely.framework`.
- And that's it!

## License

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