Add ember-simple-auth, setup login

- Allow dropdown-buttons to render a partial
This commit is contained in:
Toby Zerner 2015-01-30 12:21:18 +10:30
parent befb3d1929
commit 5cd87db5cf
17 changed files with 334 additions and 69 deletions

View File

@ -0,0 +1,37 @@
import Base from 'simple-auth/authenticators/base';
import config from '../config/environment';
export default Base.extend({
restore: function(data) {
var container = this.container;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.run.next(function() {
container.lookup('store:main').find('user', data.userId).then(function(user) {
resolve( { token: data.token, userId: data.userId, user: user } );
});
});
});
},
authenticate: function(credentials) {
var container = this.container;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: config.apiURL+'/auth/login',
type: 'POST',
data: { identification: credentials.identification, password: credentials.password }
}).then(function(response) {
container.lookup('store:main').find('user', response.userId).then(function(user) {
resolve({ token: response.token, userId: response.userId, user: user });
});
}, function(xhr, status, error) {
reject(xhr.responseJSON.errors);
});
});
},
// invalidate: function(data) {
// return new Ember.RSVP.Promise();
// }
});

View File

@ -0,0 +1,11 @@
import Base from 'simple-auth/authorizers/base';
export default Base.extend({
authorize: function(jqXHR, requestOptions) {
var token = this.get('session.token');
if (this.get('session.isAuthenticated') && !Ember.isEmpty(token)) {
jqXHR.setRequestHeader('Authorization', 'Token ' + token);
}
}
});

View File

@ -1,11 +1,32 @@
import Ember from 'ember'; import Ember from 'ember';
// import NotificationMessage from '../models/notification-message'; import AuthenticationControllerMixin from 'simple-auth/mixins/authentication-controller-mixin';
// export default Ember.Controller.extend(Ember.SimpleAuth.LoginControllerMixin, Ember.Evented, { export default Ember.Controller.extend(AuthenticationControllerMixin, {
// authenticatorFactory: 'authenticator:flarum' authenticator: 'authenticator:flarum',
actions: {
authenticate: function() {
var data = this.getProperties('identification', 'password');
var controller = this;
this.set('error', null);
this.set('loading', true);
return this._super(data).then(function() {
controller.send("sessionChanged");
}).catch(function(errors) {
switch(errors[0].code) {
case 'invalidLogin':
controller.set('error', 'Your login details are incorrect.');
break;
default:
controller.set('error', 'Something went wrong. (Error code: '+errors[0].code+')');
}
}).finally(function() {
controller.set('loading', false);
});
}
}
// }); });
export default Ember.Controller.extend();

View File

@ -0,0 +1,9 @@
import FlarumAuthorizer from '../authorizers/flarum';
export default {
name: 'authentication',
before: 'simple-auth',
initialize: function(container) {
container.register('authorizer:flarum', FlarumAuthorizer);
}
};

View File

@ -1,30 +1,27 @@
import Ember from 'ember'; import Ember from 'ember';
// import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin'; import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';
// export default Ember.Route.extend(ApplicationRouteMixin, { export default Ember.Route.extend(ApplicationRouteMixin, {
// actions: { actions: {
login: function() {
this.controllerFor('login').set('error', null);
this.render('login', {
into: 'application',
outlet: 'modal'
});
},
// login: function() { closeModal: function() {
// return this.render('login', { this.disconnectOutlet({
// into: 'application', outlet: 'modal',
// outlet: 'modal' parentView: 'application'
// }); });
// }, },
// doLogin: function() { sessionChanged: function() {
// this.get('session').authenticate('authenticator:custom', {}); this.refresh();
// }, }
}
// closeModal: function() { });
// return this.disconnectOutlet({
// outlet: 'modal',
// parentView: 'application'
// });
// }
// }
// });
export default Ember.Route.extend();

View File

@ -18,7 +18,10 @@
// Finally, with our vendor CSS loaded, we can import Flarum-specific stuff. // Finally, with our vendor CSS loaded, we can import Flarum-specific stuff.
@import "@{flarum-base}components.less"; @import "@{flarum-base}components.less";
@import "@{flarum-base}modals.less";
@import "@{flarum-base}layout.less"; @import "@{flarum-base}layout.less";
@import "@{flarum-base}composer.less";
@import "@{flarum-base}index.less"; @import "@{flarum-base}index.less";
@import "@{flarum-base}discussion.less"; @import "@{flarum-base}discussion.less";
@import "@{flarum-base}login.less";

View File

