Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding Dropbox OAuth #61

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
34 changes: 34 additions & 0 deletions auth-helper-dropbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/// <reference path="references.d.ts" />

import * as tnsOauth from './tns-oauth';
import { AuthHelper } from './auth-helper';
import * as TnsOAuth from './tns-oauth-interfaces';

/**
Contains DropBox connection credentials
*/
export class AuthHelperDropBox extends AuthHelper implements TnsOAuth.ITnsAuthHelper {
//Constructs the the object with specified id, secret and scope
constructor(clientId: string, clientSecret: string, redirectUri: string, scope: Array<string>) {
super();
var scopeStr = scope.join('%20');
this.credentials = {
authority: 'https://www.dropbox.com',
tokenEndpointBase: 'https://api.dropboxapi.com',
authorizeEndpoint: '/oauth2/authorize',
tokenEndpoint: '/oauth2/token',
clientId: clientId,
clientSecret: clientSecret,
redirectUri: redirectUri,
scope: scopeStr,
ignoredAuthParams: ['response_mode','nonce'],
ignoredTokenParams: ['response_mode','nonce','state'],
skipNextTokenNav: true
};
}
//Gets cookie domains for logging out
public logout(successPage?: string): Promise<void> {
let cookieDomains = [".dropbox.com"];
return this._logout(successPage, cookieDomains);
}
}
8 changes: 8 additions & 0 deletions demo-angular/app/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ var facebookInitOptions: tnsOAuthModule.ITnsOAuthOptionsFacebook = {

tnsOAuthModule.initFacebook(facebookInitOptions);

// var dropboxInitOptions: tnsOAuthModule.ITnsOAuthOptionsDropBox = {
// clientId: 'XXXXXXXXXX',
// clientSecret: 'XXXXXXXXXX',
// redirectUri: 'http://localhost',
// scope: [],
// };

// tnsOAuthModule.initDropBox(dropboxInitOptions);


// let uaaInitOptions: tnsOAuthModule.ITnsOAuthOptionsUaa = {
Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export declare function initGoogle(options: TnsOAuth.ITnsOAuthOptionsGoogle): Pr
export declare function initUaa(options: TnsOAuth.ITnsOAuthOptionsUaa): Promise<any>;
export declare function initLinkedIn(options: TnsOAuth.ITnsOAuthOptionsLinkedIn): Promise<any>;
export declare function initSalesforce(options: TnsOAuth.ITnsOAuthOptionsSalesforce): Promise<any>;
export declare function initDropBox(options: TnsOAuth.ITnsOAuthOptionsDropBox): Promise<any>;

export declare function accessToken(): string;
export declare function login(successPage?: string): Promise<string>;
Expand Down
18 changes: 18 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { AuthHelperGoogle } from './auth-helper-google';
import { AuthHelperUaa } from './auth-helper-uaa';
import { AuthHelperLinkedIn } from './auth-helper-linkedin';
import { AuthHelperSalesforce } from './auth-helper-salesforce';
import { AuthHelperDropBox } from './auth-helper-dropbox';

import * as TnsOAuth from './tns-oauth-interfaces';

Expand Down Expand Up @@ -125,6 +126,23 @@ export function initSalesforce(options: TnsOAuth.ITnsOAuthOptionsSalesforce): Pr
});
}

export function initDropBox(options: TnsOAuth.ITnsOAuthOptionsDropBox): Promise<any> {
return new Promise(function (resolve, reject) {
try {
if (instance !== null) {
reject("You already ran init");
return;
}

instance = new AuthHelperDropBox(options.clientId, options.clientSecret, options.redirectUri, options.scope);
resolve(instance);
} catch (ex) {
console.log("Error in AuthHelperLinkedIn.init: " + ex);
reject(ex);
}
});
}




Expand Down
8 changes: 8 additions & 0 deletions tns-oauth-interfaces.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export interface ITnsOAuthCredentials {
redirectUri: string;
responseType?: string;
scope: string;
ignoredTokenParams?: string[];
ignoredAuthParams?: string[];
skipNextTokenNav?: boolean;
}

