mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'meteor-accounts-ux-pass' into release-0.7.1
This commit is contained in:
@@ -161,6 +161,7 @@
|
||||
* Don't cache direct references to the fields arguments to the subscription
|
||||
`added` and `changed` methods. #1750
|
||||
|
||||
|
||||
## v0.7.0.1
|
||||
|
||||
* Two fixes to `meteor run` Mongo startup bugs that could lead to hangs with the
|
||||
|
||||
@@ -62,7 +62,7 @@ var openCenteredPopup = function(url, width, height) {
|
||||
return newwindow;
|
||||
};
|
||||
|
||||
// XXX COMPAT WITH 0.6.6.3
|
||||
// XXX COMPAT WITH 0.7.0.1
|
||||
// Private interface but probably used by many oauth clients in atmosphere.
|
||||
Oauth.initiateLogin = function (credentialToken, url, callback, dimensions) {
|
||||
Oauth.showPopup(
|
||||
|
||||
Submodule scripts/admin/publish-release/packages/awssum updated: 69dc3c7afb...0221218663
259
tools/auth.js
259
tools/auth.js
@@ -50,25 +50,41 @@ var withAccountsConnection = function (f) {
|
||||
};
|
||||
};
|
||||
|
||||
// Open a DDP connection to the accounts server, log in using the
|
||||
// provided token, and ensure that the connection stays logged in across
|
||||
// reconnects.
|
||||
// Open a DDP connection to the accounts server and log in using the
|
||||
// provided token. Returns the connection, or null if login fails.
|
||||
//
|
||||
// XXX if we reconnect we won't reauthenticate. Fix that before using
|
||||
// this for long-lived connections.
|
||||
var loggedInAccountsConnection = function (token) {
|
||||
var connection = getLoadedPackages().livedata.DDP.connect(
|
||||
config.getAuthDDPUrl()
|
||||
);
|
||||
var onReconnect = function () {
|
||||
connection.apply(
|
||||
'login',
|
||||
[{ resume: token }],
|
||||
{ wait: true },
|
||||
function (err, result) {
|
||||
if (err)
|
||||
throw err;
|
||||
}
|
||||
);
|
||||
};
|
||||
onReconnect();
|
||||
|
||||
var fut = new Future;
|
||||
connection.apply(
|
||||
'login',
|
||||
[{ resume: token }],
|
||||
{ wait: true },
|
||||
function (err, result) {
|
||||
fut['return']({ err: err, result: result });
|
||||
}
|
||||
);
|
||||
var outcome = fut.wait();
|
||||
|
||||
if (outcome.err) {
|
||||
connection.close();
|
||||
|
||||
if (outcome.err.error === 403) {
|
||||
// This is not an ideal value for the error code, but it means
|
||||
// "server rejected our access token". For example, it expired
|
||||
// or we revoked it from the web.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Something else went wrong
|
||||
throw outcome.err;
|
||||
}
|
||||
|
||||
return connection;
|
||||
};
|
||||
|
||||
@@ -189,26 +205,33 @@ var writeMeteorAccountsUsername = function (username) {
|
||||
// Given an object 'data' in the format returned by readSessionData,
|
||||
// modify it to make the user logged out.
|
||||
var logOutAllSessions = function (data) {
|
||||
_.each(data.sessions, function (session, domain) {
|
||||
logOutSession(session);
|
||||
});
|
||||
};
|
||||
|
||||
// As logOutAllSessions, but for a session on a particular domain
|
||||
// rather than all sessions.
|
||||
var logOutSession = function (session) {
|
||||
var crypto = require('crypto');
|
||||
|
||||
_.each(data.sessions, function (session, domain) {
|
||||
delete session.username;
|
||||
delete session.userId;
|
||||
delete session.username;
|
||||
delete session.userId;
|
||||
delete session.registrationUrl;
|
||||
|
||||
if (_.has(session, 'token')) {
|
||||
if (! (session.pendingRevoke instanceof Array))
|
||||
session.pendingRevoke = [];
|
||||
if (_.has(session, 'token')) {
|
||||
if (! (session.pendingRevoke instanceof Array))
|
||||
session.pendingRevoke = [];
|
||||
|
||||
// Delete the auth token itself, but save the tokenId, which
|
||||
// is useless for authentication. The next time we're online,
|
||||
// we'll send the tokenId to the server to revoke the token on
|
||||
// the server side too.
|
||||
if (typeof session.tokenId === "string")
|
||||
session.pendingRevoke.push(session.tokenId);
|
||||
delete session.token;
|
||||
delete session.tokenId;
|
||||
}
|
||||
});
|
||||
// Delete the auth token itself, but save the tokenId, which is
|
||||
// useless for authentication. The next time we're online, we'll
|
||||
// send the tokenId to the server to revoke the token on the
|
||||
// server side too.
|
||||
if (typeof session.tokenId === "string")
|
||||
session.pendingRevoke.push(session.tokenId);
|
||||
delete session.token;
|
||||
delete session.tokenId;
|
||||
}
|
||||
};
|
||||
|
||||
// Given an object 'data' in the format returned by readSessionData,
|
||||
@@ -222,37 +245,7 @@ var loggedIn = function (data) {
|
||||
// the logged in user doesn't have a username.
|
||||
var currentUsername = function (data) {
|
||||
var sessionData = getSession(data, config.getAccountsDomain());
|
||||
if (sessionData.username) {
|
||||
return sessionData.username;
|
||||
} else if (loggedIn(data) && sessionData.token) {
|
||||
// If it looks like we are logged in but we don't yet have a
|
||||
// username, then ask the server if we have one.
|
||||
var username = null;
|
||||
var fut = new Future();
|
||||
var connection = loggedInAccountsConnection(sessionData.token);
|
||||
connection.call('getUsername', function (err, result) {
|
||||
if (err) {
|
||||
// If anything went wrong, return null just as we would have if
|
||||
// we hadn't bothered to ask the server.
|
||||
fut['return'](null);
|
||||
return;
|
||||
}
|
||||
fut['return'](result);
|
||||
});
|
||||
|
||||
setTimeout(inFiber(function () {
|
||||
fut['return'](null);
|
||||
}), 5000);
|
||||
|
||||
username = fut.wait();
|
||||
connection.close();
|
||||
if (username) {
|
||||
writeMeteorAccountsUsername(username);
|
||||
}
|
||||
return username;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return sessionData.username || null;
|
||||
};
|
||||
|
||||
var removePendingRevoke = function (domain, tokenIds) {
|
||||
@@ -584,13 +577,18 @@ var doInteractivePasswordLogin = function (options) {
|
||||
return true;
|
||||
};
|
||||
|
||||
// options are the same as for doInteractivePasswordLogin, exept without
|
||||
// options are the same as for doInteractivePasswordLogin, except without
|
||||
// username and email.
|
||||
exports.doUsernamePasswordLogin = function (options) {
|
||||
var username = utils.readLine({
|
||||
prompt: "Username: ",
|
||||
stream: process.stderr
|
||||
});
|
||||
var username;
|
||||
|
||||
do {
|
||||
username = utils.readLine({
|
||||
prompt: "Username: ",
|
||||
stream: process.stderr
|
||||
}).trim();
|
||||
} while (username.length === 0);
|
||||
|
||||
return doInteractivePasswordLogin(_.extend({}, options, {
|
||||
username: username
|
||||
}));
|
||||
@@ -685,9 +683,71 @@ exports.logoutCommand = function (options) {
|
||||
process.stderr.write("Not logged in.\n");
|
||||
};
|
||||
|
||||
exports.currentUsername = function () {
|
||||
// If this is fully set up account (with a username and password), or
|
||||
// if not logged in, do nothing. If it is an account without a
|
||||
// username, contact the server and see if the user finished setting
|
||||
// it up, and if so grab and save the username. But contact the server
|
||||
// only once per run of the program. Options:
|
||||
// - noLogout: boolean. Set to true if you don't want this function to
|
||||
// log out the session if wehave an invalid credential (for example,
|
||||
// if a caller wants to do its own error handling for invalid
|
||||
// credentials). Defaults to false.
|
||||
var alreadyPolledForRegistration = false;
|
||||
exports.pollForRegistrationCompletion = function (options) {
|
||||
if (alreadyPolledForRegistration)
|
||||
return;
|
||||
alreadyPolledForRegistration = true;
|
||||
|
||||
options = options || {};
|
||||
|
||||
var data = readSessionData();
|
||||
return currentUsername(data);
|
||||
var session = getSession(data, config.getAccountsDomain());
|
||||
if (session.username || ! session.token)
|
||||
return;
|
||||
|
||||
// We are logged in but we don't yet have a username. Ask the server
|
||||
// if a username was chosen since we last checked.
|
||||
var username = null;
|
||||
var fut = new Future();
|
||||
var connection = loggedInAccountsConnection(session.token);
|
||||
|
||||
if (! connection) {
|
||||
// Server says our credential isn't any good anymore! Get rid of
|
||||
// it. Note that, out of an abundance of caution, this also will
|
||||
// enqueue the credential for invalidation (on a future run, we
|
||||
// will try to explicitly revoke the credential ourselves).
|
||||
if (! options.noLogout) {
|
||||
logOutSession(session);
|
||||
writeSessionData(data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
connection.call('getUsername', function (err, result) {
|
||||
if (fut.isResolved())
|
||||
return;
|
||||
|
||||
if (err) {
|
||||
// If anything went wrong, return null just as we would have if
|
||||
// we hadn't bothered to ask the server.
|
||||
fut['return'](null);
|
||||
return;
|
||||
}
|
||||
fut['return'](result);
|
||||
});
|
||||
|
||||
var timer = setTimeout(inFiber(function () {
|
||||
if (! fut.isResolved()) {
|
||||
fut['return'](null);
|
||||
}
|
||||
}), 5000);
|
||||
|
||||
username = fut.wait();
|
||||
connection.close();
|
||||
clearTimeout(timer);
|
||||
if (username) {
|
||||
writeMeteorAccountsUsername(username);
|
||||
}
|
||||
};
|
||||
|
||||
exports.registrationUrl = function () {
|
||||
@@ -698,6 +758,7 @@ exports.registrationUrl = function () {
|
||||
|
||||
exports.whoAmICommand = function (options) {
|
||||
config.printUniverseBanner();
|
||||
auth.pollForRegistrationCompletion();
|
||||
|
||||
var data = readSessionData();
|
||||
if (! loggedIn(data)) {
|
||||
@@ -775,11 +836,23 @@ exports.registerOrLogIn = withAccountsConnection(function (connection) {
|
||||
} else if (result.alreadyExisted && result.sentRegistrationEmail) {
|
||||
process.stderr.write(
|
||||
"\n" +
|
||||
"That email address is already in use. We need to confirm that it belongs\n" +
|
||||
"to you. Luckily this will only take a moment.\n" +
|
||||
"\n" +
|
||||
"Check your mail! We've sent you a link. Click it, pick a password,\n" +
|
||||
"and then come back here to deploy your app.\n");
|
||||
"You need to pick a password for your account so that you can log in.\n" +
|
||||
"An email has been sent to you with the link.\n\n");
|
||||
|
||||
var animationFrame = 0;
|
||||
var lastLinePrinted = "";
|
||||
var timer = setInterval(function () {
|
||||
var spinner = ['-', '\\', '|', '/'];
|
||||
lastLinePrinted = "Waiting for you to register on the web... " +
|
||||
spinner[animationFrame];
|
||||
process.stderr.write(lastLinePrinted + "\r");
|
||||
animationFrame = (animationFrame + 1) % spinner.length;
|
||||
}, 200);
|
||||
var stopSpinner = function () {
|
||||
process.stderr.write(new Array(lastLinePrinted.length + 1).join(' ') +
|
||||
"\r");
|
||||
clearInterval(timer);
|
||||
};
|
||||
|
||||
try {
|
||||
var waitForRegistrationResult = connection.call(
|
||||
@@ -787,17 +860,17 @@ exports.registerOrLogIn = withAccountsConnection(function (connection) {
|
||||
email
|
||||
);
|
||||
} catch (e) {
|
||||
stopSpinner();
|
||||
if (! (e instanceof getLoadedPackages().meteor.Meteor.Error))
|
||||
throw e;
|
||||
process.stderr.write(
|
||||
"\nWhen you've picked your password, run 'meteor login' and then you'll\n" +
|
||||
"be good to go.\n");
|
||||
"When you've picked your password, run 'meteor login' to log in.\n")
|
||||
return false;
|
||||
}
|
||||
|
||||
process.stderr.write("\nGreat! Nice to meet you, " +
|
||||
waitForRegistrationResult.username +
|
||||
"! Now log in with your new password.\n");
|
||||
stopSpinner();
|
||||
process.stderr.write("Username: " +
|
||||
waitForRegistrationResult.username + "\n");
|
||||
loginResult = doInteractivePasswordLogin({
|
||||
username: waitForRegistrationResult.username,
|
||||
retry: true,
|
||||
@@ -821,6 +894,34 @@ exports.registerOrLogIn = withAccountsConnection(function (connection) {
|
||||
}
|
||||
});
|
||||
|
||||
// options: firstTime, leadingNewline
|
||||
exports.maybePrintRegistrationLink = function (options) {
|
||||
options = options || {};
|
||||
|
||||
auth.pollForRegistrationCompletion();
|
||||
|
||||
var data = readSessionData();
|
||||
var session = getSession(data, config.getAccountsDomain());
|
||||
|
||||
if (session.userId && ! session.username && session.registrationUrl) {
|
||||
if (options.leadingNewline)
|
||||
process.stderr.write("\n");
|
||||
if (! options.firstTime) {
|
||||
// If they've already been prompted to set a password then this
|
||||
// is more of a friendly reminder, so we word it slightly
|
||||
// differently than the first time they're being shown a
|
||||
// registration url.
|
||||
process.stderr.write(
|
||||
"You should set a password on your Meteor developer account. It takes\n" +
|
||||
"about a minute at: " + session.registrationUrl + "\n\n");
|
||||
} else {
|
||||
process.stderr.write(
|
||||
"You can set a password on your account or change your email address at:\n" +
|
||||
session.registrationUrl + "\n\n");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.tryRevokeOldTokens = tryRevokeOldTokens;
|
||||
|
||||
exports.getSessionId = function (domain) {
|
||||
|
||||
@@ -678,6 +678,7 @@ main.registerCommand({
|
||||
}
|
||||
}, function (options) {
|
||||
var mongoUrl;
|
||||
var usedMeteorAccount = false;
|
||||
|
||||
if (options.args.length === 0) {
|
||||
// localhost mode
|
||||
@@ -708,6 +709,7 @@ main.registerCommand({
|
||||
mongoUrl = deployGalaxy.temporaryMongoUrl(site);
|
||||
} else {
|
||||
mongoUrl = deploy.temporaryMongoUrl(site);
|
||||
usedMeteorAccount = true;
|
||||
}
|
||||
|
||||
if (! mongoUrl)
|
||||
@@ -717,6 +719,8 @@ main.registerCommand({
|
||||
if (options.url) {
|
||||
console.log(mongoUrl);
|
||||
} else {
|
||||
if (usedMeteorAccount)
|
||||
auth.maybePrintRegistrationLink();
|
||||
process.stdin.pause();
|
||||
var runMongo = require('./run-mongo.js');
|
||||
runMongo.runMongoShell(mongoUrl);
|
||||
@@ -862,26 +866,16 @@ main.registerCommand({
|
||||
});
|
||||
}
|
||||
|
||||
var registrationUrl = auth.registrationUrl();
|
||||
if (registrationUrl &&
|
||||
deployResult === 0 &&
|
||||
! auth.currentUsername()) {
|
||||
process.stderr.write("\n");
|
||||
if (loggedIn) {
|
||||
if (deployResult === 0) {
|
||||
auth.maybePrintRegistrationLink({
|
||||
leadingNewline: true,
|
||||
// If the user was already logged in at the beginning of the
|
||||
// deploy, then they've already been prompted to set a password
|
||||
// and this is more of a friendly reminder to set their password,
|
||||
// so we word it slightly differently than the first time they're
|
||||
// being shown a registration url.
|
||||
process.stderr.write(
|
||||
"You should set a password on your Meteor developer account. It takes\n" +
|
||||
"about a minute at: " + registrationUrl + "\n\n");
|
||||
} else {
|
||||
process.stderr.write(
|
||||
"You can set a password on your account or change your email address at:\n" +
|
||||
registrationUrl + "\n\n");
|
||||
}
|
||||
// at least once before, so we use a slightly different message.
|
||||
firstTime: ! loggedIn
|
||||
});
|
||||
}
|
||||
|
||||
return deployResult;
|
||||
});
|
||||
|
||||
@@ -942,6 +936,7 @@ main.registerCommand({
|
||||
}
|
||||
|
||||
config.printUniverseBanner();
|
||||
auth.pollForRegistrationCompletion();
|
||||
var site = qualifySitename(options.args[0]);
|
||||
|
||||
if (hostedWithGalaxy(site)) {
|
||||
@@ -976,15 +971,14 @@ main.registerCommand({
|
||||
maxArgs: 1
|
||||
}, function (options) {
|
||||
config.printUniverseBanner();
|
||||
auth.pollForRegistrationCompletion();
|
||||
var site = qualifySitename(options.args[0]);
|
||||
|
||||
if (! auth.isLoggedIn()) {
|
||||
// XXX meteor.com/create-account or something should have a nice
|
||||
// registration form
|
||||
process.stderr.write(
|
||||
"\nYou must be logged in to claim sites. Use 'meteor login' to log in.\n" +
|
||||
"If you don't have a Meteor developer account yet, you can quickly\n" +
|
||||
"create one at www.meteor.com.\n\n");
|
||||
"You must be logged in to claim sites. Use 'meteor login' to log in.\n" +
|
||||
"If you don't have a Meteor developer account yet, create one by clicking\n" +
|
||||
"'Sign in' and then 'Create account' at www.meteor.com.\n\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1202,10 +1196,14 @@ main.registerCommand({
|
||||
name: 'login',
|
||||
options: {
|
||||
email: { type: String },
|
||||
// Undocumented: get credentials on a specific Galaxy. Do we still
|
||||
// need this?
|
||||
galaxy: { type: String }
|
||||
}
|
||||
}, function (options) {
|
||||
return auth.loginCommand(options);
|
||||
return auth.loginCommand(_.extend({
|
||||
overwriteExistingToken: true
|
||||
}, options));
|
||||
});
|
||||
|
||||
|
||||
@@ -1252,12 +1250,13 @@ main.registerCommand({
|
||||
|
||||
main.registerCommand({
|
||||
name: 'self-test',
|
||||
minArgs: 0,
|
||||
maxArgs: 1,
|
||||
options: {
|
||||
changed: { type: Boolean },
|
||||
'force-online': { type: Boolean },
|
||||
slow: { type: Boolean },
|
||||
history: { type: Number },
|
||||
tests: { type: String }
|
||||
},
|
||||
hidden: true
|
||||
}, function (options) {
|
||||
@@ -1275,13 +1274,13 @@ main.registerCommand({
|
||||
}
|
||||
|
||||
var testRegexp = undefined;
|
||||
if (options.tests) {
|
||||
if (options.args.length) {
|
||||
try {
|
||||
testRegexp = new RegExp(options.tests);
|
||||
testRegexp = new RegExp(options.args[0]);
|
||||
} catch (e) {
|
||||
if (!(e instanceof SyntaxError))
|
||||
throw e;
|
||||
process.stderr.write("Bad regular expression: " + options.tests + "\n");
|
||||
process.stderr.write("Bad regular expression: " + options.args[0] + "\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ _.extend(ServiceConnection.prototype, {
|
||||
}
|
||||
});
|
||||
|
||||
self.subscribe.apply(self, args);
|
||||
self.connection.subscribe.apply(self.connection, args);
|
||||
return fut.wait();
|
||||
},
|
||||
|
||||
|
||||
@@ -147,10 +147,13 @@ var authedRpc = function (options) {
|
||||
expectPayload: []
|
||||
});
|
||||
|
||||
if (infoResult.statusCode === 403 && rpcOptions.promptIfAuthFails) {
|
||||
if (infoResult.statusCode === 401 && rpcOptions.promptIfAuthFails) {
|
||||
// Our authentication didn't validate, so prompt the user to log in
|
||||
// again, and resend the RPC if the login succeeds.
|
||||
var username = utils.readLine({ prompt: "Username: " });
|
||||
var username = utils.readLine({
|
||||
prompt: "Username: ",
|
||||
stream: process.stderr
|
||||
});
|
||||
var loginOptions = {
|
||||
username: username,
|
||||
suppressErrorMessage: true
|
||||
@@ -262,9 +265,9 @@ var printLegacyPasswordMessage = function (site) {
|
||||
// authorized for, instruct them to get added via 'meteor authorized
|
||||
// --add' or switch accounts.
|
||||
var printUnauthorizedMessage = function () {
|
||||
var username = auth.currentUsername();
|
||||
var username = auth.loggedInUsername();
|
||||
process.stderr.write(
|
||||
"\nSorry, that site belongs to a different user.\n" +
|
||||
"Sorry, that site belongs to a different user.\n" +
|
||||
(username ? "You are currently logged in as " + username + ".\n" : "") +
|
||||
"\nEither have the site owner use 'meteor authorized --add' to add you\n" +
|
||||
"as an authorized developer for the site, or switch to an authorized\n" +
|
||||
@@ -313,12 +316,27 @@ var bundleAndDeploy = function (options) {
|
||||
if (! site)
|
||||
return 1;
|
||||
|
||||
// We should give a username/password prompt if the user was logged in
|
||||
// but the credentials are expired, unless the user is logged in but
|
||||
// doesn't have a username (in which case they should hit the email
|
||||
// prompt -- a user without a username shouldn't be given a username
|
||||
// prompt). There's an edge case where things happen in the following
|
||||
// order: user creates account, user sets username, credential expires
|
||||
// or is revoked, user comes back to deploy again. In that case,
|
||||
// they'll get an email prompt instead of a username prompt because
|
||||
// the command-line tool didn't have time to learn about their
|
||||
// username before the credential was expired.
|
||||
auth.pollForRegistrationCompletion({
|
||||
noLogout: true
|
||||
});
|
||||
var promptIfAuthFails = (auth.loggedInUsername() !== null);
|
||||
|
||||
// Check auth up front, rather than after the (potentially lengthy)
|
||||
// bundling process.
|
||||
var preflight = authedRpc({
|
||||
site: site,
|
||||
preflight: true,
|
||||
promptIfAuthFails: true
|
||||
promptIfAuthFails: promptIfAuthFails
|
||||
});
|
||||
|
||||
if (preflight.errorMessage) {
|
||||
@@ -341,7 +359,7 @@ var bundleAndDeploy = function (options) {
|
||||
var buildDir = path.join(options.appDir, '.meteor', 'local', 'build_tar');
|
||||
var bundlePath = path.join(buildDir, 'bundle');
|
||||
|
||||
process.stdout.write('Deploying to ' + site + '. Bundling...\n');
|
||||
process.stdout.write('Deploying to ' + site + '. Bundling...\n');
|
||||
|
||||
var settings = null;
|
||||
var messages = buildmessage.capture({
|
||||
@@ -487,6 +505,7 @@ var checkAuthThenSendRpc = function (site, operation, what) {
|
||||
return null;
|
||||
}
|
||||
} else { // User is logged in but not authorized for this app
|
||||
process.stderr.write("\n");
|
||||
printUnauthorizedMessage();
|
||||
return null;
|
||||
}
|
||||
@@ -539,6 +558,7 @@ var logs = function (site) {
|
||||
return 1;
|
||||
} else {
|
||||
process.stdout.write(result.message);
|
||||
auth.maybePrintRegistrationLink({ leadingNewline: true });
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
@@ -659,11 +679,12 @@ var claim = function (site) {
|
||||
});
|
||||
|
||||
if (result.errorMessage) {
|
||||
if (! auth.currentUsername() &&
|
||||
auth.pollForRegistrationCompletion();
|
||||
if (! auth.loggedInUsername() &&
|
||||
auth.registrationUrl()) {
|
||||
process.stderr.write(
|
||||
"\nBefore you can claim existing sites, you need to set a password on\n" +
|
||||
"your Meteor developer account. You can do that here in under a minute:\n\n" +
|
||||
"You need to set a password on your Meteor developer account before\n" +
|
||||
"you can claim sites. You can do that here in under a minute:\n\n" +
|
||||
auth.registrationUrl() + "\n\n");
|
||||
} else {
|
||||
process.stderr.write("Couldn't claim site: " +
|
||||
|
||||
@@ -218,6 +218,32 @@ site into your account. If you had set a password on the site you will
|
||||
be prompted for it one last time.
|
||||
|
||||
|
||||
>>> login
|
||||
Log in to your Meteor developer account
|
||||
Usage: meteor login [--email]
|
||||
|
||||
Prompts for your username and password and logs you in to your Meteor
|
||||
developer account. Pass --email to log in by email address rather than
|
||||
by username.
|
||||
|
||||
|
||||
>>> logout
|
||||
Log out of your Meteor developer account
|
||||
Usage: meteor logout
|
||||
|
||||
Log out of your Meteor developer account.
|
||||
|
||||
|
||||
>>> whoami
|
||||
Prints the username of your Meteor developer account
|
||||
Usage: meteor whoami
|
||||
|
||||
Prints the username of the currently logged-in Meteor developer.
|
||||
|
||||
See 'meteor login' to log into or 'meteor logout' to log out of your
|
||||
Meteor developer account.
|
||||
|
||||
|
||||
>>> test-packages
|
||||
Test one or more packages
|
||||
Usage: meteor test-packages [--release <release>] [options] [package...]
|
||||
@@ -291,30 +317,31 @@ Prompts for your username and password and logs you in to your
|
||||
Meteor account. Pass --email to log in by email address instead of
|
||||
username. Pass --galaxy to specify a galaxy to log in to.
|
||||
|
||||
Builds the provided directory as a package, then loads the package and
|
||||
calls the main() function inside the package. The function will receive
|
||||
any remaining arguments. The exit status will be the return value of
|
||||
main() (which is called inside a fiber).
|
||||
|
||||
>>> logout
|
||||
Log out of your Meteor account
|
||||
Usage: meteor logout
|
||||
The arguments will be parsed by Meteor's option parser, which means that
|
||||
--release will be effective (but not passed to the command), and that it will be
|
||||
an error to pass any unknown options. If you want to pass options to your tool,
|
||||
place them after a '--' argument (which turns off option parsing for the rest of
|
||||
the arguments).
|
||||
|
||||
Log out of your Meteor account.
|
||||
|
||||
|
||||
>>> whoami
|
||||
Prints the username of your Meteor developer account
|
||||
Usage: meteor whoami
|
||||
|
||||
Prints the username of the currently logged-in Meteor developer.
|
||||
|
||||
See 'meteor login' to log into or 'meteor logout' to log out of of your
|
||||
Meteor account.
|
||||
This command is for temporary, internal use, until we have a more mature
|
||||
system for building standalone command-line programs with Meteor.
|
||||
|
||||
|
||||
>>> self-test
|
||||
Run tests of the 'meteor' tool.
|
||||
Usage: meteor self-test [--changed] [--slow] [--force-online] [--history n]
|
||||
Usage: meteor self-test [pattern] [--changed] [--slow]
|
||||
[--force-online] [--history n]
|
||||
|
||||
Runs internal tests. Exits with status 0 on success.
|
||||
|
||||
If 'pattern' is provided, it should be a regular expression. Only
|
||||
tests that match the regular expression will be run.
|
||||
|
||||
Pass --changed to run only tests that have changed since they last
|
||||
passed. This uses a really rough heuristic: A test has changed iff
|
||||
there has been any change to the file in the 'selftests' subdirectory
|
||||
@@ -361,4 +388,3 @@ Grant a permission on an official service
|
||||
Usage: meteor admin grant [XXX]
|
||||
|
||||
Not yet implemented
|
||||
|
||||
|
||||
@@ -477,6 +477,21 @@ _.extend(Sandbox.prototype, {
|
||||
unlink: function (filename) {
|
||||
var self = this;
|
||||
fs.unlinkSync(path.join(self.cwd, filename));
|
||||
},
|
||||
|
||||
// Return the current contents of .meteorsession in the sandbox.
|
||||
readSessionFile: function () {
|
||||
var self = this;
|
||||
return fs.readFileSync(path.join(self.root, '.meteorsession'), 'utf8');
|
||||
},
|
||||
|
||||
// Overwrite .meteorsession in the sandbox with 'contents'. You
|
||||
// could use this in conjunction with readSessionFile to save and
|
||||
// restore authentication states.
|
||||
writeSessionFile: function (contents) {
|
||||
var self = this;
|
||||
return fs.writeFileSync(path.join(self.root, '.meteorsession'),
|
||||
contents, 'utf8');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -976,7 +991,7 @@ var tagDescriptions = {
|
||||
// these last two are not actually test tags; they reflect the use of
|
||||
// --changed and --tests
|
||||
unchanged: 'unchanged since last pass',
|
||||
misnamed: "don't match --tests argument"
|
||||
'non-matching': "don't match specified pattern"
|
||||
};
|
||||
|
||||
// options: onlyChanged, offline, includeSlowTests, historyLines, testRegexp
|
||||
@@ -1015,7 +1030,7 @@ var runTests = function (options) {
|
||||
tests = _.filter(tests, function (test) {
|
||||
return options.testRegexp.test(test.name);
|
||||
});
|
||||
skipCounts.misnamed = lengthBeforeTestRegexp - tests.length;
|
||||
skipCounts['non-matching'] = lengthBeforeTestRegexp - tests.length;
|
||||
}
|
||||
|
||||
if (options.onlyChanged) {
|
||||
|
||||
@@ -141,7 +141,7 @@ selftest.define('claim - no username', ['net', 'slow'], function () {
|
||||
run.matchErr('Password:');
|
||||
run.write('test\n');
|
||||
run.waitSecs(commandTimeoutSecs);
|
||||
run.matchErr('you need to set a password');
|
||||
run.matchErr('You need to set a password');
|
||||
run.matchErr(testUtils.registrationUrlRegexp);
|
||||
run.expectExit(1);
|
||||
// After we set a username, we should be able to claim sites.
|
||||
|
||||
@@ -3,9 +3,63 @@ var selftest = require('../selftest.js');
|
||||
var testUtils = require('../test-utils.js');
|
||||
var files = require('../files.js');
|
||||
var Sandbox = selftest.Sandbox;
|
||||
var httpHelpers = require('../http-helpers.js');
|
||||
|
||||
var commandTimeoutSecs = testUtils.accountsCommandTimeoutSecs;
|
||||
|
||||
selftest.define('deploy - expired credentials', ['net', 'slow'], function () {
|
||||
var s = new Sandbox;
|
||||
// Create an account and then expire the login token before setting a
|
||||
// username. On the next deploy, we should get an email prompt
|
||||
// followed by a registration email, not a username prompt.
|
||||
var email = testUtils.randomUserEmail();
|
||||
var appName = testUtils.randomAppName();
|
||||
var token = testUtils.deployWithNewEmail(s, email, appName);
|
||||
var sessionFile = s.readSessionFile();
|
||||
testUtils.logout(s);
|
||||
s.writeSessionFile(sessionFile);
|
||||
var run = s.run('deploy', appName);
|
||||
run.waitSecs(commandTimeoutSecs);
|
||||
run.matchErr('Expired credential');
|
||||
run.expectExit(1);
|
||||
|
||||
// Complete registration so that we can clean up our app.
|
||||
var username = testUtils.randomString(10);
|
||||
testUtils.registerWithToken(token, username,
|
||||
'testtest', email);
|
||||
testUtils.login(s, username, 'testtest');
|
||||
testUtils.cleanUpApp(s, appName);
|
||||
testUtils.logout(s);
|
||||
|
||||
// Create an account, set a username, expire the login token, and
|
||||
// deploy again. We should get a username/password prompt.
|
||||
email = testUtils.randomUserEmail();
|
||||
appName = testUtils.randomAppName();
|
||||
username = testUtils.randomString(10);
|
||||
token = testUtils.deployWithNewEmail(s, email, appName);
|
||||
testUtils.registerWithToken(token, username,
|
||||
'testtest', email);
|
||||
run = s.run('whoami');
|
||||
run.waitSecs(commandTimeoutSecs);
|
||||
run.read(username + '\n');
|
||||
run.expectExit(0);
|
||||
|
||||
sessionFile = s.readSessionFile();
|
||||
testUtils.logout(s);
|
||||
s.writeSessionFile(sessionFile);
|
||||
|
||||
run = s.run('deploy', appName);
|
||||
run.waitSecs(commandTimeoutSecs);
|
||||
run.matchErr('Username:');
|
||||
run.write(username + '\n');
|
||||
run.matchErr('Password:');
|
||||
run.write('testtest' + '\n');
|
||||
run.waitSecs(90);
|
||||
run.expectExit(0);
|
||||
|
||||
testUtils.cleanUpApp(s, appName);
|
||||
});
|
||||
|
||||
selftest.define('deploy - bad arguments', [], function () {
|
||||
var s = new Sandbox;
|
||||
|
||||
@@ -202,7 +256,7 @@ selftest.define('deploy - logged out', ['net', 'slow'], function () {
|
||||
run.matchErr('Email:');
|
||||
run.write(email + '\n');
|
||||
run.waitSecs(commandTimeoutSecs);
|
||||
run.matchErr('already in use');
|
||||
run.matchErr('pick a password');
|
||||
run.matchErr('An email has been sent to you with the link');
|
||||
run.stop();
|
||||
});
|
||||
|
||||
@@ -14,8 +14,42 @@ selftest.define("login", ['net'], function () {
|
||||
// Username and password prompts happen on stderr so that scripts can
|
||||
// run commands that do login interactively and still save the command
|
||||
// output with the login prompts appearing in it.
|
||||
//
|
||||
// Do this twice to confirm that the login command prints a prompt
|
||||
// even if you are already logged in.
|
||||
for (var i = 0; i < 2; i++) {
|
||||
run = s.run("login");
|
||||
run.matchErr("Username:");
|
||||
run.write("test\n");
|
||||
run.matchErr("Password:");
|
||||
run.write("testtest\n");
|
||||
run.waitSecs(commandTimeoutSecs);
|
||||
run.matchErr("Logged in as test.");
|
||||
run.expectExit(0);
|
||||
}
|
||||
|
||||
// Leaving username blank, or getting the password wrong, doesn't
|
||||
// reprompt. It also doesn't log you out.
|
||||
run = s.run("login");
|
||||
run.matchErr("Username:");
|
||||
run.write("\n");
|
||||
run.matchErr("Password:");
|
||||
run.write("whatever\n");
|
||||
run.waitSecs(commandTimeoutSecs);
|
||||
run.matchErr("failed");
|
||||
run.expectExit(1);
|
||||
|
||||
run = s.run("login");
|
||||
run.matchErr("Username:");
|
||||
run.write("test\n");
|
||||
run.matchErr("Password:");
|
||||
run.write("whatever\n");
|
||||
run.waitSecs(commandTimeoutSecs);
|
||||
run.matchErr("failed");
|
||||
run.expectExit(1);
|
||||
|
||||
run = s.run('login');
|
||||
run.matchErr("Username:");
|
||||
run.write("test\n");
|
||||
run.matchErr("Password:");
|
||||
run.write("testtest\n");
|
||||
|
||||
@@ -71,8 +71,23 @@ var logsOrMongoForApp = function (sandbox, command, appName, options) {
|
||||
} else {
|
||||
// If we are not logged in and this is not a legacy app, then we
|
||||
// expect a login prompt.
|
||||
//
|
||||
// (If testReprompt is true, try getting reprompted as a result
|
||||
// of entering no username or a bad password.)
|
||||
if (options.testReprompt) {
|
||||
run.matchErr('Username: ');
|
||||
run.write("\n");
|
||||
run.matchErr("Username:");
|
||||
run.write(" \n");
|
||||
}
|
||||
run.matchErr('Username: ');
|
||||
run.write((options.username || 'test') + '\n');
|
||||
if (options.testReprompt) {
|
||||
run.matchErr("Password:");
|
||||
run.write("wrongpassword\n");
|
||||
run.waitSecs(15);
|
||||
run.matchErr("failed");
|
||||
}
|
||||
run.matchErr('Password: ');
|
||||
run.write((options.password || 'testtest') + '\n');
|
||||
run.waitSecs(commandTimeoutSecs);
|
||||
@@ -137,7 +152,8 @@ _.each([false, true], function (loggedIn) {
|
||||
logsOrMongoForApp(s, command,
|
||||
'app-for-selftest-not-test-owned', {
|
||||
loggedIn: loggedIn,
|
||||
authorized: false
|
||||
authorized: false,
|
||||
testReprompt: true
|
||||
});
|
||||
|
||||
if (! loggedIn) {
|
||||
|
||||
@@ -117,8 +117,13 @@ selftest.define('deferred registration - email registration token', ['net', 'slo
|
||||
|
||||
testUtils.registerWithToken(token, username, 'testtest', email);
|
||||
|
||||
// Success! We should be able to log out and log back in with our new
|
||||
// password.
|
||||
// Success! 'meteor whoami' should now know who we are.
|
||||
run = s.run('whoami');
|
||||
run.waitSecs(testUtils.accountsCommandTimeoutSecs);
|
||||
run.read(username + '\n');
|
||||
run.expectExit(0);
|
||||
|
||||
// We should be able to log out and log back in with our new password.
|
||||
testUtils.logout(s);
|
||||
testUtils.login(s, username, 'testtest');
|
||||
|
||||
@@ -133,6 +138,52 @@ selftest.define('deferred registration - email registration token', ['net', 'slo
|
||||
// XXX Test that registration URLs get printed when they should
|
||||
});
|
||||
|
||||
selftest.define('deferred registration revocation', ['net'], function () {
|
||||
// Test that if we are logged in as a passwordless user, and our
|
||||
// credential gets revoked, and we do something like 'meteor whoami'
|
||||
// that polls to see if registration is complete, then we handle it
|
||||
// gracefully.
|
||||
|
||||
var s = new Sandbox;
|
||||
s.createApp('deployapp', 'empty');
|
||||
s.cd('deployapp');
|
||||
|
||||
// Create a new deferred registration account. (Don't bother to wait
|
||||
// for the deploy to go through.)
|
||||
var email = testUtils.randomUserEmail();
|
||||
var username = testUtils.randomString(10);
|
||||
var appName = testUtils.randomAppName();
|
||||
var run = s.run('deploy', appName);
|
||||
run.waitSecs(5);
|
||||
run.matchErr('Email:');
|
||||
run.write(email + '\n');
|
||||
run.waitSecs(90);
|
||||
run.match('Deploying');
|
||||
run.stop();
|
||||
|
||||
// 'whoami' says that we don't have a password
|
||||
run = s.run('whoami');
|
||||
run.waitSecs(15);
|
||||
run.matchErr('/setPassword?');
|
||||
run.expectExit(1);
|
||||
|
||||
// Revoke the credential without updating .meteorsession.
|
||||
var sessionState = s.readSessionFile();
|
||||
run = s.run('logout');
|
||||
run.waitSecs(15);
|
||||
run.readErr("Logged out.\n");
|
||||
run.expectEnd();
|
||||
run.expectExit(0);
|
||||
s.writeSessionFile(sessionState);
|
||||
|
||||
// 'whoami' now says that we're not logged in. No errors are printed.
|
||||
run = s.run('whoami');
|
||||
run.waitSecs(15);
|
||||
run.readErr("Not logged in. 'meteor login' to log in.\n");
|
||||
run.expectEnd();
|
||||
run.expectExit(1);
|
||||
});
|
||||
|
||||
selftest.define(
|
||||
'deferred registration - api registration token',
|
||||
['net', 'slow'],
|
||||
@@ -185,8 +236,8 @@ selftest.define(
|
||||
run.matchErr('Email:');
|
||||
run.write(email + '\n');
|
||||
run.waitSecs(testUtils.accountsCommandTimeoutSecs);
|
||||
run.matchErr('already in use');
|
||||
run.matchErr('come back here to deploy your app');
|
||||
run.matchErr('pick a password');
|
||||
run.matchErr('Waiting for you to register on the web...');
|
||||
|
||||
var registrationEmail = waitForEmail(
|
||||
email,
|
||||
@@ -203,8 +254,8 @@ selftest.define(
|
||||
|
||||
testUtils.registerWithToken(token[1], username, 'testtest', email);
|
||||
run.waitSecs(testUtils.accountsCommandTimeoutSecs);
|
||||
run.matchErr('log in with your new password');
|
||||
run.matchErr('Password:');
|
||||
run.matchErr('Username: ' + username + '\n');
|
||||
run.matchErr('Password: ');
|
||||
run.write('testtest\n');
|
||||
run.waitSecs(90);
|
||||
run.match('Now serving at');
|
||||
|
||||
Reference in New Issue
Block a user