├── .editorconfig ├── .gitignore ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── LICENSE.md ├── README.md ├── lib ├── client.js ├── core.js ├── server.js └── templates_helpers │ └── at_input.js └── package.js /.editorconfig: -------------------------------------------------------------------------------- 1 | #.editorconfig 2 | # Meteor adapted EditorConfig, http://EditorConfig.org 3 | # By RaiX 2013 4 | 5 | root = true 6 | 7 | [*.js] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | trim_trailing_whitespace = true 13 | charset = utf-8 14 | max_line_length = 80 15 | indent_brace_style = 1TBS 16 | spaces_around_operators = true 17 | quote_type = auto 18 | # curly_bracket_next_line = true 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .build* 2 | versions.json 3 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | client/compatibility 2 | packages -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | //.jshintrc 2 | { 3 | // JSHint Meteor Configuration File 4 | // Match the Meteor Style Guide 5 | // 6 | // By @raix with contributions from @aldeed and @awatson1978 7 | // Source https://github.com/raix/Meteor-jshintrc 8 | // 9 | // See http://jshint.com/docs/ for more details 10 | 11 | "maxerr" : 50, // {int} Maximum error before stopping 12 | 13 | // Enforcing 14 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 15 | "camelcase" : true, // true: Identifiers must be in camelCase 16 | "curly" : true, // true: Require {} for every new block or scope 17 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 18 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 19 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 20 | "indent" : 2, // {int} Number of spaces to use for indentation 21 | "latedef" : false, // true: Require variables/functions to be defined before being used 22 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 23 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 24 | "noempty" : true, // true: Prohibit use of empty blocks 25 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 26 | "plusplus" : false, // true: Prohibit use of `++` & `--` 27 | "quotmark" : false, // Quotation mark consistency: 28 | // false : do nothing (default) 29 | // true : ensure whatever is used is consistent 30 | // "single" : require single quotes 31 | // "double" : require double quotes 32 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 33 | "unused" : true, // true: Require all defined variables be used 34 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 35 | "trailing" : true, // true: Prohibit trailing whitespaces 36 | "maxparams" : false, // {int} Max number of formal params allowed per function 37 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 38 | "maxstatements" : false, // {int} Max number statements per function 39 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 40 | "maxlen" : 80, // {int} Max number of characters per line 41 | 42 | // Relaxing 43 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 44 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 45 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 46 | "eqnull" : false, // true: Tolerate use of `== null` 47 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 48 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) 49 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 50 | // (ex: `for each`, multiple try/catch, function expression…) 51 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 52 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 53 | "funcscope" : false, // true: Tolerate defining variables inside control statements" 54 | "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict') 55 | "iterator" : false, // true: Tolerate using the `__iterator__` property 56 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 57 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 58 | "laxcomma" : false, // true: Tolerate comma-first style coding 59 | "loopfunc" : false, // true: Tolerate functions being defined in loops 60 | "multistr" : false, // true: Tolerate multi-line strings 61 | "proto" : false, // true: Tolerate using the `__proto__` property 62 | "scripturl" : false, // true: Tolerate script-targeted URLs 63 | "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment 64 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 65 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 66 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 67 | "validthis" : false, // true: Tolerate using this in a non-constructor function 68 | 69 | // Environments 70 | "browser" : true, // Web Browser (window, document, etc) 71 | "couch" : false, // CouchDB 72 | "devel" : true, // Development/debugging (alert, confirm, etc) 73 | "dojo" : false, // Dojo Toolkit 74 | "jquery" : false, // jQuery 75 | "mootools" : false, // MooTools 76 | "node" : false, // Node.js 77 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 78 | "prototypejs" : false, // Prototype and Scriptaculous 79 | "rhino" : false, // Rhino 80 | "worker" : false, // Web Workers 81 | "wsh" : false, // Windows Scripting Host 82 | "yui" : false, // Yahoo User Interface 83 | //"meteor" : false, // Meteor.js 84 | 85 | // Legacy 86 | "nomen" : false, // true: Prohibit dangling `_` in variables 87 | "onevar" : false, // true: Allow only one `var` statement per function 88 | "passfail" : false, // true: Stop on first error 89 | "white" : false, // true: Check against strict whitespace and indentation rules 90 | 91 | // Custom globals, from http://docs.meteor.com, in the order they appear there 92 | "globals" : { 93 | "Meteor": false, 94 | "DDP": false, 95 | "Mongo": false, //Meteor.Collection renamed to Mongo.Collection 96 | "Session": false, 97 | "Accounts": false, 98 | "Template": false, 99 | "Blaze": false, //UI is being renamed Blaze 100 | "UI": false, 101 | "Match": false, 102 | "check": false, 103 | "Tracker": false, //Deps renamed to Tracker 104 | "Deps": false, 105 | "ReactiveVar": false, 106 | "EJSON": false, 107 | "HTTP": false, 108 | "Email": false, 109 | "Assets": false, 110 | "Handlebars": false, // https://github.com/meteor/meteor/wiki/Handlebars 111 | "Package": false, 112 | 113 | // Meteor internals 114 | "DDPServer": false, 115 | "global": false, 116 | "Log": false, 117 | "MongoInternals": false, 118 | "process": false, 119 | "WebApp": false, 120 | "WebAppInternals": false, 121 | 122 | // globals useful when creating Meteor packages 123 | "Npm": false, 124 | "Tinytest": false, 125 | 126 | // common Meteor packages 127 | "Random": false, 128 | "_": false, // Underscore.js 129 | "$": false, // jQuery 130 | "Router": false // iron-router 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: node_js 3 | node_js: 4 | - "0.10" 5 | before_install: 6 | - "curl -L http://git.io/ejPSng | /bin/sh" 7 | env: 8 | - TEST_COMMAND=meteor 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 [@splendido](https://github.com/splendido) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Meteor Icon](http://icon.meteor.com/package/useraccounts:iron-routing)](https://atmospherejs.com/useraccounts/iron-routing) 2 | 3 | # Iron Router add-on for User Accounts 4 | 5 | User Accounts is a suite of packages for the [Meteor.js](https://www.meteor.com/) platform. It provides highly customizable user accounts UI templates for many different front-end frameworks. At the moment it includes forms for sign in, sign up, forgot password, reset password, change password, enroll account. 6 | 7 | This package is an optional add-on for integration with [Iron Router](https://atmospherejs.com/iron/router). 8 | 9 | ## Configuration 10 | 11 | Before you configure routes for User Accounts with Iron Router, you will need to make sure you have set a few default configuration items. 12 | 13 | Assuming you have a main layout that looks like this: 14 | 15 | ```handlebars 16 | 25 | ``` 26 | 27 | You would configure the router and this package to use it like this: 28 | 29 | ```js 30 | Router.configure({ 31 | layoutTemplate: 'masterLayout', 32 | yieldTemplates: { 33 | myNav: {to: 'nav'}, 34 | myFooter: {to: 'footer'}, 35 | } 36 | }); 37 | 38 | AccountsTemplates.configure({ 39 | defaultLayout: 'myLayout', 40 | }); 41 | ``` 42 | 43 | NOTE: The above configs must load BEFORE your AccountsTemplates routes are defined (next section). 44 | 45 | ## Routes 46 | 47 | There are no routes provided by default. But you can easily configure routes for sign in, sign up, forgot password, reset password, change password, enroll account using `AccountsTemplates.configureRoute`. This is done internally relying on the awesome [Iron-Router](https://atmospherejs.com/package/iron-router) package. 48 | 49 | The simplest way is to make the call passing just the route code (available codes are: changePwd, enrollAccount, forgotPwd, resetPwd, signIn, signUp): 50 | 51 | ```javascript 52 | AccountsTemplates.configureRoute('signIn'); 53 | ``` 54 | 55 | This will set up the sign in route with a full-page form letting users access your app. 56 | 57 | **NOTE:** some routes need other useraccounts' regular options to be set in advance. Please make sure to have your calls to `AccountsTemplates.configureRoute` be executed after your calls to the regular `AccountsTemplates.configure` 58 | 59 | Actually, you can also pass in more options to adapt it to your needs with: 60 | 61 | ```javascript 62 | AccountsTemplates.configureRoute(route_code, options); 63 | ``` 64 | 65 | The following is a complete example of a route configuration: 66 | 67 | ```javascript 68 | AccountsTemplates.configureRoute('signIn', { 69 | name: 'signin', 70 | path: '/login', 71 | template: 'myLogin', 72 | layoutTemplate: 'myLayout', 73 | redirect: '/user-profile', 74 | }); 75 | ``` 76 | 77 | Fields `name`, `path`, `template`, and `layoutTemplate` are passed down directly to Router.map (see the official iron router documentation [here](https://github.com/EventedMind/iron-router/#api) for more details), while `redirect` permits to specify where to redirect the user after successful form submit. Actually, `redirect` can be a function so that, for example, the following: 78 | 79 | ```javascript 80 | AccountsTemplates.configureRoute('signIn', { 81 | redirect: function(){ 82 | var user = Meteor.user(); 83 | if (user) 84 | Router.go('/user/' + user._id); 85 | } 86 | }); 87 | ``` 88 | 89 | will redirect to, e.g., '/user/ae8WQQk6DrtDzA2AZ' after succesful login :-) 90 | 91 | 92 | All the above fields are optional and fall back to default values in case you don't provide them. Default values are as follows: 93 | 94 | | Action | route_code | Route Name | Route Path | Template | Redirect after Timeout | 95 | | --------------- | ------------- | --------------- | --------------- | -------------- |:----------------------:| 96 | | change password | changePwd | atChangePwd | /change-password | fullPageAtForm | | 97 | | enroll account | enrollAccount | atEnrollAccount | /enroll-account | fullPageAtForm | X | 98 | | forgot password | forgotPwd | atForgotPwd | /forgot-password | fullPageAtForm | X | 99 | | reset password | resetPwd | atResetPwd | /reset-password | fullPageAtForm | X | 100 | | sign in | signIn | atSignIn | /sign-in | fullPageAtForm | | 101 | | sign up | signUp | atSignUp | /sign-up | fullPageAtForm | | 102 | | verify email | verifyEmail | atVerifyEmail | /verify-email | fullPageAtForm | X | 103 | | resend verification email | resendVerificationEmail | atresendVerificationEmail | /send-again | fullPageAtForm | | 104 | 105 | If `layoutTemplate` is not specified, it falls back to what is currently set up with Iron-Router. 106 | If `redirect` is not specified, it default to the previous route (obviously routes set up with `AccountsTemplates.configureRoute` are excluded to provide a better user experience). What more, when the login form is shown to protect private content (see [Content Protection](#content-protection), the user is redirect to the protected page after successful sign in or sign up, regardless of whether a `redirect` parameter was passed for `signIn` or `signUp` route configuration or not. 107 | 108 | Besides the above list of routes you can also configure `ensureSignedIn` in order to specify different template and layout to be used for the Iron Router `ensuredSignedIn` plugin (see [Content Protection](#content-protection)): 109 | 110 | ```javascript 111 | AccountsTemplates.configureRoute('ensureSignedIn', { 112 | template: 'myLogin', 113 | layoutTemplate: 'myLayout', 114 | }); 115 | ``` 116 | 117 | in this case, any field different from `template` and `layoutTemplate` will be ignored! 118 | 119 | ## Content Protection 120 | 121 | useraccounts:iron-routing package come with an Iron Router plugin called `ensureSignedIn` which permits to prompt for the sign in form for the pages requiring the user to be signed in. 122 | It behaves nicely letting the required route path inside the address bar and bringing you back to the same route once logged in. 123 | 124 | **Please note that a fake version of `ensureSignedIn` is also available on server-side to allow for shared routing files, but there's no way to check whether a user is logged in or not on a server-side route!** 125 | 126 | To protect **all** you routes use it like this: 127 | 128 | ```javascript 129 | // Protect all Routes 130 | Router.plugin('ensureSignedIn'); 131 | 132 | // If you are using other plugins, pay attention to their load order. 133 | // Use *after* so you don't get 404's on your protected routes. 134 | Router.onBeforeAction('dataNotFound'); 135 | ``` 136 | 137 | While in case you want to protect *almost all* your routes you might want to set up the plugin this way: 138 | 139 | ```javascript 140 | Router.plugin('ensureSignedIn', { 141 | except: ['home', 'atSignIn', 'atSignUp', 'atForgotPassword'] 142 | }); 143 | ``` 144 | 145 | while an even better example could be 146 | 147 | ```javascript 148 | Router.plugin('ensureSignedIn', { 149 | except: _.pluck(AccountsTemplates.routes, 'name').concat(['home', 'contacts']) 150 | }); 151 | ``` 152 | 153 | if, instead, it's only a bunch of routes to be protected you could do (even more than once inside different files...): 154 | 155 | ```javascript 156 | Router.plugin('ensureSignedIn', { 157 | only: ['profile', 'privateStuff'] 158 | }); 159 | ``` 160 | 161 | Moreover, if you wish to customize the template and layout to be used you can change them with: 162 | 163 | ```javascript 164 | AccountsTemplates.configureRoute('ensureSignedIn', { 165 | template: 'myLogin', 166 | layoutTemplate: 'myLayout', 167 | }); 168 | ``` 169 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | /* global 2 | AccountsTemplates: false, 3 | grecaptcha: false, 4 | Iron: false, 5 | Router: false 6 | */ 7 | 'use strict'; 8 | 9 | 10 | // Previous path used for redirect after form submit 11 | AccountsTemplates._prevPath = null; 12 | 13 | // Possibly keeps reference to the handle for the timed out redirect 14 | // set on some routes 15 | AccountsTemplates.timedOutRedirect = null; 16 | 17 | 18 | AccountsTemplates.clearState = function() { 19 | _.each(this._fields, function(field){ 20 | field.clearStatus(); 21 | }); 22 | var form = this.state.form; 23 | form.set('error', null); 24 | form.set('result', null); 25 | form.set('message', null); 26 | 27 | AccountsTemplates.setDisabled(false); 28 | 29 | // Possibly clears timed out redirects 30 | if (AccountsTemplates.timedOutRedirect !== null) { 31 | Meteor.clearTimeout(AccountsTemplates.timedOutRedirect); 32 | AccountsTemplates.timedOutRedirect = null; 33 | } 34 | }; 35 | 36 | // Getter for previous route's path 37 | AccountsTemplates.getPrevPath = function() { 38 | return this._prevPath; 39 | }; 40 | 41 | // Setter for previous route's path 42 | AccountsTemplates.setPrevPath = function(newPath) { 43 | check(newPath, String); 44 | this._prevPath = newPath; 45 | }; 46 | 47 | var ensureSignedIn = function() { 48 | if (!Meteor.userId()) { 49 | Tracker.nonreactive(function () { 50 | AccountsTemplates.setPrevPath(Router.current().url); 51 | }); 52 | AccountsTemplates.setState(AccountsTemplates.options.defaultState, function(){ 53 | var err = AccountsTemplates.texts.errors.mustBeLoggedIn; 54 | AccountsTemplates.state.form.set('error', [err]); 55 | }); 56 | AccountsTemplates.avoidRedirect = true; 57 | // render the login template but keep the url in the browser the same 58 | 59 | var options = AccountsTemplates.routes.ensureSignedIn; 60 | 61 | // Determines the template to be rendered in case no specific one was configured for ensureSignedIn 62 | var signInRouteTemplate = AccountsTemplates.routes.signIn && AccountsTemplates.routes.signIn.template; 63 | var template = (options && options.template) || signInRouteTemplate || 'fullPageAtForm'; 64 | 65 | // Determines the layout to be used in case no specific one was configured for ensureSignedIn 66 | var defaultLayout = AccountsTemplates.options.defaultLayout || Router.options.layoutTemplate; 67 | var layoutTemplate = (options && options.layoutTemplate) || defaultLayout; 68 | 69 | this.layout(layoutTemplate); 70 | this.render(template); 71 | this.renderRegions(); 72 | } else { 73 | AccountsTemplates.clearError(); 74 | this.next(); 75 | } 76 | }; 77 | 78 | AccountsTemplates.ensureSignedIn = function() { 79 | console.warn( 80 | '[UserAccounts] AccountsTemplates.ensureSignedIn will be deprecated soon, please use the plugin version\n' + 81 | ' see https://github.com/meteor-useraccounts/core/blob/master/Guide.md#content-protection' 82 | ); 83 | ensureSignedIn.call(this); 84 | }; 85 | 86 | 87 | Iron.Router.plugins.ensureSignedIn = function (router, options) { 88 | // this loading plugin just creates an onBeforeAction hook 89 | router.onRun(function(){ 90 | if (Meteor.loggingIn()) { 91 | this.renderRegions(); 92 | } else { 93 | this.next(); 94 | } 95 | }, options); 96 | 97 | router.onBeforeAction( 98 | ensureSignedIn, 99 | options 100 | ); 101 | 102 | router.onStop(function(){ 103 | AccountsTemplates.clearError(); 104 | }); 105 | }; 106 | 107 | 108 | 109 | // Stores previous path on path change... 110 | Router.onStop(function() { 111 | Tracker.nonreactive(function () { 112 | var currentPath = Router.current().url; 113 | var currentPathClean = currentPath.replace(/^\/+|\/+$/gm,''); 114 | var isKnownRoute = _.map(AccountsTemplates.knownRoutes, function(path){ 115 | if (!path) { 116 | return false; 117 | } 118 | path = path.replace(/^\/+|\/+$/gm,''); 119 | var known = RegExp(path).test(currentPathClean); 120 | return known; 121 | }); 122 | if (!_.some(isKnownRoute)) { 123 | AccountsTemplates.setPrevPath(currentPath); 124 | } 125 | AccountsTemplates.avoidRedirect = false; 126 | }); 127 | }); 128 | 129 | 130 | AccountsTemplates.linkClick = function(route){ 131 | if (AccountsTemplates.disabled()) { 132 | return; 133 | } 134 | var path = AccountsTemplates.getRoutePath(route); 135 | if (path === '#' || AccountsTemplates.avoidRedirect || 136 | (Router.current().route && path === Router.current().route.path())) { 137 | AccountsTemplates.setState(route); 138 | } 139 | else { 140 | Meteor.defer(function(){ 141 | Router.go(path); 142 | }); 143 | } 144 | 145 | var firstVisibleInput = _.find(this.getFields(), function(f){ 146 | return _.contains(f.visible, route); 147 | }); 148 | if (firstVisibleInput) { 149 | $('input#at-field-' + firstVisibleInput._id).focus(); 150 | } 151 | }; 152 | 153 | AccountsTemplates.logout = function(){ 154 | var onLogoutHook = AccountsTemplates.options.onLogoutHook; 155 | var homeRoutePath = AccountsTemplates.options.homeRoutePath; 156 | Meteor.logout(function(){ 157 | if (onLogoutHook) { 158 | onLogoutHook(); 159 | } 160 | else if (homeRoutePath) { 161 | Router.go(homeRoutePath); 162 | } 163 | }); 164 | }; 165 | 166 | AccountsTemplates.postSubmitRedirect = function(route){ 167 | if (AccountsTemplates.avoidRedirect) { 168 | AccountsTemplates.avoidRedirect = false; 169 | } 170 | else { 171 | var nextPath = AccountsTemplates.routes[route] && AccountsTemplates.routes[route].redirect; 172 | if (nextPath){ 173 | if (_.isFunction(nextPath)) { 174 | nextPath(); 175 | } 176 | else { 177 | Router.go(nextPath); 178 | } 179 | }else{ 180 | var previousPath = AccountsTemplates.getPrevPath(); 181 | if (previousPath && Router.current().route.path() !== previousPath) { 182 | Router.go(previousPath); 183 | } 184 | else{ 185 | var homeRoutePath = AccountsTemplates.options.homeRoutePath; 186 | if (homeRoutePath) { 187 | Router.go(homeRoutePath); 188 | } 189 | } 190 | } 191 | } 192 | }; 193 | 194 | AccountsTemplates.submitCallback = function(error, state, onSuccess){ 195 | 196 | var onSubmitHook = AccountsTemplates.options.onSubmitHook; 197 | if(onSubmitHook) { 198 | onSubmitHook(error, state); 199 | } 200 | 201 | if (error) { 202 | if(_.isObject(error.details)) { 203 | // If error.details is an object, we may try to set fields errors from it 204 | _.each(error.details, function(error, fieldId){ 205 | AccountsTemplates.getField(fieldId).setError(error); 206 | }); 207 | } 208 | else { 209 | var err = 'error.accounts.Unknown error'; 210 | if (error.reason) { 211 | err = error.reason; 212 | } 213 | if (err.substring(0, 15) !== 'error.accounts.') { 214 | err = 'error.accounts.' + err; 215 | } 216 | AccountsTemplates.state.form.set('error', [err]); 217 | } 218 | AccountsTemplates.setDisabled(false); 219 | // Possibly resets reCaptcha form 220 | if (state === 'signUp' && AccountsTemplates.options.showReCaptcha) { 221 | grecaptcha.reset(); 222 | } 223 | } 224 | else{ 225 | if (onSuccess) { 226 | onSuccess(); 227 | } 228 | 229 | if (_.contains(['enrollAccount', 'forgotPwd', 'resetPwd', 'verifyEmail'], state)){ 230 | var redirectTimeout = AccountsTemplates.options.redirectTimeout; 231 | if (redirectTimeout > 0) { 232 | AccountsTemplates.timedOutRedirect = Meteor.setTimeout(function(){ 233 | AccountsTemplates.timedOutRedirect = null; 234 | AccountsTemplates.setDisabled(false); 235 | AccountsTemplates.postSubmitRedirect(state); 236 | }, redirectTimeout); 237 | } 238 | } 239 | else if (state){ 240 | AccountsTemplates.setDisabled(false); 241 | AccountsTemplates.postSubmitRedirect(state); 242 | } 243 | } 244 | }; 245 | -------------------------------------------------------------------------------- /lib/core.js: -------------------------------------------------------------------------------- 1 | /* global 2 | AccountsTemplates: false 3 | */ 4 | 'use strict'; 5 | 6 | // --------------------------------------------------------------------------------- 7 | 8 | // Patterns for methods" parameters 9 | 10 | // --------------------------------------------------------------------------------- 11 | 12 | // Route configuration pattern to be checked with check 13 | var ROUTE_PAT = { 14 | name: Match.Optional(String), 15 | path: Match.Optional(String), 16 | template: Match.Optional(String), 17 | layoutTemplate: Match.Optional(String), 18 | redirect: Match.Optional(Match.OneOf(String, Match.Where(_.isFunction))), 19 | }; 20 | 21 | /* 22 | Routes configuration can be done by calling AccountsTemplates.configureRoute with the route name and the 23 | following options in a separate object. E.g. AccountsTemplates.configureRoute("gingIn", option); 24 | name: String (optional). A unique route"s name to be passed to iron-router 25 | path: String (optional). A unique route"s path to be passed to iron-router 26 | template: String (optional). The name of the template to be rendered 27 | layoutTemplate: String (optional). The name of the layout to be used 28 | redirect: String (optional). The name of the route (or its path) where to redirect after form submit 29 | */ 30 | 31 | 32 | // Allowed routes along with theirs default configuration values 33 | AccountsTemplates.ROUTE_DEFAULT = { 34 | changePwd: { name: "atChangePwd", path: "/change-password"}, 35 | enrollAccount: { name: "atEnrollAccount", path: "/enroll-account"}, 36 | ensureSignedIn: { name: "atEnsureSignedIn", path: null}, 37 | forgotPwd: { name: "atForgotPwd", path: "/forgot-password"}, 38 | resetPwd: { name: "atResetPwd", path: "/reset-password"}, 39 | signIn: { name: "atSignIn", path: "/sign-in"}, 40 | signUp: { name: "atSignUp", path: "/sign-up"}, 41 | verifyEmail: { name: "atVerifyEmail", path: "/verify-email"}, 42 | resendVerificationEmail: { name: "atResendVerificationEmail", path: "/send-again"}, 43 | }; 44 | 45 | 46 | // Current configuration values 47 | // Redirects 48 | AccountsTemplates.options.homeRoutePath = "/"; 49 | AccountsTemplates.options.redirectTimeout = 2000; // 2 seconds 50 | 51 | // Known routes used to filter out previous path for redirects... 52 | AccountsTemplates.knownRoutes = []; 53 | 54 | // Configured routes 55 | AccountsTemplates.routes = {}; 56 | 57 | AccountsTemplates.configureRoute = function(route, options) { 58 | check(route, String); 59 | check(options, Match.OneOf(undefined, Match.ObjectIncluding(ROUTE_PAT))); 60 | options = _.clone(options); 61 | // Route Configuration can be done only before initialization 62 | if (this._initialized) { 63 | throw new Error("Route Configuration can be done only before AccountsTemplates.init!"); 64 | } 65 | // Only allowed routes can be configured 66 | if (!(route in this.ROUTE_DEFAULT)) { 67 | throw new Error("Unknown Route!"); 68 | } 69 | // Allow route configuration only once 70 | if (route in this.routes) { 71 | throw new Error("Route already configured!"); 72 | } 73 | 74 | // Possibly adds a initial / to the provided path 75 | if (options && options.path && options.path[0] !== "/") { 76 | options.path = "/" + options.path; 77 | } 78 | // Updates the current configuration 79 | options = _.defaults(options || {}, this.ROUTE_DEFAULT[route]); 80 | 81 | this.routes[route] = options; 82 | // Known routes are used to filter out previous path for redirects... 83 | AccountsTemplates.knownRoutes.push(options.path); 84 | 85 | if (Meteor.isServer){ 86 | // Configures "reset password" email link 87 | if (route === "resetPwd"){ 88 | var resetPwdPath = options.path.substr(1); 89 | Accounts.urls.resetPassword = function(token){ 90 | return Meteor.absoluteUrl(resetPwdPath + "/" + token); 91 | }; 92 | } 93 | // Configures "enroll account" email link 94 | if (route === "enrollAccount"){ 95 | var enrollAccountPath = options.path.substr(1); 96 | Accounts.urls.enrollAccount = function(token){ 97 | return Meteor.absoluteUrl(enrollAccountPath + "/" + token); 98 | }; 99 | } 100 | // Configures "verify email" email link 101 | if (route === "verifyEmail"){ 102 | var verifyEmailPath = options.path.substr(1); 103 | Accounts.urls.verifyEmail = function(token){ 104 | return Meteor.absoluteUrl(verifyEmailPath + "/" + token); 105 | }; 106 | } 107 | } 108 | 109 | if (route === "ensureSignedIn") { 110 | return; 111 | } 112 | if (route === "changePwd" && !AccountsTemplates.options.enablePasswordChange) { 113 | throw new Error("changePwd route configured but enablePasswordChange set to false!"); 114 | } 115 | if (route === "forgotPwd" && !AccountsTemplates.options.showForgotPasswordLink) { 116 | throw new Error("forgotPwd route configured but showForgotPasswordLink set to false!"); 117 | } 118 | if (route === "signUp" && AccountsTemplates.options.forbidClientAccountCreation) { 119 | throw new Error("signUp route configured but forbidClientAccountCreation set to true!"); 120 | } 121 | 122 | // Determines the default layout to be used in case no specific one is specified for single routes 123 | var defaultLayout = AccountsTemplates.options.defaultLayout || Router.options.layoutTemplate; 124 | 125 | var name = options.name; // Default provided... 126 | var path = options.path; // Default provided... 127 | var template = options.template || "fullPageAtForm"; 128 | var layoutTemplate = options.layoutTemplate || defaultLayout; 129 | var additionalOptions = _.omit(options, [ 130 | "layoutTemplate", "name", "path", "redirect", "template" 131 | ]); 132 | 133 | // Possibly adds token parameter 134 | if (_.contains(["enrollAccount", "resetPwd", "verifyEmail"], route)){ 135 | path += "/:paramToken"; 136 | if (route === "verifyEmail") { 137 | Router.route(path, _.extend(additionalOptions, { 138 | name: name, 139 | template: template, 140 | layoutTemplate: layoutTemplate, 141 | onRun: function() { 142 | AccountsTemplates.setState(route); 143 | AccountsTemplates.setDisabled(true); 144 | var token = this.params.paramToken; 145 | Accounts.verifyEmail(token, function(error){ 146 | AccountsTemplates.setDisabled(false); 147 | AccountsTemplates.submitCallback(error, route, function(){ 148 | AccountsTemplates.state.form.set("result", AccountsTemplates.texts.info.emailVerified); 149 | }); 150 | }); 151 | 152 | this.next(); 153 | }, 154 | onStop: function() { 155 | AccountsTemplates.clearState(); 156 | }, 157 | })); 158 | } 159 | else { 160 | Router.route(path, _.extend(additionalOptions, { 161 | name: name, 162 | template: template, 163 | layoutTemplate: layoutTemplate, 164 | onBeforeAction: function() { 165 | AccountsTemplates.paramToken = this.params.paramToken; 166 | AccountsTemplates.setState(route); 167 | this.next(); 168 | }, 169 | onStop: function() { 170 | AccountsTemplates.clearState(); 171 | AccountsTemplates.paramToken = null; 172 | } 173 | })); 174 | } 175 | } 176 | else { 177 | Router.route(path, _.extend(additionalOptions, { 178 | name: name, 179 | template: template, 180 | layoutTemplate: layoutTemplate, 181 | onBeforeAction: function() { 182 | var redirect = false; 183 | if (route === 'changePwd') { 184 | if (!Meteor.loggingIn() && !Meteor.userId()) { 185 | redirect = true; 186 | } 187 | } 188 | else if (Meteor.userId()) { 189 | redirect = true; 190 | } 191 | if (redirect) { 192 | AccountsTemplates.postSubmitRedirect(route); 193 | this.stop(); 194 | } 195 | else { 196 | AccountsTemplates.setState(route); 197 | this.next(); 198 | } 199 | }, 200 | onStop: function() { 201 | AccountsTemplates.clearState(); 202 | } 203 | })); 204 | } 205 | }; 206 | 207 | 208 | AccountsTemplates.getRouteName = function(route) { 209 | if (route in this.routes) { 210 | return this.routes[route].name; 211 | } 212 | return null; 213 | }; 214 | 215 | AccountsTemplates.getRoutePath = function(route) { 216 | if (route in this.routes) { 217 | return this.routes[route].path; 218 | } 219 | return "#"; 220 | }; 221 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | /* global 2 | Iron: false 3 | */ 4 | 'use strict'; 5 | 6 | 7 | // Fake server-side IR plugin to allow for shared routing files 8 | Iron.Router.plugins.ensureSignedIn = function (router, options) {}; 9 | -------------------------------------------------------------------------------- /lib/templates_helpers/at_input.js: -------------------------------------------------------------------------------- 1 | /* global 2 | AccountsTemplates: false, 3 | Router: false 4 | */ 5 | 'use strict'; 6 | 7 | AccountsTemplates.atInputRendered.push(function(){ 8 | var fieldId = this.data._id; 9 | var queryKey = this.data.options && this.data.options.queryKey || fieldId; 10 | var currentR = Router.current(); 11 | var inputQueryVal = currentR && currentR.params && currentR.params.query && currentR.params.query[queryKey]; 12 | if (inputQueryVal) { 13 | this.$("input#at-field-" + fieldId).val(inputQueryVal); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | // Package metadata for Meteor.js web platform (https://www.meteor.com/) 2 | // This file is defined within the Meteor documentation at 3 | // 4 | // http://docs.meteor.com/#/full/packagejs 5 | // 6 | // and it is needed to define a Meteor package 7 | 'use strict'; 8 | 9 | Package.describe({ 10 | name: 'useraccounts:iron-routing', 11 | summary: 'UserAccounts package providing routes configuration capability via iron:router.', 12 | version: '1.14.2', 13 | git: 'https://github.com/meteor-useraccounts/iron-routing.git' 14 | }); 15 | 16 | Package.onUse(function(api) { 17 | api.versionsFrom('METEOR@1.0.3'); 18 | 19 | api.use([ 20 | 'check', 21 | 'iron:router', 22 | 'underscore', 23 | 'useraccounts:core', 24 | ], ['client', 'server']); 25 | 26 | api.imply([ 27 | 'useraccounts:core@1.14.2', 28 | 'iron:router@1.0.9', 29 | ], ['client', 'server']); 30 | 31 | api.addFiles([ 32 | 'lib/core.js', 33 | 'lib/server.js', 34 | ], ['server']); 35 | 36 | api.addFiles([ 37 | 'lib/core.js', 38 | 'lib/client.js', 39 | 'lib/templates_helpers/at_input.js', 40 | ], ['client']); 41 | }); 42 | --------------------------------------------------------------------------------