@ -0,0 +1,32 @@
.modal-login {
& .form-group {
margin-bottom: 12px;
}
}
.form-group {
position: relative;
}
.form-alert {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
margin-bottom: 12px;
& .alert {
display: inline-block;
}
}
.alert-warning {
background: #D83E3E;
.box-shadow(0 2px 6px rgba(0, 0, 0, 0.3));
color: #fff;
padding: 12px 16px;
border-radius: @border-radius-base;
}
.btn-user {
& .avatar {
margin: -2px 5px -2px -5px;
.avatar-size(24px);
}
}

View File

@ -0,0 +1,79 @@
// ------------------------------------
// Modals
.modal-backdrop {
background-color: @fl-primary-color;
&.in {
opacity: 0.9;
}
}
.modal-dialog {
margin: 120px auto;
& .close {
position: absolute;
right: 5px;
top: 5px;
}
}
.modal-content {
border: 0;
border-radius: @border-radius-base;
.box-shadow(0 7px 15px rgba(0, 0, 0, 0.3));
}
.modal-sm {
width: 375px;
}
.modal-header {
text-align: center;
border: 0;
padding: 25px;
& h3 {
font-size: 22px;
margin: 0;
}
}
.modal-body {
background-color: @fl-secondary-color;
padding: 25px;
& .form-control {
background-color: #fff;
}
}
.modal-footer {
border: 0;
padding: 20px;
text-align: center;
color: @fl-body-muted-color;
}
.modal-loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
opacity: 0;
pointer-events: none;
border-radius: @border-radius-base;
.transition(opacity 0.2s);
&.active {
opacity: 1;
pointer-events: auto;
}
}
.form-centered {
text-align: center;
& .form-control, & .btn {
width: 220px;
margin: 0 auto;
text-align: center;
}
}

View File

