diff --git a/app/core/auth.coffee b/app/core/auth.coffee index 79c9f8ade..0056b91ad 100644 --- a/app/core/auth.coffee +++ b/app/core/auth.coffee @@ -14,40 +14,6 @@ init = -> Backbone.listenTo me, 'sync', -> Backbone.Mediator.publish('auth:me-synced', me: me) -module.exports.createUser = (userObject, failure=backboneFailure, nextURL=null) -> - user = new User(userObject) - user.notyErrors = false - user.save({}, { - error: (model, jqxhr, options) -> - error = parseServerError(jqxhr.responseText) - property = error.property if error.property - if jqxhr.status is 409 and property is 'name' - anonUserObject = _.omit(userObject, 'name') - module.exports.createUser anonUserObject, failure, nextURL - else - genericFailure(jqxhr) - success: -> if nextURL then window.location.href = nextURL else window.location.reload() - }) - -module.exports.createUserWithoutReload = (userObject, failure=backboneFailure) -> - user = new User(userObject) - user.save({}, { - error: failure - success: -> - Backbone.Mediator.publish('created-user-without-reload') - }) - -module.exports.loginUser = (userObject, failure=genericFailure, nextURL=null) -> - console.log 'logging in as', userObject.email - jqxhr = $.post('/auth/login', - { - username: userObject.email, - password: userObject.password - }, - (model) -> if nextURL then window.location.href = nextURL else window.location.reload() - ) - jqxhr.fail(failure) - module.exports.logoutUser = -> # TODO: Refactor to use User.logout FB?.logout?() diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 3e409016e..60842638d 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -243,6 +243,7 @@ login: sign_up: "Create Account" + email_or_username: "Email or username" log_in: "Log In" logging_in: "Logging In" log_out: "Log Out" diff --git a/app/models/User.coffee b/app/models/User.coffee index 678297279..6f7e98c2a 100644 --- a/app/models/User.coffee +++ b/app/models/User.coffee @@ -275,6 +275,13 @@ module.exports = class User extends CocoModel options.data.facebookAccessToken = application.facebookHandler.token() @fetch(options) + loginPasswordUser: (usernameOrEmail, password, options={}) -> + options.url = '/auth/login' + options.type = 'POST' + options.data ?= {} + _.extend(options.data, { username: usernameOrEmail, password }) + @fetch(options) + makeCoursePrepaid: -> coursePrepaid = @get('coursePrepaid') return null unless coursePrepaid diff --git a/app/styles/courses/hour-of-code-view.sass b/app/styles/courses/hour-of-code-view.sass deleted file mode 100644 index 1f66367e6..000000000 --- a/app/styles/courses/hour-of-code-view.sass +++ /dev/null @@ -1,19 +0,0 @@ -#hour-of-code-view - - hr - border-top: 1px solid grey - margin: 30px 20px - - #site-content-area - padding: 20px 300px - - h1 - margin-bottom: 40px - p - margin: 20px - - h3 - margin-top: 50px - - ul - margin-bottom: 50px \ No newline at end of file diff --git a/app/styles/courses/student-log-in-modal.sass b/app/styles/courses/student-log-in-modal.sass deleted file mode 100644 index 0fd2e0cf0..000000000 --- a/app/styles/courses/student-log-in-modal.sass +++ /dev/null @@ -1,7 +0,0 @@ -#student-log-in-modal - #log-in-btn - min-width: 30% - margin-bottom: 10px - - .form - margin: 0 25% \ No newline at end of file diff --git a/app/styles/courses/student-sign-up-modal.sass b/app/styles/courses/student-sign-up-modal.sass deleted file mode 100644 index 6d06268f8..000000000 --- a/app/styles/courses/student-sign-up-modal.sass +++ /dev/null @@ -1,10 +0,0 @@ -#student-sign-up-modal - #sign-up-btn - min-width: 30% - margin-bottom: 10px - - .form - margin: 0 25% - - .modal-dialog - margin-top: 0 diff --git a/app/styles/modal/auth-modal.sass b/app/styles/modal/auth-modal.sass index a9ec10461..98805ab74 100644 --- a/app/styles/modal/auth-modal.sass +++ b/app/styles/modal/auth-modal.sass @@ -143,7 +143,7 @@ //- Primary auth button - #login-button + #login-btn position: absolute top: 186px height: 70px diff --git a/app/templates/core/auth.jade b/app/templates/core/auth-modal.jade similarity index 64% rename from app/templates/core/auth.jade rename to app/templates/core/auth-modal.jade index 1b65b9e09..8bcc27620 100644 --- a/app/templates/core/auth.jade +++ b/app/templates/core/auth-modal.jade @@ -3,7 +3,7 @@ img(src="/images/pages/modal/auth/login-background.png", draggable="false").auth-modal-background h1(data-i18n="login.log_in") - div#close-modal + #close-modal span.glyphicon.glyphicon-remove .auth-form-content @@ -11,24 +11,40 @@ if showRequiredError .alert.alert-success span(data-i18n="signup.required") + + #unknown-error-alert.alert.alert-danger.hide(data-i18n="loading_error.unknown") form.form .form-group - label.control-label(for="email") - span(data-i18n="general.email") + label.control-label(for="username-or-email-input") + span(data-i18n="login.email_or_username") | : .input-border - input#email.input-large.form-control(name="email", type="email", value=formValues.email) + input#username-or-email-input.input-large.form-control( + name="emailOrUsername" + value=view.previousFormInputs.email + ) .form-group - div#recover-account-wrapper - a(data-toggle="coco-modal", data-target="core/RecoverModal", data-i18n="login.forgot_password")#link-to-recover + #recover-account-wrapper + a#link-to-recover( + data-toggle="coco-modal" + data-target="core/RecoverModal" + data-i18n="login.forgot_password" + ) label.control-label(for="password") span(data-i18n="general.password") | : .input-border - input#password.input-large.form-control(name="password", type="password", value=formValues.password) + input#password-input.input-large.form-control( + name="password" + type="password" + value=view.previousFormInputs.password + ) - input.btn.btn-lg.btn-illustrated.btn-block.btn-success#login-button(value=translate("login.log_in"), type="submit") + input#login-btn.btn.btn-lg.btn-illustrated.btn-block.btn-success( + value=translate("login.log_in") + type="submit" + ) .wait.secret h3(data-i18n="login.logging_in") diff --git a/app/templates/courses/hour-of-code-view.jade b/app/templates/courses/hour-of-code-view.jade deleted file mode 100644 index c37ecdce1..000000000 --- a/app/templates/courses/hour-of-code-view.jade +++ /dev/null @@ -1,74 +0,0 @@ -extends /templates/base - -block content - .pull-right - if me.isAnonymous() - a(href="/teachers") - span(data-i18n="courses.teachers_click") - span ! - else - a(href="/teachers/classes") - span(data-i18n="courses.teachers_click") - span ! - br - - h1.text-center(data-i18n="courses.welcome_to_hoc") - - #main-content - .well.text-center - if !me.isAnonymous() - p - strong.spr(data-i18n="courses.logged_in_as") - strong= me.get('name') || me.get('email') - - p - span.spr(data-i18n="courses.not_you") - a#log-out-link(data-i18n="login.logout") - - hr - - if !view.lastLevel - p - strong(data-i18n="courses.ready_to_play") - - p - button#start-new-game-btn.btn.btn-success.btn-lg(data-i18n="courses.start_new_game") - else - - p - strong(data-i18n="courses.welcome_back") - p - button#continue-playing-btn.btn.btn-success.btn-lg(data-i18n="courses.continue_playing") - p - em.spr - span(data-i18n="clans.last_played") - span.spr : - span= view.lastLevel.get('name').replace('Course :', '') - - if me.isAnonymous() - p - strong(data-i18n="courses.more_options") - p - button#start-new-game-btn.btn.btn-default.btn-lg(data-i18n="courses.start_new_game") - - if me.isAnonymous() - p - span.spr - - span.text-uppercase(data-i18n="general.or") - span.spl - - - p - button#log-in-btn.btn.btn-default.btn-lg(data-i18n="login.log_in") - - #begin-hoc-area.hide - h2.text-center(data-i18n="common.loading") - .progress.progress-striped.active - .progress-bar(style="width: 100%") - - - h3.text-center.text-uppercase(data-i18n="courses.play_now_learn_header") - ul - li(data-i18n="courses.play_now_learn_1") - li(data-i18n="courses.play_now_learn_2") - li(data-i18n="courses.play_now_learn_3") - li(data-i18n="courses.play_now_learn_4") diff --git a/app/templates/courses/student-log-in-modal.jade b/app/templates/courses/student-log-in-modal.jade deleted file mode 100644 index 60ce36877..000000000 --- a/app/templates/courses/student-log-in-modal.jade +++ /dev/null @@ -1,30 +0,0 @@ -extends /templates/core/modal-base - -block modal-header-content - .clearfix - - -block modal-body-content - .text-center - h2.modal-title(data-i18n="login.log_in") - - form.form - .form-group - label.control-label(for="email") - span(data-i18n="general.email") - input#email.input-large.form-control(name="email", type="email") - .form-group - label.control-label(for="password") - span(data-i18n="general.password") - input#password.input-large.form-control(name="password", type="password") - - #errors-alert.alert.alert-danger.hide - - .text-center - input#log-in-btn.btn.btn-default(data-i18n="[value]login.log_in", type="submit") - p - a#create-new-account-link(data-i18n="login.signup_switch") - p - a(data-toggle="coco-modal", data-target="core/RecoverModal", data-i18n="login.forgot_password") - -block modal-footer-content diff --git a/app/templates/courses/student-sign-up-modal.jade b/app/templates/courses/student-sign-up-modal.jade deleted file mode 100644 index 8454e228d..000000000 --- a/app/templates/courses/student-sign-up-modal.jade +++ /dev/null @@ -1,46 +0,0 @@ -extends /templates/core/modal-base - -block modal-header-content - .clearfix - - -block modal-body-content - .text-center - h2.modal-title(data-i18n="signup.sign_up") - - form.form - .form-group - label.control-label(for="email") - span(data-i18n="general.email") - input#email.input-large.form-control(name="email", type="email") - .help-block(data-i18n="courses.use_school_email") - .form-group - label.control-label(for="name") - span(data-i18n="general.name") - if me.get('name') - input#name.input-large.form-control(name="name", type="text", value="#{me.get('name')}") - else - input#name.input-large.form-control(name="name", type="text", value="", placeholder="e.g. Alex W the Skater") - .help-block(data-i18n="courses.unique_name") - .form-group - label.control-label(for="password") - span(data-i18n="general.password") - input#password.input-large.form-control(name="password", type="password") - .help-block(data-i18n="courses.pick_something") - .form-group - label.control-label(for="class-code-input") - span(data-i18n="courses.class_code") - input#class-code-input.input-large.form-control(name="classCode", value=view.classCode) - .help-block(data-i18n="courses.optional_ask") - .form-group - label.control-label(for="school-input") - span(data-i18n="signup.school_name") - input#school-input.input-large.form-control(name="schoolName", data-i18n="[placeholder]signup.school_name_placeholder") - .help-block(data-i18n="courses.optional_school") - - #errors-alert.alert.alert-danger.hide - - .text-center - input#sign-up-btn.btn.btn-default(data-i18n="[value]signup.sign_up", type="submit") - -block modal-footer-content diff --git a/app/views/core/AuthModal.coffee b/app/views/core/AuthModal.coffee index 0791d396c..13609655a 100644 --- a/app/views/core/AuthModal.coffee +++ b/app/views/core/AuthModal.coffee @@ -1,6 +1,5 @@ ModalView = require 'views/core/ModalView' -template = require 'templates/core/auth' -{loginUser, createUser, me} = require 'core/auth' +template = require 'templates/core/auth-modal' forms = require 'core/forms' User = require 'models/User' application = require 'core/application' @@ -12,15 +11,11 @@ module.exports = class AuthModal extends ModalView events: 'click #switch-to-signup-btn': 'onSignupInstead' - 'click #github-login-button': 'onGitHubLoginClicked' 'submit form': 'onSubmitForm' 'keyup #name': 'onNameChange' 'click #gplus-login-btn': 'onClickGPlusLoginButton' 'click #facebook-login-btn': 'onClickFacebookLoginButton' 'click #close-modal': 'hide' - - subscriptions: - 'errors:server-error': 'onServerError' # Initialization @@ -32,15 +27,6 @@ module.exports = class AuthModal extends ModalView application.gplusHandler.loadAPI({ success: => _.defer => @$('#gplus-login-btn').attr('disabled', false) }) application.facebookHandler.loadAPI({ success: => _.defer => @$('#facebook-login-btn').attr('disabled', false) }) - getRenderData: -> - c = super() - c.showRequiredError = @options.showRequiredError - c.showSignupRationale = @options.showSignupRationale - c.mode = @mode - c.formValues = @previousFormInputs or {} - c.me = me - c - afterRender: -> super() @playSound 'game-menu-open' @@ -58,16 +44,30 @@ module.exports = class AuthModal extends ModalView @playSound 'menu-button-click' e.preventDefault() forms.clearFormAlerts(@$el) + @$('#unknown-error-alert').addClass('hide') userObject = forms.formToObject @$el res = tv4.validateMultiple userObject, formSchema return forms.applyErrorsToForm(@$el, res.errors) unless res.valid - @enableModalInProgress(@$el) # TODO: part of forms - loginUser userObject, null, window.nextURL - - onServerError: (e) -> # TODO: work error handling into a separate forms system - @disableModalInProgress(@$el) - - + new Promise(me.loginPasswordUser(userObject.emailOrUsername, userObject.password).then) + .then(-> + if window.nextURL then window.location.href = window.nextURL else window.location.reload() + ) + .catch((jqxhr) => + showingError = false + if jqxhr.status is 401 + errorID = jqxhr.responseJSON.errorID + if errorID is 'not-found' + forms.setErrorToProperty(@$el, 'emailOrUsername', $.i18n.t('loading_error.not_found')) + showingError = true + if errorID is 'wrong-password' + forms.setErrorToProperty(@$el, 'password', $.i18n.t('account_settings.wrong_password')) + showingError = true + + if not showingError + @$('#unknown-error-alert').removeClass('hide') + ) + + # Google Plus onClickGPlusLoginButton: -> @@ -136,6 +136,14 @@ module.exports = class AuthModal extends ModalView formSchema = { type: 'object' - properties: _.pick(User.schema.properties, 'email', 'password') - required: ['email', 'password'] + properties: { + emailOrUsername: { + $or: [ + User.schema.properties.name + User.schema.properties.email + ] + } + password: User.schema.properties.password + } + required: ['emailOrUsername', 'password'] } diff --git a/app/views/core/CreateAccountModal.coffee b/app/views/core/CreateAccountModal.coffee index c7150695c..440fe1a1b 100644 --- a/app/views/core/CreateAccountModal.coffee +++ b/app/views/core/CreateAccountModal.coffee @@ -1,6 +1,5 @@ ModalView = require 'views/core/ModalView' template = require 'templates/core/create-account-modal' -{loginUser, createUser, me} = require 'core/auth' forms = require 'core/forms' User = require 'models/User' application = require 'core/application' diff --git a/app/views/courses/HourOfCodeView.coffee b/app/views/courses/HourOfCodeView.coffee deleted file mode 100644 index 3f1ce8464..000000000 --- a/app/views/courses/HourOfCodeView.coffee +++ /dev/null @@ -1,108 +0,0 @@ -app = require 'core/application' -CocoCollection = require 'collections/CocoCollection' -Course = require 'models/Course' -CourseInstance = require 'models/CourseInstance' -RootView = require 'views/core/RootView' -template = require 'templates/courses/hour-of-code-view' -utils = require 'core/utils' -LevelSession = require 'models/LevelSession' -Level = require 'models/Level' -ChooseLanguageModal = require 'views/courses/ChooseLanguageModal' -StudentLogInModal = require 'views/courses/StudentLogInModal' -StudentSignUpModal = require 'views/courses/StudentSignUpModal' -auth = require 'core/auth' - -module.exports = class HourOfCodeView extends RootView - id: 'hour-of-code-view' - template: template - - events: - 'click #continue-playing-btn': 'onClickContinuePlayingButton' - 'click #start-new-game-btn': 'onClickStartNewGameButton' - 'click #log-in-btn': 'onClickLogInButton' - 'click #log-out-link': 'onClickLogOutLink' - - initialize: -> - @setUpHourOfCode() - @courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance}) - @listenToOnce @courseInstances, 'sync', @onCourseInstancesLoaded - @courseInstances.comparator = (ci) -> return ci.get('classroomID') + ci.get('courseID') - @supermodel.loadCollection(@courseInstances, 'course_instances', { cache: false }) - - onCourseInstancesLoaded: -> - @hourOfCodeCourseInstance = @courseInstances.findWhere({hourOfCode: true}) - if @hourOfCodeCourseInstance - @sessions = new CocoCollection([], { - url: "/db/course_instance/#{@hourOfCodeCourseInstance.id}/level_sessions" - model: LevelSession - }) - @sessions.comparator = 'created' - @listenTo @sessions, 'sync', @onSessionsLoaded - @supermodel.loadCollection(@sessions, 'sessions', { cache: false }) - - onSessionsLoaded: -> - @lastSession = @sessions.last() - if @lastSession - @lastLevel = new Level() - levelData = @lastSession.get('level') - @supermodel.loadModel(@lastLevel, { - url: "/db/level/#{levelData.original}/version/#{levelData.majorVersion}" - data: { - project: 'name,slug' - } - }) - - setUpHourOfCode: -> - # If we haven't tracked this player as an hourOfCode player yet, and it's a new account, we do that now. - elapsed = new Date() - new Date(me.get('dateCreated')) - if not me.get('hourOfCode') and (elapsed < 5 * 60 * 1000 or me.get('anonymous')) - me.set('hourOfCode', true) - me.patch() - $('body').append($('')) - window.tracker?.trackEvent 'Hour of Code Begin' - - onClickContinuePlayingButton: -> - url = @continuePlayingLink() - window.tracker?.trackEvent 'HoC continue playing ', category: 'HoC', label: url - app.router.navigate(url, { trigger: true }) - - afterRender: -> - super() - @onClickStartNewGameButton() if @getQueryVariable('go') and not @lastLevel - - onClickStartNewGameButton: -> - # User without hour of code course instance, creates one, starts playing - modal = new ChooseLanguageModal({ - logoutFirst: @hourOfCodeCourseInstance? - }) - @openModalView(modal) - @listenToOnce modal, 'set-language', @startHourOfCodePlay - window.tracker?.trackEvent 'Start New Game', category: 'HoC', label: 'HoC Start New Game' - - continuePlayingLink: -> - ci = @hourOfCodeCourseInstance - "/play/level/#{@lastLevel.get('slug')}?course=#{ci.get('courseID')}&course-instance=#{ci.id}" - - startHourOfCodePlay: -> - @$('#main-content').hide() - @$('#begin-hoc-area').removeClass('hide') - hocCourseInstance = new CourseInstance() - hocCourseInstance.upsertForHOC({cache: false}) - @listenToOnce hocCourseInstance, 'sync', -> - url = hocCourseInstance.firstLevelURL() - document.location.href = url - - onClickLogInButton: -> - modal = new StudentLogInModal() - @openModalView(modal) - modal.on 'want-to-create-account', @onWantToCreateAccount, @ - window.tracker?.trackEvent 'Started Login', category: 'HoC', label: 'HoC Login' - - onWantToCreateAccount: -> - modal = new StudentSignUpModal() - @openModalView(modal) - window.tracker?.trackEvent 'Started Signup', category: 'HoC', label: 'HoC Sign Up' - - onClickLogOutLink: -> - window.tracker?.trackEvent 'Log Out', category: 'HoC', label: 'HoC Log Out' - auth.logoutUser() diff --git a/app/views/courses/StudentLogInModal.coffee b/app/views/courses/StudentLogInModal.coffee deleted file mode 100644 index 38ceda440..000000000 --- a/app/views/courses/StudentLogInModal.coffee +++ /dev/null @@ -1,43 +0,0 @@ -ModalView = require 'views/core/ModalView' -template = require 'templates/courses/student-log-in-modal' -auth = require 'core/auth' -forms = require 'core/forms' -User = require 'models/User' - -module.exports = class StudentLogInModal extends ModalView - id: 'student-log-in-modal' - template: template - - events: - 'click #log-in-btn': 'onClickLogInButton' - 'submit form': 'onSubmitForm' - 'click #create-new-account-link': 'onClickCreateNewAccountLink' - - onSubmitForm: (e) -> - e.preventDefault() - @login() - - onClickLogInButton: -> - @login() - - login: -> - # TODO: doesn't track failed login - window.tracker?.trackEvent 'Finished Login', category: 'Courses', label: 'Courses Student Login' - data = forms.formToObject @$el - @enableModalInProgress(@$el) - auth.loginUser data, (jqxhr) => - message = jqxhr.responseText - if jqxhr.status is 401 - message = 'Wrong username or password. Try again!' - # TODO: Make the server return better error message - message = _.string.capitalize(message) - @disableModalInProgress(@$el) - @$('#errors-alert').text(message).removeClass('hide') - - onClickCreateNewAccountLink: -> - @trigger 'want-to-create-account' - @hide?() - - afterInsert: -> - super() - _.delay (=> @$('input:visible:first').focus()), 500 diff --git a/app/views/courses/StudentSignUpModal.coffee b/app/views/courses/StudentSignUpModal.coffee deleted file mode 100644 index 76d2307ad..000000000 --- a/app/views/courses/StudentSignUpModal.coffee +++ /dev/null @@ -1,101 +0,0 @@ -ModalView = require 'views/core/ModalView' -template = require 'templates/courses/student-sign-up-modal' -auth = require 'core/auth' -forms = require 'core/forms' -User = require 'models/User' -Classroom = require 'models/Classroom' -utils = require 'core/utils' - -module.exports = class StudentSignUpModal extends ModalView - id: 'student-sign-up-modal' - template: template - - events: - 'submit form': 'onSubmitForm' - 'click #skip-link': 'onClickSkipLink' - - initialize: (options) -> - options ?= {} - @willPlay = options.willPlay - @classCode = utils.getQueryVariable('_cc') or '' - - afterInsert: -> - super() - _.delay (=> @$('input:visible:first').focus()), 500 - - onClickSkipLink: -> - @trigger 'click-skip-link' # defer to view that opened this modal - @hide?() - - onSubmitForm: (e) -> - e.preventDefault() - @signupClassroomPrecheck() - - emailCheck: -> - email = @$('#email').val() - filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$/i # https://news.ycombinator.com/item?id=5763990 - unless filter.test(email) - @$('#errors-alert').text($.i18n.t('share_progress_modal.email_invalid')).removeClass('hide') - return false - return true - - signupClassroomPrecheck: -> - if not _.all([@$('#email').val(), @$('#password').val(), @$('#name').val()]) - @$('#errors-alert').text('Enter email, username and password').removeClass('hide') - return - classCode = @$('#class-code-input').val() - if not classCode - return @signup() - classroom = new Classroom() - classroom.fetch({ url: '/db/classroom?code='+classCode }) - classroom.once 'sync', @signup, @ - classroom.once 'error', @onClassroomFetchError, @ - @enableModalInProgress(@$el) - - onClassroomFetchError: -> - @disableModalInProgress(@$el) - @$('#errors-alert').text('Classroom code could not be found').removeClass('hide') - - signup: -> - return unless @emailCheck() - # TODO: consolidate with AuthModal logic, or make user creation process less magical, more RESTful - data = forms.formToObject @$el - delete data.classCode - for key, val of me.attributes when key in ['preferredLanguage', 'testGroupNumber', 'dateCreated', 'wizardColor1', 'name', 'music', 'volume', 'emails', 'schoolName'] - data[key] ?= val - Backbone.Mediator.publish "auth:signed-up", {} - data.emails ?= {} - data.emails.generalNews ?= {} - data.emails.generalNews.enabled = false - # TODO: Doesn't handle failed user creation. Double posts when placed in onCreateUserSuccess. - window.tracker?.trackEvent 'Finished Student Signup', category: 'Courses', label: 'Courses Student Signup' - @enableModalInProgress(@$el) - user = new User(data) - user.notyErrors = false - user.save({}, { - validate: false # make server deal with everything - error: @onCreateUserError - success: @onCreateUserSuccess - }) - - onCreateUserError: (model, jqxhr) => - # really need to make our server errors uniform - if jqxhr.responseJSON - error = jqxhr.responseJSON - error = error[0] if _.isArray(error) - message = _.filter([error.property, error.message]).join(' ') - else - message = jqxhr.responseText - @disableModalInProgress(@$el) - @$('#errors-alert').text(message).removeClass('hide') - - onCreateUserSuccess: => - classCode = @$('#class-code-input').val() - if classCode - url = "/courses?_cc="+classCode - document.location.href = url - # This was a terrible hack to make navigating trigger when just adding query params - # application.router.navigate('/thisisahack') - # application.router.navigate(url, { trigger: true }) - else - window.location.reload() diff --git a/server/commons/auth.coffee b/server/commons/auth.coffee index 323b71d30..18e958c87 100644 --- a/server/commons/auth.coffee +++ b/server/commons/auth.coffee @@ -17,16 +17,17 @@ module.exports.setup = -> authentication.use(new LocalStrategy( (username, password, done) -> - # kind of a hacky way to make it possible for iPads to 'log in' with their unique device id - if username.length is 36 and '@' not in username # must be an identifier for vendor - q = { iosIdentifierForVendor: username } - else - q = { emailLower: username.toLowerCase() } + # TODO: Add special iPad login endpoint. There was some logic here for the old, hacky method, + # but was removed for username login + q = { $or: [ + { emailLower: username.toLowerCase() } + { slug: _.str.slugify(username) } + ]} User.findOne(q).exec((err, user) -> return done(err) if err if not user - return done(new errors.Unauthorized('not found', { property: 'email' })) + return done(new errors.Unauthorized('not found', { errorID: 'not-found' })) passwordReset = (user.get('passwordReset') or '').toLowerCase() if passwordReset and password.toLowerCase() is passwordReset User.update {_id: user.get('_id')}, {$unset: {passwordReset: ''}}, {}, -> @@ -34,7 +35,7 @@ module.exports.setup = -> hash = User.hashPassword(password) unless user.get('passwordHash') is hash - return done(new errors.Unauthorized('is wrong', { property: 'password' })) + return done(new errors.Unauthorized('is wrong', { errorID: 'wrong-password' })) return done(null, user) ) )) diff --git a/server/commons/errors.coffee b/server/commons/errors.coffee index a82eb7b0f..24276645b 100644 --- a/server/commons/errors.coffee +++ b/server/commons/errors.coffee @@ -92,6 +92,10 @@ errorResponseSchema = { type: 'string' description: 'Provided for /auth/name.' # TODO: refactor out } + errorID: { + type: 'string' + description: 'Error id to be used by the client to handle specific errors' + } } } errorProps = _.keys(errorResponseSchema.properties) diff --git a/spec/server/functional/auth.spec.coffee b/spec/server/functional/auth.spec.coffee index ffb173131..886471d37 100644 --- a/spec/server/functional/auth.spec.coffee +++ b/spec/server/functional/auth.spec.coffee @@ -24,18 +24,6 @@ describe 'POST /auth/login', -> yield utils.becomeAnonymous() done() - it 'allows logging in by iosIdentifierForVendor', utils.wrap (done) -> - yield utils.initUser({ - 'iosIdentifierForVendor': '012345678901234567890123456789012345' - 'password': '12345' - }) - [res, body] = yield request.postAsync({uri: urlLogin, json: { - username: '012345678901234567890123456789012345' - password: '12345' - }}) - expect(res.statusCode).toBe(200) - done() - it 'returns 401 when the user does not exist', utils.wrap (done) -> [res, body] = yield request.postAsync({uri: urlLogin, json: { username: 'some@email.com' @@ -55,6 +43,19 @@ describe 'POST /auth/login', -> }}) expect(res.statusCode).toBe(200) done() + + it 'allows login by username', utils.wrap (done) -> + yield utils.initUser({ + name: 'Some name that will be lowercased...' + 'email': 'some@email.com' + 'password': '12345' + }) + [res, body] = yield request.postAsync({uri: urlLogin, json: { + username: 'Some name that will be lowercased...' + password: '12345' + }}) + expect(res.statusCode).toBe(200) + done() it 'rejects wrong passwords', utils.wrap (done) -> yield utils.initUser({ diff --git a/test/app/views/courses/HeroSelectModal.spec.coffee b/test/app/views/courses/HeroSelectModal.spec.coffee index b38861f4e..7a933bf51 100644 --- a/test/app/views/courses/HeroSelectModal.spec.coffee +++ b/test/app/views/courses/HeroSelectModal.spec.coffee @@ -1,5 +1,4 @@ HeroSelectModal = require 'views/courses/HeroSelectModal' -auth = require 'core/auth' factories = require 'test/app/factories' describe 'HeroSelectModal', -> @@ -14,7 +13,6 @@ describe 'HeroSelectModal', -> beforeEach (done) -> window.me = user = factories.makeUser({ heroConfig: { thangType: hero1.get('original') } }) - auth.loginUser(user.attributes) modal = new HeroSelectModal({ currentHeroID: hero1.id }) modal.heroes.fakeRequests[0].respondWith({ status: 200, responseText: heroesResponse }) jasmine.demoModal(modal) diff --git a/test/app/views/courses/StudentLogInModal.spec.coffee b/test/app/views/courses/StudentLogInModal.spec.coffee deleted file mode 100644 index 0161264fe..000000000 --- a/test/app/views/courses/StudentLogInModal.spec.coffee +++ /dev/null @@ -1,22 +0,0 @@ -StudentLoginModal = require 'views/courses/StudentLogInModal' -RecoverModal = require 'views/core/RecoverModal' -auth = require 'core/auth' - -describe 'StudentLoginModal', -> - - modal = null - - beforeEach -> - modal = new StudentLoginModal() - modal.render() - - afterEach -> - modal.stopListening() - - it 'displays an error when you submit an empty login form', -> - spyOn(auth, 'loginUser').and.callFake (data, callback) -> - callback { status: 401, responseText: "Unauthorized" } - modal.$el.find('#log-in-btn').click() - expect(modal.$el.html()).toContain('Wrong username or password. Try again!') - - jasmine.demoModal(modal)