mirror of
https://github.com/wekan/wekan.git
synced 2025-04-20 12:07:11 -04:00
Upgrade to Meteor 2.3.4
Thanks to xet7 !
This commit is contained in:
parent
e6dc20f6c7
commit
40265144af
316 changed files with 5049 additions and 32549 deletions
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
METEOR@2.2
|
||||
METEOR@2.3.4
|
||||
|
|
120
.meteor/versions
120
.meteor/versions
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
Presence.configure({
|
||||
state() {
|
||||
return {
|
||||
currentBoardId: Session.get('currentBoard'),
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,3 +1,4 @@
|
|||
/*
|
||||
const passwordField = AccountsTemplates.removeField('password');
|
||||
const emailField = AccountsTemplates.removeField('email');
|
||||
|
||||
|
@ -88,3 +89,5 @@ if (Meteor.isServer) {
|
|||
};
|
||||
});
|
||||
}
|
||||
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
*/
|
||||
|
|
|
@ -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
66
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
|
|
2
packages/meteor-accounts-cas/.gitignore
vendored
2
packages/meteor-accounts-cas/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
.build*
|
||||
node_modules/
|
|
@ -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.
|
|
@ -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)
|
||||
|
||||
|
|
@ -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;
|
||||
};
|
|
@ -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();
|
||||
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
});
|
|
@ -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
|
2
packages/meteor-useraccounts-core/.gitignore
vendored
2
packages/meteor-useraccounts-core/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
.build*
|
||||
versions.json
|
|
@ -1,2 +0,0 @@
|
|||
client/compatibility
|
||||
packages
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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
|
|
@ -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.
|
|
@ -1,104 +0,0 @@
|
|||
[](https://atmospherejs.com/useraccounts/core)
|
||||
[](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.
|
||||
|
||||
[](https://www.gittip.com/splendido/)
|
|
@ -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();
|
||||
});
|
|
@ -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!");
|
||||
}
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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});
|
||||
}
|
||||
},
|
||||
});
|
|
@ -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();
|
||||
});
|
|
@ -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");
|
||||
}
|
||||
},
|
||||
});
|
|
@ -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;
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
},
|
||||
};
|
|
@ -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);
|
||||
},
|
||||
};
|
|
@ -1,7 +0,0 @@
|
|||
AT.prototype.atMessageHelpers = {
|
||||
message: function() {
|
||||
var messageText = AccountsTemplates.state.form.get("message");
|
||||
if (messageText)
|
||||
return T9n.get(messageText, markIfMissing=false);
|
||||
},
|
||||
};
|
|
@ -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");
|
||||
},
|
||||
};
|
|
@ -1,5 +0,0 @@
|
|||
AT.prototype.atOauthHelpers = {
|
||||
oauthService: function() {
|
||||
return AccountsTemplates.oauthServices();
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
|
@ -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);
|
||||
},
|
||||
};
|
|
@ -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");
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
},
|
||||
};
|
|
@ -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');
|
||||
},
|
||||
};
|
|
@ -1,7 +0,0 @@
|
|||
AT.prototype.atResultHelpers = {
|
||||
result: function() {
|
||||
var resultText = AccountsTemplates.state.form.get("result");
|
||||
if (resultText)
|
||||
return T9n.get(resultText, markIfMissing=false);
|
||||
},
|
||||
};
|
|
@ -1,5 +0,0 @@
|
|||
AT.prototype.atSepHelpers = {
|
||||
sepText: function(){
|
||||
return T9n.get(AccountsTemplates.texts.sep, markIfMissing=false);
|
||||
},
|
||||
};
|
|
@ -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");
|
||||
},
|
||||
};
|
|
@ -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');
|
||||
},
|
||||
};
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
|
@ -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();
|
||||
},
|
||||
};
|
|
@ -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);
|
||||
},
|
||||
};
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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']);
|
||||
});
|
|
@ -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...
|
||||
}
|
||||
});
|
|
@ -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',
|
||||
]);
|
||||
});
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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;
|
||||
};
|
|
@ -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();
|
||||
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
});
|
1
packages/wekan-accounts-oidc/.gitignore
vendored
1
packages/wekan-accounts-oidc/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
.versions
|
|
@ -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.
|
||||
|
|
@ -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: []
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
```
|
|
@ -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']
|
||||
});
|
||||
}
|
|
@ -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==');
|
||||
}
|
|
@ -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');
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
before_install:
|
||||
- "curl -L http://git.io/s0Zu-w | /bin/sh"
|
|
@ -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
|
||||
|
|
@ -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.
|
|
@ -1,32 +0,0 @@
|
|||
wekan-cfs-access-point [](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)
|
|
@ -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
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
};
|
|
@ -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 };
|
||||
};
|
|
@ -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();
|
||||
});
|
|
@ -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) <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]) <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() <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() <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() <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() <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() <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]) <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) <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) <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) <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]) <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'
|
||||
]);
|
||||
```
|
|
@ -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) <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]) <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() <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} <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() <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} <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() <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() <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() <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]) <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) <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) <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() <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) <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]) <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'
|
||||
]);
|
||||
```
|
|
@ -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');
|
||||
});
|
|
@ -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)
|
|
@ -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)
|
|
@ -1,5 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
before_install:
|
||||
- "curl -L http://git.io/s0Zu-w | /bin/sh"
|
|
@ -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.
|
|
@ -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.
|
|
@ -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]) <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]) <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]) <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() <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) <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) <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) <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) <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) <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)
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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;
|
|
@ -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);
|
||||
});
|
||||
};
|
|
@ -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) <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]) <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]) <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]) <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() <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) <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) <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) <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) <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) <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) <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) <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)
|
||||
|
||||
|
|
@ -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']);
|
||||
// });
|
|
@ -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
|
|
@ -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)
|
|
@ -1,5 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
before_install:
|
||||
- "curl -L http://git.io/s0Zu-w | /bin/sh"
|
|
@ -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.
|
|
@ -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.
|
|
@ -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) <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() <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)
|
||||
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue