summaryrefslogtreecommitdiffstats
path: root/packages/meteor-useraccounts-core
diff options
context:
space:
mode:
authorLauri Ojansivu <x@xet7.org>2019-04-20 15:18:33 +0300
committerLauri Ojansivu <x@xet7.org>2019-04-20 15:18:33 +0300
commit73e265d8fd050ae3daa67472b4465a5c49d68910 (patch)
tree677b233934a43d8f873e24c794ce289d85e3a9b7 /packages/meteor-useraccounts-core
parent6117097a93bfb11c8bd4c87a23c44a50e22ceb87 (diff)
downloadwekan-73e265d8fd050ae3daa67472b4465a5c49d68910.tar.gz
wekan-73e265d8fd050ae3daa67472b4465a5c49d68910.tar.bz2
wekan-73e265d8fd050ae3daa67472b4465a5c49d68910.zip
Include to Wekan packages directory contents, so that meteor command would build all directly.
This also simplifies build scripts. Thanks to xet7 !
Diffstat (limited to 'packages/meteor-useraccounts-core')
-rw-r--r--packages/meteor-useraccounts-core/.editorconfig18
-rw-r--r--packages/meteor-useraccounts-core/.gitignore2
-rw-r--r--packages/meteor-useraccounts-core/.jshintignore2
-rw-r--r--packages/meteor-useraccounts-core/.jshintrc132
-rw-r--r--packages/meteor-useraccounts-core/.travis.yml8
-rw-r--r--packages/meteor-useraccounts-core/Guide.md1433
-rw-r--r--packages/meteor-useraccounts-core/History.md353
-rw-r--r--packages/meteor-useraccounts-core/LICENSE.md20
-rw-r--r--packages/meteor-useraccounts-core/README.md104
-rw-r--r--packages/meteor-useraccounts-core/lib/client.js464
-rw-r--r--packages/meteor-useraccounts-core/lib/core.js593
-rw-r--r--packages/meteor-useraccounts-core/lib/field.js292
-rw-r--r--packages/meteor-useraccounts-core/lib/methods.js25
-rw-r--r--packages/meteor-useraccounts-core/lib/server.js184
-rw-r--r--packages/meteor-useraccounts-core/lib/server_methods.js142
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_error.js26
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_form.js83
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_input.js124
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_message.js7
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_nav_button.js16
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_oauth.js5
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_pwd_form.js331
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_pwd_form_btn.js18
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_pwd_link.js24
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_reCaptcha.js19
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_resend_verification_email_link.js24
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_result.js7
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_sep.js5
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_signin_link.js24
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_signup_link.js24
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_social.js105
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_terms_link.js33
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/at_title.js7
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/ensure_signed_in.html12
-rw-r--r--packages/meteor-useraccounts-core/lib/templates_helpers/ensure_signed_in.js15
-rw-r--r--packages/meteor-useraccounts-core/lib/utils.js19
-rw-r--r--packages/meteor-useraccounts-core/package.js94
-rw-r--r--packages/meteor-useraccounts-core/tests/tests.js215
38 files changed, 5009 insertions, 0 deletions
diff --git a/packages/meteor-useraccounts-core/.editorconfig b/packages/meteor-useraccounts-core/.editorconfig
new file mode 100644
index 00000000..6667b413
--- /dev/null
+++ b/packages/meteor-useraccounts-core/.editorconfig
@@ -0,0 +1,18 @@
+#.editorconfig
+# Meteor adapted EditorConfig, http://EditorConfig.org
+# By RaiX 2013
+
+root = true
+
+[*.js]
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 2
+trim_trailing_whitespace = true
+charset = utf-8
+max_line_length = 80
+indent_brace_style = 1TBS
+spaces_around_operators = true
+quote_type = auto
+# curly_bracket_next_line = true
diff --git a/packages/meteor-useraccounts-core/.gitignore b/packages/meteor-useraccounts-core/.gitignore
new file mode 100644
index 00000000..0f1edf50
--- /dev/null
+++ b/packages/meteor-useraccounts-core/.gitignore
@@ -0,0 +1,2 @@
+.build*
+versions.json
diff --git a/packages/meteor-useraccounts-core/.jshintignore b/packages/meteor-useraccounts-core/.jshintignore
new file mode 100644
index 00000000..810f9466
--- /dev/null
+++ b/packages/meteor-useraccounts-core/.jshintignore
@@ -0,0 +1,2 @@
+client/compatibility
+packages \ No newline at end of file
diff --git a/packages/meteor-useraccounts-core/.jshintrc b/packages/meteor-useraccounts-core/.jshintrc
new file mode 100644
index 00000000..7a0c90b0
--- /dev/null
+++ b/packages/meteor-useraccounts-core/.jshintrc
@@ -0,0 +1,132 @@
+//.jshintrc
+{
+ // JSHint Meteor Configuration File
+ // Match the Meteor Style Guide
+ //
+ // By @raix with contributions from @aldeed and @awatson1978
+ // Source https://github.com/raix/Meteor-jshintrc
+ //
+ // See http://jshint.com/docs/ for more details
+
+ "maxerr" : 50, // {int} Maximum error before stopping
+
+ // Enforcing
+ "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
+ "camelcase" : true, // true: Identifiers must be in camelCase
+ "curly" : true, // true: Require {} for every new block or scope
+ "eqeqeq" : true, // true: Require triple equals (===) for comparison
+ "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
+ "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
+ "indent" : 2, // {int} Number of spaces to use for indentation
+ "latedef" : false, // true: Require variables/functions to be defined before being used
+ "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
+ "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
+ "noempty" : true, // true: Prohibit use of empty blocks
+ "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
+ "plusplus" : false, // true: Prohibit use of `++` & `--`
+ "quotmark" : false, // Quotation mark consistency:
+ // false : do nothing (default)
+ // true : ensure whatever is used is consistent
+ // "single" : require single quotes
+ // "double" : require double quotes
+ "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
+ "unused" : true, // true: Require all defined variables be used
+ "strict" : true, // true: Requires all functions run in ES5 Strict Mode
+ "trailing" : true, // true: Prohibit trailing whitespaces
+ "maxparams" : false, // {int} Max number of formal params allowed per function
+ "maxdepth" : false, // {int} Max depth of nested blocks (within functions)
+ "maxstatements" : false, // {int} Max number statements per function
+ "maxcomplexity" : false, // {int} Max cyclomatic complexity per function
+ "maxlen" : 80, // {int} Max number of characters per line
+
+ // Relaxing
+ "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
+ "boss" : false, // true: Tolerate assignments where comparisons would be expected
+ "debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
+ "eqnull" : false, // true: Tolerate use of `== null`
+ "es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
+ "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
+ "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
+ // (ex: `for each`, multiple try/catch, function expression…)
+ "evil" : false, // true: Tolerate use of `eval` and `new Function()`
+ "expr" : false, // true: Tolerate `ExpressionStatement` as Programs
+ "funcscope" : false, // true: Tolerate defining variables inside control statements"
+ "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict')
+ "iterator" : false, // true: Tolerate using the `__iterator__` property
+ "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
+ "laxbreak" : false, // true: Tolerate possibly unsafe line breakings
+ "laxcomma" : false, // true: Tolerate comma-first style coding
+ "loopfunc" : false, // true: Tolerate functions being defined in loops
+ "multistr" : false, // true: Tolerate multi-line strings
+ "proto" : false, // true: Tolerate using the `__proto__` property
+ "scripturl" : false, // true: Tolerate script-targeted URLs
+ "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment
+ "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
+ "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
+ "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
+ "validthis" : false, // true: Tolerate using this in a non-constructor function
+
+ // Environments
+ "browser" : true, // Web Browser (window, document, etc)
+ "couch" : false, // CouchDB
+ "devel" : true, // Development/debugging (alert, confirm, etc)
+ "dojo" : false, // Dojo Toolkit
+ "jquery" : false, // jQuery
+ "mootools" : false, // MooTools
+ "node" : false, // Node.js
+ "nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
+ "prototypejs" : false, // Prototype and Scriptaculous
+ "rhino" : false, // Rhino
+ "worker" : false, // Web Workers
+ "wsh" : false, // Windows Scripting Host
+ "yui" : false, // Yahoo User Interface
+ //"meteor" : false, // Meteor.js
+
+ // Legacy
+ "nomen" : false, // true: Prohibit dangling `_` in variables
+ "onevar" : false, // true: Allow only one `var` statement per function
+ "passfail" : false, // true: Stop on first error
+ "white" : false, // true: Check against strict whitespace and indentation rules
+
+ // Custom globals, from http://docs.meteor.com, in the order they appear there
+ "globals" : {
+ "Meteor": false,
+ "DDP": false,
+ "Mongo": false, //Meteor.Collection renamed to Mongo.Collection
+ "Session": false,
+ "Accounts": false,
+ "Template": false,
+ "Blaze": false, //UI is being renamed Blaze
+ "UI": false,
+ "Match": false,
+ "check": false,
+ "Tracker": false, //Deps renamed to Tracker
+ "Deps": false,
+ "ReactiveVar": false,
+ "EJSON": false,
+ "HTTP": false,
+ "Email": false,
+ "Assets": false,
+ "Handlebars": false, // https://github.com/meteor/meteor/wiki/Handlebars
+ "Package": false,
+
+ // Meteor internals
+ "DDPServer": false,
+ "global": false,
+ "Log": false,
+ "MongoInternals": false,
+ "process": false,
+ "WebApp": false,
+ "WebAppInternals": false,
+
+ // globals useful when creating Meteor packages
+ "Npm": false,
+ "Tinytest": false,
+
+ // common Meteor packages
+ "Random": false,
+ "_": false, // Underscore.js
+ "$": false, // jQuery
+ "Router": false // iron-router
+ }
+}
diff --git a/packages/meteor-useraccounts-core/.travis.yml b/packages/meteor-useraccounts-core/.travis.yml
new file mode 100644
index 00000000..7846a282
--- /dev/null
+++ b/packages/meteor-useraccounts-core/.travis.yml
@@ -0,0 +1,8 @@
+sudo: required
+language: node_js
+node_js:
+ - "0.10"
+before_install:
+ - "curl -L http://git.io/ejPSng | /bin/sh"
+env:
+ - TEST_COMMAND=meteor
diff --git a/packages/meteor-useraccounts-core/Guide.md b/packages/meteor-useraccounts-core/Guide.md
new file mode 100644
index 00000000..c84b3f8b
--- /dev/null
+++ b/packages/meteor-useraccounts-core/Guide.md
@@ -0,0 +1,1433 @@
+User Accounts
+=============
+
+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, and link or remove of many 3rd party services.
+
+<a name="documentation"/>
+# Documentation
+
+* [Features](#features)
+* [Quick Start](#quickstart)
+ * [Available Versions](#available-versions)
+ * [Boilerplates](#boilerplates)
+ * [Setup](#setup)
+ * [Routing](#routing)
+ * [Templates](#templates)
+* [Basic Customization](#basic-customization)
+ * [I18n Support](#i18n)
+ * [Configuration API](#configuration-api)
+ * [Options](#options)
+ * [logout](#logout)
+ * [Internal States](#internal-states)
+ * [Content Protection](#content-protection)
+ * [reCaptcha Setup](#reCaptcha-setup)
+ * [Detect reactively when a form is being processed](#detect-reactively-when-a-form-is-being-processed)
+* [Advanced Customization](#advanced-customization)
+ * [Configuring Texts](#configuring-texts)
+ * [Form Title](#form-title)
+ * [Button Text](#button-text)
+ * [Social Button Icons](#social-button-icons)
+ * [Info Text](#info-text)
+ * [Errors Text](#errors-text)
+ * [Disabling Client-side Accounts Creation](#disabling-client-side-accounts-creation)
+ * [Form Fields Configuration](#form-fields-configuration)
+ * [Extending Templates](#extending-templates)
+ * [Grouping Fields](#grouping-fields)
+ * [CSS Rules](#css-rules)
+* [Wrapping Up for Famo.us](#wrapping-up-for-famo.us)
+* [Side Notes](#side-notes)
+ * [3rd Party Login Services Configuration](#3rd-party-login-services-configuration)
+
+
+<a name="features"/>
+## Features
+
+* fully customizable
+* security aware
+* internationalization support thanks to [accounts-t9n](https://github.com/softwarerero/meteor-accounts-t9n)
+* custom sign-up fields
+* robust server side sign-up field validation
+* easy content protection
+* return to previous route after sign-in (even for random sign in choice and not only after required sign-in)
+* fully reactive, Blaze fast!
+* no use of `Session` object
+* very easily stylizable for different font-end frameworks
+* ...[wrap it up for famo.us](#wrapping-up-for-famo.us) with a simple meteor line!
+
+
+
+<a name="quickstart"/>
+## Quick Start
+
+
+<a name="available-versions"/>
+### Available Versions
+
+* [useraccounts:bootstrap](https://atmospherejs.com/useraccounts/bootstrap) styled for [Twitter Bootstrap](http://getbootstrap.com/)
+* [useraccounts:foundation](https://atmospherejs.com/useraccounts/foundation) styled for [Zurb Foundation](http://foundation.zurb.com/)
+* [useraccounts:ionic](https://atmospherejs.com/useraccounts/ionic) styled for [Ionic](http://ionicframework.com/)
+* [useraccounts:materialize](https://atmospherejs.com/useraccounts/materialize) styled for [Materialize](http://materializecss.com/)
+* [useraccounts:polymer](https://atmospherejs.com/useraccounts/polymer) styled for [Polymer](https://www.polymer-project.org/) (WIP)
+* [useraccounts:ratchet](https://atmospherejs.com/useraccounts/ratchet) styled for [Ratchet](http://goratchet.com/)
+* [useraccounts:semantic-ui](https://atmospherejs.com/useraccounts/semantic-ui) styled for [Semantic UI](http://semantic-ui.com)
+* [useraccounts:unstyled](https://atmospherejs.com/useraccounts/unstyled) with plain html and no CSS rules
+* plus others coming soon...
+
+
+<a name="boilerplates"/>
+### Boilerplates
+
+For a very very quick start you can find some boilerplate examples inside [this repository](https://github.com/meteor-useraccounts/boilerplates).
+
+We'll try to make them richer and richer, while still keeping them as general as possible.
+
+<a name="setup"/>
+### Setup
+
+Just choose one of the packages among the [available styled versions](#available-versions) and install it:
+
+```Shell
+meteor add useraccounts:bootstrap
+meteor add <your preferred bootstrap package>
+```
+
+**Note 1:** no additional packages nor CSS/LESS/SASS files providing styles are included by useraccounts packages. This is to let you choose your preferred way to include them!
+
+**Note 2:** You don't have to add `useraccounts:core` to your app! It is automatically added when you add `useraccounts:<something>`...
+
+Then add at least one login service:
+
+```Shell
+meteor add accounts-password
+meteor add accounts-facebook
+meteor add accounts-google
+...
+```
+
+**Note**: 3rd party services need to be configured... more about this [here](http://docs.meteor.com/#meteor_loginwithexternalservice)
+
+And that's it!
+
+...but don't expect to see much without doing something more ;-)
+This is to let you configure your app exactly the way you wish, without imposing anything beforehand!
+
+
+<a name="routing"/>
+### Routing
+
+If you'd like to easily configure specific routes to deal with accounts management, you might be interested to check out
+[useraccounts:iron-routing](https://github.com/meteor-useraccounts/iron-routing) and [useraccounts:flow-routing](https://github.com/meteor-useraccounts/flow-routing) packages.
+They provide very easy routes set-up via the `AccountsTemplates.configureRoute` method.
+
+
+<a name="templates"/>
+### Templates
+
+There is **only one template** which is used to reactively draw appropriate sign in, sign up, forgot password, reset password, change password, and enroll account forms!
+
+It is `atForm` and can be used anywhere you wish like this:
+
+```html
+{{> atForm}}
+```
+
+Its design is as *transparent* as possible, making it play nicely with themes and your CSS customizations! Also, it is not wrapped inside any *container* so that you can put it anywhere, including complex multi-column layouts.
+
+In case you wish to *lock* the template to a particular state, you can specify that via the `state` parameter:
+
+```html
+{{> atForm state='signUp'}}
+```
+
+This will prevent the template from changing its content. See [internal states](#internal-states) for more details...
+
+
+Well, actually there is many, used inside `atForm`...
+
+...plus one another: `atNavButton` which can be used inside navbars to get a basic sign-in sign-out button which changes text and behaviour based on the user status (to get it working you should set up at least a `signIn` route).
+
+
+<a name="basic-customization"/>
+## Basic Customization
+
+
+<a name="i18n"/>
+### I18n Support
+
+i18n is achieved using [accounts-t9n](https://atmospherejs.com/softwarerero/accounts-t9n). The only thing you have to do is ensure
+
+```javascript
+T9n.setLanguage('<lang>');
+```
+
+is called somewhere, whenever you want to switch language.
+
+
+<a name="configuration-api"/>
+### Configuration API
+
+There are basically two different ways to interact with AccountsTemplates for basic configuration:
+
+* AccountsTemplates.configureRoute(route_code, options);
+* AccountsTemplates.configure(options);
+
+**These functions should be called in top-level code, not inside `Meteor.startup()`.**
+
+There is no specific order for the above calls to be effective, and you can call them more than once, possibly in different files.
+
+**The only other requirement is to make exactly the same calls on both the server and the client.** The best thing is to put everything inside a file shared between both. I suggest you use something like `lib/config/at_config.js`
+
+
+<a name="options"/>
+#### Options
+
+By calling `AccountsTemplates.configure(options)` you can specify a bunch of choices regarding both visual appearance and behavior.
+
+The following is an almost complete example of options configuration (with fields in alphabetical order):
+
+```javascript
+AccountsTemplates.configure({
+ // Behavior
+ confirmPassword: true,
+ enablePasswordChange: true,
+ forbidClientAccountCreation: false,
+ overrideLoginErrors: true,
+ sendVerificationEmail: false,
+ lowercaseUsername: false,
+ focusFirstInput: true,
+
+ // Appearance
+ showAddRemoveServices: false,
+ showForgotPasswordLink: false,
+ showLabels: true,
+ showPlaceholders: true,
+ showResendVerificationEmailLink: false,
+
+ // Client-side Validation
+ continuousValidation: false,
+ negativeFeedback: false,
+ negativeValidation: true,
+ positiveValidation: true,
+ positiveFeedback: true,
+ showValidating: true,
+
+ // Privacy Policy and Terms of Use
+ privacyUrl: 'privacy',
+ termsUrl: 'terms-of-use',
+
+ // Redirects
+ homeRoutePath: '/home',
+ redirectTimeout: 4000,
+
+ // Hooks
+ onLogoutHook: myLogoutFunc,
+ onSubmitHook: mySubmitFunc,
+ preSignUpHook: myPreSubmitFunc,
+ postSignUpHook: myPostSubmitFunc,
+
+ // Texts
+ texts: {
+ button: {
+ signUp: "Register Now!"
+ },
+ socialSignUp: "Register",
+ socialIcons: {
+ "meteor-developer": "fa fa-rocket"
+ },
+ title: {
+ forgotPwd: "Recover Your Password"
+ },
+ },
+});
+```
+
+Details for each of them follow.
+
+| Option | Type | Default | Description |
+| --------------------------- | -------- | --------- | ----------- |
+| **Behavior** | | | |
+| confirmPassword | Boolean | true | Specifies whether to ask the password twice for confirmation. This has no effect on the sign in form. |
+| defaultState | String | "signIn" | Specifies the state to be used initially when atForm is rendered. This is not considered when rendering atForm on configured routes. |
+| enablePasswordChange | Boolean | false | Specifies whether to allow to show the form for password change. Note: In case the `changePwd` route is not configured, this is to be done *manually* inside some custom template. |
+| enforceEmailVerification | Boolean | false | When set to true together with sendVerificationEmail, forbids user login unless the email address is verified. **Warning: experimental! Use it only if you have accounts-password as the only service!!!** |
+| focusFirstInput | Boolean | !Meteor.isCordova | When set to true, asks to autofocus the first input of atForm when the template is rendered. Note: have a look at [this issue](https://github.com/meteor-useraccounts/core/issues/594) in case you're getting problems with cordova apps. |
+| forbidClientAccountCreation | Boolean | false | Specifies whether to forbid user registration from the client side. In case it is set to true, neither the link for user registration nor the sign up form will be shown. |
+| overrideLoginErrors | Boolean | true | Asks to show a general `Login Forbidden` on a login failure, without specifying whether it was for a wrong email or for a wrong password. |
+| sendVerificationEmail | Boolean | false | Specifies whether to send the verification email after successful registration. |
+| redirectTimeout | Number | 2000 | Specifies a timeout time for the redirect after successful form submit on `enrollAccount`, `forgotPwd`, `resetPwd`, and `verifyEmail` routes. |
+| socialLoginStyle | String | "popup" | Specifies the login style for 3rd party services login. Valid values are `popup` or `redirect`. See `loginStyle` option of [Meteor.loginWith<ExternalService>](http://docs.meteor.com/#/full/meteor_loginwithexternalservice) for more information. |
+| lowercaseUsername | Boolean | false | Possibly asks to transform `username` field for user objects at registration time to be always in lowercase with no spaces. The original `username` value will be added to the `user.profile` field for later use. |
+| **Appearance** | | | |
+| hideSignInLink | Boolean | false | When set to true, asks to never show the link to the sign in page |
+| hideSignUpLink | Boolean | false | When set to true, asks to never show the link to the sign up page |
+| showAddRemoveServices | Boolean | false | Tells whether to show social account buttons also when the user is signed in. In case it is set to true, the text of buttons will change from 'Sign in With XXX' to 'Add XXX' or 'Remove XXX' when the user signs in. 'Add' will be used if that particular service is still not associated with the current account, while 'Remove' is used only in case a particular service is already used by the user **and** there are at least two services available for sign in operations. Clicks on 'Add XXX' trigger the call to `Meteor.loginWithXXX`, as usual, while click on 'Remove XXX' will call the method `ATRemoveService` provided by AccountsTemplates. This means you need to have some additional logic to deal with the call `Meteor.loginWithXXX` in order to actually add the service to the user account. One solution to this is to use the package [accounts-meld](https://atmospherejs.com/package/accounts-meld) which was build exactly for this purpose. |
+| showForgotPasswordLink | Boolean | false | Specifies whether to display a link to the forgot password page/form |
+| showLabels | Boolean | true | Specifies whether to display text labels above input elements. |
+| showPlaceholders | Boolean | true | Specifies whether to display place-holder text inside input elements. |
+| showResendVerificationEmailLink | Boolean | false | Specifies whether to display a link to the resend verification email page/form |
+| **Texts** | | | |
+| texts | Object | | Permits to specify texts to be shown on the atForm for each of its states (see [below](#configuring-texts)). |
+| **Client-side Validation** | | | |
+| continuousValidation | Boolean | false | Specifies whether to continuously validate fields' value while the user is typing. *It is performed client-side only to save round trips with the server*. |
+| negativeValidation | Boolean | false | Specifies whether to highlight input elements in case of negative validation. |
+| positiveValidation | Boolean | false | Specifies whether to highlight input elements in case of positive validation. |
+| negativeFeedback | Boolean | false | Specifies whether to display negative validation feed-back inside input elements. |
+| positiveFeedback | Boolean | false | Specifies whether to display positive validation feed-back inside input elements. |
+| showValidating | Boolean | false | Specifies whether to display a loading icon inside input elements while the validation process is in progress. |
+| **Links** | | | |
+| homeRoutePath | String | '/' | Path for the home route, to be possibly used for redirects after successful form submission. |
+| privacyUrl | String | undefined | Path for the route displaying the privacy document. In case it is specified, a link to the page will be displayed at the bottom of the form (when appropriate). |
+| termsUrl | String | undefined | Path for the route displaying the document about terms of use. In case it is specified, a link to the page will be displayed at the bottom of the form (when appropriate). |
+| **Hooks** | | | |
+| onLogoutHook | Function | | Called on `AccountsTemplates.logout` invocation: allows for custom redirects or whatever custom action to be taken on user logout. |
+| onSubmitHook | Function | | `func(error, state)` Called when the `pwdForm` is being submitted: allows for custom actions to be taken on form submission. `error` contains possible errors occurred during the submission process, `state` specifies the `atForm` internal state from which the submission was triggered. A nice use case might be closing the modal or side-menu showing `atForm` |
+| preSignUpHook | Function | | `func(password, info)` Called just before submitting the `pwdForm` for sign-up: allows for custom actions on the data being submitted. A nice use could be extending the user profile object accessing `info.profile`. to be taken on form submission. The plain text `password` is also provided for any reasonable use. |
+| postSignUpHook | Function | | `func(userId, info)` Called, **server side only**, just after a successfull user account creation, post submitting the `pwdForm` for sign-up: allows for custom actions on the data being submitted ___after___ we are sure a new user was ___successfully___ created. A common use might be applying roles to the user, as this is only possible after fully completing user creation in alanning:roles. The `userId` is available as the first parameter, so that user user object may be retrieved. The `password` is not available as it's already encrypted, though the encrypted password may be found in `info` if of use. |
+
+##### onSubmitHook
+
+A straightforward configuration about how to detect when a user logs in or registers might look like the following:
+
+```javascript
+var mySubmitFunc = function(error, state){
+ if (!error) {
+ if (state === "signIn") {
+ // Successfully logged in
+ // ...
+ }
+ if (state === "signUp") {
+ // Successfully registered
+ // ...
+ }
+ }
+};
+
+AccountsTemplates.configure({
+ onSubmitHook: mySubmitFunc
+});
+```
+
+<a name="logout"/>
+##### AccountsTemplates.logout()
+
+Should be used in place of `Meteor.logout()`. This function invokes the `onLogoutHook` specified in the optional configuration.
+Also note that `AccountsTemplates.logout()` is invoked when logging out using the `atNavButton`.
+
+
+```javascript
+//Use in place of Meteor.logout() in your client code. Also called automatically by atNavButton when clicking Sign Off
+AccountsTemplates.logout();
+
+```
+
+
+```javascript
+var myPostLogout = function(){
+ //example redirect after logout
+ Router.go('/home');
+};
+
+AccountsTemplates.configure({
+ onLogoutHook: myPostLogout
+});
+```
+
+<a name="internal-states"/>
+### Internal States
+
+The `atForm` template changes reactively based on the current internal state of AccountsTemplates.
+The current internal state can be queried with `AccountsTemplates.getState()` and set with `AccountsTemplates.setState(new_state)`
+
+
+Currently available states are:
+
+| Internal State | What's shown |
+| ----------------------- | ------------------------------------------------------------------------------------- |
+| changePwd | Change password form asking to set a new password |
+| enrollAccount | Account Enrollment form asking to set a password |
+| forgotPwd | Forgot Password form asking for the email address where to send a reset password link |
+| hide | None at all... |
+| resendVerificationEmail | Login form with an additional button to get another verification email |
+| resetPwd | Reset Password form asking to set a password |
+| signIn | Login form |
+| signUp | Registration form |
+| verifyEmail | Only the result about email verification |
+
+
+
+<a name="content-protection"/>
+### Content Protection
+
+
+If you want to secure a specific template, you could add that template like this:
+
+```handlebars
+{{> ensureSignedIn template="myTemplate"}}
+```
+and that will render the default `fullPageAtForm` template from your chosen User Accounts templates package (bootstrap, materialize, etc). Once signed in, it'll render `myTemplate` instead of the accounts form.
+
+If you want to declare a custom sign in template instead of `fullPageAtForm`, you would do this:
+
+```handlebars
+{{> ensureSignedIn template="myTemplate" auth="myLoginForm"}}
+```
+That custom auth template just needs to include `{{> atForm}}` somewhere in it. The only reason you'd use this optional feature is if you wanted to modify the layout around the `atForm` template (like
+`fullPageAtForm` does).
+
+
+In case you're using one of the routing packages [useraccounts:iron-routing](https://github.com/meteor-useraccounts/iron-routing)
+or [useraccounts:flow-routing](https://github.com/meteor-useraccounts/flow-routing) refer to their documentation for more possibilities.
+
+
+<a name="reCaptcha-setup"/>
+### reCaptcha Setup
+To set up [reCaptcha](https://www.google.com/recaptcha/intro/index.html), you need to first obtain API keys.
+
+Then, a recommended setup is as follows.
+
+A [Meteor settings file](http://docs.meteor.com/#/full/meteor_settings) with the keys:
+
+```javascript
+{
+ "public": {
+ "reCaptcha": {
+ "siteKey": YOUR SITE KEY
+ }
+ },
+ "reCaptcha": {
+ "secretKey": YOUR SECRET KEY
+ }
+}
+```
+
+and configuration to show the reCaptcha widget:
+
+```javascript
+AccountsTemplates.configure({
+ showReCaptcha: true
+});
+```
+
+The reCaptcha plugin can likewise be set up with the following complete example:
+
+
+```javascript
+AccountsTemplates.configure({
+ reCaptcha: {
+ siteKey: YOUR SITE KEY,
+ theme: "light",
+ data_type: "image"
+ },
+ showReCaptcha: true
+});
+```
+
+And, in a separate file in the `/server` folder:
+
+```javascript
+AccountsTemplates.configure({
+ reCaptcha: {
+ secretKey: YOUR SECRET KEY.
+ },
+});
+```
+
+Each option is described below:
+
+| Option | Type | Default | Description |
+| --------------------------- | -------- | --------- | ----------- |
+| siteKey | String | none | The site key needed to create the reCaptcha widget. This can be specified in just the Meteor settings file. |
+| secretKey | String | none | The secret key needed to verify the reCaptcha response. ***Warning: Only set this in a file in `/server` or in a Meteor settings file. Otherwise, your private key can be read by anyone!*** |
+| theme | String | "light" | Sets the reCaptcha theme color. The options are "light" and "dark". |
+| data_type | String | "image" | Sets the verification method. Options are "image" or "audio". |
+| showReCaptcha | Boolean | false | Whether to show the reCaptcha widget on sign in or not. No reCaptcha validation will occur if set to false. |
+
+<a name="detect-reactively-when-a-form-is-being-processed"/>
+### Detect reactively when a form is being processed
+
+`AccountsTemplates.disabled()` returns `true` when a submitted form is being processed and `false` once the submission process has been completed (successfully or not). `AccountsTemplate.disabled()` is reactive and can be used to trigger UI events, such as spinners, "Please wait" messages or to disable input elements, while the form is being processed. The function works irrespectively of form status (signIn, signUp, pwdReset etc.). A typical use-case would be in a template helper:
+
+```html
+<template name="myLogin">
+ {{#if atDisabled}}
+ Please wait...
+ {{/if}}
+ <div class="{{atClass}}">
+ {{> atForm}}
+ </div>
+</template>
+```
+
+```js
+Template.myLogin.helpers({
+ atDisabled: function() {
+ return AccountsTemplates.disabled();
+ },
+ atClass: function() {
+ return AccountsTemplates.disabled() ? 'disabled' : 'active';
+ }
+});
+```
+
+<a name="advanced-customization"/>
+## Advanced Customization
+
+
+<a name="configuring-texts"/>
+### Configuring Texts
+
+In case you wish to change texts on atForm, you can call:
+
+```javascript
+AccountsTemplates.configure({
+ texts: {
+ navSignIn: "signIn",
+ navSignOut: "signOut",
+ optionalField: "optional",
+ pwdLink_pre: "",
+ pwdLink_link: "forgotPassword",
+ pwdLink_suff: "",
+ resendVerificationEmailLink_pre: "Verification email lost?",
+ resendVerificationEmailLink_link: "Send again",
+ resendVerificationEmailLink_suff: "",
+ sep: "OR",
+ signInLink_pre: "ifYouAlreadyHaveAnAccount",
+ signInLink_link: "signin",
+ signInLink_suff: "",
+ signUpLink_pre: "dontHaveAnAccount",
+ signUpLink_link: "signUp",
+ signUpLink_suff: "",
+ socialAdd: "add",
+ socialConfigure: "configure",
+ socialIcons: {
+ "meteor-developer": "fa fa-rocket",
+ },
+ socialRemove: "remove",
+ socialSignIn: "signIn",
+ socialSignUp: "signUp",
+ socialWith: "with",
+ termsPreamble: "clickAgree",
+ termsPrivacy: "privacyPolicy",
+ termsAnd: "and",
+ termsTerms: "terms",
+ }
+});
+```
+
+the above example asks to change some of the available text configurations. You can specify only a subsets of them leaving default values unchanged.
+To learn how to change title, button, social buttons' icon, info, and errors text read below.
+
+
+<a name="form-title"/>
+#### Form Title
+
+In case you wish to change form titles, you can call:
+
+```javascript
+AccountsTemplates.configure({
+ texts: {
+ title: {
+ changePwd: "Password Title",
+ enrollAccount: "Enroll Title",
+ forgotPwd: "Forgot Pwd Title",
+ resetPwd: "Reset Pwd Title",
+ signIn: "Sign In Title",
+ signUp: "Sign Up Title",
+ verifyEmail: "Verify Email Title",
+ }
+ }
+});
+```
+
+the above example asks to change the title for all possible form states, but you can specify only a subset of them leaving default values unchanged.
+
+You can also *hide* a title by setting it to an empty string. For example with:
+
+```
+AccountsTemplates.configure({
+ texts: {
+ title: {
+ signIn: "",
+ }
+ }
+});
+```
+
+no title will be shown on the sign in form.
+
+
+<a name="button-text"/>
+#### Button Text
+
+In case you wish to change the text appearing inside the submission button, you can call:
+
+```javascript
+AccountsTemplates.configure({
+ texts: {
+ button: {
+ changePwd: "Password Text",
+ enrollAccount: "Enroll Text",
+ forgotPwd: "Forgot Pwd Text",
+ resetPwd: "Reset Pwd Text",
+ signIn: "Sign In Text",
+ signUp: "Sign Up Text",
+ }
+ }
+});
+```
+
+the above example asks to change the button text for all possible form states, but you can specify only a subset of them leaving default values unchanged.
+
+<a name="social-button-icons"/>
+#### Social Button Icons
+
+In case you wish to change the icon appearing on the left of social login buttons, you can call:
+
+```javascript
+AccountsTemplates.configure({
+ texts: {
+ socialIcons: {
+ google: "myGoogleIcon",
+ "meteor-developer": "myMeteorIcon",
+ }
+ }
+});
+```
+
+to specify a different icon classes to be used for services. By default the icon class is set to `fa fa-*service*`,
+but for the "meteor-developer" service for which `fa fa-rocket` is used. An exception is made for `useaccounts:semantic-ui`
+which sets them simply to `*service*`, which is the correct way to go.
+
+<a name="info-text"/>
+#### Info Text
+
+In case you wish to change the info text appearing inside the results box, you can call:
+
+```javascript
+AccountsTemplates.configure({
+ texts: {
+ info: {
+ emailSent: "info.emailSent",
+ emailVerified: "info.emailVerified",
+ pwdChanged: "info.passwordChanged",
+ pwdReset: "info.passwordReset",
+ pwdSet: "info.passwordReset",
+ signUpVerifyEmail: "Successful Registration! Please check your email and follow the instructions.",
+ verificationEmailSent: "A new email has been sent to you. If the email doesn't show up in your inbox, be sure to check your spam folder.",
+ }
+ }
+});
+```
+
+The above calls simply set all values as the current default ones.
+
+<a name="input-icons"/>
+#### Input Field Icons
+
+In case you wish to change the icon appearing on the right side of input fields to show their validation status, you can call:
+
+```javascript
+AccountsTemplates.configure({
+ texts: {
+ inputIcons: {
+ isValidating: "fa fa-spinner fa-spin",
+ hasSuccess: "fa fa-check",
+ hasError: "fa fa-times",
+ }
+ }
+});
+```
+
+<a name="errors-text"/>
+#### Errors Text
+
+In case you wish to change the text for errors appearing inside the error box, you can call:
+
+```javascript
+AccountsTemplates.configure({
+ texts: {
+ errors: {
+ accountsCreationDisabled: "Client side accounts creation is disabled!!!",
+ cannotRemoveService: "Cannot remove the only active service!",
+ captchaVerification: "Captcha verification failed!",
+ loginForbidden: "error.accounts.Login forbidden",
+ mustBeLoggedIn: "error.accounts.Must be logged in",
+ pwdMismatch: "error.pwdsDontMatch",
+ validationErrors: "Validation Errors",
+ verifyEmailFirst: "Please verify your email first. Check the email and follow the link!",
+ }
+ }
+});
+```
+
+The above calls simply set all values as the current default ones.
+*Note:* The above list of errors refers to those set directly by AccountsTemplates only!
+Errors which comes from the Accounts packages cannot be overwritten (at least not easily...)
+Please have a look at [Form Fields Configuration](#form-fields-configuration) to learn how to set validation errors on a field basis.
+
+
+<a name="disabling-client-side-accounts-creation"/>
+### Disabling Client-side Accounts Creation
+
+AccountsTemplates disables by default accounts creation on the client. This is done to use a dedicated method called `ATCreateUserServer` **(sending the password on the wire already hashed as usual...)** to create the new users server-side.
+This way a bulletproof profile fields full validation can be performed.
+But there is one more parameter to set in case you'd like to forbid client-side accounts creation, which is the following:
+
+* `forbidClientAccountCreation` - (Boolean, default false) Specifies whether to forbid accounts creation from the client.
+
+it is exactly the same provided by the Accounts object, so this means you need to do:
+
+```javascript
+AccountsTemplates.configure({
+ forbidClientAccountCreation: true
+});
+```
+
+instead of the usual:
+
+```javascript
+Accounts.config({
+ forbidClientAccountCreation : true
+});
+```
+
+
+<a name="form-fields-configuration"/>
+### Form Fields Configuration
+
+Every input field appearing inside AccountsTemplates forms can be easily customized both for appearance and validation behaviour. Additional (custom) fields can be added to the sign up and registration forms, and the properties of built-in fields, like `email` and `password` can be overridden (see [Remove fields](https://github.com/meteor-useraccounts/core/blob/master/Guide.md#remove-fields))
+
+Each field object is represented by the following properties:
+
+| Property | Type | Required | Description |
+| -------------------- | -----------------|:--------:| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| _id | String | X | A unique field's id/name (internal use only) to be also used as attribute name into `Meteor.user().profile` in case it identifies an additional sign up field. Usually all lowercase letters. |
+| type | String | X | Specifies the input element type. At the moment supported inputs are: `password`, `email`, `text`, `tel`, `url`, `checkbox`, `select`, `radio`, `hidden`. |
+| required | Boolean | | When set to true the corresponding field cannot be left blank |
+| displayName | String or Object | | The field name to be shown as text label above the input element. In case nothing is specified, the capitalized `_id` is used. The text label is shown only if `showLabels` options is set to true. |
+| placeholder | String or Object | | The placeholder text to be shown inside the input element. In case nothing is specified, the capitalized `_id` will be used. The place-holder is shown only if `showPlaceholders` option is set to true. |
+| select | [Object] | | Lets you specify an array of choices to be displayed for select and radio inputs. See example below. |
+| minLength | Integer | | If specified, requires the content of the field to be at least `minLength` characters. |
+| maxLength | Integer | | If specified, require the content of the field to be at most `maxLength` characters. |
+| re | RegExp | | Possibly specifies the regular expression to be used for the field's content validation. Validation is performed both client-side (at every input change if `continuousValidation` option is set to true) and server-side on form submit. |
+| func | Function | | Custom function to be used for validation. |
+| errStr | String | | Error message to be displayed in case re or func validation fail. |
+| trim | Boolean | | Trim the input value. |
+| lowercase | Boolean | | Convert the input value to lowercase. |
+| uppercase | Boolean | | Convert the input value to uppercase. |
+| transform | Function | | Custom function to transform the input value. |
+| continuousValidation | Boolean | | Continuously validate fields' value while the user is typing. *It is performed client-side only to save round trips with the server*. |
+| negativeValidation | Boolean | | Highlight input elements in case of negative validation. |
+| positiveValidation | Boolean | | Highlight input elements in case of positive validation. |
+| negativeFeedback | Boolean | | Display negative validation feedback inside input elements. |
+| positiveFeedback | Boolean | | Display positive validation feedback inside input elements. |
+| showValidating | Boolean | | Display a loading icon inside input elements while the validation process is in progress. |
+| options | Object | | Allows to pass in additional custom options to be possibly used to extend input templates (see [Extending Templates](https://github.com/meteor-useraccounts/core/blob/master/Guide.md#extending-templates)) |
+| template | String | | The name of a custom template to be used in place of the default one. |
+
+
+`displayName`, `placeholder`, and `errStr` can also be an [accounts-t9n](https://atmospherejs.com/softwarerero/accounts-t9n) registered key, in which case it will be translated based on the currently selected language.
+In case you'd like to specify a key which is not already provided by accounts-t9n you can always map your own keys. To learn how to register new labels, please refer to the official [documentation](https://github.com/softwarerero/meteor-accounts-t9n#define-translations).
+
+`continuousValidation`, `negativeFeedback`, `negativeValidation`, `positiveValidation`, `positiveFeedback`, `showValidating` can be used to override global settings (see [Form Fields Configuration](#form-fields-configuration)) on a per field basis.
+
+Furthermore, you can pass an object for `displayName`, `placeholder` to specify different texts for different form states. The matched pattern is:
+
+```javascript
+{
+ default: Match.Optional(String),
+ changePwd: Match.Optional(String),
+ enrollAccount: Match.Optional(String),
+ forgotPwd: Match.Optional(String),
+ resetPwd: Match.Optional(String),
+ signIn: Match.Optional(String),
+ signUp: Match.Optional(String),
+}
+```
+
+which permits to specify a different text for each different state, or a default value to be used for states which are not explicitly provided. For example:
+
+```javascript
+AccountsTemplates.addField({
+ _id: 'password',
+ type: 'password',
+ placeholder: {
+ signUp: "At least six characters"
+ },
+ required: true,
+ minLength: 6,
+ re: /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,}/,
+ errStr: 'At least 1 digit, 1 lowercase and 1 uppercase',
+});
+```
+
+asks AccountsTemplates to display "At least six characters" as the placeholder for the password field when the sign up form is display, and to display "Password" (the capitalized *_id*_) in any other case.
+
+##### Custom validation
+Custom validation can be achieved by providing a regular expression or a function. In case you go for the function solution, this:
+
+```javascript
+AccountsTemplates.addField({
+ _id: 'name',
+ type: 'text',
+ displayName: "Name",
+ func: function(value){return value !== 'Full Name';},
+ errStr: 'Only "Full Name" allowed!',
+});
+```
+
+will require the name input to be exactly "Full Name" (though this might not be that interesting...).
+If instead you do something along the following line:
+
+```javascript
+AccountsTemplates.addField({
+ _id: 'phone',
+ type: 'tel',
+ displayName: "Phone",
+ required: true,
+ func: function (number) {
+ if (Meteor.isServer){
+ if (isValidPhone(number))
+ return false; // meaning no error!
+ return true; // Validation error!
+ }
+ },
+ errStr: 'Invalid Phone number!',
+});
+```
+
+supposing `isValidPhone` is available only server-side, you will be validating the field only server-side, on form submission.
+
+If, differently, you do something like this:
+
+```javascript
+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) {
+ console.log("Validating username...");
+ var self = this;
+ Meteor.call("userExists", value, function(err, userExists){
+ if (!userExists)
+ self.setSuccess();
+ else
+ self.setError(userExists);
+ self.setValidating(false);
+ });
+ return;
+ }
+ // Server
+ return Meteor.call("userExists", value);
+ },
+});
+```
+
+you can achieve also client-side and server-side validation calling a server method
+During the waiting time a loading icon will be displayed (if you configure `showValidating` to be true).
+To configure the loading icon see [Input Field Icons](#input-icons).
+
+*Note:* `field.setError(err)`, `field.setSuccess()`, and `field.setValidating()` are methods used to deal with inputs' validation states. A `null` value means non-validated, `false` means correctly validated, no error, and any other value evaluated as true (usually strings specifying the reason for the validation error), are finally interpreted as error and displayed where more appropriate.
+
+#### Checkboxes, Selects, Radios, and Hidden
+
+This is an example about how to add Checkboxes, Selects, and Radios to the sign up fields:
+
+```javascript
+AccountsTemplates.addField({
+ _id: "gender",
+ type: "select",
+ displayName: "Gender",
+ select: [
+ {
+ text: "Male",
+ value: "male",
+ },
+ {
+ text: "Female",
+ value: "female",
+ },
+ ],
+});
+
+AccountsTemplates.addField({
+ _id: "fruit",
+ type: "radio",
+ displayName: "Preferred Fruit",
+ select: [
+ {
+ text: "Apple",
+ value: "aa",
+ }, {
+ text: "Banana",
+ value: "bb",
+ }, {
+ text: "Carrot",
+ value: "cc",
+ },
+ ],
+});
+
+AccountsTemplates.addField({
+ _id: "mailing_list",
+ type: "checkbox",
+ displayName: "Subscribe me to mailing List",
+});
+
+AccountsTemplates.addField({
+ _id: 'reg_code',
+ type: 'hidden'
+});
+```
+
+please note the `select` list which lets you specify the values for the choice.
+The `value` value of corresponding selected `text` will be picked up and added into the `profile` field of the user object.
+
+Hidden inputs might be of help in case you want to consider to link to your registration page from around the web (emails, ads, discount campaigns, etc...) with links like this:
+
+```
+http://my.splendido.site/sign-up?email=giorgio@example.com&reg_code=123
+```
+
+exploiting the ability of AccountsTemplates to pick-up query parameters having the same key as field ids, this would permit to get `reg_code: "123"` under the `profile` field of the user object.
+**Please use this with caution!** ..never ever do something like:
+```
+http://my.splendido.site/sign-up?role=admin
+```
+and then set the role of the new user based on the hidden `role` field. I guess you can appreciate the security hole there ;-)
+
+#### Special Field's Ids
+
+There are a number of special ids used for basic input fields. These are:
+
+* current_password
+* email
+* password
+* password_again
+* username
+* username_and_email
+
+Any other id will be interpreted as an additional sign up field.
+In case a special field is not explicitly added, it will be automatically inserted at initialization time (with appropriate default properties). To customize special fields see [Remove fields](https://github.com/meteor-useraccounts/core/blob/master/Guide.md#remove-fields)
+
+#### Add a field
+
+You can use `AccountsTemplates.addField(options)` to configure an input field. This apply for both special fields and custom ones.
+For example you can do:
+
+```javascript
+AccountsTemplates.addField({
+ _id: 'phone',
+ type: 'tel',
+ displayName: "Landline Number",
+});
+```
+
+The above snippet asks `AccountsTemplates` to draw an additional input element within the sign-up form.
+
+#### Add many fields at once
+
+Another possibility is to add many additional fields at once using `addFields`:
+
+```javascript
+AccountsTemplates.addFields([
+ {
+ _id: 'phone',
+ type: 'tel',
+ displayName: "Landline Number",
+ },
+ {
+ _id: 'fax',
+ type: 'tel',
+ displayName: "Fax Number",
+ }
+]);
+```
+
+#### Remove fields
+
+There is also a `removeField` method which can be used to remove predefined required fields and adding them again specify different options.
+
+```javascript
+AccountsTemplates.removeField('password');
+AccountsTemplates.addField({
+ _id: 'password',
+ type: 'password',
+ required: true,
+ minLength: 6,
+ re: /(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{6,}/,
+ errStr: 'At least 1 digit, 1 lower-case and 1 upper-case',
+});
+```
+
+#### Login with Username or Email
+
+In order to let the user register with both a `username` and an `email` address and let him the possibility to log in using one of them, both the `username` and `email` fields must be added.
+This is an example about how to configure such a behaviour:
+
+```javascript
+var pwd = AccountsTemplates.removeField('password');
+AccountsTemplates.removeField('email');
+AccountsTemplates.addFields([
+ {
+ _id: "username",
+ type: "text",
+ displayName: "username",
+ required: true,
+ minLength: 5,
+ },
+ {
+ _id: 'email',
+ type: 'email',
+ required: true,
+ displayName: "email",
+ re: /.+@(.+){2,}\.(.+){2,}/,
+ errStr: 'Invalid email',
+ },
+ pwd
+]);
+```
+
+This will trigger the automatic insertion of the special field `username_and_email` to be used for the sign in form.
+If you wish to further customize the `username_and_email` field you can add it together with the other two:
+
+```javascript
+var pwd = AccountsTemplates.removeField('password');
+AccountsTemplates.removeField('email');
+AccountsTemplates.addFields([
+ {
+ _id: "username",
+ type: "text",
+ displayName: "username",
+ required: true,
+ minLength: 5,
+ },
+ {
+ _id: 'email',
+ type: 'email',
+ required: true,
+ displayName: "email",
+ re: /.+@(.+){2,}\.(.+){2,}/,
+ errStr: 'Invalid email',
+ },
+ {
+ _id: 'username_and_email',
+ type: 'text',
+ required: true,
+ displayName: "Login",
+ },
+ pwd
+]);
+```
+
+
+<a name="extending-templates"/>
+### Extending Templates
+
+With the [aldeed:template-extension](https://github.com/aldeed/meteor-template-extension) package, the built-in templates or sub-templates of any `user-accounts` UI package may be replaced by custom templates. The purpose is to create more sophisticated or specialized layouts or styling.
+
+In case of input fields the option `template` (see [Form Fields Configuration](#form-fields-configuration)) can be directly used without the need to rely on `aldeed:template-extension` package.
+
+Here is a simple example of a template you can use for a field of type 'select':
+
+```html
+<template name="customSelectTemplate">
+ <select id="at-field-{{_id}}" name="at-field-{{_id}}" data-something="{{options.someOption}}">
+ {{#each select}}
+ <option value="{{value}}">{{text}}</option>
+ {{/each}}
+ </select>
+</template>
+```
+
+Custom properties that hold information about the look of the form may be attached to the `options` object of a field. It may then be used to change the output while looping the fields. Adding a divider might look like this:
+
+```javascript
+AccountsTemplates.addField({
+ _id: "address",
+ type: "text",
+
+ // Options object with custom properties for my layout. At the moment, there are
+ // no special properties; it is up the developer to invent them
+ options: {
+ // Put a divider before this field
+ dividerBefore: true
+ }
+});
+```
+
+```html
+<template name="appAtInput">
+ {{#if options.dividerBefore}}<hr>{{/if}}
+
+ {{> Template.dynamic template=templateName}}
+</template>
+```
+
+```javascript
+Template.appAtInput.replaces("atInput");
+```
+
+
+<a name="grouping-fields"/>
+#### Grouping fields
+
+Grouping fields together is a special problem in regard to layout. The issue is creating some container markup *while* iterating over the fields (the templating engine of Meteor doesn't allow outputting an opening tag inside a loop without closing it in the same iteration).
+
+A solution to the problem is demonstrated in [this gist](https://gist.github.com/dalgard/a844f6569d8f471db9a7) (Semantic UI version).
+
+
+<a name="css-rules"/>
+### CSS Rules
+
+The main atForm is build up of several pieces, appearing and disappearing based on configuration options as well as the current internal status.
+Each of these blocks is wrapped inside a `div` with class `at-<something>`: this should made your life easier if you're trying to write your own CSS rules to change templates' appearance.
+
+Social login buttons (`button.at-social-btn`) have an `id` in the form `at-<servicename>` and name `<servicename>`.
+
+Input fields for the password service form are wrapped inside a div with class `at-input`. The same div gets classes `has-error`, `has-success`, and `has-feedback` in case of negative validation result, positive validation and validation with feedback respectively.
+The input element itself has id and name in the form `at-field-<field_id>`.
+**Note:** `has-error`, `has-success`, and `has-feedback` names might change from framework to framework. These are valid for the *unstyled* and *bootstrap* versions...
+
+
+Below is a html snapshot of an over-complete `atForm` taken from the unstyled version in which you can find all elements possibly shown under different configurations and circumstances.
+
+```html
+<div class="at-form">
+ <!-- Title -->
+ <div class="at-title">
+ <h3>Create an Account</h3>
+ </div>
+ <!-- Social Buttons for Oauth Sign In / Sign Up-->
+ <div class="at-oauth">
+ <button class="at-social-btn" id="at-facebook" name="facebook">
+ <i class="fa fa-facebook"></i> Sign in with Facebook
+ </button>
+ <button class="at-social-btn" id="at-twitter" name="twitter">
+ <i class="fa fa-twitter"></i> Sign in with Twitter
+ </button>
+ </div>
+ <!-- Services Separator -->
+ <div class="at-sep">
+ <strong>OR</strong>
+ </div>
+ <!-- Global Error -->
+ <div class="at-error">
+ <p>Login forbidden</p>
+ </div>
+ <!-- Global Resutl -->
+ <div class="at-result">
+ <p>Email Sent!</p>
+ </div>
+ <!-- Password Service -->
+ <div class="at-pwd-form">
+ <form role="form" id="at-pwd-form" novalidate="">
+ <!-- Input -->
+ <div class="at-input">
+ <label for="at-field-username">
+ Username
+ </label>
+ <input type="text" id="at-field-username" name="at-field-username" placeholder="Username" autocapitalize="none" autocorrect="off">
+ </div>
+ <!-- Input with Validation Error -->
+ <div class="at-input has-error">
+ <label for="at-field-email">
+ Email
+ </label>
+ <input type="email" id="at-field-email" name="at-field-email" placeholder="Email" autocapitalize="none" autocorrect="off">
+ <span>Invalid email</span>
+ </div>
+ <!-- Input with Successful Validation -->
+ <div class="at-input has-success">
+ <label for="at-field-password">
+ Password
+ </label>
+ <input type="password" id="at-field-password" name="at-field-password" placeholder="Password" autocapitalize="none" autocorrect="off">
+ </div>
+ <!-- Forgot Password Link -->
+ <div class="at-pwd-link">
+ <p>
+ <a href="/forgot-password" id="at-forgotPwd" class="at-link at-pwd">Forgot your password?</a>
+ </p>
+ </div>
+ <!-- Form Submit Button -->
+ <button type="submit" class="at-btn submit disabled" id="at-btn">
+ Register
+ </button>
+ </form>
+ </div>
+ <!-- Link to Sign In -->
+ <div class="at-signin-link">
+ <p>
+ If you already have an account
+ <a href="/sign-in" id="at-signIn" class="at-link at-signin">sign in</a>
+ </p>
+ </div>
+ <!-- Link to Sign Up -->
+ <div class="at-signup-link">
+ <p>
+ Don't have an account?
+ <a href="/sign-up" id="at-signUp" class="at-link at-signup">Register</a>
+ </p>
+ </div>
+ <!-- Link to Privacy Policy and Terms of use -->
+ <div class="at-terms-link">
+ <p>
+ By clicking Register, you agree to our
+ <a href="/privacyPolicy">Privacy Policy</a>
+ and
+ <a href="/termsOfUse">Terms of Use</a>
+ </p>
+ </div>
+</div>
+```
+
+
+
+<a name="wrapping-up-for-famo.us"/>
+## Wrapping Up for Famo.us
+
+By simply typing
+
+```shell
+meteor add useraccounts:famous-wrapper
+```
+
+you'll be able to turn your preferred flavour of accounts templates into a package ready to be used within a [famous-views](https://atmospherejs.com/gadicohen/famous-views) + [Famo.us](http://famo.us) application.
+
+This means you can get an animated version of the `atForm` template without any effort! :-)
+
+To learn how to make animations you might want to check the following links:
+
+* http://famous-views.meteor.com
+* http://famous-views.meteor.com/examples/animate
+* http://famo.us/university/lessons/#/famous-101/animating/1
+* http://famo.us/guides/layout
+* http://famo.us/guides/animations
+* http://famo.us/docs/modifiers/StateModifier
+* http://famo.us/docs/transitions/Transitionable
+
+### configureAnimations
+
+...well, actually it might be that you don't like the default animations so you might consider to use `AccountsTemplates.configureAnimations` (provided by the wrapper...) to specify your custom animation functions.
+This is an example showing how to do it:
+
+```javascript
+var Transform;
+var Easing;
+if (Meteor.isClient){
+ FView.ready(function(require) {
+ Transform = famous.core.Transform;
+ Easing = famous.transitions.Easing;
+ });
+}
+
+var slideLeftDestroy = function(fview){
+ fview.modifier.setTransform(
+ Transform.translate(-$(window).width(),0),
+ { duration : 250, curve: Easing.easeOutSine },
+ function() { fview.destroy();}
+ );
+};
+
+
+AccountsTemplates.configureAnimations({
+ destroy: {
+ atSignupLink: slideLeftDestroy,
+ }
+});
+```
+
+this asks AT to use `slideLeftDestroy` to animate the template `atSignupLink` when it is to be destroyed.
+
+As you've just seen `configureAnimations` take an `options` object as parameter:
+
+```javascript
+AccountsTemplates.configureAnimations(options);
+```
+
+this options object can have three different keys at the first level:
+
+```javascript
+var options = {
+ render: {
+ // more stuff here...
+ },
+ destroy: {
+ // more stuff here...
+ },
+ state_change: {
+ // more stuff here...
+ },
+ animQueueDelay: 100,
+ animQueueStartDelay: 200,
+ setStateDelay: 300,
+
+};
+AccountsTemplates.configureAnimations(options);
+```
+
+they are `render`, `destroy`, `state_change`, `animQueueDelay`, `animQueueStartDelay`, and `setStateDelay`.
+The first three, what a surprise, they let you specify what to do when one of the templates building up the `atForm` is rendered, destroyed or when the form's state changes (respectively).
+
+...at the second level you can specify which animation has to be applied to which template:
+
+```javascript
+var options = {
+ render: {
+ default: animA,
+ atTitle: animB,
+ atSocial: animC,
+ atSep: animC,
+ atError: animB,
+ atResult: animB,
+ atPwdForm: null,
+ atSigninLink: null,
+ atSignupLink: animB,
+ atTermsLink: animD,
+ },
+ // ...
+};
+```
+
+the above one is the full list of available animated templates...
+The value you specify can be `null` (to remove a default animation...) or a function.
+If you specify a function it should be like the following:
+
+```javascript
+var animFunc = function(fview){
+ fview.modifier.setTransform(
+ Transform.<some_transform>( ... ),
+ { duration : <millisecs>, curve: Easing.<some_curve> }
+ );
+};
+```
+
+the `fview` parameter actually let you access the famous view associated with the template (so feel free to do whatever you wish with it...).
+
+**Warning:** when you specify an animation to be used on `destroy` you must take care of the actual destroy!
+...usually it is enough to call `fview.destroy()` when the animation completes:
+
+```javascript
+var animFunc = function(fview){
+ fview.modifier.setTransform(
+ Transform.<some_transform>( ... ),
+ { duration : <millisecs>, curve: Easing.<some_curve> },
+ function(){ fview.destroy();}
+ );
+};
+```
+
+**Warning2:** At the moment the animation for the state change is supposed to last for double the `setStateDelay` duration, and the state change is actually postponed by `setStateDelay` milliseconds. This let you divide your animation in two different part (so, e.g., you can hide things and show them again with the new content...).
+The following is the default animations used on state change:
+
+```javascript
+vFlip = function(fview){
+ fview.modifier.setTransform(
+ Transform.rotate(Math.PI-0.05,0,0),
+ {
+ duration : AccountsTemplates.animations.setStateDelay,
+ curve: "easeIn",
+ },
+ function() {
+ fview.modifier.setTransform(
+ Transform.rotate(-0.1,0,0),
+ {
+ duration : AccountsTemplates.animations.setStateDelay,
+ curve: "easeOut",
+ }
+ );
+ }
+ );
+};
+```
+
+and as you can see schedules two different animations, one after the another, lasting `setStateDelay` ms each.
+
+
+### pushToAnimationQueue
+
+In case you're interested in sequence animation, AT also provides an experimental animation cue you can use to schedule your animation with a bit of delay between them.
+To use it simply wrap the `modifier.setTransform` within an `AccountsTemplates.pushToAnimationQueue` call, like this:
+
+```jacascript
+var fallFromTop = function(fview){
+ fview.modifier.setTransform(Transform.translate(0, -$(window).height()));
+ AccountsTemplates.pushToAnimationQueue(function() {
+ fview.modifier.setTransform(
+ Transform.translate(0,0),
+ { duration : 450, curve: Easing.easeOutSine }
+ );
+ });
+};
+```
+
+the full signature for it is:
+
+```javascript
+AccountsTemplates.pushToAnimationQueue(func, at_begin);
+```
+
+and if pass `true` for `at_begin`, the function will be pushed to the begin of the cue rather than at the end.
+
+The first animation is started after `animQueueStartDelay` milliseconds from the first insertion and a delay of `animQueueStartDelay` milliseconds is applied between start of animations (you can configure these two values with `configureAnimations` function as listed above...).
+
+And that's it!
+Enjoy ;-)
+
+
+<a name="side-notes"/>
+## Side Notes
+
+
+<a name="3rd-party-login-services-configuration"/>
+### 3rd Party Login Services Configuration
+
+Normally, if you have not configured a social account with, e.g.,
+
+```javascript
+// Set up login services
+Meteor.startup(function() {
+ // Add Facebook configuration entry
+ ServiceConfiguration.configurations.update(
+ { "service": "facebook" },
+ {
+ $set: {
+ "appId": "XXXXXXXXXXXXXXX",
+ "secret": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ }
+ },
+ { upsert: true }
+ );
+
+ // Add GitHub configuration entry
+ ServiceConfiguration.configurations.update(
+ { "service": "github" },
+ {
+ $set: {
+ "clientId": "XXXXXXXXXXXXXXXXXXXX",
+ "secret": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ }
+ },
+ { upsert: true }
+ );
+});
+```
+
+3rd party login buttons are not shown. To allow display buttons with, e.g., 'Configure Foobook', simply add the packages `service-configuration` and `accounts-ui` with:
+
+```Shell
+meteor add service-configuration
+meteor add accounts-ui
+```
+
+**Warning**: At the moment the UI for service configuration is not supported and the one provided by `accounts-ui` will be shown!
diff --git a/packages/meteor-useraccounts-core/History.md b/packages/meteor-useraccounts-core/History.md
new file mode 100644
index 00000000..61bcfaf9
--- /dev/null
+++ b/packages/meteor-useraccounts-core/History.md
@@ -0,0 +1,353 @@
+## Master
+
+## v1.14.2
+
+* [flow-routing] fixed dependency on kadira:flow-router: now using the last non-Meteor@1.3 one
+
+## v1.14.1
+
+* fixed automatic update of weak dependencies on routing packages when publishing new versions
+
+## v1.14.0
+
+* [bulma] *new* `useraccounts:bulma` package to get UI templates styled for [Bulma](http://bulma.io/) (thanks to @dominikmayer)
+* [flow-routing] better error management (merged https://github.com/meteor-useraccounts/flow-routing/pull/23 thanks @stubailo)
+* [flow-routing] added support for FlowRouter 3 (merged https://github.com/meteor-useraccounts/flow-routing/pull/26 thanks @timothyarmes)
+* [foundation-sites] *new* `useraccounts:foundation-sites` package to get UI templates styled for [Foundation for Sites 6](http://foundation.zurb.com/sites.html) (thanks to @venetianthief)
+* [materialize] Added row around recaptcha (thanks @qwIvan)
+* some minor fixed to the Guide
+
+## v1.13.1
+
+* added language support to recaptcha (fixed https://github.com/meteor-useraccounts/core/issues/561 tnx @canesin)
+* fixed validation trigger for select inputs (see discussion within https://github.com/meteor-useraccounts/core/issues/569 tnx @cunneen)
+* change default value for `focusFirstInput` to get it disabled when running on Cordova (see https://github.com/meteor-useraccounts/core/issues/594 tnx @derwaldgeist)
+* fixed regression about reCaptcha reset due to https://github.com/meteor-useraccounts/core/pull/565 (merged https://github.com/meteor-useraccounts/core/pull/597 tnx @jebh)
+
+## v1.13.0
+
+* [mdl] *new* `useraccounts:mdl` package to get UI templates styled for [Material Design Lite](http://www.getmdl.io/) (kudos to @kctang and @liquidautumn, thank you guys!).
+* [flow-routing] added support for React-based layouts (merged https://github.com/meteor-useraccounts/flow-routing/pull/20 tnx @timothyarmes).
+* [materialize] fixed offset problem for fullPageAtForm on medium screens.
+* [materialize] fixed some margins (see https://github.com/meteor-useraccounts/materialize/issues/19).
+* [iron-routing] fixed a problem with route paths (merged https://github.com/meteor-useraccounts/iron-routing/pull/8 tnx @trave7er).
+* [core] updated dependency to softwarerero:accounts-t9n@1.1.7
+* [core] fixed a bug with reCaptcha (merged https://github.com/meteor-useraccounts/core/pull/565 tnx @scsirdx).
+* [core] added missing dependency on JQuery (merged https://github.com/meteor-useraccounts/core/pull/574 tnx @stubailo).
+* [core] added postSignUpHook hook to let people modify newly created user objects (merged https://github.com/meteor-useraccounts/core/pull/586 tnx @shwaydogg)
+* added [Meteor Icon](http://www.getmdl.io/) badges to all packages' README file.
+
+## v1.12.4
+
+* fixed input element classes for `useraccounts:materialize` (see https://github.com/meteor-useraccounts/materialize/pull/18)
+* fixed query parameters look-up for `useraccounts:iron-routing`
+* updated `useraccounts:polymer` to use Polimer 1.0 (see updated [boilerplate](https://github.com/meteor-useraccounts/boilerplates/tree/master/polymer) with some instructions for Meteor 1.2)
+* updates and fixes for `useraccounts:flow-rounting` (see https://github.com/meteor-useraccounts/flow-routing/issues/12)
+* improoved css for `useraccounts:semantic-ui`
+* disallowed use of `signUp` state in case `forbidClientAccountCreation` is set (see #547)
+* updated dependency on softwarerero:accounts-t9n to version 1.1.4
+* a bit of linting here and there...
+* a few typos correction and improvements to the [Guide](https://github.com/meteor-useraccounts/core/blob/master/Guide.md)
+
+## v1.12.3
+
+* fixed radio buttons for useraccounts:materialize (see https://github.com/meteor-useraccounts/core/issues/421)
+* fixed query parameters pick up for useraccounts:iron-routing (see meteor-useraccounts/core#367)
+* corrected few typos within the docs and removed unnecessary debug log
+
+## v1.12.2
+
+* various fixes and a bit of clean up for `useraccounts:flow-routing`
+
+
+## v1.12.1
+
+* fixed inifinite redirect loop for `ensuredSignedIn` within `useraccounts:flow-routing` (see https://github.com/meteor-useraccounts/flow-routing/issues/2)
+
+
+## v1.12.0
+
+* removed routing support from core: refer to [useraccounts:iron-routing](https://github.com/meteor-useraccounts/iron-routing) and [useraccounts:flow-routing](https://github.com/meteor-useraccounts/flow-routing) packages to get some ;-)
+* added template level content protection (see new [Content Protection](https://github.com/meteor-useraccounts/core/blob/master/Guide.md#content-protection) section)
+* updated `useraccounts:semantic-ui` to SUI v2.0 (thanks @lumatijev)
+* `displayName` configuration option for form fields now accepts also functions
+* added the `focusFirstInput` configuration option
+* fixed many typos and added/removed some sections in the Guide
+
+
+## v1.11.1
+
+* fixes for #410, #411, and #413
+* Added a section about available internal states to the Guide (see [Internal States](https://github.com/meteor-useraccounts/core/blob/master/Guide.md#internal-states)
+
+
+## v1.11.0
+
+* change `profile.username` to `profile.name` when using `lowercaseUsername` options (WARNING! this is a bit of a breaking change, see #388)
+* removed possibly annoying warning (see #398)
+* added a `preSignUpHook` to be possibly used to enrich the user profile just before new user registration (see #400)
+* route configuration now accepts additional parameters to be passed to IR (see #409)
+* some improvements to the docs
+
+## v1.10.0
+
+* more customizable texts (see 7d166b74f111e05b22ef2c7d93908441e242350d)
+* added autofocus for the first input field of `atPwdForm`.
+* fixed some texts configuration capability (see #380)
+* various corrections/improvements to the docs
+* allowed for `field.setError` to take in Boolean values (see #361)
+* fixed bug with `Must be logged in` error message shown after sign out (see #321)
+
+## v1.9.1
+
+* aligned `useraccounts:unstyled` with the latest PRs
+
+## v1.9.0
+
+* resend verification email (see #349, thanks @dalgard)
+* allow for a neutral message text to be displayed (see #314 and #317, thanks @dalgard)
+* more configurable error texts (see [Errors Text](https://github.com/meteor-useraccounts/core/blob/master/Guide.md#errors-text), plus #301 #342)
+* fixed little redirect bug (see #315)
+* added title configuration for `verifyEmail` state plus letting titles to be hidden by
+ setting the corresponding text to an empy string (see [Form Title](https://github.com/meteor-useraccounts/core/blob/master/Guide.md#form-title))
+
+## v1.8.1
+
+* made (a fake) `ensureSignedIn` plugin available also on server side code (fixed #291)
+
+## v1.8.0
+
+* added `lowercaseUsername` configuration option (see [Configuration API Options](https://github.com/meteor-useraccounts/core/blob/master/Guide.md#options))
+* added `ensureSignedIn` plugin for Iron Router (see [Content Protection](https://github.com/meteor-useraccounts/core/blob/master/Guide.md#content-protection))
+* fixed `ensureSignedIn` regression (see #286)
+
+## v1.7.1
+
+* fixed routing regression (see #284)
+* removed useless logs
+
+## v1.7.0
+
+* `useraccounts:materialize` to the suite! (Many thanks to @Kestanous!!!)
+* fixed glitch within `ensureSignedIn` (see #278)
+* added experimental support for [reChaptcha](https://www.google.com/recaptcha/intro/index.html) (see #268 and [reCaptcha Setup](https://github.com/meteor-useraccounts/core/blob/master/Guide.md#recaptcha-setup), great work @theplatapi!)
+* new `template` option for deeper input fields customization (see #273 and [Form Fields Configuration](https://github.com/meteor-useraccounts/core/blob/master/Guide.md#form-fields-configuration))
+* prevent access to `atChangePwd` for users not being logged in (see #207)
+* use `Meteor.userID()` in place of `Meteor.user()` where possible to reduce reactive re-computations
+* fixed bug with timed out redirects (see #263)
+* fixed reactivity bug within `ensureSignedIn` (see #262)
+* removed warning about MAIL_URL not being configured (see #267, #210)
+* better `atNavButton` behaviour (see #265 tnx @adrianmc)
+
+## v1.6.1
+
+* updated deps for iron:router and softwarerero:accounts-t9n to latest versions
+
+## v1.6.0
+
+* moved the documentation to a separate file: [Guide](https://github.com/meteor-useraccounts/core/blob/master/Guide.md)
+* fixed bug about calling `sibmitHook` (see #249 #252 tnx @dalgard)
+* new `options` for field configuration (see #250 and [Extending Templates](https://github.com/meteor-useraccounts/core/blob/master/Guide.md#extending-templates) tnx @dalgard)
+* a bit of cleanup for docs (see #251 tnx @dalgard)
+* capitalazed default value for display name and placeholder (see #247)
+* switch to official `Accounts._hasPassword` (see [this](https://github.com/meteor/meteor/pull/2271) and [this](https://github.com/meteor/meteor/pull/3410), tnx @glasser)
+* more sites using useraccounts: congrats to @nate-strauser and @msamoylov on their launches! (see [the list](https://github.com/meteor-useraccounts/core#whos-using-this))
+* new landing page for the whole project and new live examples (still to be further improoved...) :) (see [useraccounts.meteor.com](https://useraccounts.meteor.com))
+* added `transform` among the options for [field configuration](https://github.com/meteor-useraccounts/core/blob/master/Guide.md#form-fields-configuration)
+* better behaviour for input value tranform/fix
+* terms and agreements now showed also on enrollment form (see #253)
+* link to singIn now shown also on forgot password form in case `forbidClientAccountCreation` is set to true (partial solution to #229)
+* moved terms and agreements link right after the submit button (see #239)
+
+## v1.5.0
+
+* added `useraccounts:polymer` to the suite! (WIP, Thanks @kevohagan!!!)
+* fixed a bug with atVerifyEmail route (see #241 and #173)
+* little docs improovements
+
+## v1.4.1
+
+* updated dependency to softwarerero:accounts-t9n@1.0.5 to include Turkish language
+* fixed `{{> atForm state='<state>'}}` which was no more working with Meteor@1.0.2 (see #217)
+* fixed some text configuration (see #209, thanks @bumbleblym)
+* fixed some typos into the docs (see #208, thanks @bumbleblym)
+
+## v1.4.0
+
+* added `useraccounts:ionic` to the suite! (Thanks @nickw!!!)
+* updated `useraccounts:semantic-ui` to SemanticUI@1.0.0 (Thanks @lumatijev!!!)
+* added `onLogoutHook` to be able to run code (custom redirects?) on `AccountsTemplates.logout` (see #191)
+* added `onSubmitHook` among configuration parameters to be able to run code on form submission (might be useful for modals! see #201 and #180)
+* submission button get now disabled also during fields (asynchronous) validation
+* `enforceEmailVerification` now works also with username login (fixed #196)
+* better IE compatibility (see #199)
+* better input field validation flows to recover from previous errors (see #177)
+* updated dependency to softwarerero:accounts-t9n@1.0.4
+* new [Contributing section](https://github.com/meteor-useraccounts/core#contributing) among docs
+* a few improvements and typo fixes for README.md
+
+## v1.3.2 / 2014/11/25
+
+* more robust logout pattern when dealing with routes protected with ensureSigndIn
+
+## v1.3.1 / 2014/11/25
+
+* updated dependency to iron:router@1.0.3
+* fixed bug in linkClick (see #170)
+* fixed bug in configureRoute
+
+## v1.3.0 / 2014/11/23
+
+* added support for [Ratchet](http://goratchet.com/): see [useraccounts:ratchet](https://atmospherejs.com/useraccounts/ratchet). Note: form validation is currently not supported by Ratchet!
+* fixed bug in custom validation flow
+* better default validation for `email` field (see #156)
+* few corrections inside docs
+* added `ensuredSignedIn` among configurable routes so that different `template` and `layoutTemplate` can be specified (fix for #160 and #98)
+* added `socialLoginStyle` among the configuration options to select the login flow (`popup` or `redirect`) for 3rd party login services (see #163)
+* fixed bug about fields ordering
+
+## v1.2.3 / 2014/11/13
+
+* put back in a `init` method dispalying a warning to preserve backward compatibility...
+
+## v1.2.2 / 2014/11/12
+
+* fixed bad redirect for cheange password route (see #154)
+
+## v1.2.1 / 2014/11/12
+
+* fixed regression due reactivity problems after fix for #139
+
+## v1.2.0 / 2014/11/12
+
+* **breaking change:** removed the need to call `Accounts.init()`
+* added support for fields' validating state to display a 'loading' icon
+* added support for fields' icon configuration
+* added support for social buttons' icon configuration (see [this](https://github.com/meteor-useraccounts/core#social-button-icons) new section)
+* added support for `meteor-developer` oauth service (see #147)
+* fixed (special) fields ordering, see #144
+* fixed ensureSignedIn (see #152)
+* removed `new_password` and `new_password_again` special fields, simply use `password` and `password_again` from now on!
+* better redirect behaviour when a logged in user lands on a sign-in/sign-up page: usual redirect is now performed. (see #139)
+* better field validation patterns...
+* updated dependency to irou:router@1.0.1
+* updated dependency to softwarerero:accounts-t9n@1.0.2
+* corrected many errors and typos inside the Documentation
+
+## v1.1.1
+## v1.1.0
+
+* fixed `atNavButton` for useraccounts:unstyled
+* fixed variour names and links in README files
+
+## v1.1.0
+
+* new template `atNavButton`
+* added methos `AccountsTemplates.logout()` which redirects back to `homeRoutePath` when configured
+* support for hidden fields
+* url query parameters loaded into input fields -> useful mostly for hidden fields ;-)
+* granted full control over field ordering (except for special fields...). see #135
+* fixes for #130, #132
+
+## v1.0.1
+
+* fixed link to git repositories inside package.js files
+
+## v1.0.0
+
+* new names: no more splendido:accounts-templates:<somethig> but useraccounts:<somethig> !
+* updated iron:router to v1.0.0
+
+## v0.11.0
+
+* added support for checkbox, select, and radio inputs
+* added defaultState as referred in #125
+* fixes for #127
+
+## v0.10.0
+
+* better texts configuration API (as for #117)
+* prevPath fix
+
+
+## v0.9.16
+
+* updated iron:router to v0.9.4
+
+## v0.9.15
+
+* fixed #110
+
+## v0.9.14
+
+* fixed some redirection problems connected with `ensureSignedIn`
+
+## v0.9.13
+
+* experimental implementation for forbidding access with unverified email (see #108) through configuration flag `enforceEmailVerification`
+* added options to hide links: hideSignInLink, hideSignUpLink
+* fixed #107
+
+## v0.9.12
+
+* fixed #109
+
+## v0.9.11
+
+* better submit button disabling when no negative feedback is used
+* fixed #105
+
+## v0.9.10
+
+* added `defaultLayout` to configuration options
+* new callback parameter to `setState`
+* better rendering behaviour on `ensureSignedIn`
+
+## v0.9.9
+
+* Fixed links for `reset-password`, `enroll-account`, and `verify-email`
+
+## v0.9.8
+
+* fixed checks for login services (see #93)
+* minor updates to docs
+
+## v0.9.7
+
+* fixed #92, to permit the use of, e.g., `{{> atForm state="changePwd"}}` ( see [docs](https://github.com/splendido/accounts-templates-core#templates))
+
+## v0.9.6
+
+* fixed #91, pwdForm submission on signin page has no effect unless both password and usename/email are not empty
+
+## v0.9.5
+
+* show title on sign in also with other services
+* moved sign in link below pwd form
+* removed sign in link from forgot-pwd page (sign up link is still there!)
+* added class at-btn to submit button
+* added class at-signin to sign in link
+* added class at-signup to sign up link
+* added class at-pwd to forgot password link
+* accounts-t9n dependency updated to @1.0.0
+
+## v0.9.4
+
+
+## Older versions (to be written)
+
+* Fixes for #19, #24, #25, #26
+* layoutTemplate option
+* Better signup flow, with proper server side validation!
+* Fixes for #15, and #16
+* Do not show validation errors during sign in
+* Do not show sign up link when account creation is disabled
+* Better use of UnderscoreJS
+* Corrected documentation for showAddRemoveServices
+
+## v0.0.9
+
+* added configuration parameter [`showAddRemoveServices`](https://github.com/splendido/accounts-templates-core#appearance)
+* Fix ensureSignedIn for drawing correct template
+
+## v0.0.8
diff --git a/packages/meteor-useraccounts-core/LICENSE.md b/packages/meteor-useraccounts-core/LICENSE.md
new file mode 100644
index 00000000..aba77efb
--- /dev/null
+++ b/packages/meteor-useraccounts-core/LICENSE.md
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 [@splendido](https://github.com/splendido)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/meteor-useraccounts-core/README.md b/packages/meteor-useraccounts-core/README.md
new file mode 100644
index 00000000..cc8d0bf8
--- /dev/null
+++ b/packages/meteor-useraccounts-core/README.md
@@ -0,0 +1,104 @@
+[![Meteor Icon](http://icon.meteor.com/package/useraccounts:core)](https://atmospherejs.com/useraccounts/core)
+[![Build Status](https://travis-ci.org/meteor-useraccounts/core.svg?branch=master)](https://travis-ci.org/meteor-useraccounts/core)
+
+# User Accounts
+
+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, and link or remove of many 3rd party services.
+
+## Some Details
+
+The package `useraccounts:core` contains all the core logic and templates' helpers and events used by dependant packages providing styled versions of the accounts UI.
+This means that developing a version of the UI with a different styling is just a matter of writing a few dozen of html lines, nothing more!
+
+Thanks to [accounts-t9n](https://github.com/softwarerero/meteor-accounts-t9n) you can switch to your preferred language on the fly! Available languages are now: Arabic, Czech, French, German, Italian, Polish, Portuguese, Russian, Slovenian, Spanish, Swedish, Turkish and Vietnamese.
+
+For basic routing and content protection, `useraccounts:core` integrates with either [flow-router](https://github.com/meteor-useraccounts/flow-routing) or [iron-router](https://atmospherejs.com/package/iron-router).
+
+Any comments, suggestions, testing efforts, and PRs are very very welcome! Please use the [repository](https://github.com/meteor-useraccounts/ui) issues tracker for reporting bugs, problems, ideas, discussions, etc..
+
+## The UserAccounts Guide
+Detailed explanations of features and configuration options can be found in the <a href="https://github.com/meteor-useraccounts/core/blob/master/Guide.md" target="_blank">Guide</a>.
+
+## Who's using this?
+
+* [Abesea](https://abesea.com/)
+* [backspace.academy](http://backspace.academy/)
+* [bootstrappers.io](http://www.bootstrappers.io/)
+* [crater.io](http://crater.io/)
+* [Dechiper Chinese](http://app.decipherchinese.com/)
+* [Henfood](http://labs.henesis.eu/henfood)
+* [meteorgigs.io](https://www.meteorgigs.io/)
+* [Orion](http://orionjs.org/)
+* [Telescope](http://www.telesc.pe/)
+* [We Work Meteor](http://www.weworkmeteor.com/)
+
+
+Aren't you on the list?!
+If you have a production app using accounts templates, let me know! I'd like to add your link to the above ones.
+
+## Contributing
+Contributors are very welcome. There are many things you can help with,
+including finding and fixing bugs and creating examples for the brand new [wiki](https://github.com/meteor-useraccounts/wiki).
+We're also working on `useraccounts@2.0` (see the [Milestone](https://github.com/meteor-useraccounts/core/milestones)) so you can also help
+with an improved design or adding features.
+
+Some guidelines below:
+
+* **Questions**: Please create a new issue and label it as a `question`.
+
+* **New Features**: If you'd like to work on a feature,
+ start by creating a 'Feature Design: Title' issue. This will let people bat it
+ around a bit before you send a full blown pull request. Also, you can create
+ an issue to discuss a design even if you won't be working on it.
+
+* **Bugs**: If you think you found a bug, please create a "reproduction." This is a small project that demonstrates the problem as concisely as possible. If you think the bug can be reproduced with only a few steps a description by words might be enough though. The project should be cloneable from Github. Any bug reports without a reproduction that don't have an obvious solution will be marked as "awaiting-reproduction" and closed after a bit of time.
+
+### Working Locally
+This is useful if you're contributing code to useraccounts or just trying to modify something to suit your own specific needs.
+
+##### Scenario A
+
+1. Set up a local packages folder
+2. Add the PACKAGE_DIRS environment variable to your .bashrc file
+ - Example: `export PACKAGE_DIRS="/full/path/topackages/folder"`
+ - Screencast: https://www.eventedmind.com/posts/meteor-versioning-and-packages
+3. Clone the repository into your local packages directory
+4. Add the package just like any other meteor core package like this: `meteor
+ add useraccounts:unstyled`
+
+```bash
+> cd /full/path/topackages/folder
+> git clone https://github.com/meteor-useraccounts/semantic-ui.git
+> cd your/project/path
+> meteor add useraccounts:semantic-ui
+> meteor
+```
+
+##### Scenario B
+
+Like Scenario A, but skipping point 2.
+Add the official package as usual with `meteor add useraccounts:semantic-ui` but then run your project like this:
+
+```bash
+> PACKAGE_DIRS="/full/path/topackages/folder" meteor
+```
+
+##### Scenario C
+
+```bash
+> cd your/project/path
+> mkdir packages && cd packages
+> git clone https://github.com/meteor-useraccounts/semantic-ui.git
+> cd ..
+> meteor add useraccounts:semantic-ui
+> meteor
+```
+
+
+## Thanks
+
+Anyone is welcome to contribute. Fork, make your changes, and then submit a pull request.
+
+Thanks to [all those who have contributed code changes](https://github.com/meteor-useraccounts/ui/graphs/contributors) and all who have helped by submitting bug reports and feature ideas.
+
+[![Support via Gittip](https://rawgithub.com/twolfson/gittip-badge/0.2.0/dist/gittip.png)](https://www.gittip.com/splendido/)
diff --git a/packages/meteor-useraccounts-core/lib/client.js b/packages/meteor-useraccounts-core/lib/client.js
new file mode 100644
index 00000000..31c9db74
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/client.js
@@ -0,0 +1,464 @@
+/* global
+ AT: false
+*/
+"use strict";
+
+// Allowed Internal (client-side) States
+AT.prototype.STATES = [
+ "changePwd", // Change Password
+ "enrollAccount", // Account Enrollment
+ "forgotPwd", // Forgot Password
+ "hide", // Nothing displayed
+ "resetPwd", // Reset Password
+ "signIn", // Sign In
+ "signUp", // Sign Up
+ "verifyEmail", // Email verification
+ "resendVerificationEmail", // Resend verification email
+];
+
+AT.prototype._loginType = "";
+
+// Flag telling whether the whole form should appear disabled
+AT.prototype._disabled = false;
+
+// State validation
+AT.prototype._isValidState = function(value) {
+ return _.contains(this.STATES, value);
+};
+
+// Flags used to avoid clearing errors and redirecting to previous route when
+// signing in/up as a results of a call to ensureSignedIn
+AT.prototype.avoidRedirect = false;
+AT.prototype.avoidClearError = false;
+
+// Token to be provided for routes like reset-password and enroll-account
+AT.prototype.paramToken = null;
+
+AT.prototype.loginType = function () {
+ return this._loginType;
+};
+
+AT.prototype.getparamToken = function() {
+ return this.paramToken;
+};
+
+// Getter for current state
+AT.prototype.getState = function() {
+ return this.state.form.get("state");
+};
+
+// Getter for disabled state
+AT.prototype.disabled = function() {
+ return this.state.form.equals("disabled", true) ? "disabled" : undefined;
+};
+
+// Setter for disabled state
+AT.prototype.setDisabled = function(value) {
+ check(value, Boolean);
+ return this.state.form.set("disabled", value);
+};
+
+// Setter for current state
+AT.prototype.setState = function(state, callback) {
+ check(state, String);
+
+ if (!this._isValidState(state) || (this.options.forbidClientAccountCreation && state === 'signUp')) {
+ throw new Meteor.Error(500, "Internal server error", "accounts-templates-core package got an invalid state value!");
+ }
+
+ this.state.form.set("state", state);
+ if (!this.avoidClearError) {
+ this.clearState();
+ }
+ this.avoidClearError = false;
+
+ if (_.isFunction(callback)) {
+ callback();
+ }
+};
+
+AT.prototype.clearState = function() {
+ _.each(this._fields, function(field) {
+ field.clearStatus();
+ });
+
+ var form = this.state.form;
+
+ form.set("error", null);
+ form.set("result", null);
+ form.set("message", null);
+
+ AccountsTemplates.setDisabled(false);
+};
+
+AT.prototype.clearError = function() {
+ this.state.form.set("error", null);
+};
+
+AT.prototype.clearResult = function() {
+ this.state.form.set("result", null);
+};
+
+AT.prototype.clearMessage = function() {
+ this.state.form.set("message", null);
+};
+
+// Initialization
+AT.prototype.init = function() {
+ console.warn("[AccountsTemplates] There is no more need to call AccountsTemplates.init()! Simply remove the call ;-)");
+};
+
+AT.prototype._init = function() {
+ if (this._initialized) {
+ return;
+ }
+
+ var usernamePresent = this.hasField("username");
+ var emailPresent = this.hasField("email");
+
+ if (usernamePresent && emailPresent) {
+ this._loginType = "username_and_email";
+ } else {
+ this._loginType = usernamePresent ? "username" : "email";
+ }
+
+ if (this._loginType === "username_and_email") {
+ // Possibly adds the field username_and_email in case
+ // it was not configured
+ if (!this.hasField("username_and_email")) {
+ this.addField({
+ _id: "username_and_email",
+ type: "text",
+ displayName: "usernameOrEmail",
+ placeholder: "usernameOrEmail",
+ required: true,
+ });
+ }
+ }
+
+ // Only in case password confirmation is required
+ if (this.options.confirmPassword) {
+ // Possibly adds the field password_again in case
+ // it was not configured
+ if (!this.hasField("password_again")) {
+ var pwdAgain = _.clone(this.getField("password"));
+
+ pwdAgain._id = "password_again";
+ pwdAgain.displayName = {
+ "default": "passwordAgain",
+ changePwd: "newPasswordAgain",
+ resetPwd: "newPasswordAgain",
+ };
+ pwdAgain.placeholder = {
+ "default": "passwordAgain",
+ changePwd: "newPasswordAgain",
+ resetPwd: "newPasswordAgain",
+ };
+ this.addField(pwdAgain);
+ }
+ } else {
+ if (this.hasField("password_again")) {
+ throw new Error("AccountsTemplates: a field password_again was added but confirmPassword is set to false!");
+ }
+ }
+
+ // Possibly adds the field current_password in case
+ // it was not configured
+ if (this.options.enablePasswordChange) {
+ if (!this.hasField("current_password")) {
+ this.addField({
+ _id: "current_password",
+ type: "password",
+ displayName: "currentPassword",
+ placeholder: "currentPassword",
+ required: true,
+ });
+ }
+ }
+
+ // Ensuser the right order of special fields
+ var moveFieldAfter = function(fieldName, referenceFieldName) {
+ var fieldIds = AccountsTemplates.getFieldIds();
+ var refFieldId = _.indexOf(fieldIds, referenceFieldName);
+ // In case the reference field is not present, just return...
+ if (refFieldId === -1) {
+ return;
+ }
+
+ var fieldId = _.indexOf(fieldIds, fieldName);
+ // In case the sought field is not present, just return...
+ if (fieldId === -1) {
+ return;
+ }
+
+ if (fieldId !== -1 && fieldId !== (refFieldId + 1)) {
+ // removes the field
+ var field = AccountsTemplates._fields.splice(fieldId, 1)[0];
+ // push the field right after the reference field position
+ var newFieldIds = AccountsTemplates.getFieldIds();
+ var newReferenceFieldId = _.indexOf(newFieldIds, referenceFieldName);
+ AccountsTemplates._fields.splice(newReferenceFieldId + 1, 0, field);
+ }
+ };
+
+ // Ensuser the right order of special fields
+ var moveFieldBefore = function(fieldName, referenceFieldName) {
+ var fieldIds = AccountsTemplates.getFieldIds();
+ var refFieldId = _.indexOf(fieldIds, referenceFieldName);
+ // In case the reference field is not present, just return...
+ if (refFieldId === -1) {
+ return;
+ }
+
+ var fieldId = _.indexOf(fieldIds, fieldName);
+ // In case the sought field is not present, just return...
+ if (fieldId === -1) {
+ return;
+ }
+
+ if (fieldId !== -1 && fieldId !== (refFieldId - 1)) {
+ // removes the field
+ var field = AccountsTemplates._fields.splice(fieldId, 1)[0];
+ // push the field right after the reference field position
+ var newFieldIds = AccountsTemplates.getFieldIds();
+ var newReferenceFieldId = _.indexOf(newFieldIds, referenceFieldName);
+ AccountsTemplates._fields.splice(newReferenceFieldId, 0, field);
+ }
+ };
+
+ // The final order should be something like:
+ // - username
+ // - email
+ // - username_and_email
+ // - password
+ // - password_again
+ //
+ // ...so lets do it in reverse order...
+ moveFieldAfter("username_and_email", "username");
+ moveFieldAfter("username_and_email", "email");
+ moveFieldBefore("current_password", "password");
+ moveFieldAfter("password", "current_password");
+ moveFieldAfter("password_again", "password");
+
+
+ // Sets visibility condition and validation flags for each field
+ var gPositiveValidation = !!AccountsTemplates.options.positiveValidation;
+ var gNegativeValidation = !!AccountsTemplates.options.negativeValidation;
+ var gShowValidating = !!AccountsTemplates.options.showValidating;
+ var gContinuousValidation = !!AccountsTemplates.options.continuousValidation;
+ var gNegativeFeedback = !!AccountsTemplates.options.negativeFeedback;
+ var gPositiveFeedback = !!AccountsTemplates.options.positiveFeedback;
+
+ _.each(this._fields, function(field) {
+ // Visibility
+ switch(field._id) {
+ case "current_password":
+ field.visible = ["changePwd"];
+ break;
+ case "email":
+ field.visible = ["forgotPwd", "signUp", "resendVerificationEmail"];
+ if (AccountsTemplates.loginType() === "email") {
+ field.visible.push("signIn");
+ }
+ break;
+ case "password":
+ field.visible = ["changePwd", "enrollAccount", "resetPwd", "signIn", "signUp"];
+ break;
+ case "password_again":
+ field.visible = ["changePwd", "enrollAccount", "resetPwd", "signUp"];
+ break;
+ case "username":
+ field.visible = ["signUp"];
+ if (AccountsTemplates.loginType() === "username") {
+ field.visible.push("signIn");
+ }
+ break;
+ case "username_and_email":
+ field.visible = [];
+ if (AccountsTemplates.loginType() === "username_and_email") {
+ field.visible.push("signIn");
+ }
+ break;
+ default:
+ field.visible = ["signUp"];
+ }
+
+ // Validation
+ var positiveValidation = field.positiveValidation;
+ if (_.isUndefined(positiveValidation)) {
+ field.positiveValidation = gPositiveValidation;
+ }
+
+ var negativeValidation = field.negativeValidation;
+ if (_.isUndefined(negativeValidation)) {
+ field.negativeValidation = gNegativeValidation;
+ }
+
+ field.validation = field.positiveValidation || field.negativeValidation;
+ if (_.isUndefined(field.continuousValidation)) {
+ field.continuousValidation = gContinuousValidation;
+ }
+
+ field.continuousValidation = field.validation && field.continuousValidation;
+ if (_.isUndefined(field.negativeFeedback)) {
+ field.negativeFeedback = gNegativeFeedback;
+ }
+
+ if (_.isUndefined(field.positiveFeedback)) {
+ field.positiveFeedback = gPositiveFeedback;
+ }
+
+ field.feedback = field.negativeFeedback || field.positiveFeedback;
+ // Validating icon
+ var showValidating = field.showValidating;
+ if (_.isUndefined(showValidating)) {
+ field.showValidating = gShowValidating;
+ }
+
+ // Custom Template
+ if (field.template) {
+ if (field.template in Template) {
+ Template[field.template].helpers(AccountsTemplates.atInputHelpers);
+ } else {
+ console.warn(
+ "[UserAccounts] Warning no template " + field.template + " found!"
+ );
+ }
+ }
+ });
+
+ // Initializes reactive states
+ var form = new ReactiveDict();
+
+ form.set("disabled", false);
+ form.set("state", "signIn");
+ form.set("result", null);
+ form.set("error", null);
+ form.set("message", null);
+ this.state = {
+ form: form,
+ };
+
+ // Possibly subscribes to extended user data (to get the list of registered services...)
+ if (this.options.showAddRemoveServices) {
+ Meteor.subscribe("userRegisteredServices");
+ }
+
+ //Check that reCaptcha site keys are available and no secret keys visible
+ if (this.options.showReCaptcha) {
+ var atSiteKey = null;
+ var atSecretKey = null;
+ var settingsSiteKey = null;
+ var settingsSecretKey = null;
+
+ if (AccountsTemplates.options.reCaptcha) {
+ atSiteKey = AccountsTemplates.options.reCaptcha.siteKey;
+ atSecretKey = AccountsTemplates.options.reCaptcha.secretKey;
+ }
+
+ if (Meteor.settings && Meteor.settings.public && Meteor.settings.public.reCaptcha) {
+ settingsSiteKey = Meteor.settings.public.reCaptcha.siteKey;
+ settingsSecretKey = Meteor.settings.public.reCaptcha.secretKey;
+ }
+
+ if (atSecretKey || settingsSecretKey) {
+ //erase the secret key
+ if (atSecretKey) {
+ AccountsTemplates.options.reCaptcha.secretKey = null;
+ }
+
+ if (settingsSecretKey) {
+ Meteor.settings.public.reCaptcha.secretKey = null;
+ }
+
+ var loc = atSecretKey ? "User Accounts configuration!" : "Meteor settings!";
+ throw new Meteor.Error(401, "User Accounts: DANGER - reCaptcha private key leaked to client from " + loc
+ + " Provide the key in server settings ONLY.");
+ }
+
+ if (!atSiteKey && !settingsSiteKey) {
+ throw new Meteor.Error(401, "User Accounts: reCaptcha site key not found! Please provide it or set showReCaptcha to false.");
+ }
+ }
+
+ // Marks AccountsTemplates as initialized
+ this._initialized = true;
+};
+
+AT.prototype.linkClick = function(route) {
+ if (AccountsTemplates.disabled()) {
+ return;
+ }
+
+ AccountsTemplates.setState(route);
+
+ if (AccountsTemplates.options.focusFirstInput) {
+ var firstVisibleInput = _.find(this.getFields(), function(f) {
+ return _.contains(f.visible, route);
+ });
+
+ if (firstVisibleInput) {
+ $("input#at-field-" + firstVisibleInput._id).focus();
+ }
+ }
+};
+
+AT.prototype.logout = function() {
+ var onLogoutHook = AccountsTemplates.options.onLogoutHook;
+
+ Meteor.logout(function() {
+ if (onLogoutHook) {
+ onLogoutHook();
+ }
+ });
+};
+
+AT.prototype.submitCallback = function(error, state, onSuccess) {
+ var onSubmitHook = AccountsTemplates.options.onSubmitHook;
+
+ if (onSubmitHook) {
+ onSubmitHook(error, state);
+ }
+
+ if (error) {
+ if (_.isObject(error.details)) {
+ // If error.details is an object, we may try to set fields errors from it
+ _.each(error.details, function(error, fieldId) {
+ AccountsTemplates.getField(fieldId).setError(error);
+ });
+ } else {
+ var err = "error.accounts.Unknown error";
+
+ if (error.reason) {
+ err = error.reason;
+ }
+
+ if (err.substring(0, 15) !== "error.accounts.") {
+ err = "error.accounts." + err;
+ }
+
+ AccountsTemplates.state.form.set("error", [err]);
+ }
+
+ AccountsTemplates.setDisabled(false);
+ // Possibly resets reCaptcha form
+ if (state === "signUp" && AccountsTemplates.options.showReCaptcha) {
+ grecaptcha.reset();
+ }
+ } else {
+ if (onSuccess) {
+ onSuccess();
+ }
+
+ if (state) {
+ AccountsTemplates.setDisabled(false);
+ }
+ }
+};
+
+AccountsTemplates = new AT();
+
+// Initialization
+Meteor.startup(function() {
+ AccountsTemplates._init();
+});
diff --git a/packages/meteor-useraccounts-core/lib/core.js b/packages/meteor-useraccounts-core/lib/core.js
new file mode 100644
index 00000000..1c7bc07a
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/core.js
@@ -0,0 +1,593 @@
+// ---------------------------------------------------------------------------------
+// Patterns for methods" parameters
+// ---------------------------------------------------------------------------------
+
+STATE_PAT = {
+ changePwd: Match.Optional(String),
+ enrollAccount: Match.Optional(String),
+ forgotPwd: Match.Optional(String),
+ resetPwd: Match.Optional(String),
+ signIn: Match.Optional(String),
+ signUp: Match.Optional(String),
+ verifyEmail: Match.Optional(String),
+ resendVerificationEmail: Match.Optional(String),
+};
+
+ERRORS_PAT = {
+ accountsCreationDisabled: Match.Optional(String),
+ cannotRemoveService: Match.Optional(String),
+ captchaVerification: Match.Optional(String),
+ loginForbidden: Match.Optional(String),
+ mustBeLoggedIn: Match.Optional(String),
+ pwdMismatch: Match.Optional(String),
+ validationErrors: Match.Optional(String),
+ verifyEmailFirst: Match.Optional(String),
+};
+
+INFO_PAT = {
+ emailSent: Match.Optional(String),
+ emailVerified: Match.Optional(String),
+ pwdChanged: Match.Optional(String),
+ pwdReset: Match.Optional(String),
+ pwdSet: Match.Optional(String),
+ signUpVerifyEmail: Match.Optional(String),
+ verificationEmailSent: Match.Optional(String),
+};
+
+INPUT_ICONS_PAT = {
+ hasError: Match.Optional(String),
+ hasSuccess: Match.Optional(String),
+ isValidating: Match.Optional(String),
+};
+
+ObjWithStringValues = Match.Where(function (x) {
+ check(x, Object);
+ _.each(_.values(x), function(value) {
+ check(value, String);
+ });
+ return true;
+});
+
+TEXTS_PAT = {
+ button: Match.Optional(STATE_PAT),
+ errors: Match.Optional(ERRORS_PAT),
+ info: Match.Optional(INFO_PAT),
+ inputIcons: Match.Optional(INPUT_ICONS_PAT),
+ maxAllowedLength: Match.Optional(String),
+ minRequiredLength: Match.Optional(String),
+ navSignIn: Match.Optional(String),
+ navSignOut: Match.Optional(String),
+ optionalField: Match.Optional(String),
+ pwdLink_link: Match.Optional(String),
+ pwdLink_pre: Match.Optional(String),
+ pwdLink_suff: Match.Optional(String),
+ requiredField: Match.Optional(String),
+ resendVerificationEmailLink_pre: Match.Optional(String),
+ resendVerificationEmailLink_link: Match.Optional(String),
+ resendVerificationEmailLink_suff: Match.Optional(String),
+ sep: Match.Optional(String),
+ signInLink_link: Match.Optional(String),
+ signInLink_pre: Match.Optional(String),
+ signInLink_suff: Match.Optional(String),
+ signUpLink_link: Match.Optional(String),
+ signUpLink_pre: Match.Optional(String),
+ signUpLink_suff: Match.Optional(String),
+ socialAdd: Match.Optional(String),
+ socialConfigure: Match.Optional(String),
+ socialIcons: Match.Optional(ObjWithStringValues),
+ socialRemove: Match.Optional(String),
+ socialSignIn: Match.Optional(String),
+ socialSignUp: Match.Optional(String),
+ socialWith: Match.Optional(String),
+ termsAnd: Match.Optional(String),
+ termsPreamble: Match.Optional(String),
+ termsPrivacy: Match.Optional(String),
+ termsTerms: Match.Optional(String),
+ title: Match.Optional(STATE_PAT),
+};
+
+// Configuration pattern to be checked with check
+CONFIG_PAT = {
+ // Behaviour
+ confirmPassword: Match.Optional(Boolean),
+ defaultState: Match.Optional(String),
+ enablePasswordChange: Match.Optional(Boolean),
+ enforceEmailVerification: Match.Optional(Boolean),
+ focusFirstInput: Match.Optional(Boolean),
+ forbidClientAccountCreation: Match.Optional(Boolean),
+ lowercaseUsername: Match.Optional(Boolean),
+ overrideLoginErrors: Match.Optional(Boolean),
+ sendVerificationEmail: Match.Optional(Boolean),
+ socialLoginStyle: Match.Optional(Match.OneOf("popup", "redirect")),
+
+ // Appearance
+ defaultLayout: Match.Optional(String),
+ hideSignInLink: Match.Optional(Boolean),
+ hideSignUpLink: Match.Optional(Boolean),
+ showAddRemoveServices: Match.Optional(Boolean),
+ showForgotPasswordLink: Match.Optional(Boolean),
+ showResendVerificationEmailLink: Match.Optional(Boolean),
+ showLabels: Match.Optional(Boolean),
+ showPlaceholders: Match.Optional(Boolean),
+
+ // Client-side Validation
+ continuousValidation: Match.Optional(Boolean),
+ negativeFeedback: Match.Optional(Boolean),
+ negativeValidation: Match.Optional(Boolean),
+ positiveFeedback: Match.Optional(Boolean),
+ positiveValidation: Match.Optional(Boolean),
+ showValidating: Match.Optional(Boolean),
+
+ // Privacy Policy and Terms of Use
+ privacyUrl: Match.Optional(String),
+ termsUrl: Match.Optional(String),
+
+ // Redirects
+ homeRoutePath: Match.Optional(String),
+ redirectTimeout: Match.Optional(Number),
+
+ // Hooks
+ onLogoutHook: Match.Optional(Function),
+ onSubmitHook: Match.Optional(Function),
+ preSignUpHook: Match.Optional(Function),
+ postSignUpHook: Match.Optional(Function),
+
+ texts: Match.Optional(TEXTS_PAT),
+
+ //reCaptcha config
+ reCaptcha: Match.Optional({
+ data_type: Match.Optional(Match.OneOf("audio", "image")),
+ secretKey: Match.Optional(String),
+ siteKey: Match.Optional(String),
+ theme: Match.Optional(Match.OneOf("dark", "light")),
+ }),
+
+ showReCaptcha: Match.Optional(Boolean),
+};
+
+
+FIELD_SUB_PAT = {
+ "default": Match.Optional(String),
+ changePwd: Match.Optional(String),
+ enrollAccount: Match.Optional(String),
+ forgotPwd: Match.Optional(String),
+ resetPwd: Match.Optional(String),
+ signIn: Match.Optional(String),
+ signUp: Match.Optional(String),
+};
+
+
+// Field pattern
+FIELD_PAT = {
+ _id: String,
+ type: String,
+ required: Match.Optional(Boolean),
+ displayName: Match.Optional(Match.OneOf(String, Match.Where(_.isFunction), FIELD_SUB_PAT)),
+ placeholder: Match.Optional(Match.OneOf(String, FIELD_SUB_PAT)),
+ select: Match.Optional([{text: String, value: Match.Any}]),
+ minLength: Match.Optional(Match.Integer),
+ maxLength: Match.Optional(Match.Integer),
+ re: Match.Optional(RegExp),
+ func: Match.Optional(Match.Where(_.isFunction)),
+ errStr: Match.Optional(String),
+
+ // Client-side Validation
+ continuousValidation: Match.Optional(Boolean),
+ negativeFeedback: Match.Optional(Boolean),
+ negativeValidation: Match.Optional(Boolean),
+ positiveValidation: Match.Optional(Boolean),
+ positiveFeedback: Match.Optional(Boolean),
+
+ // Transforms
+ trim: Match.Optional(Boolean),
+ lowercase: Match.Optional(Boolean),
+ uppercase: Match.Optional(Boolean),
+ transform: Match.Optional(Match.Where(_.isFunction)),
+
+ // Custom options
+ options: Match.Optional(Object),
+ template: Match.Optional(String),
+};
+
+// -----------------------------------------------------------------------------
+// AccountsTemplates object
+// -----------------------------------------------------------------------------
+
+// -------------------
+// Client/Server stuff
+// -------------------
+
+// Constructor
+AT = function() {
+
+};
+
+AT.prototype.CONFIG_PAT = CONFIG_PAT;
+
+/*
+ Each field object is represented by the following properties:
+ _id: String (required) // A unique field"s id / name
+ type: String (required) // Displayed input type
+ required: Boolean (optional) // Specifies Whether to fail or not when field is left empty
+ displayName: String (optional) // The field"s name to be displayed as a label above the input element
+ placeholder: String (optional) // The placeholder text to be displayed inside the input element
+ minLength: Integer (optional) // Possibly specifies the minimum allowed length
+ maxLength: Integer (optional) // Possibly specifies the maximum allowed length
+ re: RegExp (optional) // Regular expression for validation
+ func: Function (optional) // Custom function for validation
+ errStr: String (optional) // Error message to be displayed in case re validation fails
+*/
+
+
+// Allowed input types
+AT.prototype.INPUT_TYPES = [
+ "checkbox",
+ "email",
+ "hidden",
+ "password",
+ "radio",
+ "select",
+ "tel",
+ "text",
+ "url",
+];
+
+// Current configuration values
+AT.prototype.options = {
+ // Appearance
+ //defaultLayout: undefined,
+ showAddRemoveServices: false,
+ showForgotPasswordLink: false,
+ showResendVerificationEmailLink: false,
+ showLabels: true,
+ showPlaceholders: true,
+
+ // Behaviour
+ confirmPassword: true,
+ defaultState: "signIn",
+ enablePasswordChange: false,
+ focusFirstInput: !Meteor.isCordova,
+ forbidClientAccountCreation: false,
+ lowercaseUsername: false,
+ overrideLoginErrors: true,
+ sendVerificationEmail: false,
+ socialLoginStyle: "popup",
+
+ // Client-side Validation
+ //continuousValidation: false,
+ //negativeFeedback: false,
+ //negativeValidation: false,
+ //positiveValidation: false,
+ //positiveFeedback: false,
+ //showValidating: false,
+
+ // Privacy Policy and Terms of Use
+ privacyUrl: undefined,
+ termsUrl: undefined,
+
+ // Hooks
+ onSubmitHook: undefined,
+};
+
+AT.prototype.texts = {
+ button: {
+ changePwd: "updateYourPassword",
+ //enrollAccount: "createAccount",
+ enrollAccount: "signUp",
+ forgotPwd: "emailResetLink",
+ resetPwd: "setPassword",
+ signIn: "signIn",
+ signUp: "signUp",
+ resendVerificationEmail: "Send email again",
+ },
+ errors: {
+ accountsCreationDisabled: "Client side accounts creation is disabled!!!",
+ cannotRemoveService: "Cannot remove the only active service!",
+ captchaVerification: "Captcha verification failed!",
+ loginForbidden: "error.accounts.Login forbidden",
+ mustBeLoggedIn: "error.accounts.Must be logged in",
+ pwdMismatch: "error.pwdsDontMatch",
+ validationErrors: "Validation Errors",
+ verifyEmailFirst: "Please verify your email first. Check the email and follow the link!",
+ },
+ navSignIn: 'signIn',
+ navSignOut: 'signOut',
+ info: {
+ emailSent: "info.emailSent",
+ emailVerified: "info.emailVerified",
+ pwdChanged: "info.passwordChanged",
+ pwdReset: "info.passwordReset",
+ pwdSet: "Password Set",
+ signUpVerifyEmail: "Successful Registration! Please check your email and follow the instructions.",
+ verificationEmailSent: "A new email has been sent to you. If the email doesn't show up in your inbox, be sure to check your spam folder.",
+ },
+ inputIcons: {
+ isValidating: "fa fa-spinner fa-spin",
+ hasSuccess: "fa fa-check",
+ hasError: "fa fa-times",
+ },
+ maxAllowedLength: "Maximum allowed length",
+ minRequiredLength: "Minimum required length",
+ optionalField: "optional",
+ pwdLink_pre: "",
+ pwdLink_link: "forgotPassword",
+ pwdLink_suff: "",
+ requiredField: "Required Field",
+ resendVerificationEmailLink_pre: "Verification email lost?",
+ resendVerificationEmailLink_link: "Send again",
+ resendVerificationEmailLink_suff: "",
+ sep: "OR",
+ signInLink_pre: "ifYouAlreadyHaveAnAccount",
+ signInLink_link: "signin",
+ signInLink_suff: "",
+ signUpLink_pre: "dontHaveAnAccount",
+ signUpLink_link: "signUp",
+ signUpLink_suff: "",
+ socialAdd: "add",
+ socialConfigure: "configure",
+ socialIcons: {
+ "meteor-developer": "fa fa-rocket"
+ },
+ socialRemove: "remove",
+ socialSignIn: "signIn",
+ socialSignUp: "signUp",
+ socialWith: "with",
+ termsPreamble: "clickAgree",
+ termsPrivacy: "privacyPolicy",
+ termsAnd: "and",
+ termsTerms: "terms",
+ title: {
+ changePwd: "changePassword",
+ enrollAccount: "createAccount",
+ forgotPwd: "resetYourPassword",
+ resetPwd: "resetYourPassword",
+ signIn: "signIn",
+ signUp: "createAccount",
+ verifyEmail: "",
+ resendVerificationEmail: "Send the verification email again",
+ },
+};
+
+AT.prototype.SPECIAL_FIELDS = [
+ "password_again",
+ "username_and_email",
+];
+
+// SignIn / SignUp fields
+AT.prototype._fields = [
+ new Field({
+ _id: "email",
+ type: "email",
+ required: true,
+ lowercase: true,
+ trim: true,
+ func: function(email) {
+ return !_.contains(email, '@');
+ },
+ errStr: 'Invalid email',
+ }),
+ new Field({
+ _id: "password",
+ type: "password",
+ required: true,
+ minLength: 6,
+ displayName: {
+ "default": "password",
+ changePwd: "newPassword",
+ resetPwd: "newPassword",
+ },
+ placeholder: {
+ "default": "password",
+ changePwd: "newPassword",
+ resetPwd: "newPassword",
+ },
+ }),
+];
+
+
+AT.prototype._initialized = false;
+
+// Input type validation
+AT.prototype._isValidInputType = function(value) {
+ return _.indexOf(this.INPUT_TYPES, value) !== -1;
+};
+
+AT.prototype.addField = function(field) {
+ // Fields can be added only before initialization
+ if (this._initialized) {
+ throw new Error("AccountsTemplates.addField should strictly be called before AccountsTemplates.init!");
+ }
+
+ field = _.pick(field, _.keys(FIELD_PAT));
+ check(field, FIELD_PAT);
+ // Checks there"s currently no field called field._id
+ if (_.indexOf(_.pluck(this._fields, "_id"), field._id) !== -1) {
+ throw new Error("A field called " + field._id + " already exists!");
+ }
+ // Validates field.type
+ if (!this._isValidInputType(field.type)) {
+ throw new Error("field.type is not valid!");
+ }
+ // Checks field.minLength is strictly positive
+ if (typeof field.minLength !== "undefined" && field.minLength <= 0) {
+ throw new Error("field.minLength should be greater than zero!");
+ }
+ // Checks field.maxLength is strictly positive
+ if (typeof field.maxLength !== "undefined" && field.maxLength <= 0) {
+ throw new Error("field.maxLength should be greater than zero!");
+ }
+ // Checks field.maxLength is greater than field.minLength
+ if (typeof field.minLength !== "undefined" && typeof field.minLength !== "undefined" && field.maxLength < field.minLength) {
+ throw new Error("field.maxLength should be greater than field.maxLength!");
+ }
+
+ if (!(Meteor.isServer && _.contains(this.SPECIAL_FIELDS, field._id))) {
+ this._fields.push(new Field(field));
+ }
+
+ return this._fields;
+};
+
+AT.prototype.addFields = function(fields) {
+ var ok;
+
+ try { // don"t bother with `typeof` - just access `length` and `catch`
+ ok = fields.length > 0 && "0" in Object(fields);
+ } catch (e) {
+ throw new Error("field argument should be an array of valid field objects!");
+ }
+ if (ok) {
+ _.map(fields, function(field) {
+ this.addField(field);
+ }, this);
+ } else {
+ throw new Error("field argument should be an array of valid field objects!");
+ }
+
+ return this._fields;
+};
+
+AT.prototype.configure = function(config) {
+ // Configuration options can be set only before initialization
+ if (this._initialized) {
+ throw new Error("Configuration options must be set before AccountsTemplates.init!");
+ }
+
+ // Updates the current configuration
+ check(config, CONFIG_PAT);
+ var options = _.omit(config, "texts", "reCaptcha");
+ this.options = _.defaults(options, this.options);
+
+ // Possibly sets up reCaptcha options
+ var reCaptcha = config.reCaptcha;
+ if (reCaptcha) {
+ // Updates the current button object
+ this.options.reCaptcha = _.defaults(reCaptcha, this.options.reCaptcha || {});
+ }
+
+ // Possibly sets up texts...
+ if (config.texts) {
+ var texts = config.texts;
+ var simpleTexts = _.omit(texts, "button", "errors", "info", "inputIcons", "socialIcons", "title");
+
+ this.texts = _.defaults(simpleTexts, this.texts);
+
+ if (texts.button) {
+ // Updates the current button object
+ this.texts.button = _.defaults(texts.button, this.texts.button);
+ }
+
+ if (texts.errors) {
+ // Updates the current errors object
+ this.texts.errors = _.defaults(texts.errors, this.texts.errors);
+ }
+
+ if (texts.info) {
+ // Updates the current info object
+ this.texts.info = _.defaults(texts.info, this.texts.info);
+ }
+
+ if (texts.inputIcons) {
+ // Updates the current inputIcons object
+ this.texts.inputIcons = _.defaults(texts.inputIcons, this.texts.inputIcons);
+ }
+
+ if (texts.socialIcons) {
+ // Updates the current socialIcons object
+ this.texts.socialIcons = _.defaults(texts.socialIcons, this.texts.socialIcons);
+ }
+
+ if (texts.title) {
+ // Updates the current title object
+ this.texts.title = _.defaults(texts.title, this.texts.title);
+ }
+ }
+};
+
+
+AT.prototype.configureRoute = function(route, options) {
+ console.warn('You now need a routing package like useraccounts:iron-routing or useraccounts:flow-routing to be able to configure routes!');
+};
+
+
+AT.prototype.hasField = function(fieldId) {
+ return !!this.getField(fieldId);
+};
+
+AT.prototype.getField = function(fieldId) {
+ var field = _.filter(this._fields, function(field) {
+ return field._id === fieldId;
+ });
+
+ return (field.length === 1) ? field[0] : undefined;
+};
+
+AT.prototype.getFields = function() {
+ return this._fields;
+};
+
+AT.prototype.getFieldIds = function() {
+ return _.pluck(this._fields, "_id");
+};
+
+AT.prototype.getRoutePath = function(route) {
+ return "#";
+};
+
+AT.prototype.oauthServices = function() {
+ // Extracts names of available services
+ var names;
+
+ if (Meteor.isServer) {
+ names = (Accounts.oauth && Accounts.oauth.serviceNames()) || [];
+ } else {
+ names = (Accounts.oauth && Accounts.loginServicesConfigured() && Accounts.oauth.serviceNames()) || [];
+ }
+ // Extracts names of configured services
+ var configuredServices = [];
+
+ if (Accounts.loginServiceConfiguration) {
+ configuredServices = _.pluck(Accounts.loginServiceConfiguration.find().fetch(), "service");
+ }
+
+ // Builds a list of objects containing service name as _id and its configuration status
+ var services = _.map(names, function(name) {
+ return {
+ _id : name,
+ configured: _.contains(configuredServices, name),
+ };
+ });
+
+ // Checks whether there is a UI to configure services...
+ // XXX: this only works with the accounts-ui package
+ var showUnconfigured = typeof Accounts._loginButtonsSession !== "undefined";
+
+ // Filters out unconfigured services in case they"re not to be displayed
+ if (!showUnconfigured) {
+ services = _.filter(services, function(service) {
+ return service.configured;
+ });
+ }
+
+ // Sorts services by name
+ services = _.sortBy(services, function(service) {
+ return service._id;
+ });
+
+ return services;
+};
+
+AT.prototype.removeField = function(fieldId) {
+ // Fields can be removed only before initialization
+ if (this._initialized) {
+ throw new Error("AccountsTemplates.removeField should strictly be called before AccountsTemplates.init!");
+ }
+ // Tries to look up the field with given _id
+ var index = _.indexOf(_.pluck(this._fields, "_id"), fieldId);
+
+ if (index !== -1) {
+ return this._fields.splice(index, 1)[0];
+ } else if (!(Meteor.isServer && _.contains(this.SPECIAL_FIELDS, fieldId))) {
+ throw new Error("A field called " + fieldId + " does not exist!");
+ }
+};
diff --git a/packages/meteor-useraccounts-core/lib/field.js b/packages/meteor-useraccounts-core/lib/field.js
new file mode 100644
index 00000000..c3ecfbb9
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/field.js
@@ -0,0 +1,292 @@
+// ---------------------------------------------------------------------------------
+// Field object
+// ---------------------------------------------------------------------------------
+
+Field = function(field) {
+ check(field, FIELD_PAT);
+ _.defaults(this, field);
+
+ this.validating = new ReactiveVar(false);
+ this.status = new ReactiveVar(null);
+};
+
+if (Meteor.isClient) {
+ Field.prototype.clearStatus = function() {
+ return this.status.set(null);
+ };
+}
+
+if (Meteor.isServer) {
+ Field.prototype.clearStatus = function() {
+ // Nothing to do server-side
+ return;
+ };
+}
+
+Field.prototype.fixValue = function(value) {
+ if (this.type === "checkbox") {
+ return !!value;
+ }
+
+ if (this.type === "select") {
+ // TODO: something working...
+ return value;
+ }
+
+ if (this.type === "radio") {
+ // TODO: something working...
+ return value;
+ }
+
+ // Possibly applies required transformations to the input value
+ if (this.trim) {
+ value = value.trim();
+ }
+
+ if (this.lowercase) {
+ value = value.toLowerCase();
+ }
+
+ if (this.uppercase) {
+ value = value.toUpperCase();
+ }
+
+ if (!!this.transform) {
+ value = this.transform(value);
+ }
+
+ return value;
+};
+
+if (Meteor.isClient) {
+ Field.prototype.getDisplayName = function(state) {
+ var displayName = this.displayName;
+
+ if (_.isFunction(displayName)) {
+ displayName = displayName();
+ } else if (_.isObject(displayName)) {
+ displayName = displayName[state] || displayName["default"];
+ }
+
+ if (!displayName) {
+ displayName = capitalize(this._id);
+ }
+
+ return displayName;
+ };
+}
+
+if (Meteor.isClient) {
+ Field.prototype.getPlaceholder = function(state) {
+ var placeholder = this.placeholder;
+
+ if (_.isObject(placeholder)) {
+ placeholder = placeholder[state] || placeholder["default"];
+ }
+
+ if (!placeholder) {
+ placeholder = capitalize(this._id);
+ }
+
+ return placeholder;
+ };
+}
+
+Field.prototype.getStatus = function() {
+ return this.status.get();
+};
+
+if (Meteor.isClient) {
+ Field.prototype.getValue = function(templateInstance) {
+ if (this.type === "checkbox") {
+ return !!(templateInstance.$("#at-field-" + this._id + ":checked").val());
+ }
+
+ if (this.type === "radio") {
+ return templateInstance.$("[name=at-field-"+ this._id + "]:checked").val();
+ }
+
+ return templateInstance.$("#at-field-" + this._id).val();
+ };
+}
+
+if (Meteor.isClient) {
+ Field.prototype.hasError = function() {
+ return this.negativeValidation && this.status.get();
+ };
+}
+
+if (Meteor.isClient) {
+ Field.prototype.hasIcon = function() {
+ if (this.showValidating && this.isValidating()) {
+ return true;
+ }
+
+ if (this.negativeFeedback && this.hasError()) {
+ return true;
+ }
+
+ if (this.positiveFeedback && this.hasSuccess()) {
+ return true;
+ }
+ };
+}
+
+if (Meteor.isClient) {
+ Field.prototype.hasSuccess = function() {
+ return this.positiveValidation && this.status.get() === false;
+ };
+}
+
+if (Meteor.isClient)
+ Field.prototype.iconClass = function() {
+ if (this.isValidating()) {
+ return AccountsTemplates.texts.inputIcons["isValidating"];
+ }
+
+ if (this.hasError()) {
+ return AccountsTemplates.texts.inputIcons["hasError"];
+ }
+
+ if (this.hasSuccess()) {
+ return AccountsTemplates.texts.inputIcons["hasSuccess"];
+ }
+ };
+
+if (Meteor.isClient) {
+ Field.prototype.isValidating = function() {
+ return this.validating.get();
+ };
+}
+
+if (Meteor.isClient) {
+ Field.prototype.setError = function(err) {
+ check(err, Match.OneOf(String, undefined, Boolean));
+
+ if (err === false) {
+ return this.status.set(false);
+ }
+
+ return this.status.set(err || true);
+ };
+}
+
+if (Meteor.isServer) {
+ Field.prototype.setError = function(err) {
+ // Nothing to do server-side
+ return;
+ };
+}
+
+if (Meteor.isClient) {
+ Field.prototype.setSuccess = function() {
+ return this.status.set(false);
+ };
+}
+
+if (Meteor.isServer) {
+ Field.prototype.setSuccess = function() {
+ // Nothing to do server-side
+ return;
+ };
+}
+
+if (Meteor.isClient) {
+ Field.prototype.setValidating = function(state) {
+ check(state, Boolean);
+ return this.validating.set(state);
+ };
+}
+
+if (Meteor.isServer) {
+ Field.prototype.setValidating = function(state) {
+ // Nothing to do server-side
+ return;
+ };
+}
+
+if (Meteor.isClient) {
+ Field.prototype.setValue = function(templateInstance, value) {
+ if (this.type === "checkbox") {
+ templateInstance.$("#at-field-" + this._id).prop('checked', true);
+ return;
+ }
+
+ if (this.type === "radio") {
+ templateInstance.$("[name=at-field-"+ this._id + "]").prop('checked', true);
+ return;
+ }
+
+ templateInstance.$("#at-field-" + this._id).val(value);
+ };
+}
+
+Field.prototype.validate = function(value, strict) {
+ check(value, Match.OneOf(undefined, String, Boolean));
+ this.setValidating(true);
+ this.clearStatus();
+
+ if (_.isUndefined(value) || value === '') {
+ if (!!strict) {
+ if (this.required) {
+ this.setError(AccountsTemplates.texts.requiredField);
+ this.setValidating(false);
+
+ return AccountsTemplates.texts.requiredField;
+ } else {
+ this.setSuccess();
+ this.setValidating(false);
+
+ return false;
+ }
+ } else {
+ this.clearStatus();
+ this.setValidating(false);
+
+ return null;
+ }
+ }
+
+ var valueLength = value.length;
+ var minLength = this.minLength;
+ if (minLength && valueLength < minLength) {
+ this.setError(AccountsTemplates.texts.minRequiredLength + ": " + minLength);
+ this.setValidating(false);
+
+ return AccountsTemplates.texts.minRequiredLength + ": " + minLength;
+ }
+
+ var maxLength = this.maxLength;
+ if (maxLength && valueLength > maxLength) {
+ this.setError(AccountsTemplates.texts.maxAllowedLength + ": " + maxLength);
+ this.setValidating(false);
+
+ return AccountsTemplates.texts.maxAllowedLength + ": " + maxLength;
+ }
+
+ if (this.re && valueLength && !value.match(this.re)) {
+ this.setError(this.errStr);
+ this.setValidating(false);
+
+ return this.errStr;
+ }
+
+ if (this.func) {
+ var result = this.func(value);
+ var err = result === true ? this.errStr || true : result;
+
+ if (_.isUndefined(result)) {
+ return err;
+ }
+
+ this.status.set(err);
+ this.setValidating(false);
+
+ return err;
+ }
+
+ this.setSuccess();
+ this.setValidating(false);
+
+ return false;
+};
diff --git a/packages/meteor-useraccounts-core/lib/methods.js b/packages/meteor-useraccounts-core/lib/methods.js
new file mode 100644
index 00000000..0d3a070d
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/methods.js
@@ -0,0 +1,25 @@
+/* global
+ AccountsTemplates: false
+*/
+"use strict";
+
+Meteor.methods({
+ ATRemoveService: function(serviceName) {
+ check(serviceName, String);
+
+ var userId = this.userId;
+
+ if (userId) {
+ var user = Meteor.users.findOne(userId);
+ var numServices = _.keys(user.services).length; // including "resume"
+ var unset = {};
+
+ if (numServices === 2) {
+ throw new Meteor.Error(403, AccountsTemplates.texts.errors.cannotRemoveService, {});
+ }
+
+ unset["services." + serviceName] = "";
+ Meteor.users.update(userId, {$unset: unset});
+ }
+ },
+});
diff --git a/packages/meteor-useraccounts-core/lib/server.js b/packages/meteor-useraccounts-core/lib/server.js
new file mode 100644
index 00000000..2a925dc7
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/server.js
@@ -0,0 +1,184 @@
+/* global
+ AT: false,
+ AccountsTemplates: false
+*/
+"use strict";
+
+// Initialization
+AT.prototype.init = function() {
+ console.warn("[AccountsTemplates] There is no more need to call AccountsTemplates.init()! Simply remove the call ;-)");
+};
+
+AT.prototype._init = function() {
+ if (this._initialized) {
+ return;
+ }
+
+ // Checks there is at least one account service installed
+ if (!Package["accounts-password"] && (!Accounts.oauth || Accounts.oauth.serviceNames().length === 0)) {
+ throw Error("AccountsTemplates: You must add at least one account service!");
+ }
+
+ // A password field is strictly required
+ var password = this.getField("password");
+ if (!password) {
+ throw Error("A password field is strictly required!");
+ }
+
+ if (password.type !== "password") {
+ throw Error("The type of password field should be password!");
+ }
+
+ // Then we can have "username" or "email" or even both of them
+ // but at least one of the two is strictly required
+ var username = this.getField("username");
+ var email = this.getField("email");
+
+ if (!username && !email) {
+ throw Error("At least one field out of username and email is strictly required!");
+ }
+
+ if (username && !username.required) {
+ throw Error("The username field should be required!");
+ }
+
+ if (email) {
+ if (email.type !== "email") {
+ throw Error("The type of email field should be email!");
+ }
+
+ if (username) {
+ // username and email
+ if (username.type !== "text") {
+ throw Error("The type of username field should be text when email field is present!");
+ }
+ } else {
+ // email only
+ if (!email.required) {
+ throw Error("The email field should be required when username is not present!");
+ }
+ }
+ } else {
+ // username only
+ if (username.type !== "text" && username.type !== "tel") {
+ throw Error("The type of username field should be text or tel!");
+ }
+ }
+
+ // Possibly publish more user data in order to be able to show add/remove
+ // buttons for 3rd-party services
+ if (this.options.showAddRemoveServices) {
+ // Publish additional current user info to get the list of registered services
+ // XXX TODO: use
+ // Accounts.addAutopublishFields({
+ // forLoggedInUser: ['services.facebook'],
+ // forOtherUsers: [],
+ // })
+ // ...adds only user.services.*.id
+ Meteor.publish("userRegisteredServices", function() {
+ var userId = this.userId;
+ return Meteor.users.find(userId, {fields: {services: 1}});
+ /*
+ if (userId) {
+ var user = Meteor.users.findOne(userId);
+ var services_id = _.chain(user.services)
+ .keys()
+ .reject(function(service) {return service === "resume";})
+ .map(function(service) {return "services." + service + ".id";})
+ .value();
+ var projection = {};
+ _.each(services_id, function(key) {projection[key] = 1;});
+ return Meteor.users.find(userId, {fields: projection});
+ }
+ */
+ });
+ }
+
+ // Security stuff
+ if (this.options.overrideLoginErrors) {
+ Accounts.validateLoginAttempt(function(attempt) {
+ if (attempt.error) {
+ var reason = attempt.error.reason;
+ if (reason === "User not found" || reason === "Incorrect password") {
+ throw new Meteor.Error(403, AccountsTemplates.texts.errors.loginForbidden);
+ }
+ }
+ return attempt.allowed;
+ });
+ }
+
+ if (this.options.sendVerificationEmail && this.options.enforceEmailVerification) {
+ Accounts.validateLoginAttempt(function(attempt) {
+ if (!attempt.allowed) {
+ return false;
+ }
+
+ if (attempt.type !== "password" || attempt.methodName !== "login") {
+ return attempt.allowed;
+ }
+
+ var user = attempt.user;
+ if (!user) {
+ return attempt.allowed;
+ }
+
+ var ok = true;
+ var loginEmail = attempt.methodArguments[0].user.email.toLowerCase();
+ if (loginEmail) {
+ var email = _.filter(user.emails, function(obj) {
+ return obj.address.toLowerCase() === loginEmail;
+ });
+ if (!email.length || !email[0].verified) {
+ ok = false;
+ }
+ } else {
+ // we got the username, lets check there's at lease one verified email
+ var emailVerified = _.chain(user.emails)
+ .pluck('verified')
+ .any()
+ .value();
+
+ if (!emailVerified) {
+ ok = false;
+ }
+ }
+ if (!ok) {
+ throw new Meteor.Error(401, AccountsTemplates.texts.errors.verifyEmailFirst);
+ }
+
+ return attempt.allowed;
+ });
+ }
+
+ //Check that reCaptcha secret keys are available
+ if (this.options.showReCaptcha) {
+ var atSecretKey = AccountsTemplates.options.reCaptcha && AccountsTemplates.options.reCaptcha.secretKey;
+ var settingsSecretKey = Meteor.settings.reCaptcha && Meteor.settings.reCaptcha.secretKey;
+
+ if (!atSecretKey && !settingsSecretKey) {
+ throw new Meteor.Error(401, "User Accounts: reCaptcha secret key not found! Please provide it or set showReCaptcha to false." );
+ }
+ }
+
+ // Marks AccountsTemplates as initialized
+ this._initialized = true;
+};
+
+AccountsTemplates = new AT();
+
+// Client side account creation is disabled by default:
+// the methos ATCreateUserServer is used instead!
+// to actually disable client side account creation use:
+//
+// AccountsTemplates.config({
+// forbidClientAccountCreation: true
+// });
+
+Accounts.config({
+ forbidClientAccountCreation: true
+});
+
+// Initialization
+Meteor.startup(function() {
+ AccountsTemplates._init();
+});
diff --git a/packages/meteor-useraccounts-core/lib/server_methods.js b/packages/meteor-useraccounts-core/lib/server_methods.js
new file mode 100644
index 00000000..500440d7
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/server_methods.js
@@ -0,0 +1,142 @@
+/* global
+ AccountsTemplates
+*/
+"use strict";
+
+Meteor.methods({
+ ATCreateUserServer: function(options) {
+ if (AccountsTemplates.options.forbidClientAccountCreation) {
+ throw new Meteor.Error(403, AccountsTemplates.texts.errors.accountsCreationDisabled);
+ }
+
+ // createUser() does more checking.
+ check(options, Object);
+ var allFieldIds = AccountsTemplates.getFieldIds();
+
+ // Picks-up whitelisted fields for profile
+ var profile = options.profile;
+ profile = _.pick(profile, allFieldIds);
+ profile = _.omit(profile, "username", "email", "password");
+
+ // Validates fields" value
+ var signupInfo = _.clone(profile);
+ if (options.username) {
+ signupInfo.username = options.username;
+
+ if (AccountsTemplates.options.lowercaseUsername) {
+ signupInfo.username = signupInfo.username.trim().replace(/\s+/gm, ' ');
+ options.profile.name = signupInfo.username;
+ signupInfo.username = signupInfo.username.toLowerCase().replace(/\s+/gm, '');
+ options.username = signupInfo.username;
+ }
+ }
+
+ if (options.email) {
+ signupInfo.email = options.email;
+
+ if (AccountsTemplates.options.lowercaseUsername) {
+ signupInfo.email = signupInfo.email.toLowerCase().replace(/\s+/gm, '');
+ options.email = signupInfo.email;
+ }
+ }
+
+ if (options.password) {
+ signupInfo.password = options.password;
+ }
+
+ var validationErrors = {};
+ var someError = false;
+
+ // Validates fields values
+ _.each(AccountsTemplates.getFields(), function(field) {
+ var fieldId = field._id;
+ var value = signupInfo[fieldId];
+
+ if (fieldId === "password") {
+ // Can"t Pick-up password here
+ // NOTE: at this stage the password is already encripted,
+ // so there is no way to validate it!!!
+ check(value, Object);
+ return;
+ }
+
+ var validationErr = field.validate(value, "strict");
+ if (validationErr) {
+ validationErrors[fieldId] = validationErr;
+ someError = true;
+ }
+ });
+
+ if (AccountsTemplates.options.showReCaptcha) {
+ var secretKey = null;
+
+ if (AccountsTemplates.options.reCaptcha && AccountsTemplates.options.reCaptcha.secretKey) {
+ secretKey = AccountsTemplates.options.reCaptcha.secretKey;
+ } else {
+ secretKey = Meteor.settings.reCaptcha.secretKey;
+ }
+
+ var apiResponse = HTTP.post("https://www.google.com/recaptcha/api/siteverify", {
+ params: {
+ secret: secretKey,
+ response: options.profile.reCaptchaResponse,
+ remoteip: this.connection.clientAddress,
+ }
+ }).data;
+
+ if (!apiResponse.success) {
+ throw new Meteor.Error(403, AccountsTemplates.texts.errors.captchaVerification,
+ apiResponse['error-codes'] ? apiResponse['error-codes'].join(", ") : "Unknown Error.");
+ }
+ }
+
+ if (someError) {
+ throw new Meteor.Error(403, AccountsTemplates.texts.errors.validationErrors, validationErrors);
+ }
+
+ // Possibly removes the profile field
+ if (_.isEmpty(options.profile)) {
+ delete options.profile;
+ }
+
+ // Create user. result contains id and token.
+ var userId = Accounts.createUser(options);
+ // safety belt. createUser is supposed to throw on error. send 500 error
+ // instead of sending a verification email with empty userid.
+ if (! userId) {
+ throw new Error("createUser failed to insert new user");
+ }
+
+ // Call postSignUpHook, if any...
+ var postSignUpHook = AccountsTemplates.options.postSignUpHook;
+ if (postSignUpHook) {
+ postSignUpHook(userId, options);
+ }
+
+ // Send a email address verification email in case the context permits it
+ // and the specific configuration flag was set to true
+ if (options.email && AccountsTemplates.options.sendVerificationEmail) {
+ Accounts.sendVerificationEmail(userId, options.email);
+ }
+ },
+
+ // Resend a user's verification e-mail
+ ATResendVerificationEmail: function (email) {
+ check(email, String);
+
+ var user = Meteor.users.findOne({ "emails.address": email });
+
+ // Send the standard error back to the client if no user exist with this e-mail
+ if (!user) {
+ throw new Meteor.Error(403, "User not found");
+ }
+
+ try {
+ Accounts.sendVerificationEmail(user._id);
+ } catch (error) {
+ // Handle error when email already verified
+ // https://github.com/dwinston/send-verification-email-bug
+ throw new Meteor.Error(403, "Already verified");
+ }
+ },
+});
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_error.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_error.js
new file mode 100644
index 00000000..5673dfe7
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_error.js
@@ -0,0 +1,26 @@
+AT.prototype.atErrorHelpers = {
+ singleError: function() {
+ var errors = AccountsTemplates.state.form.get("error");
+ return errors && errors.length === 1;
+ },
+ error: function() {
+ return AccountsTemplates.state.form.get("error");
+ },
+ errorText: function(){
+ var field, err;
+ if (this.field){
+ field = T9n.get(this.field, markIfMissing=false);
+ err = T9n.get(this.err, markIfMissing=false);
+ }
+ else
+ err = T9n.get(this.valueOf(), markIfMissing=false);
+
+ // Possibly removes initial prefix in case the key in not found inside t9n
+ if (err.substring(0, 15) === "error.accounts.")
+ err = err.substring(15);
+
+ if (field)
+ return field + ": " + err;
+ return err;
+ },
+};
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_form.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_form.js
new file mode 100644
index 00000000..95a34c0c
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_form.js
@@ -0,0 +1,83 @@
+AT.prototype.atFormHelpers = {
+ hide: function(){
+ var state = this.state || AccountsTemplates.getState();
+ return state === "hide";
+ },
+ showTitle: function(next_state){
+ var state = next_state || this.state || AccountsTemplates.getState();
+ if (Meteor.userId() && state === "signIn")
+ return false;
+ return !!AccountsTemplates.texts.title[state];
+ },
+ showOauthServices: function(next_state){
+ var state = next_state || this.state || AccountsTemplates.getState();
+ if (!(state === "signIn" || state === "signUp"))
+ return false;
+ var services = AccountsTemplates.oauthServices();
+ if (!services.length)
+ return false;
+ if (Meteor.userId())
+ return AccountsTemplates.options.showAddRemoveServices;
+ return true;
+ },
+ showServicesSeparator: function(next_state){
+ var pwdService = Package["accounts-password"] !== undefined;
+ var state = next_state || this.state || AccountsTemplates.getState();
+ var rightState = (state === "signIn" || state === "signUp");
+ return rightState && !Meteor.userId() && pwdService && AccountsTemplates.oauthServices().length;
+ },
+ showError: function(next_state) {
+ return !!AccountsTemplates.state.form.get("error");
+ },
+ showResult: function(next_state) {
+ return !!AccountsTemplates.state.form.get("result");
+ },
+ showMessage: function(next_state) {
+ return !!AccountsTemplates.state.form.get("message");
+ },
+ showPwdForm: function(next_state) {
+ if (Package["accounts-password"] === undefined)
+ return false;
+ var state = next_state || this.state || AccountsTemplates.getState();
+ if ((state === "verifyEmail") || (state === "signIn" && Meteor.userId()))
+ return false;
+ return true;
+ },
+ showSignInLink: function(next_state){
+ if (AccountsTemplates.options.hideSignInLink)
+ return false;
+ var state = next_state || this.state || AccountsTemplates.getState();
+ if (AccountsTemplates.options.forbidClientAccountCreation && state === "forgotPwd")
+ return true;
+ return state === "signUp";
+ },
+ showSignUpLink: function(next_state){
+ if (AccountsTemplates.options.hideSignUpLink)
+ return false;
+ var state = next_state || this.state || AccountsTemplates.getState();
+ return ((state === "signIn" && !Meteor.userId()) || state === "forgotPwd") && !AccountsTemplates.options.forbidClientAccountCreation;
+ },
+ showTermsLink: function(next_state){
+ //TODO: Add privacyRoute and termsRoute as alternatives (the point of named routes is
+ // being able to change the url in one place only)
+ if (!!AccountsTemplates.options.privacyUrl || !!AccountsTemplates.options.termsUrl) {
+ var state = next_state || this.state || AccountsTemplates.getState();
+ if (state === "signUp" || state === "enrollAccount" ) {
+ return true;
+ }
+ }
+ /*
+ if (state === "signIn"){
+ var pwdService = Package["accounts-password"] !== undefined;
+ if (!pwdService)
+ return true;
+ }
+ */
+ return false;
+ },
+ showResendVerificationEmailLink: function(){
+ var parentData = Template.currentData();
+ var state = (parentData && parentData.state) || AccountsTemplates.getState();
+ return (state === "signIn" || state === "forgotPwd") && AccountsTemplates.options.showResendVerificationEmailLink;
+ },
+};
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_input.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_input.js
new file mode 100644
index 00000000..fe74eeb1
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_input.js
@@ -0,0 +1,124 @@
+AT.prototype.atInputRendered = [function(){
+ var fieldId = this.data._id;
+
+ var parentData = Template.currentData();
+ var state = (parentData && parentData.state) || AccountsTemplates.getState();
+
+ if (AccountsTemplates.options.focusFirstInput) {
+ var firstVisibleInput = _.find(AccountsTemplates.getFields(), function(f){
+ return _.contains(f.visible, state);
+ });
+
+ if (firstVisibleInput && firstVisibleInput._id === fieldId) {
+ this.$("input#at-field-" + fieldId).focus();
+ }
+ }
+}];
+
+AT.prototype.atInputHelpers = {
+ disabled: function() {
+ return AccountsTemplates.disabled();
+ },
+ showLabels: function() {
+ return AccountsTemplates.options.showLabels;
+ },
+ displayName: function() {
+ var parentData = Template.parentData();
+ var state = (parentData && parentData.state) || AccountsTemplates.getState();
+ var displayName = this.getDisplayName(state);
+ return T9n.get(displayName, markIfMissing=false);
+ },
+ optionalText: function(){
+ return "(" + T9n.get(AccountsTemplates.texts.optionalField, markIfMissing=false) + ")";
+ },
+ templateName: function() {
+ if (this.template)
+ return this.template;
+ if (this.type === "checkbox")
+ return "atCheckboxInput";
+ if (this.type === "select")
+ return "atSelectInput";
+ if (this.type === "radio")
+ return "atRadioInput";
+ if (this.type === "hidden")
+ return "atHiddenInput";
+ return "atTextInput";
+ },
+ values: function(){
+ var id = this._id;
+ return _.map(this.select, function(select){
+ var s = _.clone(select);
+ s._id = id + "-" + select.value;
+ s.id = id;
+ return s;
+ });
+ },
+ errorText: function() {
+ var err = this.getStatus();
+ return T9n.get(err, markIfMissing=false);
+ },
+ placeholder: function() {
+ if (AccountsTemplates.options.showPlaceholders) {
+ var parentData = Template.parentData();
+ var state = (parentData && parentData.state) || AccountsTemplates.getState();
+ var placeholder = this.getPlaceholder(state);
+ return T9n.get(placeholder, markIfMissing=false);
+ }
+ },
+};
+
+AT.prototype.atInputEvents = {
+ "focusin input": function(event, t){
+ var field = Template.currentData();
+ field.clearStatus();
+ },
+ "focusout input, change select": function(event, t){
+ var field = Template.currentData();
+ var fieldId = field._id;
+ var rawValue = field.getValue(t);
+ var value = field.fixValue(rawValue);
+ // Possibly updates the input value
+ if (value !== rawValue) {
+ field.setValue(t, value);
+ }
+
+ // Client-side only validation
+ if (!field.validation)
+ return;
+ var parentData = Template.parentData();
+ var state = (parentData && parentData.state) || AccountsTemplates.getState();
+ // No validation during signIn
+ if (state === "signIn")
+ return;
+ // Special case for password confirmation
+ if (value && fieldId === "password_again"){
+ if (value !== $("#at-field-password").val())
+ return field.setError(AccountsTemplates.texts.errors.pwdMismatch);
+ }
+ field.validate(value);
+ },
+ "keyup input": function(event, t){
+ var field = Template.currentData();
+ // Client-side only continuous validation
+ if (!field.continuousValidation)
+ return;
+ var parentData = Template.parentData();
+ var state = (parentData && parentData.state) || AccountsTemplates.getState();
+ // No validation during signIn
+ if (state === "signIn")
+ return;
+ var fieldId = field._id;
+ var rawValue = field.getValue(t);
+ var value = field.fixValue(rawValue);
+ // Possibly updates the input value
+ if (value !== rawValue) {
+ field.setValue(t, value);
+ }
+ // Special case for password confirmation
+ if (value && fieldId === "password_again"){
+ if (value !== $("#at-field-password").val())
+ return field.setError(AccountsTemplates.texts.errors.pwdMismatch);
+ }
+ field.validate(value);
+ },
+};
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_message.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_message.js
new file mode 100644
index 00000000..baa9ca04
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_message.js
@@ -0,0 +1,7 @@
+AT.prototype.atMessageHelpers = {
+ message: function() {
+ var messageText = AccountsTemplates.state.form.get("message");
+ if (messageText)
+ return T9n.get(messageText, markIfMissing=false);
+ },
+}; \ No newline at end of file
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_nav_button.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_nav_button.js
new file mode 100644
index 00000000..c434060d
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_nav_button.js
@@ -0,0 +1,16 @@
+AT.prototype.atNavButtonHelpers = {
+ text: function(){
+ var key = Meteor.userId() ? AccountsTemplates.texts.navSignOut : AccountsTemplates.texts.navSignIn;
+ return T9n.get(key, markIfMissing=false);
+ }
+};
+
+AT.prototype.atNavButtonEvents = {
+ 'click #at-nav-button': function(event){
+ event.preventDefault();
+ if (Meteor.userId())
+ AccountsTemplates.logout();
+ else
+ AccountsTemplates.linkClick("signIn");
+ },
+};
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_oauth.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_oauth.js
new file mode 100644
index 00000000..1b1d13c1
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_oauth.js
@@ -0,0 +1,5 @@
+AT.prototype.atOauthHelpers = {
+ oauthService: function() {
+ return AccountsTemplates.oauthServices();
+ },
+}; \ No newline at end of file
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_pwd_form.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_pwd_form.js
new file mode 100644
index 00000000..2f8d53c4
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_pwd_form.js
@@ -0,0 +1,331 @@
+AT.prototype.atPwdFormHelpers = {
+ disabled: function() {
+ return AccountsTemplates.disabled();
+ },
+ fields: function() {
+ var parentData = Template.currentData();
+ var state = (parentData && parentData.state) || AccountsTemplates.getState();
+ return _.filter(AccountsTemplates.getFields(), function(s) {
+ return _.contains(s.visible, state);
+ });
+ },
+ showForgotPasswordLink: function() {
+ var parentData = Template.currentData();
+ var state = (parentData && parentData.state) || AccountsTemplates.getState();
+ return state === "signIn" && AccountsTemplates.options.showForgotPasswordLink;
+ },
+ showReCaptcha: function() {
+ var parentData = Template.currentData();
+ var state = (parentData && parentData.state) || AccountsTemplates.getState();
+ return state === "signUp" && AccountsTemplates.options.showReCaptcha;
+ },
+};
+
+
+var toLowercaseUsername = function(value){
+ return value.toLowerCase().replace(/\s+/gm, '');
+};
+
+AT.prototype.atPwdFormEvents = {
+ // Form submit
+ "submit #at-pwd-form": function(event, t) {
+ event.preventDefault();
+ t.$("#at-btn").blur();
+
+ AccountsTemplates.setDisabled(true);
+
+ var parentData = Template.currentData();
+ var state = (parentData && parentData.state) || AccountsTemplates.getState();
+ var preValidation = (state !== "signIn");
+
+ // Client-side pre-validation
+ // Validates fields values
+ // NOTE: This is the only place where password validation can be enforced!
+ var formData = {};
+ var someError = false;
+ var errList = [];
+ _.each(AccountsTemplates.getFields(), function(field){
+ // Considers only visible fields...
+ if (!_.contains(field.visible, state))
+ return;
+
+ var fieldId = field._id;
+
+ var rawValue = field.getValue(t);
+ var value = field.fixValue(rawValue);
+ // Possibly updates the input value
+ if (value !== rawValue) {
+ field.setValue(t, value);
+ }
+ if (value !== undefined && value !== "") {
+ formData[fieldId] = value;
+ }
+
+ // Validates the field value only if current state is not "signIn"
+ if (preValidation && field.getStatus() !== false){
+ var validationErr = field.validate(value, "strict");
+ if (validationErr) {
+ if (field.negativeValidation)
+ field.setError(validationErr);
+ else{
+ var fId = T9n.get(field.getDisplayName(), markIfMissing=false);
+ //errList.push(fId + ": " + err);
+ errList.push({
+ field: field.getDisplayName(),
+ err: validationErr
+ });
+ }
+ someError = true;
+ }
+ else
+ field.setSuccess();
+ }
+ });
+
+ // Clears error and result
+ AccountsTemplates.clearError();
+ AccountsTemplates.clearResult();
+ AccountsTemplates.clearMessage();
+ // Possibly sets errors
+ if (someError){
+ if (errList.length)
+ AccountsTemplates.state.form.set("error", errList);
+ AccountsTemplates.setDisabled(false);
+ //reset reCaptcha form
+ if (state === "signUp" && AccountsTemplates.options.showReCaptcha) {
+ grecaptcha.reset();
+ }
+ return;
+ }
+
+ // Extracts username, email, and pwds
+ var current_password = formData.current_password;
+ var email = formData.email;
+ var password = formData.password;
+ var password_again = formData.password_again;
+ var username = formData.username;
+ var username_and_email = formData.username_and_email;
+ // Clears profile data removing username, email, and pwd
+ delete formData.current_password;
+ delete formData.email;
+ delete formData.password;
+ delete formData.password_again;
+ delete formData.username;
+ delete formData.username_and_email;
+
+ if (AccountsTemplates.options.confirmPassword){
+ // Checks passwords for correct match
+ if (password_again && password !== password_again){
+ var pwd_again = AccountsTemplates.getField("password_again");
+ if (pwd_again.negativeValidation)
+ pwd_again.setError(AccountsTemplates.texts.errors.pwdMismatch);
+ else
+ AccountsTemplates.state.form.set("error", [{
+ field: pwd_again.getDisplayName(),
+ err: AccountsTemplates.texts.errors.pwdMismatch
+ }]);
+ AccountsTemplates.setDisabled(false);
+ //reset reCaptcha form
+ if (state === "signUp" && AccountsTemplates.options.showReCaptcha) {
+ grecaptcha.reset();
+ }
+ return;
+ }
+ }
+
+ // -------
+ // Sign In
+ // -------
+ if (state === "signIn") {
+ var pwdOk = !!password;
+ var userOk = true;
+ var loginSelector;
+ if (email) {
+ if (AccountsTemplates.options.lowercaseUsername) {
+ email = toLowercaseUsername(email);
+ }
+
+ loginSelector = {email: email};
+ }
+ else if (username) {
+ if (AccountsTemplates.options.lowercaseUsername) {
+ username = toLowercaseUsername(username);
+ }
+ loginSelector = {username: username};
+ }
+ else if (username_and_email) {
+ if (AccountsTemplates.options.lowercaseUsername) {
+ username_and_email = toLowercaseUsername(username_and_email);
+ }
+ loginSelector = username_and_email;
+ }
+ else
+ userOk = false;
+
+ // Possibly exits if not both 'password' and 'username' are non-empty...
+ if (!pwdOk || !userOk){
+ AccountsTemplates.state.form.set("error", [AccountsTemplates.texts.errors.loginForbidden]);
+ AccountsTemplates.setDisabled(false);
+ return;
+ }
+
+
+ return Meteor.loginWithPassword(loginSelector, password, function(error) {
+ AccountsTemplates.submitCallback(error, state);
+ });
+ }
+
+ // -------
+ // Sign Up
+ // -------
+ if (state === "signUp") {
+ // Possibly gets reCaptcha response
+ if (AccountsTemplates.options.showReCaptcha) {
+ var response = grecaptcha.getResponse();
+ if (response === "") {
+ // recaptcha verification has not completed yet (or has expired)...
+ // ...simply ignore submit event!
+ AccountsTemplates.setDisabled(false);
+ return;
+ } else {
+ formData.reCaptchaResponse = response;
+ }
+ }
+
+ var hash = Accounts._hashPassword(password);
+ var options = {
+ username: username,
+ email: email,
+ password: hash,
+ profile: formData,
+ };
+
+ // Call preSignUpHook, if any...
+ var preSignUpHook = AccountsTemplates.options.preSignUpHook;
+ if (preSignUpHook) {
+ preSignUpHook(password, options);
+ }
+
+ return Meteor.call("ATCreateUserServer", options, function(error){
+ if (error && error.reason === 'Email already exists.') {
+ if (AccountsTemplates.options.showReCaptcha) {
+ grecaptcha.reset();
+ }
+ }
+ AccountsTemplates.submitCallback(error, undefined, function(){
+ if (AccountsTemplates.options.sendVerificationEmail && AccountsTemplates.options.enforceEmailVerification){
+ AccountsTemplates.submitCallback(error, state, function () {
+ AccountsTemplates.state.form.set("result", AccountsTemplates.texts.info.signUpVerifyEmail);
+ // Cleans up input fields' content
+ _.each(AccountsTemplates.getFields(), function(field){
+ // Considers only visible fields...
+ if (!_.contains(field.visible, state))
+ return;
+
+ var elem = t.$("#at-field-" + field._id);
+
+ // Naïve reset
+ if (field.type === "checkbox") elem.prop('checked', false);
+ else elem.val("");
+
+ });
+ AccountsTemplates.setDisabled(false);
+ AccountsTemplates.avoidRedirect = true;
+ });
+ }
+ else {
+ var loginSelector;
+
+ if (email) {
+ if (AccountsTemplates.options.lowercaseUsername) {
+ email = toLowercaseUsername(email);
+ }
+
+ loginSelector = {email: email};
+ }
+ else if (username) {
+ if (AccountsTemplates.options.lowercaseUsername) {
+ username = toLowercaseUsername(username);
+ }
+ loginSelector = {username: username};
+ }
+ else {
+ if (AccountsTemplates.options.lowercaseUsername) {
+ username_and_email = toLowercaseUsername(username_and_email);
+ }
+ loginSelector = username_and_email;
+ }
+
+ Meteor.loginWithPassword(loginSelector, password, function(error) {
+ AccountsTemplates.submitCallback(error, state, function(){
+ AccountsTemplates.setState("signIn");
+ });
+ });
+ }
+ });
+ });
+ }
+
+ //----------------
+ // Forgot Password
+ //----------------
+ if (state === "forgotPwd"){
+ return Accounts.forgotPassword({
+ email: email
+ }, function(error) {
+ AccountsTemplates.submitCallback(error, state, function(){
+ AccountsTemplates.state.form.set("result", AccountsTemplates.texts.info.emailSent);
+ t.$("#at-field-email").val("");
+ });
+ });
+ }
+
+ //--------------------------------
+ // Reset Password / Enroll Account
+ //--------------------------------
+ if (state === "resetPwd" || state === "enrollAccount") {
+ var paramToken = AccountsTemplates.getparamToken();
+ return Accounts.resetPassword(paramToken, password, function(error) {
+ AccountsTemplates.submitCallback(error, state, function(){
+ var pwd_field_id;
+ if (state === "resetPwd")
+ AccountsTemplates.state.form.set("result", AccountsTemplates.texts.info.pwdReset);
+ else // Enroll Account
+ AccountsTemplates.state.form.set("result", AccountsTemplates.texts.info.pwdSet);
+ t.$("#at-field-password").val("");
+ if (AccountsTemplates.options.confirmPassword)
+ t.$("#at-field-password_again").val("");
+ });
+ });
+ }
+
+ //----------------
+ // Change Password
+ //----------------
+ if (state === "changePwd"){
+ return Accounts.changePassword(current_password, password, function(error) {
+ AccountsTemplates.submitCallback(error, state, function(){
+ AccountsTemplates.state.form.set("result", AccountsTemplates.texts.info.pwdChanged);
+ t.$("#at-field-current_password").val("");
+ t.$("#at-field-password").val("");
+ if (AccountsTemplates.options.confirmPassword)
+ t.$("#at-field-password_again").val("");
+ });
+ });
+ }
+
+ //----------------
+ // Resend Verification E-mail
+ //----------------
+ if (state === "resendVerificationEmail"){
+ return Meteor.call("ATResendVerificationEmail", email, function (error) {
+ AccountsTemplates.submitCallback(error, state, function(){
+ AccountsTemplates.state.form.set("result", AccountsTemplates.texts.info.verificationEmailSent);
+ t.$("#at-field-email").val("");
+
+ AccountsTemplates.avoidRedirect = true;
+ });
+ });
+ }
+ },
+};
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_pwd_form_btn.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_pwd_form_btn.js
new file mode 100644
index 00000000..fc263623
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_pwd_form_btn.js
@@ -0,0 +1,18 @@
+AT.prototype.atPwdFormBtnHelpers = {
+ submitDisabled: function(){
+ var disable = _.chain(AccountsTemplates.getFields())
+ .map(function(field){
+ return field.hasError() || field.isValidating();
+ })
+ .some()
+ .value()
+ ;
+ if (disable)
+ return "disabled";
+ },
+ buttonText: function() {
+ var parentData = Template.currentData();
+ var state = (parentData && parentData.state) || AccountsTemplates.getState();
+ return T9n.get(AccountsTemplates.texts.button[state], markIfMissing=false);
+ },
+};
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_pwd_link.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_pwd_link.js
new file mode 100644
index 00000000..dd93a398
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_pwd_link.js
@@ -0,0 +1,24 @@
+AT.prototype.atPwdLinkHelpers = {
+ disabled: function() {
+ return AccountsTemplates.disabled();
+ },
+ forgotPwdLink: function(){
+ return AccountsTemplates.getRoutePath("forgotPwd");
+ },
+ preText: function(){
+ return T9n.get(AccountsTemplates.texts.pwdLink_pre, markIfMissing=false);
+ },
+ linkText: function(){
+ return T9n.get(AccountsTemplates.texts.pwdLink_link, markIfMissing=false);
+ },
+ suffText: function(){
+ return T9n.get(AccountsTemplates.texts.pwdLink_suff, markIfMissing=false);
+ },
+};
+
+AT.prototype.atPwdLinkEvents = {
+ "click #at-forgotPwd": function(event, t) {
+ event.preventDefault();
+ AccountsTemplates.linkClick("forgotPwd");
+ },
+}; \ No newline at end of file
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_reCaptcha.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_reCaptcha.js
new file mode 100644
index 00000000..ea0c0c69
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_reCaptcha.js
@@ -0,0 +1,19 @@
+AT.prototype.atReCaptchaRendered = function() {
+ $.getScript('//www.google.com/recaptcha/api.js?hl=' + T9n.getLanguage());
+};
+
+AT.prototype.atReCaptchaHelpers = {
+ key: function() {
+ if (AccountsTemplates.options.reCaptcha && AccountsTemplates.options.reCaptcha.siteKey)
+ return AccountsTemplates.options.reCaptcha.siteKey;
+ return Meteor.settings.public.reCaptcha.siteKey;
+ },
+
+ theme: function() {
+ return AccountsTemplates.options.reCaptcha && AccountsTemplates.options.reCaptcha.theme;
+ },
+
+ data_type: function() {
+ return AccountsTemplates.options.reCaptcha && AccountsTemplates.options.reCaptcha.data_type;
+ },
+};
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_resend_verification_email_link.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_resend_verification_email_link.js
new file mode 100644
index 00000000..5587900c
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_resend_verification_email_link.js
@@ -0,0 +1,24 @@
+AT.prototype.atResendVerificationEmailLinkHelpers = {
+ disabled: function () {
+ return AccountsTemplates.disabled();
+ },
+ resendVerificationEmailLink: function () {
+ return AccountsTemplates.getRoutePath("resendVerificationEmail");
+ },
+ preText: function(){
+ return T9n.get(AccountsTemplates.texts.resendVerificationEmailLink_pre, markIfMissing=false);
+ },
+ linkText: function(){
+ return T9n.get(AccountsTemplates.texts.resendVerificationEmailLink_link, markIfMissing=false);
+ },
+ suffText: function(){
+ return T9n.get(AccountsTemplates.texts.resendVerificationEmailLink_suff, markIfMissing=false);
+ },
+};
+
+AT.prototype.atResendVerificationEmailLinkEvents = {
+ "click #at-resend-verification-email": function(event, t) {
+ event.preventDefault();
+ AccountsTemplates.linkClick('resendVerificationEmail');
+ },
+}; \ No newline at end of file
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_result.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_result.js
new file mode 100644
index 00000000..d4b287dd
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_result.js
@@ -0,0 +1,7 @@
+AT.prototype.atResultHelpers = {
+ result: function() {
+ var resultText = AccountsTemplates.state.form.get("result");
+ if (resultText)
+ return T9n.get(resultText, markIfMissing=false);
+ },
+}; \ No newline at end of file
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_sep.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_sep.js
new file mode 100644
index 00000000..7c27557d
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_sep.js
@@ -0,0 +1,5 @@
+AT.prototype.atSepHelpers = {
+ sepText: function(){
+ return T9n.get(AccountsTemplates.texts.sep, markIfMissing=false);
+ },
+}; \ No newline at end of file
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_signin_link.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_signin_link.js
new file mode 100644
index 00000000..14f6e88c
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_signin_link.js
@@ -0,0 +1,24 @@
+AT.prototype.atSigninLinkHelpers = {
+ disabled: function() {
+ return AccountsTemplates.disabled();
+ },
+ signInLink: function(){
+ return AccountsTemplates.getRoutePath("signIn");
+ },
+ preText: function(){
+ return T9n.get(AccountsTemplates.texts.signInLink_pre, markIfMissing=false);
+ },
+ linkText: function(){
+ return T9n.get(AccountsTemplates.texts.signInLink_link, markIfMissing=false);
+ },
+ suffText: function(){
+ return T9n.get(AccountsTemplates.texts.signInLink_suff, markIfMissing=false);
+ },
+};
+
+AT.prototype.atSigninLinkEvents = {
+ "click #at-signIn": function(event, t) {
+ event.preventDefault();
+ AccountsTemplates.linkClick("signIn");
+ },
+};
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_signup_link.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_signup_link.js
new file mode 100644
index 00000000..29c809a4
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_signup_link.js
@@ -0,0 +1,24 @@
+AT.prototype.atSignupLinkHelpers = {
+ disabled: function() {
+ return AccountsTemplates.disabled();
+ },
+ signUpLink: function(){
+ return AccountsTemplates.getRoutePath("signUp");
+ },
+ preText: function(){
+ return T9n.get(AccountsTemplates.texts.signUpLink_pre, markIfMissing=false);
+ },
+ linkText: function(){
+ return T9n.get(AccountsTemplates.texts.signUpLink_link, markIfMissing=false);
+ },
+ suffText: function(){
+ return T9n.get(AccountsTemplates.texts.signUpLink_suff, markIfMissing=false);
+ },
+};
+
+AT.prototype.atSignupLinkEvents = {
+ "click #at-signUp": function(event, t) {
+ event.preventDefault();
+ AccountsTemplates.linkClick('signUp');
+ },
+};
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_social.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_social.js
new file mode 100644
index 00000000..912fd6e9
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_social.js
@@ -0,0 +1,105 @@
+AT.prototype.atSocialHelpers = {
+ disabled: function() {
+ if (AccountsTemplates.disabled())
+ return "disabled";
+ var user = Meteor.user();
+ if (user){
+ var numServices = 0;
+ if (user.services)
+ numServices = _.keys(user.services).length; // including "resume"
+ if (numServices === 2 && user.services[this._id])
+ return "disabled";
+ }
+ },
+ name: function(){
+ return this._id;
+ },
+ iconClass: function() {
+ var ic = AccountsTemplates.texts.socialIcons[this._id];
+ if (!ic)
+ ic = "fa fa-" + this._id;
+ return ic;
+ },
+ buttonText: function() {
+ var service = this;
+ var serviceName = this._id;
+ if (serviceName === "meteor-developer")
+ serviceName = "meteor";
+ serviceName = capitalize(serviceName);
+ if (!service.configured)
+ return T9n.get(AccountsTemplates.texts.socialConfigure, markIfMissing=false) + " " + serviceName;
+ var showAddRemove = AccountsTemplates.options.showAddRemoveServices;
+ var user = Meteor.user();
+ if (user && showAddRemove){
+ if (user.services && user.services[this._id]){
+ var numServices = _.keys(user.services).length; // including "resume"
+ if (numServices === 2)
+ return serviceName;
+ else
+ return T9n.get(AccountsTemplates.texts.socialRemove, markIfMissing=false) + " " + serviceName;
+ } else
+ return T9n.get(AccountsTemplates.texts.socialAdd, markIfMissing=false) + " " + serviceName;
+ }
+ var parentData = Template.parentData();
+ var state = (parentData && parentData.state) || AccountsTemplates.getState();
+ var prefix = state === "signIn" ?
+ T9n.get(AccountsTemplates.texts.socialSignIn, markIfMissing=false) :
+ T9n.get(AccountsTemplates.texts.socialSignUp, markIfMissing=false);
+ return prefix + " " + T9n.get(AccountsTemplates.texts.socialWith, markIfMissing=false) + " " + serviceName;
+ },
+};
+
+AT.prototype.atSocialEvents = {
+ "click button": function(event, t) {
+ event.preventDefault();
+ event.currentTarget.blur();
+ if (AccountsTemplates.disabled())
+ return;
+ var user = Meteor.user();
+ if (user && user.services && user.services[this._id]){
+ var numServices = _.keys(user.services).length; // including "resume"
+ if (numServices === 2)
+ return;
+ else{
+ AccountsTemplates.setDisabled(true);
+ Meteor.call("ATRemoveService", this._id, function(error){
+ AccountsTemplates.setDisabled(false);
+ });
+ }
+ } else {
+ AccountsTemplates.setDisabled(true);
+ var parentData = Template.parentData();
+ var state = (parentData && parentData.state) || AccountsTemplates.getState();
+ var serviceName = this._id;
+ var methodName;
+ if (serviceName === 'meteor-developer')
+ methodName = "loginWithMeteorDeveloperAccount";
+ else
+ methodName = "loginWith" + capitalize(serviceName);
+ var loginWithService = Meteor[methodName];
+ options = {
+ loginStyle: AccountsTemplates.options.socialLoginStyle,
+ };
+ if (Accounts.ui) {
+ if (Accounts.ui._options.requestPermissions[serviceName]) {
+ options.requestPermissions = Accounts.ui._options.requestPermissions[serviceName];
+ }
+ if (Accounts.ui._options.requestOfflineToken[serviceName]) {
+ options.requestOfflineToken = Accounts.ui._options.requestOfflineToken[serviceName];
+ }
+ }
+ loginWithService(options, function(err) {
+ AccountsTemplates.setDisabled(false);
+ if (err && err instanceof Accounts.LoginCancelledError) {
+ // do nothing
+ }
+ else if (err && err instanceof ServiceConfiguration.ConfigError) {
+ if (Accounts._loginButtonsSession)
+ return Accounts._loginButtonsSession.configureService(serviceName);
+ }
+ else
+ AccountsTemplates.submitCallback(err, state);
+ });
+ }
+ },
+};
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_terms_link.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_terms_link.js
new file mode 100644
index 00000000..0ada35cb
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_terms_link.js
@@ -0,0 +1,33 @@
+AT.prototype.atTermsLinkHelpers = {
+ disabled: function() {
+ return AccountsTemplates.disabled();
+ },
+ text: function(){
+ return T9n.get(AccountsTemplates.texts.termsPreamble, markIfMissing=false);
+ },
+ privacyUrl: function(){
+ return AccountsTemplates.options.privacyUrl;
+ },
+ privacyLinkText: function(){
+ return T9n.get(AccountsTemplates.texts.termsPrivacy, markIfMissing=false);
+ },
+ showTermsAnd: function(){
+ return !!AccountsTemplates.options.privacyUrl && !!AccountsTemplates.options.termsUrl;
+ },
+ and: function(){
+ return T9n.get(AccountsTemplates.texts.termsAnd, markIfMissing=false);
+ },
+ termsUrl: function(){
+ return AccountsTemplates.options.termsUrl;
+ },
+ termsLinkText: function(){
+ return T9n.get(AccountsTemplates.texts.termsTerms, markIfMissing=false);
+ },
+};
+
+AT.prototype.atTermsLinkEvents = {
+ "click a": function(event) {
+ if (AccountsTemplates.disabled())
+ event.preventDefault();
+ },
+}; \ No newline at end of file
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/at_title.js b/packages/meteor-useraccounts-core/lib/templates_helpers/at_title.js
new file mode 100644
index 00000000..74f711b9
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/at_title.js
@@ -0,0 +1,7 @@
+AT.prototype.atTitleHelpers = {
+ title: function() {
+ var parentData = Template.currentData();
+ var state = (parentData && parentData.state) || AccountsTemplates.getState();
+ return T9n.get(AccountsTemplates.texts.title[state], markIfMissing = false);
+ },
+};
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/ensure_signed_in.html b/packages/meteor-useraccounts-core/lib/templates_helpers/ensure_signed_in.html
new file mode 100644
index 00000000..08c0d7e3
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/ensure_signed_in.html
@@ -0,0 +1,12 @@
+<!-- Template level auth -->
+<template name="ensureSignedIn">
+ {{#if signedIn}}
+ {{> Template.dynamic template=template}}
+ {{else}}
+ {{#if auth}}
+ {{> Template.dynamic template=auth}}
+ {{else}}
+ {{> fullPageAtForm}}
+ {{/if}}
+ {{/if}}
+</template>
diff --git a/packages/meteor-useraccounts-core/lib/templates_helpers/ensure_signed_in.js b/packages/meteor-useraccounts-core/lib/templates_helpers/ensure_signed_in.js
new file mode 100644
index 00000000..3d947aae
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/templates_helpers/ensure_signed_in.js
@@ -0,0 +1,15 @@
+
+Template.ensureSignedIn.helpers({
+ signedIn: function () {
+ if (!Meteor.user()) {
+ AccountsTemplates.setState(AccountsTemplates.options.defaultState, function(){
+ var err = AccountsTemplates.texts.errors.mustBeLoggedIn;
+ AccountsTemplates.state.form.set('error', [err]);
+ });
+ return false;
+ } else {
+ AccountsTemplates.clearError();
+ return true;
+ }
+ }
+});
diff --git a/packages/meteor-useraccounts-core/lib/utils.js b/packages/meteor-useraccounts-core/lib/utils.js
new file mode 100644
index 00000000..30b108ca
--- /dev/null
+++ b/packages/meteor-useraccounts-core/lib/utils.js
@@ -0,0 +1,19 @@
+capitalize = function(str) {
+ return str.charAt(0).toUpperCase() + str.slice(1);
+};
+
+signedInAs = function() {
+ var user = Meteor.user();
+
+ if (user) {
+ if (user.username) {
+ return user.username;
+ } else if (user.profile && user.profile.name) {
+ return user.profile.name;
+ } else if (user.emails && user.emails[0]) {
+ return user.emails[0].address;
+ } else {
+ return "Signed In";
+ }
+ }
+};
diff --git a/packages/meteor-useraccounts-core/package.js b/packages/meteor-useraccounts-core/package.js
new file mode 100644
index 00000000..f6e0ca32
--- /dev/null
+++ b/packages/meteor-useraccounts-core/package.js
@@ -0,0 +1,94 @@
+'use strict';
+
+Package.describe({
+ summary: 'Meteor sign up and sign in templates core package.',
+ version: '1.14.2',
+ name: 'useraccounts:core',
+ git: 'https://github.com/meteor-useraccounts/core.git',
+});
+
+Package.onUse(function(api) {
+ //api.versionsFrom('METEOR@1.0.3');
+
+ api.use([
+ 'accounts-base',
+ 'check',
+ 'underscore',
+ 'reactive-var',
+ ], ['client', 'server']);
+
+ api.use([
+ 'blaze',
+ 'reactive-dict',
+ 'templating',
+ 'jquery'
+ ], 'client');
+
+ api.use([
+ 'http'
+ ], 'server');
+
+ api.imply([
+ 'accounts-base',
+ 'softwarerero:accounts-t9n@1.3.3',
+ ], ['client', 'server']);
+
+ api.imply([
+ 'templating',
+ ], ['client']);
+
+ api.addFiles([
+ 'lib/field.js',
+ 'lib/core.js',
+ 'lib/server.js',
+ 'lib/methods.js',
+ 'lib/server_methods.js',
+ ], ['server']);
+
+ api.addFiles([
+ 'lib/utils.js',
+ 'lib/field.js',
+ 'lib/core.js',
+ 'lib/client.js',
+ 'lib/templates_helpers/at_error.js',
+ 'lib/templates_helpers/at_form.js',
+ 'lib/templates_helpers/at_input.js',
+ 'lib/templates_helpers/at_nav_button.js',
+ 'lib/templates_helpers/at_oauth.js',
+ 'lib/templates_helpers/at_pwd_form.js',
+ 'lib/templates_helpers/at_pwd_form_btn.js',
+ 'lib/templates_helpers/at_pwd_link.js',
+ 'lib/templates_helpers/at_reCaptcha.js',
+ 'lib/templates_helpers/at_resend_verification_email_link.js',
+ 'lib/templates_helpers/at_result.js',
+ 'lib/templates_helpers/at_sep.js',
+ 'lib/templates_helpers/at_signin_link.js',
+ 'lib/templates_helpers/at_signup_link.js',
+ 'lib/templates_helpers/at_social.js',
+ 'lib/templates_helpers/at_terms_link.js',
+ 'lib/templates_helpers/at_title.js',
+ 'lib/templates_helpers/at_message.js',
+ 'lib/templates_helpers/ensure_signed_in.html',
+ 'lib/templates_helpers/ensure_signed_in.js',
+ 'lib/methods.js',
+ ], ['client']);
+
+ api.export([
+ 'AccountsTemplates',
+ ], ['client', 'server']);
+});
+
+Package.onTest(function(api) {
+ api.use('useraccounts:core@1.14.2');
+
+ api.use([
+ 'accounts-password',
+ 'tinytest',
+ 'test-helpers',
+ 'underscore',
+ ], ['client', 'server']);
+
+ api.addFiles([
+ 'tests/tests.js',
+ ], ['client', 'server']);
+});
diff --git a/packages/meteor-useraccounts-core/tests/tests.js b/packages/meteor-useraccounts-core/tests/tests.js
new file mode 100644
index 00000000..985200a3
--- /dev/null
+++ b/packages/meteor-useraccounts-core/tests/tests.js
@@ -0,0 +1,215 @@
+Tinytest.add("AccountsTemplates - addField/removeField", function(test) {
+ // Calls after AccountsTemplates.init()
+ AccountsTemplates._initialized = true;
+ test.throws(function() {
+ AccountsTemplates.addField("");
+ }, function(err) {
+ if (err instanceof Error && err.message === "AccountsTemplates.addField should strictly be called before AccountsTemplates.init!")
+ return true;
+ });
+ test.throws(function() {
+ AccountsTemplates.removeField("");
+ }, function(err) {
+ if (err instanceof Error && err.message === "AccountsTemplates.removeField should strictly be called before AccountsTemplates.init!")
+ return true;
+ });
+ AccountsTemplates._initialized = false;
+
+ // Trying to remove a non-existing field
+ test.throws(function() {
+ AccountsTemplates.removeField("foo");
+ }, function(err) {
+ if (err instanceof Error && err.message == "A field called foo does not exist!")
+ return true;
+ });
+
+ // Trying to remove an existing field
+ var email = AccountsTemplates.removeField("email");
+ test.isUndefined(AccountsTemplates.getField("email"));
+ // ...and puts it back in for tests re-run
+ AccountsTemplates.addField(email);
+
+ // Trying to add an already existing field
+ test.throws(function() {
+ var pwd = AccountsTemplates.getField("password");
+ AccountsTemplates.addField(pwd);
+ }, function(err) {
+ if (err instanceof Error && err.message == "A field called password already exists!")
+ return true;
+ });
+
+ var login = {
+ _id: "login",
+ displayName: "Email",
+ type: "email"
+ };
+
+ // Successful add
+ AccountsTemplates.addField(login);
+ // ...and removes it for tests re-run
+ AccountsTemplates.removeField("login");
+
+ // Invalid field.type
+ test.throws(function() {
+ AccountsTemplates.addField({
+ _id: "foo",
+ displayName: "Foo",
+ type: "bar"
+ });
+ }, function(err) {
+ if (err instanceof Error && err.message == "field.type is not valid!")
+ return true;
+ });
+
+ // Invalid minLength
+ test.throws(function() {
+ AccountsTemplates.addField({
+ _id: "first-name",
+ displayName: "First Name",
+ type: "text",
+ minLength: 0
+ });
+ }, function(err) {
+ if (err instanceof Error && err.message == "field.minLength should be greater than zero!")
+ return true;
+ });
+ // Invalid maxLength
+ test.throws(function() {
+ AccountsTemplates.addField({
+ _id: "first-name",
+ displayName: "First Name",
+ type: "text",
+ maxLength: 0
+ });
+ }, function(err) {
+ if (err instanceof Error && err.message == "field.maxLength should be greater than zero!")
+ return true;
+ });
+ // maxLength < minLength
+ test.throws(function() {
+ AccountsTemplates.addField({
+ _id: "first-name",
+ displayName: "First Name",
+ type: "text",
+ minLength: 2,
+ maxLength: 1
+ });
+ }, function(err) {
+ if (err instanceof Error && err.message == "field.maxLength should be greater than field.maxLength!")
+ return true;
+ });
+
+ // Successful add
+ var first_name = {
+ _id: "first_name",
+ displayName: "First Name",
+ type: "text",
+ minLength: 2,
+ maxLength: 50,
+ required: true
+ };
+ AccountsTemplates.addField(first_name);
+ // Now removes it to be consistent with tests re-run
+ AccountsTemplates.removeField("first_name");
+});
+
+
+Tinytest.add("AccountsTemplates - addFields", function(test) {
+ // Fake uninitialized state...
+ AccountsTemplates._initialized = false;
+
+ if (Meteor.isClient) {
+ // addFields does not exist client-side
+ test.throws(function() {
+ AccountsTemplates.addFields();
+ });
+ } else {
+ // Not an array of objects
+ test.throws(function() {
+ AccountsTemplates.addFields("");
+ }, function(err) {
+ if (err instanceof Error && err.message === "field argument should be an array of valid field objects!")
+ return true;
+ });
+ test.throws(function() {
+ AccountsTemplates.addFields(100);
+ }, function(err) {
+ if (err instanceof Error && err.message === "field argument should be an array of valid field objects!")
+ return true;
+ });
+ // Empty array
+ test.throws(function() {
+ AccountsTemplates.addFields([]);
+ }, function(err) {
+ if (err instanceof Error && err.message === "field argument should be an array of valid field objects!")
+ return true;
+ });
+
+ // Successful add
+ var first_name = {
+ _id: "first_name",
+ displayName: "First Name",
+ type: "text",
+ minLength: 2,
+ maxLength: 50,
+ required: true
+ };
+ var last_name = {
+ _id: "last_name",
+ displayName: "Last Name",
+ type: "text",
+ minLength: 2,
+ maxLength: 100,
+ required: false
+ };
+ AccountsTemplates.addFields([first_name, last_name]);
+ // Now removes ot to be consistend with tests re-run
+ AccountsTemplates.removeField("first_name");
+ AccountsTemplates.removeField("last_name");
+ }
+ // Restores initialized state...
+ AccountsTemplates._initialized = true;
+});
+
+
+Tinytest.add("AccountsTemplates - setState/getState", function(test) {
+ if (Meteor.isServer) {
+ // getState does not exist server-side
+ test.throws(function() {
+ AccountsTemplates.getState();
+ });
+ // setState does not exist server-side
+ test.throws(function() {
+ AccountsTemplates.setState();
+ });
+ } else {
+ _.each(AccountsTemplates.STATES, function(state){
+ AccountsTemplates.setState(state);
+ test.equal(AccountsTemplates.getState(), state);
+ });
+ // Setting an invalid state should throw a Meteor.Error
+ test.throws(function() {
+ AccountsTemplates.setState("foo");
+ }, function(err) {
+ if (err instanceof Meteor.Error && err.details == "accounts-templates-core package got an invalid state value!")
+ return true;
+ });
+ }
+});
+
+
+// -------------------------------------
+// TODO: complite the following tests...
+// -------------------------------------
+
+
+Tinytest.add("AccountsTemplates - configure", function(test) {
+ if (Meteor.isClient) {
+ // configure does not exist client-side
+ test.throws(function() {
+ AccountsTemplates.configure({});
+ });
+ } else {
+ // TODO: write actual tests...
+ }
+}); \ No newline at end of file