Commit 6eaf2e6d authored by Randall Leeds's avatar Randall Leeds Committed by Aron Carroll

Break auth from app and add tests

Does what it says on the tin.
parent 07fc65c6
...@@ -2,6 +2,7 @@ imports = [ ...@@ -2,6 +2,7 @@ imports = [
'bootstrap' 'bootstrap'
'ngAnimate' 'ngAnimate'
'ngRoute' 'ngRoute'
'h.auth'
'h.controllers' 'h.controllers'
'h.directives' 'h.directives'
'h.app_directives' 'h.app_directives'
......
imports = [
'h.session'
]
class AuthController
this.$inject = ['$scope', '$timeout', 'session']
constructor: ( $scope, $timeout, session ) ->
timeout = null
this.submit = (form) ->
return unless form.$valid
data = {}
method = '$' + form.$name
for own key of session
delete session[key]
angular.extend session, $scope.model
session[method](
(session) ->
$scope.model = null
$scope.$broadcast 'success'
, (response) ->
{errors, reason} = response.data
if reason
if reason == 'Invalid username or password.'
form.password.$setValidity('response', false)
form.password.responseErrorMessage = reason
else
form.responseErrorMessage = reason
else
form.responseErrorMessage = null
for field, error of errors
form[field].$setValidity('response', false)
form[field].responseErrorMessage = error
$scope.$broadcast 'error', form.$name
)
$scope.$on '$destroy', ->
if timeout
$timeout.cancel timeout
$scope.$watchCollection 'model', (value) ->
# Reset the auth forms after five minutes of inactivity
if timeout
$timeout.cancel timeout
# If the model is not empty, start the timeout
if value
timeout = $timeout ->
$scope.model = null
$scope.$broadcast 'timeout'
, 300000
authDirective = ->
controller: 'AuthController'
link: (scope, elem, attrs, [form, auth]) ->
elem.on 'submit', (event) ->
scope.$apply ->
$target = angular.element event.target
$form = $target.controller('form')
$form.responseErrorMessage = null
for ctrl in $form.$error.response?.slice?() or []
ctrl.$setValidity('response', true)
auth.submit($form)
scope.$on 'error', (event) ->
scope[attrs.onError]?(event)
scope.$on 'success', (event) ->
scope[attrs.onSuccess]?(event)
scope.$on 'timeout', (event) ->
scope[attrs.onTimeout]?(event)
scope.$watch 'model', (value) ->
if value is null
form.$setPristine()
require: ['form', 'auth']
restrict: 'C'
scope: true
angular.module('h.auth', imports)
.controller('AuthController', AuthController)
.directive('auth', authDirective)
...@@ -16,10 +16,8 @@ class App ...@@ -16,10 +16,8 @@ class App
scope: scope:
frame: frame:
visible: false visible: false
sheet:
collapsed: true
tab: null
ongoingHighlightSwitch: false ongoingHighlightSwitch: false
sheet: {}
sorts: [ sorts: [
'Newest' 'Newest'
'Oldest' 'Oldest'
...@@ -48,12 +46,12 @@ class App ...@@ -48,12 +46,12 @@ class App
frame: $scope.frame or @scope.frame frame: $scope.frame or @scope.frame
socialView: annotator.socialView socialView: annotator.socialView
ongoingHighlightSwitch: false ongoingHighlightSwitch: false
model: session
search: search:
facets: SEARCH_FACETS facets: SEARCH_FACETS
values: SEARCH_VALUES values: SEARCH_VALUES
query: $location.search() query: $location.search()
show: not angular.equals($location.search(), {}) show: not angular.equals($location.search(), {})
session: session
_reset() _reset()
...@@ -66,18 +64,16 @@ class App ...@@ -66,18 +64,16 @@ class App
$scope.initUpdater() $scope.initUpdater()
$scope.reloadAnnotations() $scope.reloadAnnotations()
$scope.$watch 'model.personas', (newValue, oldValue) => $scope.$watch 'session.personas', (newValue, oldValue) =>
if newValue?.length if newValue?.length
unless $scope.model.persona and $scope.model.persona in newValue unless $scope.session.persona and $scope.session.persona in newValue
$scope.model.persona = newValue[0] $scope.session.persona = newValue[0]
else else
$scope.model.persona = null $scope.session.persona = null
$scope.$watch 'model.persona', (newValue, oldValue) =>
$scope.sheet.collapsed = true
$scope.$watch 'session.persona', (newValue, oldValue) =>
unless annotator.discardDrafts() unless annotator.discardDrafts()
$scope.model.persona = oldValue $scope.session.persona = oldValue
return return
plugins.Auth?.element.removeData('annotator:headers') plugins.Auth?.element.removeData('annotator:headers')
...@@ -135,14 +131,13 @@ class App ...@@ -135,14 +131,13 @@ class App
annotator.show() annotator.show()
annotator.host.notify method: 'showFrame', params: routeName annotator.host.notify method: 'showFrame', params: routeName
else if oldValue else if oldValue
$scope.sheet.collapsed = true
annotator.hide() annotator.hide()
annotator.host.notify method: 'hideFrame', params: routeName annotator.host.notify method: 'hideFrame', params: routeName
for p in annotator.providers for p in annotator.providers
p.channel.notify method: 'setActiveHighlights' p.channel.notify method: 'setActiveHighlights'
$scope.$watch 'sheet.collapsed', (hidden) -> $scope.$watch 'sheet.show', (visible) ->
$scope.sheet.tab = if hidden then null else 'login' $scope.sheet.tab = if visible then 'login' else null
$scope.$watch 'sheet.tab', (tab) -> $scope.$watch 'sheet.tab', (tab) ->
$timeout -> $timeout ->
...@@ -167,25 +162,6 @@ class App ...@@ -167,25 +162,6 @@ class App
filter = streamfilter.getFilter() filter = streamfilter.getFilter()
sock.send(JSON.stringify({filter})) sock.send(JSON.stringify({filter}))
$scope.$on 'authTimeout', ->
# Skip the reset if we're logged in
unless $scope.model.persona
$scope.$broadcast 'reset'
flash 'info',
'For your security, the forms have been reset due to inactivity.'
$scope.$on 'showAuth', (event, show=true) ->
$scope.sheet.collapsed = !show
$scope.$on 'reset', _reset
$scope.$on 'success', (event, action) ->
angular.extend $scope.model, session.model
if action == 'forgot'
$scope.sheet.tab = 'activate'
else
$scope.sheet.collapsed = true
$rootScope.viewState = $rootScope.viewState =
sort: '' sort: ''
view: 'Screen' view: 'Screen'
...@@ -377,6 +353,14 @@ class App ...@@ -377,6 +353,14 @@ class App
cleanup (a for a in annotations when a.thread) cleanup (a for a in annotations when a.thread)
annotator.subscribe 'annotationsLoaded', cleanup annotator.subscribe 'annotationsLoaded', cleanup
$scope.authSuccess = ->
$scope.sheet.show = false
$scope.authTimeout = ->
flash 'info',
'For your security, the forms have been reset due to inactivity.'
_reset()
$scope.initUpdater = (failureCount=0) -> $scope.initUpdater = (failureCount=0) ->
_dfdSock = $q.defer() _dfdSock = $q.defer()
_sock = socket() _sock = socket()
...@@ -409,7 +393,7 @@ class App ...@@ -409,7 +393,7 @@ class App
unless data instanceof Array then data = [data] unless data instanceof Array then data = [data]
p = $scope.model.persona p = $scope.session.persona
user = if p? then "acct:" + p.username + "@" + p.provider else '' user = if p? then "acct:" + p.username + "@" + p.provider else ''
unless data instanceof Array then data = [data] unless data instanceof Array then data = [data]
...@@ -699,72 +683,6 @@ class Annotation ...@@ -699,72 +683,6 @@ class Annotation
$scope.model.highlightText.replace regexp, annotator.highlighter $scope.model.highlightText.replace regexp, annotator.highlighter
class Auth
this.$inject = ['$scope', '$timeout', 'session']
constructor: ( $scope, $timeout, session) ->
base =
username: null
email: null
password: null
code: null
_timeout = null
_reset = ->
angular.extend $scope.model, base
for own _, ctrl of $scope when angular.isFunction ctrl?.$setPristine
ctrl.$setPristine()
form.responseErrorMessage = null
_updateFormValidity = (form, reason) ->
if reason == 'Invalid username or password.'
form.password.$setValidity('invalid', false)
else if reason
form.responseErrorMessage = reason
_updateFieldValidity = (form, errors) ->
for field, error of errors
form[field].$setValidity('response', false)
form[field].responseErrorMessage = error
_resetFormValidity = (form) ->
form.password.$setValidity('invalid', true)
for own _, field of form when field.$setValidity
field.$setValidity('response', true)
field.responseErrorMessage = null
_error = (form, data) ->
{errors, reason} = data
_updateFormValidity(form, reason)
_updateFieldValidity(form, errors)
$scope.$emit('error', form.$name)
_startTimeout = ->
# Reset the auth forms after five minutes of inactivity
if _timeout then $timeout.cancel _timeout
_timeout = $timeout (-> $scope.$emit 'authTimeout'), 3000000
$scope.$on 'reset', _reset
$scope.$watchCollection 'model', ->
# (Re)start (i.e., delay) the authentication form timeout
unless $scope.sheet.collapsed
_startTimeout()
$scope.submit = (form) ->
_resetFormValidity(form)
angular.extend session, $scope.model
return unless form.$valid
promise = session["$#{form.$name}"] ->
$scope.$emit('success', form.$name)
promise.then(_reset, _error.bind(null, form))
class Editor class Editor
this.$inject = [ this.$inject = [
'$location', '$routeParams', '$sce', '$scope', '$location', '$routeParams', '$sce', '$scope',
...@@ -1104,7 +1022,6 @@ class Notification ...@@ -1104,7 +1022,6 @@ class Notification
angular.module('h.controllers', imports) angular.module('h.controllers', imports)
.controller('AppController', App) .controller('AppController', App)
.controller('AnnotationController', Annotation) .controller('AnnotationController', Annotation)
.controller('AuthController', Auth)
.controller('EditorController', Editor) .controller('EditorController', Editor)
.controller('ViewerController', Viewer) .controller('ViewerController', Viewer)
.controller('SearchController', Search) .controller('SearchController', Search)
......
...@@ -101,17 +101,6 @@ ol { ...@@ -101,17 +101,6 @@ ol {
top: 1em; top: 1em;
} }
&.collapsed {
display: none;
overflow: hidden;
}
footer {
font-size: .8em;
font-family: $sansFontFamily;
text-align: right;
}
input:not([type="submit"]) { width: 100%; } input:not([type="submit"]) { width: 100%; }
} }
......
assert = chai.assert
sinon.assert.expose assert, prefix: null
class MockSession
$login: sinon.stub()
$register: (callback, errback) ->
errback
data:
errors:
username: 'taken'
reason: 'registration error'
describe 'h.auth', ->
beforeEach module('h.auth')
beforeEach module ($provide) ->
$provide.value '$timeout', sinon.spy()
$provide.value 'flash', sinon.spy()
$provide.value 'session', new MockSession()
return
describe 'AuthController', ->
$scope = null
$timeout = null
auth = null
session = null
beforeEach inject ($controller, $rootScope, _$timeout_, _session_) ->
$scope = $rootScope.$new()
$timeout = _$timeout_
auth = $controller 'AuthController', {$scope}
session = _session_
session.$login.reset()
describe '#submit()', ->
it 'should call session methods on submit', ->
auth.submit
$name: 'login'
$valid: true
assert.called session.$login
it 'should do nothing when the form is invalid', ->
auth.submit
$name: 'login'
$valid: false
assert.notCalled session.$login
it 'should set response errors', ->
form =
$name: 'register'
$valid: true
username:
$setValidity: sinon.stub()
email:
$setValidity: sinon.stub()
auth.submit(form)
assert.calledWith form.username.$setValidity, 'response', false
assert.equal form.username.responseErrorMessage, 'taken'
assert.equal form.responseErrorMessage, 'registration error'
describe 'timeout', ->
it 'should happen after a period of inactivity', ->
sinon.spy $scope, '$broadcast'
$scope.model =
username: 'test'
email: 'test@example.com'
password: 'secret'
code: '1234'
$scope.$digest()
assert.called $timeout
$timeout.lastCall.args[0]()
assert.isNull $scope.model, 'the model is erased'
assert.calledWith $scope.$broadcast, 'timeout'
it 'should not happen if the model is empty', ->
$scope.$digest()
assert.notCalled $timeout
describe 'authDirective', ->
elem = null
session = null
$rootScope = null
$scope = null
beforeEach inject ($compile, _$rootScope_, _session_) ->
elem = angular.element(
'''
<div class="auth" ng-form="form"
on-error="stub" on-success="stub" on-timeout="stub">
<form name="login">
<input type="text" name="username" ng-model="username"></input>
</form>
</div>
'''
)
session = _session_
$rootScope = _$rootScope_
$scope = $compile(elem)($rootScope).scope()
$scope.$digest()
it 'should reset response errors before submit', ->
$scope.form.login.responseErrorMessage = 'test'
$scope.form.login.username.$setValidity('response', false)
assert.isFalse $scope.form.login.$valid
elem.find('input').trigger('submit')
assert.isTrue $scope.form.login.$valid
assert.isNull $scope.form.login.responseErrorMessage
it 'should reset to pristine state when the model is reset', ->
$scope.form.$setDirty()
$scope.$digest()
assert.isFalse $scope.form.$pristine
$scope.model = null
$scope.$digest()
assert.isTrue $scope.form.$pristine
it 'should invoke handlers set by attributes', ->
$scope.stub = sinon.stub()
for event in ['error', 'success', 'timeout']
$scope.stub.reset()
$scope.$broadcast(event)
assert.called $scope.stub
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment