Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/glide-authenticate/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ package:
- dist/**
functions:
callback:
handler: dist/lib.callback
handler: dist/events/oauth/callback.default
events:
- http:
path: oauth/callback
method: get
connect:
handler: dist/lib.connect
handler: dist/events/connect.default
events:
- websocket:
route: $connect
message:
handler: dist/lib.message
handler: dist/events/message.default
events:
- websocket:
route: $default
refresh:
handler: dist/lib.refresh
handler: dist/events/oauth/refresh.default
events:
- http:
path: oauth/refresh
Expand Down
5 changes: 5 additions & 0 deletions packages/glide-authenticate/src/events/connect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { handler } from "../utilities";

export default handler(async () => {
// noop
});
15 changes: 15 additions & 0 deletions packages/glide-authenticate/src/events/message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { MessageType } from "../types";
import { handler, params, oauth2, send } from "../utilities";

export default handler(async request => {
const { connectionId, domainName, stage } = request.requestContext;
const strategy = oauth2.configure(params.get(request, "environment"));
const result = strategy.getAuthorizationUrl({
state: JSON.stringify({ connectionId, domainName, stage }),
});

await send(request.requestContext, {
data: result,
type: MessageType.Initialize,
});
});
26 changes: 26 additions & 0 deletions packages/glide-authenticate/src/events/oauth/callback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Connection } from "jsforce";

import { MessageType } from "../../types";
import { handler, params, oauth2, send } from "../../utilities";

export const callback = handler(async request => {
const environment = params.get(request, "environment");
const connection = new Connection({
oauth2: oauth2.configure(environment),
});

await connection.authorize(params.require(request, "code"));
await send(JSON.parse(params.require(request, "state")), {
data: {
accessToken: connection.accessToken,
oauth2: oauth2.options(environment),
// @ts-ignore
refreshToken: connection.refreshToken,
},
type: MessageType.Authenticated,
});

return {
message: "You have been successfully logged in",
};
});
13 changes: 13 additions & 0 deletions packages/glide-authenticate/src/events/oauth/refresh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { handler, oauth2 } from "../../utilities";

export const refresh = handler(async request => {
const { environment, token } = JSON.parse(request.body || "{}");
const strategy = oauth2.configure(environment);
const result = await strategy.refreshToken(token);

return {
oauth2: oauth2.options(environment),
accessToken: result.access_token,
refreshToken: result.refresh_token,
};
});
60 changes: 0 additions & 60 deletions packages/glide-authenticate/src/lib.ts
Original file line number Diff line number Diff line change
@@ -1,60 +0,0 @@
import { Connection, OAuth2 } from "jsforce";
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was split up into multiple files to make it easier to maintain in the future. Each file in the events directory now represents a single lambda event handler.

import nconf from "nconf";

import { handler, params, send } from "./utilities";

const oauth2 = new OAuth2(nconf.env("__").get("oauth"));

const enum MessageType {
Authenticated = "AUTHENTICATED",
Initialize = "INITIALIZE",
}

export const callback = handler(async request => {
const client = JSON.parse(params.require(request, "state"));
const connection = new Connection({ oauth2 });

await connection.authorize(params.require(request, "code"));

await send(client, {
data: {
accessToken: connection.accessToken,
oauth2: nconf.get("oauth"),
// @ts-ignore
refreshToken: connection.refreshToken,
},
type: MessageType.Authenticated,
});

return {
message: "You have been successfully logged in",
};
});

export const connect = handler(async () => {
// noop
});

export const message = handler(async request => {
const state = JSON.stringify({
connectionId: request.requestContext.connectionId,
domainName: request.requestContext.domainName,
stage: request.requestContext.stage,
});

await send(request.requestContext, {
data: oauth2.getAuthorizationUrl({ state }),
type: MessageType.Initialize,
});
});

export const refresh = handler(async request => {
const { token } = JSON.parse(request.body || "{}");
const results = await oauth2.refreshToken(token);

return {
oauth2: nconf.get("oauth"),
accessToken: results.access_token,
refreshToken: results.refresh_token,
};
});
22 changes: 22 additions & 0 deletions packages/glide-authenticate/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export enum Environment {
Default = "default",
Sandbox = "sandbox",
}

export namespace Environment {
export function from(input?: string): Environment {
switch (input) {
case Environment.Sandbox: {
return Environment.Sandbox;
}
default: {
return Environment.Default;
}
}
}
}

export const enum MessageType {
Authenticated = "AUTHENTICATED",
Initialize = "INITIALIZE",
}
30 changes: 28 additions & 2 deletions packages/glide-authenticate/src/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import lambda, { Context } from "aws-lambda";
import { ApiGatewayManagementApi } from "aws-sdk";
import { OAuth2, OAuth2Options } from "jsforce";
import nconf from "nconf";

import { Environment } from "./types";

let apiGateway: ApiGatewayManagementApi | null = null;

Expand All @@ -11,6 +15,8 @@ export type Response = lambda.APIGatewayProxyResult;
export type Responder = (request: Request, context: Context) => Promise<object | null | void>;

export function handler(responder: Responder): lambda.APIGatewayProxyHandler {
nconf.env("__");

return (request, context, callback) => {
responder(request, context).then(
data => {
Expand Down Expand Up @@ -52,9 +58,29 @@ export async function send(client: Client, data: object): Promise<void> {
await apiGateway.postToConnection(message).promise();
}

export namespace oauth2 {
const url = {
[Environment.Default]: "https://login.salesforce.com",
[Environment.Sandbox]: "https://test.salesforce.com",
};

export function configure(environment?: string): OAuth2 {
return new OAuth2(options(environment));
}

export function options(environment?: string): OAuth2Options {
return {
...nconf.get("oauth"),
loginUrl: url[Environment.from(environment)],
};
}
}

export namespace params {
export function get(request: Request, param: string): string | undefined {
return (request.queryStringParameters || {})[param];
export function get(request: Request, param: string): string | undefined;
export function get(request: Request, param: string, value: string): string;
export function get(request: Request, param: string, value?: string): string | undefined {
return (request.queryStringParameters || {})[param] || value;
}

export function require(request: Request, param: string): string {
Expand Down
14 changes: 9 additions & 5 deletions src/commands/init.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// tslint:disable: object-shorthand-properties-first

import { Definition, Operator } from "@glide/runtime";
import { Command } from "commander";
import jsforce from "jsforce";

import { Options } from "../lib";
import { login } from "../oauth";
import { Environment, login } from "../oauth";
import { json, string } from "../utilities";

interface Identifiers {
Expand All @@ -18,9 +19,10 @@ interface Inflection {
singular: string;
}

export default async function init(origin: string, path: string = "glide.json"): Promise<void> {
const { instance, schema } = createOptions(origin);
const connection = await login(instance);
export default async function init(origin: string, path: string, flags: Command): Promise<void> {
const { instance, sandbox, schema } = createOptions(origin, flags);
const environment = sandbox ? Environment.Default : Environment.Sandbox;
const connection = await login(instance, environment);
const sobjects = await connection.describeGlobal().then(({ sobjects }) => {
return Promise.all(sobjects.map(({ name }) => connection.describe(name)));
});
Expand Down Expand Up @@ -55,13 +57,15 @@ export default async function init(origin: string, path: string = "glide.json"):

await json.write(path, {
instance,
sandbox,
schema,
});
}

function createOptions(instance: string): Options {
function createOptions(instance: string, flags: Command): Options {
return {
instance,
sandbox: Boolean(flags.sandbox),
schema: {
mutations: {},
queries: {},
Expand Down
15 changes: 12 additions & 3 deletions src/commands/serve.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Connection } from "jsforce";
import open from "open";

import glide, { Options } from "../lib";
import { login } from "../oauth";
import { Environment, login } from "../oauth";
import { display, json, isDevEnv } from "../utilities";

export interface Flags {
Expand All @@ -16,7 +17,7 @@ export default async function serve(path: string = "glide.json", flags: Flags):
const listener = glide(options).listen(flags.port, () => {
const address = display.address(listener);

console.log(`server listening on ${address}`);
console.log(`serving objects from ${options.instance} on ${address}`);

if (isDevEnv()) {
open(address).then(browser => browser.unref());
Expand All @@ -35,9 +36,17 @@ export default async function serve(path: string = "glide.json", flags: Flags):

async function configure(path: string): Promise<Options> {
const options = await json.read<Options>(path);
let connection: Connection | null = null;

if (isDevEnv()) {
connection = await login(
options.instance,
options.sandbox ? Environment.Default : Environment.Sandbox,
);
}

return {
connection: isDevEnv() ? await login(options.instance) : null,
connection,
...options,
};
}
6 changes: 6 additions & 0 deletions src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ export interface Options {
*/
instance: string;

