Merge pull request #12407 from meteor/release-3.0-tests-oauth1

Release 3.0 making `oauth1` async
This commit is contained in:
Gabriel Grubba
2023-01-04 10:04:48 -03:00
committed by GitHub
6 changed files with 50 additions and 38 deletions

View File

@@ -28,12 +28,20 @@
* `oauth`:
- `_endOfPopupResponseTemplate` and `_endOfRedirectResponseTemplate` are no longer a property but now a function that returns a promise of the same value as before
- the following methods are now async:
- the following server methods are now async:
- `OAuth._renderOauthResults`
- `OAuth._endOfLoginResponse`
- `OAuth.renderEndOfLoginResponse`
- `OAuth._storePendingCredential`
- `OAuth._retrievePendingCredential`
- `ensureConfigured`
- `_cleanStaleResults`
* `oauth1`:
- the following server methods are now async:
- `OAuth._storeRequestToken`
- `OAuth._retrieveRequestToken`
#### Internal API changes

View File

@@ -156,7 +156,7 @@ const middleware = async (req, res, next) => {
throw new Error(`Unexpected OAuth service ${serviceName}`);
// Make sure we're configured
ensureConfigured(serviceName);
await ensureConfigured(serviceName);
const handler = OAuth._requestHandlers[service.version];
if (!handler)
@@ -167,7 +167,6 @@ const middleware = async (req, res, next) => {
} else {
requestData = req.body;
}
await handler(service, requestData, res);
} catch (err) {
// if we got thrown an error, save it off, it will get passed to
@@ -179,7 +178,7 @@ const middleware = async (req, res, next) => {
// style the error or react to it in any way.
if (requestData?.state && err instanceof Error) {
try { // catch any exceptions to avoid crashing runner
OAuth._storePendingCredential(OAuth._credentialTokenFromQuery(requestData), err);
await OAuth._storePendingCredential(OAuth._credentialTokenFromQuery(requestData), err);
} catch (err) {
// Ignore the error and just give up. If we failed to store the
// error, then the login will just fail with a generic error.
@@ -193,7 +192,7 @@ const middleware = async (req, res, next) => {
// think to check server logs (we hope?)
// Catch errors because any exception here will crash the runner.
try {
OAuth._endOfLoginResponse(res, {
await OAuth._endOfLoginResponse(res, {
query: requestData,
loginStyle: OAuth._loginStyleFromQuery(requestData),
error: err
@@ -237,11 +236,14 @@ const oauthServiceName = req => {
};
// Make sure we're configured
const ensureConfigured = serviceName => {
if (!ServiceConfiguration.configurations.findOne({service: serviceName})) {
throw new ServiceConfiguration.ConfigError();
}
};
const ensureConfigured =
async serviceName => {
const config =
await ServiceConfiguration.configurations.findOne({ service: serviceName })
if (!config) {
throw new ServiceConfiguration.ConfigError();
}
};
const isSafe = value => {
// This matches strings generated by `Random.secret` and

View File

@@ -16,18 +16,22 @@ OAuth._pendingCredentials = new Mongo.Collection(
_preventAutopublish: true
});
OAuth._pendingCredentials.createIndex('key', { unique: true });
OAuth._pendingCredentials.createIndex('credentialSecret');
OAuth._pendingCredentials.createIndex('createdAt');
// TODO[FIBERS]: I Need TLA
async function init() {
await OAuth._pendingCredentials.createIndex('key', { unique: true });
await OAuth._pendingCredentials.createIndex('credentialSecret');
await OAuth._pendingCredentials.createIndex('createdAt');
}
init()
// Periodically clear old entries that were never retrieved
const _cleanStaleResults = () => {
const _cleanStaleResults = async () => {
// Remove credentials older than 1 minute
const timeCutoff = new Date();
timeCutoff.setMinutes(timeCutoff.getMinutes() - 1);
OAuth._pendingCredentials.remove({ createdAt: { $lt: timeCutoff } });
await OAuth._pendingCredentials.remove({ createdAt: { $lt: timeCutoff } });
};
const _cleanupHandle = Meteor.setInterval(_cleanStaleResults, 60 * 1000);
@@ -78,7 +82,6 @@ OAuth._retrievePendingCredential =
key,
credentialSecret,
});
if (pendingCredential) {
await OAuth._pendingCredentials.remove({ _id: pendingCredential._id });
if (pendingCredential.credential.error)

View File

@@ -47,13 +47,13 @@ const _cleanupHandle = Meteor.setInterval(_cleanStaleResults, 60 * 1000);
// @param requestToken {string}
// @param requestTokenSecret {string}
//
OAuth._storeRequestToken = (key, requestToken, requestTokenSecret) => {
OAuth._storeRequestToken = async (key, requestToken, requestTokenSecret) => {
check(key, String);
// We do an upsert here instead of an insert in case the user happens
// to somehow send the same `state` parameter twice during an OAuth
// login; we don't want a duplicate key error.
OAuth._pendingRequestTokens.upsert({
await OAuth._pendingRequestTokens.upsert({
key,
}, {
key,
@@ -69,12 +69,12 @@ OAuth._storeRequestToken = (key, requestToken, requestTokenSecret) => {
//
// @param key {string}
//
OAuth._retrieveRequestToken = key => {
OAuth._retrieveRequestToken = async key => {
check(key, String);
const pendingRequestToken = OAuth._pendingRequestTokens.findOne({ key: key });
const pendingRequestToken = await OAuth._pendingRequestTokens.findOne({ key: key });
if (pendingRequestToken) {
OAuth._pendingRequestTokens.remove({ _id: pendingRequestToken._id });
await OAuth._pendingRequestTokens.remove({ _id: pendingRequestToken._id });
return {
requestToken: OAuth.openSecret(pendingRequestToken.requestToken),
requestTokenSecret: OAuth.openSecret(

View File

@@ -26,7 +26,7 @@ OAuth._queryParamsWithAuthTokenUrl = (authUrl, oauthBinding, params = {}, whitel
// connect middleware
OAuth._requestHandlers['1'] = async (service, query, res) => {
const config = ServiceConfiguration.configurations.findOne({service: service.serviceName});
const config = await ServiceConfiguration.configurations.findOne({service: service.serviceName});
if (! config) {
throw new ServiceConfiguration.ConfigError(service.serviceName);
}
@@ -48,7 +48,7 @@ OAuth._requestHandlers['1'] = async (service, query, res) => {
await oauthBinding.prepareRequestToken(callbackUrl);
// Keep track of request token so we can verify it on the next step
OAuth._storeRequestToken(
await OAuth._storeRequestToken(
OAuth._credentialTokenFromQuery(query),
oauthBinding.requestToken,
oauthBinding.requestTokenSecret);
@@ -76,7 +76,7 @@ OAuth._requestHandlers['1'] = async (service, query, res) => {
// and close the window to allow the login handler to proceed
// Get the user's request token so we can verify it and clear it
const requestTokenInfo = OAuth._retrieveRequestToken(
const requestTokenInfo = await OAuth._retrieveRequestToken(
OAuth._credentialTokenFromQuery(query));
if (! requestTokenInfo) {
@@ -102,7 +102,7 @@ OAuth._requestHandlers['1'] = async (service, query, res) => {
// Store the login result so it can be retrieved in another
// browser tab by the result handler
OAuth._storePendingCredential(credentialToken, {
await OAuth._storePendingCredential(credentialToken, {
serviceName: service.serviceName,
serviceData: oauthResult.serviceData,
options: oauthResult.options
@@ -111,6 +111,6 @@ OAuth._requestHandlers['1'] = async (service, query, res) => {
// Either close the window, redirect, or render nothing
// if all else fails
OAuth._renderOauthResults(res, query, credentialSecret);
await OAuth._renderOauthResults(res, query, credentialSecret);
}
};

View File

@@ -23,7 +23,7 @@ const testPendingCredential = async (test, method) => {
this.accessTokenSecret = twitterfooAccessTokenSecret;
};
ServiceConfiguration.configurations.insert({service: serviceName});
await ServiceConfiguration.configurations.insert({service: serviceName});
try {
// register a fake login service
@@ -40,7 +40,7 @@ const testPendingCredential = async (test, method) => {
}));
// simulate logging in using twitterfoo
OAuth._storeRequestToken(credentialToken, twitterfooAccessToken);
await OAuth._storeRequestToken(credentialToken, twitterfooAccessToken);
const req = {
method,
@@ -73,9 +73,8 @@ const testPendingCredential = async (test, method) => {
};
await OAuthTest.middleware(req, res);
const credentialSecret = respData;
// Test that the result for the token is available
let result = OAuth._retrievePendingCredential(credentialToken,
let result = await OAuth._retrievePendingCredential(credentialToken,
credentialSecret);
const serviceData = OAuth.openSecrets(result.serviceData);
test.equal(result.serviceName, serviceName);
@@ -86,7 +85,7 @@ const testPendingCredential = async (test, method) => {
test.equal(result.options.option1, twitterOption1);
// Test that pending credential is removed after being retrieved
result = OAuth._retrievePendingCredential(credentialToken);
result = await OAuth._retrievePendingCredential(credentialToken);
test.isUndefined(result);
} finally {
@@ -110,24 +109,24 @@ Tinytest.addAsync("oauth1 - pendingCredential is stored and can be retrieved (wi
}
});
Tinytest.add("oauth1 - duplicate key for request token", test => {
Tinytest.addAsync("oauth1 - duplicate key for request token", async test => {
const key = Random.id();
const token = Random.id();
const secret = Random.id();
OAuth._storeRequestToken(key, token, secret);
await OAuth._storeRequestToken(key, token, secret);
const newToken = Random.id();
const newSecret = Random.id();
OAuth._storeRequestToken(key, newToken, newSecret);
const result = OAuth._retrieveRequestToken(key);
await OAuth._storeRequestToken(key, newToken, newSecret);
const result = await OAuth._retrieveRequestToken(key);
test.equal(result.requestToken, newToken);
test.equal(result.requestTokenSecret, newSecret);
});
Tinytest.add("oauth1 - null, undefined key for request token", test => {
Tinytest.addAsync("oauth1 - null, undefined key for request token", async test => {
const token = Random.id();
const secret = Random.id();
test.throws(() => OAuth._storeRequestToken(null, token, secret));
test.throws(() => OAuth._storeRequestToken(undefined, token, secret));
await test.throwsAsync(() => OAuth._storeRequestToken(null, token, secret));
await test.throwsAsync(() => OAuth._storeRequestToken(undefined, token, secret));
});
Tinytest.add("oauth1 - signature is built correctly", test => {