passport-opskins
Version:
A simple passport strategy for authenticating users through the OPSkins platform
357 lines (300 loc) • 11.7 kB
JavaScript
const request = require('request'),
crypto = require('crypto'),
url = require('url'),
querystring = require('querystring'),
fs = require('fs'),
path = require('path');
function isValidJson(json) {
try {
JSON.parse(json);
} catch(err) {
return false;
}
return true;
}
module.exports = {
Strategy: function(obj, callback) {
if (!obj.name || !obj.returnURL || !obj.apiKey)
throw new Error('Missing name, returnURL or apiKey parameter. These are required.');
this.apiKey = Buffer.from(obj.apiKey + ':', 'ascii').toString('base64');
this.siteName = obj.name;
this.returnURL = obj.returnURL;
this.mobile = obj.mobile;
this.scopes = obj.scopes || 'identity';
this.states = [];
this.mobileStr = obj.mobile ? `&mobile=1` : ``;
this.permanentStr = obj.permanent ? `&duration=permanent` : ``;
this.clientID = null;
this.clientSecret = null;
this.name = 'opskins';
this.debug = obj.debug || null;
this.callback = callback;
this.passReqToCallback = obj.passReqToCallback || false;
this.setIdAndSecret = function(id, secret) {
this.clientID = id;
this.clientSecret = secret;
};
this.getLocalSavedClientList = function() {
if (!fs.existsSync(path.join(__dirname, 'clients.json')))
return [];
let data = fs.readFileSync(path.join(__dirname, 'clients.json'), 'utf8');
if (!isValidJson(data))
return [];
return JSON.parse(data).clients;
};
this.pushToLocalSavedClientList = function(client) {
if (!fs.existsSync(path.join(__dirname, 'clients.json')))
fs.writeFileSync(path.join(__dirname, 'clients.json'), JSON.stringify({
clients: []
}));
let jsonObj = fs.readFileSync(path.join(__dirname, 'clients.json'), 'utf8');
if (!isValidJson(jsonObj))
fs.writeFileSync(path.join(__dirname, 'clients.json'), JSON.stringify({
clients: []
}));
jsonObj = JSON.parse(jsonObj);
jsonObj.clients.push(client);
fs.writeFileSync(path.join(__dirname, 'clients.json'), JSON.stringify(jsonObj));
};
this.deleteClient = function(clientid) {
let options = {
url: 'https://api.opskins.com/IOAuth/DeleteClient/v1/',
headers: {
'authorization': `Basic ${this.apiKey}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `client_id=${clientid}`
};
request.post(options, (err, response, body) => {
if (err)
console.error(err);
});
};
this.getApiKey = function() {
return this.apiKey;
};
let _self = this;
this.getClientList = function(cb) {
let options = {
url: 'https://api.opskins.com/IOAuth/GetOwnedClientList/v1/',
headers: {
'authorization': `Basic ${_self.getApiKey()}`,
'Content-Type': 'application/json; charset=utf-8'
}
};
request.get(options, (err, response, body) => {
if (err)
return cb(err);
if (!isValidJson(body))
return cb(new Error(`Invalid JSON response`));
let realBody = JSON.parse(body);
if (realBody.status !== 1)
return cb(new Error(`Error retrieving clients: ${realBody.message}`));
cb(null, realBody.response.clients);
});
};
this.getOrMakeClient = function() {
let localSavedClients = this.getLocalSavedClientList();
let datApiKey = this.apiKey;
this.getClientList((err, clients) => {
if (err) return console.error(err);
let _dat = this;
let existingClient = null;
clients.forEach(function (client) {
localSavedClients.forEach(function(localClient) {
if (localClient.client_id == client.client_id && localClient.name == client.name && localClient.redirect_uri == client.redirect_uri && _dat.returnURL == client.redirect_uri)
existingClient = localClient;
});
});
if (existingClient) {
return this.setIdAndSecret(existingClient.client_id, existingClient.secret);
}
let options = {
url: 'https://api.opskins.com/IOAuth/CreateClient/v1/',
headers: {
'authorization': `Basic ${datApiKey}`,
'Content-Type': 'application/json; charset=utf-8'
},
body: `{"name": "${this.siteName}", "redirect_uri": "${this.returnURL}"}`
};
request.post(options, (err, response, body) => {
if (err)
return console.error(err);
if (!isValidJson(body))
return console.error(new Error(`Invalid JSON response`));
body = JSON.parse(body);
if (!body.response || !body.response.client || !body.response.client.client_id || !body.response.secret)
throw new Error(body.message);
body.response.client.secret = body.response.secret;
this.pushToLocalSavedClientList(body.response.client);
this.setIdAndSecret(body.response.client.client_id, body.response.secret);
});
});
};
this.getOrMakeClient();
this.updateStates = function(states) {
this.states = states;
};
this.getStates = function() {
return this.states;
};
this.getReturnUrl = function() {
return this.returnURL;
};
this.getAuth = function() {
return 'Basic ' + Buffer.from(this.clientID + ':' + this.clientSecret).toString('base64');
}
this.goLogin = function() {
const rand = crypto.randomBytes(4).toString('hex');
this.states.push(rand);
let _dat = this;
setTimeout(function () {
for (let i = 0; i < _dat.states.length; i++) {
if (_dat.states[i] == rand) {
_dat.states.splice(i, 1);
_dat.updateStates(_dat.states);
}
}
}, 600000);
return `https://oauth.opskins.com/v1/authorize?state=${rand}&client_id=${this.clientID}&response_type=code&scope=${this.scopes}${this.mobileStr}${this.permanentStr}`;
};
let _this = this;
this.authenticate = function(data, redirect) {
let urlOptions = data._parsedUrl;
let originalUrl = data.originalUrl;
if (url.parse(_this.getReturnUrl()).pathname == url.parse(originalUrl).pathname) {
let parsedQuery = querystring.parse(urlOptions.query);
let originated;
_this.getStates().forEach(function (state) {
if (state == parsedQuery.state) {
originated = true;
}
});
if (!originated) {
let err = new Error(`Authentication did not originate on this server`);
if (_this.debug)
return this.error(err);
console.error(err);
return this.fail(err);
}
let auth = _this.getAuth();
let headers = {
'Authorization': auth,
'Content-Type': 'application/x-www-form-urlencoded'
};
let options = {
url: 'https://oauth.opskins.com/v1/access_token',
method: 'POST',
headers: headers,
body: `grant_type=authorization_code&code=${parsedQuery.code}`
};
request.post(options, (err, response, body) => {
if (err) {
if (_this.debug)
return this.error(err);
console.error(err);
return this.fail(err);
}
if (!isValidJson(body)) {
let err = new Error(`Invalid JSON response`);
if (_this.debug)
return this.error(err);
console.error(err);
return this.fail(err);
}
body = JSON.parse(body);
if (body.error) {
let err = new Error(`Failed to serialize user into session: ${body.error}`);
if (_this.debug)
return this.error(err);
console.error(err);
return this.fail(err);
}
let headers2 = {
'Authorization': `Bearer ${body.access_token}`
};
let options2 = {
url: 'https://api.opskins.com/IUser/GetProfile/v1/',
headers: headers2
};
request.get(options2, (err, response, body3) => {
if (err) {
if (_this.debug)
return this.error(err);
console.error(err);
return this.fail(err);
}
if (!isValidJson(body3)) {
let err = new Error(`Invalid JSON response`);
if (_this.debug)
return this.error(err);
console.error(err);
return this.fail(err);
}
let realBody = JSON.parse(body3);
if (realBody.error) {
let err = new Error(`Failed to serialize user into session: ${realBody.error}`);
if (_this.debug)
return this.error(err);
console.error(err);
return this.fail(err);
}
let userObj = realBody.response;
// OPSkins don't give these anymore
// userObj.balance = realBody.balance;
// userObj.credits = realBody.credits;
// userObj.cryptoBalances = realBody.cryptoBalances;
userObj.access = body;
userObj.access.code = parsedQuery.code;
let datErr = _this.debug ? this.error : this.fail;
let datSuccess = this.success;
if(this.passReqToCallback) {
_this.callback(data, userObj, function(err, user) {
if (err) {
if (!_this.debug)
console.error(err);
return datErr(err);
}
datSuccess(user);
});
} else {
_this.callback(userObj, function(err, user) {
if (err) {
if (!_this.debug)
console.error(err);
return datErr(err);
}
datSuccess(user);
});
}
});
});
} else {
data.res.redirect(_this.goLogin());
}
};
this.refreshAccessToken = function(refreshToken, cb) {
let auth = this.getAuth();
let headers = {
'Authorization': auth,
'Content-Type': 'application/x-www-form-urlencoded'
};
let options = {
url: 'https://oauth.opskins.com/v1/access_token',
method: 'POST',
headers: headers,
body: `grant_type=refresh_token&refresh_token=${refreshToken}`
};
request.post(options, (err, response, body) => {
if (err)
return cb(err);
if (!isValidJson(body))
return cb(new Error(`Invalid JSON response`));
body = JSON.parse(body);
if (body.error)
return cb(new Error(body.error));
cb(null, body.access_token);
});
};
}
};