Skip to content

Commit

Permalink
Implementation for web-based GitHub login (#2)
Browse files Browse the repository at this point in the history
* Implementation for web-based GitHub login

* Update Makefile to point to Package-Builder

* Update .travis.yml
  • Loading branch information
youming-lin authored and shmuelk committed Aug 31, 2016
1 parent 135255e commit 26fcfdc
Show file tree
Hide file tree
Showing 10 changed files with 313 additions and 1 deletion.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
.DS_Store

# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
Kitura-CredentialsGitHub.xcodeproj/

## Build generated
.build/
build/
DerivedData/

Expand Down Expand Up @@ -34,8 +38,8 @@ playground.xcworkspace
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
.build/
Packages/

# CocoaPods
#
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "Package-Builder"]
path = Package-Builder
url = https://github.com/IBM-Swift/Package-Builder.git
1 change: 1 addition & 0 deletions .swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DEVELOPMENT-SNAPSHOT-2016-08-23-a
28 changes: 28 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Travis CI build file for Kitura-redis.
# Kitura runs on OS X and Linux (Ubuntu v15.10).

# whitelist (branches that should be built)
branches:
only:
- master
- develop
- /^issue.*$/

notifications:
slack:
secure: "spl/MlD2mVGlto7/zMYQrswJDOM/2T/IcJ7AgrJCZ1spPZeM7HqKzgIwjlfK2Um65gzYITECTW6DEMI4pgporqHkKpCv56VCl2qoiwy3jFj2UMBFF7Bzga8hEZSygtREVz359rBZ9hfZb1+UKS/5z95Ewdm4GOIfX8GlqvWRISZqq9LpJVjw3qXg3JbRJMJJPJBCjHyEGEY4WoIYTvOifAphGNXTu9ELQeYh+Vfoq1S5lFwjwlVc7H2QYRvtQc5sNQVylq/sGbBr9AGCXUbnexmz+7VNI7+XiRcDO2EoXpHAuCZAkI+lwlXwqv2JZuHFv/1VKIRArL46pLxeQyf6RN8wWvKMfsBx/fR5KOX2InUeYeBl6yCN9A7+gMjBYamg8UuRkM7JgzChVoZz9uQrQZaQOUwo3hWbNwJK20kpAO12LKN6jOgkp+EsoRIO/j9eLxbTJBPsGIJ5YDGRUn8bMY6yg1OEiS17bmjPMIUikk8o8GXGntfHDMMTzyeYPdSmDjuaBkDs+/b5jIxERw5qVI4f3lyaFxe+S24aA6wT3sQjmBCUuo15uoSpiwsarp2k66pnNgA63oeMn+JmdInRTEgC7ZwDCMGdLZpIFt9fhHlGGvbgrhDPxmaNKe1GAWICjisQiO4GTw6iNgy4VAqGBkuG6uzNYBiLd0QfW0LlY6s="

matrix:
include:
- os: linux
dist: trusty
sudo: required
- os: osx
osx_image: xcode8
sudo: required

before_install:
- git submodule update --init --remote --merge --recursive

script:
- ./Package-Builder/build-package.sh $TRAVIS_BRANCH $TRAVIS_BUILD_DIR
33 changes: 33 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Contributing to Kitura

We welcome contributions, but request you follow these guidelines.

