-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1282 from code-corps/1265-github-state-service
Add github-state service for generating and validating the connect state parameter
- Loading branch information
Showing
2 changed files
with
128 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import Ember from 'ember'; | ||
|
||
const { get, Service, inject: { service } } = Ember; | ||
|
||
/** | ||
* Converts an integer to a hex string and returns the last two characters as | ||
* a string. | ||
* | ||
* @method integerToHex | ||
* @param {Integer} integer An integer to convert | ||
* @return {String} A hexadecimal string of length 2. | ||
*/ | ||
function integerToHex(integer) { | ||
return (`0${integer.toString(16)}`.substr(-2)); | ||
} | ||
|
||
/** | ||
* Generates a randomized array of decimals, converts them to hexadecimal values | ||
* and joins into a string in order to output a random string of specified | ||
* length. | ||
* | ||
* @method generateRandomString | ||
* @param {Integer} length The length of the random string to generate | ||
* @return {String} A random string of the specified length | ||
*/ | ||
function generateRandomString(length = 40) { | ||
let unsignedInt8Array = new window.Uint8Array(length / 2); | ||
let randomizedArray = window.crypto.getRandomValues(unsignedInt8Array); | ||
return Array.from(randomizedArray).map(integerToHex).join(''); | ||
} | ||
|
||
/** | ||
* Service used to protect against cross site request forgery during the github | ||
* connect process. The two methods, `generate()` and `validate(state)` are | ||
* served to initialy generate and then later validate a `state` string used | ||
* in the process. | ||
* | ||
* @class GithubStateService | ||
* @module code-corps-ember/services/github-state | ||
* @extends Ember.Service | ||
* @uses SessionService | ||
* @public | ||
*/ | ||
export default Service.extend({ | ||
/** | ||
* We use the injected session service to store the `state` in a way that | ||
* persists across tabs | ||
* | ||
* @property session | ||
* @type Ember.Service | ||
* @private | ||
*/ | ||
session: service(), | ||
|
||
/** | ||
* Generates and returns a random string. The string is also stored into the | ||
* user's session. | ||
* | ||
* This string can then be used as a `state` variable when navigating to | ||
* github's connect URL. Github will then return it upon succesful approval, | ||
* so it can be validated using the sibling `validate(state)` function. | ||
* | ||
* @method generate | ||
* @return {String} A randomly generated string. The string is also stored in the user's session. | ||
* @public | ||
*/ | ||
generate() { | ||
let githubState = generateRandomString(); | ||
// session service overrides `set`, so we need to use it directly | ||
get(this, 'session').set('data.githubState', githubState); | ||
return githubState; | ||
}, | ||
|
||
/** | ||
* Validates a state string. | ||
* | ||
* The string will be compared against another string, generated by the | ||
* sibling `generate()` function and stored into the current user's session, | ||
* to determine if it's valid or not. | ||
* | ||
* @method validate | ||
* @param {String} stateToCheck The string to check if valid | ||
* @return {Boolean} True if the provided string is also the one stored in the user's session, false otherwise. | ||
* @public | ||
*/ | ||
validate(stateToCheck) { | ||
let state = get(this, 'session.data.githubState'); | ||
return state && state === stateToCheck; | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { moduleFor, test } from 'ember-qunit'; | ||
import Test from 'ember-simple-auth/authenticators/test'; | ||
import setupSession from 'ember-simple-auth/initializers/setup-session'; | ||
import setupSessionService from 'ember-simple-auth/initializers/setup-session-service'; | ||
|
||
moduleFor('service:github-state', 'Unit | Service | github state', { | ||
needs: ['service:session'], | ||
beforeEach() { | ||
this.register('authenticator:test', Test); | ||
setupSession(this.registry); | ||
setupSessionService(this.registry); | ||
} | ||
}); | ||
|
||
test('validates a generated "state" as correct', function(assert) { | ||
assert.expect(1); | ||
|
||
let service = this.subject(); | ||
let state = service.generate(); | ||
|
||
assert.ok(service.validate(state), 'Validation passed.'); | ||
}); | ||
|
||
test('validates some random "state" as incorrect', function(assert) { | ||
assert.expect(1); | ||
|
||
let state = 'some random string'; | ||
let service = this.subject(); | ||
assert.notOk(service.validate(state), 'Validation failed.'); | ||
}); | ||
|
||
test('validates undefined and null as incorrect, even though stored state is also undefined', function(assert) { | ||
assert.expect(2); | ||
|
||
let service = this.subject(); | ||
assert.notOk(service.validate(undefined), 'Validation failed for undefined.'); | ||
assert.notOk(service.validate(null), 'Validation failed for null.'); | ||
}); |