Skip to content

Instantly share code, notes, and snippets.

@polluterofminds
Created September 17, 2019 21:25
Show Gist options
  • Save polluterofminds/24b24215332cebddd185da026b01ff99 to your computer and use it in GitHub Desktop.
Save polluterofminds/24b24215332cebddd185da026b01ff99 to your computer and use it in GitHub Desktop.
import { lookupProfile } from 'blockstack';
require('dotenv').config()
const config = require('./config.json');
const request = require('request-promise');
const { InstanceDataStore } = require('blockstack/lib/auth/sessionStore');
const { connectToGaiaHub } = require('blockstack/lib/storage/hub');
const { AppConfig, UserSession } = require('blockstack');
let idAddress;
let configObj;
let apiKey;
let wallet;
let textile;
let headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' };
export function nameLookUp(name) {
//Note: if we want to support other names spaces and other root id, we will need a different approach.
const options = { url: `https://core.blockstack.org/v1/names/${name}.id.blockstack`, method: 'GET' };
return request(options)
.then(async () => {
return {
pass: false,
message: "name taken"
}
})
.catch(error => {
console.log(error.error)
if(error.error === '{\n "status": "available"\n}\n' || error.error === '{"status":"available"}') {
return {
pass: true,
message: "name available"
}
} else {
return {
pass: false,
body: error
}
}
});
}
export async function makeKeychain(credObj, devConfig) {
//Send the username and the passphrase which will be used by the server to encrypt sensitive data
const dataString = JSON.stringify({
username: credObj.id,
email: credObj.email,
password: credObj.password,
development: devConfig.development ? true : false,
devId: devConfig.devId,
storageModules: devConfig.storageModules,
authModules: devConfig.authModules
});
headers['Authorization'] = devConfig.apiKey;
//This is a simple call to replicate blockstack's make keychain function
const options = { url: config.KEYCHAIN_URL, method: 'POST', headers: headers, body: dataString };
return request(options)
.then(async (body) => {
// POST succeeded...
return {
success: true,
message: "successfully created keychain",
body: body
}
})
.catch(error => {
// POST failed...
console.log('ERROR: ', error)
return {
success: false,
message: "failed to create keychain",
body: error
}
});
}
export async function makeAppKeyPair(params, profile) {
//Need to determine if this call is being made on account registration or not
const dataString = JSON.stringify({
username: params.username,
password: params.password,
url: params.appObj.appOrigin,
profile: profile && profile.apps ? profile : null,
development: params.appObj.development ? true : false,
isDeveloper: params.appObj.isDev ? true : false,
devId: params.appObj.devId,
storageModules: params.appObj.storageModules,
authModules: params.appObj.authModules
});
headers['Authorization'] = params.appObj.apiKey;
var options = { url: config.APP_KEY_URL, method: 'POST', headers: headers, body: dataString };
return request(options)
.then((body) => {
return {
success: true,
message: "successfully created app keypair",
body: body
}
})
.catch((error) => {
console.log('Error: ', error.message);
return {
success: false,
message: "failed to created app keypair",
body: error.message
}
});
}
export async function createUserAccount(credObj, config, options={}) {
const statusCallbackFn = options.hasOwnProperty('statusCallbackFn') ?
options['statusCallbackFn'] : undefined
//For now we can continue to use Blockstack's name lookup, even for non-Blockstack auth
console.log("Verifying name availability...");
if (statusCallbackFn) {
statusCallbackFn("Verifying name availability...")
}
const nameCheck = await nameLookUp(credObj.id);
if(nameCheck.pass) {
console.log("Name check passed");
try {
console.log("Making keychain...");
if (statusCallbackFn) {
statusCallbackFn("Making keychain...")
}
const keychain = await makeKeychain(credObj, config);
if(keychain.success === false) {
//This would happen for a variety of reasons, just return the server message
return {
success: false,
message: keychain.body
}
} else {
console.log("Keychain made")
idAddress = keychain.body;
//Now we make the profile
let profile = makeProfile(config);
const appKeyParams = {
username: credObj.id,
appObj: config,
password: credObj.password
}
try {
console.log("Making app keys...");
if (statusCallbackFn) {
statusCallbackFn("Making app keys...")
}
const appKeys = await makeAppKeyPair(appKeyParams, profile);
if(appKeys) {
console.log("App keys created");
const appPrivateKey = JSON.parse(appKeys.body).blockstack ? JSON.parse(appKeys.body).blockstack.private : "";
configObj = JSON.parse(appKeys.body).config || {};
apiKey = JSON.parse(appKeys.body).apiKey || "";
wallet = JSON.parse(appKeys.body).wallet;
textile = JSON.parse(appKeys.body).textile || "";
const appUrl = JSON.parse(appKeys.body).blockstack.appUrl || "";
profile.apps[config.appOrigin] = appUrl;
//Let's register the name now
console.log("Registering name...");
if (statusCallbackFn) {
statusCallbackFn("Registering name...")
}
const registeredName = await registerSubdomain(credObj.id, idAddress);
if(registeredName) {
console.log("Name registered");
}
//Now, we login
try {
const userSessionParams = {
accountCreation: true,
credObj,
appObj: config,
userPayload: {
privateKey: appPrivateKey,
}
}
console.log("Logging in...");
if (statusCallbackFn) {
statusCallbackFn("Logging in...")
}
const userSession = await login(userSessionParams, profile);
if(userSession) {
console.log("Logged in");
return {
message: "user session created",
body: userSession.body
}
} else {
return {
message: "trouble creating user session",
body: null
}
}
} catch (loginErr) {
return {
message: "trouble logging in",
body: loginErr
}
}
} else {
return {
message: "error creating app keys",
body: null
}
}
} catch(error) {
return {
message: "error creating app keys",
body: error
}
}
}
} catch(keychainErr) {
return {
message: "Failed to create keychain",
body: keychainErr
}
}
} else {
return {
message: nameCheck.message,
body: null
}
}
}
export async function login(params, newProfile) {
//params object should include the credentials obj, appObj, (optional) user payload with appKey and mnemonic and (optional) a bool determining whether we need to fetch data from the DB
//@params fetchFromDB is a bool. Should be false if account was just created
//@params credObj is simply the username and password
//@params appObj is provided by the developer and is an object containing app scopes and app origin
//@params userPayload object that includes the app key and the mnemonic
if(params.accountCreation) {
const userPayload = params.userPayload;
const sessionObj = {
scopes: params.appObj.scopes,
appOrigin: params.appObj.appOrigin,
appPrivKey: userPayload.privateKey,
hubUrl: params.credObj.hubUrl ? params.credObj.hubUrl : "https://hub.blockstack.org", //Still have to think through this one
username: params.credObj.id,
profile: newProfile
}
try {
const userSession = await makeUserSession(sessionObj);
if(userSession) {
return {
message: "user session created",
body: userSession.body
}
} else {
return {
message: "error creating user session",
body: null
}
}
} catch (userSessErr) {
return {
message: "error creating user session",
body: userSessErr
}
}
} else {
const appKeyParams = {
username: params.credObj.id,
appObj: params.appObj,
password: params.credObj.password
}
const profile = await updateProfile(params.credObj.id, params.appObj);
try {
const appKeys = await makeAppKeyPair(appKeyParams, profile);
if(appKeys.success) {
const appPrivateKey = JSON.parse(appKeys.body).blockstack ? JSON.parse(appKeys.body).blockstack.private : "";
const appUrl = JSON.parse(appKeys.body).blockstack.appUrl || "";
configObj = JSON.parse(appKeys.body).config;
apiKey = JSON.parse(appKeys.body).apiKey || "";
wallet = JSON.parse(appKeys.body).wallet;
textile = JSON.parse(appKeys.body).textile || "";
profile.apps[params.appObj.appOrigin] = appUrl;
//Now, we login
try {
const userSessionParams = {
credObj: params.credObj,
appObj: params.appObj,
userPayload: {
privateKey: appPrivateKey,
}
}
const userPayload = userSessionParams.userPayload;
const sessionObj = {
scopes: params.appObj.scopes,
appOrigin: params.appObj.appOrigin,
appPrivKey: userPayload.privateKey,
hubUrl: params.credObj.hubUrl ? params.credObj.hubUrl : "https://hub.blockstack.org", //Still have to think through this one
username: params.credObj.id,
profile: newProfile
}
const userSession = await makeUserSession(sessionObj);
if(userSession) {
return {
message: "user session created",
body: userSession.body
}
} else {
return {
message: "trouble creating user session",
body: null
}
}
} catch (loginErr) {
return {
message: "trouble logging in",
body: loginErr
}
}
} else {
return {
message: "error creating app keys",
body: appKeys.body
}
}
} catch(error) {
return {
message: "error creating app keys",
body: appKeys.body ? appKeys.body : error
}
}
}
}
export async function makeUserSession(sessionObj) {
if(!configObj) {
configObj = {};
}
const appConfig = new AppConfig(
sessionObj.scopes,
sessionObj.appOrigin
)
const dataStore = new InstanceDataStore({
userData: {
appPrivateKey: sessionObj.appPrivKey,
identityAddress: idAddress,
hubUrl: sessionObj.hubUrl,
identityAddress: idAddress,
devConfig: configObj,
username: sessionObj.username,
gaiaHubConfig: await connectToGaiaHub('https://hub.blockstack.org', sessionObj.appPrivKey,""),
profile: sessionObj.profile,
wallet: wallet ? wallet : {},
textile,
apiKey
},
})
const userSession = new UserSession({
appConfig,
sessionStore: dataStore
})
try {
return {
message: "user session created",
body: userSession
}
} catch(err) {
return {
message: "failed to create user session",
body: err
}
}
}
export function registerSubdomain(name) {
const newId = idAddress.split('ID-')[1]
const zonefile = `$ORIGIN ${name}\n$TTL 3600\n_https._tcp URI 10 1 \"https://gaia.blockstack.org/hub/${newId}/profile.json\"\n`;
const dataString = JSON.stringify({zonefile, name, owner_address: newId})
console.log(dataString);
const options = {
url: config.SUBDOMAIN_REGISTRATION,
method: 'POST',
headers: {
'cache-control': 'no-cache,no-cache',
'Content-Type': 'application/json',
'Authorization': 'bearer API-KEY-IF-USED'
},
body: dataString
};
return request(options)
.then(async (body) => {
// POST succeeded...
console.log('success username registered')
return {
message: "username registered",
body: body
}
})
.catch(error => {
// POST failed...
console.log('ERROR: ', error)
return {
message: "failed to register username",
body: error
}
});
}
export function makeProfile(appObj) {
let profile = {
'@type': 'Person',
'@context': 'http://schema.org',
'apps': {}
}
if(appObj.scopes.indexOf("publish_data") > -1) {
profile.apps[appObj.appOrigin] = ""
}
return profile;
}
export async function updateProfile(name, appObj) {
//First we look up the profile
let profile
try {
profile = await lookupProfile(`${name}.id.blockstack`);
console.log("PROFILE:", profile);
if (profile.apps) {
if(profile.apps[appObj.appOrigin]) {
//Don't need to do anything unless the gaia hub url is an empty string
} else {
if(appObj.scopes.indexOf("publish_data") > -1) {
profile.apps[appObj.appOrigin] = ""
}
}
}
return profile;
}
catch (error) {
console.log("ERROR:", error);
profile = {
'@type': 'Person',
'@context': 'http://schema.org',
'apps': {}
}
if(appObj.scopes.indexOf("publish_data") > -1) {
profile.apps[appObj.appOrigin] = ""
}
return profile;
}
}
export function getConfig(params) {
const payload = {
devId: params.devId,
development: params.development ? true : false
};
headers['Authorization'] = params.apiKey;
console.log(payload);
console.log(headers);
var options = { url: config.GET_CONFIG_URL, method: 'POST', headers: headers, form: payload };
return request(options)
.then((body) => {
console.log(body);
return {
message: "get developer account config",
body: body
}
})
.catch(error => {
console.log('Error: ', error);
return {
message: "failed to get developer account",
body: error
}
});
}
export function updateConfig(updates, verification) {
const payload = {
devId: updates.username,
config: JSON.stringify(updates.config),
development: updates.development ? true : false
};
console.log(payload);
if(verification) {
headers['Authorization'] = updates.verificationID;
} else {
headers['Authorization'] = updates.apiKey;
}
console.log(headers);
var options = { url: config.UPDATE_CONFIG_URL, method: 'POST', headers: headers, form: payload };
return request(options)
.then((body) => {
console.log(body);
return {
message: "updated developer account",
body: body
}
})
.catch(error => {
console.log('Error: ', error);
return {
message: "failed to update developer account",
body: error
}
});
}
export function createContract(params) {
const payload = {
devId: params.devId,
password: params.password,
username: params.username,
abi: JSON.stringify(params.abi),
bytecode: params.bytecode,
development: params.development ? true : false
}
headers['Authorization'] = params.apiKey;
var options = { url: config.CREATE_CONTRACT_URL, method: 'POST', headers: headers, form: payload };
return request(options)
.then((body) => {
console.log(body);
return {
message: "contract created and deployed",
body: body
}
})
.catch(error => {
console.log('Error: ', error);
return {
message: "failed to create contract",
body: error
}
});
}
export function fetchContract(params) {
const payload = {
devId: params.devId,
contractAddress: params.contractAddress,
abi: JSON.stringify(params.abi),
development: params.development ? true : false
}
headers['Authorization'] = params.apiKey;
var options = { url: config.FETCH_CONTRACT_URL, method: 'POST', headers: headers, form: payload };
return request(options)
.then((body) => {
console.log(body);
return {
message: "retreived contract and executed",
body: body
}
})
.catch(error => {
console.log('Error: ', error);
return {
message: "failed to retreive contract",
body: error
}
});
}
export function pinContent(params) {
const payload = JSON.stringify({
devId: params.devId,
username: params.username,
devSuppliedIdentifier: params.id,
contentToPin: params.content,
development: params.development ? true : false
});
headers['Authorization'] = params.apiKey;
var options = { url: config.PIN_CONTENT_URL, method: 'POST', headers: headers, body: payload };
return request(options)
.then((body) => {
return {
message: "content successfully pinned",
body: body
}
})
.catch(error => {
console.log('Error: ', error);
return {
message: "failed to pin content",
body: error
}
});
}
export function fetchPinnedContent(params) {
const payload = JSON.stringify({
devId: params.devId,
username: params.username,
devSuppliedIdentifier: params.id,
development: params.development ? true : false
})
headers['Authorization'] = params.apiKey;
var options = { url: config.FETCH_PINNED_CONTENT_URL, method: 'POST', headers: headers, body: payload };
return request(options)
.then((body) => {
return {
message: "Found pinned content",
body: body
}
})
.catch(error => {
console.log('Error: ', error);
return {
message: "failed to find pinned content",
body: error
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment