mirror of
https://github.com/wekan/wekan.git
synced 2025-04-23 05:27:14 -04:00
Added Sandstorm Accounts.
This commit is contained in:
parent
40aae378b6
commit
f260b7ba88
20 changed files with 889 additions and 2 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
3
packages/wekan-accounts-sandstorm/.gitignore
vendored
Normal file
3
packages/wekan-accounts-sandstorm/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
.build*
|
||||
test-app/.meteor/local
|
||||
test-app/.meteor-spk
|
|
@ -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
|
1
packages/wekan-accounts-sandstorm/.test-app/.meteor/.gitignore
vendored
Normal file
1
packages/wekan-accounts-sandstorm/.test-app/.meteor/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
local
|
7
packages/wekan-accounts-sandstorm/.test-app/.meteor/.id
Normal file
7
packages/wekan-accounts-sandstorm/.test-app/.meteor/.id
Normal file
|
@ -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
|
31
packages/wekan-accounts-sandstorm/.test-app/.meteor/packages
Normal file
31
packages/wekan-accounts-sandstorm/.test-app/.meteor/packages
Normal file
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
server
|
||||
browser
|
|
@ -0,0 +1 @@
|
|||
METEOR@1.5.1
|
83
packages/wekan-accounts-sandstorm/.test-app/.meteor/versions
Normal file
83
packages/wekan-accounts-sandstorm/.test-app/.meteor/versions
Normal file
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
/* CSS declarations go here */
|
|
@ -0,0 +1,19 @@
|
|||
<head>
|
||||
<title>accounts-meteor-test</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Welcome to Meteor!</h1>
|
||||
|
||||
{{> hello}}
|
||||
</body>
|
||||
|
||||
<template name="hello">
|
||||
<p>Resubscribes: {{counter}}</p>
|
||||
|
||||
<h2>server</h2>
|
||||
<pre>{{serverInfo}}</pre>
|
||||
|
||||
<h2>client</h2>
|
||||
<pre>{{clientInfo}}</pre>
|
||||
</template>
|
|
@ -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();
|
||||
});
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
../..
|
|
@ -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"),
|
||||
]
|
||||
);
|
21
packages/wekan-accounts-sandstorm/LICENSE
Normal file
21
packages/wekan-accounts-sandstorm/LICENSE
Normal file
|
@ -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.
|
||||
|
137
packages/wekan-accounts-sandstorm/README.md
Normal file
137
packages/wekan-accounts-sandstorm/README.md
Normal file
|
@ -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()`.
|
186
packages/wekan-accounts-sandstorm/client.js
Normal file
186
packages/wekan-accounts-sandstorm/client.js
Normal file
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
45
packages/wekan-accounts-sandstorm/package.js
Normal file
45
packages/wekan-accounts-sandstorm/package.js
Normal file
|
@ -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");
|
||||
});
|
210
packages/wekan-accounts-sandstorm/server.js
Normal file
210
packages/wekan-accounts-sandstorm/server.js
Normal file
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue