summaryrefslogtreecommitdiffstats
path: root/packages/meteor-useraccounts-core/lib/core.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meteor-useraccounts-core/lib/core.js')
-rw-r--r--packages/meteor-useraccounts-core/lib/core.js593
1 files changed, 593 insertions, 0 deletions
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!");
+ }
+};