@velas/account-agent
Version:
sdk
321 lines (252 loc) • 10.5 kB
JavaScript
import urljoin from 'url-join';
import qs from 'qs';
import assert from '../helper/assert';
import Storage from '../helper/storage';
import KeyStorage from '../helper/key-storage';
import use from '../middleware/use';
import checkJWT from '../middleware/check-jwt';
import checkScopes from '../middleware/check-scopes';
import checkClient from '../middleware/check-client';
import initInteraction from '../middleware/init-interaction';
import deleteInteraction from '../middleware/delete-interaction';
import checkRedirectUri from '../middleware/check-redirect-uri';
import saveInteraction from '../middleware/save-interaction';
import checkPolicy from '../middleware/check-policy';
import processSubmission from '../middleware/process-submission';
import bindSession from '../middleware/bind-session';
import bindSessions from '../middleware/bind-sessions';
import bindScopes from '../middleware/bind-scopes';
import AuthorizationCode from '../middleware/authorization-code';
import RedirectUri from '../middleware/redirect-uri';
import removeExpiredItems from '../middleware/remove-expired-items';
import tokenController from '../controllers/token';
import transactionController from '../controllers/transaction';
import userinfoController from '../controllers/userinfo';
import exchangeController from '../controllers/exchange';
import storageAccessController from '../controllers/storageAccess';
import Client from '../Client';
import getSession from '../helper/session';
import ScopesConstructor from '../helper/scopes';
/**
* Creates a new Agent API
* @constructor
* @param {Object} options
*/
function Agent(options) {
assert.check(
options,
{ type: 'object', message: 'options parameter is not valid' },
{
StorageHandler: { type: 'function', message: 'StorageHandler option is required' },
KeyStorageHandler: { type: 'function', message: 'KeyStorageHandler option is required' },
client_host: { type: 'string', message: 'client_host option is required' },
client_provider: { type: 'object', message: 'client_provider option is required' },
client_account_contract: { type: 'string', message: 'client_account_contract option is required' },
backend_payer_public_key: { type: 'string', message: 'backend_payer_public_key option is required' },
},
);
this.baseOptions = options;
this.baseOptions.issuer = 'agent';
const storage = new Storage(this.baseOptions);
const keyStorage = new KeyStorage(this.baseOptions);
const client = new Client(this.baseOptions, keyStorage);
this.provider = {
Session: getSession(storage, keyStorage),
storage,
keyStorage,
client,
sc: new ScopesConstructor({ account_contract: options.client_account_contract }),
};
};
async function methodHandler(method, ctx) {
try {
if (!['token', 'transaction', 'userinfo', 'exchange', 'storage_access' ].includes(method)) throw new Error('method option is wrong');
if (method === 'token') {
await use([ checkJWT ], ctx);
return await tokenController(ctx);
} else if(method === 'transaction') {
return await transactionController(ctx);
} else if(method === 'userinfo') {
return await userinfoController(ctx);
} else if(method === 'exchange') {
return await exchangeController(ctx);
} else if(method === 'storage_access') {
return await storageAccessController(ctx);
};
} catch(e) {
return {
error: 'invalid_request',
description: e.message,
};
};
};
const eventHendler = async function(event) {
const { origin, data: { method, state, token, data }} = event;
if (!state || method === 'custom') return;
event.source.postMessage({ state, stage: 'pending'}, origin);
const ctx = {
params: { token, method, data, origin },
provider: this.provider,
};
event.source.postMessage({ state, stage: 'done', ...await methodHandler(method, ctx)}, origin);
};
Agent.prototype.process = async function(method, { token, data }) {
assert.check(method, { type: 'string', message: 'options method is not valid' });
assert.check(token, { type: 'string', message: 'options token is not valid' });
const ctx = {
params: { token, method, data },
provider: this.provider,
};
return await methodHandler(method, ctx);
};
Agent.prototype.addEventListener = function() {
var eventListener = (eventHendler).bind(this);
if ( window.addEventListener ) { window.addEventListener("message", eventListener, false) }
else { window.attachEvent("onmessage", eventListener, false) }; // IE 8;
};
Agent.prototype.interaction = async function(options) {
assert.check(
options,
{ type: 'object', message: 'options parameter is not valid' },
{ id: { type: 'string', message: 'id option is required' }},
);
const foundInteraction = await this.provider.storage.getItem(`${'Interaction'}:${options.id}`);
assert.check(foundInteraction, { type: 'object', message: 'interaction not found' });
return { interaction: foundInteraction };
};
Agent.prototype.authorize = async function(options) {
assert.check(
options,
{ type: 'object', message: 'options parameter is not valid' },
{ token: { type: 'string', message: 'token option is required' }},
);
const ctx = {
params: { token: options.token },
provider: this.provider,
};
await use([
checkJWT,
checkScopes,
initInteraction,
removeExpiredItems,
bindSessions,
checkClient,
checkRedirectUri,
checkPolicy,
bindScopes,
saveInteraction,
], ctx);
if (ctx.interaction.redirect) {
return { redirect: ctx.interaction.redirect };
};
return { interaction: ctx.interaction };
};
Agent.prototype.endSession = async function(interaction_id, session_id) {
assert.check(interaction_id,{ type: 'string', message: 'interaction_id option is required in endSession' });
assert.check(session_id,{ type: 'string', message: 'session_id option is required in endSession' });
const foundInteraction = await this.provider.storage.getItem(`${'Interaction'}:${interaction_id}`);
assert.check(foundInteraction, { type: 'object', message: 'interaction not found' });
const session = await this.provider.Session.findById(session_id);
if (session) await session.destroy();
const ctx = {
result: {},
interaction: foundInteraction,
provider: this.provider,
};
await use([
bindSession,
processSubmission,
bindSessions,
checkPolicy,
bindScopes,
saveInteraction,
], ctx);
if (ctx.interaction.stage === 'completed') {
await use([
AuthorizationCode,
RedirectUri,
deleteInteraction,
], ctx);
return { redirect: ctx.redirect };
};
return { interaction: ctx.interaction };
};
Agent.prototype.logout = async function(interaction_id, session_id, client_id) {
assert.check(interaction_id,{ type: 'string', message: 'interaction_id option is required in logout' });
assert.check(session_id,{ type: 'string', message: 'session_id option is required in logout' });
assert.check(client_id,{ type: 'string', message: 'client_id option is required in logout' });
const foundInteraction = await this.provider.storage.getItem(`${'Interaction'}:${interaction_id}`);
assert.check(foundInteraction, { type: 'object', message: 'interaction not found' });
const session = await this.provider.Session.findById(session_id);
if (session) await session.logout(client_id);
const ctx = {
result: {},
interaction: foundInteraction,
provider: this.provider,
};
await use([
bindSession,
processSubmission,
bindSessions,
checkPolicy,
bindScopes,
saveInteraction,
], ctx);
if (ctx.interaction.stage === 'completed') {
await use([
AuthorizationCode,
RedirectUri,
deleteInteraction,
], ctx);
return { redirect: ctx.redirect };
};
return { interaction: ctx.interaction };
};
Agent.prototype.cancelInteraction = async function(id) {
assert.check(id, { type: 'string', message: 'id option is required' });
const foundInteraction = await this.provider.storage.getItem(`${'Interaction'}:${id}`);
assert.check(foundInteraction, { type: 'object', message: 'interaction not found' });
let redirect = {
error: 'access_denied',
description: 'canceled by user',
state: foundInteraction.params.state || undefined,
}
if (foundInteraction.params.mode === 'redirect') {
redirect = urljoin(foundInteraction.params.redirect_uri, '', '?' + qs.stringify(redirect));
}
this.provider.storage.removeItem(`${'Interaction'}:${id}`);
return { redirect };
};
Agent.prototype.finishInteraction = async function(id, result) {
assert.check(
result,
{ type: 'object', message: 'result parameter is not valid in finishInteraction' },
{ mergeWithLastSubmission: { type: 'boolean', message: 'mergeWithLastSubmission option is required in finishInteraction' }},
);
assert.check(id,{ type: 'string', message: 'id option is required in finishInteraction' });
const foundInteraction = await this.provider.storage.getItem(`${'Interaction'}:${id}`);
assert.check(foundInteraction, { type: 'object', message: 'interaction not found' });
const ctx = {
result,
interaction: foundInteraction,
provider: this.provider,
};
await use([
bindSession,
processSubmission,
bindSessions,
checkPolicy,
bindScopes,
saveInteraction,
], ctx);
if (ctx.interaction.stage === 'completed') {
await use([
AuthorizationCode,
RedirectUri,
deleteInteraction,
], ctx);
return { redirect: ctx.redirect };
};
return { interaction: ctx.interaction };
}
export default Agent;