update rcs token refresh to use new endpoint

added a higher order function for generating a refresh function with
and initial jwt. this was moved from the serviceRCELoader to its own
module since it could be used by other modules in the future. i also
made some other improvements like returning a promise to make error
handleing possible (while keeping the existing callback
functionality), and not making extra api requests if the function is
called multiple times before the the api responds.

closes CNVS-35199

test plan:
- edit a wiki page with rcs enabled
- wait an hour
  - or - temporariy change the expiration of jwts in canvas
    - open lib/canvas/security/services_jwt.rb
    - change the 3600 in the create_payload class method to 30
    - reload the edit page
    - wait 30 seconds instead
- make sure you can still make api requests
  - click load more on something, upload a file, etc

Change-Id: I21c89bfc82d4bf1ae3ff25b467b5f6d1e674cfac
Reviewed-on: https://gerrit.instructure.com/103303
Reviewed-by: Simon Williams <simon@instructure.com>
Tested-by: Jenkins
QA-Review: Tucker McKnight <tmcknight@instructure.com>
Product-Review: Brent Burgoyne <bburgoyne@instructure.com>
This commit is contained in:
Brent Burgoyne 2017-02-27 10:29:04 -07:00
parent b36f17b65b
commit d225415e9f
4 changed files with 87 additions and 9 deletions

24
app/jsx/shared/jwt.js Normal file
View File

@ -0,0 +1,24 @@
import axios from 'axios'
export function refreshFn (initialToken) {
let token = initialToken
let promise = null
return (done) => {
if (promise === null) {
promise = axios
.post('/api/v1/jwts/refresh', { jwt: token })
.then((resp) => {
promise = null
token = resp.data.token
return token
})
}
if (typeof done === 'function') {
promise.then(done)
}
return promise
}
}

View File

@ -1,16 +1,11 @@
import $ from 'jquery'
import _ from 'underscore'
import {refreshFn as refreshToken} from 'jsx/shared/jwt'
import editorOptions from 'jsx/shared/rce/editorOptions'
import loadEventListeners from 'jsx/shared/rce/loadEventListeners'
import polyfill from 'jsx/shared/rce/polyfill'
import splitAssetString from 'compiled/str/splitAssetString'
let refreshToken = function(callback){
return $.post("/api/v1/jwts").done((response)=>{
callback(response.token)
})
}
let RCELoader = {
preload() {
this.loadRCE(function(){})
@ -32,7 +27,7 @@ import splitAssetString from 'compiled/str/splitAssetString'
let context = splitAssetString(ENV.context_asset_string)
let props = {
jwt: ENV.JWT,
refreshToken: refreshToken,
refreshToken: refreshToken(ENV.JWT),
host: ENV.RICH_CONTENT_APP_HOST,
canUploadFiles: ENV.RICH_CONTENT_CAN_UPLOAD_FILES,
contextType: context[0],

View File

@ -1,10 +1,11 @@
define [
'jquery'
'jsx/shared/rce/serviceRCELoader'
'jsx/shared/jwt',
'helpers/editorUtils'
'helpers/fakeENV'
'helpers/fixtures'
], ($, RCELoader, editorUtils, fakeENV, fixtures) ->
], ($, RCELoader, jwt, editorUtils, fakeENV, fixtures) ->
QUnit.module 'loadRCE',
setup: ->
fakeENV.setup()
@ -160,6 +161,8 @@ define [
@sidebar = {}
@rce = { renderSidebarIntoDiv: sinon.stub().callsArgWith(2, @sidebar) }
sinon.stub(RCELoader, 'loadRCE').callsArgWith(0, @rce)
@refreshToken = sinon.spy()
@stub(jwt, 'refreshFn').returns(@refreshToken)
teardown: ->
fakeENV.teardown()
@ -191,7 +194,8 @@ define [
RCELoader.loadSidebarOnTarget(@$div, cb)
ok @rce.renderSidebarIntoDiv.called
props = @rce.renderSidebarIntoDiv.args[0][1]
equal(typeof props.refreshToken, 'function')
ok jwt.refreshFn.calledWith(props.jwt)
equal(props.refreshToken, @refreshToken)
test 'passes brand config json url', ->
ENV.active_brand_config_json_url = {}

View File

@ -0,0 +1,55 @@
define([
'jsx/shared/jwt',
'axios'
], (jwt, axios) => {
let thenStub
let promise
let token
let newToken
let refreshFn
QUnit.module('Jwt refreshFn', {
setup () {
token = 'testjwt'
newToken = 'newtoken'
promise = Promise.resolve(newToken)
sinon.spy(promise, 'then')
thenStub = sinon.stub().returns(promise)
this.stub(axios, 'post').returns({ then: thenStub })
refreshFn = jwt.refreshFn(token)
}
})
test('posts token to refresh endpoint', () => {
refreshFn()
ok(axios.post.calledWithMatch('/api/v1/jwts/refresh', { jwt: token }))
})
test('only posts once if called multiple times before response', () => {
refreshFn()
refreshFn()
equal(axios.post.callCount, 1)
})
test('returns promise resolved after request', () => {
equal(refreshFn(), promise)
})
test('calls callbacks with new token', () => {
const spy = sinon.spy()
refreshFn(spy)
ok(promise.then.calledWith(spy))
})
test('gets token from response data', () => {
refreshFn()
equal(thenStub.firstCall.args[0]({ data: { token: newToken } }), newToken)
})
test('updates token in closure', () => {
refreshFn()
thenStub.firstCall.args[0]({ data: { token: newToken } })
refreshFn()
ok(axios.post.calledWithMatch('/api/v1/jwts/refresh', { jwt: newToken }))
})
})