diff --git a/packages/okta-angular/CHANGELOG.md b/packages/okta-angular/CHANGELOG.md index b6c6d8df..4277c3e7 100644 --- a/packages/okta-angular/CHANGELOG.md +++ b/packages/okta-angular/CHANGELOG.md @@ -1,3 +1,29 @@ +# 2.0.0 + +[#690](https://github.com/okta/okta-oidc-js/pull/690) + +### Features + +- `OktaCallbackComponent` will catch and display exceptions thrown from `handleAuthentication()` +- `onAuthRequired` callbacks will now receive the Angular injector as the 2nd parameter. This change allows logic using any services available within your application. + +### Bug Fixes + +- Saved URI is now stored in `sessionStorage` instead of `localStorage`. This fixes an issue which can occur when multiple instances of the app are loading at the same time. +- `OktaCallbackComponent` uses `window.location.replace()` to complete the login flow after `handleAuthentication` completes. This fixes an issue where the user could navigate back to the callback hander. + +### Breaking Changes + +- Signature for `onAuthRequired` callback functions has changed. Callbacks will receive the `OktaAuthService` as the first argument, and the Angular `Injector` as the second argument. +- Static initializer `OktaAuthModule.initAuth()` has been removed. `OKTA_CONFIG` should be provided directly by your module. +- `getFromUri` now returns an absolute URI as a string +- `setFromUri` takes a string. If it is a relative path, it will be converted to an absolute URI before being saved. +- Legacy config formats are no longer supported. See [Configuration Reference](https://github.com/okta/okta-auth-js#configuration-reference) for supported values. + +### Other + +- Upgrades `@okta/okta-auth-js` to version 3.0.0 + # 1.4.0 ### Features diff --git a/packages/okta-angular/README.md b/packages/okta-angular/README.md index 465d7212..01302e93 100644 --- a/packages/okta-angular/README.md +++ b/packages/okta-angular/README.md @@ -158,7 +158,7 @@ If a user does not have a valid session, they will be redirected to the Okta Log ### `OktaCallbackComponent` -Handles the callback after the redirect. By default, it parses the tokens from the uri, stores them, then redirects to `/`. If a protected route (using [`OktaAuthGuard`](#oktaauthguard)) caused the redirect, then the callback redirects to the protected route. For more advanced cases, this component can be copied to your own source tree and modified as needed. +Handles the callback after the redirect. By default, it parses the tokens from the uri, stores them, then redirects to `/`. If a protected route (using [`OktaAuthGuard`](#oktaauthguard)) caused the redirect, then the callback will redirect back to the protected route. If an error is thrown while processing tokens, the component will display the error and not perform any redirect. This logic can be customized by copying the component to your own source tree and modified as needed. For example, you may want to capture or display errors differently or provide a helpful link for your users in case they encounter an error on the callback route. The most common error is the user does not have permission to access the application. In this case, they may be able to contact an administrator to obtain access. You should define a route to handle the callback URL (`/implicit/callback` by default). Also add `OktaCallbackComponent` to the declarations section of in your `NgModule`. @@ -220,7 +220,10 @@ import { ... } from '@okta/okta-angular'; -export function onAuthRequired(oktaAuth, router) { +export function onAuthRequired(oktaAuth, injector) { + // Use injector to access any service available within your application + const router = injector.get(Router); + // Redirect the user to your custom login page router.navigate(['/custom-login']); } @@ -334,13 +337,13 @@ Parses the tokens returned as hash fragments in the OAuth 2.0 Redirect URI, then Terminates the user's session in Okta and clears all stored tokens. Accepts an optional `uri` parameter to push the user to after logout. -#### `oktaAuth.setFromUri(uri, queryParams)` +#### `oktaAuth.setFromUri(uri)` -Used to capture the current URL state before a redirect occurs. Used primarily for custom [`canActivate`](https://angular.io/api/router/CanActivate) navigation guards. +Used to capture the current URL state before a redirect occurs. Used by custom [`canActivate`](https://angular.io/api/router/CanActivate) navigation guards. #### `oktaAuth.getFromUri()` -Returns the stored URI and query parameters stored when the `OktaAuthGuard` and/or `setFromUri` was used. +Returns the URI stored when the `OktaAuthGuard` and/or `setFromUri` was used. #### `oktaAuth.getTokenManager()` diff --git a/packages/okta-angular/package.json b/packages/okta-angular/package.json index 1f56d5d8..cbafefcf 100644 --- a/packages/okta-angular/package.json +++ b/packages/okta-angular/package.json @@ -1,7 +1,7 @@ { "name": "@okta/okta-angular", "private": true, - "version": "1.4.0", + "version": "2.0.0", "description": "Angular support for Okta", "main": "./bundles/okta-angular.umd.js", "module": "./fesm5/okta-angular.js", @@ -40,7 +40,7 @@ "license": "Apache-2.0", "dependencies": { "@okta/configuration-validation": "^0.4.1", - "@okta/okta-auth-js": "^2.11.2", + "@okta/okta-auth-js": "^3.0.0", "tslib": "^1.9.0" }, "devDependencies": { diff --git a/packages/okta-angular/src/@types/okta__okta-auth-js/index.d.ts b/packages/okta-angular/src/@types/okta__okta-auth-js/index.d.ts index 61cba1d8..3afb49de 100644 --- a/packages/okta-angular/src/@types/okta__okta-auth-js/index.d.ts +++ b/packages/okta-angular/src/@types/okta__okta-auth-js/index.d.ts @@ -1,9 +1,17 @@ declare module '@okta/okta-auth-js'; +declare interface TokenHash { + [key: string] : Token; +} +declare interface ParseFromUrlResponse { + tokens: TokenHash; + state: string; +} + declare interface TokenAPI { - getUserInfo(accessToken: Token): Promise; - getWithRedirect(params: object): Promise; - parseFromUrl(): Token[] + getUserInfo(accessToken?: AccessToken, idToken?: IDToken): Promise; + getWithRedirect(params?: object): Promise; + parseFromUrl(): ParseFromUrlResponse; } declare class OktaAuth { diff --git a/packages/okta-angular/src/okta-angular.ts b/packages/okta-angular/src/okta-angular.ts index 8b7970de..93070264 100644 --- a/packages/okta-angular/src/okta-angular.ts +++ b/packages/okta-angular/src/okta-angular.ts @@ -13,7 +13,7 @@ export { OktaAuthModule } from './okta/okta.module'; export { OktaAuthGuard } from './okta/okta.guard'; export { OktaAuthService } from './okta/services/okta.service'; -export { OKTA_CONFIG } from './okta/models/okta.config'; +export { OktaConfig, OKTA_CONFIG } from './okta/models/okta.config'; export { UserClaims } from './okta/models/user-claims'; // Okta View Components diff --git a/packages/okta-angular/src/okta/components/callback.component.ts b/packages/okta-angular/src/okta/components/callback.component.ts index abba953c..1d650627 100644 --- a/packages/okta-angular/src/okta/components/callback.component.ts +++ b/packages/okta-angular/src/okta/components/callback.component.ts @@ -10,16 +10,32 @@ * See the License for the specific language governing permissions and limitations under the License. */ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { OktaAuthService } from '../services/okta.service'; -@Component({template: `` }) -export class OktaCallbackComponent { - constructor(private okta: OktaAuthService) { +@Component({ + template: `
{{error}}
` +}) +export class OktaCallbackComponent implements OnInit { + error: string; + + constructor(private okta: OktaAuthService) {} + + async ngOnInit() { /** * Handles the response from Okta and parses tokens. */ - okta.handleAuthentication(); + return this.okta.handleAuthentication() + .then(() => { + /** + * Navigate back to the saved uri, or root of application. + */ + const fromUri = this.okta.getFromUri(); + window.location.replace(fromUri); + }) + .catch(e => { + this.error = e.toString(); + }); } } diff --git a/packages/okta-angular/src/okta/components/login-redirect.component.ts b/packages/okta-angular/src/okta/components/login-redirect.component.ts index 9897bc64..c5641050 100644 --- a/packages/okta-angular/src/okta/components/login-redirect.component.ts +++ b/packages/okta-angular/src/okta/components/login-redirect.component.ts @@ -10,13 +10,13 @@ * See the License for the specific language governing permissions and limitations under the License. */ -import { Component } from '@angular/core'; - +import { Component, OnInit } from '@angular/core'; import { OktaAuthService } from '../services/okta.service'; @Component({ template: `` }) -export class OktaLoginRedirectComponent { - constructor(private okta: OktaAuthService) { - okta.loginRedirect(); +export class OktaLoginRedirectComponent implements OnInit { + constructor(private okta: OktaAuthService) {} + async ngOnInit() { + return this.okta.loginRedirect(); } } diff --git a/packages/okta-angular/src/okta/createService.ts b/packages/okta-angular/src/okta/createService.ts index 46c1fbf1..816dc7e4 100644 --- a/packages/okta-angular/src/okta/createService.ts +++ b/packages/okta-angular/src/okta/createService.ts @@ -1,7 +1,7 @@ +import { Injector } from '@angular/core'; import { OktaConfig } from './models/okta.config'; -import { Router } from '@angular/router'; import { OktaAuthService } from './services/okta.service'; -export function createOktaService(config: OktaConfig, router: Router) { - return new OktaAuthService(config, router); +export function createOktaService(config: OktaConfig, injector: Injector) { + return new OktaAuthService(config, injector); } diff --git a/packages/okta-angular/src/okta/models/okta.config.ts b/packages/okta-angular/src/okta/models/okta.config.ts index fb0b9b53..d6f67d05 100644 --- a/packages/okta-angular/src/okta/models/okta.config.ts +++ b/packages/okta-angular/src/okta/models/okta.config.ts @@ -10,13 +10,10 @@ * See the License for the specific language governing permissions and limitations under the License. */ -import { InjectionToken } from '@angular/core'; - -import { Router } from '@angular/router'; - +import { InjectionToken, Injector } from '@angular/core'; import { OktaAuthService } from '../services/okta.service'; -export type AuthRequiredFunction = (oktaAuth: OktaAuthService, router: Router) => void; +export type AuthRequiredFunction = (oktaAuth: OktaAuthService, injector: Injector) => void; export type IsAuthenticatedFunction = () => Promise; export type OnSessionExpiredFunction = () => void; diff --git a/packages/okta-angular/src/okta/models/token-manager.ts b/packages/okta-angular/src/okta/models/token-manager.ts index b7fd432c..ab8c447d 100644 --- a/packages/okta-angular/src/okta/models/token-manager.ts +++ b/packages/okta-angular/src/okta/models/token-manager.ts @@ -9,9 +9,11 @@ export interface IDToken { claims: UserClaims; } +export type Token = AccessToken | IDToken; + export interface TokenManager { - get(key: string): AccessToken | IDToken; - add(key: string, token: AccessToken | IDToken): void; + get(key: string): Token; + add(key: string, token: Token): void; on(event: string, handler: Function): void; off(event: string, handler: Function): void; } diff --git a/packages/okta-angular/src/okta/okta.guard.ts b/packages/okta-angular/src/okta/okta.guard.ts index fafdee4a..a4dd901e 100644 --- a/packages/okta-angular/src/okta/okta.guard.ts +++ b/packages/okta-angular/src/okta/okta.guard.ts @@ -10,12 +10,11 @@ * See the License for the specific language governing permissions and limitations under the License. */ -import { Injectable } from '@angular/core'; +import { Injectable, Injector } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, - Router } from '@angular/router'; import { OktaAuthService } from './services/okta.service'; @@ -23,7 +22,7 @@ import { AuthRequiredFunction } from './models/okta.config'; @Injectable() export class OktaAuthGuard implements CanActivate { - constructor(private oktaAuth: OktaAuthService, private router: Router) { } + constructor(private oktaAuth: OktaAuthService, private injector: Injector) { } /** * Gateway for protected route. Returns true if there is a valid accessToken, @@ -45,11 +44,10 @@ export class OktaAuthGuard implements CanActivate { /** * Store the current path */ - const path = state.url.split(/[?#]/)[0]; - this.oktaAuth.setFromUri(path, route.queryParams); + this.oktaAuth.setFromUri(state.url); if (onAuthRequired) { - onAuthRequired(this.oktaAuth, this.router); + onAuthRequired(this.oktaAuth, this.injector); } else { this.oktaAuth.loginRedirect(); } diff --git a/packages/okta-angular/src/okta/okta.module.ts b/packages/okta-angular/src/okta/okta.module.ts index 25a92d1d..4019048e 100644 --- a/packages/okta-angular/src/okta/okta.module.ts +++ b/packages/okta-angular/src/okta/okta.module.ts @@ -10,24 +10,22 @@ * See the License for the specific language governing permissions and limitations under the License. */ -import { ModuleWithProviders, NgModule } from '@angular/core'; - +import { NgModule, Injector } from '@angular/core'; import { OktaCallbackComponent } from './components/callback.component'; import { OktaLoginRedirectComponent } from './components/login-redirect.component'; import { OktaAuthService } from './services/okta.service'; import { OktaAuthGuard } from './okta.guard'; -import { OktaConfig, OKTA_CONFIG } from './models/okta.config'; +import { OKTA_CONFIG } from './models/okta.config'; import { createOktaService } from './createService'; -import { Router } from '@angular/router'; @NgModule({ declarations: [ OktaCallbackComponent, - OktaLoginRedirectComponent + OktaLoginRedirectComponent, ], exports: [ OktaCallbackComponent, - OktaLoginRedirectComponent + OktaLoginRedirectComponent, ], providers: [ OktaAuthGuard, @@ -36,20 +34,11 @@ import { Router } from '@angular/router'; useFactory: createOktaService, deps: [ OKTA_CONFIG, - Router + Injector ] } ] }) export class OktaAuthModule { - // Deprecated. Your app should provide OKTA_CONFIG directly - static initAuth(config: OktaConfig): ModuleWithProviders { - return { - ngModule: OktaAuthModule, - providers: [ - // Will NOT provide config when using AOT compiler. Your app module should provide this value statically in its providers section. - { provide: OKTA_CONFIG, useValue: config } - ] - }; - } + } diff --git a/packages/okta-angular/src/okta/services/okta.service.ts b/packages/okta-angular/src/okta/services/okta.service.ts index 9c28a65c..22d323a5 100644 --- a/packages/okta-angular/src/okta/services/okta.service.ts +++ b/packages/okta-angular/src/okta/services/okta.service.ts @@ -10,18 +10,16 @@ * See the License for the specific language governing permissions and limitations under the License. */ -import { Inject, Injectable } from '@angular/core'; -import { Router, NavigationExtras } from '@angular/router'; +import { Inject, Injectable, Injector } from '@angular/core'; import { assertIssuer, assertClientId, assertRedirectUri, - buildConfigObject } from '@okta/configuration-validation'; import { OKTA_CONFIG, OktaConfig, AuthRequiredFunction } from '../models/okta.config'; import { UserClaims } from '../models/user-claims'; -import { TokenManager } from '../models/token-manager'; +import { TokenManager, AccessToken, IDToken } from '../models/token-manager'; import packageInfo from '../packageInfo'; @@ -38,14 +36,14 @@ export class OktaAuthService { private observers: Observer[]; $authenticationState: Observable; - constructor(@Inject(OKTA_CONFIG) private auth: OktaConfig, private router: Router) { + constructor(@Inject(OKTA_CONFIG) config: OktaConfig, private injector: Injector) { this.observers = []; /** * Cache the auth config. */ - this.config = buildConfigObject(auth); // use normalized config object - this.config.scopes = this.config.scopes || []; + this.config = Object.assign({}, config); + this.config.scopes = this.config.scopes || ['openid', 'email']; // Automatically enter login flow if session has expired or was ended outside the application // The default behavior can be overriden by setting your own `onSessionExpired` function on the OktaConfig @@ -70,10 +68,10 @@ export class OktaAuthService { } login(fromUri?: string, additionalParams?: object) { - this.setFromUri(fromUri || this.router.url); - const onAuthRequired: AuthRequiredFunction | undefined = this.getOktaConfig().onAuthRequired; + this.setFromUri(fromUri); + const onAuthRequired: AuthRequiredFunction | undefined = this.config.onAuthRequired; if (onAuthRequired) { - return onAuthRequired(this, this.router); + return onAuthRequired(this, this.injector); } return this.loginRedirect(undefined, additionalParams); } @@ -83,7 +81,8 @@ export class OktaAuthService { } /** - * Checks if there is an access token and id token + * Checks if there is an access token OR an id token + * A custom method may be provided on config to override this logic */ async isAuthenticated(): Promise { // Support a user-provided method to check authentication @@ -105,7 +104,7 @@ export class OktaAuthService { */ async getAccessToken(): Promise { try { - const accessToken = await this.oktaAuth.tokenManager.get('accessToken'); + const accessToken: AccessToken = await this.oktaAuth.tokenManager.get('accessToken') as AccessToken; return accessToken.accessToken; } catch (err) { // The user no longer has an existing SSO session in the browser. @@ -120,7 +119,7 @@ export class OktaAuthService { */ async getIdToken(): Promise { try { - const idToken = await this.oktaAuth.tokenManager.get('idToken'); + const idToken: IDToken = await this.oktaAuth.tokenManager.get('idToken') as IDToken; return idToken.idToken; } catch (err) { // The user no longer has an existing SSO session in the browser. @@ -135,17 +134,13 @@ export class OktaAuthService { * accessToken is provided or parses the available idToken. */ async getUser(): Promise { - const accessToken = await this.oktaAuth.tokenManager.get('accessToken'); - const idToken = await this.oktaAuth.tokenManager.get('idToken'); - if (accessToken && idToken) { - const userinfo = await this.oktaAuth.token.getUserInfo(accessToken); - if (userinfo.sub === idToken.claims.sub) { - // Only return the userinfo response if subjects match to - // mitigate token substitution attacks - return userinfo; - } + const accessToken: AccessToken = await this.oktaAuth.tokenManager.get('accessToken') as AccessToken; + const idToken: IDToken = await this.oktaAuth.tokenManager.get('idToken') as IDToken; + if (!accessToken || !idToken) { + // Returns raw claims from idToken if there is no accessToken. + return idToken ? idToken.claims : undefined; } - return idToken ? idToken.claims : undefined; + return this.oktaAuth.token.getUserInfo(); } /** @@ -165,12 +160,10 @@ export class OktaAuthService { this.setFromUri(fromUri); } - // Normalize params, set defaults - const params = buildConfigObject(additionalParams); - params.scopes = params.scopes || this.config.scopes; - params.responseType = params.responseType - || this.config.responseType - || ['id_token', 'token']; + const params = Object.assign({ + scopes: this.config.scopes, + responseType: this.config.responseType + }, additionalParams); return this.oktaAuth.token.getWithRedirect(params); // can throw } @@ -180,60 +173,40 @@ export class OktaAuthService { * @param uri * @param queryParams */ - setFromUri(uri: string, queryParams?: object) { - const json = JSON.stringify({ - uri: uri, - params: queryParams - }); - localStorage.setItem('referrerPath', json); + setFromUri(fromUri?: string) { + // Use current location if fromUri was not passed + fromUri = fromUri || window.location.href; + // If a relative path was passed, convert to absolute URI + if (fromUri.charAt(0) === '/') { + fromUri = window.location.origin + fromUri; + } + sessionStorage.setItem('referrerPath', fromUri); } /** * Returns the referrer path from localStorage or app root. */ - getFromUri(): { uri: string, extras: NavigationExtras } { - const referrerPath = localStorage.getItem('referrerPath'); - localStorage.removeItem('referrerPath'); - - let path; - if (referrerPath) { - path = JSON.parse(referrerPath); - } - if (!path) { - path = { uri: '/', params: {} }; - } - - const navigationExtras: NavigationExtras = { - queryParams: path.params - }; - - return { - uri: path.uri, - extras: navigationExtras - }; + getFromUri(): string { + const fromUri = sessionStorage.getItem('referrerPath') || window.location.origin; + sessionStorage.removeItem('referrerPath'); + return fromUri; } /** * Parses the tokens from the callback URL. */ async handleAuthentication(): Promise { - const tokens = await this.oktaAuth.token.parseFromUrl(); - tokens.forEach(token => { - if (token.idToken) { - this.oktaAuth.tokenManager.add('idToken', token); - } - if (token.accessToken) { - this.oktaAuth.tokenManager.add('accessToken', token); - } - }); + const res = await this.oktaAuth.token.parseFromUrl(); + const tokens = res.tokens; + if (tokens.accessToken) { + this.oktaAuth.tokenManager.add('accessToken', tokens.accessToken as AccessToken); + } + if (tokens.idToken) { + this.oktaAuth.tokenManager.add('idToken', tokens.idToken as IDToken); + } if (await this.isAuthenticated()) { this.emitAuthenticationState(true); } - /** - * Navigate back to the initial view or root of application. - */ - const fromUri = this.getFromUri(); - this.router.navigate([fromUri.uri], fromUri.extras); } /** @@ -242,17 +215,20 @@ export class OktaAuthService { * @param options */ async logout(options?: any): Promise { - let uri = null; + let redirectUri = null; options = options || {}; if (typeof options === 'string') { - uri = options; - options = {}; + redirectUri = options; + // If a relative path was passed, convert to absolute URI + if (redirectUri.charAt(0) === '/') { + redirectUri = window.location.origin + redirectUri; + } + options = { + postLogoutRedirectUri: redirectUri + }; } await this.oktaAuth.signOut(options); this.emitAuthenticationState(false); - if (!options.postLogoutRedirectUri && !this.config.postLogoutRedirectUri) { - this.router.navigate([uri || '/']); - } } /** diff --git a/packages/okta-angular/test/e2e/harness/src/app/app.module.ts b/packages/okta-angular/test/e2e/harness/src/app/app.module.ts index 6f09ecc0..537d2cf4 100644 --- a/packages/okta-angular/test/e2e/harness/src/app/app.module.ts +++ b/packages/okta-angular/test/e2e/harness/src/app/app.module.ts @@ -11,7 +11,7 @@ */ import { BrowserModule } from '@angular/platform-browser'; -import { NgModule } from '@angular/core'; +import { NgModule, Injector } from '@angular/core'; import { Routes, RouterModule, Router } from '@angular/router'; import { environment } from './../environments/environment'; @@ -24,7 +24,8 @@ import { OktaAuthModule, OktaAuthService, OktaCallbackComponent, - OktaLoginRedirectComponent + OktaLoginRedirectComponent, + OKTA_CONFIG } from '@okta/okta-angular'; /** @@ -34,11 +35,13 @@ import { ProtectedComponent } from './protected.component'; import { AppComponent } from './app.component'; import { SessionTokenLoginComponent } from './sessionToken-login.component'; -export function onNeedsAuthenticationGuard(oktaAuth: OktaAuthService, router: Router) { +export function onNeedsAuthenticationGuard(oktaAuth: OktaAuthService, injector: Injector) { + const router = injector.get(Router); router.navigate(['/sessionToken-login']); } -export function onNeedsGlobalAuthenticationGuard(oktaAuth: OktaAuthService, router: Router) { +export function onNeedsGlobalAuthenticationGuard(oktaAuth: OktaAuthService, injector: Injector) { + const router = injector.get(Router); router.navigate(['/login']); } @@ -93,8 +96,6 @@ const config = { pkce, redirectUri, clientId: environment.CLIENT_ID, - scope: 'email', - responseType: 'id_token token', onAuthRequired: onNeedsGlobalAuthenticationGuard, testing: { disableHttpsCheck: false @@ -111,13 +112,18 @@ if (environment.OKTA_TESTING_DISABLEHTTPSCHECK) { imports: [ BrowserModule, RouterModule.forRoot(appRoutes), - OktaAuthModule.initAuth(config) + OktaAuthModule ], declarations: [ AppComponent, ProtectedComponent, SessionTokenLoginComponent ], + providers: [{ + provide: OKTA_CONFIG, + useValue: config + }], bootstrap: [ AppComponent ] }) + export class AppModule { } diff --git a/packages/okta-angular/test/e2e/harness/src/app/sessionToken-login.component.ts b/packages/okta-angular/test/e2e/harness/src/app/sessionToken-login.component.ts index 6ee42a91..200fc285 100644 --- a/packages/okta-angular/test/e2e/harness/src/app/sessionToken-login.component.ts +++ b/packages/okta-angular/test/e2e/harness/src/app/sessionToken-login.component.ts @@ -40,7 +40,7 @@ export class SessionTokenLoginComponent { constructor(private okta: OktaAuthService) { const baseUrl = environment.ISSUER.split('/oauth2')[0]; this.oktaAuth = new OktaAuth({ - url: baseUrl + issuer: baseUrl }); } @@ -49,9 +49,11 @@ export class SessionTokenLoginComponent { username: username, password: password }) - .then(res => this.okta.loginRedirect('/', { - sessionToken: res.sessionToken - })) + .then(res => { + return this.okta.loginRedirect('/', { + sessionToken: res.sessionToken + }); + }) .catch(err => console.log('Found an error', err)); } } diff --git a/packages/okta-angular/test/spec/callback.component.test.ts b/packages/okta-angular/test/spec/callback.component.test.ts new file mode 100644 index 00000000..30bf74d2 --- /dev/null +++ b/packages/okta-angular/test/spec/callback.component.test.ts @@ -0,0 +1,92 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; + +import { + OktaAuthService, + OKTA_CONFIG, + OktaCallbackComponent +} from '../../src/okta-angular'; + +describe('OktaCallbackComponent', () => { + let component: OktaCallbackComponent; + let fixture: ComponentFixture; + let service: OktaAuthService; + let originalLocation: any; + beforeEach(() => { + originalLocation = window.location; + delete window.location; + (window.location as any) = { + replace: jest.fn() + }; + const config = { + clientId: 'foo', + issuer: 'https://foo', + redirectUri: 'https://foo' + }; + + TestBed.configureTestingModule({ + declarations: [ + OktaCallbackComponent + ], + providers: [ + OktaAuthService, + { + provide: OKTA_CONFIG, + useValue: config + }, + ], + }); + fixture = TestBed.createComponent(OktaCallbackComponent); + component = fixture.componentInstance; + service = TestBed.get(OktaAuthService); + }); + afterEach(() => { + window.location = originalLocation; + }); + it('should create the component', async(() => { + expect(component).toBeTruthy(); + })); + + it('should call handleAuthentication', async(() => { + jest.spyOn(service, 'handleAuthentication').mockReturnValue(Promise.resolve()); + fixture.detectChanges(); + expect(service.handleAuthentication).toHaveBeenCalled(); + })); + + it('should call location.replace with the saved uri', async(() => { + const uri = 'http://fakey.local'; + const fakePromise: unknown = { + then: function(cb: Function) { + cb(); + return { + catch: jest.fn() + }; + } + }; + jest.spyOn(service, 'handleAuthentication').mockReturnValue(fakePromise as Promise); + jest.spyOn(service, 'getFromUri').mockReturnValue(uri); + fixture.detectChanges(); + expect(service.handleAuthentication).toHaveBeenCalled(); + expect(service.getFromUri).toHaveBeenCalled(); + expect(window.location.replace).toHaveBeenCalledWith(uri); + })); + + it('catches errors from handleAuthentication', async(() => { + const error = new Error('test error'); + const fakePromise: unknown = { + then: function() { + return { + catch: function(cb: Function) { + cb(error); + } + }; + } + }; + jest.spyOn(service, 'handleAuthentication').mockReturnValue(fakePromise as Promise); + jest.spyOn(service, 'getFromUri').mockReturnValue(''); + fixture.detectChanges(); + expect(service.handleAuthentication).toHaveBeenCalled(); + expect(service.getFromUri).not.toHaveBeenCalled(); + expect(window.location.replace).not.toHaveBeenCalled(); + expect(component.error).toBe('Error: test error'); + })); +}); diff --git a/packages/okta-angular/test/spec/guard.test.ts b/packages/okta-angular/test/spec/guard.test.ts index d4b6f371..6981d907 100644 --- a/packages/okta-angular/test/spec/guard.test.ts +++ b/packages/okta-angular/test/spec/guard.test.ts @@ -8,8 +8,10 @@ import { OktaAuthModule, OktaAuthService, OktaAuthGuard, + OKTA_CONFIG, } from '../../src/okta-angular'; import { ActivatedRouteSnapshot, RouterStateSnapshot, Router, RouterState } from '@angular/router'; +import { Injector } from '@angular/core'; const VALID_CONFIG = { clientId: 'foo', @@ -27,9 +29,15 @@ function createService(options: any) { TestBed.configureTestingModule({ imports: [ RouterTestingModule.withRoutes([{ path: 'foo', redirectTo: '/foo' }]), - OktaAuthModule.initAuth(VALID_CONFIG) + OktaAuthModule + ], + providers: [ + OktaAuthService, + { + provide: OKTA_CONFIG, + useValue: VALID_CONFIG + }, ], - providers: [OktaAuthService], }); const service = TestBed.get(OktaAuthService); service.getTokenManager = jest.fn().mockReturnValue({ on: jest.fn() }); @@ -52,8 +60,8 @@ describe('Angular auth guard', () => { describe('isAuthenticated() = true', () => { it('returns true', async () => { const service = createService({ isAuthenticated: true }); - const router: unknown = undefined; - const guard = new OktaAuthGuard(service, router as Router); + const injector: unknown = undefined; + const guard = new OktaAuthGuard(service, injector as Injector); const route: unknown = undefined; const state: unknown = undefined; const res = await guard.canActivate(route as ActivatedRouteSnapshot, state as RouterStateSnapshot); @@ -67,10 +75,12 @@ describe('Angular auth guard', () => { let state: RouterStateSnapshot; let route: ActivatedRouteSnapshot; let router: Router; + let injector: Injector; beforeEach(() => { service = createService({ isAuthenticated: false }); router = TestBed.get(Router); - guard = new OktaAuthGuard(service, router); + injector = TestBed.get(Injector); + guard = new OktaAuthGuard(service, injector); const routerState: RouterState = router.routerState; state = routerState.snapshot; route = state.root; @@ -88,7 +98,7 @@ describe('Angular auth guard', () => { expect(service.loginRedirect).toHaveBeenCalled(); }); - it('calls "setFromUri" with baseUrl and query object', async () => { + it('calls "setFromUri" with state url', async () => { const baseUrl = 'http://fake.url/path'; const query = '?query=foo&bar=baz'; const hash = '#hash=foo'; @@ -96,13 +106,13 @@ describe('Angular auth guard', () => { const queryObj = { 'bar': 'baz' }; route.queryParams = queryObj; const res = await guard.canActivate(route, state); - expect(service.setFromUri).toHaveBeenCalledWith(baseUrl, queryObj); + expect(service.setFromUri).toHaveBeenCalledWith(state.url); }); it('onAuthRequired can be set on route', async () => { const fn = route.data['onAuthRequired'] = jest.fn(); const res = await guard.canActivate(route, state); - expect(fn).toHaveBeenCalledWith(service, router as Router); + expect(fn).toHaveBeenCalledWith(service, injector); }); it('onAuthRequired can be set on config', async () => { @@ -110,7 +120,7 @@ describe('Angular auth guard', () => { const fn = config.onAuthRequired = jest.fn(); const res = await guard.canActivate(route, state); - expect(fn).toHaveBeenCalledWith(service, router as Router); + expect(fn).toHaveBeenCalledWith(service, injector); }); }); }); @@ -119,13 +129,20 @@ describe('Angular auth guard', () => { TestBed.configureTestingModule({ imports: [ RouterTestingModule.withRoutes([{ path: 'foo', redirectTo: '/foo' }]), - OktaAuthModule.initAuth(VALID_CONFIG) + OktaAuthModule + ], + providers: [ + OktaAuthService, + OktaAuthGuard, + { + provide: OKTA_CONFIG, + useValue: VALID_CONFIG + }, ], - providers: [OktaAuthService, OktaAuthGuard], }); const guard = TestBed.get(OktaAuthGuard); expect(guard.oktaAuth).toBeTruthy(); - expect(guard.router).toBeTruthy(); + expect(guard.injector).toBeTruthy(); expect(typeof guard.canActivate).toBe('function'); }); }); diff --git a/packages/okta-angular/test/spec/login-redirect.component.test.ts b/packages/okta-angular/test/spec/login-redirect.component.test.ts new file mode 100644 index 00000000..780e61f7 --- /dev/null +++ b/packages/okta-angular/test/spec/login-redirect.component.test.ts @@ -0,0 +1,47 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; + +import { + OktaAuthService, + OKTA_CONFIG, + OktaLoginRedirectComponent +} from '../../src/okta-angular'; + +describe('OktaLoginRedirectComponent', () => { + let component: OktaLoginRedirectComponent; + let fixture: ComponentFixture; + let service: OktaAuthService; + + beforeEach(() => { + const config = { + clientId: 'foo', + issuer: 'https://foo', + redirectUri: 'https://foo' + }; + + TestBed.configureTestingModule({ + declarations: [ + OktaLoginRedirectComponent + ], + providers: [ + OktaAuthService, + { + provide: OKTA_CONFIG, + useValue: config + }, + ], + }); + fixture = TestBed.createComponent(OktaLoginRedirectComponent); + component = fixture.componentInstance; + service = TestBed.get(OktaAuthService); + }); + + it('should create the component', async(() => { + expect(component).toBeTruthy(); + })); + + it('should call loginRedirect', async(() => { + jest.spyOn(service, 'loginRedirect').mockReturnValue(undefined); + fixture.detectChanges(); + expect(service.loginRedirect).toHaveBeenCalled(); + })); +}); diff --git a/packages/okta-angular/test/spec/service.test.ts b/packages/okta-angular/test/spec/service.test.ts index f379e32c..b12e1d6b 100644 --- a/packages/okta-angular/test/spec/service.test.ts +++ b/packages/okta-angular/test/spec/service.test.ts @@ -1,9 +1,7 @@ jest.mock('@okta/okta-auth-js'); -import { Router } from '@angular/router'; import { TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import OktaAuth from '@okta/okta-auth-js'; import PACKAGE_JSON from '../../package.json'; @@ -11,19 +9,21 @@ import PACKAGE_JSON from '../../package.json'; import { OktaAuthModule, OktaAuthService, - OKTA_CONFIG + OktaConfig, + OKTA_CONFIG, } from '../../src/okta-angular'; - -const VALID_CONFIG = { - clientId: 'foo', - issuer: 'https://foo', - redirectUri: 'https://foo' -}; +import { Injector } from '@angular/core'; describe('Angular service', () => { let _mockAuthJS: any; + let VALID_CONFIG: OktaConfig; beforeEach(() => { + VALID_CONFIG = { + clientId: 'foo', + issuer: 'https://foo', + redirectUri: 'https://foo' + }; OktaAuth.mockClear(); _mockAuthJS = { tokenManager: { @@ -50,12 +50,13 @@ describe('Angular service', () => { return Object.assign({}, VALID_CONFIG, config); } + const createInstance = (params = {}) => { + OktaAuth.mockImplementation(() => _mockAuthJS); + const injector: unknown = undefined; + return () => new OktaAuthService(params, injector as Injector); + }; + describe('configuration', () => { - const createInstance = (params = {}) => { - OktaAuth.mockImplementation(() => _mockAuthJS); - const router: unknown = undefined; - return () => new OktaAuthService(params, router as Router); - }; it('should throw if no issuer is provided', () => { expect(createInstance()).toThrow(); }); @@ -110,37 +111,56 @@ describe('Angular service', () => { ).not.toThrow(); }); + it('sets default scopes if none are provided', () => { + const service = createInstance(VALID_CONFIG)(); + expect(service.getOktaConfig()).toMatchInlineSnapshot(` + Object { + "clientId": "foo", + "issuer": "https://foo", + "onSessionExpired": [Function], + "redirectUri": "https://foo", + "scopes": Array [ + "openid", + "email", + ], + } + `); + }); it('will add "openid" scope if not present', () => { - const config = createInstance(VALID_CONFIG)().getOktaConfig(); + const config = createInstance(extendConfig({ scopes: ['foo'] }))().getOktaConfig(); expect(config.scopes).toMatchInlineSnapshot(` Array [ "openid", + "foo", ] `); }); }); it('Can set the https secure cookie setting', () => { - const router: unknown = undefined; - const service = new OktaAuthService(Object.assign({}, VALID_CONFIG, { tokenManager: { secure: true }}), router as Router); + const service = createInstance(extendConfig({ tokenManager: { secure: true }}))(); const tmConfig = service.getOktaConfig().tokenManager; expect(tmConfig).toBeDefined(); expect((tmConfig || {}).secure).toBe(true); }); it('Adds a user agent on internal oktaAuth instance', () => { - const router: unknown = undefined; - const service = new OktaAuthService(VALID_CONFIG, router as Router); + const service = createInstance(VALID_CONFIG)(); expect(service['oktaAuth'].userAgent.indexOf(`@okta/okta-angular/${PACKAGE_JSON.version}`)).toBeGreaterThan(-1); }); it('Can create the service via angular injection', () => { TestBed.configureTestingModule({ imports: [ - RouterTestingModule.withRoutes([{ path: 'foo', redirectTo: '/foo' }]), - OktaAuthModule.initAuth(VALID_CONFIG) + OktaAuthModule + ], + providers: [ + OktaAuthService, + { + provide: OKTA_CONFIG, + useValue: VALID_CONFIG + }, ], - providers: [OktaAuthService], }); const service = TestBed.get(OktaAuthService); expect(service.config).toMatchInlineSnapshot(` @@ -149,11 +169,10 @@ describe('Angular service', () => { "issuer": "https://foo", "onSessionExpired": [Function], "redirectUri": "https://foo", - "responseType": undefined, "scopes": Array [ "openid", + "email", ], - "tokenManager": undefined, } `); }); @@ -164,10 +183,15 @@ describe('Angular service', () => { config = extendConfig(config || {}); TestBed.configureTestingModule({ imports: [ - RouterTestingModule.withRoutes([{ path: 'foo', redirectTo: '/foo' }]), - OktaAuthModule.initAuth(config) + OktaAuthModule + ], + providers: [ + OktaAuthService, + { + provide: OKTA_CONFIG, + useValue: config + }, ], - providers: [OktaAuthService], }); return TestBed.get(OktaAuthService); } @@ -325,7 +349,7 @@ describe('Angular service', () => { }); - it('idtoken but no accessToken = returns idToken claims', async () => { + it('idtoken but no accessToken = idToken claims', async () => { const mockToken = { idToken: 'foo', claims: 'baz', @@ -346,22 +370,14 @@ describe('Angular service', () => { expect(retVal).toBe(mockToken.claims); }); - it('idtoken and accessToken = calls getUserInfo and returns user object', async () => { + it('accessToken but no idtoken = returns undefined', async () => { const mockToken = { - claims: { - sub: 'test-sub', - }, - }; - const userInfo = { - sub: 'test-sub', + accessToken: 'foo' }; const mockAuthJS = extendMockAuthJS({ - token: { - getUserInfo: jest.fn().mockReturnValue(Promise.resolve(userInfo)), - }, tokenManager: { get: jest.fn().mockImplementation(key => { - if (key === 'idToken' || key === 'accessToken') { + if (key === 'accessToken') { return Promise.resolve(mockToken); } return Promise.resolve(null); @@ -371,17 +387,17 @@ describe('Angular service', () => { const service = createService(undefined, mockAuthJS); const retVal = await service.getUser(); - expect(retVal).toBe(userInfo); + expect(retVal).toBe(undefined); }); - it('idtoken and accessToken but scope-mismatch calls getUserInfo and returns scopes', async () => { + it('idtoken and accessToken = calls getUserInfo and returns user object', async () => { const mockToken = { claims: { sub: 'test-sub', }, }; const userInfo = { - sub: 'test-sub-other', + sub: 'test-sub', }; const mockAuthJS = extendMockAuthJS({ token: { @@ -399,61 +415,63 @@ describe('Angular service', () => { const service = createService(undefined, mockAuthJS); const retVal = await service.getUser(); - expect(mockAuthJS.token.getUserInfo).toHaveBeenCalled(); - expect(retVal).toBe(mockToken.claims); + expect(retVal).toBe(userInfo); }); }); describe('getOktaConfig', () => { - it('returns normalized config', () => { - const service = createService({ - client_id: 'foo', - issuer: 'https://foo', - redirect_uri: 'https://foo', - auto_renew: true - }); + it('returns config', () => { + const service = createService(VALID_CONFIG); expect(service.getOktaConfig()).toMatchInlineSnapshot(` Object { - "auto_renew": true, "clientId": "foo", - "client_id": "foo", "issuer": "https://foo", "onSessionExpired": [Function], "redirectUri": "https://foo", - "redirect_uri": "https://foo", - "responseType": undefined, "scopes": Array [ "openid", + "email", ], - "tokenManager": Object { - "autoRenew": true, - "storage": undefined, - }, } `); }); }); describe('setFromUri', () => { - it('Saves the "referrerPath" in localStorage', () => { - localStorage.setItem('referrerPath', ''); - expect(localStorage.getItem('referrerPath')).toBe(''); + it('Saves the "referrerPath" in sessionStorage', () => { + sessionStorage.setItem('referrerPath', ''); + expect(sessionStorage.getItem('referrerPath')).toBe(''); const service = createService(); const uri = 'https://foo.random'; service.setFromUri(uri); - const val = JSON.parse(localStorage.getItem('referrerPath') || '{}'); - expect(val.uri).toBe(uri); + const val = sessionStorage.getItem('referrerPath'); + expect(val).toBe(uri); + }); + it('Saves the window.location.href by default', () => { + sessionStorage.setItem('referrerPath', ''); + expect(sessionStorage.getItem('referrerPath')).toBe(''); + const service = createService(); + service.setFromUri(); + const val = sessionStorage.getItem('referrerPath'); + expect(val).toBe(window.location.href); }); }); describe('getFromUri', () => { - test('cleares referrer from localStorage', () => { + it('cleares referrer from localStorage', () => { const TEST_VALUE = 'foo-bar'; - localStorage.setItem('referrerPath', JSON.stringify({ uri: TEST_VALUE })); + sessionStorage.setItem('referrerPath', TEST_VALUE); const service = createService(); const res = service.getFromUri(); - expect(res.uri).toBe(TEST_VALUE); - expect(localStorage.getItem('referrerPath')).not.toBeTruthy(); + expect(res).toBe(TEST_VALUE); + expect(sessionStorage.getItem('referrerPath')).not.toBeTruthy(); + }); + it('returns window.location.origin if nothing was set', () => { + sessionStorage.setItem('referrerPath', ''); + const service = createService(); + const res = service.getFromUri(); + expect(res).toBe(window.location.origin); + expect(sessionStorage.getItem('referrerPath')).not.toBeTruthy(); }); }); @@ -473,10 +491,11 @@ describe('Angular service', () => { it('calls onAuthRequired, if provided, instead of loginRedirect', () => { const onAuthRequired = jest.fn().mockReturnValue(expectedRes); const service = createService({ onAuthRequired }); + const injector = TestBed.get(Injector); const res = service.login(); expect(res).toBe(expectedRes); expect(OktaAuthService.prototype.loginRedirect).not.toHaveBeenCalled(); - expect(onAuthRequired).toHaveBeenCalledWith(service, service.router); + expect(onAuthRequired).toHaveBeenCalledWith(service, injector); }); it('Calls setFromUri with fromUri, if provided', () => { @@ -486,7 +505,7 @@ describe('Angular service', () => { expect(OktaAuthService.prototype.setFromUri).toHaveBeenCalledWith(fromUri); }); - it('Calls setFromUri with router.url, by default', () => { + it('Calls setFromUri with undefined, by default', () => { jest.spyOn(OktaAuthService.prototype, 'setFromUri').mockReturnValue(undefined); const service = createService(); const routerUrl = '/fakevalue?superfake#withhash'; @@ -494,7 +513,7 @@ describe('Angular service', () => { url: routerUrl }; service.login(); - expect(OktaAuthService.prototype.setFromUri).toHaveBeenCalledWith(routerUrl); + expect(OktaAuthService.prototype.setFromUri).toHaveBeenCalledWith(undefined); }); it('Passes "additionalParams" to loginRedirect', () => { @@ -510,37 +529,33 @@ describe('Angular service', () => { }); describe('loginRedirect', () => { - - it('If a URI is passed, it saves the "referrerPath" in localStorage', async () => { - localStorage.setItem('referrerPath', ''); - expect(localStorage.getItem('referrerPath')).toBe(''); + beforeEach(() => { + jest.spyOn(OktaAuthService.prototype, 'setFromUri').mockReturnValue(undefined); + }); + it('If a URI is passed, it calls setFromUri', async () => { const service = createService(); const uri = 'https://foo.random'; await service.loginRedirect(uri); - const val = JSON.parse(localStorage.getItem('referrerPath') || '{}'); - expect(val.uri).toBe(uri); + expect(service.setFromUri).toHaveBeenCalledWith(uri); }); - it('If no URI is passed, it does not save the "referrerPath" in localStorage', async () => { - localStorage.setItem('referrerPath', ''); - expect(localStorage.getItem('referrerPath')).toBe(''); + it('If no URI is passed, it does not call setFromUri', async () => { const service = createService(); - const uri = 'https://foo.random'; await service.loginRedirect(); - expect(localStorage.getItem('referrerPath')).toBe(''); + expect(service.setFromUri).not.toHaveBeenCalled(); }); - it('Sets responseType if none supplied', async () => { - const service = createService(); + it('Sets responseType and scopes from config if none supplied', async () => { + const service = createService({ responseType: ['fake'], scopes: ['openid', 'fake']}); const uri = 'https://foo.random'; await service.loginRedirect(uri); expect(service.oktaAuth.token.getWithRedirect).toHaveBeenCalledWith({ - responseType: ['id_token', 'token'], - scopes: ['openid'], + responseType: ['fake'], + scopes: ['openid', 'fake'], }); }); - it('Accepts additional parameters', async () => { + it('Accepts additional parameters, which override config values', async () => { const service = createService(); const uri = 'https://foo.random'; const params = { @@ -587,14 +602,17 @@ describe('Angular service', () => { describe('handleAuthentication', () => { let service: OktaAuthService; - let tokens: any[]; + let response: ParseFromUrlResponse; let isAuthenticated: boolean; beforeEach(() => { - tokens = []; + response = { + tokens: {}, + state: 'abc' + }; isAuthenticated = false; const mockAuthJS = extendMockAuthJS({ token: { - parseFromUrl: jest.fn().mockImplementation(() => tokens) + parseFromUrl: jest.fn().mockImplementation(() => response) }, tokenManager: { add: jest.fn() @@ -602,7 +620,6 @@ describe('Angular service', () => { }); service = createService(undefined, mockAuthJS); jest.spyOn(service, 'isAuthenticated').mockImplementation(() => Promise.resolve(isAuthenticated)); - jest.spyOn((service as any).router as any, 'navigate').mockReturnValue(null); jest.spyOn(service as any, 'emitAuthenticationState'); }); @@ -614,7 +631,7 @@ describe('Angular service', () => { it('stores tokens', async () => { const accessToken = { accessToken: 'foo' }; const idToken = { idToken: 'bar' }; - tokens = [accessToken, idToken]; + response.tokens = { accessToken, idToken }; await service.handleAuthentication(); expect((service as any).oktaAuth.tokenManager.add).toHaveBeenNthCalledWith(1, 'accessToken', accessToken); expect((service as any).oktaAuth.tokenManager.add).toHaveBeenNthCalledWith(2, 'idToken', idToken); @@ -631,13 +648,6 @@ describe('Angular service', () => { await service.handleAuthentication(); expect((service as any).emitAuthenticationState).toHaveBeenCalled(); }); - - it('navigates to the saved uri', async () => { - const uri = 'https://fake.test.foo'; - service.setFromUri(uri); - await service.handleAuthentication(); - expect((service as any).router.navigate).toHaveBeenCalledWith([uri], { queryParams: undefined }); - }); }); describe('logout', () => { @@ -652,7 +662,6 @@ describe('Angular service', () => { } }); service = createService(config, mockAuthJS); - jest.spyOn((service as any).router, 'navigate').mockReturnValue(null); jest.spyOn(service as any, 'emitAuthenticationState'); } @@ -668,36 +677,36 @@ describe('Angular service', () => { expect((service as any).emitAuthenticationState).toHaveBeenCalledWith(false); }); - it('redirects to the root by default', async () => { + it('can be called with no options', async () => { bootstrap(); await service.logout(); - expect((service as any).router.navigate).toHaveBeenCalledWith(['/']); + expect((service as any).oktaAuth.signOut).toHaveBeenCalledWith({}); }); it('accepts an argument for uri to redirect to', async () => { bootstrap(); const uri = 'https://my.custom.uri'; await service.logout(uri); - expect((service as any).router.navigate).toHaveBeenCalledWith([uri]); + expect((service as any).oktaAuth.signOut).toHaveBeenCalledWith({ + postLogoutRedirectUri: uri + }); }); - it('accepts an options object', async () => { + it('converts relative path to absolute uri', async () => { bootstrap(); - await service.logout({ foo: 'bar' }); - expect((service as any).router.navigate).toHaveBeenCalledWith(['/']); + const uri = '/relative-path'; + await service.logout(uri); + expect((service as any).oktaAuth.signOut).toHaveBeenCalledWith({ + postLogoutRedirectUri: window.location.origin + uri + }); }); - it('Does not call router.navigate if a "postLogoutRedirectUri" option was passed to logout()', async () => { - bootstrap(); - await service.logout({ postLogoutRedirectUri: 'foo' }); - expect((service as any).router.navigate).not.toHaveBeenCalled(); - }); - it('Does not call router.navigate if a "postLogoutRedirectUri" option was passed to constructor', async () => { - const config = extendConfig({ postLogoutRedirectUri: 'fake' }); - bootstrap(config); - await service.logout(); - expect((service as any).router.navigate).not.toHaveBeenCalled(); + it('accepts an options object', async () => { + bootstrap(); + const options = { foo: 'bar' }; + await service.logout(options); + expect((service as any).oktaAuth.signOut).toHaveBeenCalledWith(options); }); it('Returns a promise', async () => { @@ -717,7 +726,7 @@ describe('Angular service', () => { expect(e).toBe(testError); }) .then(() => { - expect((service as any).router.navigate).not.toHaveBeenCalled(); + expect((service as any).emitAuthenticationState).not.toHaveBeenCalled(); }); }); }); diff --git a/packages/okta-angular/yarn.lock b/packages/okta-angular/yarn.lock index 418feb49..c336d683 100644 --- a/packages/okta-angular/yarn.lock +++ b/packages/okta-angular/yarn.lock @@ -412,16 +412,15 @@ version "0.4.1" resolved "https://registry.yarnpkg.com/@okta/configuration-validation/-/configuration-validation-0.4.1.tgz#6fa4520bc96c27b3d7aedcb0523de1fbceee9105" -"@okta/okta-auth-js@^2.11.2": - version "2.11.2" - resolved "https://registry.yarnpkg.com/@okta/okta-auth-js/-/okta-auth-js-2.11.2.tgz#d2d867e45eb98d453f156ec68c927bf1594277f6" +"@okta/okta-auth-js@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@okta/okta-auth-js/-/okta-auth-js-3.0.0.tgz#8fc9d07e3e2906f6f2506fa92a483789892110fb" + integrity sha512-RD9R2JdNYKeg4OD6weRk/tHURVbC88L40ve2qZdbi1UrfdLyh3wlmrz5H/9H80my1t5JACZvUIR9MWnQaEapGA== dependencies: Base64 "0.3.0" cross-fetch "^3.0.0" js-cookie "2.2.0" node-cache "^4.2.0" - q "1.4.1" - reqwest "2.0.5" tiny-emitter "1.1.0" xhr2 "0.1.3" @@ -4628,10 +4627,6 @@ punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" -q@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" - qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -4846,10 +4841,6 @@ require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" -reqwest@2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/reqwest/-/reqwest-2.0.5.tgz#00fb15ac4918c419ca82b43f24c78882e66039a1" - resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"