wekan/packages/meteor-useraccounts-core/lib/core.js

594 lines
18 KiB
JavaScript
Raw Normal View History

// ---------------------------------------------------------------------------------
// 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!");
}
};