@ -1,10 +1,10 @@
<a href="#" {{bind-attr class=":dropdown-toggle buttonClass"}} data-toggle="dropdown" {{action "buttonClick"}}> <a href="#" {{bind-attr class=":dropdown-toggle buttonClass"}} data-toggle="dropdown" {{action "buttonClick"}}>
{{#if iconHtml}} {{#if buttonPartial}}
{{{iconHtml}}} {{partial buttonPartial}}
{{else}} {{else}}
{{fa-icon icon class="icon-glyph"}} {{fa-icon icon class="icon-glyph"}}
<span class="label">{{label}}</span>
{{fa-icon "caret-down" class="icon-caret"}}
{{/if}} {{/if}}
<span class="label">{{label}}</span>
{{fa-icon "caret-down" class="icon-caret"}}
</a> </a>
{{ui/controls/item-list items=items class=dropdownMenuClass}} {{ui/controls/item-list items=items class=dropdownMenuClass}}

View File

@ -1,27 +1,33 @@
<div class="modal-dialog modal-sm"> <div class="modal-dialog modal-sm modal-login">
<div class="modal-content"> <div class="modal-content">
<form {{action 'authenticate' on='submit'}}> <button class="close btn btn-icon btn-link" {{action "close" target="view"}}>{{fa-icon "times"}}</button>
<form {{action "authenticate" on="submit"}}>
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" {{action close target="view"}}>&times;</button> <h3>Log In</h3>
<h3 style="margin:0">Log In</h3>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group"> <div class="form-centered">
{{input value=identification type="text" class="form-control" placeholder="Username or Email"}} <div class="form-group">
</div> {{#if error}}
<div class="form-group"> <div class="form-alert">
{{input value=password type="password" class="form-control" placeholder="Password"}} <div class="alert alert-warning">{{error}}</div>
</div> </div>
<div class="checkbox" style="margin:0"> {{/if}}
<label> {{input value=identification name="email" type="text" class="form-control" placeholder="Username or Email" disabled=loading}}
{{input checked=remember type="checkbox"}} Remember me </div>
</label> <div class="form-group">
{{input value=password name="password" type="password" class="form-control" placeholder="Password" disabled=loading}}
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block" {{bind-attr disabled=loading}}>Log In</button>
</div>
</div> </div>
</div> </div>
<div class="modal-footer" style="margin:0"> <div class="modal-footer">
<a href="#" class="btn btn-default" {{action close target="view"}}>Close</a> <p class="forgot-password-link"><a href="#">Forgot password?</a></p>
{{input class="btn btn-primary" type="submit" value="Log In"}} <p class="sign-up-link">Don't have an account? <a href="#">Sign Up</a></p>
</div> </div>
</form> </form>
</div> </div>
</div> {{ui/controls/loading-indicator classNameBindings=":modal-loading loading:active"}}
</div>

View File

@ -0,0 +1,2 @@
{{user-avatar user}}
<span class="label">{{label}}</span>

View File

@ -3,6 +3,8 @@ import Ember from 'ember';
import ActionButton from '../components/ui/controls/action-button'; import ActionButton from '../components/ui/controls/action-button';
import SearchInput from '../components/ui/controls/search-input'; import SearchInput from '../components/ui/controls/search-input';
import DropdownSelect from '../components/ui/controls/dropdown-select'; import DropdownSelect from '../components/ui/controls/dropdown-select';
import DropdownButton from '../components/ui/controls/dropdown-button';
import SeparatorItem from '../components/ui/items/separator-item';
import TaggedArray from '../utils/tagged-array'; import TaggedArray from '../utils/tagged-array';
var $ = Ember.$; var $ = Ember.$;
@ -14,10 +16,7 @@ export default Ember.View.extend({
}.property('controller.forumTitle'), }.property('controller.forumTitle'),
didInsertElement: function() { didInsertElement: function() {
// Create and populate an array of items to be rendered in the header.
this.set('headerPrimaryItems', TaggedArray.create());
this.set('headerSecondaryItems', TaggedArray.create());
this.trigger('populateHeader', this.get('headerPrimaryItems'), this.get('headerSecondaryItems'));
// Create and populate an array of items to be rendered in the footer. // Create and populate an array of items to be rendered in the footer.
this.set('footerPrimaryItems', TaggedArray.create()); this.set('footerPrimaryItems', TaggedArray.create());
@ -36,6 +35,13 @@ export default Ember.View.extend({
}).resize(); }).resize();
}, },
switchHeader: function() {
// Create and populate an array of items to be rendered in the header.
this.set('headerPrimaryItems', TaggedArray.create());
this.set('headerSecondaryItems', TaggedArray.create());
this.trigger('populateHeader', this.get('headerPrimaryItems'), this.get('headerSecondaryItems'));
}.observes('controller.session.user'),
populateHeaderDefault: function(primary, secondary) { populateHeaderDefault: function(primary, secondary) {
var controller = this.get('controller'); var controller = this.get('controller');
@ -50,17 +56,64 @@ export default Ember.View.extend({
}); });
secondary.pushObjectWithTag(search, 'search'); secondary.pushObjectWithTag(search, 'search');
var signUp = ActionButton.create({ if (this.get('controller.session.isAuthenticated')) {
label: 'Sign Up', var userItems = TaggedArray.create();
className: 'btn btn-link'
});
secondary.pushObjectWithTag(signUp, 'signUp');
var logIn = ActionButton.create({ var profile = ActionButton.create({
label: 'Log In', label: 'Profile',
className: 'btn btn-link' icon: 'user'
}); });
secondary.pushObjectWithTag(logIn, 'logIn'); userItems.pushObjectWithTag(profile, 'profile');
var settings = ActionButton.create({
label: 'Settings',
icon: 'cog'
});
userItems.pushObjectWithTag(settings, 'settings');
userItems.pushObject(SeparatorItem.create());
var rememberMe = ActionButton.create({
label: 'Remember Me',
icon: 'square-o'
});
userItems.pushObjectWithTag(rememberMe, 'rememberMe');
var logOut = ActionButton.create({
label: 'Log Out',
icon: 'sign-out',
action: function() {
controller.send('invalidateSession');
}
});
userItems.pushObjectWithTag(logOut, 'logOut');
var userDropdown = DropdownButton.extend({
label: Ember.computed.alias('user.username'),
buttonClass: 'btn btn-default btn-naked btn-rounded btn-user',
buttonPartial: 'partials/user-button',
menuClass: 'pull-right'
});
secondary.pushObjectWithTag(userDropdown.create({
items: userItems,
user: this.get('controller.session.user')
}), 'user');
} else {
var signUp = ActionButton.create({
label: 'Sign Up',
className: 'btn btn-link'
});
secondary.pushObjectWithTag(signUp, 'signUp');
var logIn = ActionButton.create({
label: 'Log In',
className: 'btn btn-link',
action: function() {
controller.send('login');
}
});
secondary.pushObjectWithTag(logIn, 'logIn');
}
}.on('populateHeader'), }.on('populateHeader'),
populateFooterDefault: function(primary, secondary) { populateFooterDefault: function(primary, secondary) {

View File

@ -8,11 +8,20 @@ export default Ember.View.extend({
var self = this; var self = this;
this.$().modal('show').on('hidden.bs.modal', function() { this.$().modal('show').on('hidden.bs.modal', function() {
self.get('controller').send('closeModal'); self.get('controller').send('closeModal');
}).on('shown.bs.modal', function() {
$(this).find('input:first').select();
}); });
this.get('controller.session').on('sessionAuthenticationSucceeded', this, this.hide); this.get('controller.session').on('sessionAuthenticationSucceeded', this, this.hide);
}, },
refocus: function() {
var view = this;
Ember.run.scheduleOnce('afterRender', function() {
view.$('input[name=password]').select();
});
}.observes('controller.loading'),
willDestroyElement: function() { willDestroyElement: function() {
this.get('controller.session').off('sessionAuthenticationSucceeded', this, this.hide); this.get('controller.session').off('sessionAuthenticationSucceeded', this, this.hide);
}, },

View File

@ -17,6 +17,7 @@
"font-awesome": "~4", "font-awesome": "~4",
"spin.js": "~2.0.1", "spin.js": "~2.0.1",
"pace": "~0.7.1", "pace": "~0.7.1",
"moment": "~2.8.4" "moment": "~2.8.4",
"ember-simple-auth": "0.7.2"
} }
} }

View File

@ -20,6 +20,10 @@ module.exports = function(environment) {
} }
}; };
ENV['simple-auth'] = {
authorizer: 'authorizer:flarum'
};
if (environment === 'development') { if (environment === 'development') {
// ENV.APP.LOG_RESOLVER = true; // ENV.APP.LOG_RESOLVER = true;
// ENV.APP.LOG_ACTIVE_GENERATION = true; // ENV.APP.LOG_ACTIVE_GENERATION = true;

View File

@ -28,6 +28,7 @@
"ember-cli-inject-live-reload": "^1.3.0", "ember-cli-inject-live-reload": "^1.3.0",
"ember-cli-less": "^1.0.5", "ember-cli-less": "^1.0.5",
"ember-cli-qunit": "0.1.2", "ember-cli-qunit": "0.1.2",
"ember-cli-simple-auth": "^0.7.2",
"ember-data": "1.0.0-beta.14", "ember-data": "1.0.0-beta.14",
"ember-dynamic-component": "0.0.4", "ember-dynamic-component": "0.0.4",
"ember-export-application-global": "^1.0.0", "ember-export-application-global": "^1.0.0",

View File

@ -9,7 +9,7 @@
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="flarum/config/environment" content="%7B%22modulePrefix%22%3A%22flarum%22%2C%22environment%22%3A%22development%22%2C%22baseURL%22%3A%22/%22%2C%22apiURL%22%3A%22/api%22%2C%22locationType%22%3A%22hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%7D%2C%22APP%22%3A%7B%7D%2C%22contentSecurityPolicyHeader%22%3A%22Content-Security-Policy-Report-Only%22%2C%22contentSecurityPolicy%22%3A%7B%22default-src%22%3A%22%27none%27%22%2C%22script-src%22%3A%22%27self%27%20%27unsafe-eval%27%22%2C%22font-src%22%3A%22%27self%27%22%2C%22connect-src%22%3A%22%27self%27%22%2C%22img-src%22%3A%22%27self%27%22%2C%22style-src%22%3A%22%27self%27%22%2C%22media-src%22%3A%22%27self%27%22%7D%2C%22exportApplicationGlobal%22%3Atrue%7D" /> <meta name="flarum/config/environment" content="%7B%22modulePrefix%22%3A%22flarum%22%2C%22environment%22%3A%22development%22%2C%22baseURL%22%3A%22/%22%2C%22apiURL%22%3A%22/api%22%2C%22locationType%22%3A%22hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%7D%2C%22APP%22%3A%7B%7D%2C%22simple-auth%22%3A%7B%22authorizer%22%3A%22authorizer%3Aflarum%22%7D%2C%22contentSecurityPolicyHeader%22%3A%22Content-Security-Policy-Report-Only%22%2C%22contentSecurityPolicy%22%3A%7B%22default-src%22%3A%22%27none%27%22%2C%22script-src%22%3A%22%27self%27%20%27unsafe-eval%27%22%2C%22font-src%22%3A%22%27self%27%22%2C%22connect-src%22%3A%22%27self%27%22%2C%22img-src%22%3A%22%27self%27%22%2C%22style-src%22%3A%22%27self%27%22%2C%22media-src%22%3A%22%27self%27%22%7D%2C%22exportApplicationGlobal%22%3Atrue%7D" />
<base href="/"> <base href="/">