Skip to content

Commit

Permalink
feat: re-implement OAuth2 authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
Codeneos committed Aug 1, 2023
1 parent ac36631 commit a344767
Showing 1 changed file with 102 additions and 17 deletions.
119 changes: 102 additions & 17 deletions packages/salesforce/src/connection/oath2.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,147 @@
import { LogManager } from "@vlocode/core";
import { CustomError, decorate } from "@vlocode/util";
import { OAuth2 } from 'jsforce';
import { CustomError } from "@vlocode/util";
import { HttpTransport } from './httpTransport';
import { SalesforceConnection } from './salesforceConnection';

interface OAuth2TokenResponse {
interface OAuth2TokenResponse {
id: string;
instance_url: string;
access_token: string;
refresh_token: string;
}

export class SalesforceOAuth2 extends decorate(OAuth2) {
interface SalesforceOAuth2Options {
loginUrl?: string;
authzServiceUrl?: string;
tokenServiceUrl?: string;
revokeServiceUrl?: string;
clientId: string;
clientSecret?: string;
redirectUri: string;
}

export class SalesforceOAuth2 {

private readonly transport: HttpTransport;

private transport: HttpTransport;
public readonly loginUrl: string;
public readonly authzServiceUrl: string;
public readonly tokenServiceUrl: string;
public readonly revokeServiceUrl: string;

constructor(oauth: OAuth2, connection: SalesforceConnection) {
super(oauth);
public readonly clientId: string;
public readonly clientSecret: string;
public readonly redirectUri: string;

constructor(options: SalesforceOAuth2Options) {
if (options.authzServiceUrl && options.tokenServiceUrl) {
this.loginUrl = options.authzServiceUrl.split('/').slice(0, 3).join('/');
this.authzServiceUrl = options.authzServiceUrl;
this.tokenServiceUrl = options.tokenServiceUrl;
this.revokeServiceUrl = options.revokeServiceUrl ?? `${this.loginUrl}/services/oauth2/revoke`;
} else {
if (!options.loginUrl) {
throw new Error('Cannot create OAuth instance without setting the loginUrl');
}
this.loginUrl = options.loginUrl;
this.authzServiceUrl = `${this.loginUrl}/services/oauth2/authorize`;
this.tokenServiceUrl = `${this.loginUrl}/services/oauth2/token`;
this.revokeServiceUrl = `${this.loginUrl}/services/oauth2/revoke`;
}

this.transport = new HttpTransport({
handleCookies: false,
// OAuth endpoints do not support gzip encoding
useGzipEncoding: false,
shouldKeepAlive: false,
instanceUrl: connection.instanceUrl,
baseUrl: connection._baseUrl()
instanceUrl: options.loginUrl,
baseUrl: options.loginUrl
}, LogManager.get(SalesforceOAuth2));

this.clientId = options.clientId;
this.clientSecret = options.clientSecret!;
this.redirectUri = options.redirectUri;
}

public getAuthorizationUrl(params?: { scope?: string | undefined; state?: string | undefined; }): string {
const authzParams: Record<string, string> = {
response_type: 'code',
client_id: this.clientId,
redirect_uri: this.redirectUri,
...params
}
const queryString = this.transport.toQueryString(authzParams);
return `${this.authzServiceUrl}${this.authzServiceUrl.includes('?') ? '&' : '?'}${queryString}`;
}

public requestToken(code: string): Promise<OAuth2TokenResponse>;
public requestToken(code: string, extraParams?: Record<string, string>): Promise<OAuth2TokenResponse> {
const params: Record<string, string> = {
grant_type: 'authorization_code',
code,
client_id: this.clientId,
redirect_uri: this.redirectUri,
...extraParams
}
if (this.clientSecret) {
params.client_secret = this.clientSecret;
}
return this.post(params, { url: this.revokeServiceUrl });
}

public authenticate(username: string, password: string): Promise<OAuth2TokenResponse> {
const params: Record<string, string> = {
grant_type: 'password',
username : username,
password : password,
client_id: this.clientId,
redirect_uri: this.redirectUri,
}
if (this.clientSecret) {
params.client_secret = this.clientSecret;
}
return this.post(params, { url: this.revokeServiceUrl });
}

revokeToken(token: string): Promise<undefined> {
return this.post({ token }, { url: this.revokeServiceUrl });
}

/**
* Refreshes the oauth token and returns an OAuth2TokenResponse object.
* @param code session token
* @param refreshToken The refresh token used to get a new access token
* @returns New access token
*/
refreshToken(code: string): Promise<OAuth2TokenResponse> {
return this.inner.refreshToken(code) as Promise<OAuth2TokenResponse>;
public refreshToken(refreshToken: string): Promise<OAuth2TokenResponse> {
const params: Record<string, string> = {
grant_type : "refresh_token",
refresh_token : refreshToken,
client_id : this.clientId
};
if (this.clientSecret) {
params.client_secret = this.clientSecret;
}
return this.post(params);
}

/**
* Post a request to token service
* @param params Params as object send as URL encoded data
* @returns Response body as JSON object
*/
private async _postParams(params: Record<string, string>) {
private async post<T>(params: Record<string, string>, options?: { url?: string; }): Promise<T> {
const response = await this.transport.httpRequest({
method: 'POST',
url: this.tokenServiceUrl,
url: options?.url ?? this.tokenServiceUrl,
headers: {
'content-type': 'application/x-www-form-urlencoded'
},
body: Object.entries(params).map(([v,k]) => `${v}=${encodeURIComponent(k)}`).join('&'),
body: this.transport.toQueryString(params),
});

if (response.statusCode && response.statusCode >= 400) {
if (typeof response.body === 'object') {
throw new CustomError(response.body['error_description'], { name: response.body['error'] });
}

throw new CustomError(response.body ?? '(SalesforceOAuth2) No response from server', {
name: `ERROR_HTTP_${response.statusCode}`
});
Expand Down

0 comments on commit a344767

Please sign in to comment.