Skip to content

Commit

Permalink
[SDK-2542] Expose methods from Auth0 SPA SDK (#150)
Browse files Browse the repository at this point in the history
  • Loading branch information
Steve Hobbs authored May 6, 2021
1 parent 599ba2f commit 2117a23
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 13 deletions.
36 changes: 34 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

115 changes: 113 additions & 2 deletions projects/auth0-angular/src/lib/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { TestBed } from '@angular/core/testing';
import { AuthService } from './auth.service';
import { Auth0ClientService } from './auth.client';
import { Auth0Client, IdToken } from '@auth0/auth0-spa-js';
import {
Auth0Client,
IdToken,
LogoutUrlOptions,
RedirectLoginOptions,
} from '@auth0/auth0-spa-js';
import { AbstractNavigator } from './abstract-navigator';
import { bufferCount, bufferTime, filter } from 'rxjs/operators';
import { bufferCount, bufferTime, filter, mergeMap, tap } from 'rxjs/operators';
import { Location } from '@angular/common';
import { AuthConfig, AuthConfigService } from './auth.config';

Expand Down Expand Up @@ -42,6 +47,8 @@ describe('AuthService', () => {
spyOn(auth0Client, 'getIdTokenClaims').and.resolveTo(undefined);
spyOn(auth0Client, 'logout');
spyOn(auth0Client, 'getTokenSilently').and.resolveTo('__access_token__');
spyOn(auth0Client, 'buildAuthorizeUrl').and.resolveTo('/authorize');
spyOn(auth0Client, 'buildLogoutUrl').and.returnValue('/v2/logout');

spyOn(auth0Client, 'getTokenWithPopup').and.resolveTo(
'__access_token_from_popup__'
Expand Down Expand Up @@ -609,4 +616,108 @@ describe('AuthService', () => {
});
});
});

describe('handleRedirectCallback', () => {
let navigator: AbstractNavigator;

beforeEach(() => {
TestBed.resetTestingModule();

navigator = jasmine.createSpyObj('RouteNavigator', {
navigateByUrl: Promise.resolve(true),
}) as any;

TestBed.configureTestingModule({
...moduleSetup,
providers: [
{
provide: AbstractNavigator,
useValue: navigator,
},
{
provide: Auth0ClientService,
useValue: auth0Client,
},
{
provide: Location,
useValue: locationSpy,
},
{
provide: AuthConfigService,
useValue: {
...authConfig,
skipRedirectCallback: true,
},
},
],
});

locationSpy.path.and.returnValue('');
});

it('should call the underlying SDK', (done) => {
const localService = createService();

localService.handleRedirectCallback().subscribe(() => {
expect(auth0Client.handleRedirectCallback).toHaveBeenCalled();
done();
});
});

it('should call the underlying SDK and pass options', (done) => {
const url = 'http://localhost';
const localService = createService();

localService.handleRedirectCallback(url).subscribe(() => {
expect(auth0Client.handleRedirectCallback).toHaveBeenCalledWith(url);
done();
});
});

it('should refresh the internal state', (done) => {
const localService = createService();

localService.isAuthenticated$
.pipe(bufferCount(2))
.subscribe((authenticatedStates) => {
expect(authenticatedStates).toEqual([false, true]);
expect(auth0Client.isAuthenticated).toHaveBeenCalled();
done();
});

localService.isLoading$
.pipe(
filter((isLoading) => !isLoading),
tap(() =>
(auth0Client.isAuthenticated as jasmine.Spy).and.resolveTo(true)
),
mergeMap(() => localService.handleRedirectCallback())
)
.subscribe();
});
});

describe('buildAuthorizeUrl', () => {
it('should call the underlying SDK', (done) => {
const options: RedirectLoginOptions = {};

service.buildAuthorizeUrl(options).subscribe((url) => {
expect(url).toBeTruthy();
expect(auth0Client.buildAuthorizeUrl).toHaveBeenCalledWith(options);
done();
});
});
});

describe('buildLogoutUrl', () => {
it('should call the underlying SDK', (done) => {
const options: LogoutUrlOptions = {};

service.buildLogoutUrl(options).subscribe((url) => {
expect(url).toBeTruthy();
expect(auth0Client.buildLogoutUrl).toHaveBeenCalledWith(options);
done();
});
});
});
});
67 changes: 58 additions & 9 deletions projects/auth0-angular/src/lib/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
GetTokenSilentlyOptions,
GetTokenWithPopupOptions,
RedirectLoginResult,
LogoutUrlOptions,
} from '@auth0/auth0-spa-js';

import {
Expand All @@ -35,6 +36,7 @@ import {
switchMap,
mergeMap,
scan,
withLatestFrom,
} from 'rxjs/operators';

import { Auth0ClientService } from './auth.client';
Expand Down Expand Up @@ -310,6 +312,62 @@ export class AuthService implements OnDestroy {
);
}

/**
* ```js
* handleRedirectCallback(url).subscribe(result => ...)
* ```
*
* After the browser redirects back to the callback page,
* call `handleRedirectCallback` to handle success and error
* responses from Auth0. If the response is successful, results
* will be valid according to their expiration times.
*
* Calling this method also refreshes the authentication and user states.
*
* @param url The URL to that should be used to retrieve the `state` and `code` values. Defaults to `window.location.href` if not given.
*/
handleRedirectCallback(url?: string): Observable<RedirectLoginResult> {
return defer(() => this.auth0Client.handleRedirectCallback(url)).pipe(
withLatestFrom(this.isLoadingSubject$),
tap(([result, isLoading]) => {
if (!isLoading) {
this.refreshState$.next();
}
const target = result?.appState?.target ?? '/';
this.navigator.navigateByUrl(target);
}),
map(([result]) => result)
);
}

/**
* ```js
* buildAuthorizeUrl().subscribe(url => ...)
* ```
*
* Builds an `/authorize` URL for loginWithRedirect using the parameters
* provided as arguments. Random and secure `state` and `nonce`
* parameters will be auto-generated.
* @param options The options
* @returns A URL to the authorize endpoint
*/
buildAuthorizeUrl(options?: RedirectLoginOptions): Observable<string> {
return defer(() => this.auth0Client.buildAuthorizeUrl(options));
}

/**
* ```js
* buildLogoutUrl().subscribe(url => ...)
* ```
* Builds a URL to the logout endpoint.
*
* @param options The options used to configure the parameters that appear in the logout endpoint URL.
* @returns a URL to the logout endpoint using the parameters provided as arguments.
*/
buildLogoutUrl(options?: LogoutUrlOptions): Observable<string> {
return of(this.auth0Client.buildLogoutUrl(options));
}

private shouldHandleCallback(): Observable<boolean> {
return of(this.location.path()).pipe(
map((search) => {
Expand All @@ -321,13 +379,4 @@ export class AuthService implements OnDestroy {
})
);
}

private handleRedirectCallback(): Observable<RedirectLoginResult> {
return defer(() => this.auth0Client.handleRedirectCallback()).pipe(
tap((result) => {
const target = result?.appState?.target ?? '/';
this.navigator.navigateByUrl(target);
})
);
}
}

0 comments on commit 2117a23

Please sign in to comment.