core.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. // ---------------------------------------------------------------------------------
  2. // Patterns for methods" parameters
  3. // ---------------------------------------------------------------------------------
  4. STATE_PAT = {
  5. changePwd: Match.Optional(String),
  6. enrollAccount: Match.Optional(String),
  7. forgotPwd: Match.Optional(String),
  8. resetPwd: Match.Optional(String),
  9. signIn: Match.Optional(String),
  10. signUp: Match.Optional(String),
  11. verifyEmail: Match.Optional(String),
  12. resendVerificationEmail: Match.Optional(String),
  13. };
  14. ERRORS_PAT = {
  15. accountsCreationDisabled: Match.Optional(String),
  16. cannotRemoveService: Match.Optional(String),
  17. captchaVerification: Match.Optional(String),
  18. loginForbidden: Match.Optional(String),
  19. mustBeLoggedIn: Match.Optional(String),
  20. pwdMismatch: Match.Optional(String),
  21. validationErrors: Match.Optional(String),
  22. verifyEmailFirst: Match.Optional(String),
  23. };
  24. INFO_PAT = {
  25. emailSent: Match.Optional(String),
  26. emailVerified: Match.Optional(String),
  27. pwdChanged: Match.Optional(String),
  28. pwdReset: Match.Optional(String),
  29. pwdSet: Match.Optional(String),
  30. signUpVerifyEmail: Match.Optional(String),
  31. verificationEmailSent: Match.Optional(String),
  32. };
  33. INPUT_ICONS_PAT = {
  34. hasError: Match.Optional(String),
  35. hasSuccess: Match.Optional(String),
  36. isValidating: Match.Optional(String),
  37. };
  38. ObjWithStringValues = Match.Where(function (x) {
  39. check(x, Object);
  40. _.each(_.values(x), function(value) {
  41. check(value, String);
  42. });
  43. return true;
  44. });
  45. TEXTS_PAT = {
  46. button: Match.Optional(STATE_PAT),
  47. errors: Match.Optional(ERRORS_PAT),
  48. info: Match.Optional(INFO_PAT),
  49. inputIcons: Match.Optional(INPUT_ICONS_PAT),
  50. maxAllowedLength: Match.Optional(String),
  51. minRequiredLength: Match.Optional(String),
  52. navSignIn: Match.Optional(String),
  53. navSignOut: Match.Optional(String),
  54. optionalField: Match.Optional(String),
  55. pwdLink_link: Match.Optional(String),
  56. pwdLink_pre: Match.Optional(String),
  57. pwdLink_suff: Match.Optional(String),
  58. requiredField: Match.Optional(String),
  59. resendVerificationEmailLink_pre: Match.Optional(String),
  60. resendVerificationEmailLink_link: Match.Optional(String),
  61. resendVerificationEmailLink_suff: Match.Optional(String),
  62. sep: Match.Optional(String),
  63. signInLink_link: Match.Optional(String),
  64. signInLink_pre: Match.Optional(String),
  65. signInLink_suff: Match.Optional(String),
  66. signUpLink_link: Match.Optional(String),
  67. signUpLink_pre: Match.Optional(String),
  68. signUpLink_suff: Match.Optional(String),
  69. socialAdd: Match.Optional(String),
  70. socialConfigure: Match.Optional(String),
  71. socialIcons: Match.Optional(ObjWithStringValues),
  72. socialRemove: Match.Optional(String),
  73. socialSignIn: Match.Optional(String),
  74. socialSignUp: Match.Optional(String),
  75. socialWith: Match.Optional(String),
  76. termsAnd: Match.Optional(String),
  77. termsPreamble: Match.Optional(String),
  78. termsPrivacy: Match.Optional(String),
  79. termsTerms: Match.Optional(String),
  80. title: Match.Optional(STATE_PAT),
  81. };
  82. // Configuration pattern to be checked with check
  83. CONFIG_PAT = {
  84. // Behaviour
  85. confirmPassword: Match.Optional(Boolean),
  86. defaultState: Match.Optional(String),
  87. enablePasswordChange: Match.Optional(Boolean),
  88. enforceEmailVerification: Match.Optional(Boolean),
  89. focusFirstInput: Match.Optional(Boolean),
  90. forbidClientAccountCreation: Match.Optional(Boolean),
  91. lowercaseUsername: Match.Optional(Boolean),
  92. overrideLoginErrors: Match.Optional(Boolean),
  93. sendVerificationEmail: Match.Optional(Boolean),
  94. socialLoginStyle: Match.Optional(Match.OneOf("popup", "redirect")),
  95. // Appearance
  96. defaultLayout: Match.Optional(String),
  97. hideSignInLink: Match.Optional(Boolean),
  98. hideSignUpLink: Match.Optional(Boolean),
  99. showAddRemoveServices: Match.Optional(Boolean),
  100. showForgotPasswordLink: Match.Optional(Boolean),
  101. showResendVerificationEmailLink: Match.Optional(Boolean),
  102. showLabels: Match.Optional(Boolean),
  103. showPlaceholders: Match.Optional(Boolean),
  104. // Client-side Validation
  105. continuousValidation: Match.Optional(Boolean),
  106. negativeFeedback: Match.Optional(Boolean),
  107. negativeValidation: Match.Optional(Boolean),
  108. positiveFeedback: Match.Optional(Boolean),
  109. positiveValidation: Match.Optional(Boolean),
  110. showValidating: Match.Optional(Boolean),
  111. // Privacy Policy and Terms of Use
  112. privacyUrl: Match.Optional(String),
  113. termsUrl: Match.Optional(String),
  114. // Redirects
  115. homeRoutePath: Match.Optional(String),
  116. redirectTimeout: Match.Optional(Number),
  117. // Hooks
  118. onLogoutHook: Match.Optional(Function),
  119. onSubmitHook: Match.Optional(Function),
  120. preSignUpHook: Match.Optional(Function),
  121. postSignUpHook: Match.Optional(Function),
  122. texts: Match.Optional(TEXTS_PAT),
  123. //reCaptcha config
  124. reCaptcha: Match.Optional({
  125. data_type: Match.Optional(Match.OneOf("audio", "image")),
  126. secretKey: Match.Optional(String),
  127. siteKey: Match.Optional(String),
  128. theme: Match.Optional(Match.OneOf("dark", "light")),
  129. }),
  130. showReCaptcha: Match.Optional(Boolean),
  131. };
  132. FIELD_SUB_PAT = {
  133. "default": Match.Optional(String),
  134. changePwd: Match.Optional(String),
  135. enrollAccount: Match.Optional(String),
  136. forgotPwd: Match.Optional(String),
  137. resetPwd: Match.Optional(String),
  138. signIn: Match.Optional(String),
  139. signUp: Match.Optional(String),
  140. };
  141. // Field pattern
  142. FIELD_PAT = {
  143. _id: String,
  144. type: String,
  145. required: Match.Optional(Boolean),
  146. displayName: Match.Optional(Match.OneOf(String, Match.Where(_.isFunction), FIELD_SUB_PAT)),
  147. placeholder: Match.Optional(Match.OneOf(String, FIELD_SUB_PAT)),
  148. select: Match.Optional([{text: String, value: Match.Any}]),
  149. minLength: Match.Optional(Match.Integer),
  150. maxLength: Match.Optional(Match.Integer),
  151. re: Match.Optional(RegExp),
  152. func: Match.Optional(Match.Where(_.isFunction)),
  153. errStr: Match.Optional(String),
  154. // Client-side Validation
  155. continuousValidation: Match.Optional(Boolean),
  156. negativeFeedback: Match.Optional(Boolean),
  157. negativeValidation: Match.Optional(Boolean),
  158. positiveValidation: Match.Optional(Boolean),
  159. positiveFeedback: Match.Optional(Boolean),
  160. // Transforms
  161. trim: Match.Optional(Boolean),
  162. lowercase: Match.Optional(Boolean),
  163. uppercase: Match.Optional(Boolean),
  164. transform: Match.Optional(Match.Where(_.isFunction)),
  165. // Custom options
  166. options: Match.Optional(Object),
  167. template: Match.Optional(String),
  168. };
  169. // -----------------------------------------------------------------------------
  170. // AccountsTemplates object
  171. // -----------------------------------------------------------------------------
  172. // -------------------
  173. // Client/Server stuff
  174. // -------------------
  175. // Constructor
  176. AT = function() {
  177. };
  178. AT.prototype.CONFIG_PAT = CONFIG_PAT;
  179. /*
  180. Each field object is represented by the following properties:
  181. _id: String (required) // A unique field"s id / name
  182. type: String (required) // Displayed input type
  183. required: Boolean (optional) // Specifies Whether to fail or not when field is left empty
  184. displayName: String (optional) // The field"s name to be displayed as a label above the input element
  185. placeholder: String (optional) // The placeholder text to be displayed inside the input element
  186. minLength: Integer (optional) // Possibly specifies the minimum allowed length
  187. maxLength: Integer (optional) // Possibly specifies the maximum allowed length
  188. re: RegExp (optional) // Regular expression for validation
  189. func: Function (optional) // Custom function for validation
  190. errStr: String (optional) // Error message to be displayed in case re validation fails
  191. */
  192. // Allowed input types
  193. AT.prototype.INPUT_TYPES = [
  194. "checkbox",
  195. "email",
  196. "hidden",
  197. "password",
  198. "radio",
  199. "select",
  200. "tel",
  201. "text",
  202. "url",
  203. ];
  204. // Current configuration values
  205. AT.prototype.options = {
  206. // Appearance
  207. //defaultLayout: undefined,
  208. showAddRemoveServices: false,
  209. showForgotPasswordLink: false,
  210. showResendVerificationEmailLink: false,
  211. showLabels: true,
  212. showPlaceholders: true,
  213. // Behaviour
  214. confirmPassword: true,
  215. defaultState: "signIn",
  216. enablePasswordChange: false,
  217. focusFirstInput: !Meteor.isCordova,
  218. forbidClientAccountCreation: false,
  219. lowercaseUsername: false,
  220. overrideLoginErrors: true,
  221. sendVerificationEmail: false,
  222. socialLoginStyle: "popup",
  223. // Client-side Validation
  224. //continuousValidation: false,
  225. //negativeFeedback: false,
  226. //negativeValidation: false,
  227. //positiveValidation: false,
  228. //positiveFeedback: false,
  229. //showValidating: false,
  230. // Privacy Policy and Terms of Use
  231. privacyUrl: undefined,
  232. termsUrl: undefined,
  233. // Hooks
  234. onSubmitHook: undefined,
  235. };
  236. AT.prototype.texts = {
  237. button: {
  238. changePwd: "updateYourPassword",
  239. //enrollAccount: "createAccount",
  240. enrollAccount: "signUp",
  241. forgotPwd: "emailResetLink",
  242. resetPwd: "setPassword",
  243. signIn: "signIn",
  244. signUp: "signUp",
  245. resendVerificationEmail: "Send email again",
  246. },
  247. errors: {
  248. accountsCreationDisabled: "Client side accounts creation is disabled!!!",
  249. cannotRemoveService: "Cannot remove the only active service!",
  250. captchaVerification: "Captcha verification failed!",
  251. loginForbidden: "error.accounts.Login forbidden",
  252. mustBeLoggedIn: "error.accounts.Must be logged in",
  253. pwdMismatch: "error.pwdsDontMatch",
  254. validationErrors: "Validation Errors",
  255. verifyEmailFirst: "Please verify your email first. Check the email and follow the link!",
  256. },
  257. navSignIn: 'signIn',
  258. navSignOut: 'signOut',
  259. info: {
  260. emailSent: "info.emailSent",
  261. emailVerified: "info.emailVerified",
  262. pwdChanged: "info.passwordChanged",
  263. pwdReset: "info.passwordReset",
  264. pwdSet: "Password Set",
  265. signUpVerifyEmail: "Successful Registration! Please check your email and follow the instructions.",
  266. 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.",
  267. },
  268. inputIcons: {
  269. isValidating: "fa fa-spinner fa-spin",
  270. hasSuccess: "fa fa-check",
  271. hasError: "fa fa-times",
  272. },
  273. maxAllowedLength: "Maximum allowed length",
  274. minRequiredLength: "Minimum required length",
  275. optionalField: "optional",
  276. pwdLink_pre: "",
  277. pwdLink_link: "forgotPassword",
  278. pwdLink_suff: "",
  279. requiredField: "Required Field",
  280. resendVerificationEmailLink_pre: "Verification email lost?",
  281. resendVerificationEmailLink_link: "Send again",
  282. resendVerificationEmailLink_suff: "",
  283. sep: "OR",
  284. signInLink_pre: "ifYouAlreadyHaveAnAccount",
  285. signInLink_link: "signin",
  286. signInLink_suff: "",
  287. signUpLink_pre: "dontHaveAnAccount",
  288. signUpLink_link: "signUp",
  289. signUpLink_suff: "",
  290. socialAdd: "add",
  291. socialConfigure: "configure",
  292. socialIcons: {
  293. "meteor-developer": "fa fa-rocket"
  294. },
  295. socialRemove: "remove",
  296. socialSignIn: "signIn",
  297. socialSignUp: "signUp",
  298. socialWith: "with",
  299. termsPreamble: "clickAgree",
  300. termsPrivacy: "privacyPolicy",
  301. termsAnd: "and",
  302. termsTerms: "terms",
  303. title: {
  304. changePwd: "changePassword",
  305. enrollAccount: "createAccount",
  306. forgotPwd: "resetYourPassword",
  307. resetPwd: "resetYourPassword",
  308. signIn: "signIn",
  309. signUp: "createAccount",
  310. verifyEmail: "",
  311. resendVerificationEmail: "Send the verification email again",
  312. },
  313. };
  314. AT.prototype.SPECIAL_FIELDS = [
  315. "password_again",
  316. "username_and_email",
  317. ];
  318. // SignIn / SignUp fields
  319. AT.prototype._fields = [
  320. new Field({
  321. _id: "email",
  322. type: "email",
  323. required: true,
  324. lowercase: true,
  325. trim: true,
  326. func: function(email) {
  327. return !_.contains(email, '@');
  328. },
  329. errStr: 'Invalid email',
  330. }),
  331. new Field({
  332. _id: "password",
  333. type: "password",
  334. required: true,
  335. minLength: 6,
  336. displayName: {
  337. "default": "password",
  338. changePwd: "newPassword",
  339. resetPwd: "newPassword",
  340. },
  341. placeholder: {
  342. "default": "password",
  343. changePwd: "newPassword",
  344. resetPwd: "newPassword",
  345. },
  346. }),
  347. ];
  348. AT.prototype._initialized = false;
  349. // Input type validation
  350. AT.prototype._isValidInputType = function(value) {
  351. return _.indexOf(this.INPUT_TYPES, value) !== -1;
  352. };
  353. AT.prototype.addField = function(field) {
  354. // Fields can be added only before initialization
  355. if (this._initialized) {
  356. throw new Error("AccountsTemplates.addField should strictly be called before AccountsTemplates.init!");
  357. }
  358. field = _.pick(field, _.keys(FIELD_PAT));
  359. check(field, FIELD_PAT);
  360. // Checks there"s currently no field called field._id
  361. if (_.indexOf(_.pluck(this._fields, "_id"), field._id) !== -1) {
  362. throw new Error("A field called " + field._id + " already exists!");
  363. }
  364. // Validates field.type
  365. if (!this._isValidInputType(field.type)) {
  366. throw new Error("field.type is not valid!");
  367. }
  368. // Checks field.minLength is strictly positive
  369. if (typeof field.minLength !== "undefined" && field.minLength <= 0) {
  370. throw new Error("field.minLength should be greater than zero!");
  371. }
  372. // Checks field.maxLength is strictly positive
  373. if (typeof field.maxLength !== "undefined" && field.maxLength <= 0) {
  374. throw new Error("field.maxLength should be greater than zero!");
  375. }
  376. // Checks field.maxLength is greater than field.minLength
  377. if (typeof field.minLength !== "undefined" && typeof field.minLength !== "undefined" && field.maxLength < field.minLength) {
  378. throw new Error("field.maxLength should be greater than field.maxLength!");
  379. }
  380. if (!(Meteor.isServer && _.contains(this.SPECIAL_FIELDS, field._id))) {
  381. this._fields.push(new Field(field));
  382. }
  383. return this._fields;
  384. };
  385. AT.prototype.addFields = function(fields) {
  386. var ok;
  387. try { // don"t bother with `typeof` - just access `length` and `catch`
  388. ok = fields.length > 0 && "0" in Object(fields);
  389. } catch (e) {
  390. throw new Error("field argument should be an array of valid field objects!");
  391. }
  392. if (ok) {
  393. _.map(fields, function(field) {
  394. this.addField(field);
  395. }, this);
  396. } else {
  397. throw new Error("field argument should be an array of valid field objects!");
  398. }
  399. return this._fields;
  400. };
  401. AT.prototype.configure = function(config) {
  402. // Configuration options can be set only before initialization
  403. if (this._initialized) {
  404. throw new Error("Configuration options must be set before AccountsTemplates.init!");
  405. }
  406. // Updates the current configuration
  407. check(config, CONFIG_PAT);
  408. var options = _.omit(config, "texts", "reCaptcha");
  409. this.options = _.defaults(options, this.options);
  410. // Possibly sets up reCaptcha options
  411. var reCaptcha = config.reCaptcha;
  412. if (reCaptcha) {
  413. // Updates the current button object
  414. this.options.reCaptcha = _.defaults(reCaptcha, this.options.reCaptcha || {});
  415. }
  416. // Possibly sets up texts...
  417. if (config.texts) {
  418. var texts = config.texts;
  419. var simpleTexts = _.omit(texts, "button", "errors", "info", "inputIcons", "socialIcons", "title");
  420. this.texts = _.defaults(simpleTexts, this.texts);
  421. if (texts.button) {
  422. // Updates the current button object
  423. this.texts.button = _.defaults(texts.button, this.texts.button);
  424. }
  425. if (texts.errors) {
  426. // Updates the current errors object
  427. this.texts.errors = _.defaults(texts.errors, this.texts.errors);
  428. }
  429. if (texts.info) {
  430. // Updates the current info object
  431. this.texts.info = _.defaults(texts.info, this.texts.info);
  432. }
  433. if (texts.inputIcons) {
  434. // Updates the current inputIcons object
  435. this.texts.inputIcons = _.defaults(texts.inputIcons, this.texts.inputIcons);
  436. }
  437. if (texts.socialIcons) {
  438. // Updates the current socialIcons object
  439. this.texts.socialIcons = _.defaults(texts.socialIcons, this.texts.socialIcons);
  440. }
  441. if (texts.title) {
  442. // Updates the current title object
  443. this.texts.title = _.defaults(texts.title, this.texts.title);
  444. }
  445. }
  446. };
  447. AT.prototype.configureRoute = function(route, options) {
  448. console.warn('You now need a routing package like useraccounts:iron-routing or useraccounts:flow-routing to be able to configure routes!');
  449. };
  450. AT.prototype.hasField = function(fieldId) {
  451. return !!this.getField(fieldId);
  452. };
  453. AT.prototype.getField = function(fieldId) {
  454. var field = _.filter(this._fields, function(field) {
  455. return field._id === fieldId;
  456. });
  457. return (field.length === 1) ? field[0] : undefined;
  458. };
  459. AT.prototype.getFields = function() {
  460. return this._fields;
  461. };
  462. AT.prototype.getFieldIds = function() {
  463. return _.pluck(this._fields, "_id");
  464. };
  465. AT.prototype.getRoutePath = function(route) {
  466. return "#";
  467. };
  468. AT.prototype.oauthServices = function() {
  469. // Extracts names of available services
  470. var names;
  471. if (Meteor.isServer) {
  472. names = (Accounts.oauth && Accounts.oauth.serviceNames()) || [];
  473. } else {
  474. names = (Accounts.oauth && Accounts.loginServicesConfigured() && Accounts.oauth.serviceNames()) || [];
  475. }
  476. // Extracts names of configured services
  477. var configuredServices = [];
  478. if (Accounts.loginServiceConfiguration) {
  479. configuredServices = _.pluck(Accounts.loginServiceConfiguration.find().fetch(), "service");
  480. }
  481. // Builds a list of objects containing service name as _id and its configuration status
  482. var services = _.map(names, function(name) {
  483. return {
  484. _id : name,
  485. configured: _.contains(configuredServices, name),
  486. };
  487. });
  488. // Checks whether there is a UI to configure services...
  489. // XXX: this only works with the accounts-ui package
  490. var showUnconfigured = typeof Accounts._loginButtonsSession !== "undefined";
  491. // Filters out unconfigured services in case they"re not to be displayed
  492. if (!showUnconfigured) {
  493. services = _.filter(services, function(service) {
  494. return service.configured;
  495. });
  496. }
  497. // Sorts services by name
  498. services = _.sortBy(services, function(service) {
  499. return service._id;
  500. });
  501. return services;
  502. };
  503. AT.prototype.removeField = function(fieldId) {
  504. // Fields can be removed only before initialization
  505. if (this._initialized) {
  506. throw new Error("AccountsTemplates.removeField should strictly be called before AccountsTemplates.init!");
  507. }
  508. // Tries to look up the field with given _id
  509. var index = _.indexOf(_.pluck(this._fields, "_id"), fieldId);
  510. if (index !== -1) {
  511. return this._fields.splice(index, 1)[0];
  512. } else if (!(Meteor.isServer && _.contains(this.SPECIAL_FIELDS, fieldId))) {
  513. throw new Error("A field called " + fieldId + " does not exist!");
  514. }
  515. };