mirror of
https://github.com/wekan/wekan.git
synced 2025-09-22 01:50:48 +02:00
593 lines
18 KiB
JavaScript
593 lines
18 KiB
JavaScript
// ---------------------------------------------------------------------------------
|
|
// 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!");
|
|
}
|
|
};
|