Skip to content

Commit

Permalink
added oauth token logic and cookies (#28)
Browse files Browse the repository at this point in the history
* added oauth token logic and cookies

* manage scopes from the manifest file

* updated versioning to be semantic
  • Loading branch information
jmango22 authored Sep 19, 2019
1 parent 9a70923 commit 67fbff9
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 19 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@domoinc/ryuu-proxy",
"version": "3.0.3",
"version": "3.1.0",
"description": "a middleware that provides a proxy for local domo app development",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
82 changes: 64 additions & 18 deletions src/lib/Transport/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import * as request from 'request';
import { Request } from 'express';
import { IncomingMessage, IncomingHttpHeaders } from 'http';

import { getMostRecentLogin } from '../utils';
import { Manifest, DomoClient, ProxyOptions } from '../models';
import { getMostRecentLogin, isOauthEnabled, getOauthTokens } from '../utils';
import { Manifest, DomoClient, ProxyOptions, OauthToken } from '../models';
import { CLIENT_ID } from '../constants';

export default class Transport {
private manifest: Manifest;
private clientPromise: Promise<DomoClient>;
private domainPromise: Promise;
private appContextId: string;
private oauthTokenPromise: Promise<OauthToken | undefined>;

constructor({
manifest,
Expand All @@ -22,6 +23,7 @@ export default class Transport {
this.appContextId = (typeof appContextId === 'string') ? appContextId : Domo.createUUID();
this.clientPromise = this.getLastLogin();
this.domainPromise = this.getDomoDomain();
this.oauthTokenPromise = this.getScopedOauthTokens(appContextId);
}

request = (options: request.Options) => this.clientPromise
Expand Down Expand Up @@ -71,6 +73,14 @@ export default class Transport {
.then(recentLogin => new Domo(recentLogin.instance, recentLogin.refreshToken, CLIENT_ID));
}

getScopedOauthTokens(appContextId: string): Promise<OauthToken | undefined> {
if (isOauthEnabled(this.manifest)) {
return getOauthTokens(appContextId, this.manifest.scopes);
}

return new Promise(resolve => resolve(undefined));
}

getDomoDomain(): Promise<string> {
const uuid = this.appContextId;
let domoClient;
Expand Down Expand Up @@ -132,46 +142,82 @@ export default class Transport {

return this.createContext();
})
.then((context) => {
.then(context => (this.prepareHeaders(req.headers, context.id)))
.then((headers) => {
const jar = request.jar();

return {
jar,
headers: this.prepareHeaders(req.headers, context.id),
headers,
url: api,
method: req.method,
};
});
}

private prepareHeaders(headers: IncomingHttpHeaders, context: string): IncomingHttpHeaders {
if (!headers.hasOwnProperty('referer')) headers.referer = 'https://0.0.0.0:3000';

const referer = (headers.referer.indexOf('?') >= 0)
? (`${headers.referer}&context=${context}`)
: (`${headers.referer}?userId=27&customer=dev&locale=en-US&platform=desktop&context=${context}`);
private prepareHeaders(headers: IncomingHttpHeaders, context: string): Promise<IncomingHttpHeaders> {
return this.oauthTokenPromise.then((tokens: OauthToken | undefined) => {
if (!headers.hasOwnProperty('referer')) headers.referer = 'https://0.0.0.0:3000';
const referer = (headers.referer.indexOf('?') >= 0)
? (`${headers.referer}&context=${context}`)
: (`${headers.referer}?userId=27&customer=dev&locale=en-US&platform=desktop&context=${context}`);

const cookieHeader = this.prepareCookies(headers, tokens);

const filters: string[] = (this.isMultiPartRequest(headers))
? ([
'content-type',
'content-length',
'cookie',
])
: ([
'cookie',
]);

if (this.isMultiPartRequest(headers)) {
// remove properties that are provided by the form-data library in request
return {
...Object.keys(headers).reduce(
(newHeaders, key) => {
if (key.toLowerCase() !== 'content-type' && key.toLowerCase() !== 'content-length') {
if (!filters.includes(key.toLowerCase())) {
newHeaders[key] = headers[key];
}
return newHeaders;
},
{}),
...cookieHeader,
referer,
host: undefined,
};
});
}

private prepareCookies(headers: IncomingHttpHeaders, tokens: OauthToken | undefined): { cookie: string } | {} {
const existingCookie = Object.keys(headers).reduce(
(newHeaders, key) => {
if (key.toLowerCase() === 'cookie') {
// handle if cookie is an array
if (Array.isArray(headers[key])) {
newHeaders['cookie'] = (headers[key] as string[]).join('; ');
} else {
newHeaders['cookie'] = headers[key] as string;
}
}
return newHeaders;
},
{});

const tokenCookie = (tokens !== undefined)
? ({ cookie: `_daatv1=${ tokens.access }; _dartv1=${ tokens.refresh }` })
: ({});

if (existingCookie['cookie'] !== undefined && tokenCookie['cookie'] !== undefined) {
return ({ cookie: `${existingCookie['cookie']}; ${tokenCookie['cookie']}` });
}

if (existingCookie['cookie'] === undefined) {
return tokenCookie;
}

return {
...headers,
referer,
host: undefined,
};
return existingCookie;
}

private parseBody(req: IncomingMessage): Promise<string|void> {
Expand Down
1 change: 1 addition & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const CLIENT_ID = 'domo:internal:devstudio';
export const OAUTH_ENABLED = 'oAuthEnabled';
7 changes: 7 additions & 0 deletions src/lib/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,11 @@ export interface Manifest {
width: number;
height: number;
};
oAuthEnabled?: boolean;
scopes?: string[];
}

export interface OauthToken {
access: string;
refresh: string;
}
25 changes: 25 additions & 0 deletions src/lib/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import * as fs from 'fs-extra';
import * as keytar from 'keytar';
import * as Promise from 'core-js/es6/promise';

import { OAUTH_ENABLED } from '../constants';
import { OauthToken, Manifest } from '../models';

export function getMostRecentLogin() {
const home = Domo.getHomeDir();
const logins = glob.sync(home + '/login/*.json');
Expand All @@ -19,3 +22,25 @@ export function getMostRecentLogin() {
return loginData;
});
}

export const isOauthEnabled = (manifest: Manifest): boolean =>
(Object.keys(manifest).includes(OAUTH_ENABLED) && manifest[OAUTH_ENABLED]);

export function getOauthTokens(proxyId: string, scopes: string[] | undefined): Promise<OauthToken> {
return getMostRecentLogin()
.then((loginData) => {
const instance = loginData.instance;
const allScopes = (scopes !== undefined)
? ([
'domoapps',
...scopes,
])
: (['domoapps']);

return Promise.all([
keytar.getPassword(`domoapps-oauth-access-${allScopes.join('-')}`, `${instance}-${proxyId}`),
keytar.getPassword(`domoapps-oauth-refresh-${allScopes.join('-')}`, `${instance}-${proxyId}`),
]);
})
.then((tokens: [string, string]) => ({ access: tokens[0], refresh: tokens[1] }));
}

0 comments on commit 67fbff9

Please sign in to comment.