Made all login Patreon only, limited some functionality to $5 patrons

This commit is contained in:
Thaum Rystra
2020-04-30 22:38:27 +02:00
parent 1ca6bc834a
commit 073578b90d
19 changed files with 654 additions and 314 deletions

View File

@@ -0,0 +1 @@
.build*

View File

@@ -0,0 +1,17 @@
Package.describe({
summary: 'Login service for Patreon accounts',
version: '0.1.0',
});
Package.onUse(api => {
api.use('ecmascript');
api.use('accounts-base', ['client', 'server']);
// Export Accounts (etc) to packages using this one.
api.imply('accounts-base', ['client', 'server']);
api.use('accounts-oauth', ['client', 'server']);
api.use('patreon-oauth');
api.imply('patreon-oauth');
api.addFiles('patreon.js');
});

View File

@@ -0,0 +1,26 @@
Accounts.oauth.registerService('patreon');
console.log('accounts-patreon');
if (Meteor.isClient) {
const loginWithPatreon = (options, callback) => {
// support a callback without options
if (! callback && typeof options === 'function') {
callback = options;
options = null;
}
const credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
Patreon.requestCredential(options, credentialRequestCompleteCallback);
};
Accounts.registerClientLoginFunction('patreon', loginWithPatreon);
Meteor.loginWithPatreon =
(...args) => Accounts.applyLoginFunction('patreon', args);
} else {
Accounts.addAutopublishFields({
// publish all fields including access token, which can legitimately
// be used from the client (if transmitted over ssl or on
// localhost). http://www.meetup.com/meetup_api/auth/#oauth2implicit
forLoggedInUser: ['services.patreon'],
forOtherUsers: ['services.patreon.id']
});
}

View File

@@ -0,0 +1,3 @@
# patreon-oauth
An implementation of the Patreon OAuth flow. See the [Meteor Guide](https://guide.meteor.com/accounts.html) for more details.

View File

@@ -0,0 +1,18 @@
Package.describe({
summary: 'Patreon OAuth flow',
version: '0.1.0'
});
Package.onUse(api => {
api.use('ecmascript');
api.use('oauth2', ['client', 'server']);
api.use('oauth', ['client', 'server']);
api.use('http', 'server');
api.use('random', 'client');
api.use('service-configuration', ['client', 'server']);
api.addFiles('patreon_server.js', 'server');
api.addFiles('patreon_client.js', 'client');
api.export('Patreon');
});

View File

@@ -0,0 +1,54 @@
Patreon = {};
console.log('patreon-oauth');
// Request Patreon credentials for the user
// @param options {optional}
// @param credentialRequestCompleteCallback {Function} Callback function to call on
// completion. Takes one argument, credentialToken on success, or Error on
// error.
Patreon.requestCredential = (options, credentialRequestCompleteCallback) => {
// support both (options, callback) and (callback).
if (!credentialRequestCompleteCallback && typeof options === 'function') {
credentialRequestCompleteCallback = options;
options = {};
}
const config = ServiceConfiguration.configurations.findOne({service: 'patreon'});
if (!config) {
credentialRequestCompleteCallback && credentialRequestCompleteCallback(
new ServiceConfiguration.ConfigError());
return;
}
// For some reason, meetup converts underscores to spaces in the state
// parameter when redirecting back to the client, so we use
// `Random.id()` here (alphanumerics) instead of `Random.secret()`
// (base 64 characters).
const credentialToken = Random.id();
const scope = (options && options.requestPermissions) || [
'identity',
'identity[email]',
];
const flatScope = scope.map(encodeURIComponent).join(' ');
//const flatScope = encodeURIComponent(scope.join(','));
console.log({flatScope})
const loginStyle = OAuth._loginStyle('patreon', config, options);
const loginUrl =
'https://www.patreon.com/oauth2/authorize' +
`?client_id=${config.clientId}` +
'&response_type=code' +
(flatScope ? `&scope=${flatScope}` : '') +
`&redirect_uri=${OAuth._redirectUri('patreon', config)}` +
`&state=${OAuth._stateParam(loginStyle, credentialToken, options && options.redirectUrl)}`;
OAuth.launchLogin({
loginService: 'patreon',
loginStyle,
loginUrl,
credentialRequestCompleteCallback,
credentialToken,
});
};

View File

@@ -0,0 +1,75 @@
Patreon = {};
OAuth.registerService('patreon', 2, null, query => {
const response = getAccessToken(query);
const accessToken = response.access_token;
const refreshToken = response.refresh_token;
const scope = response.scope;
const expiresAt = (+new Date) + (1000 * response.expires_in);
const identity = getIdentity(accessToken);
let serviceData = {
id : identity.data.id,
email: identity.data.attributes.email,
entitledCents: identity.included[0] &&
identity.included[0].attributes.currently_entitled_amount_cents || 0,
accessToken,
refreshToken,
scope,
expiresAt,
};
return { serviceData };
});
const getAccessToken = query => {
const config = ServiceConfiguration.configurations.findOne({service: 'patreon'});
if (!config)
throw new ServiceConfiguration.ConfigError();
let response;
try {
response = HTTP.post(
'https://www.patreon.com/api/oauth2/token', {headers: {Accept: 'application/json'}, params: {
code: query.code,
client_id: config.clientId,
client_secret: config.secret,
grant_type: 'authorization_code',
redirect_uri: OAuth._redirectUri('patreon', config),
}});
} catch (err) {
throw Object.assign(
new Error(`Failed to complete OAuth handshake with Patreon. ${err.message}`),
{ response: err.response }
);
}
if (response.data.error) { // if the http response was a json object with an error attribute
throw new Error(`Failed to complete OAuth handshake with Patreon. ${response.data.error}`);
} else {
return response.data;
}
};
const getIdentity = accessToken => {
try {
const response = HTTP.get(
'https://www.patreon.com/api/oauth2/v2/identity?' +
'fields%5Buser%5D=email&' +
'fields%5Bmember%5D=currently_entitled_amount_cents&' +
'include=memberships',
{
headers: {authorization: `Bearer ${accessToken}`},
}
);
let data = JSON.parse(response.content);
return data;
} catch (err) {
throw Object.assign(
new Error(`Failed to fetch identity from Patreon. ${err.message}`),
{ response: err.response }
);
}
};
Patreon.retrieveCredential = (credentialToken, credentialSecret) =>
OAuth.retrieveCredential(credentialToken, credentialSecret);