diff --git a/.meteor/packages b/.meteor/packages index 601cd7b32..96b23a76f 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -26,7 +26,6 @@ mongo@1.15.0-rc272.0 mquandalle:collection-mutations # Account system -kenton:accounts-sandstorm #wekan-ldap #wekan-accounts-cas #wekan-accounts-oidc @@ -142,3 +141,4 @@ useraccounts:unstyled service-configuration@1.3.0 communitypackages:picker simple:rest-accounts-password +wekan-accounts-sandstorm diff --git a/.meteor/versions b/.meteor/versions index 5896492cd..d091ebf74 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -76,7 +76,6 @@ 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.1.0 konecty:mongo-counter@0.0.5_3 launch-screen@1.3.0 livedata@1.0.18 @@ -225,5 +224,6 @@ useraccounts:flow-routing@1.15.0 useraccounts:unstyled@1.14.2 webapp@1.13.1 webapp-hashing@1.1.0 +wekan-accounts-sandstorm@0.7.0 wekan-markdown@1.0.9 zimme:active-route@2.3.2 diff --git a/packages/wekan-accounts-sandstorm/.gitignore b/packages/wekan-accounts-sandstorm/.gitignore new file mode 100644 index 000000000..b39d80246 --- /dev/null +++ b/packages/wekan-accounts-sandstorm/.gitignore @@ -0,0 +1,3 @@ +.build* +test-app/.meteor/local +test-app/.meteor-spk diff --git a/packages/wekan-accounts-sandstorm/.test-app/.meteor/.finished-upgraders b/packages/wekan-accounts-sandstorm/.test-app/.meteor/.finished-upgraders new file mode 100644 index 000000000..910574ce2 --- /dev/null +++ b/packages/wekan-accounts-sandstorm/.test-app/.meteor/.finished-upgraders @@ -0,0 +1,17 @@ +# This file contains information which helps Meteor properly upgrade your +# app when you run 'meteor update'. You should check it into version control +# with your project. + +notices-for-0.9.0 +notices-for-0.9.1 +0.9.4-platform-file +notices-for-facebook-graph-api-2 +1.2.0-standard-minifiers-package +1.2.0-meteor-platform-split +1.2.0-cordova-changes +1.2.0-breaking-changes +1.3.0-split-minifiers-package +1.4.0-remove-old-dev-bundle-link +1.4.1-add-shell-server-package +1.4.3-split-account-service-packages +1.5-add-dynamic-import-package diff --git a/packages/wekan-accounts-sandstorm/.test-app/.meteor/.gitignore b/packages/wekan-accounts-sandstorm/.test-app/.meteor/.gitignore new file mode 100644 index 000000000..408303742 --- /dev/null +++ b/packages/wekan-accounts-sandstorm/.test-app/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/packages/wekan-accounts-sandstorm/.test-app/.meteor/.id b/packages/wekan-accounts-sandstorm/.test-app/.meteor/.id new file mode 100644 index 000000000..2b0fce4e8 --- /dev/null +++ b/packages/wekan-accounts-sandstorm/.test-app/.meteor/.id @@ -0,0 +1,7 @@ +# This file contains a token that is unique to your project. +# Check it into your repository along with the rest of this directory. +# It can be used for purposes such as: +# - ensuring you don't accidentally deploy one app on top of another +# - providing package authors with aggregated statistics + +1w4v0yxh077n01wrnl8j diff --git a/packages/wekan-accounts-sandstorm/.test-app/.meteor/packages b/packages/wekan-accounts-sandstorm/.test-app/.meteor/packages new file mode 100644 index 000000000..68bc73b73 --- /dev/null +++ b/packages/wekan-accounts-sandstorm/.test-app/.meteor/packages @@ -0,0 +1,31 @@ +# Meteor packages used by this project, one per line. +# Check this file (and the other files in this directory) into your repository. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +# List accounts-sandstorm first so that any missing dependencies it has +# are discovered. +kenton:accounts-sandstorm + +# Optional dependency. Should still work commented-out. +accounts-base@1.3.1 + +meteor-base@1.1.0 # Packages every Meteor app needs to have +mobile-experience@1.0.4 # Packages for a great mobile UX +mongo@1.1.19 # The database Meteor supports right now +blaze-html-templates # Compile .html files into Meteor Blaze views +session@1.1.7 # Client-side reactive dictionary for your app +jquery@1.11.10 # Helpful client-side library +tracker@1.1.3 # Meteor's client-side reactive programming library + +es5-shim@4.6.15 # ECMAScript 5 compatibility for older browsers. +ecmascript@0.8.1 # Enable ECMAScript2015+ syntax in app code + +autopublish@1.0.7 # Publish all data to the clients (for prototyping) +insecure@1.0.7 # Allow all DB writes from clients (for prototyping) + +standard-minifier-css +standard-minifier-js +shell-server +dynamic-import diff --git a/packages/wekan-accounts-sandstorm/.test-app/.meteor/platforms b/packages/wekan-accounts-sandstorm/.test-app/.meteor/platforms new file mode 100644 index 000000000..efeba1b50 --- /dev/null +++ b/packages/wekan-accounts-sandstorm/.test-app/.meteor/platforms @@ -0,0 +1,2 @@ +server +browser diff --git a/packages/wekan-accounts-sandstorm/.test-app/.meteor/release b/packages/wekan-accounts-sandstorm/.test-app/.meteor/release new file mode 100644 index 000000000..1e7fc5b56 --- /dev/null +++ b/packages/wekan-accounts-sandstorm/.test-app/.meteor/release @@ -0,0 +1 @@ +METEOR@1.5.1 diff --git a/packages/wekan-accounts-sandstorm/.test-app/.meteor/versions b/packages/wekan-accounts-sandstorm/.test-app/.meteor/versions new file mode 100644 index 000000000..c72576ee5 --- /dev/null +++ b/packages/wekan-accounts-sandstorm/.test-app/.meteor/versions @@ -0,0 +1,83 @@ +accounts-base@1.3.1 +allow-deny@1.0.6 +autopublish@1.0.7 +autoupdate@1.3.12 +babel-compiler@6.19.4 +babel-runtime@1.0.1 +base64@1.0.10 +binary-heap@1.0.10 +blaze@2.3.2 +blaze-html-templates@1.1.2 +blaze-tools@1.0.10 +boilerplate-generator@1.1.1 +caching-compiler@1.1.9 +caching-html-compiler@1.1.2 +callback-hook@1.0.10 +check@1.2.5 +ddp@1.3.0 +ddp-client@2.0.0 +ddp-common@1.2.9 +ddp-rate-limiter@1.0.7 +ddp-server@2.0.0 +deps@1.0.12 +diff-sequence@1.0.7 +dynamic-import@0.1.1 +ecmascript@0.8.2 +ecmascript-runtime@0.4.1 +ecmascript-runtime-client@0.4.3 +ecmascript-runtime-server@0.4.1 +ejson@1.0.13 +es5-shim@4.6.15 +fastclick@1.0.13 +geojson-utils@1.0.10 +hot-code-push@1.0.4 +html-tools@1.0.11 +htmljs@1.0.11 +http@1.2.12 +id-map@1.0.9 +insecure@1.0.7 +jquery@1.11.10 +kenton:accounts-sandstorm@0.7.0 +launch-screen@1.1.1 +livedata@1.0.18 +localstorage@1.1.1 +logging@1.1.17 +meteor@1.7.1 +meteor-base@1.1.0 +minifier-css@1.2.16 +minifier-js@2.1.1 +minimongo@1.2.1 +mobile-experience@1.0.4 +mobile-status-bar@1.0.14 +modules@0.9.4 +modules-runtime@0.8.0 +mongo@1.1.22 +mongo-id@1.0.6 +npm-mongo@2.2.30 +observe-sequence@1.0.16 +ordered-dict@1.0.9 +promise@0.8.9 +random@1.0.10 +rate-limit@1.0.8 +reactive-dict@1.1.9 +reactive-var@1.0.11 +reload@1.1.11 +retry@1.0.9 +routepolicy@1.0.12 +service-configuration@1.0.11 +session@1.1.7 +shell-server@0.2.4 +spacebars@1.0.15 +spacebars-compiler@1.1.3 +standard-minifier-css@1.3.4 +standard-minifier-js@2.1.1 +templating@1.3.2 +templating-compiler@1.3.2 +templating-runtime@1.3.2 +templating-tools@1.1.2 +tracker@1.1.3 +ui@1.0.13 +underscore@1.0.10 +url@1.1.0 +webapp@1.3.17 +webapp-hashing@1.0.9 diff --git a/packages/wekan-accounts-sandstorm/.test-app/accounts-meteor-test.css b/packages/wekan-accounts-sandstorm/.test-app/accounts-meteor-test.css new file mode 100644 index 000000000..b6b4052b4 --- /dev/null +++ b/packages/wekan-accounts-sandstorm/.test-app/accounts-meteor-test.css @@ -0,0 +1 @@ +/* CSS declarations go here */ diff --git a/packages/wekan-accounts-sandstorm/.test-app/accounts-meteor-test.html b/packages/wekan-accounts-sandstorm/.test-app/accounts-meteor-test.html new file mode 100644 index 000000000..c7ce7638b --- /dev/null +++ b/packages/wekan-accounts-sandstorm/.test-app/accounts-meteor-test.html @@ -0,0 +1,19 @@ +
+Resubscribes: {{counter}}
+ +{{serverInfo}}
+
+ {{clientInfo}}
+
diff --git a/packages/wekan-accounts-sandstorm/.test-app/accounts-meteor-test.js b/packages/wekan-accounts-sandstorm/.test-app/accounts-meteor-test.js
new file mode 100644
index 000000000..9e5539e45
--- /dev/null
+++ b/packages/wekan-accounts-sandstorm/.test-app/accounts-meteor-test.js
@@ -0,0 +1,48 @@
+if (Meteor.isClient) {
+ var Info = new Mongo.Collection("info");
+ var Counter = new Mongo.Collection("counter");
+
+ Template.hello.onCreated(function () {
+ Meteor.subscribe("info");
+ Meteor.subscribe("counter");
+ });
+
+ Template.hello.helpers({
+ counter: function () {
+ if (!Template.instance().subscriptionsReady()) return "not ready";
+ return Counter.findOne("counter").counter;
+ },
+
+ serverInfo: function () {
+ var obj = Info.findOne("info");
+ console.log("server", Meteor.loggingIn && Meteor.loggingIn(), obj);
+ return JSON.stringify(obj, null, 2);
+ },
+
+ clientInfo: function () {
+ var obj = Meteor.sandstormUser();
+ console.log("client", Meteor.loggingIn && Meteor.loggingIn(), obj);
+ return JSON.stringify(obj, null, 2);
+ },
+ });
+}
+
+if (Meteor.isServer) {
+ Meteor.startup(function () {
+ // code to run on server at startup
+ });
+
+ Meteor.publish("info", function () {
+ var user = Meteor.users && this.userId && Meteor.users.findOne(this.userId);
+ this.added("info", "info", {userId: this.userId, user: user, sandstormUser: this.connection.sandstormUser(),
+ sessionId: this.connection.sandstormSessionId(),
+ tabId: this.connection.sandstormTabId()});
+ this.ready();
+ });
+
+ var counter = 0;
+ Meteor.publish("counter", function () {
+ this.added("counter", "counter", {counter: counter++});
+ this.ready();
+ });
+}
diff --git a/packages/wekan-accounts-sandstorm/.test-app/packages/kenton:accounts-sandstorm b/packages/wekan-accounts-sandstorm/.test-app/packages/kenton:accounts-sandstorm
new file mode 120000
index 000000000..c25bddb6d
--- /dev/null
+++ b/packages/wekan-accounts-sandstorm/.test-app/packages/kenton:accounts-sandstorm
@@ -0,0 +1 @@
+../..
\ No newline at end of file
diff --git a/packages/wekan-accounts-sandstorm/.test-app/sandstorm-pkgdef.capnp b/packages/wekan-accounts-sandstorm/.test-app/sandstorm-pkgdef.capnp
new file mode 100644
index 000000000..a2df64a0c
--- /dev/null
+++ b/packages/wekan-accounts-sandstorm/.test-app/sandstorm-pkgdef.capnp
@@ -0,0 +1,74 @@
+@0xb412d6a17c04e5cc;
+
+using Spk = import "/sandstorm/package.capnp";
+
+const pkgdef :Spk.PackageDefinition = (
+ id = "y49n7yrxk6p3ud1hkgeup1mah6f7a488nancvay7v6y1wxq78cn0",
+
+ manifest = (
+ appTitle = (defaultText = "Meteor Accounts Test App"),
+ appVersion = 0,
+ appMarketingVersion = (defaultText = "0.0.0"),
+ actions = [
+ ( title = (defaultText = "New Test"),
+ command = .myCommand
+ )
+ ],
+
+ continueCommand = .myCommand,
+ ),
+
+ sourceMap = (
+ searchPath = [
+ ( sourcePath = ".meteor-spk/deps" ),
+ ( sourcePath = ".meteor-spk/bundle" )
+ ]
+ ),
+
+ alwaysInclude = [ "." ],
+
+ bridgeConfig = (
+ viewInfo = (
+ permissions = [
+ (
+ name = "editor",
+ title = (defaultText = "editor"),
+ description = (defaultText = "grants ability to modify data"),
+ ),
+ (
+ name = "commenter",
+ title = (defaultText = "commenter"),
+ description = (defaultText = "grants ability to modify data"),
+ ),
+ ],
+ roles = [
+ (
+ title = (defaultText = "editor"),
+ permissions = [true, true],
+ verbPhrase = (defaultText = "can edit"),
+ description = (defaultText = "editors may view all site data and change settings."),
+ ),
+ (
+ title = (defaultText = "commenter"),
+ permissions = [false, true],
+ verbPhrase = (defaultText = "can comment"),
+ description = (defaultText = "viewers may view what other users have written."),
+ ),
+ (
+ title = (defaultText = "viewer"),
+ permissions = [false, false],
+ verbPhrase = (defaultText = "can view"),
+ description = (defaultText = "viewers may view what other users have written."),
+ ),
+ ],
+ ),
+ ),
+);
+
+const myCommand :Spk.Manifest.Command = (
+ argv = ["/sandstorm-http-bridge", "4000", "--", "node", "start.js"],
+ environ = [
+ (key = "PATH", value = "/usr/local/bin:/usr/bin:/bin"),
+ (key = "SANDSTORM", value = "1"),
+ ]
+);
diff --git a/packages/wekan-accounts-sandstorm/LICENSE b/packages/wekan-accounts-sandstorm/LICENSE
new file mode 100644
index 000000000..039de6863
--- /dev/null
+++ b/packages/wekan-accounts-sandstorm/LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2014 Sandstorm Development Group, Inc. and contributors
+Licensed under the MIT License:
+
+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.
+
diff --git a/packages/wekan-accounts-sandstorm/README.md b/packages/wekan-accounts-sandstorm/README.md
new file mode 100644
index 000000000..17ad0d5e8
--- /dev/null
+++ b/packages/wekan-accounts-sandstorm/README.md
@@ -0,0 +1,137 @@
+# Sandstorm.io login integration for Meteor.js
+
+[Sandstorm](https://sandstorm.io) is a platform for personal clouds that makes
+installing apps to your personal server as easy as installing apps to your
+phone.
+
+[Meteor](https://meteor.com) is a revolutionary web app framework. Sandstorm's
+own UI is built using Meteor, and Meteor is also a great way to build Sandstorm
+apps.
+
+This package is meant to be used by Meteor apps built to run on Sandstorm.
+It integrates with Sandstorm's built-in login system to log the user in
+automatically when they open the app. The user's `profile.name` will be
+populated from Sandstorm. When using this package, you should not use
+`accounts-ui` at all; just let login happen automatically.
+
+## Including in your app
+
+To use this package in your Meteor project, simply install it from the Meteor
+package repository:
+
+ meteor add kenton:accounts-sandstorm
+
+To package a Meteor app for Sandstorm, see
+[the Meteor app packaging guide](https://docs.sandstorm.io/en/latest/vagrant-spk/packaging-tutorial-meteor/).
+
+Note that this package does nothing if the `SANDSTORM` environment variable is
+not set. Therefore, it is safe to include the package even in non-Sandstorm
+builds of your app. Note that `sandstorm-pkgdef.capnp` files generated by
+`spk init` automatically have a line like `(key = "SANDSTORM", value = "1"),`
+which sets the environment variable, so you shouldn't have to do anything
+special to enable it.
+
+Conversely, when `SANDSTORM` is set, this package will enter Highlander Mode
+in which it will *disable* all other accounts packages. This makes it safe
+to include those other accounts packages in the Sandstorm build, which is
+often convenient, although they will add bloat to your spk.
+
+## Usage
+
+* On the client, call `Meteor.sandstormUser()`. (This is a reactive data source.)
+* In a method or publish (on the server), call `this.connection.sandstormUser()`.
+
+Either of these will return an object containing the following fields:
+
+* `id`: From `X-Sandstorm-User-Id`; globally unique and stable
+ identifier for this user. `null` if the user is not logged in.
+* `name`: From "X-Sandstorm-Username`, the user's display name (e.g.
+ `"Kenton Varda"`).
+* `picture`: From `X-Sandstorm-User-Picture`, URL of the user's preferred
+ avatar, or `null` if they don't have one.
+* `permissions`: From `X-Sandstorm-Permissions` (but parsed to a list),
+ the list of permissions the user has as determined by the Sandstorm
+ sharing model. Apps can define their own permissions.
+* `preferredHandle`: From `X-Sandstorm-Preferred-Handle`, the user's
+ preferred handle ("username", in the unix sense). This is NOT
+ guaranteed to be unique; it's just a different form of display name.
+* `pronouns`: From `X-Sandstorm-User-Pronouns`, indicates the pronouns
+ by which the user prefers to be referred.
+
+See [the Sandstorm docs](https://docs.sandstorm.io/en/latest/developing/auth/#headers-that-an-app-receives) for more information about these fields.
+
+Note that `sandstormUser()` may return `null` on the client side if the login
+handshake has not completed yet (`Meteor.loggingIn()` returns `true` during
+this time). It never returns `null` on the server, but it may throw an
+exception if the client skipped the authentication handshake (which indicates
+the client is not running accounts-sandstorm, which is rather suspicious!).
+
+## Synchronization with Meteor Accounts
+
+`accounts-sandstorm` does NOT require `accounts-base`. However, if you do
+include `accounts-base` in your dependencies, then `accounts-sandstorm` will
+integrate with it in order to store information about users seen previously.
+In particular:
+
+* A Meteor account will be automatically created for each logged-in Sandstorm user,
+ the first time they visit the grain.
+* In the `Meteor.users` table, `services.sandstorm` will contain the same data
+ returned by `Meteor.sandstormUser()`.
+* `Meteor.loggingIn()` will return `true` during the initial handshake (when
+ `sandstormUser()` would return `null`).
+
+Please note, however, that you should prefer `sandstormUser()` over
+`Meteor.user().services.sandstorm` whenever possible, **especially** for enforcing
+permissions, for a few reasons:
+
+* Anonymous users do NOT get a table entry, therefore `Meteor.user()` will be
+ `null` for them. However, anonymous users of a sharing link may have permissions!
+* Moreover, in the future, anonymous users may additionally be able to assign
+ themselves names, handles, avatars, etc. The only thing that makes them "anonymous"
+ is that they have not provided the app with a unique identifier that could be used
+ to recognize the same user when they visit again later.
+* `services.sandstorm` is only updated when the user is online; it may be stale
+ when they are not present. This implies that when a user's access is revoked,
+ their user table entry will never be updated again, and will continue to
+ indicate that they have permissions when they in fact no longer do.
+
+## Development aids
+
+`accounts-sandstorm` normally works its magic when running inside Sandstorm. However,
+it's often a lot more convenient to develop Meteor apps using Meteor's normal dev tools
+which currently cannot run inside Sandstorm.
+
+Therefore, when *not* running inside Sansdtorm, you may use the following console
+function to fake your user information:
+
+ SandstormAccounts.setTestUserInfo({
+ id: "12345",
+ name: "Alice",
+ // ... other parameters, as listed above ...
+ });
+
+This will cause `accounts-sandstorm` to spoof the `X-Sandstorm-*` headers with the
+parameters you provided when it attempts to log in. When actually running inside
+Sandstorm, such spoofing is blocked by Sandstorm, but when running outside it will
+work and now you can test your app.
+
+Note that this functionality, like all of the package, is only enabled if you set the
+`SANDSTORM` environment variable. So, run `meteor` like so:
+
+ SANDSTORM=1 meteor
+
+## Migrating from 0.1
+
+In version 0.1.x of this puackage, there was no `sandstormUser()` function; the
+only mode of operation was through Meteor accounts. This had problems with
+permissions and anonymous users as described adove. Introducing `sandstormUser()`
+is a huge update.
+
+For almost all users, 0.2 should be a drop-in replacement for 0.1, only adding
+new features. Please note, though, two possible issues:
+
+* If you did not explicitly depend on `accounts-base` before, you must add this
+ dependency, since it is no longer implied by `accounts-sansdtorm`.
+* The `/.sandstorm-credentials` endpoint no longer exists. If you were directly
+ fetching this undocumented endpoint before, you will need to switch your code
+ to use `Meteor.sandstormUser()`.
diff --git a/packages/wekan-accounts-sandstorm/client.js b/packages/wekan-accounts-sandstorm/client.js
new file mode 100644
index 000000000..61c176fcb
--- /dev/null
+++ b/packages/wekan-accounts-sandstorm/client.js
@@ -0,0 +1,186 @@
+// Copyright (c) 2014 Sandstorm Development Group, Inc. and contributors
+// Licensed under the MIT License:
+//
+// 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.
+
+function loginWithSandstorm(connection, apiHost, apiToken) {
+ // Log in the connection using Sandstorm authentication.
+ //
+ // After calling this, connection.sandstormUser() will reactively return an object containing
+ // Sansdstorm user info, including permissions as authenticated by the server. Even if the user
+ // is anonymous, this information is returned. `sandstormUser()` returns `null` up until the
+ // point where login succeeds.
+
+ // How this works:
+ // 1. We create a cryptographically random token, which we're going to send to the server twice.
+ // 2. We make a method call to log in with this token. The server initially has no idea who
+ // is calling and will block waiting for info. (The method is marked "wait" on the client side
+ // so that further method calls are blocked until login completes.)
+ // 3. We also send an XHR with the same token. When the server receives the XHR, it harvests the
+ // Sandstorm headers, looks for the corresponding login method call, marks its connection as
+ // logged in, and then lets it return.
+ //
+ // We don't actually use Accounts.callLoginMethod() because we don't need or want the
+ // "resume token" logic. On a disconnect, we need to re-authenticate, because the user's
+ // permissions may have changed (indeed, this may be the reason for the disconnect).
+
+ // If the connection doesn't already have a sandstormUser() method, add it now.
+ if (!connection._sandstormUser) {
+ connection._sandstormUser = new ReactiveVar(null);
+ connection.sandstormUser = connection._sandstormUser.get.bind(connection._sandstormUser);
+ }
+
+ // Generate a random token which we'll send both over an XHR and over DDP at the same time.
+ var token = Random.secret();
+
+ var waiting = true; // We'll keep retrying XHRs until the method returns.
+ var reconnected = false;
+
+ var onResultReceived = function (error, result) {
+ waiting = false;
+
+ if (error) {
+ // ignore for now; loggedInAndDataReadyCallback() will get the error too
+ } else {
+ connection.onReconnect = function () {
+ reconnected = true;
+ loginWithSandstorm(connection, apiHost, apiToken);
+ };
+ }
+ };
+
+ var loggedInAndDataReadyCallback = function (error, result) {
+ if (reconnected) {
+ // Oh, we're already on a future connection attempt. Don't mess with anything.
+ return;
+ }
+
+ if (error) {
+ console.error("loginWithSandstorm failed:", error);
+ } else {
+ connection._sandstormUser.set(result.sandstorm);
+ connection.setUserId(result.userId);
+ }
+ };
+
+ Meteor.apply("loginWithSandstorm", [token],
+ {wait: true, onResultReceived: onResultReceived},
+ loggedInAndDataReadyCallback);
+
+ var sendXhr = function () {
+ if (!waiting) return; // Method call finished.
+
+ headers = {"Content-Type": "application/x-sandstorm-login-token"};
+
+ var testInfo = localStorage.sandstormTestUserInfo;
+ if (testInfo) {
+ testInfo = JSON.parse(testInfo);
+ if (testInfo.id) {
+ headers["X-Sandstorm-User-Id"] = testInfo.id;
+ }
+ if (testInfo.name) {
+ headers["X-Sandstorm-Username"] = encodeURI(testInfo.name);
+ }
+ if (testInfo.picture) {
+ headers["X-Sandstorm-User-Picture"] = testInfo.picture;
+ }
+ if (testInfo.permissions) {
+ headers["X-Sandstorm-Permissions"] = testInfo.permissions.join(",");
+ }
+ if (testInfo.preferredHandle) {
+ headers["X-Sandstorm-Preferred-Handle"] = testInfo.preferredHandle;
+ }
+ if (testInfo.pronouns) {
+ headers["X-Sandstorm-User-Pronouns"] = testInfo.pronouns;
+ }
+ }
+
+ var postUrl = "/.sandstorm-login";
+ // Sandstorm mobile apps need to point at a different host and use an Authorization token.
+ if (apiHost) {
+ postUrl = apiHost + postUrl;
+ headers.Authorization = "Bearer " + apiToken;
+ }
+
+ // Send the token in an HTTP POST request which on the server side will allow us to receive the
+ // Sandstorm headers.
+ HTTP.post(postUrl,
+ {content: token, headers: headers},
+ function (error, result) {
+ if (error) {
+ console.error("couldn't get /.sandstorm-login:", error);
+
+ if (waiting) {
+ // Try again in a second.
+ Meteor.setTimeout(sendXhr, 1000);
+ }
+ }
+ });
+ };
+
+ // Wait until the connection is up before we start trying to send XHRs.
+ var stopImmediately = false; // Unfortunately, Tracker.autorun() runs the first time inline.
+ var handle = Tracker.autorun(function () {
+ if (!waiting) {
+ if (handle) {
+ handle.stop();
+ } else {
+ stopImmediately = true;
+ }
+ return;
+ } else if (connection.status().connected) {
+ if (handle) {
+ handle.stop();
+ } else {
+ stopImmediately = true;
+ }
+
+ // Wait 10ms before our first attempt to send the rendezvous XHR because if it arrives
+ // before the method call it will be rejected.
+ Meteor.setTimeout(sendXhr, 10);
+ }
+ });
+ if (stopImmediately) handle.stop();
+}
+
+if (__meteor_runtime_config__.SANDSTORM) {
+ // Auto-login the main Meteor connection.
+ loginWithSandstorm(Meteor.connection, __meteor_runtime_config__.SANDSTORM_API_HOST,
+ __meteor_runtime_config__.SANDSTORM_API_TOKEN);
+
+ if (Package["accounts-base"]) {
+ // Make Meteor.loggingIn() work by calling a private method of accounts-base. If this breaks then
+ // maybe we should just overwrite Meteor.loggingIn() instead.
+ Tracker.autorun(function () {
+ Package["accounts-base"].Accounts._setLoggingIn(!Meteor.connection.sandstormUser());
+ });
+ }
+
+ Meteor.sandstormUser = function () {
+ return Meteor.connection.sandstormUser();
+ };
+
+ SandstormAccounts = {
+ setTestUserInfo: function (info) {
+ localStorage.sandstormTestUserInfo = JSON.stringify(info);
+ loginWithSandstorm(Meteor.connection, __meteor_runtime_config__.SANDSTORM_API_HOST,
+ __meteor_runtime_config__.SANDSTORM_API_TOKEN);
+ }
+ };
+}
diff --git a/packages/wekan-accounts-sandstorm/package.js b/packages/wekan-accounts-sandstorm/package.js
new file mode 100644
index 000000000..1606cf263
--- /dev/null
+++ b/packages/wekan-accounts-sandstorm/package.js
@@ -0,0 +1,45 @@
+// Copyright (c) 2014 Sandstorm Development Group, Inc. and contributors
+// Licensed under the MIT License:
+//
+// 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.
+
+Package.describe({
+ summary: "Login service for Sandstorm.io applications",
+ version: "0.7.0",
+ name: "wekan-accounts-sandstorm",
+ git: "https://github.com/sandstorm-io/meteor-accounts-sandstorm.git"
+});
+
+Package.onUse(function(api) {
+ api.versionsFrom('1.8.2');
+
+ api.use('random', ['client', 'server']);
+ api.use('accounts-base@~2.2.3-rc272.0', ['client', 'server'], {weak: true});
+ api.use('webapp', 'server');
+ api.use('http', 'client');
+ api.use('tracker', 'client');
+ api.use('reactive-var', 'client');
+ api.use('check', 'server');
+ api.use('ddp-server', 'server');
+
+ api.addFiles("client.js", "client");
+ api.addFiles("server.js", "server");
+
+ api.export("SandstormAccounts", "client");
+});
diff --git a/packages/wekan-accounts-sandstorm/server.js b/packages/wekan-accounts-sandstorm/server.js
new file mode 100644
index 000000000..032549692
--- /dev/null
+++ b/packages/wekan-accounts-sandstorm/server.js
@@ -0,0 +1,210 @@
+// Copyright (c) 2014 Sandstorm Development Group, Inc. and contributors
+// Licensed under the MIT License:
+//
+// 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.
+
+if (process.env.SANDSTORM) {
+ __meteor_runtime_config__.SANDSTORM = true;
+}
+
+if (__meteor_runtime_config__.SANDSTORM) {
+ if (Package["accounts-base"]) {
+ // Highlander Mode: Disable all non-Sandstorm login mechanisms.
+ Package["accounts-base"].Accounts.validateLoginAttempt(function (attempt) {
+ if (!attempt.allowed) {
+ return false;
+ }
+ if (attempt.type !== "sandstorm") {
+ throw new Meteor.Error(403, "Non-Sandstorm login mechanisms disabled on Sandstorm.");
+ }
+ return true;
+ });
+ Package["accounts-base"].Accounts.validateNewUser(function (user) {
+ if (!user.services.sandstorm) {
+ throw new Meteor.Error(403, "Non-Sandstorm login mechanisms disabled on Sandstorm.");
+ }
+ return true;
+ });
+ }
+
+ var Future = Npm.require("fibers/future");
+
+ var inMeteor = Meteor.bindEnvironment(function (callback) {
+ callback();
+ });
+
+ var logins = {};
+ // Maps tokens to currently-waiting login method calls.
+
+ if (Package["accounts-base"]) {
+ Meteor.users.createIndex("services.sandstorm.id", {unique: 1, sparse: 1});
+ }
+
+ Meteor.onConnection(function (connection) {
+ connection._sandstormUser = null;
+ connection._sandstormSessionId = null;
+ connection._sandstormTabId = null;
+ connection.sandstormUser = function () {
+ if (!connection._sandstormUser) {
+ throw new Meteor.Error(400, "Client did not complete authentication handshake.");
+ }
+ return this._sandstormUser;
+ };
+ connection.sandstormSessionId = function () {
+ if (!connection._sandstormUser) {
+ throw new Meteor.Error(400, "Client did not complete authentication handshake.");
+ }
+ return this._sandstormSessionId;
+ }
+ connection.sandstormTabId = function () {
+ if (!connection._sandstormUser) {
+ throw new Meteor.Error(400, "Client did not complete authentication handshake.");
+ }
+ return this._sandstormTabId;
+ }
+ });
+
+ Meteor.methods({
+ loginWithSandstorm: function (token) {
+ check(token, String);
+
+ var future = new Future();
+
+ logins[token] = future;
+
+ var timeout = setTimeout(function () {
+ future.throw(new Meteor.Error("timeout", "Gave up waiting for login rendezvous XHR."));
+ }, 10000);
+
+ var info;
+ try {
+ info = future.wait();
+ } finally {
+ clearTimeout(timeout);
+ delete logins[token];
+ }
+
+ // Set connection info. The call to setUserId() resets all publishes. We update the
+ // connection's sandstorm info first so that when the publishes are re-run they'll see the
+ // new info. In theory we really want to update it exactly when this.userId is updated, but
+ // we'd have to dig into Meteor internals to pull that off. Probably updating it a little
+ // early is fine?
+ //
+ // Note that calling setUserId() with the same ID a second time still goes through the motions
+ // of restarting all subscriptions, which is important if the permissions changed. Hopefully
+ // Meteor won't decide to "optimize" this by returning early if the user ID hasn't changed.
+ this.connection._sandstormUser = info.sandstorm;
+ this.connection._sandstormSessionId = info.sessionId;
+ this.connection._sandstormTabId = info.tabId;
+ this.setUserId(info.userId);
+
+ return info;
+ }
+ });
+
+ WebApp.rawConnectHandlers.use(function (req, res, next) {
+ if (req.url === "/.sandstorm-login") {
+ handlePostToken(req, res);
+ return;
+ }
+ return next();
+ });
+
+ function readAll(stream) {
+ var future = new Future();
+
+ var chunks = [];
+ stream.on("data", function (chunk) {
+ chunks.push(chunk.toString());
+ });
+ stream.on("error", function (err) {
+ future.throw(err);
+ });
+ stream.on("end", function () {
+ future.return();
+ });
+
+ future.wait();
+
+ return chunks.join("");
+ }
+
+ var handlePostToken = Meteor.bindEnvironment(function (req, res) {
+ inMeteor(function () {
+ try {
+ // Note that cross-origin POSTs cannot set arbitrary Content-Types without explicit CORS
+ // permission, so this effectively prevents XSRF.
+ if (req.headers["content-type"].split(";")[0].trim() !== "application/x-sandstorm-login-token") {
+ throw new Error("wrong Content-Type for .sandstorm-login: " + req.headers["content-type"]);
+ }
+
+ var token = readAll(req);
+
+ var future = logins[token];
+ if (!future) {
+ throw new Error("no current login request matching token");
+ }
+
+ var permissions = req.headers["x-sandstorm-permissions"];
+ if (permissions && permissions !== "") {
+ permissions = permissions.split(",");
+ } else {
+ permissions = [];
+ }
+
+ var sandstormInfo = {
+ id: req.headers["x-sandstorm-user-id"] || null,
+ name: decodeURIComponent(req.headers["x-sandstorm-username"]),
+ permissions: permissions,
+ picture: req.headers["x-sandstorm-user-picture"] || null,
+ preferredHandle: req.headers["x-sandstorm-preferred-handle"] || null,
+ pronouns: req.headers["x-sandstorm-user-pronouns"] || null,
+ };
+
+ var userInfo = {sandstorm: sandstormInfo};
+ if (Package["accounts-base"]) {
+ if (sandstormInfo.id) {
+ // The user is logged into Sandstorm. Create a Meteor account for them, or find the
+ // existing one, and record the user ID.
+ var login = Package["accounts-base"].Accounts.updateOrCreateUserFromExternalService(
+ "sandstorm", sandstormInfo, {profile: {name: sandstormInfo.name}});
+ userInfo.userId = login.userId;
+ } else {
+ userInfo.userId = null;
+ }
+ } else {
+ // Since the app isn't using regular Meteor accounts, we can define Meteor.userId()
+ // however we want.
+ userInfo.userId = sandstormInfo.id;
+ }
+
+ userInfo.sessionId = req.headers["x-sandstorm-session-id"] || null;
+ userInfo.tabId = req.headers["x-sandstorm-tab-id"] || null;
+ future.return(userInfo);
+ res.writeHead(204, {});
+ res.end();
+ } catch (err) {
+ res.writeHead(500, {
+ "Content-Type": "text/plain"
+ });
+ res.end(err.stack);
+ }
+ });
+ });
+}