Browse Source

Complete (?) configurable fake accounts package.

Frederic G. MARAND 8 years ago
parent
commit
0c5f127166

+ 5 - 1
.eslintrc.js

@@ -14,8 +14,12 @@ module.exports = {
 
     "Accounts": true,
     "Meteor": true,
+    "Package": true,
+    "ServiceConfiguration": true,
     "Session": true,
-    "Template": true
+    "Template": true,
+    "Tinytest": true,
+    "check": true
   },
 
   "plugins": ["react"],

+ 1 - 0
.meteor/packages

@@ -19,4 +19,5 @@ ecmascript              # Enable ECMAScript2015+ syntax in app code
 accounts-ui
 check
 service-configuration
+accounts-fake
 autopublish

+ 1 - 0
.meteor/versions

@@ -1,4 +1,5 @@
 accounts-base@1.2.2
+accounts-fake@0.0.1
 accounts-ui@1.1.6
 accounts-ui-unstyled@1.1.8
 autopublish@1.0.4

+ 0 - 1
client/admin.js

@@ -1,7 +1,6 @@
 Meteor.loginAsAdmin = function (password, callback) {
   // Create a login request with admin:true, so our loginHandler can handle it.
   let loginRequest = { admin: true, password: password };
-  Meteor._debug('Client loginAsAdmin, pass:', password);
   // Send the login request.
   Accounts.callLoginMethod({
     methodArguments: [loginRequest],

+ 10 - 0
docs/service-configuration/README.md

@@ -11,6 +11,16 @@ This package just provides access to a configuration collection in `ServiceConfi
 
 All properties of that collection are published client-side, except `secret` which is only available server-side.
 
+Configuration is inserted by the `AccountsServer.configureLoginService(options)` method from `accounts-base`. In Meteor core, this method is used by `login_buttons_dialogs.js` like:
+
+      Accounts.connection.call("configureLoginService", configuration, function (error, result) {
+        if (error)
+          Meteor._debug("Error configuring login service " + serviceName, error);
+        else
+          loginButtonsSession.set('configureLoginServiceDialogVisible', false);
+        }
+      );
+
 ## Collections
 
 * `meteor_accounts_loginServiceConfiguration`

+ 54 - 0
packages/accounts-fake/README.md

@@ -0,0 +1,54 @@
+# Accounts-fake
+
+This is a fake accounts package for Meteor 1.2.
+
+It implements the APIs needed to build a real accounts package, but uses a basic `user`/`action` pair, with this truth table, trying to log in as user `"foo"`.
+
+| "foo" exists ? | action    | result                                     |
+|:--------------:|:---------:|--------------------------------------------|
+| true           | true      | logged-in as "foo"                         |
+| true           | false     | login failure                              |
+| false          | true      | user "foo" created, and logged-in as "foo" |
+| false          | false     | login failure                              |
+
+# Logging in and out
+
+This is performed client-side: invoke the Meteor standard login process using the `fake` login service:
+
+    // To successfully log in, possibly creating the "foo" account.
+    Meteor.loginWithFake("foo", true, callback);
+
+    // To fail at logging in.
+    Meteor.loginWithFake("foo", false, callback);
+
+    // To logout.
+    Meteor.logout();
+
+# Configuring the package
+
+This package currently uses a simple configuration to demonstrate use of the accounts service configuration mechanism. It is made of two keys: `public` and `secret`. The truth table for the package is the following:
+
+| "public"         | "secret"             | package status                        |
+|:----------------:|:--------------------:|:--------------------------------------|
+| undefined        | undefined            | unconfigured                          |
+| anything defined | not same as "public" | configuration error: invalid "secret" |
+| anything defined | same as "public"     | package configured                    |
+
+The package takes its configuration from `Meteor.settings`, exposing its `public` value and hiding its `secret` on the client side.
+
+This means your `settings.json` file must look somehow like this:
+
+    {
+      "fake": {
+        "secret": "the secret, share you will"
+      },
+      "public": {
+        "fake": {
+          "public": "the secret, share you will"
+        },
+      },
+    }
+
+And your application using the package must be called like this to use settings:
+
+    meteor --settings settings.json

+ 21 - 0
packages/accounts-fake/accounts-fake-client.js

@@ -0,0 +1,21 @@
+// Write your package code here!
+Meteor.loginWithFake = function (arg1, arg2) {
+  let options, callback;
+  // Support a callback without options
+  if (!arg2 && typeof arg1 === "function") {
+    callback = arg1;
+    options = [];
+  }
+  else {
+    options = arg1;
+    callback = arg2;
+  }
+
+  // Other options:
+  // - methodName: "login"
+  // - suppressLoggingIn: false
+  Accounts.callLoginMethod({
+    methodArguments: [{ "fake": options }],
+    userCallback: callback
+  });
+};

+ 129 - 0
packages/accounts-fake/accounts-fake-server.js

@@ -0,0 +1,129 @@
+/**
+ * @file
+ * On the server side, an accounts package basically performs these tasks:
+ *
+ * - it exposes additional fields on Meteor.user() for autopublish
+ * - it registers as a login handler with a given service name
+ * - it publishes its runtime service configuration
+ */
+
+const SERVICE_NAME = "fake";
+
+/**
+ * Return the list of extra fields available on Meteor.user().
+ *
+ * This is invoked only if autopublish is enabled.
+ *
+ * @return {Object}
+ *   An object with up to two keys:
+ *   - forLoggedInUsers: an array of fields published to the current user
+ *   - forOtherUsers: an array of fields published to other users
+ */
+function autopublishFields() {
+  const allFields = "services." + SERVICE_NAME;
+  const publicFields = allFields + ".public";
+
+  return {
+    forLoggedInUser: [allFields],
+    forOtherUsers: [publicFields]
+  };
+}
+
+/**
+ * The fake login handler.
+ *
+ * @param {Object} loginRequest
+ *   The login request passed by Meteor. It will be of interest to the package
+ *   only if it contains a key named after the package.
+ *
+ * @return {Object} The result of a login request
+ *   - Undefined if the package does not handle this request.
+ *   - False if the package rejects the request.
+ *   - A result object containing the user information in case of login success.
+ */
+function loginHandler(loginRequest) {
+  let loginResult;
+
+  // A login request goes through all these handlers to find its login handler.
+  // So in our login handler, we only consider login requests which have an
+  // field matching our service name, i.e. "fake". To avoid false positives,
+  // any login package will only look for login request information under its
+  // own service name, returning undefined otherwise.
+  if (!loginRequest[SERVICE_NAME]) {
+    return loginResult;
+  }
+
+  let options = loginRequest[SERVICE_NAME];
+
+  // Never forget to check tainted data like these.
+  // noinspection JSCheckFunctionSignatures
+  check(options, {
+    action: Boolean,
+    user: String
+  });
+
+  // Use our ever-so-sophisticated authentication logic.
+  if (!options.action) {
+    loginResult = {
+      type: SERVICE_NAME,
+      error: new Meteor.Error("The login action said not to login.")
+    };
+
+    return loginResult;
+  }
+
+  // In case of success, ensure user account exists to find its id.
+  let userName = options.user;
+  let userCriteria = { username: userName };
+  let userDocument = Meteor.users.findOne(userCriteria);
+  let userId = userDocument
+    ? userDocument._id
+    : userName.toLocaleLowerCase();
+
+  // Return a user
+  let serviceData = {
+    id: userId,
+    extra: { some: "extra" }
+  };
+
+  // Publish part of the package-specific user information.
+  let userOptions = {
+    profile: serviceData.extra
+  };
+
+  return Accounts.updateOrCreateUserFromExternalService(SERVICE_NAME, serviceData, userOptions);
+}
+
+/**
+ * Configure the service from its settings.
+ *
+ * Remember, this is a demo service which just demonstrates how to access both
+ * public and private parts of configuration and expose them to client and
+ * server.
+ *
+ * @return {void}
+ */
+function configure() {
+  let settings = Meteor.settings;
+  let secret = settings[SERVICE_NAME].secret;
+  let notSecret = settings.public[SERVICE_NAME]["not-secret"];
+
+  if (typeof secret === "undefined" || secret !== notSecret) {
+    throw new Meteor.ConfigError(SERVICE_NAME);
+  }
+
+  let serviceConfig = {
+    service: SERVICE_NAME,
+    secret: secret,
+    notSecret: notSecret
+  };
+
+  // Unlike OAuth, we always reload the service configuration on application startup.
+  let configurations = ServiceConfiguration.configurations;
+  let selector = { service: SERVICE_NAME };
+  configurations.upsert(selector, serviceConfig);
+}
+
+Meteor.startup(configure);
+Accounts.registerLoginHandler(SERVICE_NAME, loginHandler);
+Accounts.addAutopublishFields(autopublishFields());

+ 5 - 0
packages/accounts-fake/accounts-fake-tests.js

@@ -0,0 +1,5 @@
+// Write your tests here!
+// Here is an example.
+Tinytest.add("example", function (test) {
+  test.equal(true, true);
+});

+ 24 - 0
packages/accounts-fake/package.js

@@ -0,0 +1,24 @@
+Package.describe({
+  name: "accounts-fake",
+  version: "0.0.1",
+  // Brief, one-line summary of the package.
+  summary: "A fake accounts package to demonstrate Meteor undocumented accounts API",
+  git: "",
+  documentation: "README.md"
+});
+
+Package.onUse(function(api) {
+  api.versionsFrom("1.2.1");
+  api.use("ecmascript");
+  api.use("accounts-base");
+  api.use("service-configuration");
+  api.addFiles("accounts-fake-client.js", "client");
+  api.addFiles("accounts-fake-server.js", "server");
+});
+
+Package.onTest(function (api) {
+  api.use("ecmascript");
+  api.use("tinytest");
+  api.use("accounts-fake");
+  api.addFiles("accounts-fake-tests.js");
+});

+ 10 - 0
settings.json

@@ -0,0 +1,10 @@
+{
+  "fake": {
+    "secret": "the secret, share you will"
+  },
+  "public": {
+    "fake": {
+      "not-secret": "the secret, share you will"
+    }
+  }
+}