├── LICENSE ├── README.md ├── customAccounts.html ├── customAccounts.css └── customAccounts.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 CH Buckingham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Complete-Meteor-Account-Pipeline 2 | Meteor.js: Easier & Complete Default Account System Files
3 | The account backend in meteor is pretty decent, and is wired up to all the basics, but manipulating the front end is an exercise in frustration, and wiring up the additional features is a headache. The CSS is all wrapped up in compiled files and LASS. So you install one of the packages that removes the styling, but even that makes it a huge pain to change or manipulate the UI. And even then, that UI is tied to some of the basic vanilla behavior and leaves out a lot of the smart and complex features that are available and sometimes necessary. 4 | 5 | So instead of making a package that would just be difficult to customize to my needs on projects going forward, I baked all features of the Meteor account system down into 3 files: 6 |
 7 | customAccounts.html
 8 | customAccounts.css
 9 | customAccounts.js
10 | 
11 | By default, all the features are in place, and the UI is styled to match meteor's home page. As well as being broken into easy to understand templates for all states and use cases...using actual buttons instead of a weird hybrid of hyperlinks and buttons. Every account related feature and function is either wired up in the javascript file, or if it's a less common feature, then stubbed in with example uses. 12 | 13 | Just drop 'em into your project folder, no package installation required. 14 | https://github.com/CHBDev/Complete-Meteor-Account-Pipeline 15 | -------------------------------------------------------------------------------- /customAccounts.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 23 | 24 | 34 | 35 | 49 | 50 | 51 | 62 | 63 | 67 | 68 | 73 | -------------------------------------------------------------------------------- /customAccounts.css: -------------------------------------------------------------------------------- 1 | /*Meteor Theme*/ 2 | 3 | 4 | #customOptionsToggle{ 5 | background-color: white; 6 | color: #27272b; 7 | border: 2px solid #27272b; 8 | font-size: .9em; 9 | font-weight: bold; 10 | padding: .5em 1em .4em 1em; 11 | margin: 2px; 12 | text-decoration: none; 13 | transition: all .2s; 14 | webkit-transition: all .2s; 15 | outline: 0; 16 | } 17 | #customOptionsToggle:hover{ 18 | color: #de4f4f; 19 | border: 2px solid #de4f4f; 20 | } 21 | #customOptionsToggle:active{ 22 | 23 | } 24 | 25 | .usernameCap{ 26 | text-transform: uppercase; 27 | } 28 | 29 | 30 | #customOptions{ 31 | text-decoration: none; 32 | font-size: .9em; 33 | font-weight: bold; 34 | background-color: white; 35 | text-align: center; 36 | display: inline-block; 37 | position: absolute; 38 | z-index: 100; 39 | border: 2px solid #27272b; 40 | width: 200px; 41 | height: auto; 42 | margin: 2px; 43 | } 44 | 45 | #customOptions span{ 46 | width: 100%; 47 | border-bottom: 1px solid #27272b; 48 | margin: 2px; 49 | } 50 | 51 | /*=============================*/ 52 | /*Default Holder for All Custom Account Inputs and Buttons 53 | You can use this window anywhere, or take the peices out of it 54 | Or use it in the provided options flyout, etc */ 55 | #customLogin{ 56 | text-align: center; 57 | width: 100%; 58 | margin-top: 1em; 59 | margin-bottom: 1em; 60 | } 61 | 62 | /*Message Field at the base of the customLogin window, displays status*/ 63 | #options-error{ 64 | display: inline-block; 65 | margin-left: auto; 66 | margin-right: auto; 67 | color: white; 68 | background-color: #de4f4f; 69 | width: 94%; 70 | margin: 2px; 71 | padding: 0em 1em 0em 1em; 72 | transition: all 1s; 73 | webkit-transition: all 1s; 74 | font-size: .9em; 75 | line-height: 1.3em; 76 | } 77 | 78 | /*shared settings*/ 79 | .optionsButton{ 80 | width: 94%; 81 | background-color: white; 82 | color: black; 83 | border: 2px solid #27272b; 84 | font-size: .9em; 85 | font-weight: bold; 86 | padding: .4em 1em .4em 1em; 87 | margin: 2px; 88 | transition: all .2s; 89 | webkit-transition: all .2s; 90 | text-transform: uppercase; 91 | } 92 | .optionsButton:hover{ 93 | color: #de4f4f; 94 | border: 2px solid #de4f4f; 95 | } 96 | 97 | .optionsInput{ 98 | width: 94%; 99 | background-color: white; 100 | color: black; 101 | border: 2px solid #27272b; 102 | font-size: .9em; 103 | font-weight: bold; 104 | padding: .4em 1em .4em 1em; 105 | margin: 2px; 106 | transition: all .2s; 107 | webkit-transition: all .2s; 108 | 109 | } 110 | .optionsInput:hover{ 111 | color: #de4f4f; 112 | border: 2px solid #de4f4f; 113 | } 114 | 115 | /*=============================*/ 116 | /*Signin Section*/ 117 | #input-username{ 118 | } 119 | 120 | #input-password{ 121 | } 122 | 123 | #logIn{ 124 | 125 | } 126 | 127 | /*=============================*/ 128 | /*Signout Section*/ 129 | #logOut{ 130 | 131 | } 132 | 133 | 134 | /*Password Change Area*/ 135 | #input-password-change-current{ 136 | 137 | } 138 | 139 | #input-password-change-new{ 140 | 141 | } 142 | 143 | #input-password-change-new-again{ 144 | 145 | } 146 | 147 | #closeChangePassword{ 148 | 149 | } 150 | 151 | #openChangePassword{ 152 | 153 | } 154 | 155 | 156 | /*=============================*/ 157 | /*Create new user buttons*/ 158 | #openCreate{ 159 | 160 | } 161 | 162 | #closeCreate{ 163 | 164 | } 165 | 166 | #createUser{ 167 | 168 | } 169 | -------------------------------------------------------------------------------- /customAccounts.js: -------------------------------------------------------------------------------- 1 | 2 | if (Meteor.isServer) { 3 | //customize emails sent to users 4 | Accounts.emailTemplates.siteName = "AwesomeSite"; 5 | Accounts.emailTemplates.from = "AwesomeSite Admin "; 6 | 7 | Accounts.emailTemplates.enrollAccount.subject = function (user) { 8 | return "Welcome to Awesome Town, " + user.profile.name; 9 | }; 10 | Accounts.emailTemplates.enrollAccount.text = function (user, url) { 11 | return "You have been selected to participate in building a better future!" 12 | + " To activate your account, simply click the link below:\n\n" 13 | + url; 14 | }; 15 | Accounts.emailTemplates.resetPassword.subject = function (user) { 16 | return "Reset Password Request for " + user.profile.name; 17 | }; 18 | Accounts.emailTemplates.resetPassword.text = function (user, url) { 19 | return "Password reset information for user " + user + ". at " + url + "."; 20 | }; 21 | Accounts.emailTemplates.verifyEmail.subject = function (user) { 22 | return "Please Verify your Email for " + user.profile.name; 23 | }; 24 | Accounts.emailTemplates.verifyEmail.text = function (user, url) { 25 | return "Please verify email for " + user + ". at " + url + "."; 26 | }; 27 | //END 28 | 29 | throwError = function(str){ 30 | throw new Meteor.Error(403, str); 31 | return false; 32 | }; 33 | 34 | Accounts.validateNewUser(function (user) { 35 | 36 | console.log(user); 37 | 38 | if(!user.username){ 39 | return throwError('Username was blank'); 40 | } 41 | 42 | if (user.username.length < 5){ 43 | return throwError('Username needs at least 5 characters'); 44 | } 45 | 46 | user.username = user.username.toLowerCase(); 47 | 48 | if(user.username === "root" ){ 49 | return throwError('Reserved username'); 50 | } 51 | 52 | return true; 53 | }); 54 | 55 | // Accounts.onCreateUser(function(options, user) { 56 | // // We still want the default hook's 'profile' behavior. 57 | // if (options.profile) 58 | // user.profile = options.profile; 59 | // return user; 60 | // }); 61 | 62 | sendReset = function(user, emailOrDefault){ 63 | Accounts.sendResetPasswordEmail(userId, emailOrDefault); 64 | } 65 | 66 | // Accounts.sendEnrollmentEmail(userId, [email]) 67 | // Accounts.sendVerificationEmail(userId, [email]) 68 | } 69 | 70 | if (Meteor.isClient) { 71 | 72 | Accounts.onResetPasswordLink(function(){ 73 | 74 | }); 75 | 76 | Accounts.onLogin(function(){ 77 | //called on login success 78 | }); 79 | 80 | Accounts.onLoginFailure(function(){ 81 | //on login failure 82 | }); 83 | 84 | Accounts.onEmailVerificationLink(function(token, done){ 85 | tokenEmailConfirm(token); 86 | done(); 87 | }); 88 | 89 | passwordSanity = function(pwd, optPwd){ 90 | 91 | if(optPwd && pwd !== optPwd){ 92 | doOptionsChangeError("Confirmed password does not match"); 93 | return false; 94 | } 95 | 96 | //1 lower, 1 upper, 1 number, 6-15 length, no spaces 97 | var pass = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).{4,10}$/; 98 | if(!pass.test(pwd) ){ 99 | doOptionsChangeError("Regex requires: 1 Upper, 6-15, no spaces"); 100 | return false; 101 | } 102 | 103 | if(JSON.parse(JSON.stringify(pwd)) !== pwd){ 104 | doOptionsChangeError("JSON Test Failed"); 105 | return false; 106 | } 107 | 108 | return true; 109 | } 110 | 111 | usernameSanity = function(usr){ 112 | if(JSON.parse(JSON.stringify(usr)) !== usr){ 113 | doOptionsChangeError("user contains illegal characters"); 114 | return false; 115 | } 116 | return true; 117 | } 118 | 119 | logIn = function(){ 120 | var username = $("#input-username").val(); 121 | var password = $("#input-password").val(); 122 | username = username.toLowerCase(); 123 | 124 | 125 | if(usernameSanity(username) === false){ 126 | return; 127 | } 128 | if(passwordSanity(password) === false){ 129 | return; 130 | } 131 | 132 | Meteor.loginWithPassword({username: username}, password, function(error){ 133 | if(error){ 134 | doOptionsChangeError(error); 135 | }else{ 136 | Meteor.logoutOtherClients(); 137 | } 138 | 139 | }); 140 | }; 141 | 142 | logOut = function(){ 143 | Meteor.logout(function(error){ 144 | if(error){ 145 | //something 146 | } 147 | }); 148 | }; 149 | 150 | Template.registerHelper('checkCreateIsOpen', function(){ 151 | return Session.get("createIsOpen"); 152 | }); 153 | 154 | closeCreateUser = function(){ 155 | Session.set("createIsOpen", false); 156 | } 157 | 158 | openCreateUser = function(){ 159 | Session.set("createIsOpen", true); 160 | } 161 | 162 | createUser = function(){ 163 | var username = $('#input-username').val(); 164 | var newPassword = $('#input-password').val(); 165 | var newPasswordAgain = $('#input-password-again').val(); 166 | 167 | if(!usernameSanity(username)) return; 168 | if(!passwordSanity(newPassword, newPasswordAgain)) return; 169 | 170 | var options = { 171 | //can add email to this option 172 | username: username, 173 | password: newPassword, 174 | profile: {nickname: username } 175 | }; 176 | 177 | disableButton("createUser"); 178 | 179 | Accounts.createUser(options, function(error){ 180 | //on create 181 | if(error){ 182 | console.log("error on create user"); 183 | doOptionsChangeError(error); 184 | }else{ 185 | console.log("user created"); 186 | closeCreateUser(); 187 | } 188 | enableButton("createUser"); 189 | }); 190 | } 191 | 192 | closeChangePassword = function(){ 193 | Session.set("changePasswordIsOpen", false); 194 | }; 195 | 196 | openChangePassword = function(){ 197 | Session.set("changePasswordIsOpen", true); 198 | }; 199 | 200 | Template.registerHelper('checkChangePasswordIsOpen', function(){ 201 | return Session.get("changePasswordIsOpen"); 202 | }); 203 | 204 | //stores settimout id here so can be cleared later 205 | //before being set again 206 | var errTimeoutId = null; 207 | doOptionsChangeError = function(errOrText){ 208 | var text; 209 | if(typeof errOrText === "string"){ 210 | text = errOrText; 211 | }else{ 212 | text = errOrText.reason; 213 | } 214 | $("#options-error").text(text); 215 | clearTimeout(errTimeoutId); 216 | errTimeoutId = setTimeout(function(){ 217 | $("#options-error").text(""); 218 | }, 5000); 219 | 220 | }; 221 | 222 | changePassword = function(){ 223 | var oldPassword = $('#input-password-change-current').val(); 224 | var newPassword = $('#input-password-change-new').val(); 225 | var newPasswordAgain = $('#input-password-change-new-again').val(); 226 | 227 | disableButton("change-password"); 228 | //must be logged in 229 | Accounts.changePassword(oldPassword, newPassword, function(error){ 230 | if(error){ 231 | doOptionsChangeError(error); 232 | }else{ 233 | //re-enable button 234 | closeChangePassword(); 235 | $('#input-password-change-current').val(""); 236 | $('#input-password-change-new').val(""); 237 | $('#input-password-change-new-again').val(""); 238 | doOptionsChangeError("password change successful"); 239 | } 240 | 241 | enableButton("change-password"); 242 | 243 | }); 244 | }; 245 | 246 | forgotPassword = function(){ 247 | var options = {email: "string"}; 248 | Accounts.forgotPassword(options, function(error){ 249 | //forgot password 250 | }); 251 | }; 252 | 253 | tokenPasswordConfirm = function(){ 254 | var token = "string"; 255 | var newPassword = "string" 256 | Accounts.resetPassword(token, newPassword, function(error){ 257 | //token confirmed 258 | }) 259 | }; 260 | 261 | tokenEmailConfirm = function(token){ 262 | token = token || //pull from somewehre? 263 | Accounts.verifyEmail(token, function(error){ 264 | //email confirmed 265 | }); 266 | }; 267 | 268 | disableButton = function(str){ 269 | $("#" + str).prop("disabled", true); 270 | }; 271 | 272 | enableButton = function(str){ 273 | $("#" + str).removeProp("disabled"); 274 | }; 275 | 276 | toggleOptions = function(){ 277 | console.log("toggle"); 278 | if(Session.get("optionsToggleIsOpen") !== true){ 279 | Session.set("optionsToggleIsOpen", true); 280 | }else{ 281 | Session.set("optionsToggleIsOpen", false); 282 | } 283 | }; 284 | } 285 | 286 | --------------------------------------------------------------------------------