Compare commits

..

3 Commits
0.2.4 ... 0.2.5

Author SHA1 Message Date
Stefan Zermatten
b99da301cd Updated meteor 2015-05-11 12:16:38 +02:00
Stefan Zermatten
0a01885300 Finished implementing useraccounts 2015-05-11 12:15:00 +02:00
Stefan Zermatten
5cb1515235 Began implementing useraccounts and permissions properly 2015-05-08 12:59:38 +02:00
14 changed files with 238 additions and 62 deletions

View File

@@ -20,3 +20,7 @@ mike:mocha
dburles:mongo-collection-instances dburles:mongo-collection-instances
percolate:migrations percolate:migrations
ecwyne:mathjs ecwyne:mathjs
useraccounts:polymer
accounts-google
splendido:accounts-meld
email

View File

@@ -1,9 +1,11 @@
accounts-base@1.2.0 accounts-base@1.2.0
accounts-google@1.0.4
accounts-oauth@1.1.5
accounts-password@1.1.1 accounts-password@1.1.1
accounts-ui@1.1.5 accounts-ui@1.1.5
accounts-ui-unstyled@1.1.7 accounts-ui-unstyled@1.1.7
aldeed:collection2@2.3.3 aldeed:collection2@2.3.3
aldeed:simple-schema@1.3.2 aldeed:simple-schema@1.3.3
amplify@1.0.0 amplify@1.0.0
autoupdate@1.2.1 autoupdate@1.2.1
base64@1.0.3 base64@1.0.3
@@ -24,6 +26,7 @@ ejson@1.0.6
email@1.0.6 email@1.0.6
fastclick@1.0.3 fastclick@1.0.3
geojson-utils@1.0.3 geojson-utils@1.0.3
google@1.1.5
html-tools@1.0.4 html-tools@1.0.4
htmljs@1.0.4 htmljs@1.0.4
http@1.1.0 http@1.1.0
@@ -44,20 +47,22 @@ less@1.0.14
livedata@1.0.13 livedata@1.0.13
localstorage@1.0.3 localstorage@1.0.3
logging@1.0.7 logging@1.0.7
matb33:collection-hooks@0.7.11 matb33:collection-hooks@0.7.13
meteor@1.1.6 meteor@1.1.6
meteor-platform@1.2.2 meteor-platform@1.2.2
mike:mocha@0.5.3 mike:mocha@0.5.4
minifiers@1.1.5 minifiers@1.1.5
minimongo@1.0.8 minimongo@1.0.8
mobile-status-bar@1.0.3 mobile-status-bar@1.0.3
momentjs:moment@2.10.3 momentjs:moment@2.10.3
mongo@1.1.0 mongo@1.1.0
npm-bcrypt@0.7.8_2 npm-bcrypt@0.7.8_2
oauth@1.1.4
oauth2@1.1.3
observe-sequence@1.0.6 observe-sequence@1.0.6
ordered-dict@1.0.3 ordered-dict@1.0.3
package-version-parser@3.0.3 package-version-parser@3.0.3
percolate:migrations@0.7.3 percolate:migrations@0.7.5
practicalmeteor:chai@1.9.2_3 practicalmeteor:chai@1.9.2_3
practicalmeteor:loglevel@1.1.0_3 practicalmeteor:loglevel@1.1.0_3
random@1.0.3 random@1.0.3
@@ -72,16 +77,21 @@ sanjo:meteor-version@1.0.0
service-configuration@1.0.4 service-configuration@1.0.4
session@1.1.0 session@1.1.0
sha@1.0.3 sha@1.0.3
softwarerero:accounts-t9n@1.0.9
spacebars@1.0.6 spacebars@1.0.6
spacebars-compiler@1.0.6 spacebars-compiler@1.0.6
splendido:accounts-emails-field@1.2.0
splendido:accounts-meld@1.3.0
srp@1.0.3 srp@1.0.3
templating@1.1.1 templating@1.1.1
tracker@1.0.7 tracker@1.0.7
ui@1.0.6 ui@1.0.6
underscore@1.0.3 underscore@1.0.3
url@1.0.4 url@1.0.4
useraccounts:core@1.9.1
useraccounts:polymer@1.9.1
velocity:chokidar@0.12.6_1 velocity:chokidar@0.12.6_1
velocity:core@0.6.0 velocity:core@0.6.1
velocity:html-reporter@0.5.3 velocity:html-reporter@0.5.3
velocity:meteor-internals@1.1.0_7 velocity:meteor-internals@1.1.0_7
velocity:shim@0.1.0 velocity:shim@0.1.0

View File