/**
* If `true`, `https://test.salesforce.com` will be used in place of
* `https://login.salesforce.com` as a login url.
*/
sandbox?: boolean;

/**
* A schema defintion that describes the shape of your Salesforce instance.
*/
Expand Down
1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default function main(): void {
commander
.command("init <url> [path]")
.description("generates a glide.json file in the current directory")
.option("-s, --sandbox", "should be set if the given url points to a salesforce sandbox")
.action(subcommand(init));

commander
Expand Down
23 changes: 17 additions & 6 deletions src/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ type Message =
| Readonly<{ data: Session; type: MessageType.Authenticated }>
| Readonly<{ data: string; type: MessageType.Initialize }>;

interface RefreshParams {
readonly environment?: Environment;
readonly token: string;
}

interface Refresh {
readonly data?: Session;
readonly errors?: [Pick<Error, "message">];
Expand All @@ -32,11 +37,19 @@ const enum MessageType {
Initialize = "INITIALIZE",
}

export async function login(instance: string): Promise<Connection> {
export const enum Environment {
Default = "default",
Sandbox = "sandbox",
}

export async function login(instance: string, environment: Environment): Promise<Connection> {
const credentials = await loadCredentials();

if (credentials[instance]) {
return refreshToken(instance, credentials[instance]);
return refreshToken(instance, {
environment,
token: credentials[instance],
});
}

return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -102,10 +115,8 @@ async function loadCredentials(): Promise<Credentials> {
return credentials;
}

async function refreshToken(instance: string, token: string): Promise<Connection> {
const [{ data, errors }, response] = await json.post<Refresh>(Endpoint.Refresh, {
token,
});
async function refreshToken(instance: string, params: RefreshParams): Promise<Connection> {
const [{ data, errors }, response] = await json.post<Refresh>(Endpoint.Refresh, params);

return response.ok
? createConnection(instance, data!)
Expand Down