├── .editorconfig ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── .versions ├── LICENSE ├── README.md ├── accounts-anonymous-client-tests.js ├── accounts-anonymous-client.js ├── accounts-anonymous-server-tests.js ├── accounts-anonymous-server.js ├── accounts-anonymous.js └── package.js /.editorconfig: -------------------------------------------------------------------------------- 1 | #.editorconfig 2 | # Meteor adapted EditorConfig, http://EditorConfig.org 3 | # By RaiX 2013 4 | 5 | root = true 6 | 7 | [*.js] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | trim_trailing_whitespace = true 13 | charset = utf-8 14 | max_line_length = 80 15 | indent_brace_style = 1TBS 16 | spaces_around_operators = true 17 | quote_type = auto 18 | # curly_bracket_next_line = true -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | client/compatibility 2 | packages 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | //.jshintrc 2 | { 3 | // JSHint Meteor Configuration File 4 | // Match the Meteor Style Guide 5 | // 6 | // By @raix with contributions from @aldeed and @awatson1978 7 | // Source https://github.com/raix/Meteor-jshintrc 8 | // 9 | // See http://jshint.com/docs/ for more details 10 | 11 | "maxerr" : 50, // {int} Maximum error before stopping 12 | 13 | // Enforcing 14 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 15 | "camelcase" : true, // true: Identifiers must be in camelCase 16 | "curly" : true, // true: Require {} for every new block or scope 17 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 18 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 19 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 20 | "indent" : 2, // {int} Number of spaces to use for indentation 21 | "latedef" : false, // true: Require variables/functions to be defined before being used 22 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 23 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 24 | "noempty" : true, // true: Prohibit use of empty blocks 25 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 26 | "plusplus" : false, // true: Prohibit use of `++` & `--` 27 | "quotmark" : false, // Quotation mark consistency: 28 | // false : do nothing (default) 29 | // true : ensure whatever is used is consistent 30 | // "single" : require single quotes 31 | // "double" : require double quotes 32 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 33 | "unused" : true, // true: Require all defined variables be used 34 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 35 | "trailing" : true, // true: Prohibit trailing whitespaces 36 | "maxparams" : false, // {int} Max number of formal params allowed per function 37 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 38 | "maxstatements" : false, // {int} Max number statements per function 39 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 40 | "maxlen" : 80, // {int} Max number of characters per line 41 | 42 | // Relaxing 43 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 44 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 45 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 46 | "eqnull" : false, // true: Tolerate use of `== null` 47 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 48 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) 49 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 50 | // (ex: `for each`, multiple try/catch, function expression…) 51 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 52 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 53 | "funcscope" : false, // true: Tolerate defining variables inside control statements" 54 | "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict') 55 | "iterator" : false, // true: Tolerate using the `__iterator__` property 56 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 57 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 58 | "laxcomma" : false, // true: Tolerate comma-first style coding 59 | "loopfunc" : false, // true: Tolerate functions being defined in loops 60 | "multistr" : false, // true: Tolerate multi-line strings 61 | "proto" : false, // true: Tolerate using the `__proto__` property 62 | "scripturl" : false, // true: Tolerate script-targeted URLs 63 | "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment 64 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 65 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 66 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 67 | "validthis" : false, // true: Tolerate using this in a non-constructor function 68 | 69 | // Environments 70 | "browser" : true, // Web Browser (window, document, etc) 71 | "couch" : false, // CouchDB 72 | "devel" : true, // Development/debugging (alert, confirm, etc) 73 | "dojo" : false, // Dojo Toolkit 74 | "jquery" : false, // jQuery 75 | "mootools" : false, // MooTools 76 | "node" : false, // Node.js 77 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 78 | "prototypejs" : false, // Prototype and Scriptaculous 79 | "rhino" : false, // Rhino 80 | "worker" : false, // Web Workers 81 | "wsh" : false, // Windows Scripting Host 82 | "yui" : false, // Yahoo User Interface 83 | //"meteor" : false, // Meteor.js 84 | 85 | // Legacy 86 | "nomen" : false, // true: Prohibit dangling `_` in variables 87 | "onevar" : false, // true: Allow only one `var` statement per function 88 | "passfail" : false, // true: Stop on first error 89 | "white" : false, // true: Check against strict whitespace and indentation rules 90 | 91 | // Custom globals, from http://docs.meteor.com, in the order they appear there 92 | "globals" : { 93 | "Meteor": false, 94 | "DDP": false, 95 | "Mongo": false, //Meteor.Collection renamed to Mongo.Collection 96 | "Session": false, 97 | "Accounts": false, 98 | "Template": false, 99 | "Blaze": false, //UI is being renamed Blaze 100 | "UI": false, 101 | "Match": false, 102 | "check": false, 103 | "Tracker": false, //Deps renamed to Tracker 104 | "Deps": false, 105 | "ReactiveVar": false, 106 | "EJSON": false, 107 | "HTTP": false, 108 | "Email": false, 109 | "Assets": false, 110 | "Handlebars": false, // https://github.com/meteor/meteor/wiki/Handlebars 111 | "Package": false, 112 | "App": false, //mobile-config.js 113 | 114 | // Meteor internals 115 | "DDPServer": false, 116 | "global": false, 117 | "Log": false, 118 | "MongoInternals": false, 119 | "process": false, 120 | "WebApp": false, 121 | "WebAppInternals": false, 122 | 123 | // globals useful when creating Meteor packages 124 | "Npm": false, 125 | "Tinytest": false, 126 | 127 | // common Meteor packages 128 | "Random": false, 129 | "_": false, // Underscore.js 130 | "$": false, // jQuery 131 | "Router": false // iron-router 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: node_js 3 | node_js: 4 | - "0.10" 5 | 6 | before_install: 7 | - "curl -L http://git.io/ejPSng | /bin/sh" 8 | -------------------------------------------------------------------------------- /.versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.1 2 | accounts-password@1.1.3 3 | babel-compiler@5.8.24_1 4 | babel-runtime@0.1.4 5 | base64@1.0.4 6 | binary-heap@1.0.4 7 | blaze@2.1.3 8 | blaze-tools@1.0.4 9 | boilerplate-generator@1.0.4 10 | brettle:accounts-anonymous@0.3.1 11 | brettle:accounts-multiple@0.3.1 12 | brettle:workaround-issue-4862@0.0.3 13 | callback-hook@1.0.4 14 | check@1.0.6 15 | ddp@1.2.2 16 | ddp-client@1.2.1 17 | ddp-common@1.2.1 18 | ddp-rate-limiter@1.0.0 19 | ddp-server@1.2.1 20 | deps@1.0.9 21 | diff-sequence@1.0.1 22 | ecmascript@0.1.4 23 | ecmascript-collections@0.1.6 24 | ejson@1.0.7 25 | email@1.0.7 26 | geojson-utils@1.0.4 27 | html-tools@1.0.5 28 | htmljs@1.0.5 29 | id-map@1.0.4 30 | jquery@1.11.4 31 | local-test:brettle:accounts-anonymous@0.3.1 32 | localstorage@1.0.5 33 | logging@1.0.8 34 | meteor@1.1.7 35 | minimongo@1.0.9 36 | mongo@1.1.1 37 | mongo-id@1.0.1 38 | npm-bcrypt@0.7.8_2 39 | npm-mongo@1.4.39_1 40 | observe-sequence@1.0.7 41 | ordered-dict@1.0.4 42 | promise@0.4.8 43 | random@1.0.4 44 | rate-limit@1.0.0 45 | reactive-var@1.0.6 46 | retry@1.0.4 47 | routepolicy@1.0.6 48 | service-configuration@1.0.5 49 | sha@1.0.4 50 | spacebars@1.0.7 51 | spacebars-compiler@1.0.7 52 | srp@1.0.4 53 | tinytest@1.0.6 54 | tracker@1.0.8 55 | ui@1.0.8 56 | underscore@1.0.4 57 | webapp@1.2.2 58 | webapp-hashing@1.0.5 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 artwells 4 | Copyright (c) 2015 Dean Brettle 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # brettle:accounts-anonymous 2 | 3 | [![Build Status](https://travis-ci.org/brettle/meteor-accounts-anonymous.svg?branch=master)](https://travis-ci.org/brettle/meteor-accounts-anonymous) 4 | 5 | Allow users to login anonymously (i.e. no username, email, password, or OAuth 6 | service like accounts-google) 7 | 8 | This package is part of the `brettle:accounts-*` suite of packages. See 9 | [`brettle:accounts-deluxe`](https://atmospherejs.com/brettle/accounts-deluxe) 10 | for an overview of the suite and a live demo. 11 | 12 | ## Features 13 | - Supports truly anonymous users 14 | - Does not require accounts-password 15 | - Fires server event when an anonymous user logs in as a different user 16 | 17 | ## Installation 18 | ```sh 19 | meteor add brettle:accounts-anonymous 20 | ``` 21 | 22 | ## Usage 23 | 24 | On the client, to login as a new anonymous user, call 25 | `AccountsAnonymous.login([callback])`, while not logged in. The optional 26 | `callback` runs with no arguments on success, or with a single `Error` argument 27 | on failure. If the login was successful, Meteor will have stored a "resume" 28 | login token in the browser's localStorage which it will automatically send when 29 | making future connections to the server. This token allows the user to resume 30 | as the same anonymous user as long as the token exists (i.e. the user hasn't 31 | logged out or logged in as some other user), and hasn't 32 | [expired](http://docs.meteor.com/#/full/accounts_config). 33 | 34 | On the server, call `AccountsAnonymous.onAbandoned(func)` to register a callback 35 | to call if an anonymous user logs in as a different user. When this occurs, 36 | Meteor replaces the anonymous user's token with the new user's token, so there 37 | will be no way to log in again as the anonymous user. The `func` callback takes 38 | the anonymous user as its sole argument. You might use the call back to clean 39 | up any data associated with the user. 40 | 41 | ## History and Acknowledgements 42 | 43 | This is a friendly hard fork of the great 44 | [artwells:accounts-guest](https://github.com/artwells/meteor-accounts-guest) 45 | package. Before the fork, I [contributed 46 | code](https://github.com/artwells/meteor-accounts-guest/pull/35) which added 47 | support for using artwells:accounts-guest without accounts-password. I'm forking 48 | now because I'm not interested in the password-based guests which that package 49 | uses and I'd also like to split out some of the features into separate packages. 50 | I don't see any way to make these changes to artwells:accounts-guest without 51 | breaking compatibility for existing users of that package. Thus the hard fork. 52 | -------------------------------------------------------------------------------- /accounts-anonymous-client-tests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* globals AccountsAnonymous */ 3 | 4 | Tinytest.addAsync('AccountsAnonymous - login()', function (test, onComplete) { 5 | Meteor.logout(function (err) { 6 | test.isUndefined(err); 7 | test.isNull(Meteor.userId(), 'Logged out user should be null'); 8 | AccountsAnonymous.login(function (err) { 9 | test.isUndefined(err); 10 | var firstUserId = Meteor.userId(); 11 | test.isNotNull(firstUserId, 'Logged in user should not be null'); 12 | AccountsAnonymous.login(function (err) { 13 | test.instanceOf(err, Meteor.Error); 14 | test.equal(err.error, AccountsAnonymous._ALREADY_LOGGED_IN_ERROR); 15 | var secondUserId = Meteor.userId(); 16 | test.equal(secondUserId, firstUserId); 17 | Meteor.logout(function (err) { 18 | test.isUndefined(err); 19 | test.isNull(Meteor.userId(), 'Logged out user should be null'); 20 | AccountsAnonymous.login(function (err) { 21 | test.isUndefined(err); 22 | var thirdUserId = Meteor.userId(); 23 | test.isNotNull(thirdUserId); 24 | test.notEqual(thirdUserId, secondUserId); 25 | onComplete(); 26 | }); 27 | }); 28 | }); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /accounts-anonymous-client.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* globals AccountsAnonymous */ 3 | 4 | AccountsAnonymous.login = function (callback) { 5 | callback = callback || function () {}; 6 | if (Meteor.userId()) { 7 | callback(new Meteor.Error(AccountsAnonymous._ALREADY_LOGGED_IN_ERROR, 8 | "You can't login anonymously while you are already logged in.")); 9 | return; 10 | } 11 | Accounts.callLoginMethod({ 12 | methodArguments: [{ 13 | anonymous: true 14 | }], 15 | userCallback: function (error) { 16 | if (error) { 17 | if (callback) { callback(error); } 18 | } else { 19 | if (callback) { callback(); } 20 | } 21 | } 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /accounts-anonymous-server-tests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* globals AccountsMultiple, AccountsAnonymous */ 3 | 4 | Tinytest.add( 5 | 'AccountsAnonymous - onAbandoned', 6 | function (test) { 7 | AccountsMultiple._unregisterAll(); 8 | AccountsAnonymous._init(); 9 | AccountsAnonymous.onAbandoned(function (user) { 10 | actualCalls++; 11 | actualId = user._id; 12 | }); 13 | var actualCalls = 0; 14 | var expectedCalls = 0; 15 | var expectedId; 16 | var actualId; 17 | var connection = DDP.connect(Meteor.absoluteUrl()); 18 | var anonId, pwId; 19 | try { 20 | // User not logged in. 21 | anonId = connection.call('login', { anonymous: true }).id; 22 | test.equal(actualCalls, expectedCalls); 23 | 24 | // Anonymous user logged in. 25 | expectedId = anonId; 26 | pwId = connection.call('createUser', 27 | { email: 'testuser@example.com', password: 'password' } 28 | ).id; 29 | Meteor.users.remove(pwId); 30 | expectedCalls++; 31 | test.equal(actualCalls, expectedCalls); 32 | test.equal(actualId, anonId); 33 | } finally { 34 | connection.disconnect(); 35 | if (anonId) { Meteor.users.remove(anonId); } 36 | if (pwId) { Meteor.users.remove(pwId); } 37 | } 38 | } 39 | ); 40 | 41 | Tinytest.add( 42 | 'AccountsAnonymous - onAbandoned - user already removed', 43 | function (test) { 44 | AccountsMultiple._unregisterAll(); 45 | AccountsAnonymous._init(); 46 | var onAbandonedCalls = 0; 47 | AccountsAnonymous.onAbandoned(function (/* user (unused) */) { 48 | onAbandonedCalls++; 49 | }); 50 | 51 | var debugCalls = 0; 52 | var origDebug = Meteor._debug; 53 | Meteor._debug = function(/*arguments*/) { 54 | debugCalls++; 55 | origDebug.apply(this, arguments); 56 | }; 57 | 58 | var anonId, pwId; 59 | var connection = DDP.connect(Meteor.absoluteUrl()); 60 | try { 61 | // Create an anonymous user 62 | anonId = connection.call('login', { anonymous: true }).id; 63 | 64 | // Add a one-off validateLoginAttempt handler that removes it. 65 | var validateLoginStopper = Accounts.validateLoginAttempt(function() { 66 | validateLoginStopper.stop(); 67 | Meteor.users.remove(anonId); 68 | return true; 69 | }); 70 | 71 | // When we login as a new user, the above handler will remove the 72 | // attempting user. onAbandoned will still fire but no errors should 73 | // occur. 74 | pwId = connection.call('createUser', 75 | { email: 'testuser@example.com', password: 'password' } 76 | ).id; 77 | Meteor.users.remove(pwId); 78 | test.equal(onAbandonedCalls, 1); 79 | test.equal(debugCalls, 0); 80 | } finally { 81 | connection.disconnect(); 82 | Meteor._debug = origDebug; 83 | if (anonId) { Meteor.users.remove(anonId); } 84 | if (pwId) { Meteor.users.remove(pwId); } 85 | } 86 | } 87 | ); 88 | -------------------------------------------------------------------------------- /accounts-anonymous-server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* globals AccountsAnonymous, AccountsMultiple, Hook */ 3 | 4 | Accounts.registerLoginHandler("anonymous", function(options) { 5 | if (!options || !options.anonymous || Meteor.userId()) { 6 | return undefined; 7 | } 8 | 9 | var newUserId = Accounts.insertUserDoc(options, {}); 10 | return { 11 | userId: newUserId 12 | }; 13 | }); 14 | 15 | AccountsAnonymous._onAbandonedHook = new Hook({ 16 | bindEnvironment: false, 17 | debugPrintExceptions: "AccountsAnonymous.onAbandoned callback" 18 | }); 19 | 20 | AccountsAnonymous.onAbandoned = function(func) { 21 | var self = this; 22 | return self._onAbandonedHook.register(func); 23 | }; 24 | 25 | var callbackSet = { 26 | onSwitch: function(attemptingUser /* , attempt (unused) */) { 27 | if (isAnonymous(attemptingUser)) { 28 | AccountsAnonymous._onAbandonedHook.each(function(callback) { 29 | callback(attemptingUser); 30 | return true; 31 | }); 32 | } 33 | } 34 | }; 35 | 36 | AccountsAnonymous._init = function() { 37 | AccountsMultiple.register(callbackSet); 38 | }; 39 | 40 | AccountsAnonymous._init(); 41 | 42 | function isAnonymous(user) { 43 | // A user is anonymous if they don't have any services other than "resume" 44 | return (user.services && _.size(user.services) === 1 && user.services.resume); 45 | } 46 | -------------------------------------------------------------------------------- /accounts-anonymous.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* globals AccountsAnonymous: true */ 3 | AccountsAnonymous = {}; 4 | 5 | AccountsAnonymous._ALREADY_LOGGED_IN_ERROR = 6 | 'accounts-anonymous-already-logged-in'; 7 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Package.describe({ 4 | summary: "Support anonymous logins", 5 | version: "0.3.1", 6 | name: "brettle:accounts-anonymous", 7 | git: "https://github.com/brettle/meteor-accounts-anonymous.git" 8 | }); 9 | 10 | Package.onUse(function(api) { 11 | api.versionsFrom('1.0.4'); 12 | api.use(['accounts-base'], 'client'); 13 | api.use(['accounts-base', 'callback-hook'], 'server'); 14 | api.use('underscore', 'server'); 15 | api.use('brettle:accounts-multiple@0.3.1', 'server'); 16 | api.addFiles('accounts-anonymous.js', ['client', 'server']); 17 | api.export('AccountsAnonymous'); 18 | api.addFiles('accounts-anonymous-server.js', 'server'); 19 | api.addFiles('accounts-anonymous-client.js', 'client'); 20 | }); 21 | 22 | Package.onTest(function(api) { 23 | api.versionsFrom('1.0.4'); 24 | api.use(['brettle:accounts-anonymous@0.3.1', 'accounts-base', 'tinytest'], 25 | ['client', 'server']); 26 | api.use('brettle:accounts-multiple@0.3.1'); 27 | api.use('accounts-password', 'server'); 28 | api.use('ddp', 'server'); 29 | api.addFiles('accounts-anonymous-server-tests.js', 'server'); 30 | api.addFiles('accounts-anonymous-client-tests.js', 'client'); 31 | }); 32 | --------------------------------------------------------------------------------