Skip to content

Commit

Permalink
Docs: Add usage example to APIDoc and updated README (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
helenmasters authored and Andrew-Lees11 committed Feb 13, 2019
1 parent f9dc117 commit bb7b535
Show file tree
Hide file tree
Showing 36 changed files with 553 additions and 244 deletions.
167 changes: 119 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@


<p align="center">
<a href="http://www.kitura.io/">
<img src="https://img.shields.io/badge/docs-kitura.io-1FBCE4.svg" alt="Docs">
<a href="https://ibm-swift.github.io/Kitura-Session/index.html">
<img src="https://img.shields.io/badge/apidoc-KituraSession-1FBCE4.svg?style=flat" alt="APIDoc">
</a>
<a href="https://travis-ci.org/IBM-Swift/Kitura-Session">
<img src="https://travis-ci.org/IBM-Swift/Kitura-Session.svg?branch=master" alt="Build Status - Master">
Expand All @@ -21,90 +21,161 @@
</p>

# Kitura-Session
A pluggable framework for managing user sessions in a Swift server using Kitura

## Summary
A pluggable framework for managing user sessions in a Swift server using Kitura

## Table of Contents
* [Swift version](#swift-version)
* [API](#api)
* [Example](#example)
* [Plugins](#plugins)
* [License](#license)
A pluggable framework for managing user sessions in a Swift server using Kitura.

## Swift version
The latest version of Kitura-Session requires **Swift 4.0** or later. You can download this version of the Swift binaries by following this [link](https://swift.org/download/). Compatibility with other Swift versions is not guaranteed.

## Usage

#### Add dependencies

Add the `Kitura-Session` package to the dependencies within your application’s `Package.swift` file. Substitute `"x.x.x"` with the latest `Kitura-Session` [release](https://github.com/IBM-Swift/Kitura-Session/releases).

## API
In order to use the Session middleware, an instance of `Session` has to be created:
```swift
.package(url: "https://github.com/IBM-Swift/Kitura-Session.git", from: "x.x.x")
```

Add `KituraSession` to your target's dependencies:

```swift
.target(name: "example", dependencies: ["KituraSession"]),
```

#### Import package

```swift
import KituraSession
```

## Raw routing Session

#### Getting Started

In order to use the Session middleware on a Raw route, an instance of `Session` has to be created:
```swift
public init(secret: String, cookie: [CookieParameter]?=nil, store: Store?=nil)
```
**Where:**
- *secret* is a String to be used for session encoding. It should be a large unguessable string, say minimum 14 characters long.
- *cookie* is a list of options for session's cookies. The options are (specified in `CookieParameter` enumeration): `name` - cookie's name, defaults to "kitura-session-id", `path` - cookie's Path attribute defaults to "/", `secure` - cookie's Secure attribute, false by default, and `maxAge` - an NSTimeInterval with cookie's expiration time in seconds, defaults to -1.0, i.e., no expiration.
- *store* is an instance of a plugin for session backing store that implements `Store` protocol. If not set, `InMemoryStore` is used.
- *secret* is a String to be used for session encoding. It should be a large unguessable string, a minimum of 14 characters long.
- *cookie* is a list of options for session's cookies. The options are (as specified in the `CookieParameter` enumeration):
- `name` - cookie's name, defaults to "kitura-session-id".
- `path` - cookie's path attribute, this defines the path for which this cookie should be supplied. Defaults to "/" which means allow any path.
- `secure` - cookie's secure attribute, this indicates whwether the cookie should be provided only over secure (https) connections. Defaults to false.
- `maxAge` - cookie's maxAge attribute, that is, the maximum age (in seconds) from the time of issue that the cookie should be kept for. Defaults to -1.0, i.e. no expiration.
- *store* is an instance of a plugin for a session backing store that implements the `Store` protocol. If not set, `InMemoryStore` is used.
<br>

The last two parameters are optional.
The *cookie* and *store* parameters are optional.

<br>
The *secret* parameter is used to secure the session ID and ensure that the session ID cannot be guessed. *Secret* is used to derive a pair of encryption and signature keys via PBKDF2 and a fixed IV to make the session ID cookie be authenticated encrypted. *Secret* isn't used directly to encrypt or compute the MAC of the cookie.
The *secret* parameter is used to secure the session ID and ensure that the session ID cannot be guessed. *Secret* is used to derive a pair of encryption and signature keys via PBKDF2 and a fixed IV to make the session ID cookie be authenticated and encrypted. *Secret* isn't used directly to encrypt or compute the MAC of the cookie.

## Example
#### Example

This is an example of `Session` middleware with [`KituraSessionRedis`](https://github.com/IBM-Swift/Kitura-Session-Redis) plugin:
In this example, an instance of `RedisStore` is created that will be used to persist session data (see [`KituraSessionRedis`](https://github.com/IBM-Swift/Kitura-Session-Redis) for more information). An instance of `Session` is then created, specifying *redisStore* as the session store. Finally, the *session* instance is registered as a middleware on the desired path.

```swift
import Kitura
import KituraSession
import KituraSessionRedis

let redisStore = RedisStore(redisHost: host, redisPort: port)
let session = Session(secret: "Some secret", store: redisStore)
router.all(middleware: session)
```
First an instance of `RedisStore` is created (see [`KituraSessionRedis`](https://github.com/IBM-Swift/Kitura-Session-Redis) for more information), then an instance of `Session` with the store as parameter is created, and finally it is connected to the desired path.

## Codable Session Example
#### Storing Any in a session

The example below defines a `User` struct and a `Router` with the sessions middleware.
The router has a POST route that decodes a `User` instance from the request body
and stores it in the request session using the user's id as the key.
The router has a GET route that reads a user id from the query parameters
and decodes the instance of `User` that is in the session for that id.
Within your Kitura routes, you can store `Any` type inside the `request.session` for a given key. This can then be retrieved as an `Any` and cast to the required type:

```swift
router.post("/session") {request, response, next in
request.session?["key"] = "value"
next()
}
router.get("/session") {request, response, next in
let value = request.session?["key"] as? String
next()
}
```

This `Any` type must be JSON serializable, otherwise the session will fail when it attempts to save the session.

#### Storing Codable in a Session

Available from **Swift 4.1** or later

Within your Kitura routes, you can also store `Codable` objects inside the `request.session` for a given key. This can then be retrieved as the declared type:

```swift
public struct User: Codable {
let id: String
let name: String
let name: String
}
let router = Router()
router.all(middleware: Session(secret: "secret"))
router.post("/user") { request, response, next in
let user = try request.read(as: User.self)
request.session?[user.id] = user
response.status(.created)
response.send(user)
next()
let user = User(name: "Kitura")
request.session?["User"] = user
next()
}
router.get("/user") { request, response, next in
guard let userID = request.queryParameters["userid"] else {
return try response.status(.notFound).end()
}
guard let user: User = request.session?[userID] else {
return try response.status(.internalServerError).end()
}
response.status(.OK)
response.send(user)
next()
let user: User? = request.session?["Kitura"]
next()
}
```

## TypeSafeSession Example

To use sessions on a Codable route, declare a type that conforms to the TypeSafeSession protocol:

```swift
// Defines the session instance data
final class MySession: TypeSafeSession {
let sessionId: String // Requirement: every session must have an ID
var books: [Book] // User-defined type, where Book conforms to Codable

init(sessionId: String) { // Requirement: must be able to create a new (empty)
self.sessionId = sessionId // session containing just an ID. Assign a default or
books = [] // empty value for any non-optional properties.
}
}

// Defines the configuration of the user's type: how the cookie is constructed, and how the session is persisted.
extension MySession {
static let sessionCookie: SessionCookie = SessionCookie(name: "MySession", secret: "Top Secret")
static var store: Store?
}
```

The MySession type can then be included in the application's Codable route handlers. For example:

```swift
struct Book: Codable {
let title: String
let author: String
}

router.get("/cart") { (session: MySession, respondWith: ([Book]?, RequestError?) -> Void) in
respondWith(session.books, nil)
}

router.post("/cart") { (session: MySession, book: Book, respondWith: (Book?, RequestError) -> Void) in
var session = session // Required when mutating a Struct
session.books.append(book)
session.save()
respondWith(book, nil)
}

```
## Plugins

* [Redis store](https://github.com/IBM-Swift/Kitura-Session-Redis)
* [SQL store using Kuery](https://github.com/krzyzanowskim/Kitura-Session-Kuery) (community authored)

## API Documentation
For more information visit our [API reference](https://ibm-swift.github.io/Kitura-Session/index.html).

## Community

We love to talk server-side Swift, and Kitura. Join our [Slack](http://swift-at-ibm-slack.mybluemix.net/) to meet the team!

## License
This library is licensed under Apache 2.0. Full license text is available in [LICENSE](LICENSE.txt).
19 changes: 14 additions & 5 deletions Sources/KituraSession/CookieParameter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,27 @@ import Foundation

// MARK CookieParameter

/// The parameters for configurating the cookies used to send the session IDs to the clients.
/// The parameters for configuring the cookies used to send the session IDs to the clients.
///
/// ### Usage Example: ###
/// ```swift
/// let session = Session(secret: "Something very secret", cookie: [.name("mySessionId")])
/// router.all(middleware: session)
/// ```
/// In the example, an instance of `Session` is created with a custom value for the `CookieParameter` name.
public enum CookieParameter {

/// The cookie's name.
/// The cookie's name. Defaults to "kitura-session-id".
case name(String)

/// The cookie's Path attribute.
/// The cookie's path attribute. This specifies the path for which the cookie is valid. The client should only provide this cookie for requests on this path.
case path(String)

/// The cookie's Secure attribute.
/// The cookie's secure attribute, indicating whether the cookie should be provided only
/// over secure (https) connections. Defaults to false.
case secure(Bool)

/// The cookie's Max-Age attribute.
/// The cookie's maxAge attribute, that is, the maximum age (in seconds) from the time of issue that
/// the cookie should be kept for. Defaults to -1.0, i.e. no expiration.
case maxAge(TimeInterval)
}
23 changes: 22 additions & 1 deletion Sources/KituraSession/Session.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,27 @@ import Foundation
// MARK Session

/// A pluggable middleware for managing user sessions.
///
/// In order to use the Session middleware, an instance of `Session` has to be created. In the example
/// below an instance of `Session` is created, then it is connected to the desired path. Two route to are then registered that save and retrieve a `User` from the session.
///
/// ### Usage Example: ###
/// ```swift
/// let session = Session(secret: "Something very secret")
/// router.all(middleware: session)
/// public struct User: Codable {
/// let name: String
/// }
/// router.post("/user") { request, response, next in
/// let user = User(name: "Kitura")
/// request.session?["User"] = user
/// next()
/// }
/// router.get("/user") { request, response, next in
/// let user: User? = request.session?["Kitura"]
/// next()
/// }
/// ```
public class Session: RouterMiddleware {

/// Store for session state
Expand All @@ -33,7 +54,7 @@ public class Session: RouterMiddleware {
/// Initialize a new `Session` management middleware.
///
/// - Parameter secret: The string used to encrypt the session ID cookie.
/// - Parameter cookie: An array of the cookie's paramaters and attributes.
/// - Parameter cookie: An array of the cookie's parameters and attributes.
/// - Parameter store: The `Store` plugin to be used to store the session state.
public init(secret: String, cookie: [CookieParameter]?=nil, store: Store?=nil) {
if let store = store {
Expand Down
10 changes: 5 additions & 5 deletions Sources/KituraSession/SessionCookie.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ internal struct CookieParameters {
}

/// Defines the properties of an HTTP Cookie which will be used for a `TypeSafeSession`.
/// It is valid for multiple `TypeSafeSession` types to use the same name (ie. same cookie),
/// provided that they also use the same secret.
/// It is valid for multiple `TypeSafeSession` types to use the same name (i.e. same cookie),
/// provided they also use the same secret.
/// ### Usage Example: ###
/// ```swift
/// static let sessionCookie = SessionCookie(name: "kitura-session-id", secret: "xyz789", secure: false, maxAge: 300)
Expand Down Expand Up @@ -72,9 +72,9 @@ public struct SessionCookie {
/// ```swift
/// static let sessionCookie = SessionCookie(name: "kitura-session-id", secret: "xyz789", secure: false, maxAge: 300)
/// ```
/// - Parameter name: The name of the cookie, for example, 'kitura-session-id'
/// - Parameter secret: The secret data used to encrypt and decrypt session cookies with this name
/// - Parameter secure: Whether the cookie should be provided only over secure (https) connections. Defaults to false
/// - Parameter name: The name of the cookie, for example, 'kitura-session-id'.
/// - Parameter secret: The secret data used to encrypt and decrypt session cookies with this name.
/// - Parameter secure: Whether the cookie should be provided only over secure (https) connections. Defaults to false.
/// - Parameter path: The path for which this cookie should be supplied. Defaults to allow any path.
/// - Parameter domain: The domain to which this cookie applies. Defaults to the subdomain of the server issuing the cookie.
/// - Parameter maxAge: The maximum age (in seconds) from the time of issue that the cookie should be kept for. This is a request to the client and may not be honoured.
Expand Down
14 changes: 7 additions & 7 deletions Sources/KituraSession/TypeSafeSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import Foundation
// MARK TypeSafeSession

/**
A `TypeSafeMiddleware` for managing user sessions. The user defines a final class with the fields they wish to use within the session. This class can then save or destroy itself from a static `Store`, which is keyed by a `sessionId`. The sessionId can be extracted from the session cookie to initialise an instance of the users class with the session data. If no store is defined, the session will default to an in-memory store.
A `TypeSafeMiddleware` for managing user sessions. The user defines a final class with the fields they wish to use within the session. This class can then save or destroy itself from a static `Store`, which is keyed by a `sessionId`. The sessionId can be extracted from the session cookie to initialise an instance of the user's class with the session data. If no store is defined, the session will default to an in-memory store.
### Usage Example: ###
In this example, a class conforming to the TypeSafeSession protocol is defined containing an optional "name" field. Then a route on "/session" is set up that stores a received name into the session.
```swift
Expand All @@ -44,7 +44,7 @@ import Foundation
respondWith(session.name, nil)
}
```
__Note__: When using multiple TypeSafeSession classes together, If the cookie names are the same, the cookie secret must also be the same. Otherwise the sessions will conflict and overwrite each others cookies. (Different cookie names can use different secrets)
__Note__: When using multiple TypeSafeSession classes together, if the cookie names are the same, the cookie secret must also be the same. Otherwise the sessions will conflict and overwrite each others cookies. (Different cookie names can use different secrets).
*/
public protocol TypeSafeSession: TypeSafeMiddleware, Codable {

Expand All @@ -66,7 +66,7 @@ public protocol TypeSafeSession: TypeSafeMiddleware, Codable {
/// Create a new instance (an empty session), where the only known value is the
/// (newly created) session id. Non-optional fields must be given a default value.
///
/// Existing sessions are restored via the Codable API by decoding a retreived JSON
/// Existing sessions are restored via the Codable API by decoding a retrieved JSON
/// representation.
init(sessionId: String)

Expand Down Expand Up @@ -105,7 +105,7 @@ public protocol TypeSafeSession: TypeSafeMiddleware, Codable {

extension TypeSafeSession {

/// Static handle function that will try and create an instance if Self. It will check the request for the session cookie. If the cookie is not present it will create a cookie and initialize a new session for the user. If a session cookie is found, this function will decode and return an instance of itself from the store.
/// Static handle function that will try and create an instance of Self. It will check the request for the session cookie. If the cookie is not present it will create a cookie and initialize a new session for the user. If a session cookie is found, this function will decode and return an instance of itself from the store.
///
/// - Parameter request: The `RouterRequest` object used to get information
/// about the request.
Expand Down Expand Up @@ -171,7 +171,7 @@ extension TypeSafeSession {
}

/**
Save the current session instance to the store
Save the current session instance to the store.
### Usage Example: ###
```swift
router.post("/session") { (session: MySession, name: String, respondWith: (String?, RequestError?) -> Void) in
Expand Down Expand Up @@ -203,7 +203,7 @@ extension TypeSafeSession {
}

/**
Destroy the session, removing it and all its associated data from the store
Destroy the session, removing it and all its associated data from the store.
### Usage Example: ###
```swift
router.delete("/session") { (session: MySession, respondWith: (RequestError?) -> Void) in
Expand All @@ -225,7 +225,7 @@ extension TypeSafeSession {
}
}

/// Touch the session, refreshing its expiry time in the store
/// Touch the session, refreshing its expiry time in the store.
public func touch(callback: @escaping (Error?) -> Void = { _ in }) {
guard let store = Self.store else {
Log.error("Unexpectedly found a nil store")
Expand Down
Loading

0 comments on commit bb7b535

Please sign in to comment.