diff --git a/auth-helper-dropbox.ts b/auth-helper-dropbox.ts new file mode 100644 index 0000000..3f3f4b0 --- /dev/null +++ b/auth-helper-dropbox.ts @@ -0,0 +1,34 @@ +/// + +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) { + 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 { + let cookieDomains = [".dropbox.com"]; + return this._logout(successPage, cookieDomains); + } +} diff --git a/demo-angular/app/main.ts b/demo-angular/app/main.ts index d553a07..131315a 100644 --- a/demo-angular/app/main.ts +++ b/demo-angular/app/main.ts @@ -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 = { diff --git a/index.d.ts b/index.d.ts index a7e9935..b36d80a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -9,6 +9,7 @@ export declare function initGoogle(options: TnsOAuth.ITnsOAuthOptionsGoogle): Pr export declare function initUaa(options: TnsOAuth.ITnsOAuthOptionsUaa): Promise; export declare function initLinkedIn(options: TnsOAuth.ITnsOAuthOptionsLinkedIn): Promise; export declare function initSalesforce(options: TnsOAuth.ITnsOAuthOptionsSalesforce): Promise; +export declare function initDropBox(options: TnsOAuth.ITnsOAuthOptionsDropBox): Promise; export declare function accessToken(): string; export declare function login(successPage?: string): Promise; diff --git a/index.ts b/index.ts index 06b48a5..c0cc9ea 100644 --- a/index.ts +++ b/index.ts @@ -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'; @@ -125,6 +126,23 @@ export function initSalesforce(options: TnsOAuth.ITnsOAuthOptionsSalesforce): Pr }); } +export function initDropBox(options: TnsOAuth.ITnsOAuthOptionsDropBox): Promise { + 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); + } + }); +} + diff --git a/tns-oauth-interfaces.d.ts b/tns-oauth-interfaces.d.ts index 3175248..b43a164 100644 --- a/tns-oauth-interfaces.d.ts +++ b/tns-oauth-interfaces.d.ts @@ -22,6 +22,9 @@ export interface ITnsOAuthCredentials { redirectUri: string; responseType?: string; scope: string; + ignoredTokenParams?: string[]; + ignoredAuthParams?: string[]; + skipNextTokenNav?: boolean; } export interface ITnsOAuthCredentialsUaa extends ITnsOAuthCredentials { @@ -68,3 +71,8 @@ export interface ITnsOAuthOptionsSalesforce extends ITnsOAuthOptions { redirectUri: string; responseType: string; } + +export interface ITnsOAuthOptionsDropBox extends ITnsOAuthOptions { + clientSecret: string; + redirectUri: string; +} diff --git a/tns-oauth.ts b/tns-oauth.ts index 2ab7a75..ff6f84a 100644 --- a/tns-oauth.ts +++ b/tns-oauth.ts @@ -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. @@ -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); } @@ -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); } @@ -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() { @@ -104,6 +127,7 @@ export function getTokenFromCache() { export function loginViaAuthorizationCodeFlow(credentials: TnsOAuthModule.ITnsOAuthCredentials, successPage?: string): Promise { return new Promise((resolve, reject) => { var navCount = 0; + var tokenNavStarted = false; let checkCodeIntercept = (webView, error, url): boolean => { var retStr = ''; @@ -135,6 +159,9 @@ export function loginViaAuthorizationCodeFlow(credentials: TnsOAuthModule.ITnsOA let errSubCode = qsObj['error_subcode']; if (codeStr) { try { + if(credentials.skipNextTokenNav && tokenNavStarted) return true; + + tokenNavStarted = true; getTokenFromCode(credentials, codeStr) .then((response: TnsOAuthModule.ITnsOAuthTokenResult) => { TnsOAuthTokenCache.setToken(response); diff --git a/tsconfig.json b/tsconfig.json index ad6ff72..83e5d73 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -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",