export interface ITnsOAuthCredentialsUaa extends ITnsOAuthCredentials {
Expand Down Expand Up @@ -68,3 +71,8 @@ export interface ITnsOAuthOptionsSalesforce extends ITnsOAuthOptions {
redirectUri: string;
responseType: string;
}

export interface ITnsOAuthOptionsDropBox extends ITnsOAuthOptions {
clientSecret: string;
redirectUri: string;
}
71 changes: 49 additions & 22 deletions tns-oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ function getAuthHeaderFromCredentials(credentials: TnsOAuthModule.ITnsOAuthCrede
return customAuthHeader;
}

function isIgnoredParameter(paramName: string, ignoredParameters: string[]): boolean {
return ignoredParameters.indexOf(paramName) > -1;
}

function setMemberIfNotIgnored(params: any, paramName: string, ignoredParams: string[], value: string): any {
if(!isIgnoredParameter(paramName, ignoredParams)) params[paramName] = value;
return params;
}

function addQueryStringIfNotIgnored(queryStr: string, paramName: string, ignoredParams: string[], value: string): string {
if(!isIgnoredParameter(paramName, ignoredParams)) queryStr += (queryStr.startsWith('?') ? '&' : '?') + (paramName + '=' + value);
return queryStr;
}


/**
* Gets a token for a given resource.
Expand All @@ -43,13 +57,15 @@ function getTokenFromCode(credentials: TnsOAuthModule.ITnsOAuthCredentials, code
customAuthHeader
);

let oauthParams = {
grant_type: 'authorization_code',
redirect_uri: credentials.redirectUri,
response_mode: 'form_post',
nonce: utils.newUUID(),
state: 'abcd'
};
credentials.ignoredTokenParams = credentials.ignoredTokenParams || [];

let oauthParams = { };

oauthParams = setMemberIfNotIgnored(oauthParams, 'grant_type', credentials.ignoredTokenParams, 'authorization_code');
oauthParams = setMemberIfNotIgnored(oauthParams, 'redirect_uri', credentials.ignoredTokenParams, credentials.redirectUri);
oauthParams = setMemberIfNotIgnored(oauthParams, 'response_mode', credentials.ignoredTokenParams, 'form_post');
oauthParams = setMemberIfNotIgnored(oauthParams, 'nonce', credentials.ignoredTokenParams, utils.newUUID());
oauthParams = setMemberIfNotIgnored(oauthParams, 'state', credentials.ignoredTokenParams, 'abcd');

return oauth2.getOAuthAccessToken(code, oauthParams);
}
Expand All @@ -71,13 +87,15 @@ export function getTokenFromRefreshToken(credentials: TnsOAuthModule.ITnsOAuthCr
customAuthHeader
);

let oauthParams = {
grant_type: 'refresh_token',
redirect_uri: credentials.redirectUri,
response_mode: 'form_post',
nonce: utils.newUUID(),
state: 'abcd'
};
credentials.ignoredTokenParams = credentials.ignoredTokenParams || [];

let oauthParams = { };

oauthParams = setMemberIfNotIgnored(oauthParams, 'grant_type', credentials.ignoredTokenParams, 'refresh_token');
oauthParams = setMemberIfNotIgnored(oauthParams, 'redirect_uri', credentials.ignoredTokenParams, credentials.redirectUri);
oauthParams = setMemberIfNotIgnored(oauthParams, 'response_mode', credentials.ignoredTokenParams, 'form_post');
oauthParams = setMemberIfNotIgnored(oauthParams, 'nonce', credentials.ignoredTokenParams, utils.newUUID());
oauthParams = setMemberIfNotIgnored(oauthParams, 'state', credentials.ignoredTokenParams, 'abcd');

return oauth2.getOAuthAccessToken(refreshToken, oauthParams);
}
Expand All @@ -87,14 +105,19 @@ export function getTokenFromRefreshToken(credentials: TnsOAuthModule.ITnsOAuthCr
* @return {string} a fully formed uri with which authentication can be completed
*/
export function getAuthUrl(credentials: TnsOAuthModule.ITnsOAuthCredentials): string {
return credentials.authority + credentials.authorizeEndpoint +
'?client_id=' + credentials.clientId +
'&response_type=code' +
'&redirect_uri=' + credentials.redirectUri +
'&scope=' + credentials.scope +
'&response_mode=query' +
'&nonce=' + utils.newUUID() +
'&state=abcd';
var queryStr = '';

credentials.ignoredAuthParams = credentials.ignoredAuthParams || [];

queryStr = addQueryStringIfNotIgnored(queryStr, 'client_id', credentials.ignoredAuthParams, credentials.clientId);
queryStr = addQueryStringIfNotIgnored(queryStr, 'response_type', credentials.ignoredAuthParams, 'code');
queryStr = addQueryStringIfNotIgnored(queryStr, 'redirect_uri', credentials.ignoredAuthParams, credentials.redirectUri);
queryStr = addQueryStringIfNotIgnored(queryStr, 'scope', credentials.ignoredAuthParams, credentials.scope);
queryStr = addQueryStringIfNotIgnored(queryStr, 'response_mode', credentials.ignoredAuthParams, 'query');
queryStr = addQueryStringIfNotIgnored(queryStr, 'nonce', credentials.ignoredAuthParams, utils.newUUID());
queryStr = addQueryStringIfNotIgnored(queryStr, 'state', credentials.ignoredAuthParams, 'abcd');

return credentials.authority + credentials.authorizeEndpoint + queryStr;
}

export function getTokenFromCache() {
Expand All @@ -104,6 +127,7 @@ export function getTokenFromCache() {
export function loginViaAuthorizationCodeFlow(credentials: TnsOAuthModule.ITnsOAuthCredentials, successPage?: string): Promise<TnsOAuthModule.ITnsOAuthTokenResult> {
return new Promise((resolve, reject) => {
var navCount = 0;
var tokenNavStarted = false;

let checkCodeIntercept = (webView, error, url): boolean => {
var retStr = '';
Expand Down Expand Up @@ -135,6 +159,9 @@ export function loginViaAuthorizationCodeFlow(credentials: TnsOAuthModule.ITnsOA
let errSubCode = qsObj['error_subcode'];
if (codeStr) {
try {
if(credentials.skipNextTokenNav && tokenNavStarted) return true;
Copy link
Author

Choose a reason for hiding this comment

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

We had a second call to checkCodeIntercept that was happening within the shouldOverrideUrlLoading method of the webview helper. Dropbox was returning an error because the code had already been used to receive a token by that point. I don't know if that would be an issue for other OAuth providers as well, but I've corrected it here using the skipNextTokenNav property, which currently only the Dropbox provider uses.


tokenNavStarted = true;
getTokenFromCode(credentials, codeStr)
.then((response: TnsOAuthModule.ITnsOAuthTokenResult) => {
TnsOAuthTokenCache.setToken(response);
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"auth-helper-office365.ts",
"auth-helper-uaa.ts",
"auth-helper-linkedin.ts",
"auth-helper-dropbox.ts",
"index.ts",
"tns-oauth.ts",
"tns-oauth-page-provider.ts",
Expand Down