- [Raising issues](#raising-issues)
- [Contributor License Agreement](#contributor-license-agreement)
- [Coding Standards](#coding-standards)


## Raising issues

Please raise any bug reports on the issue tracker. Be sure to
search the list to see if your issue has already been raised.

A good bug report is one that make it easy for us to understand what you were
trying to do and what went wrong. Provide as much context as possible so we can try to recreate the issue.

### Contributor License Agreement

In order for us to accept pull-requests, the contributor must first complete
a Contributor License Agreement (CLA). Please see our [CLA repo](https://github.com/IBM-Swift/CLA) for more information.

This clarifies the intellectual property license granted with any contribution. It is for your protection as a
Contributor as well as the protection of IBM and its customers; it does not
change your rights to use your own Contributions for any other purpose.

### Coding standards

Please ensure you follow the coding standards used throughout the existing
code base. Some basic rules include:

- all files must have the Apache license in the header.
- all PRs must have passing builds for all operating systems.
22 changes: 22 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright IBM Corporation 2016
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Makefile
export KITURA_CI_BUILD_SCRIPTS_DIR=Package-Builder/build

-include Package-Builder/build/Makefile

Package-Builder/build/Makefile:
@echo --- Fetching Package-Builder submodule
git submodule update --init --remote --merge --recursive
1 change: 1 addition & 0 deletions Package-Builder
Submodule Package-Builder added at eff80f
24 changes: 24 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright IBM Corporation 2016
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

import PackageDescription

let package = Package(
name: "Kitura-CredentialsGitHub",
dependencies: [
.Package(url: "https://github.com/IBM-Swift/Kitura-Credentials.git", majorVersion: 0, minor: 28),
]
)
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Kitura-CredentialsGitHub
Plugin for the Credentials framework that authenticate using GitHub

![Mac OS X](https://img.shields.io/badge/os-Mac%20OS%20X-green.svg?style=flat)
![Linux](https://img.shields.io/badge/os-linux-green.svg?style=flat)
![Apache 2](https://img.shields.io/badge/license-Apache2-blue.svg?style=flat)

## Summary
Plugin for [Kitura-Credentials](https://github.com/IBM-Swift/Kitura-Credentials) framework that authenticates using the [GitHub web login with OAuth](https://developer.github.com/v3/oauth/#web-application-flow).

## Table of Contents
* [Swift version](#swift-version)
* [Example of GitHub web login](#example-of-github-web-login)
* [License](#license)

## Swift version
The latest version of Kitura-CredentialsGitHub works with the DEVELOPMENT-SNAPSHOT-2016-08-23-a version of the Swift binaries. 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.

## Example of GitHub web login
First create an instance of `CredentialsGitHub` plugin and register it with `Credentials` framework:
```swift
import Credentials
import CredentialsGitHub

let credentials = Credentials()
let gitCredentials = CredentialsGitHub(clientId: gitClientId, clientSecret: gitClientSecret, callbackUrl: serverUrl + "/login/github/callback", userAgent: "my-kitura-app")
credentials.register(gitCredentials)
```
**Where:**
- *gitClientId* is the Client ID of your app in your GitHub Developer applications
- *gitClientSecret* is the Client Secret of your app in your GitHub Developer applications
- *userAgent* is an optional argument that passes along a User-Agent of your choice on API calls against GitHub. By default, `Kitura-CredentialsGitHub` is set as the User-Agent. [User-Agent is required when invoking GitHub APIs](https://developer.github.com/v3/#user-agent-required).

**Note:** The *callbackUrl* parameter above is used to tell the GitHub web login page where the user's browser should be redirected when the login is successful. It should be a URL handled by the server you are writing.
Specify where to redirect non-authenticated requests:
```swift
credentials.options["failureRedirect"] = "/login/github"
```

Connect `credentials` middleware to requests to `/private`:

```swift
router.all("/private", middleware: credentials)
router.get("/private/data", handler: { request, response, next in
...
next()
})
```
And call `authenticate` to login with GitHub and to handle the redirect (callback) from the GitHub login web page after a successful login:

```swift
router.get("/login/github",
handler: credentials.authenticate(gitCredentials.name))

router.get("/login/github/callback",
handler: credentials.authenticate(gitCredentials.name))
```
## License
This library is licensed under Apache 2.0. Full license text is available in [LICENSE](LICENSE.txt).
137 changes: 137 additions & 0 deletions Sources/CredentialsGitHub/CredentialsGitHub.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/**
* Copyright IBM Corporation 2016
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

import Kitura
import KituraNet
import LoggerAPI
import Credentials

import SwiftyJSON

import Foundation

public class CredentialsGitHub : CredentialsPluginProtocol {

private var clientId : String

private var clientSecret : String

public var callbackUrl : String

/// User-Agent must be set in order to access GitHub API (i.e., to get user profile)
/// https://developer.github.com/v3/#user-agent-required
public private(set) var userAgent: String

public var name : String {
return "GitHub"
}

public var redirecting : Bool {
return true
}

public init (clientId: String, clientSecret : String, callbackUrl : String, userAgent: String?=nil) {
self.clientId = clientId
self.clientSecret = clientSecret
self.callbackUrl = callbackUrl
self.userAgent = userAgent ?? "Kitura-CredentialsGitHub"
}

public var usersCache : NSCache<NSString, BaseCacheElement>?

/// https://developer.github.com/v3/oauth/#web-application-flow
public func authenticate (request: RouterRequest, response: RouterResponse,
options: [String:Any], onSuccess: @escaping (UserProfile) -> Void,
onFailure: @escaping (HTTPStatusCode?, [String:String]?) -> Void,
onPass: @escaping (HTTPStatusCode?, [String:String]?) -> Void,
inProgress: @escaping () -> Void) {
if let code = request.queryParameters["code"] {
// query contains code: exchange code for access token
var requestOptions: [ClientRequest.Options] = []
requestOptions.append(.schema("https://"))
requestOptions.append(.hostname("github.com"))
requestOptions.append(.method("POST"))
requestOptions.append(.path("/login/oauth/access_token?client_id=\(clientId)&redirect_uri=\(callbackUrl)&client_secret=\(clientSecret)&code=\(code)"))
var headers = [String:String]()
headers["Accept"] = "application/json"
requestOptions.append(.headers(headers))

let requestForToken = HTTP.request(requestOptions) { fbResponse in
if let fbResponse = fbResponse, fbResponse.statusCode == .OK {
// get user profile with access token
do {
var body = Data()
try fbResponse.readAllData(into: &body)
var jsonBody = JSON(data: body)
if let token = jsonBody["access_token"].string {
requestOptions = []
requestOptions.append(.schema("https://"))
requestOptions.append(.hostname("api.github.com"))
requestOptions.append(.method("GET"))
requestOptions.append(.path("/user"))
headers = [String:String]()
headers["Accept"] = "application/json"
headers["User-Agent"] = self.userAgent
headers["Authorization"] = "token \(token)"
requestOptions.append(.headers(headers))

let requestForProfile = HTTP.request(requestOptions) { profileResponse in
if let profileResponse = profileResponse, profileResponse.statusCode == .OK {
do {
body = Data()
try profileResponse.readAllData(into: &body)
jsonBody = JSON(data: body)

if let id = jsonBody["id"].number?.stringValue,
let name = jsonBody["name"].string {
let userProfile = UserProfile(id: id, displayName: name, provider: self.name)
onSuccess(userProfile)
return
}
}
catch {
Log.error("Failed to read \(self.name) response")
}
}
else {
onFailure(nil, nil)
}
}
requestForProfile.end()
}
}
catch {
Log.error("Failed to read \(self.name) response")
}
}
else {
onFailure(nil, nil)
}
}
requestForToken.end()
}
else {
// Log in
do {
try response.redirect("https://github.com/login/oauth/authorize?client_id=\(clientId)&redirect_uri=\(callbackUrl)&response_type=code")
inProgress()
}
catch {
Log.error("Failed to redirect to \(name) login page")
}
}
}
}

0 comments on commit 26fcfdc

Please sign in to comment.