Upgrade to Meteor 2.3.4

Thanks to xet7 !
This commit is contained in:
Lauri Ojansivu 2021-06-25 14:11:53 +03:00 committed by Denis Perov
parent e6dc20f6c7
commit 40265144af
316 changed files with 5049 additions and 32549 deletions

View file

@ -3,12 +3,12 @@
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-base@1.4.0
meteor-base@1.5.1
# Build system
ecmascript@0.15.1
standard-minifier-css@1.7.2
standard-minifier-js@2.6.0
ecmascript@0.15.2
standard-minifier-css@1.7.3
standard-minifier-js@2.6.1
mquandalle:jade
coffeescript@2.4.1!
@ -17,23 +17,14 @@ es5-shim@4.8.0
# Collections
aldeed:collection2
wekan-cfs-standard-packages
cottz:publish-relations
dburles:collection-helpers
idmontie:migrations
matb33:collection-hooks
matteodem:easy-search
mongo@1.11.0
mongo@1.12.0
mquandalle:collection-mutations
# Account system
kenton:accounts-sandstorm
service-configuration@1.0.11
useraccounts:unstyled
useraccounts:flow-routing
wekan-ldap
wekan-accounts-cas
wekan-accounts-oidc
# Utilities
check@1.3.1
@ -43,7 +34,6 @@ reactive-dict@1.3.0
session@1.2.0
tracker@1.2.0
underscore@1.0.10
3stack:presence
arillo:flow-router-helpers
audit-argument-checks@1.0.7
kadira:blaze-layout
@ -51,8 +41,7 @@ kadira:dochead
mquandalle:autofocus
ongoworks:speakingurl
raix:handlebar-helpers
tap:i18n
http@1.4.2
http@2.0.0
# UI components
blaze
@ -64,17 +53,11 @@ mquandalle:mousetrap-bindglobal
peerlibrary:blaze-components@=0.15.1
templates:tabs
meteor-autosize
simple:json-routes
rajit:bootstrap3-datepicker
shell-server@0.5.0
simple:rest-accounts-password
useraccounts:core
email@2.0.0
email@2.1.1
horka:swipebox
dynamic-import@0.6.0
accounts-password@1.7.0
wekan-cfs-gridfs
dynamic-import@0.7.1
rzymek:fullcalendar
momentjs:moment@2.22.2
browser-policy-framing@1.1.0
@ -84,13 +67,10 @@ msavin:usercache
coagmano:stylus@1.1.0!
meteorhacks:subs-manager
meteorhacks:picker
lamhieu:unblock
meteorhacks:aggregate@1.3.0
wekan-markdown
konecty:mongo-counter
percolate:synced-cron
wekan-cfs-filesystem
steffo:meteor-accounts-saml
rajit:bootstrap3-datepicker-fi
rajit:bootstrap3-datepicker-ar
rajit:bootstrap3-datepicker-bg
@ -139,10 +119,13 @@ rajit:bootstrap3-datepicker-uk
rajit:bootstrap3-datepicker-vi
rajit:bootstrap3-datepicker-zh-cn
rajit:bootstrap3-datepicker-zh-tw
staringatlights:fast-render
spacebars
easylogic:summernote
pascoual:pdfkit
wekan-accounts-lockout
lmieulet:meteor-coverage
meteortesting:mocha
aldeed:simple-schema
accounts-password@2.0.0
matb33:collection-hooks
simple:json-routes
kadira:flow-router
spacebars

View file

@ -1 +1 @@
METEOR@2.2
METEOR@2.3.4

View file

@ -1,7 +1,5 @@
3stack:presence@1.1.2
accounts-base@1.9.0
accounts-oauth@1.2.0
accounts-password@1.7.1
accounts-base@2.0.1
accounts-password@2.0.0
aldeed:collection2@2.10.0
aldeed:collection2-core@1.2.0
aldeed:schema-deny@1.1.0
@ -11,7 +9,7 @@ allow-deny@1.1.0
arillo:flow-router-helpers@0.5.2
audit-argument-checks@1.0.7
autoupdate@1.7.0
babel-compiler@7.6.1
babel-compiler@7.6.2
babel-runtime@1.5.0
base64@1.0.12
binary-heap@1.0.11
@ -21,31 +19,30 @@ boilerplate-generator@1.7.1
browser-policy-common@1.0.11
browser-policy-framing@1.1.0
caching-compiler@1.2.2
caching-html-compiler@1.2.0
callback-hook@1.3.0
caching-html-compiler@1.2.1
callback-hook@1.3.1
cfs:http-methods@0.0.32
check@1.3.1
chuangbo:cookie@1.1.0
coagmano:stylus@1.1.0
coffeescript@2.4.1
coffeescript-compiler@2.4.1
cottz:publish-relations@2.0.8
dburles:collection-helpers@1.1.0
ddp@1.4.0
ddp-client@2.4.1
ddp-client@2.5.0
ddp-common@1.4.0
ddp-rate-limiter@1.0.9
ddp-server@2.3.3
ddp-rate-limiter@1.1.0
ddp-server@2.4.0
deps@1.0.12
diff-sequence@1.1.1
dynamic-import@0.6.0
dynamic-import@0.7.1
easylogic:summernote@0.8.8
ecmascript@0.15.1
ecmascript@0.15.2
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.11.1
ecmascript-runtime-server@0.10.1
ejson@1.1.1
email@2.0.0
email@2.1.1
es5-shim@4.8.0
fastclick@1.0.13
fetch@0.1.1
@ -55,7 +52,7 @@ horka:swipebox@1.0.2
hot-code-push@1.0.4
html-tools@1.1.2
htmljs@1.1.1
http@1.4.4
http@2.0.0
id-map@1.1.1
idmontie:migrations@1.0.3
inter-process-messaging@0.1.1
@ -63,40 +60,36 @@ jquery@1.11.11
kadira:blaze-layout@2.3.0
kadira:dochead@1.5.0
kadira:flow-router@2.12.1
kenton:accounts-sandstorm@0.7.0
konecty:mongo-counter@0.0.5_3
lamhieu:meteorx@2.1.1
lamhieu:unblock@1.0.0
launch-screen@1.2.1
launch-screen@1.3.0
livedata@1.0.18
lmieulet:meteor-coverage@3.2.0
lmieulet:meteor-coverage@1.1.4
localstorage@1.2.0
logging@1.2.0
matb33:collection-hooks@0.9.1
matb33:collection-hooks@1.1.0
matteodem:easy-search@1.6.4
mdg:validation-error@0.5.1
meteor@1.9.3
meteor-autosize@5.0.1
meteor-base@1.4.0
meteor-base@1.5.1
meteor-platform@1.2.6
meteorhacks:aggregate@1.3.0
meteorhacks:collection-utils@1.2.0
meteorhacks:picker@1.0.3
meteorhacks:subs-manager@1.6.4
meteorspark:util@0.2.0
meteortesting:browser-tests@1.3.4
meteortesting:mocha@2.0.1
meteortesting:mocha-core@8.0.1
meteortesting:browser-tests@0.2.0
meteortesting:mocha@0.6.0
minifier-css@1.5.4
minifier-js@2.6.0
minifier-js@2.6.1
minifiers@1.1.8-faster-rebuild.0
minimongo@1.6.2
minimongo@1.7.0
mobile-status-bar@1.1.0
modern-browsers@0.1.5
modules@0.16.0
modules-runtime@0.12.0
momentjs:moment@2.29.1
mongo@1.11.1
mongo@1.12.0
mongo-decimal@0.1.2
mongo-dev-server@1.1.0
mongo-id@1.0.8
@ -107,24 +100,24 @@ mquandalle:collection-mutations@0.1.0
mquandalle:jade@0.4.9
mquandalle:jade-compiler@0.4.5
mquandalle:jquery-textcomplete@0.8.0_1
mquandalle:jquery-ui-drag-drop-sort@0.2.0
mquandalle:moment@1.0.1
mquandalle:mousetrap-bindglobal@0.0.1
mrt:just-i18n@0.3.0
msavin:usercache@1.8.0
npm-bcrypt@0.9.4
npm-mongo@3.9.0
oauth@1.3.2
oauth2@1.3.0
observe-sequence@1.0.16
npm-mongo@3.9.1
observe-sequence@1.0.19
ongoworks:speakingurl@1.1.0
ordered-dict@1.1.0
pascoual:pdfkit@1.0.7
peerlibrary:assert@0.3.0
peerlibrary:base-component@0.16.0
peerlibrary:base-component@0.17.1
peerlibrary:blaze-components@0.15.1
peerlibrary:computed-field@0.10.0
peerlibrary:reactive-field@0.6.0
percolate:synced-cron@1.3.2
promise@0.11.2
practicalmeteor:mocha-core@1.0.1
promise@0.12.0
raix:eventemitter@0.1.3
raix:handlebar-helpers@0.2.5
rajit:bootstrap3-datepicker@1.7.1_1
@ -183,67 +176,30 @@ reactive-dict@1.3.0
reactive-var@1.0.11
reload@1.3.1
retry@1.1.0
routepolicy@1.1.0
routepolicy@1.1.1
rzymek:fullcalendar@3.8.0
server-render@0.3.1
service-configuration@1.0.11
service-configuration@1.1.0
session@1.2.0
sha@1.0.9
shell-server@0.5.0
simple:authenticate-user-by-token@1.0.1
simple:json-routes@2.1.0
simple:rest-accounts-password@1.1.2
simple:rest-bearer-token-parser@1.0.1
simple:rest-json-error-handler@1.0.1
socket-stream-client@0.3.3
softwarerero:accounts-t9n@1.3.11
socket-stream-client@0.4.0
spacebars@1.2.0
spacebars-compiler@1.2.1
srp@1.1.0
standard-minifier-css@1.7.2
standard-minifier-js@2.6.0
staringatlights:fast-render@3.3.0
staringatlights:inject-data@2.3.0
steffo:meteor-accounts-saml@0.0.18
spacebars-compiler@1.3.0
standard-minifier-css@1.7.3
standard-minifier-js@2.6.1
tap:i18n@1.8.2
templates:tabs@2.3.0
templating@1.4.0
templating@1.4.1
templating-compiler@1.4.1
templating-runtime@1.4.0
templating-tools@1.2.0
templating-runtime@1.5.0
templating-tools@1.2.1
tracker@1.2.0
twbs:bootstrap@3.3.6
ui@1.0.13
underscore@1.0.10
url@1.3.2
useraccounts:core@1.14.2
useraccounts:flow-routing@1.14.2
useraccounts:unstyled@1.14.2
webapp@1.10.1
webapp@1.11.1
webapp-hashing@1.1.0
wekan-accounts-cas@0.1.0
wekan-accounts-lockout@1.0.0
wekan-accounts-oidc@1.0.10
wekan-cfs-access-point@0.1.50
wekan-cfs-base-package@0.0.30
wekan-cfs-collection@0.5.5
wekan-cfs-collection-filters@0.2.4
wekan-cfs-data-man@0.0.6
wekan-cfs-file@0.1.17
wekan-cfs-filesystem@0.1.2
wekan-cfs-gridfs@0.0.34
wekan-cfs-http-methods@0.0.32
wekan-cfs-http-publish@0.0.13
wekan-cfs-power-queue@0.9.11
wekan-cfs-reactive-list@0.0.9
wekan-cfs-reactive-property@0.0.4
wekan-cfs-standard-packages@0.5.10
wekan-cfs-storage-adapter@0.2.4
wekan-cfs-tempstore@0.1.6
wekan-cfs-upload-http@0.0.21
wekan-cfs-worker@0.1.5
wekan-ldap@0.0.2
wekan-markdown@1.0.9
wekan-oidc@1.0.12
yasaricli:slugify@0.0.7
zimme:active-route@2.3.2

View file