@@ -1,44 +1,23 @@
Schema = {};
Schema.User = new SimpleSchema({
username: {
type: String,
regEx: /^[a-z0-9A-Z_]{3,15}$/,
optional: true,
},
emails: {
type: [Object],
// this must be optional if you also use other login services like facebook,
// but if you use only accounts-password, then it can be required
optional: true,
},
"emails.$.address": {
type: String,
regEx: SimpleSchema.RegEx.Email,
},
"emails.$.verified": {
type: Boolean
},
createdAt: {
type: Date
},
services: {
type: Object,
optional: true,
blackbox: true,
},
roles: {
type: [String],
optional: true,
},
});
Meteor.users.attachSchema(Schema.User);
Meteor.users.allow({ Meteor.users.allow({
update: function(userId, doc, fields, modifier) { update: function(userId, doc, fields, modifier) {
return userId === doc._id && if (
fields.length === 1 && doc._id === userId &&
fields[0] === "username"; _.contains(fields, "username") &&
_.contains(fields, "profile") &&
fields.length === 2 &&
_.keys(modifier).length === 1 &&
modifier.$set &&
modifier.$set["profile.username"] &&
modifier.$set.username &&
_.keys(modifier.$set).length === 2
){
var expectedUsername = modifier.$set["profile.username"];
expectedUsername = expectedUsername.toLowerCase().replace(/\s+/gm, "");
if (modifier.$set.username !== expectedUsername){
return false;
}
var foundUser = Meteor.call("getUserId", expectedUsername);
return !foundUser || foundUser === userId;
}
} }
}); });

View File

@@ -3,6 +3,12 @@ Router.configure({
layoutTemplate: "layout", layoutTemplate: "layout",
}); });
Router.plugin("ensureSignedIn", {
except: ["home", "atSignIn", "atSignUp", "atForgotPassword", "notFound"]
});
Router.plugin("dataNotFound", {notFoundTemplate: "notFound"});
Router.map(function() { Router.map(function() {
this.route("/", { this.route("/", {
name: "home", name: "home",
@@ -56,4 +62,8 @@ Router.map(function() {
document.title = appName + " Account"; document.title = appName + " Account";
}, },
}); });
this.route("/loginButtons", {
name: "loginButtons",
})
}); });

View File

@@ -1,13 +1,13 @@
<template name="layout"> <template name="layout">
<core-drawer-panel> <core-drawer-panel>
<core-header-panel drawer navigation flex mode="seamed" class="white"> <core-header-panel drawer navigation flex mode="seamed" class="white">
<div id="accountSummary"> <div id="accountSummary">
{{> loginButtons}}
{{#if currentUser}} {{#if currentUser}}
<div id="profileLink" style="text-decoration: underline; cursor: pointer;"> <div id="profileLink" style="text-decoration: underline; cursor: pointer;">
My account {{profileLink}}
</div> </div>
{{else}}
<a href="/sign-in" style="color: white;">Sign in</a>
{{/if}} {{/if}}
</div> </div>
<div id="navPanel"> <div id="navPanel">

View File

@@ -9,7 +9,11 @@ Template.layout.destroyed = function() {
Template.layout.helpers({ Template.layout.helpers({
notSelected: function(){ notSelected: function(){
return Session.get("global.ui.detailShow") ? "not-selected" : null; return Session.get("global.ui.detailShow") ? "not-selected" : null;
} },
profileLink: function() {
var user = Meteor.user();
return user.profile && user.profile.username || user.username || "My Account";
},
}); });
Template.layout.events({ Template.layout.events({

View File

@@ -0,0 +1,11 @@
<template name="notFound">
<div layout vertical center center-justified fit>
<h2>The data for the page you requested could not be found.</h2>
{{#if currentUser}}
<h2>It might not exist, or you might not have permission to view it.</h2>
{{else}}
<h2>Perhaps you need to sign in first:</h2>
{{atForm}}
{{/if}}
</div>
</template>

View File

@@ -3,11 +3,7 @@
<core-toolbar class="blue-grey white-text"> <core-toolbar class="blue-grey white-text">
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button> <core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
<div id="username" class="clickable" flex> <div id="username" class="clickable" flex>
{{#if username}} {{profileName}}
{{username}}
{{else}}
Tap to set username
{{/if}}
</div> </div>
</core-toolbar> </core-toolbar>
<div id="userProfile" class="padded"> <div id="userProfile" class="padded">
@@ -20,5 +16,7 @@
{{/each}} {{/each}}
</div> </div>
</div> </div>
{{> atForm}}
{{> atNavButton }}
{{/with}} {{/with}}
</template> </template>

View File

@@ -1,3 +1,12 @@
Template.profile.helpers({
profileName: function() {
var user = Meteor.user();
return user.profile && user.profile.username ||
user.username ||
"Tap to set username";
}
});
Template.profile.events({ Template.profile.events({
"tap #username": function(){ "tap #username": function(){
if (this._id === Meteor.userId()){ if (this._id === Meteor.userId()){

View File

@@ -1,9 +1,8 @@
<template name="usernameDialog"> <template name="usernameDialog">
{{#with currentUser}} <div>
<div> <paper-input id="usernameInput" label="Username" value={{profileName}}></paper-input>
<paper-input id="usernameInput" label="Username" value={{username}}></paper-input> </div>
</div> <div style="color: red;" class="vertMargin">{{errorMessage}}</div>
{{/with}}
<paper-button id="cancelButton" affirmative> Cancel </paper-button> <paper-button id="cancelButton" affirmative> Cancel </paper-button>
<paper-button id="changeButton" affirmative> Change Username </paper-button> <paper-button id="changeButton" disabled={{invalid}} affirmative> Change Username </paper-button>
</template> </template>

View File

@@ -1,8 +1,49 @@
var getUsername = function() {
var user = Meteor.user();
return user.profile && user.profile.username || user.username;
};
Template.usernameDialog.onCreated(function() {
this.errorMessage = new ReactiveVar("");
this.username = new ReactiveVar(getUsername());
});
Template.usernameDialog.helpers({
profileName: function() {
return getUsername();
},
invalid: function() {
return !!Template.instance().errorMessage.get();
},
errorMessage: function() {
return Template.instance().errorMessage.get();
},
});
Template.usernameDialog.events({ Template.usernameDialog.events({
"change #usernameInput, input #usernameInput": function(event, instance) {
var username = instance.find("#usernameInput").value;
username = username.trim().toLowerCase().replace(/\s+/gm, "");
if (username.length < 3){
instance.errorMessage.set("Username too short");
} else {
instance.errorMessage.set("Validating...");
Meteor.call("getUserId", username, function(err, userId){
if (userId && userId !== Meteor.userId())
instance.errorMessage.set("This username is taken");
else
instance.errorMessage.set("");
});
}
},
"tap #changeButton": function(event, instance){ "tap #changeButton": function(event, instance){
var username = instance.find("#usernameInput").value;
username = username.trim().replace(/\s+/gm, " ");
var profileName = username;
username = username.toLowerCase().replace(/\s+/gm, "");
Meteor.users.update( Meteor.users.update(
Meteor.userId(), Meteor.userId(),
{$set: {username: instance.find("#usernameInput").value}} {$set: {username: username, "profile.username": profileName}}
); );
} },
}); });

View File

@@ -0,0 +1,13 @@
<template name="titledAtForm">
<core-toolbar class="blue-grey white-text">
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
<div flex>
</div>
</core-toolbar>
<div class="scroll-y padded" fit layout vertical center center-justified>
<paper-shadow class="white" style="max-width: 400px;">
{{> atForm}}
</paper-shadow>
</div>
</template>

View File

@@ -0,0 +1,71 @@
AccountsTemplates.configure({
//behaviour
confirmPassword: true,
enablePasswordChange: true,
enforceEmailVerification: true,
overrideLoginErrors: false,
sendVerificationEmail: true,
lowercaseUsername: true,
//appearance
continuousValidation: true,
negativeValidation: true,
negativeFeedback: true,
showValidating: true,
showAddRemoveServices: true,
showForgotPasswordLink: true,
showResendVerificationEmailLink: true,
});
AccountsTemplates.configureRoute("changePwd", {
template: "titledAtForm",
});
AccountsTemplates.configureRoute("enrollAccount", {
template: "titledAtForm",
});
AccountsTemplates.configureRoute("forgotPwd", {
template: "titledAtForm",
});
AccountsTemplates.configureRoute("resetPwd", {
template: "titledAtForm",
});
AccountsTemplates.configureRoute("signIn", {
template: "titledAtForm",
});
AccountsTemplates.configureRoute("signUp", {
template: "titledAtForm",
});
AccountsTemplates.configureRoute("verifyEmail", {
template: "titledAtForm",
});
AccountsTemplates.configureRoute("resendVerificationEmail", {
template: "titledAtForm",
});
if (Meteor.isServer){
Meteor.methods({
"userExists": function(username){
return !!Meteor.users.findOne({username: username});
},
});
}
AccountsTemplates.addField({
_id: "username",
type: "text",
required: true,
func: function(value){
if (Meteor.isClient) {
var self = this;
Meteor.call("userExists", value, function(err, userExists){
if (!userExists)
self.setSuccess();
else
self.setError("This username is taken");
self.setValidating(false);
});
return;
}
// Server
return Meteor.call("userExists", value);
},
});

View File

@@ -0,0 +1,27 @@
AccountsMeld.configure({
meldDBCallback: function(sourceUserId, destinationUserId){
// Here you can modify every collection you need for the document referencing
// to sourceUserId to be modified in order to point to destinationUserId
Characters.update(
{owner: sourceUserId},
{$set: {owner: destinationUserId}},
{multi: true}
);
Characters.update(
{writers: sourceUserId},
{
$pull: {writers: sourceUserId},
$addToSet: {writers: destinationUserId},
},
{multi: true}
);
Characters.update(
{readers: sourceUserId},
{
$pull: {readers: sourceUserId},
$addToSet: {readers: destinationUserId},
},
{multi: true}
);
},
});