@ -1795,6 +1795,7 @@ Template.cardAssigneePopup.helpers({
return user && user.isBoardAdmin() ? 'admin' : 'normal';
},
/*
presenceStatusClassName() {
const user = Users.findOne(this.userId);
const userPresence = presences.findOne({ userId: this.userId });
@ -1804,7 +1805,7 @@ Template.cardAssigneePopup.helpers({
return 'active';
else return 'idle';
},
*/
isCardAssignee() {
const card = Template.parentData();
const cardAssignees = card.getAssignees();

View file

@ -25,6 +25,7 @@ Template.userAvatar.helpers({
return user && user.isBoardAdmin() ? 'admin' : 'normal';
},
/*
presenceStatusClassName() {
const user = Users.findOne(this.userId);
const userPresence = presences.findOne({ userId: this.userId });
@ -34,6 +35,8 @@ Template.userAvatar.helpers({
return 'active';
else return 'idle';
},
*/
});
Template.userAvatarInitials.helpers({

View file

@ -1,7 +0,0 @@
Presence.configure({
state() {
return {
currentBoardId: Session.get('currentBoard'),
};
},
});

View file

@ -1,3 +1,4 @@
/*
const passwordField = AccountsTemplates.removeField('password');
const emailField = AccountsTemplates.removeField('email');
@ -88,3 +89,5 @@ if (Meteor.isServer) {
};
});
}
*/

View file

@ -7,7 +7,7 @@ FlowRouter.triggers.exit([
FlowRouter.route('/', {
name: 'home',
triggersEnter: [AccountsTemplates.ensureSignedIn],
//triggersEnter: [qAccountsTemplates.ensureSignedIn],
action() {
Session.set('currentBoard', null);
Session.set('currentList', null);
@ -31,7 +31,7 @@ FlowRouter.route('/', {
FlowRouter.route('/public', {
name: 'public',
triggersEnter: [AccountsTemplates.ensureSignedIn],
//triggersEnter: [AccountsTemplates.ensureSignedIn],
action() {
Session.set('currentBoard', null);
Session.set('currentList', null);
@ -150,7 +150,7 @@ FlowRouter.route('/b/templates', {
FlowRouter.route('/my-cards', {
name: 'my-cards',
triggersEnter: [AccountsTemplates.ensureSignedIn],
//triggersEnter: [AccountsTemplates.ensureSignedIn],
action() {
Filter.reset();
Session.set('sortBy', '');
@ -170,7 +170,7 @@ FlowRouter.route('/my-cards', {
FlowRouter.route('/due-cards', {
name: 'due-cards',
triggersEnter: [AccountsTemplates.ensureSignedIn],
//triggersEnter: [AccountsTemplates.ensureSignedIn],
action() {
Filter.reset();
Session.set('sortBy', '');
@ -190,7 +190,7 @@ FlowRouter.route('/due-cards', {
FlowRouter.route('/global-search', {
name: 'global-search',
triggersEnter: [AccountsTemplates.ensureSignedIn],
//triggersEnter: [AccountsTemplates.ensureSignedIn],
action() {
Filter.reset();
Session.set('sortBy', '');
@ -236,7 +236,7 @@ FlowRouter.route('/broken-cards', {
FlowRouter.route('/import/:source', {
name: 'import',
triggersEnter: [AccountsTemplates.ensureSignedIn],
//triggersEnter: [AccountsTemplates.ensureSignedIn],
action(params) {
if (Session.get('currentBoard')) {
Session.set('fromBoard', Session.get('currentBoard'));
@ -261,7 +261,7 @@ FlowRouter.route('/import/:source', {
FlowRouter.route('/setting', {
name: 'setting',
triggersEnter: [
AccountsTemplates.ensureSignedIn,
//AccountsTemplates.ensureSignedIn,
() => {
Session.set('currentBoard', null);
Session.set('currentList', null);
@ -286,7 +286,7 @@ FlowRouter.route('/setting', {
FlowRouter.route('/information', {
name: 'information',
triggersEnter: [
AccountsTemplates.ensureSignedIn,
//AccountsTemplates.ensureSignedIn,
() => {
Session.set('currentBoard', null);
Session.set('currentList', null);
@ -310,7 +310,7 @@ FlowRouter.route('/information', {
FlowRouter.route('/people', {
name: 'people',
triggersEnter: [
AccountsTemplates.ensureSignedIn,
//AccountsTemplates.ensureSignedIn,
() => {
Session.set('currentBoard', null);
Session.set('currentList', null);
@ -334,7 +334,7 @@ FlowRouter.route('/people', {
FlowRouter.route('/admin-reports', {
name: 'admin-reports',
triggersEnter: [
AccountsTemplates.ensureSignedIn,
//AccountsTemplates.ensureSignedIn,
() => {
Session.set('currentBoard', null);
Session.set('currentList', null);

View file

@ -1,3 +1,5 @@
/*
export const AttachmentStorage = new Mongo.Collection(
'cfs_gridfs.attachments.files',
);
@ -24,7 +26,7 @@ if (localFSStore) {
const Grid = Npm.require('gridfs-stream');
// calulate the absolute path here, because FS.Store.FileSystem didn't expose the aboslutepath or FS.Store didn't expose api calls :(
let pathname = localFSStore;
/*eslint camelcase: ["error", {allow: ["__meteor_bootstrap__"]}] */
// eslint camelcase: ["error", {allow: ["__meteor_bootstrap__"]}]
if (!pathname && __meteor_bootstrap__ && __meteor_bootstrap__.serverDir) {
pathname = path.join(
@ -180,6 +182,8 @@ Attachments = new FS.Collection('attachments', {
stores: [store],
});
if (Meteor.isServer) {
Meteor.startup(() => {
Attachments.files._ensureIndex({ cardId: 1 });
@ -266,3 +270,4 @@ if (Meteor.isServer) {
}
export default Attachments;
*/

View file

@ -1,3 +1,5 @@
/*
Avatars = new FS.Collection('avatars', {
stores: [new FS.Store.GridFS('avatars')],
filter: {
@ -27,3 +29,4 @@ Avatars.files.before.insert((userId, doc) => {
});
export default Avatars;
*/

View file

@ -1,12 +0,0 @@
if (Meteor.isServer) {
Meteor.startup(() => {
// Date of 7 days ago
let lastWeek = new Date();
lastWeek.setDate(lastWeek.getDate() - 7);
presences.remove({ ttl: { $lte: lastWeek } });
// Create index for serverId that is queried often
presences._collection._ensureIndex({ serverId: -1 });
});
}

66
package-lock.json generated
View file

@ -709,6 +709,13 @@
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
}
}
},
"check-error": {
@ -717,6 +724,11 @@
"integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
"dev": true
},
"clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18="
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -1538,9 +1550,9 @@
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="
},
"esprima": {
"version": "4.0.1",
@ -2048,6 +2060,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash._reinterpolate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
"integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0="
},
"lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
@ -2109,6 +2126,23 @@
"resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz",
"integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g="
},
"lodash.template": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
"integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==",
"requires": {
"lodash._reinterpolate": "^3.0.0",
"lodash.templatesettings": "^4.0.0"
}
},
"lodash.templatesettings": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz",
"integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==",
"requires": {
"lodash._reinterpolate": "^3.0.0"
}
},
"lodash.union": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
@ -2170,6 +2204,14 @@
"stylis": "^4.0.10"
}
},
"message-box": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/message-box/-/message-box-0.2.7.tgz",
"integrity": "sha512-C4ccA5nHb58kTS+pLrgF/JWtr7fAIkHxRDceH7tdy5fMA783nUfbYwZ7H2XLvSeYfcnWIYCig5dWW+icK9X/Ag==",
"requires": {
"lodash.template": "^4.5.0"
}
},
"meteor-node-stubs": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-1.1.0.tgz",
@ -2977,14 +3019,18 @@
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
"optional": true
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"moment-mini": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.24.0.tgz",
"integrity": "sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ=="
},
"mongo-object": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/mongo-object/-/mongo-object-0.1.4.tgz",
"integrity": "sha512-QtYk0gupWEn2+iB+DDRt1L+WbcNYvJRaHdih/dcqthOa1DbnREUGSs2WGcW478GNYpElflo/yybZXu0sTiRXHg=="
},
"mongodb": {
"version": "3.7.3",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz",
@ -3362,6 +3408,16 @@
"object-inspect": "^1.9.0"
}
},
"simpl-schema": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/simpl-schema/-/simpl-schema-1.12.0.tgz",
"integrity": "sha512-lzXC3L8jJbPhNXGR3cjlyIauqqrC5WUJS4O34Ym/wLIvb8K3ZieK+1OfTzs4mBpDc3Y8u53gQFAr1X37DmTcEg==",
"requires": {
"clone": "^2.1.2",
"message-box": "^0.2.7",
"mongo-object": "^0.1.4"
}
},
"sinon": {
"version": "11.1.2",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.2.tgz",

View file

@ -31,6 +31,7 @@
"core-js": "^2.6.12",
"dompurify": "^2.3.0",
"es6-promise": "^4.2.4",
"escape-string-regexp": "^5.0.0",
"exceljs": "^4.2.1",
"fibers": "^5.0.0",
"gridfs-stream": "https://github.com/wekan/gridfs-stream/tarball/master",
@ -42,11 +43,13 @@
"markdown-it": "^12.3.2",
"markdown-it-emoji": "^2.0.0",
"meteor-node-stubs": "^1.1.0",
"moment": "^2.29.1",
"mongodb": "^3.7.3",
"os": "^0.1.2",
"page": "^1.11.5",
"papaparse": "^5.3.1",
"qs": "^6.10.1",
"simpl-schema": "^1.12.0",
"source-map-support": "^0.5.19"
},
"meteor": {

View file

@ -12,11 +12,11 @@ Package.onUse(function (api) {
if(api.versionsFrom) api.versionsFrom('1.8.2');
api.use('templating');
api.use("ecmascript", ['server', 'client']);
api.use("ecmascript", ['server', 'client']);
api.export('Markdown', ['server', 'client']);
api.use('ui', 'client', {weak: true});
api.use('ui', 'client', {weak: true});
api.add_files('src/template-integration.js', 'client');
api.addFiles('src/template-integration.js', 'client');
});

View file

@ -1,2 +0,0 @@
.build*
node_modules/

View file

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014-2019 The Wekan Team
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.

View file

@ -1,88 +0,0 @@
This is a merged repository of useful forks of: atoy40:accounts-cas
===================
([(https://atmospherejs.com/atoy40/accounts-cas](https://atmospherejs.com/atoy40/accounts-cas))
## Essential improvements by ppoulard to atoy40 and xaionaro versions
* Added support of CAS attributes
With this plugin, you can pick CAS attributes : https://github.com/joshchan/node-cas/wiki/CAS-Attributes
Moved to Wekan GitHub org from from https://github.com/ppoulard/meteor-accounts-cas
## Install
```
cd ~site
mkdir packages
cd packages
git clone https://github.com/wekan/meteor-accounts-cas
cd ~site
meteor add wekan:accounts-cas
```
## Usage
Put CAS settings in Meteor.settings (for example using METEOR_SETTINGS env or --settings) like so:
If casVersion is not defined, it will assume you use CAS 1.0. (note by xaionaro: option `casVersion` seems to be just ignored in the code, ATM).
Server side settings:
```
Meteor.settings = {
"cas": {
"baseUrl": "https://cas.example.com/cas",
"autoClose": true,
"validateUrl":"https://cas.example.com/cas/p3/serviceValidate",
"casVersion": 3.0,
"attributes": {
"debug" : true
}
},
}
```
CAS `attributes` settings :
* `attributes`: by default `{}` : all default values below will apply
* * `debug` : by default `false` ; `true` will print to the server console the CAS attribute names to map, the CAS attributes values retrieved, if necessary the new user account created, and finally the user to use
* * `id` : by default, the CAS user is used for the user account, but you can specified another CAS attribute
* * `firstname` : by default `cas:givenName` ; but you can use your own CAS attribute
* * `lastname` : by default `cas:sn` (respectively) ; but you can use your own CAS attribute
* * `fullname` : by default unused, but if you specify your own CAS attribute, it will be used instead of the `firstname` + `lastname`
* * `mail` : by default `cas:mail`
Client side settings:
```
Meteor.settings = {
"public": {
"cas": {
"loginUrl": "https://cas.example.com/login",
"serviceParam": "service",
"popupWidth": 810,
"popupHeight": 610,
"popup": true,
}
}
}
```
`proxyUrl` is not required. Setup [ROOT_URL](http://docs.meteor.com/api/core.html#Meteor-absoluteUrl) environment variable instead.
Then, to start authentication, you have to call the following method from the client (for example in a click handler) :
```
Meteor.loginWithCas([callback]);
```
It must open a popup containing you CAS login form or redirect to the CAS login form (depending on "popup" setting).
If popup is disabled (== false), then it's required to execute `Meteor.initCas([callback])` in `Meteor.startup` of the client side. ATM, `Meteor.initCas()` completes authentication.
## Examples
* [https://devel.mephi.ru/dyokunev/start-mephi-ru](https://devel.mephi.ru/dyokunev/start-mephi-ru)

View file

@ -1,117 +0,0 @@
function addParameterToURL(url, param){
var urlSplit = url.split('?');
return url+(urlSplit.length>0 ? '?':'&') + param;
}
Meteor.initCas = function(callback) {
const casTokenMatch = window.location.href.match(/[?&]casToken=([^&]+)/);
if (casTokenMatch == null) {
return;
}
window.history.pushState('', document.title, window.location.href.replace(/([&?])casToken=[^&]+[&]?/, '$1').replace(/[?&]+$/g, ''));
Accounts.callLoginMethod({
methodArguments: [{ cas: { credentialToken: casTokenMatch[1] } }],
userCallback: function(err){
if (err == null) {
// should we do anything on success?
}
if (callback != null) {
callback(err);
}
}
});
}
Meteor.loginWithCas = function(options, callback) {
var credentialToken = Random.id();
if (!Meteor.settings.public &&
!Meteor.settings.public.cas &&
!Meteor.settings.public.cas.loginUrl) {
return;
}
var settings = Meteor.settings.public.cas;
var backURL = window.location.href.replace('#', '');
if (options != null && options.redirectUrl != null)
backURL = options.redirectUrl;
var serviceURL = addParameterToURL(backURL, 'casToken='+credentialToken);
var loginUrl = settings.loginUrl +
"?" + (settings.serviceParam || "service") + "=" +
encodeURIComponent(serviceURL)
if (settings.popup == false) {
window.location = loginUrl;
return;
}
var popup = openCenteredPopup(
loginUrl,
settings.width || 800,
settings.height || 600
);
var checkPopupOpen = setInterval(function() {
try {
if(popup && popup.document && popup.document.getElementById('popupCanBeClosed')) {
popup.close();
}
// Fix for #328 - added a second test criteria (popup.closed === undefined)
// to humour this Android quirk:
// http://code.google.com/p/android/issues/detail?id=21061
var popupClosed = popup.closed || popup.closed === undefined;
} catch (e) {
// For some unknown reason, IE9 (and others?) sometimes (when
// the popup closes too quickly?) throws "SCRIPT16386: No such
// interface supported" when trying to read 'popup.closed'. Try
// again in 100ms.
return;
}
if (popupClosed) {
clearInterval(checkPopupOpen);
// check auth on server.
Accounts.callLoginMethod({
methodArguments: [{ cas: { credentialToken: credentialToken } }],
userCallback: err => {
// Fix redirect bug after login successfully
if (!err) {
window.location.href = '/';
}
}
});
}
}, 100);
};
var openCenteredPopup = function(url, width, height) {
var screenX = typeof window.screenX !== 'undefined'
? window.screenX : window.screenLeft;
var screenY = typeof window.screenY !== 'undefined'
? window.screenY : window.screenTop;
var outerWidth = typeof window.outerWidth !== 'undefined'
? window.outerWidth : document.body.clientWidth;
var outerHeight = typeof window.outerHeight !== 'undefined'
? window.outerHeight : (document.body.clientHeight - 22);
// XXX what is the 22?
// Use `outerWidth - width` and `outerHeight - height` for help in
// positioning the popup centered relative to the current window
var left = screenX + (outerWidth - width) / 2;
var top = screenY + (outerHeight - height) / 2;
var features = ('width=' + width + ',height=' + height +
',left=' + left + ',top=' + top + ',scrollbars=yes');
var newwindow = window.open(url, '_blank', features);
if (newwindow.focus)
newwindow.focus();
return newwindow;
};

View file

@ -1,71 +0,0 @@
Meteor.loginWithCas = function(callback) {
var credentialToken = Random.id();
if (!Meteor.settings.public &&
!Meteor.settings.public.cas &&
!Meteor.settings.public.cas.loginUrl) {
return;
}
var settings = Meteor.settings.public.cas;
var loginUrl = settings.loginUrl +
"?" + (settings.service || "service") + "=" +
Meteor.absoluteUrl('_cas/') +
credentialToken;
var fail = function (err) {
Meteor._debug("Error from OAuth popup: " + JSON.stringify(err));
};
// When running on an android device, we sometimes see the
// `pageLoaded` callback fire twice for the final page in the OAuth
// popup, even though the page only loads once. This is maybe an
// Android bug or maybe something intentional about how onPageFinished
// works that we don't understand and isn't well-documented.
var oauthFinished = false;
var pageLoaded = function (event) {
if (oauthFinished) {
return;
}
if (event.url.indexOf(Meteor.absoluteUrl('_cas')) === 0) {
oauthFinished = true;
// On iOS, this seems to prevent "Warning: Attempt to dismiss from
// view controller <MainViewController: ...> while a presentation
// or dismiss is in progress". My guess is that the last
// navigation of the OAuth popup is still in progress while we try
// to close the popup. See
// https://issues.apache.org/jira/browse/CB-2285.
//
// XXX Can we make this timeout smaller?
setTimeout(function () {
popup.close();
// check auth on server.
Accounts.callLoginMethod({
methodArguments: [{ cas: { credentialToken: credentialToken } }],
userCallback: callback
});
}, 100);
}
};
var onExit = function () {
popup.removeEventListener('loadstop', pageLoaded);
popup.removeEventListener('loaderror', fail);
popup.removeEventListener('exit', onExit);
};
var popup = window.open(loginUrl, '_blank', 'location=no,hidden=no');
popup.addEventListener('loadstop', pageLoaded);
popup.addEventListener('loaderror', fail);
popup.addEventListener('exit', onExit);
popup.show();
};

View file

@ -1,304 +0,0 @@
"use strict";
const Fiber = Npm.require('fibers');
const https = Npm.require('https');
const url = Npm.require('url');
const xmlParser = Npm.require('xml2js');
// Library
class CAS {
constructor(options) {
options = options || {};
if (!options.validate_url) {
throw new Error('Required CAS option `validateUrl` missing.');
}
if (!options.service) {
throw new Error('Required CAS option `service` missing.');
}
const cas_url = url.parse(options.validate_url);
if (cas_url.protocol != 'https:' ) {
throw new Error('Only https CAS servers are supported.');
} else if (!cas_url.hostname) {
throw new Error('Option `validateUrl` must be a valid url like: https://example.com/cas/serviceValidate');
} else {
this.hostname = cas_url.host;
this.port = 443;// Should be 443 for https
this.validate_path = cas_url.pathname;
}
this.service = options.service;
}
validate(ticket, callback) {
const httparams = {
host: this.hostname,
port: this.port,
path: url.format({
pathname: this.validate_path,
query: {ticket: ticket, service: this.service},
}),
};
https.get(httparams, (res) => {
res.on('error', (e) => {
console.log('error' + e);
callback(e);
});
// Read result
res.setEncoding('utf8');
let response = '';
res.on('data', (chunk) => {
response += chunk;
});
res.on('end', (error) => {
if (error) {
console.log('error callback');
console.log(error);
callback(undefined, false);
} else {
xmlParser.parseString(response, (err, result) => {
if (err) {
console.log('Bad response format.');
callback({message: 'Bad response format. XML could not parse it'});
} else {
if (result['cas:serviceResponse'] == null) {
console.log('Empty response.');
callback({message: 'Empty response.'});
}
if (result['cas:serviceResponse']['cas:authenticationSuccess']) {
const userData = {
id: result['cas:serviceResponse']['cas:authenticationSuccess'][0]['cas:user'][0].toLowerCase(),
};
const attributes = result['cas:serviceResponse']['cas:authenticationSuccess'][0]['cas:attributes'][0];
// Check allowed ldap groups if exist (array only)
// example cas settings : "allowedLdapGroups" : ["wekan", "admin"],
let findedGroup = false;
const allowedLdapGroups = Meteor.settings.cas.allowedLdapGroups || false;
for (const fieldName in attributes) {
if (allowedLdapGroups && fieldName === 'cas:memberOf') {
for (const groups in attributes[fieldName]) {
const str = attributes[fieldName][groups];
if (!Array.isArray(allowedLdapGroups)) {
callback({message: 'Settings "allowedLdapGroups" must be an array'});
}
for (const allowedLdapGroup in allowedLdapGroups) {
if (str.search(`cn=${allowedLdapGroups[allowedLdapGroup]}`) >= 0) {
findedGroup = true;
}
}
}
}
userData[fieldName] = attributes[fieldName][0];
}
if (allowedLdapGroups && !findedGroup) {
callback({message: 'Group not finded.'}, false);
} else {
callback(undefined, true, userData);
}
} else {
callback(undefined, false);
}
}
});
}
});
});
}
}
////// END OF CAS MODULE
let _casCredentialTokens = {};
let _userData = {};
//RoutePolicy.declare('/_cas/', 'network');
// Listen to incoming OAuth http requests
WebApp.connectHandlers.use((req, res, next) => {
// Need to create a Fiber since we're using synchronous http calls and nothing
// else is wrapping this in a fiber automatically
Fiber(() => {
middleware(req, res, next);
}).run();
});
const middleware = (req, res, next) => {
// Make sure to catch any exceptions because otherwise we'd crash
// the runner
try {
urlParsed = url.parse(req.url, true);
// Getting the ticket (if it's defined in GET-params)
// If no ticket, then request will continue down the default
// middlewares.
const query = urlParsed.query;
if (query == null) {
next();
return;
}
const ticket = query.ticket;
if (ticket == null) {
next();
return;
}
const serviceUrl = Meteor.absoluteUrl(urlParsed.href.replace(/^\//g, '')).replace(/([&?])ticket=[^&]+[&]?/g, '$1').replace(/[?&]+$/g, '');
const redirectUrl = serviceUrl;//.replace(/([&?])casToken=[^&]+[&]?/g, '$1').replace(/[?&]+$/g, '');
// get auth token
const credentialToken = query.casToken;
if (!credentialToken) {
end(res, redirectUrl);
return;
}
// validate ticket
casValidate(req, ticket, credentialToken, serviceUrl, () => {
end(res, redirectUrl);
});
} catch (err) {
console.log("account-cas: unexpected error : " + err.message);
end(res, redirectUrl);
}
};
const casValidate = (req, ticket, token, service, callback) => {
// get configuration
if (!Meteor.settings.cas/* || !Meteor.settings.cas.validate*/) {
throw new Error('accounts-cas: unable to get configuration.');
}
const cas = new CAS({
validate_url: Meteor.settings.cas.validateUrl,
service: service,
version: Meteor.settings.cas.casVersion
});
cas.validate(ticket, (err, status, userData) => {
if (err) {
console.log("accounts-cas: error when trying to validate " + err);
console.log(err);
} else {
if (status) {
console.log(`accounts-cas: user validated ${userData.id}
(${JSON.stringify(userData)})`);
_casCredentialTokens[token] = { id: userData.id };
_userData = userData;
} else {
console.log("accounts-cas: unable to validate " + ticket);
}
}
callback();
});
return;
};
/*
* Register a server-side login handle.
* It is call after Accounts.callLoginMethod() is call from client.
*/
Accounts.registerLoginHandler((options) => {
if (!options.cas)
return undefined;
if (!_hasCredential(options.cas.credentialToken)) {
throw new Meteor.Error(Accounts.LoginCancelledError.numericError,
'no matching login attempt found');
}
const result = _retrieveCredential(options.cas.credentialToken);
const attrs = Meteor.settings.cas.attributes || {};
// CAS keys
const fn = attrs.firstname || 'cas:givenName';
const ln = attrs.lastname || 'cas:sn';
const full = attrs.fullname;
const mail = attrs.mail || 'cas:mail'; // or 'email'
const uid = attrs.id || 'id';
if (attrs.debug) {
if (full) {
console.log(`CAS fields : id:"${uid}", fullname:"${full}", mail:"${mail}"`);
} else {
console.log(`CAS fields : id:"${uid}", firstname:"${fn}", lastname:"${ln}", mail:"${mail}"`);
}
}
const name = full ? _userData[full] : _userData[fn] + ' ' + _userData[ln];
// https://docs.meteor.com/api/accounts.html#Meteor-users
options = {
// _id: Meteor.userId()
username: _userData[uid], // Unique name
emails: [
{ address: _userData[mail], verified: true }
],
createdAt: new Date(),
profile: {
// The profile is writable by the user by default.
name: name,
fullname : name,
email : _userData[mail]
},
active: true,
globalRoles: ['user']
};
if (attrs.debug) {
console.log(`CAS response : ${JSON.stringify(result)}`);
}
let user = Meteor.users.findOne({ 'username': options.username });
if (! user) {
if (attrs.debug) {
console.log(`Creating user account ${JSON.stringify(options)}`);
}
const userId = Accounts.insertUserDoc({}, options);
user = Meteor.users.findOne(userId);
}
if (attrs.debug) {
console.log(`Using user account ${JSON.stringify(user)}`);
}
return { userId: user._id };
});
const _hasCredential = (credentialToken) => {
return _.has(_casCredentialTokens, credentialToken);
}
/*
* Retrieve token and delete it to avoid replaying it.
*/
const _retrieveCredential = (credentialToken) => {
const result = _casCredentialTokens[credentialToken];
delete _casCredentialTokens[credentialToken];
return result;
}
const closePopup = (res) => {
if (Meteor.settings.cas && Meteor.settings.cas.popup == false) {
return;
}
res.writeHead(200, {'Content-Type': 'text/html'});
const content = '<html><body><div id="popupCanBeClosed"></div></body></html>';
res.end(content, 'utf-8');
}
const redirect = (res, whereTo) => {
res.writeHead(302, {'Location': whereTo});
const content = '<html><head><meta http-equiv="refresh" content="0; url='+whereTo+'" /></head><body>Redirection to <a href='+whereTo+'>'+whereTo+'</a></body></html>';
res.end(content, 'utf-8');
return
}
const end = (res, whereTo) => {
if (Meteor.settings.cas && Meteor.settings.cas.popup == false) {
redirect(res, whereTo);
} else {
closePopup(res);
}
}

View file

@ -1,29 +0,0 @@
Package.describe({
summary: "CAS support for accounts",
version: "0.1.0",
name: "wekan:accounts-cas",
git: "https://github.com/wekan/meteor-accounts-cas"
});
Package.onUse(function(api) {
api.versionsFrom('METEOR@1.3.5.1');
api.use('routepolicy', 'server');
api.use('webapp', 'server');
api.use('accounts-base', ['client', 'server']);
// Export Accounts (etc) to packages using this one.
api.imply('accounts-base', ['client', 'server']);
api.use('underscore');
api.add_files('cas_client.js', 'web.browser');
api.add_files('cas_client_cordova.js', 'web.cordova');
api.add_files('cas_server.js', 'server');
});
Npm.depends({
xml2js: "0.4.17",
cas: "https://github.com/anrizal/node-cas/tarball/2baed530842e7a437f8f71b9346bcac8e84773cc"
});
Cordova.depends({
'cordova-plugin-inappbrowser': '1.2.0'
});

View file

@ -1,18 +0,0 @@
#.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

View file

@ -1,2 +0,0 @@
.build*
versions.json

View file

@ -1,2 +0,0 @@
client/compatibility
packages

View file

@ -1,132 +0,0 @@
//.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
}
}

View file

@ -1,8 +0,0 @@
sudo: required
language: node_js
node_js:
- "0.10"
before_install:
- "curl -L http://git.io/ejPSng | /bin/sh"
env:
- TEST_COMMAND=meteor

File diff suppressed because it is too large Load diff

View file

@ -1,353 +0,0 @@
## 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

View file

@ -1,20 +0,0 @@
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.

View file

@ -1,104 +0,0 @@
[![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/)

View file

@ -1,464 +0,0 @@
/* 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();
});

View file

@ -1,593 +0,0 @@
// ---------------------------------------------------------------------------------
// 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!");
}
};

View file

@ -1,292 +0,0 @@
// ---------------------------------------------------------------------------------
// 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;
};

View file

@ -1,25 +0,0 @@
/* 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});
}
},
});

View file

@ -1,184 +0,0 @@
/* 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();
});

View file

@ -1,142 +0,0 @@
/* 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");
}
},
});

View file

@ -1,26 +0,0 @@
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;
},
};

View file

@ -1,83 +0,0 @@
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;
},
};

View file

@ -1,124 +0,0 @@
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);
},
};

View file

@ -1,7 +0,0 @@
AT.prototype.atMessageHelpers = {
message: function() {
var messageText = AccountsTemplates.state.form.get("message");
if (messageText)
return T9n.get(messageText, markIfMissing=false);
},
};

View file

@ -1,16 +0,0 @@
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");
},
};

View file

@ -1,5 +0,0 @@
AT.prototype.atOauthHelpers = {
oauthService: function() {
return AccountsTemplates.oauthServices();
},
};

View file

@ -1,331 +0,0 @@
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;
});
});
}
},
};

View file

@ -1,18 +0,0 @@
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);
},
};

View file

@ -1,24 +0,0 @@
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");
},
};

View file

@ -1,19 +0,0 @@
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;
},
};

View file

@ -1,24 +0,0 @@
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');
},
};

View file

@ -1,7 +0,0 @@
AT.prototype.atResultHelpers = {
result: function() {
var resultText = AccountsTemplates.state.form.get("result");
if (resultText)
return T9n.get(resultText, markIfMissing=false);
},
};

View file

@ -1,5 +0,0 @@
AT.prototype.atSepHelpers = {
sepText: function(){
return T9n.get(AccountsTemplates.texts.sep, markIfMissing=false);
},
};

View file

@ -1,24 +0,0 @@
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");
},
};

View file

@ -1,24 +0,0 @@
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');
},
};

View file

@ -1,105 +0,0 @@
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);
});
}
},
};

View file

@ -1,33 +0,0 @@
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();
},
};

View file

@ -1,7 +0,0 @@
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);
},
};

View file

@ -1,12 +0,0 @@
<!-- Template level auth -->
<template name="ensureSignedIn">
{{#if signedIn}}
{{> Template.dynamic template=template}}
{{else}}
{{#if auth}}
{{> Template.dynamic template=auth}}
{{else}}
{{> fullPageAtForm}}
{{/if}}
{{/if}}
</template>

View file

@ -1,15 +0,0 @@
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;
}
}
});

View file

@ -1,19 +0,0 @@
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";
}
}
};

View file

@ -1,94 +0,0 @@
'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']);
});

View file

@ -1,215 +0,0 @@
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...
}
});

View file

@ -1,13 +0,0 @@
Package.describe({
name: 'meteorhacks:meteorx',
summary: 'Proxy for getting another meteorx fork',
version: '1.4.1'
});
Package.onUse((api) => {
api.export('MeteorX');
api.use([
'lamhieu:meteorx',
]);
});

View file

@ -1,88 +0,0 @@
This is a merged repository of useful forks of: atoy40:accounts-cas
===================
([(https://atmospherejs.com/atoy40/accounts-cas](https://atmospherejs.com/atoy40/accounts-cas))
## Essential improvements by ppoulard to atoy40 and xaionaro versions
* Added support of CAS attributes
With this plugin, you can pick CAS attributes : https://github.com/joshchan/node-cas/wiki/CAS-Attributes
Moved to Wekan GitHub org from from https://github.com/ppoulard/meteor-accounts-cas
## Install
```
cd ~site
mkdir packages
cd packages
git clone https://github.com/wekan/meteor-accounts-cas
cd ~site
meteor add wekan:accounts-cas
```
## Usage
Put CAS settings in Meteor.settings (for example using METEOR_SETTINGS env or --settings) like so:
If casVersion is not defined, it will assume you use CAS 1.0. (note by xaionaro: option `casVersion` seems to be just ignored in the code, ATM).
Server side settings:
```
Meteor.settings = {
"cas": {
"baseUrl": "https://cas.example.com/cas",
"autoClose": true,
"validateUrl":"https://cas.example.com/cas/p3/serviceValidate",
"casVersion": 3.0,
"attributes": {
"debug" : true
}
},
}
```
CAS `attributes` settings :
* `attributes`: by default `{}` : all default values below will apply
* * `debug` : by default `false` ; `true` will print to the server console the CAS attribute names to map, the CAS attributes values retrieved, if necessary the new user account created, and finally the user to use
* * `id` : by default, the CAS user is used for the user account, but you can specified another CAS attribute
* * `firstname` : by default `cas:givenName` ; but you can use your own CAS attribute
* * `lastname` : by default `cas:sn` (respectively) ; but you can use your own CAS attribute
* * `fullname` : by default unused, but if you specify your own CAS attribute, it will be used instead of the `firstname` + `lastname`
* * `mail` : by default `cas:mail`
Client side settings:
```
Meteor.settings = {
"public": {
"cas": {
"loginUrl": "https://cas.example.com/login",
"serviceParam": "service",
"popupWidth": 810,
"popupHeight": 610,
"popup": true,
}
}
}
```
`proxyUrl` is not required. Setup [ROOT_URL](http://docs.meteor.com/api/core.html#Meteor-absoluteUrl) environment variable instead.
Then, to start authentication, you have to call the following method from the client (for example in a click handler) :
```
Meteor.loginWithCas([callback]);
```
It must open a popup containing you CAS login form or redirect to the CAS login form (depending on "popup" setting).
If popup is disabled (== false), then it's required to execute `Meteor.initCas([callback])` in `Meteor.startup` of the client side. ATM, `Meteor.initCas()` completes authentication.
## Examples
* [https://devel.mephi.ru/dyokunev/start-mephi-ru](https://devel.mephi.ru/dyokunev/start-mephi-ru)

View file

@ -1,112 +0,0 @@
function addParameterToURL(url, param){
var urlSplit = url.split('?');
return url+(urlSplit.length>0 ? '?':'&') + param;
}
Meteor.initCas = function(callback) {
const casTokenMatch = window.location.href.match(/[?&]casToken=([^&]+)/);
if (casTokenMatch == null) {
return;
}
window.history.pushState('', document.title, window.location.href.replace(/([&?])casToken=[^&]+[&]?/, '$1').replace(/[?&]+$/g, ''));
Accounts.callLoginMethod({
methodArguments: [{ cas: { credentialToken: casTokenMatch[1] } }],
userCallback: function(err){
if (err == null) {
// should we do anything on success?
}
if (callback != null) {
callback(err);
}
}
});
}
Meteor.loginWithCas = function(options, callback) {
var credentialToken = Random.id();
if (!Meteor.settings.public &&
!Meteor.settings.public.cas &&
!Meteor.settings.public.cas.loginUrl) {
return;
}
var settings = Meteor.settings.public.cas;
var backURL = window.location.href.replace('#', '');
if (options != null && options.redirectUrl != null)
backURL = options.redirectUrl;
var serviceURL = addParameterToURL(backURL, 'casToken='+credentialToken);
var loginUrl = settings.loginUrl +
"?" + (settings.serviceParam || "service") + "=" +
encodeURIComponent(serviceURL)
if (settings.popup == false) {
window.location = loginUrl;
return;
}
var popup = openCenteredPopup(
loginUrl,
settings.width || 800,
settings.height || 600
);
var checkPopupOpen = setInterval(function() {
try {
if(popup && popup.document && popup.document.getElementById('popupCanBeClosed')) {
popup.close();
}
// Fix for #328 - added a second test criteria (popup.closed === undefined)
// to humour this Android quirk:
// http://code.google.com/p/android/issues/detail?id=21061
var popupClosed = popup.closed || popup.closed === undefined;
} catch (e) {
// For some unknown reason, IE9 (and others?) sometimes (when
// the popup closes too quickly?) throws "SCRIPT16386: No such
// interface supported" when trying to read 'popup.closed'. Try
// again in 100ms.
return;
}
if (popupClosed) {
clearInterval(checkPopupOpen);
// check auth on server.
Accounts.callLoginMethod({
methodArguments: [{ cas: { credentialToken: credentialToken } }],
userCallback: callback
});
}
}, 100);
};
var openCenteredPopup = function(url, width, height) {
var screenX = typeof window.screenX !== 'undefined'
? window.screenX : window.screenLeft;
var screenY = typeof window.screenY !== 'undefined'
? window.screenY : window.screenTop;
var outerWidth = typeof window.outerWidth !== 'undefined'
? window.outerWidth : document.body.clientWidth;
var outerHeight = typeof window.outerHeight !== 'undefined'
? window.outerHeight : (document.body.clientHeight - 22);
// XXX what is the 22?
// Use `outerWidth - width` and `outerHeight - height` for help in
// positioning the popup centered relative to the current window
var left = screenX + (outerWidth - width) / 2;
var top = screenY + (outerHeight - height) / 2;
var features = ('width=' + width + ',height=' + height +
',left=' + left + ',top=' + top + ',scrollbars=yes');
var newwindow = window.open(url, '_blank', features);
if (newwindow.focus)
newwindow.focus();
return newwindow;
};

View file

@ -1,71 +0,0 @@
Meteor.loginWithCas = function(callback) {
var credentialToken = Random.id();
if (!Meteor.settings.public &&
!Meteor.settings.public.cas &&
!Meteor.settings.public.cas.loginUrl) {
return;
}
var settings = Meteor.settings.public.cas;
var loginUrl = settings.loginUrl +
"?" + (settings.service || "service") + "=" +
Meteor.absoluteUrl('_cas/') +
credentialToken;
var fail = function (err) {
Meteor._debug("Error from OAuth popup: " + JSON.stringify(err));
};
// When running on an android device, we sometimes see the
// `pageLoaded` callback fire twice for the final page in the OAuth
// popup, even though the page only loads once. This is maybe an
// Android bug or maybe something intentional about how onPageFinished
// works that we don't understand and isn't well-documented.
var oauthFinished = false;
var pageLoaded = function (event) {
if (oauthFinished) {
return;
}
if (event.url.indexOf(Meteor.absoluteUrl('_cas')) === 0) {
oauthFinished = true;
// On iOS, this seems to prevent "Warning: Attempt to dismiss from
// view controller <MainViewController: ...> while a presentation
// or dismiss is in progress". My guess is that the last
// navigation of the OAuth popup is still in progress while we try
// to close the popup. See
// https://issues.apache.org/jira/browse/CB-2285.
//
// XXX Can we make this timeout smaller?
setTimeout(function () {
popup.close();
// check auth on server.
Accounts.callLoginMethod({
methodArguments: [{ cas: { credentialToken: credentialToken } }],
userCallback: callback
});
}, 100);
}
};
var onExit = function () {
popup.removeEventListener('loadstop', pageLoaded);
popup.removeEventListener('loaderror', fail);
popup.removeEventListener('exit', onExit);
};
var popup = window.open(loginUrl, '_blank', 'location=no,hidden=no');
popup.addEventListener('loadstop', pageLoaded);
popup.addEventListener('loaderror', fail);
popup.addEventListener('exit', onExit);
popup.show();
};

View file

@ -1,281 +0,0 @@
"use strict";
const Fiber = Npm.require('fibers');
const https = Npm.require('https');
const url = Npm.require('url');
const xmlParser = Npm.require('xml2js');
// Library
class CAS {
constructor(options) {
options = options || {};
if (!options.validate_url) {
throw new Error('Required CAS option `validateUrl` missing.');
}
if (!options.service) {
throw new Error('Required CAS option `service` missing.');
}
const cas_url = url.parse(options.validate_url);
if (cas_url.protocol != 'https:' ) {
throw new Error('Only https CAS servers are supported.');
} else if (!cas_url.hostname) {
throw new Error('Option `validateUrl` must be a valid url like: https://example.com/cas/serviceValidate');
} else {
this.hostname = cas_url.host;
this.port = 443;// Should be 443 for https
this.validate_path = cas_url.pathname;
}
this.service = options.service;
}
validate(ticket, callback) {
const httparams = {
host: this.hostname,
port: this.port,
path: url.format({
pathname: this.validate_path,
query: {ticket: ticket, service: this.service},
}),
};
https.get(httparams, (res) => {
res.on('error', (e) => {
console.log('error' + e);
callback(e);
});
// Read result
res.setEncoding('utf8');
let response = '';
res.on('data', (chunk) => {
response += chunk;
});
res.on('end', (error) => {
if (error) {
console.log('error callback');
console.log(error);
callback(undefined, false);
} else {
xmlParser.parseString(response, (err, result) => {
if (err) {
console.log('Bad response format.');
callback({message: 'Bad response format. XML could not parse it'});
} else {
if (result['cas:serviceResponse'] == null) {
console.log('Empty response.');
callback({message: 'Empty response.'});
}
if (result['cas:serviceResponse']['cas:authenticationSuccess']) {
var userData = {
id: result['cas:serviceResponse']['cas:authenticationSuccess'][0]['cas:user'][0].toLowerCase(),
}
const attributes = result['cas:serviceResponse']['cas:authenticationSuccess'][0]['cas:attributes'][0];
for (var fieldName in attributes) {
userData[fieldName] = attributes[fieldName][0];
};
callback(undefined, true, userData);
} else {
callback(undefined, false);
}
}
});
}
});
});
}
}
////// END OF CAS MODULE
let _casCredentialTokens = {};
let _userData = {};
//RoutePolicy.declare('/_cas/', 'network');
// Listen to incoming OAuth http requests
WebApp.connectHandlers.use((req, res, next) => {
// Need to create a Fiber since we're using synchronous http calls and nothing
// else is wrapping this in a fiber automatically
Fiber(() => {
middleware(req, res, next);
}).run();
});
const middleware = (req, res, next) => {
// Make sure to catch any exceptions because otherwise we'd crash
// the runner
try {
urlParsed = url.parse(req.url, true);
// Getting the ticket (if it's defined in GET-params)
// If no ticket, then request will continue down the default
// middlewares.
const query = urlParsed.query;
if (query == null) {
next();
return;
}
const ticket = query.ticket;
if (ticket == null) {
next();
return;
}
const serviceUrl = Meteor.absoluteUrl(urlParsed.href.replace(/^\//g, '')).replace(/([&?])ticket=[^&]+[&]?/g, '$1').replace(/[?&]+$/g, '');
const redirectUrl = serviceUrl;//.replace(/([&?])casToken=[^&]+[&]?/g, '$1').replace(/[?&]+$/g, '');
// get auth token
const credentialToken = query.casToken;
if (!credentialToken) {
end(res, redirectUrl);
return;
}
// validate ticket
casValidate(req, ticket, credentialToken, serviceUrl, () => {
end(res, redirectUrl);
});
} catch (err) {
console.log("account-cas: unexpected error : " + err.message);
end(res, redirectUrl);
}
};
const casValidate = (req, ticket, token, service, callback) => {
// get configuration
if (!Meteor.settings.cas/* || !Meteor.settings.cas.validate*/) {
throw new Error('accounts-cas: unable to get configuration.');
}
const cas = new CAS({
validate_url: Meteor.settings.cas.validateUrl,
service: service,
version: Meteor.settings.cas.casVersion
});
cas.validate(ticket, (err, status, userData) => {
if (err) {
console.log("accounts-cas: error when trying to validate " + err);
console.log(err);
} else {
if (status) {
console.log(`accounts-cas: user validated ${userData.id}
(${JSON.stringify(userData)})`);
_casCredentialTokens[token] = { id: userData.id };
_userData = userData;
} else {
console.log("accounts-cas: unable to validate " + ticket);
}
}
callback();
});
return;
};
/*
* Register a server-side login handle.
* It is call after Accounts.callLoginMethod() is call from client.
*/
Accounts.registerLoginHandler((options) => {
if (!options.cas)
return undefined;
if (!_hasCredential(options.cas.credentialToken)) {
throw new Meteor.Error(Accounts.LoginCancelledError.numericError,
'no matching login attempt found');
}
const result = _retrieveCredential(options.cas.credentialToken);
const attrs = Meteor.settings.cas.attributes || {};
// CAS keys
const fn = attrs.firstname || 'cas:givenName';
const ln = attrs.lastname || 'cas:sn';
const full = attrs.fullname;
const mail = attrs.mail || 'cas:mail'; // or 'email'
const uid = attrs.id || 'id';
if (attrs.debug) {
if (full) {
console.log(`CAS fields : id:"${uid}", fullname:"${full}", mail:"${mail}"`);
} else {
console.log(`CAS fields : id:"${uid}", firstname:"${fn}", lastname:"${ln}", mail:"${mail}"`);
}
}
const name = full ? _userData[full] : _userData[fn] + ' ' + _userData[ln];
// https://docs.meteor.com/api/accounts.html#Meteor-users
options = {
// _id: Meteor.userId()
username: _userData[uid], // Unique name
emails: [
{ address: _userData[mail], verified: true }
],
createdAt: new Date(),
profile: {
// The profile is writable by the user by default.
name: name,
fullname : name,
email : _userData[mail]
},
active: true,
globalRoles: ['user']
};
if (attrs.debug) {
console.log(`CAS response : ${JSON.stringify(result)}`);
}
let user = Users.findOne({ 'username': options.username });
if (! user) {
if (attrs.debug) {
console.log(`Creating user account ${JSON.stringify(options)}`);
}
const userId = Accounts.insertUserDoc({}, options);
user = Users.findOne(userId);
}
if (attrs.debug) {
console.log(`Using user account ${JSON.stringify(user)}`);
}
return { userId: user._id };
});
const _hasCredential = (credentialToken) => {
return _.has(_casCredentialTokens, credentialToken);
}
/*
* Retrieve token and delete it to avoid replaying it.
*/
const _retrieveCredential = (credentialToken) => {
const result = _casCredentialTokens[credentialToken];
delete _casCredentialTokens[credentialToken];
return result;
}
const closePopup = (res) => {
if (Meteor.settings.cas && Meteor.settings.cas.popup == false) {
return;
}
res.writeHead(200, {'Content-Type': 'text/html'});
const content = '<html><body><div id="popupCanBeClosed"></div></body></html>';
res.end(content, 'utf-8');
}
const redirect = (res, whereTo) => {
res.writeHead(302, {'Location': whereTo});
const content = '<html><head><meta http-equiv="refresh" content="0; url='+whereTo+'" /></head><body>Redirection to <a href='+whereTo+'>'+whereTo+'</a></body></html>';
res.end(content, 'utf-8');
return
}
const end = (res, whereTo) => {
if (Meteor.settings.cas && Meteor.settings.cas.popup == false) {
redirect(res, whereTo);
} else {
closePopup(res);
}
}

View file

@ -1,29 +0,0 @@
Package.describe({
summary: "CAS support for accounts",
version: "0.1.0",
name: "wekan-accounts-cas",
git: "https://github.com/wekan/wekan-accounts-cas"
});
Package.onUse(function(api) {
api.versionsFrom('METEOR@1.3.5.1');
api.use('routepolicy', 'server');
api.use('webapp', 'server');
api.use('accounts-base', ['client', 'server']);
// Export Accounts (etc) to packages using this one.
api.imply('accounts-base', ['client', 'server']);
api.use('underscore');
api.add_files('cas_client.js', 'web.browser');
api.add_files('cas_client_cordova.js', 'web.cordova');
api.add_files('cas_server.js', 'server');
});
Npm.depends({
xml2js: "0.4.17",
cas: "https://github.com/anrizal/node-cas/tarball/2baed530842e7a437f8f71b9346bcac8e84773cc"
});
Cordova.depends({
'cordova-plugin-inappbrowser': '1.2.0'
});

View file

@ -1 +0,0 @@
.versions

View file

@ -1,14 +0,0 @@
Copyright (C) 2016 SWITCH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,75 +0,0 @@
# salleman:accounts-oidc package
A Meteor login service for OpenID Connect (OIDC).
## Installation
meteor add salleman:accounts-oidc
## Usage
`Meteor.loginWithOidc(options, callback)`
* `options` - object containing options, see below (optional)
* `callback` - callback function (optional)
#### Example
```js
Template.myTemplateName.events({
'click #login-button': function() {
Meteor.loginWithOidc();
}
);
```
## Options
These options override service configuration stored in the database.
* `loginStyle`: `redirect` or `popup`
* `redirectUrl`: Where to redirect after successful login. Only used if `loginStyle` is set to `redirect`
## Manual Configuration Setup
You can manually configure this package by upserting the service configuration on startup. First, add the `service-configuration` package:
meteor add service-configuration
### Service Configuration
The following service configuration are available:
* `clientId`: OIDC client identifier
* `secret`: OIDC client shared secret
* `serverUrl`: URL of the OIDC server. e.g. `https://openid.example.org:8443`
* `authorizationEndpoint`: Endpoint of the OIDC authorization service, e.g. `/oidc/authorize`
* `tokenEndpoint`: Endpoint of the OIDC token service, e.g. `/oidc/token`
* `userinfoEndpoint`: Endpoint of the OIDC userinfo service, e.g. `/oidc/userinfo`
* `idTokenWhitelistFields`: A list of fields from IDToken to be added to Meteor.user().services.oidc object
### Project Configuration
Then in your project:
```js
if (Meteor.isServer) {
Meteor.startup(function () {
ServiceConfiguration.configurations.upsert(
{ service: 'oidc' },
{
$set: {
loginStyle: 'redirect',
clientId: 'my-client-id-registered-with-the-oidc-server',
secret: 'my-client-shared-secret',
serverUrl: 'https://openid.example.org',
authorizationEndpoint: '/oidc/authorize',
tokenEndpoint: '/oidc/token',
userinfoEndpoint: '/oidc/userinfo',
idTokenWhitelistFields: []
}
}
);
});
}
```

View file

@ -1,22 +0,0 @@
Accounts.oauth.registerService('oidc');
if (Meteor.isClient) {
Meteor.loginWithOidc = function(options, callback) {
// support a callback without options
if (! callback && typeof options === "function") {
callback = options;
options = null;
}
var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
Oidc.requestCredential(options, credentialRequestCompleteCallback);
};
} else {
Accounts.addAutopublishFields({
// not sure whether the OIDC api can be used from the browser,
// thus not sure if we should be sending access tokens; but we do it
// for all other oauth2 providers, and it may come in handy.
forLoggedInUser: ['services.oidc'],
forOtherUsers: ['services.oidc.id']
});
}

View file

@ -1,3 +0,0 @@
#login-buttons-image-oidc {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACQUlEQVQ4T5WTy2taQRTGv4kSXw0IoYIihCjFmhhfhUqW9a+o0I2LInTRRbtw05V2I9KQuuimi24KXQqChIhQQcQgGGNz0YpvMCG1yL1tGqvBZsKMIIXcjQcOcznnfL+ZOecOEUVx4/Ly8mQ6neqxhKlUKmltbc1Nut2uqJ/bEnJAkiTmEhEEgVqtViiVyjuAP70j/Pj6Htbglzu52WyGdrsNUq1Wqc1mk939+9sHPP7wTVM232g0QMrlMrXb7bIFndgcbAk3ZPP1eh2kVCrRra2tRcFoNEK1WoXf78fg3Rxsfl3H3t4e3G43dnd3wWrMZjNqtRpIsVhcAFKpFPL5PBfF43H8TDj49/2XAvb393F2dgaNRgNKKaLR6ByQz+epw+HAwcEBisUijEYjgsEg1Go1pA9ODtC/+MZFDCKKIo9FIhEIggCSy+Xozs4OYrEY2ChDoRAIIVww/ujhxdrnFTSbTX6Cfr+Pi4sLhMNhnJ6egmSzWepyuZBIJGAwGBAIBLiY2ezTI74qg2UoFIqFr6ys4OrqiveKHB4eckAmk8FgMMD29jZ8Ph8XKj4/5uu/ZyXZKXBAOp2mHo+H/0isD6zDOp0Om5ubsAuvcA+/8ffpkSygUqmApFIp6vV6+b2ZsNfrodVqYTgcwqXtwul04pfhiSzg+PgYJJlMUovFwhvIbHV1lTs70c3NDSaTCa6vr+8A2FvodDr8CmwuepPJtIDIbvdfkInPz89ZRCKFQmFjNBqdjMfjpZ6jVquV1tfX3bcYegI7CyIWlgAAAABJRU5ErkJggg==');
}

View file

@ -1,19 +0,0 @@
Package.describe({
summary: "OpenID Connect (OIDC) for Meteor accounts",
version: "1.0.10",
name: "wekan-accounts-oidc",
git: "https://github.com/wekan/meteor-accounts-oidc.git",
});
Package.onUse(function(api) {
api.use('accounts-base@1.2.0', ['client', 'server']);
// Export Accounts (etc) to packages using this one.
api.imply('accounts-base', ['client', 'server']);
api.use('accounts-oauth@1.1.0', ['client', 'server']);
api.use('wekan-oidc@1.0.10', ['client', 'server']);
api.addFiles('oidc_login_button.css', 'client');
api.addFiles('oidc.js');
});

View file

@ -1,5 +0,0 @@
language: node_js
node_js:
- "0.10"
before_install:
- "curl -L http://git.io/s0Zu-w | /bin/sh"

View file

@ -1,288 +0,0 @@
# Changelog
## [v0.1.50] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.46)
#### 21/1/19 by Harry Adel
- Bump to version 0.1.50
- *Merged pull-request:* "filename conversion for FS.HTTP.Handlers.Get" [#9](https://github.com/zcfs/Meteor-CollectionFS/pull/994) ([yatusiter](https://github.com/yatusiter))
## [v0.1.46] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.46)
#### 30/3/15 by Eric Dobbertin
- Bump to version 0.1.46
- *Merged pull-request:* [#611](https://github.com/zcfs/Meteor-CollectionFS/issues/611)
- Exposed request handlers on `FS.HTTP.Handlers` object so that app can override
## [v0.1.43] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.43)
#### 20/12/14 by Morten Henriksen
- add changelog
- Bump to version 0.1.43
- *Fixed bug:* "Doesn't work in IE 8" [#10](https://github.com/zcfs/Meteor-cfs-access-point/issues/10)
- *Merged pull-request:* "rootUrlPathPrefix fix for cordova" [#9](https://github.com/zcfs/Meteor-cfs-access-point/issues/9) ([dmitriyles](https://github.com/dmitriyles))
- *Merged pull-request:* "Support for expiration token" [#1](https://github.com/zcfs/Meteor-cfs-access-point/issues/1) ([tanis2000](https://github.com/tanis2000))
Patches by GitHub users [@dmitriyles](https://github.com/dmitriyles), [@tanis2000](https://github.com/tanis2000).
## [v0.1.42] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.42)
#### 17/12/14 by Morten Henriksen
## [v0.1.41] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.41)
#### 17/12/14 by Morten Henriksen
- mbr update, remove versions.json
- Cordova rootUrlPathPrefix fix
- Bump to version 0.1.41
## [v0.1.40] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.40)
#### 17/12/14 by Morten Henriksen
- mbr fixed warnings
- fixes to GET handler
- add back tests
- support apps in server subdirectories; closes #8
- 0.9.1 support
## [v0.0.39] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.39)
#### 28/08/14 by Morten Henriksen
- Meteor Package System Update
## [v0.0.38] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.38)
#### 27/08/14 by Eric Dobbertin
## [v0.0.37] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.37)
#### 26/08/14 by Eric Dobbertin
- change package name to lowercase
## [v0.0.36] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.36)
#### 06/08/14 by Eric Dobbertin
- pass correct arg
## [v0.0.35] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.35)
#### 06/08/14 by Eric Dobbertin
- move to correct place
## [v0.0.34] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.34)
#### 05/08/14 by Eric Dobbertin
- *Merged pull-request:* "Added contentLength for ranges and inline content" [#5](https://github.com/zcfs/Meteor-cfs-access-point/issues/5) ([maomorales](https://github.com/maomorales))
- Content-Length and Last-Modified headers
Patches by GitHub user [@maomorales](https://github.com/maomorales).
## [v0.0.33] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.33)
#### 31/07/14 by Eric Dobbertin
- *Merged pull-request:* "Force browser to download with filename passed in url" [#3](https://github.com/zcfs/Meteor-cfs-access-point/issues/3) ([elbowz](https://github.com/elbowz))
- Force browser to download with filename passed in url
Patches by GitHub user [@elbowz](https://github.com/elbowz).
## [v0.0.32] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.32)
#### 28/07/14 by Eric Dobbertin
- support collection-specific GET headers
- update API docs
## [v0.0.31] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.31)
#### 06/07/14 by Eric Dobbertin
- allow override filename
## [v0.0.30] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.30)
#### 30/04/14 by Eric Dobbertin
- ignore auth on server so that url method can be called on the server
## [v0.0.29] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.29)
#### 30/04/14 by Eric Dobbertin
- rework the new authtoken stuff to make it easier to debug and cleaner
## [v0.0.28] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.28)
#### 29/04/14 by Eric Dobbertin
- generate api docs
- adjustments to use new FS.File API functions, plus have `url` function omit query string whenever possible
- *Merged pull-request:* "Support for expiration token" [#1](https://github.com/zcfs/Meteor-cfs-access-point/issues/1) ([tanis2000](https://github.com/tanis2000))
- Switched to HTTP.call() to get the server time
- Better check for options.auth being a number. Check to see if we have Buffer() available on the server side. New check to make sure we have the token. Switched Metheor.method to HTTP.methods for the getServerTime() function.
- Expiration is now optional. If auth is set to a number, that is the number of seconds the token is valid for.
- Added time sync with the server for token generation.
- Added code to pass a token with a set expiration date from the client. Added token check on the server side.
Patches by GitHub user [@tanis2000](https://github.com/tanis2000).
## [v0.0.27] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.27)
#### 08/04/14 by Eric Dobbertin
- clean up/fix whole-file upload handler
## [v0.0.26] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.26)
#### 07/04/14 by Eric Dobbertin
- add URL options to get temporary images while uploading and storing
## [v0.0.25] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.25)
#### 03/04/14 by Eric Dobbertin
- * allow `setBaseUrl` to be called either outside of Meteor.startup or inside * move encodeParams helper to FS.Utility
## [v0.0.24] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.24)
#### 03/04/14 by Eric Dobbertin
- properly remount URLs
- when uploading chunks, check the insert allow/deny since it's part of inserting
## [v0.0.23] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.23)
#### 31/03/14 by Eric Dobbertin
- use latest releases
## [v0.0.22] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.22)
#### 29/03/14 by Morten Henriksen
- remove underscore deps
## [v0.0.21] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.21)
#### 25/03/14 by Morten Henriksen
- add comments about shareId
## [v0.0.20] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.20)
#### 23/03/14 by Morten Henriksen
- Rollback to specific git dependency
- Try modified test script
- deps are already in collectionFS
## [v0.0.19] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.19)
#### 22/03/14 by Morten Henriksen
- try to fix travis test by using general package references
## [v0.0.18] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.18)
#### 22/03/14 by Morten Henriksen
- If the read stream fails we send an error to the client
## [v0.0.17] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.17)
#### 21/03/14 by Morten Henriksen
- remove smart lock
- commit smart.lock, trying to get tests to pass on travis
- some minor pkg adjustments; trying to get tests to pass on travis
## [v0.0.16] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.16)
#### 18/03/14 by Morten Henriksen
- Rollback to using the direct storage adapter - makes more sense when serving files
- shift to new http.methods streaming api
- move server side DDP access points to cfs-download-ddp pkg; update API docs
- fix typo...
- return something useful
- convert to streaming
- Add streaming WIP
- fix/adjust some tests; minor improvements to some handlers
- Add unmount and allow mount to use default selector function
- Refactor access point - wip
## [v0.0.15] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.15)
#### 05/03/14 by Morten Henriksen
- Refactor note, encode stuff should be prefixed into FS.Utility
- FS.File.url add user deps when auth is used
- fix url method
- query string fix
- move PUT access points for HTTP upload into this package; mount DELETE on /record/ as well as /files/; some fixes and improvements to handlers
## [v0.0.14] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.14)
#### 03/03/14 by Eric Dobbertin
- better error; return Buffer instead of converting to Uint8Array
## [v0.0.13] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.13)
#### 02/03/14 by Eric Dobbertin
- more tests, make everything work, add unpublish method
- Merge branch 'master' of https://github.com/zcfs/Meteor-cfs-access-point
## [v0.0.12] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.12)
#### 01/03/14 by Eric Dobbertin
- add travis-ci image
- rework URLs a bit, use http-publish package to publish FS.Collection listing, and add a test for this (!)
- add http-publish dependency
- del should be delete
## [v0.0.11] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.11)
#### 28/02/14 by Eric Dobbertin
- move some code to other packages; redo the HTTP GET/DEL methods
## [v0.0.10] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.10)
#### 28/02/14 by Eric Dobbertin
- move DDP upload methods to new cfs-upload-ddp package
## [v0.0.9] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.9)
#### 21/02/14 by Eric Dobbertin
- new URL syntax; use the store's file key instead of ID; also fix allow/deny checks with insecure
## [v0.0.8] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.8)
#### 20/02/14 by Eric Dobbertin
- support HTTP PUT of new file and fix PUT of existing file
## [v0.0.7] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.7)
#### 17/02/14 by Morten Henriksen
- add http-methods dependency
## [v0.0.6] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.6)
#### 16/02/14 by Morten Henriksen
## [v0.0.5] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.5)
#### 16/02/14 by Morten Henriksen
- a few fixes and improvements
- need to actually mount it
- attempt at switching to generic HTTP access point; also add support for chunked http downloads (range header)
## [v0.0.4] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.4)
#### 15/02/14 by Morten Henriksen
- Merge branch 'master' of https://github.com/zcfs/Meteor-cfs-access-point
- corrected typo
- added debugging
- call HTTP.methods on server only
- run client side, too, for side effects
- rework for additional abstraction; also DDP methods don't need to be per-collection so they no longer are
## [v0.0.3] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.3)
#### 13/02/14 by Morten Henriksen
## [v0.0.2] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.2)
#### 13/02/14 by Morten Henriksen
## [v0.0.1] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.1)
#### 13/02/14 by Morten Henriksen
- init commit

View file

@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com
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.

View file

@ -1,32 +0,0 @@
wekan-cfs-access-point [![Build Status](https://travis-ci.org/CollectionFS/Meteor-cfs-access-point.png?branch=master)](https://travis-ci.org/CollectionFS/Meteor-cfs-access-point)
=========================
This is a Meteor package used by
[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS).
You don't need to manually add this package to your app. It is added when you
add the `wekan-cfs-standard-packages` package. You could potentially use your own access point
package instead.
## Define a URL for Collection Listing
To define a URL that accepts GET requests and returns a list of published
files in a FS.Collection:
```js
Images = new FS.Collection("images", {
stores: [myStore]
});
FS.HTTP.publish(Images, function () {
// `this` provides a context similar to Meteor.publish
return Images.find();
});
```
The URL will be '/cfs/record/images', where the `cfs` piece is configurable
using the `FS.HTTP.setBaseUrl` method.
## API Documentation
[Here](api.md)

View file

@ -1,58 +0,0 @@
FS.HTTP.setHeadersForGet = function setHeadersForGet() {
// Client Stub
};
FS.HTTP.now = function() {
return new Date(new Date() + FS.HTTP._serverTimeDiff);
};
// Returns the localstorage if its found and working
// TODO: check if this works in IE
// could use Meteor._localStorage - just needs a rewrite
FS.HTTP._storage = function() {
var storage,
fail,
uid;
try {
uid = "test";
(storage = window.localStorage).setItem(uid, uid);
fail = (storage.getItem(uid) !== uid);
storage.removeItem(uid);
if (fail) {
storage = false;
}
} catch(e) {
console.log("Error initializing storage for FS.HTTP");
console.log(e);
}
return storage;
};
// get our storage if found
FS.HTTP.storage = FS.HTTP._storage();
FS.HTTP._prefix = 'fsHTTP.';
FS.HTTP._serverTimeDiff = 0; // Time difference in ms
if (FS.HTTP.storage) {
// Initialize the FS.HTTP._serverTimeDiff
FS.HTTP._serverTimeDiff = (1*FS.HTTP.storage.getItem(FS.HTTP._prefix+'timeDiff')) || 0;
// At client startup we figure out the time difference between server and
// client time - this includes lag and timezone
Meteor.startup(function() {
// Call the server method an get server time
HTTP.get(rootUrlPathPrefix + '/cfs/servertime', function(error, result) {
if (!error) {
// Update our server time diff
var dateNew = new Date(+result.content);
FS.HTTP._serverTimeDiff = dateNew - new Date();// - lag or/and timezone
// Update the localstorage
FS.HTTP.storage.setItem(FS.HTTP._prefix + 'timeDiff', FS.HTTP._serverTimeDiff);
} else {
console.log(error.message);
}
}); // EO Server call
});
}

View file

@ -1,199 +0,0 @@
rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || "";
// Adjust the rootUrlPathPrefix if necessary
if (rootUrlPathPrefix.length > 0) {
if (rootUrlPathPrefix.slice(0, 1) !== '/') {
rootUrlPathPrefix = '/' + rootUrlPathPrefix;
}
if (rootUrlPathPrefix.slice(-1) === '/') {
rootUrlPathPrefix = rootUrlPathPrefix.slice(0, -1);
}
}
// prepend ROOT_URL when isCordova
if (Meteor.isCordova) {
rootUrlPathPrefix = Meteor.absoluteUrl(rootUrlPathPrefix.replace(/^\/+/, '')).replace(/\/+$/, '');
}
baseUrl = '/cfs';
FS.HTTP = FS.HTTP || {};
// Note the upload URL so that client uploader packages know what it is
FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files';
/**
* @method FS.HTTP.setBaseUrl
* @public
* @param {String} newBaseUrl - Change the base URL for the HTTP GET and DELETE endpoints.
* @returns {undefined}
*/
FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) {
// Adjust the baseUrl if necessary
if (newBaseUrl.slice(0, 1) !== '/') {
newBaseUrl = '/' + newBaseUrl;
}
if (newBaseUrl.slice(-1) === '/') {
newBaseUrl = newBaseUrl.slice(0, -1);
}
// Update the base URL
baseUrl = newBaseUrl;
// Change the upload URL so that client uploader packages know what it is
FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files';
// Remount URLs with the new baseUrl, unmounting the old, on the server only.
// If existingMountPoints is empty, then we haven't run the server startup
// code yet, so this new URL will be used at that point for the initial mount.
if (Meteor.isServer && !FS.Utility.isEmpty(_existingMountPoints)) {
mountUrls();
}
};
/*
* FS.File extensions
*/
/**
* @method FS.File.prototype.urlRelative Construct the file url
* @public
* @param {Object} [options]
* @param {String} [options.store] Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
* @param {Boolean} [options.auth=null] Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
* @param {Boolean} [options.download=false] Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
* @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
* @param {Boolean} [options.returnWhenStored=false] Flag relevant only on server, Return the URL only when file has been saved to the requested store.
* @param {Boolean} [options.metadata=false] Return the URL for the file metadata access point rather than the file itself.
* @param {String} [options.uploading=null] A URL to return while the file is being uploaded.
* @param {String} [options.storing=null] A URL to return while the file is being stored.
* @param {String} [options.filename=null] Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
*
* Returns the relative HTTP URL for getting the file or its metadata.
*/
FS.File.prototype.urlRelative = function(options) {
var self = this;
options = options || {};
options = FS.Utility.extend({
store: null,
auth: null,
download: false,
metadata: false,
brokenIsFine: false,
returnWhenStored: false,
uploading: null, // return this URL while uploading
storing: null, // return this URL while storing
filename: null // override the filename that is shown to the user
}, options.hash || options); // check for "hash" prop if called as helper
// Primarily useful for displaying a temporary image while uploading an image
if (options.uploading && !self.isUploaded()) {
return options.uploading;
}
if (self.isMounted()) {
// See if we've stored in the requested store yet
var storeName = options.store || self.collection.primaryStore.name;
if (!self.hasStored(storeName)) {
if (options.storing) {
return options.storing;
} else if (!options.brokenIsFine) {
// In case we want to get back the url only when he is stored
if (Meteor.isServer && options.returnWhenStored) {
// Wait till file is stored to storeName
self.onStored(storeName);
} else {
// We want to return null if we know the URL will be a broken
// link because then we can avoid rendering broken links, broken
// images, etc.
return null;
}
}
}
// Add filename to end of URL if we can determine one
var filename = options.filename || self.name({store: storeName});
if (typeof filename === "string" && filename.length) {
filename = '/' + filename;
} else {
filename = '';
}
// TODO: Could we somehow figure out if the collection requires login?
var authToken = '';
if (Meteor.isClient && typeof Accounts !== "undefined" && typeof Accounts._storedLoginToken === "function") {
if (options.auth !== false) {
// Add reactive deps on the user
Meteor.userId();
var authObject = {
authToken: Accounts._storedLoginToken() || ''
};
// If it's a number, we use that as the expiration time (in seconds)
if (options.auth === +options.auth) {
authObject.expiration = FS.HTTP.now() + options.auth * 1000;
}
// Set the authToken
var authString = JSON.stringify(authObject);
authToken = FS.Utility.btoa(authString);
}
} else if (typeof options.auth === "string") {
// If the user supplies auth token the user will be responsible for
// updating
authToken = options.auth;
}
// Construct query string
var params = {};
if (authToken !== '') {
params.token = authToken;
}
if (options.download) {
params.download = true;
}
if (options.store) {
// We use options.store here instead of storeName because we want to omit the queryString
// whenever possible, allowing users to have "clean" URLs if they want. The server will
// assume the first store defined on the server, which means that we are assuming that
// the first on the client is also the first on the server. If that's not the case, the
// store option should be supplied.
params.store = options.store;
}
var queryString = FS.Utility.encodeParams(params);
if (queryString.length) {
queryString = '?' + queryString;
}
// Determine which URL to use
var area;
if (options.metadata) {
area = '/record';
} else {
area = '/files';
}
// Construct and return the http method url
return baseUrl + area + '/' + self.collection.name + '/' + self._id + filename + queryString;
}
};
/**
* @method FS.File.prototype.url Construct the file url
* @public
* @param {Object} [options]
* @param {String} [options.store] Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
* @param {Boolean} [options.auth=null] Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
* @param {Boolean} [options.download=false] Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
* @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
* @param {Boolean} [options.metadata=false] Return the URL for the file metadata access point rather than the file itself.
* @param {String} [options.uploading=null] A URL to return while the file is being uploaded.
* @param {String} [options.storing=null] A URL to return while the file is being stored.
* @param {String} [options.filename=null] Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
*
* Returns the HTTP URL for getting the file or its metadata.
*/
FS.File.prototype.url = function(options) {
self = this;
return rootUrlPathPrefix + self.urlRelative(options);
};

View file

@ -1,307 +0,0 @@
getHeaders = [];
getHeadersByCollection = {};
var contentDisposition = Npm.require('content-disposition');
FS.HTTP.Handlers = {};
/**
* @method FS.HTTP.Handlers.Del
* @public
* @returns {any} response
*
* HTTP DEL request handler
*/
FS.HTTP.Handlers.Del = function httpDelHandler(ref) {
var self = this;
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
// If DELETE request, validate with 'remove' allow/deny, delete the file, and return
FS.Utility.validateAction(ref.collection.files._validators['remove'], ref.file, self.userId);
/*
* From the DELETE spec:
* A successful response SHOULD be 200 (OK) if the response includes an
* entity describing the status, 202 (Accepted) if the action has not
* yet been enacted, or 204 (No Content) if the action has been enacted
* but the response does not include an entity.
*/
self.setStatusCode(200);
return {
deleted: !!ref.file.remove()
};
};
/**
* @method FS.HTTP.Handlers.GetList
* @public
* @returns {Object} response
*
* HTTP GET file list request handler
*/
FS.HTTP.Handlers.GetList = function httpGetListHandler() {
// Not Yet Implemented
// Need to check publications and return file list based on
// what user is allowed to see
};
/*
requestRange will parse the range set in request header - if not possible it
will throw fitting errors and autofill range for both partial and full ranges
throws error or returns the object:
{
start
end
length
unit
partial
}
*/
var requestRange = function(req, fileSize) {
if (req) {
if (req.headers) {
var rangeString = req.headers.range;
// Make sure range is a string
if (rangeString === ''+rangeString) {
// range will be in the format "bytes=0-32767"
var parts = rangeString.split('=');
var unit = parts[0];
// Make sure parts consists of two strings and range is of type "byte"
if (parts.length == 2 && unit == 'bytes') {
// Parse the range
var range = parts[1].split('-');
var start = Number(range[0]);
var end = Number(range[1]);
// Fix invalid ranges?
if (range[0] != start) start = 0;
if (range[1] != end || !end) end = fileSize - 1;
// Make sure range consists of a start and end point of numbers and start is less than end
if (start < end) {
var partSize = 0 - start + end + 1;
// Return the parsed range
return {
start: start,
end: end,
length: partSize,
size: fileSize,
unit: unit,
partial: (partSize < fileSize)
};
} else {
throw new Meteor.Error(416, "Requested Range Not Satisfiable");
}
} else {
// The first part should be bytes
throw new Meteor.Error(416, "Requested Range Unit Not Satisfiable");
}
} else {
// No range found
}
} else {
// throw new Error('No request headers set for _parseRange function');
}
} else {
throw new Error('No request object passed to _parseRange function');
}
return {
start: 0,
end: fileSize - 1,
length: fileSize,
size: fileSize,
unit: 'bytes',
partial: false
};
};
/**
* @method FS.HTTP.Handlers.Get
* @public
* @returns {any} response
*
* HTTP GET request handler
*/
FS.HTTP.Handlers.Get = function httpGetHandler(ref) {
var self = this;
// Once we have the file, we can test allow/deny validators
// XXX: pass on the "share" query eg. ?share=342hkjh23ggj for shared url access?
FS.Utility.validateAction(ref.collection._validators['download'], ref.file, self.userId /*, self.query.shareId*/);
var storeName = ref.storeName;
// If no storeName was specified, use the first defined storeName
if (typeof storeName !== "string") {
// No store handed, we default to primary store
storeName = ref.collection.primaryStore.name;
}
// Get the storage reference
var storage = ref.collection.storesLookup[storeName];
if (!storage) {
throw new Meteor.Error(404, "Not Found", 'There is no store "' + storeName + '"');
}
// Get the file
var copyInfo = ref.file.copies[storeName];
if (!copyInfo) {
throw new Meteor.Error(404, "Not Found", 'This file was not stored in the ' + storeName + ' store');
}
// Set the content type for file
if (typeof copyInfo.type === "string") {
self.setContentType(copyInfo.type);
} else {
self.setContentType('application/octet-stream');
}
// Add 'Content-Disposition' header if requested a download/attachment URL
if (typeof ref.download !== "undefined") {
var filename = ref.filename || copyInfo.name;
self.addHeader('Content-Disposition', contentDisposition(filename));
} else {
self.addHeader('Content-Disposition', 'inline');
}
// Get the contents range from request
var range = requestRange(self.request, copyInfo.size);
// Some browsers cope better if the content-range header is
// still included even for the full file being returned.
self.addHeader('Content-Range', range.unit + ' ' + range.start + '-' + range.end + '/' + range.size);
// If a chunk/range was requested instead of the whole file, serve that'
if (range.partial) {
self.setStatusCode(206, 'Partial Content');
} else {
self.setStatusCode(200, 'OK');
}
// Add any other global custom headers and collection-specific custom headers
FS.Utility.each(getHeaders.concat(getHeadersByCollection[ref.collection.name] || []), function(header) {
self.addHeader(header[0], header[1]);
});
// Inform clients about length (or chunk length in case of ranges)
self.addHeader('Content-Length', range.length);
// Last modified header (updatedAt from file info)
self.addHeader('Last-Modified', copyInfo.updatedAt.toUTCString());
// Inform clients that we accept ranges for resumable chunked downloads
self.addHeader('Accept-Ranges', range.unit);
if (FS.debug) console.log('Read file "' + (ref.filename || copyInfo.name) + '" ' + range.unit + ' ' + range.start + '-' + range.end + '/' + range.size);
var readStream = storage.adapter.createReadStream(ref.file, {start: range.start, end: range.end});
readStream.on('error', function(err) {
// Send proper error message on get error
if (err.message && err.statusCode) {
self.Error(new Meteor.Error(err.statusCode, err.message));
} else {
self.Error(new Meteor.Error(503, 'Service unavailable'));
}
});
readStream.pipe(self.createWriteStream());
};
// File with unicode or other encodings filename can upload to server susscessfully,
// but when download, the HTTP header "Content-Disposition" cannot accept
// characters other than ASCII, the filename should be converted to binary or URI encoded.
// https://github.com/wekan/wekan/issues/784
const originalHandler = FS.HTTP.Handlers.Get;
FS.HTTP.Handlers.Get = function (ref) {
try {
var userAgent = (this.requestHeaders['user-agent']||'').toLowerCase();
if(userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
ref.filename = encodeURIComponent(ref.filename);
} else if(userAgent.indexOf('firefox') >= 0) {
ref.filename = new Buffer(ref.filename).toString('binary');
} else {
/* safari*/
ref.filename = new Buffer(ref.filename).toString('binary');
}
} catch (ex){
ref.filename = ref.filename;
}
return originalHandler.call(this, ref);
};
/**
* @method FS.HTTP.Handlers.PutInsert
* @public
* @returns {Object} response object with _id property
*
* HTTP PUT file insert request handler
*/
FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) {
var self = this;
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
FS.debug && console.log("HTTP PUT (insert) handler");
// Create the nice FS.File
var fileObj = new FS.File();
// Set its name
fileObj.name(opts.filename || null);
// Attach the readstream as the file's data
fileObj.attachData(self.createReadStream(), {type: self.requestHeaders['content-type'] || 'application/octet-stream'});
// Validate with insert allow/deny
FS.Utility.validateAction(ref.collection.files._validators['insert'], fileObj, self.userId);
// Insert file into collection, triggering readStream storage
ref.collection.insert(fileObj);
// Send response
self.setStatusCode(200);
// Return the new file id
return {_id: fileObj._id};
};
/**
* @method FS.HTTP.Handlers.PutUpdate
* @public
* @returns {Object} response object with _id and chunk properties
*
* HTTP PUT file update chunk request handler
*/
FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) {
var self = this;
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
var chunk = parseInt(opts.chunk, 10);
if (isNaN(chunk)) chunk = 0;
FS.debug && console.log("HTTP PUT (update) handler received chunk: ", chunk);
// Validate with insert allow/deny; also mounts and retrieves the file
FS.Utility.validateAction(ref.collection.files._validators['insert'], ref.file, self.userId);
self.createReadStream().pipe( FS.TempStore.createWriteStream(ref.file, chunk) );
// Send response
self.setStatusCode(200);
return { _id: ref.file._id, chunk: chunk };
};

View file

@ -1,362 +0,0 @@
var path = Npm.require("path");
HTTP.publishFormats({
fileRecordFormat: function (input) {
// Set the method scope content type to json
this.setContentType('application/json');
if (FS.Utility.isArray(input)) {
return EJSON.stringify(FS.Utility.map(input, function (obj) {
return FS.Utility.cloneFileRecord(obj);
}));
} else {
return EJSON.stringify(FS.Utility.cloneFileRecord(input));
}
}
});
/**
* @method FS.HTTP.setHeadersForGet
* @public
* @param {Array} headers - List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
* @param {Array|String} [collections] - Which collections the headers should be added for. Omit this argument to add the header for all collections.
* @returns {undefined}
*/
FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) {
if (typeof collections === "string") {
collections = [collections];
}
if (collections) {
FS.Utility.each(collections, function(collectionName) {
getHeadersByCollection[collectionName] = headers || [];
});
} else {
getHeaders = headers || [];
}
};
/**
* @method FS.HTTP.publish
* @public
* @param {FS.Collection} collection
* @param {Function} func - Publish function that returns a cursor.
* @returns {undefined}
*
* Publishes all documents returned by the cursor at a GET URL
* with the format baseUrl/record/collectionName. The publish
* function `this` is similar to normal `Meteor.publish`.
*/
FS.HTTP.publish = function fsHttpPublish(collection, func) {
var name = baseUrl + '/record/' + collection.name;
// Mount collection listing URL using http-publish package
HTTP.publish({
name: name,
defaultFormat: 'fileRecordFormat',
collection: collection,
collectionGet: true,
collectionPost: false,
documentGet: true,
documentPut: false,
documentDelete: false
}, func);
FS.debug && console.log("Registered HTTP method GET URLs:\n\n" + name + '\n' + name + '/:id\n');
};
/**
* @method FS.HTTP.unpublish
* @public
* @param {FS.Collection} collection
* @returns {undefined}
*
* Unpublishes a restpoint created by a call to `FS.HTTP.publish`
*/
FS.HTTP.unpublish = function fsHttpUnpublish(collection) {
// Mount collection listing URL using http-publish package
HTTP.unpublish(baseUrl + '/record/' + collection.name);
};
_existingMountPoints = {};
/**
* @method defaultSelectorFunction
* @private
* @returns { collection, file }
*
* This is the default selector function
*/
var defaultSelectorFunction = function() {
var self = this;
// Selector function
//
// This function will have to return the collection and the
// file. If file not found undefined is returned - if null is returned the
// search was not possible
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
// Get the collection name from the url
var collectionName = opts.collectionName;
// Get the id from the url
var id = opts.id;
// Get the collection
var collection = FS._collections[collectionName];
//if Mongo ObjectIds are used, then we need to use that in find statement
if(collection.options.idGeneration && collection.options.idGeneration === 'MONGO') {
// Get the file if possible else return null
var file = (id && collection)? collection.findOne({ _id: new Meteor.Collection.ObjectID(id)}): null;
} else {
var file = (id && collection)? collection.findOne({ _id: id }): null;
}
// Return the collection and the file
return {
collection: collection,
file: file,
storeName: opts.store,
download: opts.download,
filename: opts.filename
};
};
/*
* @method FS.HTTP.mount
* @public
* @param {array of string} mountPoints mount points to map rest functinality on
* @param {function} selector_f [selector] function returns `{ collection, file }` for mount points to work with
*
*/
FS.HTTP.mount = function(mountPoints, selector_f) {
// We take mount points as an array and we get a selector function
var selectorFunction = selector_f || defaultSelectorFunction;
var accessPoint = {
'stream': true,
'auth': expirationAuth,
'post': function(data) {
// Use the selector for finding the collection and file reference
var ref = selectorFunction.call(this);
// We dont support post - this would be normal insert eg. of filerecord?
throw new Meteor.Error(501, "Not implemented", "Post is not supported");
},
'put': function(data) {
// Use the selector for finding the collection and file reference
var ref = selectorFunction.call(this);
// Make sure we have a collection reference
if (!ref.collection)
throw new Meteor.Error(404, "Not Found", "No collection found");
// Make sure we have a file reference
if (ref.file === null) {
// No id supplied so we will create a new FS.File instance and
// insert the supplied data.
return FS.HTTP.Handlers.PutInsert.apply(this, [ref]);
} else {
if (ref.file) {
return FS.HTTP.Handlers.PutUpdate.apply(this, [ref]);
} else {
throw new Meteor.Error(404, "Not Found", 'No file found');
}
}
},
'get': function(data) {
// Use the selector for finding the collection and file reference
var ref = selectorFunction.call(this);
// Make sure we have a collection reference
if (!ref.collection)
throw new Meteor.Error(404, "Not Found", "No collection found");
// Make sure we have a file reference
if (ref.file === null) {
// No id supplied so we will return the published list of files ala
// http.publish in json format
return FS.HTTP.Handlers.GetList.apply(this, [ref]);
} else {
if (ref.file) {
return FS.HTTP.Handlers.Get.apply(this, [ref]);
} else {
throw new Meteor.Error(404, "Not Found", 'No file found');
}
}
},
'delete': function(data) {
// Use the selector for finding the collection and file reference
var ref = selectorFunction.call(this);
// Make sure we have a collection reference
if (!ref.collection)
throw new Meteor.Error(404, "Not Found", "No collection found");
// Make sure we have a file reference
if (ref.file) {
return FS.HTTP.Handlers.Del.apply(this, [ref]);
} else {
throw new Meteor.Error(404, "Not Found", 'No file found');
}
}
};
var accessPoints = {};
// Add debug message
FS.debug && console.log('Registered HTTP method URLs:');
FS.Utility.each(mountPoints, function(mountPoint) {
// Couple mountpoint and accesspoint
accessPoints[mountPoint] = accessPoint;
// Remember our mountpoints
_existingMountPoints[mountPoint] = mountPoint;
// Add debug message
FS.debug && console.log(mountPoint);
});
// XXX: HTTP:methods should unmount existing mounts in case of overwriting?
HTTP.methods(accessPoints);
};
/**
* @method FS.HTTP.unmount
* @public
* @param {string | array of string} [mountPoints] Optional, if not specified all mountpoints are unmounted
*
*/
FS.HTTP.unmount = function(mountPoints) {
// The mountPoints is optional, can be string or array if undefined then
// _existingMountPoints will be used
var unmountList;
// Container for the mount points to unmount
var unmountPoints = {};
if (typeof mountPoints === 'undefined') {
// Use existing mount points - unmount all
unmountList = _existingMountPoints;
} else if (mountPoints === ''+mountPoints) {
// Got a string
unmountList = [mountPoints];
} else if (mountPoints.length) {
// Got an array
unmountList = mountPoints;
}
// If we have a list to unmount
if (unmountList) {
// Iterate over each item
FS.Utility.each(unmountList, function(mountPoint) {
// Check _existingMountPoints to make sure the mount point exists in our
// context / was created by the FS.HTTP.mount
if (_existingMountPoints[mountPoint]) {
// Mark as unmount
unmountPoints[mountPoint] = false;
// Release
delete _existingMountPoints[mountPoint];
}
});
FS.debug && console.log('FS.HTTP.unmount:');
FS.debug && console.log(unmountPoints);
// Complete unmount
HTTP.methods(unmountPoints);
}
};
// ### FS.Collection maps on HTTP pr. default on the following restpoints:
// *
// baseUrl + '/files/:collectionName/:id/:filename',
// baseUrl + '/files/:collectionName/:id',
// baseUrl + '/files/:collectionName'
//
// Change/ replace the existing mount point by:
// ```js
// // unmount all existing
// FS.HTTP.unmount();
// // Create new mount point
// FS.HTTP.mount([
// '/cfs/files/:collectionName/:id/:filename',
// '/cfs/files/:collectionName/:id',
// '/cfs/files/:collectionName'
// ]);
// ```
//
mountUrls = function mountUrls() {
// We unmount first in case we are calling this a second time
FS.HTTP.unmount();
FS.HTTP.mount([
baseUrl + '/files/:collectionName/:id/:filename',
baseUrl + '/files/:collectionName/:id',
baseUrl + '/files/:collectionName'
]);
};
// Returns the userId from URL token
var expirationAuth = function expirationAuth() {
var self = this;
// Read the token from '/hello?token=base64'
var encodedToken = self.query.token;
FS.debug && console.log("token: "+encodedToken);
if (!encodedToken || !Meteor.users) return false;
// Check the userToken before adding it to the db query
// Set the this.userId
var tokenString = FS.Utility.atob(encodedToken);
var tokenObject;
try {
tokenObject = JSON.parse(tokenString);
} catch(err) {
throw new Meteor.Error(400, 'Bad Request');
}
// XXX: Do some check here of the object
var userToken = tokenObject.authToken;
if (userToken !== ''+userToken) {
throw new Meteor.Error(400, 'Bad Request');
}
// If we have an expiration token we should check that it's still valid
if (tokenObject.expiration != null) {
// check if its too old
var now = Date.now();
if (tokenObject.expiration < now) {
FS.debug && console.log('Expired token: ' + tokenObject.expiration + ' is less than ' + now);
throw new Meteor.Error(500, 'Expired token');
}
}
// We are not on a secure line - so we have to look up the user...
var user = Meteor.users.findOne({
$or: [
{'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(userToken)},
{'services.resume.loginTokens.token': userToken}
]
});
// Set the userId in the scope
return user && user._id;
};
HTTP.methods(
{'/cfs/servertime': {
get: function(data) {
return Date.now().toString();
}
}
});
// Unify client / server api
FS.HTTP.now = function() {
return Date.now();
};
// Start up the basic mount points
Meteor.startup(function () {
mountUrls();
});

View file

@ -1,271 +0,0 @@
## wekan-cfs-access-point Public API ##
CollectionFS, add ddp and http accesspoint capability
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
-
### <a name="FS.HTTP.setBaseUrl"></a>*FSHTTP*.setBaseUrl(newBaseUrl)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __setBaseUrl__ is defined in `FS.HTTP`*
__Arguments__
* __newBaseUrl__ *{String}*
Change the base URL for the HTTP GET and DELETE endpoints.
__Returns__ *{undefined}*
> ```FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) { ...``` [access-point-common.js:29](access-point-common.js#L29)
-
### <a name="FS.File.prototype.url"></a>*fsFile*.url([options])&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __url__ is defined in `prototype` of `FS.File`*
__Arguments__
* __options__ *{Object}* (Optional)
* __store__ *{String}* (Optional)
Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
* __auth__ *{Boolean}* (Optional, Default = null)
Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
* __download__ *{Boolean}* (Optional, Default = false)
Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
* __brokenIsFine__ *{Boolean}* (Optional, Default = false)
Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
* __metadata__ *{Boolean}* (Optional, Default = false)
Return the URL for the file metadata access point rather than the file itself.
* __uploading__ *{String}* (Optional, Default = null)
A URL to return while the file is being uploaded.
* __storing__ *{String}* (Optional, Default = null)
A URL to return while the file is being stored.
* __filename__ *{String}* (Optional, Default = null)
Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
Returns the HTTP URL for getting the file or its metadata.
> ```FS.File.prototype.url = function(options) { ...``` [access-point-common.js:72](access-point-common.js#L72)
-
### <a name="FS.HTTP.Handlers.Del"></a>*FSHTTPHandlers*.Del()&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __Del__ is defined in `FS.HTTP.Handlers`*
__Returns__ *{any}*
response
HTTP DEL request handler
> ```FS.HTTP.Handlers.Del = function httpDelHandler(ref) { ...``` [access-point-handlers.js:13](access-point-handlers.js#L13)
-
### <a name="FS.HTTP.Handlers.GetList"></a>*FSHTTPHandlers*.GetList()&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __GetList__ is defined in `FS.HTTP.Handlers`*
__Returns__ *{Object}*
response
HTTP GET file list request handler
> ```FS.HTTP.Handlers.GetList = function httpGetListHandler() { ...``` [access-point-handlers.js:41](access-point-handlers.js#L41)
-
### <a name="FS.HTTP.Handlers.Get"></a>*FSHTTPHandlers*.Get()&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __Get__ is defined in `FS.HTTP.Handlers`*
__Returns__ *{any}*
response
HTTP GET request handler
> ```FS.HTTP.Handlers.Get = function httpGetHandler(ref) { ...``` [access-point-handlers.js:135](access-point-handlers.js#L135)
-
### <a name="FS.HTTP.Handlers.PutInsert"></a>*FSHTTPHandlers*.PutInsert()&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __PutInsert__ is defined in `FS.HTTP.Handlers`*
__Returns__ *{Object}*
response object with _id property
HTTP PUT file insert request handler
> ```FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) { ...``` [access-point-handlers.js:229](access-point-handlers.js#L229)
-
### <a name="FS.HTTP.Handlers.PutUpdate"></a>*FSHTTPHandlers*.PutUpdate()&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __PutUpdate__ is defined in `FS.HTTP.Handlers`*
__Returns__ *{Object}*
response object with _id and chunk properties
HTTP PUT file update chunk request handler
> ```FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { ...``` [access-point-handlers.js:264](access-point-handlers.js#L264)
-
### <a name="FS.HTTP.setHeadersForGet"></a>*FSHTTP*.setHeadersForGet(headers, [collections])&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __setHeadersForGet__ is defined in `FS.HTTP`*
__Arguments__
* __headers__ *{Array}*
List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
* __collections__ *{Array|String}* (Optional)
Which collections the headers should be added for. Omit this argument to add the header for all collections.
__Returns__ *{undefined}*
> ```FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) { ...``` [access-point-server.js:24](access-point-server.js#L24)
-
### <a name="FS.HTTP.publish"></a>*FSHTTP*.publish(collection, func)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __publish__ is defined in `FS.HTTP`*
__Arguments__
* __collection__ *{[FS.Collection](#FS.Collection)}*
* __func__ *{Function}*
Publish function that returns a cursor.
__Returns__ *{undefined}*
Publishes all documents returned by the cursor at a GET URL
with the format baseUrl/record/collectionName. The publish
function `this` is similar to normal `Meteor.publish`.
> ```FS.HTTP.publish = function fsHttpPublish(collection, func) { ...``` [access-point-server.js:48](access-point-server.js#L48)
-
### <a name="FS.HTTP.unpublish"></a>*FSHTTP*.unpublish(collection)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __unpublish__ is defined in `FS.HTTP`*
__Arguments__
* __collection__ *{[FS.Collection](#FS.Collection)}*
__Returns__ *{undefined}*
Unpublishes a restpoint created by a call to `FS.HTTP.publish`
> ```FS.HTTP.unpublish = function fsHttpUnpublish(collection) { ...``` [access-point-server.js:73](access-point-server.js#L73)
-
### <a name="FS.HTTP.mount"></a>*FSHTTP*.mount(mountPoints, selector_f)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __mount__ is defined in `FS.HTTP`*
__Arguments__
* __mountPoints__ *{[array of string](#array of string)}*
mount points to map rest functinality on
* __selector_f__ *{function}*
[selector] function returns `{ collection, file }` for mount points to work with
> ```FS.HTTP.mount = function(mountPoints, selector_f) { ...``` [access-point-server.js:125](access-point-server.js#L125)
-
### <a name="FS.HTTP.unmount"></a>*FSHTTP*.unmount([mountPoints])&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __unmount__ is defined in `FS.HTTP`*
__Arguments__
* __mountPoints__ *{[string ](#string )|[ array of string](# array of string)}* (Optional)
Optional, if not specified all mountpoints are unmounted
> ```FS.HTTP.unmount = function(mountPoints) { ...``` [access-point-server.js:223](access-point-server.js#L223)
-
### FS.Collection maps on HTTP pr. default on the following restpoints:
*
baseUrl + '/files/:collectionName/:id/:filename',
baseUrl + '/files/:collectionName/:id',
baseUrl + '/files/:collectionName'
Change/ replace the existing mount point by:
```js
unmount all existing
FS.HTTP.unmount();
Create new mount point
FS.HTTP.mount([
'/cfs/files/:collectionName/:id/:filename',
'/cfs/files/:collectionName/:id',
'/cfs/files/:collectionName'
]);
```

View file

@ -1,332 +0,0 @@
## Public and Private API ##
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
***
__File: ["access-point-common.js"](access-point-common.js) Where: {server|client}__
***
### <a name="FS.HTTP.setBaseUrl"></a>*FSHTTP*.setBaseUrl(newBaseUrl)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __setBaseUrl__ is defined in `FS.HTTP`*
__Arguments__
* __newBaseUrl__ *{String}*
Change the base URL for the HTTP GET and DELETE endpoints.
__Returns__ *{undefined}*
> ```FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) { ...``` [access-point-common.js:29](access-point-common.js#L29)
-
### <a name="FS.File.prototype.url"></a>*fsFile*.url([options])&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __url__ is defined in `prototype` of `FS.File`*
__Arguments__
* __options__ *{Object}* (Optional)
* __store__ *{String}* (Optional)
Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
* __auth__ *{Boolean}* (Optional, Default = null)
Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
* __download__ *{Boolean}* (Optional, Default = false)
Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
* __brokenIsFine__ *{Boolean}* (Optional, Default = false)
Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
* __metadata__ *{Boolean}* (Optional, Default = false)
Return the URL for the file metadata access point rather than the file itself.
* __uploading__ *{String}* (Optional, Default = null)
A URL to return while the file is being uploaded.
* __storing__ *{String}* (Optional, Default = null)
A URL to return while the file is being stored.
* __filename__ *{String}* (Optional, Default = null)
Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
Returns the HTTP URL for getting the file or its metadata.
> ```FS.File.prototype.url = function(options) { ...``` [access-point-common.js:72](access-point-common.js#L72)
***
__File: ["access-point-handlers.js"](access-point-handlers.js) Where: {server}__
***
### <a name="FS.HTTP.Handlers.Del"></a>*FSHTTPHandlers*.Del()&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __Del__ is defined in `FS.HTTP.Handlers`*
__Returns__ *{any}*
response
HTTP DEL request handler
> ```FS.HTTP.Handlers.Del = function httpDelHandler(ref) { ...``` [access-point-handlers.js:13](access-point-handlers.js#L13)
-
### <a name="self.setStatusCode"></a>*self*.setStatusCode {any}&nbsp;&nbsp;<sub><i>Server</i></sub> ###
```
From the DELETE spec:
A successful response SHOULD be 200 (OK) if the response includes an
entity describing the status, 202 (Accepted) if the action has not
yet been enacted, or 204 (No Content) if the action has been enacted
but the response does not include an entity.
```
*This property __setStatusCode__ is defined in `self`*
> ```self.setStatusCode(200);``` [access-point-handlers.js:27](access-point-handlers.js#L27)
-
### <a name="FS.HTTP.Handlers.GetList"></a>*FSHTTPHandlers*.GetList()&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __GetList__ is defined in `FS.HTTP.Handlers`*
__Returns__ *{Object}*
response
HTTP GET file list request handler
> ```FS.HTTP.Handlers.GetList = function httpGetListHandler() { ...``` [access-point-handlers.js:41](access-point-handlers.js#L41)
-
### <a name="requestRange"></a>requestRange {any}&nbsp;&nbsp;<sub><i>Server</i></sub> ###
```
requestRange will parse the range set in request header - if not possible it
will throw fitting errors and autofill range for both partial and full ranges
throws error or returns the object:
{
start
end
length
unit
partial
}
```
*This property is private*
> ```var requestRange = function(req, fileSize) { ...``` [access-point-handlers.js:60](access-point-handlers.js#L60)
-
### <a name="FS.HTTP.Handlers.Get"></a>*FSHTTPHandlers*.Get()&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __Get__ is defined in `FS.HTTP.Handlers`*
__Returns__ *{any}*
response
HTTP GET request handler
> ```FS.HTTP.Handlers.Get = function httpGetHandler(ref) { ...``` [access-point-handlers.js:135](access-point-handlers.js#L135)
-
### <a name="FS.HTTP.Handlers.PutInsert"></a>*FSHTTPHandlers*.PutInsert()&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __PutInsert__ is defined in `FS.HTTP.Handlers`*
__Returns__ *{Object}*
response object with _id property
HTTP PUT file insert request handler
> ```FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) { ...``` [access-point-handlers.js:229](access-point-handlers.js#L229)
-
### <a name="FS.HTTP.Handlers.PutUpdate"></a>*FSHTTPHandlers*.PutUpdate()&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __PutUpdate__ is defined in `FS.HTTP.Handlers`*
__Returns__ *{Object}*
response object with _id and chunk properties
HTTP PUT file update chunk request handler
> ```FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { ...``` [access-point-handlers.js:264](access-point-handlers.js#L264)
***
__File: ["access-point-server.js"](access-point-server.js) Where: {server}__
***
### <a name="FS.HTTP.setHeadersForGet"></a>*FSHTTP*.setHeadersForGet(headers, [collections])&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __setHeadersForGet__ is defined in `FS.HTTP`*
__Arguments__
* __headers__ *{Array}*
List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
* __collections__ *{Array|String}* (Optional)
Which collections the headers should be added for. Omit this argument to add the header for all collections.
__Returns__ *{undefined}*
> ```FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) { ...``` [access-point-server.js:24](access-point-server.js#L24)
-
### <a name="FS.HTTP.publish"></a>*FSHTTP*.publish(collection, func)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __publish__ is defined in `FS.HTTP`*
__Arguments__
* __collection__ *{[FS.Collection](#FS.Collection)}*
* __func__ *{Function}*
Publish function that returns a cursor.
__Returns__ *{undefined}*
Publishes all documents returned by the cursor at a GET URL
with the format baseUrl/record/collectionName. The publish
function `this` is similar to normal `Meteor.publish`.
> ```FS.HTTP.publish = function fsHttpPublish(collection, func) { ...``` [access-point-server.js:48](access-point-server.js#L48)
-
### <a name="FS.HTTP.unpublish"></a>*FSHTTP*.unpublish(collection)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __unpublish__ is defined in `FS.HTTP`*
__Arguments__
* __collection__ *{[FS.Collection](#FS.Collection)}*
__Returns__ *{undefined}*
Unpublishes a restpoint created by a call to `FS.HTTP.publish`
> ```FS.HTTP.unpublish = function fsHttpUnpublish(collection) { ...``` [access-point-server.js:73](access-point-server.js#L73)
-
### <a name="defaultSelectorFunction"></a>defaultSelectorFunction()&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method is private*
__Returns__ *{ collection, file }*
This is the default selector function
> ```var defaultSelectorFunction = function() { ...``` [access-point-server.js:87](access-point-server.js#L87)
-
### <a name="FS.HTTP.mount"></a>*FSHTTP*.mount(mountPoints, selector_f)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __mount__ is defined in `FS.HTTP`*
__Arguments__
* __mountPoints__ *{[array of string](#array of string)}*
mount points to map rest functinality on
* __selector_f__ *{function}*
[selector] function returns `{ collection, file }` for mount points to work with
> ```FS.HTTP.mount = function(mountPoints, selector_f) { ...``` [access-point-server.js:125](access-point-server.js#L125)
-
### <a name="FS.HTTP.unmount"></a>*FSHTTP*.unmount([mountPoints])&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __unmount__ is defined in `FS.HTTP`*
__Arguments__
* __mountPoints__ *{[string ](#string )|[ array of string](# array of string)}* (Optional)
Optional, if not specified all mountpoints are unmounted
> ```FS.HTTP.unmount = function(mountPoints) { ...``` [access-point-server.js:223](access-point-server.js#L223)
-
### FS.Collection maps on HTTP pr. default on the following restpoints:
*
baseUrl + '/files/:collectionName/:id/:filename',
baseUrl + '/files/:collectionName/:id',
baseUrl + '/files/:collectionName'
Change/ replace the existing mount point by:
```js
unmount all existing
FS.HTTP.unmount();
Create new mount point
FS.HTTP.mount([
'/cfs/files/:collectionName/:id/:filename',
'/cfs/files/:collectionName/:id',
'/cfs/files/:collectionName'
]);
```

View file

@ -1,65 +0,0 @@
Package.describe({
name: 'wekan-cfs-access-point',
version: '0.1.50',
summary: 'CollectionFS, add ddp and http accesspoint capability',
git: 'https://github.com/zcfs/Meteor-cfs-access-point.git'
});
Npm.depends({
"content-disposition": "0.5.0"
});
Package.onUse(function(api) {
api.versionsFrom('1.0');
// This imply is needed for tests, and is technically probably correct anyway.
api.imply([
'wekan-cfs-base-package'
]);
api.use([
//CFS packages
'wekan-cfs-base-package@0.0.30',
'wekan-cfs-file@0.1.16',
//Core packages
'check',
'ejson',
//Other packages
'wekan-cfs-http-methods@0.0.29',
'wekan-cfs-http-publish@0.0.13'
]);
api.addFiles([
'access-point-common.js',
'access-point-handlers.js',
'access-point-server.js'
], 'server');
api.addFiles([
'access-point-common.js',
'access-point-client.js'
], 'client');
});
Package.onTest(function (api) {
api.versionsFrom('1.0');
api.use([
//CFS packages
'wekan-cfs-access-point',
'wekan-cfs-standard-packages@0.0.2',
'wekan-cfs-gridfs@0.0.0',
//Core packages
'test-helpers',
'http',
'tinytest',
'underscore',
'ejson',
'ordered-dict',
'random',
'deps'
]);
api.addFiles('tests/client-tests.js', 'client');
api.addFiles('tests/server-tests.js', 'server');
});

View file

@ -1,125 +0,0 @@
function equals(a, b) {
return !!(EJSON.stringify(a) === EJSON.stringify(b));
}
Tinytest.add('cfs-access-point - client - test environment', function(test) {
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
test.isTrue(typeof FS.HTTP !== 'undefined', 'test environment not initialized FS.HTTP');
});
Images = new FS.Collection('images', {
stores: [
new FS.Store.GridFS('gridList')
]
});
Meteor.subscribe("img");
var id;
Tinytest.addAsync('cfs-access-point - client - addTestImage', function(test, onComplete) {
Meteor.call('addTestImage', function(err, result) {
id = result;
test.equal(typeof id, "string", "Test image was not inserted properly");
//Don't continue until the data has been stored
Deps.autorun(function (c) {
var img = Images.findOne(id);
if (img && img.hasCopy('gridList')) {
onComplete();
c.stop();
}
});
});
});
Tinytest.addAsync('cfs-access-point - client - GET list of files in collection', function(test, onComplete) {
HTTP.get(Meteor.absoluteUrl('cfs/record/images'), function(err, result) {
// Test the length of array result
var len = result.data && result.data.length;
test.isTrue(!!len, 'Result was empty');
// Get the object
var obj = result.data && result.data[0] || {};
test.equal(obj._id, id, 'Didn\'t get the expected result');
onComplete();
});
});
Tinytest.addAsync('cfs-access-point - client - GET filerecord', function(test, onComplete) {
HTTP.get(Meteor.absoluteUrl('cfs/record/images/' + id), function(err, result) {
// Get the object
var obj = result.data;
test.equal(typeof obj, "object", "Expected object data");
test.equal(obj._id, id, 'Didn\'t get the expected result');
onComplete();
});
});
Tinytest.addAsync('cfs-access-point - client - GET file itself', function(test, onComplete) {
HTTP.get(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) {
test.isTrue(!!result.content, "Expected content in response");
console.log(result);
test.equal(result.statusCode, 200, "Expected 200 OK response");
onComplete();
});
});
Tinytest.addAsync('cfs-access-point - client - PUT new file data (update)', function(test, onComplete) {
// TODO
// HTTP.put(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) {
// test.equal(result.statusCode, 200, "Expected 200 OK response");
onComplete();
// });
});
Tinytest.addAsync('cfs-access-point - client - PUT insert a new file', function(test, onComplete) {
// TODO
// HTTP.put(Meteor.absoluteUrl('cfs/files/images'), function(err, result) {
// test.equal(result.statusCode, 200, "Expected 200 OK response");
onComplete();
// });
});
Tinytest.addAsync('cfs-access-point - client - DELETE filerecord and data', function(test, onComplete) {
HTTP.del(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) {
test.equal(result.statusCode, 200, "Expected 200 OK response");
// Make sure it's gone
HTTP.get(Meteor.absoluteUrl('cfs/record/images/' + id), function(err, result) {
test.isTrue(!!err, 'Expected 404 error');
test.equal(result.statusCode, 404, "Expected 404 response");
onComplete();
});
});
});
//TODO test FS.File.prototype.url method with various options
//Test API:
//test.isFalse(v, msg)
//test.isTrue(v, msg)
//test.equalactual, expected, message, not
//test.length(obj, len)
//test.include(s, v)
//test.isNaN(v, msg)
//test.isUndefined(v, msg)
//test.isNotNull
//test.isNull
//test.throws(func)
//test.instanceOf(obj, klass)
//test.notEqual(actual, expected, message)
//test.runId()
//test.exception(exception)
//test.expect_fail()
//test.ok(doc)
//test.fail(doc)
//test.equal(a, b, msg)

View file

@ -1,68 +0,0 @@
function equals(a, b) {
return !!(EJSON.stringify(a) === EJSON.stringify(b));
}
FS.debug = true;
Tinytest.add('cfs-access-point - server - test environment', function(test) {
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
test.isTrue(typeof FS.HTTP !== 'undefined', 'test environment not initialized FS.HTTP');
});
Images = new FS.Collection('images', {
stores: [
new FS.Store.GridFS('gridList')
]
});
Images.allow({
insert: function() {
return true;
},
update: function() {
return true;
},
remove: function() {
return true;
},
download: function() {
return true;
}
});
Meteor.publish("img", function () {
return Images.find();
});
FS.HTTP.publish(Images, function () {
return Images.find();
});
Meteor.methods({
addTestImage: function() {
Images.remove({});
var url = "http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg";
var fsFile = Images.insert(url);
return fsFile._id;
}
});
//Test API:
//test.isFalse(v, msg)
//test.isTrue(v, msg)
//test.equalactual, expected, message, not
//test.length(obj, len)
//test.include(s, v)
//test.isNaN(v, msg)
//test.isUndefined(v, msg)
//test.isNotNull
//test.isNull
//test.throws(func)
//test.instanceOf(obj, klass)
//test.notEqual(actual, expected, message)
//test.runId()
//test.exception(exception)
//test.expect_fail()
//test.ok(doc)
//test.fail(doc)
//test.equal(a, b, msg)

View file

@ -1,5 +0,0 @@
language: node_js
node_js:
- "0.10"
before_install:
- "curl -L http://git.io/s0Zu-w | /bin/sh"

View file

@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013-2015 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com
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.

View file

@ -1,11 +0,0 @@
wekan-cfs-base-package
=========================
This is a Meteor package used by
[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS).
You don't need to manually add this package to your app. It is added when you
add the `wekan-cfs-standard-packages` package.
This package provides the `FS` namespace and helper methods used by many
CollectionFS packages.

View file

@ -1,213 +0,0 @@
## cfs-base-package Public API ##
CollectionFS, Base package
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
#############################################################################
HELPERS
#############################################################################
-
### <a name="FS.Utility.cloneFileRecord"></a>*fsUtility*.cloneFileRecord(rec, [options])&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __cloneFileRecord__ is defined in `FS.Utility`*
__Arguments__
* __rec__ *{[FS.File](#FS.File)|[FS.Collection filerecord](#FS.Collection filerecord)}*
* __options__ *{Object}* (Optional)
* __full__ *{Boolean}* (Optional, Default = false)
Set `true` to prevent certain properties from being omitted from the clone.
__Returns__ *{Object}*
Cloned filerecord
Makes a shallow clone of `rec`, filtering out some properties that might be present if
it's an FS.File instance, but which we never want to be part of the stored
filerecord.
This is a blacklist clone rather than a whitelist because we want the user to be able
to specify whatever additional properties they wish.
In general, we expect the following whitelist properties used by the internal and
external APIs:
_id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt
Those properties, and any additional properties added by the user, should be present
in the returned object, which is suitable for inserting into the backing collection or
extending an FS.File instance.
> ```FS.Utility.cloneFileRecord = function(rec, options) { ...``` [base-common.js:71](base-common.js#L71)
-
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([err])&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __defaultCallback__ is defined in `FS.Utility`*
__Arguments__
* __err__ *{[Error](#Error)}* (Optional)
__Returns__ *{undefined}*
Can be used as a default callback for client methods that need a callback.
Simply throws the provided error if there is one.
> ```FS.Utility.defaultCallback = function defaultCallback(err) { ...``` [base-common.js:96](base-common.js#L96)
-
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([f], [err])&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __defaultCallback__ is defined in `FS.Utility`*
__Arguments__
* __f__ *{Function}* (Optional)
A callback function, if you have one. Can be undefined or null.
* __err__ *{[Meteor.Error ](#Meteor.Error )|[ Error ](# Error )|[ String](# String)}* (Optional)
Error or error message (string)
__Returns__ *{Any}*
the callback result if any
Handle Error, creates an Error instance with the given text. If callback is
a function, passes the error to that function. Otherwise throws it. Useful
for dealing with errors in methods that optionally accept a callback.
> ```FS.Utility.handleError = function(f, err, result) { ...``` [base-common.js:120](base-common.js#L120)
-
### <a name="FS.Utility.noop"></a>*fsUtility*.noop()&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __noop__ is defined in `FS.Utility`*
Use this to hand a no operation / empty function
> ```FS.Utility.noop = function() { ...``` [base-common.js:134](base-common.js#L134)
-
### <a name="FS.Utility.getFileExtension"></a>*fsUtility*.getFileExtension(name)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __getFileExtension__ is defined in `FS.Utility`*
__Arguments__
* __name__ *{String}*
A filename, filepath, or URL that may or may not have an extension.
__Returns__ *{String}*
The extension or an empty string if no extension found.
> ```FS.Utility.getFileExtension = function utilGetFileExtension(name) { ...``` [base-common.js:205](base-common.js#L205)
-
### <a name="FS.Utility.setFileExtension"></a>*fsUtility*.setFileExtension(name, ext)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __setFileExtension__ is defined in `FS.Utility`*
__Arguments__
* __name__ *{String}*
A filename that may or may not already have an extension.
* __ext__ *{String}*
An extension without leading period, which you want to be the new extension on `name`.
__Returns__ *{String}*
The filename with changed extension.
> ```FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) { ...``` [base-common.js:222](base-common.js#L222)
-
### <a name="FS.Utility.binaryToBuffer"></a>*fsUtility*.binaryToBuffer(data)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __binaryToBuffer__ is defined in `FS.Utility`*
__Arguments__
* __data__ *{Uint8Array}*
__Returns__ *{Buffer}*
Converts a Uint8Array instance to a Node Buffer instance
> ```FS.Utility.binaryToBuffer = function(data) { ...``` [base-server.js:9](base-server.js#L9)
-
### <a name="FS.Utility.bufferToBinary"></a>*fsUtility*.bufferToBinary(data)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __bufferToBinary__ is defined in `FS.Utility`*
__Arguments__
* __data__ *{Buffer}*
__Returns__ *{Uint8Array}*
Converts a Node Buffer instance to a Uint8Array instance
> ```FS.Utility.bufferToBinary = function(data) { ...``` [base-server.js:26](base-server.js#L26)
-
### <a name="FS.Utility.eachFile"></a>*fsUtility*.eachFile(e, f)&nbsp;&nbsp;<sub><i>Client</i></sub> ###
*This method __eachFile__ is defined in `FS.Utility`*
__Arguments__
* __e__ *{[Event](#Event)}*
Browser event
* __f__ *{Function}*
Function to run for each file found in the event.
__Returns__ *{undefined}*
Utility for iteration over files in event
> ```FS.Utility.eachFile = function(e, f) { ...``` [base-client.js:37](base-client.js#L37)

View file

@ -1,51 +0,0 @@
//XXX not sure this is still working properly?
FS.Utility.connectionLogin = function(connection) {
// We check if the accounts package is installed, since we depend on
// `Meteor.userId()`
if (typeof Accounts !== 'undefined') {
// Monitor logout from main connection
Meteor.startup(function() {
Tracker.autorun(function() {
var userId = Meteor.userId();
if (userId) {
connection.onReconnect = function() {
var token = Accounts._storedLoginToken();
connection.apply('login', [{resume: token}], function(err, result) {
if (!err && result) {
connection.setUserId(result.id);
}
});
};
} else {
connection.onReconnect = null;
connection.setUserId(null);
}
});
});
}
};
/**
* @method FS.Utility.eachFile
* @public
* @param {Event} e - Browser event
* @param {Function} f - Function to run for each file found in the event.
* @returns {undefined}
*
* Utility for iteration over files in event
*/
FS.Utility.eachFile = function(e, f) {
var evt = (e.originalEvent || e);
var files = evt.target.files;
if (!files || files.length === 0) {
files = evt.dataTransfer ? evt.dataTransfer.files : [];
}
for (var i = 0; i < files.length; i++) {
f(files[i], i);
}
};

View file

@ -1,317 +0,0 @@
// Exported namespace
FS = {};
// namespace for adapters; XXX should this be added by cfs-storage-adapter pkg instead?
FS.Store = {
GridFS: function () {
throw new Error('To use FS.Store.GridFS, you must add the "wekan-cfs-gridfs" package.');
},
FileSystem: function () {
throw new Error('To use FS.Store.FileSystem, you must add the "wekan-cfs-filesystem" package.');
},
S3: function () {
throw new Error('To use FS.Store.S3, you must add the "wekan-cfs-s3" package.');
},
WABS: function () {
throw new Error('To use FS.Store.WABS, you must add the "wekan-cfs-wabs" package.');
},
Dropbox: function () {
throw new Error('To use FS.Store.Dropbox, you must add the "wekan-cfs-dropbox" package.');
}
};
// namespace for access points
FS.AccessPoint = {};
// namespace for utillities
FS.Utility = {};
// A general place for any package to store global config settings
FS.config = {};
// An internal collection reference
FS._collections = {};
// Test scope
_Utility = {};
// #############################################################################
//
// HELPERS
//
// #############################################################################
/** @method _Utility.defaultZero
* @private
* @param {Any} val Returns number or 0 if value is a falsy
*/
_Utility.defaultZero = function(val) {
return +(val || 0);
};
/**
* @method FS.Utility.cloneFileRecord
* @public
* @param {FS.File|FS.Collection filerecord} rec
* @param {Object} [options]
* @param {Boolean} [options.full=false] Set `true` to prevent certain properties from being omitted from the clone.
* @returns {Object} Cloned filerecord
*
* Makes a shallow clone of `rec`, filtering out some properties that might be present if
* it's an FS.File instance, but which we never want to be part of the stored
* filerecord.
*
* This is a blacklist clone rather than a whitelist because we want the user to be able
* to specify whatever additional properties they wish.
*
* In general, we expect the following whitelist properties used by the internal and
* external APIs:
*
* _id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt
*
* Those properties, and any additional properties added by the user, should be present
* in the returned object, which is suitable for inserting into the backing collection or
* extending an FS.File instance.
*
*/
FS.Utility.cloneFileRecord = function(rec, options) {
options = options || {};
var result = {};
// We use this method for two purposes. If using it to clone one FS.File into another, then
// we want a full clone. But if using it to get a filerecord object for inserting into the
// internal collection, then there are certain properties we want to omit so that they aren't
// stored in the collection.
var omit = options.full ? [] : ['collectionName', 'collection', 'data', 'createdByTransform'];
for (var prop in rec) {
if (rec.hasOwnProperty(prop) && !_.contains(omit, prop)) {
result[prop] = rec[prop];
}
}
return result;
};
/**
* @method FS.Utility.defaultCallback
* @public
* @param {Error} [err]
* @returns {undefined}
*
* Can be used as a default callback for client methods that need a callback.
* Simply throws the provided error if there is one.
*/
FS.Utility.defaultCallback = function defaultCallback(err) {
if (err) {
// Show gentle error if Meteor error
if (err instanceof Meteor.Error) {
console.error(err.message);
} else {
// Normal error, just throw error
throw err;
}
}
};
/**
* @method FS.Utility.defaultCallback
* @public
* @param {Function} [f] A callback function, if you have one. Can be undefined or null.
* @param {Meteor.Error | Error | String} [err] Error or error message (string)
* @returns {Any} the callback result if any
*
* Handle Error, creates an Error instance with the given text. If callback is
* a function, passes the error to that function. Otherwise throws it. Useful
* for dealing with errors in methods that optionally accept a callback.
*/
FS.Utility.handleError = function(f, err, result) {
// Set callback
var callback = (typeof f === 'function')? f : FS.Utility.defaultCallback;
// Set the err
var error = (err === ''+err)? new Error(err) : err;
// callback
return callback(error, result);
}
/**
* @method FS.Utility.noop
* @public
* Use this to hand a no operation / empty function
*/
FS.Utility.noop = function() {};
/**
* @method validateAction
* @private
* @param {Object} validators - The validators object to use, with `deny` and `allow` properties.
* @param {FS.File} fileObj - Mounted or mountable file object to be passed to validators.
* @param {String} userId - The ID of the user who is attempting the action.
* @returns {undefined}
*
* Throws a "400-Bad Request" Meteor error if the file is not mounted or
* a "400-Access denied" Meteor error if the action is not allowed.
*/
FS.Utility.validateAction = function validateAction(validators, fileObj, userId) {
var denyValidators = validators.deny;
var allowValidators = validators.allow;
// If insecure package is used and there are no validators defined,
// allow the action.
if (typeof Package === 'object'
&& Package.insecure
&& denyValidators.length + allowValidators.length === 0) {
return;
}
// If already mounted, validators should receive a fileObj
// that is fully populated
if (fileObj.isMounted()) {
fileObj.getFileRecord();
}
// Any deny returns true means denied.
if (_.any(denyValidators, function(validator) {
return validator(userId, fileObj);
})) {
throw new Meteor.Error(403, "Access denied");
}
// Any allow returns true means proceed. Throw error if they all fail.
if (_.all(allowValidators, function(validator) {
return !validator(userId, fileObj);
})) {
throw new Meteor.Error(403, "Access denied");
}
};
/**
* @method FS.Utility.getFileName
* @private
* @param {String} name - A filename, filepath, or URL
* @returns {String} The filename without the URL, filepath, or query string
*/
FS.Utility.getFileName = function utilGetFileName(name) {
// in case it's a URL, strip off potential query string
// should have no effect on filepath
name = name.split('?')[0];
// strip off beginning path or url
var lastSlash = name.lastIndexOf('/');
if (lastSlash !== -1) {
name = name.slice(lastSlash + 1);
}
return name;
};
/**
* @method FS.Utility.getFileExtension
* @public
* @param {String} name - A filename, filepath, or URL that may or may not have an extension.
* @returns {String} The extension or an empty string if no extension found.
*/
FS.Utility.getFileExtension = function utilGetFileExtension(name) {
name = FS.Utility.getFileName(name);
// Seekout the last '.' if found
var found = name.lastIndexOf('.');
// Return the extension if found else ''
// If found is -1, we return '' because there is no extension
// If found is 0, we return '' because it's a hidden file
return (found > 0 ? name.slice(found + 1).toLowerCase() : '');
};
/**
* @method FS.Utility.setFileExtension
* @public
* @param {String} name - A filename that may or may not already have an extension.
* @param {String} ext - An extension without leading period, which you want to be the new extension on `name`.
* @returns {String} The filename with changed extension.
*/
FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) {
if (!name || !name.length) {
return name;
}
var currentExt = FS.Utility.getFileExtension(name);
if (currentExt.length) {
name = name.slice(0, currentExt.length * -1) + ext;
} else {
name = name + '.' + ext;
}
return name;
};
/*
* Borrowed these from http package
*/
FS.Utility.encodeParams = function encodeParams(params) {
var buf = [];
_.each(params, function(value, key) {
if (buf.length)
buf.push('&');
buf.push(FS.Utility.encodeString(key), '=', FS.Utility.encodeString(value));
});
return buf.join('').replace(/%20/g, '+');
};
FS.Utility.encodeString = function encodeString(str) {
return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
};
/*
* btoa and atob shims for client and server
*/
FS.Utility._btoa = function _fsUtility_btoa(str) {
var buffer;
if (str instanceof Buffer) {
buffer = str;
} else {
buffer = new Buffer(str.toString(), 'binary');
}
return buffer.toString('base64');
};
FS.Utility.btoa = function fsUtility_btoa(str) {
if (typeof btoa === 'function') {
// Client
return btoa(str);
} else if (typeof Buffer !== 'undefined') {
// Server
return FS.Utility._btoa(str);
} else {
throw new Error('FS.Utility.btoa: Cannot base64 encode on your system');
}
};
FS.Utility._atob = function _fsUtility_atob(str) {
return new Buffer(str, 'base64').toString('binary');
};
FS.Utility.atob = function fsUtility_atob(str) {
if (typeof atob === 'function') {
// Client
return atob(str);
} else if (typeof Buffer !== 'undefined') {
// Server
return FS.Utility._atob(str);
} else {
throw new Error('FS.Utility.atob: Cannot base64 encode on your system');
}
};
// Api wrap for 3party libs like underscore
FS.Utility.extend = _.extend;
FS.Utility.each = _.each;
FS.Utility.isEmpty = _.isEmpty;
FS.Utility.indexOf = _.indexOf;
FS.Utility.isArray = _.isArray;
FS.Utility.map = _.map;
FS.Utility.once = _.once;
FS.Utility.include = _.include;
FS.Utility.size = _.size;

View file

@ -1,95 +0,0 @@
/**
* @method FS.Utility.binaryToBuffer
* @public
* @param {Uint8Array} data
* @returns {Buffer}
*
* Converts a Uint8Array instance to a Node Buffer instance
*/
FS.Utility.binaryToBuffer = function(data) {
var len = data.length;
var buffer = new Buffer(len);
for (var i = 0; i < len; i++) {
buffer[i] = data[i];
}
return buffer;
};
/**
* @method FS.Utility.bufferToBinary
* @public
* @param {Buffer} data
* @returns {Uint8Array}
*
* Converts a Node Buffer instance to a Uint8Array instance
*/
FS.Utility.bufferToBinary = function(data) {
var len = data.length;
var binary = EJSON.newBinary(len);
for (var i = 0; i < len; i++) {
binary[i] = data[i];
}
return binary;
};
/**
* @method FS.Utility.safeCallback
* @public
* @param {Function} callback
* @returns {Function}
*
* Makes a callback safe for Meteor code
*/
FS.Utility.safeCallback = function (callback) {
return Meteor.bindEnvironment(callback, function(err) { throw err; });
};
/**
* @method FS.Utility.safeStream
* @public
* @param {Stream} nodestream
* @returns {Stream}
*
* Adds `safeOn` and `safeOnce` methods to a NodeJS Stream
* object. These are the same as `on` and `once`, except
* that the callback is wrapped for use in Meteor.
*/
FS.Utility.safeStream = function(nodestream) {
if (!nodestream || typeof nodestream.on !== 'function')
throw new Error('FS.Utility.safeStream requires a NodeJS Stream');
// Create Meteor safe events
nodestream.safeOn = function(name, callback) {
return nodestream.on(name, FS.Utility.safeCallback(callback));
};
// Create Meteor safe events
nodestream.safeOnce = function(name, callback) {
return nodestream.once(name, FS.Utility.safeCallback(callback));
};
// Return the modified stream - modified anyway
return nodestream;
};
/**
* @method FS.Utility.eachFileFromPath
* @public
* @param {String} p - Server path
* @param {Function} f - Function to run for each file found in the path.
* @returns {undefined}
*
* Utility for iteration over files from path on server
*/
FS.Utility.eachFileFromPath = function(p, f) {
var fs = Npm.require('fs');
var path = Npm.require('path');
var files = fs.readdirSync(p);
files.map(function (file) {
return path.join(p, file);
}).filter(function (filePath) {
return fs.statSync(filePath).isFile() && path.basename(filePath)[0] !== '.';
}).forEach(function (filePath) {
f(filePath);
});
};

View file

@ -1,293 +0,0 @@
## Public and Private API ##
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
***
__File: ["base-common.js"](base-common.js) Where: {server|client}__
***
#############################################################################
HELPERS
#############################################################################
-
### <a name="_Utility.defaultZero"></a>*_utility*.defaultZero(val)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method is private*
*This method __defaultZero__ is defined in `_Utility`*
__Arguments__
* __val__ *{Any}*
Returns number or 0 if value is a falsy
> ```_Utility.defaultZero = function(val) { ...``` [base-common.js:42](base-common.js#L42)
-
### <a name="FS.Utility.cloneFileRecord"></a>*fsUtility*.cloneFileRecord(rec, [options])&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __cloneFileRecord__ is defined in `FS.Utility`*
__Arguments__
* __rec__ *{[FS.File](#FS.File)|[FS.Collection filerecord](#FS.Collection filerecord)}*
* __options__ *{Object}* (Optional)
* __full__ *{Boolean}* (Optional, Default = false)
Set `true` to prevent certain properties from being omitted from the clone.
__Returns__ *{Object}*
Cloned filerecord
Makes a shallow clone of `rec`, filtering out some properties that might be present if
it's an FS.File instance, but which we never want to be part of the stored
filerecord.
This is a blacklist clone rather than a whitelist because we want the user to be able
to specify whatever additional properties they wish.
In general, we expect the following whitelist properties used by the internal and
external APIs:
_id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt
Those properties, and any additional properties added by the user, should be present
in the returned object, which is suitable for inserting into the backing collection or
extending an FS.File instance.
> ```FS.Utility.cloneFileRecord = function(rec, options) { ...``` [base-common.js:71](base-common.js#L71)
-
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([err])&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __defaultCallback__ is defined in `FS.Utility`*
__Arguments__
* __err__ *{[Error](#Error)}* (Optional)
__Returns__ *{undefined}*
Can be used as a default callback for client methods that need a callback.
Simply throws the provided error if there is one.
> ```FS.Utility.defaultCallback = function defaultCallback(err) { ...``` [base-common.js:96](base-common.js#L96)
-
### <a name="FS.Utility.defaultCallback"></a>*fsUtility*.defaultCallback([f], [err])&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __defaultCallback__ is defined in `FS.Utility`*
__Arguments__
* __f__ *{Function}* (Optional)
A callback function, if you have one. Can be undefined or null.
* __err__ *{[Meteor.Error ](#Meteor.Error )|[ Error ](# Error )|[ String](# String)}* (Optional)
Error or error message (string)
__Returns__ *{Any}*
the callback result if any
Handle Error, creates an Error instance with the given text. If callback is
a function, passes the error to that function. Otherwise throws it. Useful
for dealing with errors in methods that optionally accept a callback.
> ```FS.Utility.handleError = function(f, err, result) { ...``` [base-common.js:120](base-common.js#L120)
-
### <a name="FS.Utility.noop"></a>*fsUtility*.noop()&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __noop__ is defined in `FS.Utility`*
Use this to hand a no operation / empty function
> ```FS.Utility.noop = function() { ...``` [base-common.js:134](base-common.js#L134)
-
### <a name="validateAction"></a>validateAction(validators, fileObj, userId)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method is private*
__Arguments__
* __validators__ *{Object}*
The validators object to use, with `deny` and `allow` properties.
* __fileObj__ *{[FS.File](#FS.File)}*
Mounted or mountable file object to be passed to validators.
* __userId__ *{String}*
The ID of the user who is attempting the action.
__Returns__ *{undefined}*
Throws a "400-Bad Request" Meteor error if the file is not mounted or
a "400-Access denied" Meteor error if the action is not allowed.
> ```FS.Utility.validateAction = function validateAction(validators, fileObj, userId) { ...``` [base-common.js:147](base-common.js#L147)
-
### <a name="FS.Utility.getFileName"></a>*fsUtility*.getFileName(name)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method is private*
*This method __getFileName__ is defined in `FS.Utility`*
__Arguments__
* __name__ *{String}*
A filename, filepath, or URL
__Returns__ *{String}*
The filename without the URL, filepath, or query string
> ```FS.Utility.getFileName = function utilGetFileName(name) { ...``` [base-common.js:187](base-common.js#L187)
-
### <a name="FS.Utility.getFileExtension"></a>*fsUtility*.getFileExtension(name)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __getFileExtension__ is defined in `FS.Utility`*
__Arguments__
* __name__ *{String}*
A filename, filepath, or URL that may or may not have an extension.
__Returns__ *{String}*
The extension or an empty string if no extension found.
> ```FS.Utility.getFileExtension = function utilGetFileExtension(name) { ...``` [base-common.js:205](base-common.js#L205)
-
### <a name="FS.Utility.setFileExtension"></a>*fsUtility*.setFileExtension(name, ext)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __setFileExtension__ is defined in `FS.Utility`*
__Arguments__
* __name__ *{String}*
A filename that may or may not already have an extension.
* __ext__ *{String}*
An extension without leading period, which you want to be the new extension on `name`.
__Returns__ *{String}*
The filename with changed extension.
> ```FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) { ...``` [base-common.js:222](base-common.js#L222)
***
__File: ["base-server.js"](base-server.js) Where: {server}__
***
### <a name="FS.Utility.binaryToBuffer"></a>*fsUtility*.binaryToBuffer(data)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __binaryToBuffer__ is defined in `FS.Utility`*
__Arguments__
* __data__ *{Uint8Array}*
__Returns__ *{Buffer}*
Converts a Uint8Array instance to a Node Buffer instance
> ```FS.Utility.binaryToBuffer = function(data) { ...``` [base-server.js:9](base-server.js#L9)
-
### <a name="FS.Utility.bufferToBinary"></a>*fsUtility*.bufferToBinary(data)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __bufferToBinary__ is defined in `FS.Utility`*
__Arguments__
* __data__ *{Buffer}*
__Returns__ *{Uint8Array}*
Converts a Node Buffer instance to a Uint8Array instance
> ```FS.Utility.bufferToBinary = function(data) { ...``` [base-server.js:26](base-server.js#L26)
***
__File: ["base-client.js"](base-client.js) Where: {client}__
***
### <a name="FS.Utility.eachFile"></a>*fsUtility*.eachFile(e, f)&nbsp;&nbsp;<sub><i>Client</i></sub> ###
*This method __eachFile__ is defined in `FS.Utility`*
__Arguments__
* __e__ *{[Event](#Event)}*
Browser event
* __f__ *{Function}*
Function to run for each file found in the event.
__Returns__ *{undefined}*
Utility for iteration over files in event
> ```FS.Utility.eachFile = function(e, f) { ...``` [base-client.js:37](base-client.js#L37)

View file

@ -1,37 +0,0 @@
Package.describe({
version: '0.0.30',
name: 'wekan-cfs-base-package',
summary: 'CollectionFS, Base package',
git: 'https://github.com/zcfs/Meteor-cfs-base-package.git'
});
Package.onUse(function(api) {
api.versionsFrom('1.0');
api.use(['deps', 'underscore', 'ejson']);
if (api.export) {
api.export('FS');
api.export('_Utility', { testOnly: true });
}
api.addFiles([
'base-common.js',
'base-server.js'
], 'server');
api.addFiles([
'polyfill.base64.js',
'base-common.js',
'base-client.js'
], 'client');
});
// Package.on_test(function (api) {
// api.use(['wekan-cfs-base-package', 'cfs-file']);
// api.use('test-helpers', 'server');
// api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict',
// 'random', 'deps']);
// api.add_files('tests/common-tests.js', ['client', 'server']);
// });

View file

@ -1,179 +0,0 @@
/*
* Copyright (c) 2010 Nick Galbreath
* http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
*
* 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.
*/
/* base64 encode/decode compatible with window.btoa/atob
*
* window.atob/btoa is a Firefox extension to convert binary data (the "b")
* to base64 (ascii, the "a").
*
* It is also found in Safari and Chrome. It is not available in IE.
*
* if (!window.btoa) window.btoa = base64.encode
* if (!window.atob) window.atob = base64.decode
*
* The original spec's for atob/btoa are a bit lacking
* https://developer.mozilla.org/en/DOM/window.atob
* https://developer.mozilla.org/en/DOM/window.btoa
*
* window.btoa and base64.encode takes a string where charCodeAt is [0,255]
* If any character is not [0,255], then an DOMException(5) is thrown.
*
* window.atob and base64.decode take a base64-encoded string
* If the input length is not a multiple of 4, or contains invalid characters
* then an DOMException(5) is thrown.
*/
var base64 = {};
base64.PADCHAR = '=';
base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
base64.makeDOMException = function() {
// sadly in FF,Safari,Chrome you can't make a DOMException
var e, tmp;
try {
return new DOMException(DOMException.INVALID_CHARACTER_ERR);
} catch (tmp) {
// not available, just passback a duck-typed equiv
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error/prototype
var ex = new Error("DOM Exception 5");
// ex.number and ex.description is IE-specific.
ex.code = ex.number = 5;
ex.name = ex.description = "INVALID_CHARACTER_ERR";
// Safari/Chrome output format
ex.toString = function() { return 'Error: ' + ex.name + ': ' + ex.message; };
return ex;
}
}
base64.getbyte64 = function(s,i) {
// This is oddly fast, except on Chrome/V8.
// Minimal or no improvement in performance by using a
// object with properties mapping chars to value (eg. 'A': 0)
var idx = base64.ALPHA.indexOf(s.charAt(i));
if (idx === -1) {
throw base64.makeDOMException();
}
return idx;
}
base64.decode = function(s) {
// convert to string
s = '' + s;
var getbyte64 = base64.getbyte64;
var pads, i, b10;
var imax = s.length
if (imax === 0) {
return s;
}
if (imax % 4 !== 0) {
throw base64.makeDOMException();
}
pads = 0
if (s.charAt(imax - 1) === base64.PADCHAR) {
pads = 1;
if (s.charAt(imax - 2) === base64.PADCHAR) {
pads = 2;
}
// either way, we want to ignore this last block
imax -= 4;
}
var x = [];
for (i = 0; i < imax; i += 4) {
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
(getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
}
switch (pads) {
case 1:
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6);
x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
break;
case 2:
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
x.push(String.fromCharCode(b10 >> 16));
break;
}
return x.join('');
}
base64.getbyte = function(s,i) {
var x = s.charCodeAt(i);
if (x > 255) {
throw base64.makeDOMException();
}
return x;
}
base64.encode = function(s) {
if (arguments.length !== 1) {
throw new SyntaxError("Not enough arguments");
}
var padchar = base64.PADCHAR;
var alpha = base64.ALPHA;
var getbyte = base64.getbyte;
var i, b10;
var x = [];
// convert to string
s = '' + s;
var imax = s.length - s.length % 3;
if (s.length === 0) {
return s;
}
for (i = 0; i < imax; i += 3) {
b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
x.push(alpha.charAt(b10 >> 18));
x.push(alpha.charAt((b10 >> 12) & 0x3F));
x.push(alpha.charAt((b10 >> 6) & 0x3f));
x.push(alpha.charAt(b10 & 0x3f));
}
switch (s.length - imax) {
case 1:
b10 = getbyte(s,i) << 16;
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
padchar + padchar);
break;
case 2:
b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
alpha.charAt((b10 >> 6) & 0x3f) + padchar);
break;
}
return x.join('');
}
if (!window.btoa) window.btoa = base64.encode
if (!window.atob) window.atob = base64.decode

View file

@ -1,161 +0,0 @@
function equals(a, b) {
return EJSON.stringify(a) === EJSON.stringify(b);
}
Tinytest.add('cfs-base-package - test environment', function(test) {
test.isTrue(typeof FS !== 'undefined',
'FS scope not declared');
test.isTrue(typeof FS.Store !== 'undefined',
'FS scope "FS.Store" not declared');
test.isTrue(typeof FS.AccessPoint !== 'undefined',
'FS scope "FS.AccessPoint" not declared');
test.isTrue(typeof FS.Utility !== 'undefined',
'FS scope "FS.Utility" not declared');
test.isTrue(typeof FS._collections !== 'undefined',
'FS scope "FS._collections" not declared');
test.isTrue(typeof _Utility !== 'undefined',
'_Utility test scope not declared');
});
Tinytest.add('cfs-base-package - _Utility.defaultZero', function(test) {
test.equal(_Utility.defaultZero(), 0, 'Failes to return 0 when (undefined)');
test.equal(_Utility.defaultZero(undefined), 0, 'Failes to return 0 when undefined');
test.equal(_Utility.defaultZero(null), 0, 'Failes to return 0 when null');
test.equal(_Utility.defaultZero(false), 0, 'Failes to return 0 when false');
test.equal(_Utility.defaultZero(0), 0, 'Failes to return 0 when 0');
test.equal(_Utility.defaultZero(-1), -1, 'Failes to return -1');
test.equal(_Utility.defaultZero(1), 1, 'Failes to return 1');
test.equal(_Utility.defaultZero(-0.1), -0.1, 'Failes to return -0.1');
test.equal(_Utility.defaultZero(0.1), 0.1, 'Failes to return 0.1');
test.equal(_Utility.defaultZero(''), 0, 'Failes to return ""');
test.equal(_Utility.defaultZero({}), NaN, 'Failes to return NaN when object');
test.equal(_Utility.defaultZero("dfdsfs"), NaN, 'Failes to return NaN when string');
test.equal(_Utility.defaultZero("1"), 1, 'Failes to return 1 when string "1"');
});
Tinytest.add('cfs-base-package - FS.Utility.cloneFileRecord', function(test) {
// Given an object with any props, should filter out 'collectionName',
// 'collection', 'data', and 'createdByTransform'
var result = FS.Utility.cloneFileRecord({a: 1, b: {c: 1}, d: [1, 2], collectionName: 'test', collection: {}, data: {}, createdByTransform: false});
test.equal(result, {a: 1, b: {c: 1}, d: [1, 2]});
// Given an FS.File instance, should filter out 'collectionName',
// 'collection', 'data', and 'createdByTransform' and return a plain Object
var fileObj = new FS.File({a: 1, b: {c: 1}, d: [1, 2], name: 'name.png', type: 'image/png', size: 100, collectionName: 'test', collection: {}, data: {}, createdByTransform: false});
test.isTrue(fileObj instanceof FS.File);
var result = FS.Utility.cloneFileRecord(fileObj);
test.isFalse(result instanceof FS.File);
test.isTrue(equals(result, {a: 1, b: {c: 1}, d: [1, 2], name: 'name.png', type: 'image/png', size: 100}));
});
Tinytest.add('cfs-base-package - FS.Utility.defaultCallback', function(test) {
// should throw an error passed in, but not a Meteor.Error
test.throws(function () {
var cb = FS.Utility.defaultCallback;
cb(new Error('test'));
});
var cb2 = FS.Utility.defaultCallback;
test.isUndefined(cb2(new Meteor.Error('test')));
});
Tinytest.add('cfs-base-package - FS.Utility.handleError', function(test) {
test.isTrue(true);
// TODO
});
Tinytest.add('cfs-base-package - FS.Utility.binaryToBuffer', function(test) {
test.isTrue(true);
// TODO
});
Tinytest.add('cfs-base-package - FS.Utility.bufferToBinary', function(test) {
test.isTrue(true);
// TODO
});
Tinytest.add('cfs-base-package - FS.Utility.connectionLogin', function(test) {
test.isTrue(true);
// TODO
});
Tinytest.add('cfs-base-package - FS.Utility.getFileName', function(test) {
function t(input, expected) {
var ext = FS.Utility.getFileName(input);
test.equal(ext, expected, 'Got incorrect filename');
}
t('bar.png', 'bar.png');
t('foo/bar.png', 'bar.png');
t('/foo/foo/bar.png', 'bar.png');
t('http://foobar.com/file.png', 'file.png');
t('http://foobar.com/file', 'file');
t('http://foobar.com/file.png?a=b', 'file.png');
t('http://foobar.com/.file?a=b', '.file');
t('file', 'file');
t('.file', '.file');
t('foo/.file', '.file');
t('/foo/foo/.file', '.file');
});
Tinytest.add('cfs-base-package - FS.Utility.getFileExtension', function(test) {
function t(input, expected) {
var ext = FS.Utility.getFileExtension(input);
test.equal(ext, expected, 'Got incorrect extension');
}
t('bar.png', 'png');
t('foo/bar.png', 'png');
t('/foo/foo/bar.png', 'png');
t('http://foobar.com/file.png', 'png');
t('http://foobar.com/file', '');
t('http://foobar.com/file.png?a=b', 'png');
t('http://foobar.com/file?a=b', '');
t('file', '');
t('.file', '');
t('foo/.file', '');
t('/foo/foo/.file', '');
});
Tinytest.add('cfs-base-package - FS.Utility.setFileExtension', function(test) {
function t(name, ext, expected) {
var newName = FS.Utility.setFileExtension(name, ext);
test.equal(newName, expected, 'Extension was not set correctly');
}
t('bar.png', 'jpeg', 'bar.jpeg');
t('bar', 'jpeg', 'bar.jpeg');
t('.bar', 'jpeg', '.bar.jpeg');
t('', 'jpeg', '');
t(null, 'jpeg', null);
});
//Test API:
//Tinytest.add('', function(test) {});
//Tinytest.addAsync('', function(test, onComplete) {});
//test.isFalse(v, msg)
//test.isTrue(v, msg)
//test.equalactual, expected, message, not
//test.length(obj, len)
//test.include(s, v)
//test.isNaN(v, msg)
//test.isUndefined(v, msg)
//test.isNotNull
//test.isNull
//test.throws(func)
//test.instanceOf(obj, klass)
//test.notEqual(actual, expected, message)
//test.runId()
//test.exception(exception)
//test.expect_fail()
//test.ok(doc)
//test.fail(doc)
//test.equal(a, b, msg)

View file

@ -1,5 +0,0 @@
language: node_js
node_js:
- "0.10"
before_install:
- "curl -L http://git.io/s0Zu-w | /bin/sh"

View file

@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013-2014 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com
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.

View file

@ -1,8 +0,0 @@
wekan-cfs-collection-filters
=========================
This is a Meteor package used by
[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS).
You don't need to manually add this package to your app. It is added when you
add the `wekan-cfs-standard-packages` package.

View file

@ -1,44 +0,0 @@
## cfs-collection-filters Public API ##
CollectionFS, adds FS.Collection filters
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
-
### <a name="FS.Collection.prototype.filters"></a>*fsCollection*.filters(filters)&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __filters__ is defined in `prototype` of `FS.Collection`*
__Arguments__
* __filters__ *{Object}*
File filters for this collection.
__Returns__ *{undefined}*
> ```FS.Collection.prototype.filters = function fsColFilters(filters) { ...``` [filters.js:7](filters.js#L7)
-
### <a name="FS.Collection.prototype.allowsFile"></a>*fsCollection*.allowsFile()&nbsp;&nbsp;<sub><i>Anywhere</i></sub> ###
*This method __allowsFile__ is defined in `prototype` of `FS.Collection`*
__Returns__ *{boolean}*
True if the collection allows this file.
Checks based on any filters defined on the collection. If the
file is not valid according to the filters, this method returns false
and also calls the filter `onInvalid` method defined for the
collection, passing it an English error string that explains why it
failed.
> ```FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) { ...``` [filters.js:108](filters.js#L108)

View file

@ -1,191 +0,0 @@
/**
* @method FS.Collection.prototype.filters
* @public
* @param {Object} filters - File filters for this collection.
* @returns {undefined}
*/
FS.Collection.prototype.filters = function fsColFilters(filters) {
var self = this;
// Check filter option values and normalize them for quicker checking later
if (filters) {
// check/adjust allow/deny
FS.Utility.each(['allow', 'deny'], function (type) {
if (!filters[type]) {
filters[type] = {};
} else if (typeof filters[type] !== "object") {
throw new Error(type + ' filter must be an object');
}
});
// check/adjust maxSize
if (typeof filters.maxSize === "undefined") {
filters.maxSize = null;
} else if (filters.maxSize && typeof filters.maxSize !== "number") {
throw new Error('maxSize filter must be an number');
}
// check/adjust extensions
FS.Utility.each(['allow', 'deny'], function (type) {
if (!filters[type].extensions) {
filters[type].extensions = [];
} else if (!FS.Utility.isArray(filters[type].extensions)) {
throw new Error(type + '.extensions filter must be an array of extensions');
} else {
//convert all to lowercase
for (var i = 0, ln = filters[type].extensions.length; i < ln; i++) {
filters[type].extensions[i] = filters[type].extensions[i].toLowerCase();
}
}
});
// check/adjust content types
FS.Utility.each(['allow', 'deny'], function (type) {
if (!filters[type].contentTypes) {
filters[type].contentTypes = [];
} else if (!FS.Utility.isArray(filters[type].contentTypes)) {
throw new Error(type + '.contentTypes filter must be an array of content types');
}
});
self.options.filter = filters;
}
// Define deny functions to enforce file filters on the server
// for inserts and updates that initiate from untrusted code.
self.files.deny({
insert: function(userId, fsFile) {
return !self.allowsFile(fsFile);
},
update: function(userId, fsFile, fields, modifier) {
// TODO will need some kind of additional security here:
// Don't allow them to change the type, size, name, and
// anything else that would be security or data integrity issue.
// Such security should probably be added by cfs-collection package, not here.
return !self.allowsFile(fsFile);
},
fetch: []
});
// If insecure package is in use, we need to add allow rules that return
// true. Otherwise, it would seemingly turn off insecure mode.
if (Package && Package.insecure) {
self.allow({
insert: function() {
return true;
},
update: function() {
return true;
},
remove: function() {
return true;
},
download: function() {
return true;
},
fetch: [],
transform: null
});
}
// If insecure package is NOT in use, then adding the deny function
// does not have any effect on the main app's security paradigm. The
// user will still be required to add at least one allow function of her
// own for each operation for this collection. And the user may still add
// additional deny functions, but does not have to.
};
/**
* @method FS.Collection.prototype.allowsFile Does the collection allow the specified file?
* @public
* @returns {boolean} True if the collection allows this file.
*
* Checks based on any filters defined on the collection. If the
* file is not valid according to the filters, this method returns false
* and also calls the filter `onInvalid` method defined for the
* collection, passing it an English error string that explains why it
* failed.
*/
FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) {
var self = this;
// Get filters
var filter = self.options.filter;
if (!filter) {
return true;
}
var saveAllFileExtensions = (filter.allow.extensions.length === 0);
var saveAllContentTypes = (filter.allow.contentTypes.length === 0);
// Get info about the file
var filename = fileObj.name();
var contentType = fileObj.type();
if (!saveAllContentTypes && !contentType) {
filter.onInvalid && filter.onInvalid(filename + " has an unknown content type");
return false;
}
var fileSize = fileObj.size();
if (!fileSize || isNaN(fileSize)) {
filter.onInvalid && filter.onInvalid(filename + " has an unknown file size");
return false;
}
// Do extension checks only if we have a filename
if (filename) {
var ext = fileObj.getExtension();
if (!((saveAllFileExtensions ||
FS.Utility.indexOf(filter.allow.extensions, ext) !== -1) &&
FS.Utility.indexOf(filter.deny.extensions, ext) === -1)) {
filter.onInvalid && filter.onInvalid(filename + ' has the extension "' + ext + '", which is not allowed');
return false;
}
}
// Do content type checks
if (!((saveAllContentTypes ||
contentTypeInList(filter.allow.contentTypes, contentType)) &&
!contentTypeInList(filter.deny.contentTypes, contentType))) {
filter.onInvalid && filter.onInvalid(filename + ' is of the type "' + contentType + '", which is not allowed');
return false;
}
// Do max size check
if (typeof filter.maxSize === "number" && fileSize > filter.maxSize) {
filter.onInvalid && filter.onInvalid(filename + " is too big");
return false;
}
return true;
};
/**
* @method contentTypeInList Is the content type string in the list?
* @private
* @param {String[]} list - Array of content types
* @param {String} contentType - The content type
* @returns {Boolean}
*
* Returns true if the content type is in the list, or if it matches
* one of the special types in the list, e.g., "image/*".
*/
function contentTypeInList(list, contentType) {
var listType, found = false;
for (var i = 0, ln = list.length; i < ln; i++) {
listType = list[i];
if (listType === contentType) {
found = true;
break;
}
if (listType === "image/*" && contentType.indexOf("image/") === 0) {
found = true;
break;
}
if (listType === "audio/*" && contentType.indexOf("audio/") === 0) {
found = true;
break;
}
if (listType === "video/*" && contentType.indexOf("video/") === 0) {
found = true;
break;
}
}
return found;
}

Some files were not shown because too many files have changed in this diff Show more