diff --git a/angular-oauth2-oidc/gulpfile.js b/angular-oauth2-oidc/gulpfile.js index b3669666..6b9dfb32 100644 --- a/angular-oauth2-oidc/gulpfile.js +++ b/angular-oauth2-oidc/gulpfile.js @@ -72,7 +72,7 @@ gulp.task('rollup:fesm', function () { // Bundle's entry point // See https://github.com/rollup/rollup/wiki/JavaScript-API#entry - entry: `${buildFolder}/index.js`, + input: `${buildFolder}/index.js`, // Allow mixing of hypothetical and actual files. "Actual" files can be files // accessed by Rollup or produced by plugins further down the chain. @@ -105,7 +105,7 @@ gulp.task('rollup:umd', function () { // Bundle's entry point // See https://github.com/rollup/rollup/wiki/JavaScript-API#entry - entry: `${buildFolder}/index.js`, + input: `${buildFolder}/index.js`, // Allow mixing of hypothetical and actual files. "Actual" files can be files // accessed by Rollup or produced by plugins further down the chain. @@ -131,7 +131,7 @@ gulp.task('rollup:umd', function () { // The name to use for the module for UMD/IIFE bundles // (required for bundles with exports) // See https://github.com/rollup/rollup/wiki/JavaScript-API#modulename - moduleName: 'angular-oauth2-oidc', + name: 'angular-oauth2-oidc', // See https://github.com/rollup/rollup/wiki/JavaScript-API#globals globals: { diff --git a/angular-oauth2-oidc/package.json b/angular-oauth2-oidc/package.json index fd5f8b16..90fc24fa 100644 --- a/angular-oauth2-oidc/package.json +++ b/angular-oauth2-oidc/package.json @@ -42,7 +42,7 @@ "del": "^2.2.2", "gulp": "^3.9.1", "gulp-rename": "^1.2.2", - "gulp-rollup": "^2.11.0", + "gulp-rollup": "^2.15.0", "jasmine-core": "~2.5.2", "jasmine-spec-reporter": "~3.2.0", "karma": "~1.4.1", @@ -55,7 +55,7 @@ "node-sass-tilde-importer": "^1.0.0", "node-watch": "^0.5.2", "protractor": "~5.1.0", - "rollup": "^0.41.6", + "rollup": "^0.50.0", "run-sequence": "^1.2.2", "rxjs": "^5.1.0", "ts-node": "~2.0.0", diff --git a/angular-oauth2-oidc/src/index.ts b/angular-oauth2-oidc/src/index.ts index 321d5037..401f686b 100644 --- a/angular-oauth2-oidc/src/index.ts +++ b/angular-oauth2-oidc/src/index.ts @@ -1,5 +1,7 @@ import { NgModule, ModuleWithProviders } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { HttpClientModule } from '@angular/common/http'; + import { OAuthService } from './oauth-service'; import { UrlHelperService } from './url-helper.service'; @@ -26,7 +28,7 @@ export * from './tokens'; @NgModule({ imports: [ CommonModule, - //HttpModule + HttpClientModule ], declarations: [ ], diff --git a/angular-oauth2-oidc/src/oauth-service.ts b/angular-oauth2-oidc/src/oauth-service.ts index ace8891d..7ad8e0c1 100644 --- a/angular-oauth2-oidc/src/oauth-service.ts +++ b/angular-oauth2-oidc/src/oauth-service.ts @@ -1,12 +1,13 @@ -import { Http, URLSearchParams, Headers } from '@angular/http'; import { Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; +import { Subscription } from 'rxjs/Subscription'; + import { ValidationHandler, ValidationParams } from './token-validation/validation-handler'; import { UrlHelperService } from './url-helper.service'; -import { Subscription } from 'rxjs/Subscription'; import { OAuthEvent, OAuthInfoEvent, OAuthErrorEvent, OAuthSuccessEvent } from './events'; -import { OAuthStorage, LoginOptions, ParsedIdToken } from './types'; +import { OAuthStorage, LoginOptions, ParsedIdToken, OidcDiscoveryDoc, TokenResponse, UserInfo } from './types'; import { b64DecodeUnicode } from './base64-helper'; import { AuthConfig } from './auth.config'; @@ -65,7 +66,7 @@ export class OAuthService private silentRefreshSubject: string; constructor( - private http: Http, + private http: HttpClient, @Optional() storage: OAuthStorage, @Optional() tokenValidationHandler: ValidationHandler, @Optional() private config: AuthConfig, @@ -92,7 +93,6 @@ export class OAuthService this.setupRefreshTimer(); - } /** @@ -140,14 +140,13 @@ export class OAuthService * @param params Additional parameter to pass */ public setupAutomaticSilentRefresh(params: object = {}) { - this - .events - .filter(e => e.type === 'token_expires') - .subscribe(e => { - this.silentRefresh(params).catch(_ => { - this.debug('automatic silent refresh did not work'); - }) - }); + this.events + .filter(e => e.type === 'token_expires') + .subscribe(e => { + this.silentRefresh(params).catch(_ => { + this.debug('automatic silent refresh did not work'); + }); + }); this.restartRefreshTimerIfStillLoggedIn(); } @@ -306,7 +305,7 @@ export class OAuthService fullUrl = this.issuer || ''; if (!fullUrl.endsWith('/')) { fullUrl += '/'; - } + } fullUrl += '.well-known/openid-configuration'; } @@ -315,7 +314,7 @@ export class OAuthService return; } - this.http.get(fullUrl).map(r => r.json()).subscribe( + this.http.get(fullUrl).subscribe( (doc) => { if (!this.validateDiscoveryDocument(doc)) { @@ -368,7 +367,7 @@ export class OAuthService private loadJwks(): Promise { return new Promise((resolve, reject) => { if (this.jwksUri) { - this.http.get(this.jwksUri).map(r => r.json()).subscribe( + this.http.get(this.jwksUri).subscribe( jwks => { this.jwks = jwks; this.eventsSubject.next(new OAuthSuccessEvent('discovery_document_loaded')); @@ -388,55 +387,55 @@ export class OAuthService } - private validateDiscoveryDocument(doc: object): boolean { + private validateDiscoveryDocument(doc: OidcDiscoveryDoc): boolean { let errors: string[]; - if (doc['issuer'] !== this.issuer) { + if (doc.issuer !== this.issuer) { console.error( 'invalid issuer in discovery document', 'expected: ' + this.issuer, - 'current: ' + doc['issuer'] + 'current: ' + doc.issuer ); return false; } - errors = this.validateUrlFromDiscoveryDocument(doc['authorization_endpoint']); + errors = this.validateUrlFromDiscoveryDocument(doc.authorization_endpoint); if (errors.length > 0) { console.error('error validating authorization_endpoint in discovery document', errors); return false; } - errors = this.validateUrlFromDiscoveryDocument(doc['end_session_endpoint']); + errors = this.validateUrlFromDiscoveryDocument(doc.end_session_endpoint); if (errors.length > 0) { console.error('error validating end_session_endpoint in discovery document', errors); return false; } - errors = this.validateUrlFromDiscoveryDocument(doc['token_endpoint']); + errors = this.validateUrlFromDiscoveryDocument(doc.token_endpoint); if (errors.length > 0) { console.error('error validating token_endpoint in discovery document', errors); } - errors = this.validateUrlFromDiscoveryDocument(doc['userinfo_endpoint']); + errors = this.validateUrlFromDiscoveryDocument(doc.userinfo_endpoint); if (errors.length > 0) { console.error('error validating userinfo_endpoint in discovery document', errors); return false; } - errors = this.validateUrlFromDiscoveryDocument(doc['jwks_uri']); + errors = this.validateUrlFromDiscoveryDocument(doc.jwks_uri); if (errors.length > 0) { console.error('error validating jwks_uri in discovery document', errors); return false; } - if (this.sessionChecksEnabled && !doc['check_session_iframe']) { + if (this.sessionChecksEnabled && !doc.check_session_iframe) { console.warn( 'sessionChecksEnabled is activated but discovery document' + ' does not contain a check_session_iframe field'); } - this.sessionChecksEnabled = doc['check_session_iframe']; + this.sessionChecksEnabled = !!doc.check_session_iframe; return true; } @@ -458,7 +457,7 @@ export class OAuthService public fetchTokenUsingPasswordFlowAndLoadUserProfile( userName: string, password: string, - headers: Headers = new Headers()): Promise { + headers: HttpHeaders = new HttpHeaders()): Promise { return this .fetchTokenUsingPasswordFlow(userName, password, headers) .then(() => this.loadUserProfile()); @@ -481,17 +480,17 @@ export class OAuthService return new Promise((resolve, reject) => { - let headers = new Headers(); - headers.set('Authorization', 'Bearer ' + this.getAccessToken()); + const headers = new HttpHeaders() + .set('Authorization', 'Bearer ' + this.getAccessToken()); - this.http.get(this.userinfoEndpoint, { headers }).map(r => r.json()).subscribe( - (doc) => { - this.debug('userinfo received', doc); + this.http.get(this.userinfoEndpoint, { headers }).subscribe( + (info) => { + this.debug('userinfo received', info); let existingClaims = this.getIdentityClaims() || {}; - + if (!this.skipSubjectCheck) { - if (this.oidc && (!existingClaims['sub'] || doc.sub !== existingClaims['sub'])) { + if (this.oidc && (!existingClaims['sub'] || info.sub !== existingClaims['sub'])) { let err = 'if property oidc is true, the received user-id (sub) has to be the user-id ' + 'of the user that has logged in with oidc.\n' + 'if you are not using oidc but just oauth2 password flow set oidc to false'; @@ -501,11 +500,11 @@ export class OAuthService } } - doc = Object.assign({}, existingClaims, doc); + info = Object.assign({}, existingClaims, info); - this._storage.setItem('id_token_claims_obj', JSON.stringify(doc)); + this._storage.setItem('id_token_claims_obj', JSON.stringify(info)); this.eventsSubject.next(new OAuthSuccessEvent('user_profile_loaded')); - resolve(doc); + resolve(info); }, (err) => { console.error('error loading user info', err); @@ -522,7 +521,7 @@ export class OAuthService * @param password * @param headers Optional additional http-headers. */ - public fetchTokenUsingPasswordFlow(userName: string, password: string, headers: Headers = new Headers()): Promise { + public fetchTokenUsingPasswordFlow(userName: string, password: string, headers: HttpHeaders = new HttpHeaders()): Promise { if (!this.validateUrlForHttps(this.tokenEndpoint)) { throw new Error('tokenEndpoint must use Http. Also check property requireHttps.'); @@ -544,7 +543,7 @@ export class OAuthService let params = search.toString(); - this.http.post(this.tokenEndpoint, params, { headers }).map(r => r.json()).subscribe( + this.http.post(this.tokenEndpoint, params, { headers }).subscribe( (tokenResponse) => { this.debug('tokenResponse', tokenResponse); this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in); @@ -586,12 +585,12 @@ export class OAuthService search.set('client_secret', this.dummyClientSecret); } - let headers = new Headers(); - headers.set('Content-Type', 'application/x-www-form-urlencoded'); + const headers = new HttpHeaders() + .set('Content-Type', 'application/x-www-form-urlencoded'); let params = search.toString(); - this.http.post(this.tokenEndpoint, params, { headers }).map(r => r.json()).subscribe( + this.http.post(this.tokenEndpoint, params, { headers }).subscribe( (tokenResponse) => { this.debug('refresh tokenResponse', tokenResponse); this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in); @@ -785,8 +784,7 @@ export class OAuthService } private waitForSilentRefreshAfterSessionChange() { - this - .events + this.events .filter((e: OAuthEvent) => e.type === 'silently_refreshed' || e.type === 'silent_refresh_timeout' @@ -1396,7 +1394,7 @@ export class OAuthService this._storage.removeItem('id_token_expires_at'); this._storage.removeItem('id_token_stored_at'); this._storage.removeItem('access_token_stored_at'); - + this.silentRefreshSubject = null; if (!this.logoutUrl) return; @@ -1406,7 +1404,7 @@ export class OAuthService let logoutUrl: string; if (!this.validateUrlForHttps(this.logoutUrl)) throw new Error('logoutUrl must use Http. Also check property requireHttps.'); - + // For backward compatibility if (this.logoutUrl.indexOf('{{') > -1) { logoutUrl = this.logoutUrl.replace(/\{\{id_token\}\}/, id_token); diff --git a/angular-oauth2-oidc/src/types.ts b/angular-oauth2-oidc/src/types.ts index 46978bc6..ebee719e 100644 --- a/angular-oauth2-oidc/src/types.ts +++ b/angular-oauth2-oidc/src/types.ts @@ -77,3 +77,61 @@ export interface ParsedIdToken { idTokenHeaderJson: string; idTokenExpiresAt: number; } + +/** + * Represents the response from the token endpoint + * http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint + */ +export interface TokenResponse { + access_token: string; + token_type: string; + expires_in: number; + refresh_token: string; + scope: string; + state?: string; +} + +/** + * Represents the response from the user info endpoint + * http://openid.net/specs/openid-connect-core-1_0.html#UserInfo + */ +export interface UserInfo { + sub: string; + [key: string]: any; +} + +/** + * Represents an OpenID Connect discovery document + */ +export interface OidcDiscoveryDoc { + issuer: string; + authorization_endpoint: string; + token_endpoint: string; + token_endpoint_auth_methods_supported: string[]; + token_endpoint_auth_signing_alg_values_supported: string[]; + userinfo_endpoint: string; + check_session_iframe: string; + end_session_endpoint: string; + jwks_uri: string; + registration_endpoint: string; + scopes_supported: string[]; + response_types_supported: string[]; + acr_values_supported: string[]; + response_modes_supported: string[]; + grant_types_supported: string[]; + subject_types_supported: string[]; + userinfo_signing_alg_values_supported: string[]; + userinfo_encryption_alg_values_supported: string[]; + userinfo_encryption_enc_values_supported: string[]; + id_token_signing_alg_values_supported: string[]; + id_token_encryption_alg_values_supported: string[]; + id_token_encryption_enc_values_supported: string[]; + request_object_signing_alg_values_supported: string[]; + display_values_supported: string[]; + claim_types_supported: string[]; + claims_supported: string[]; + claims_parameter_supported: boolean; + service_documentation: string; + ui_locales_supported: string[]; +} + diff --git a/gulpfile.js b/gulpfile.js index b3669666..6b9dfb32 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -72,7 +72,7 @@ gulp.task('rollup:fesm', function () { // Bundle's entry point // See https://github.com/rollup/rollup/wiki/JavaScript-API#entry - entry: `${buildFolder}/index.js`, + input: `${buildFolder}/index.js`, // Allow mixing of hypothetical and actual files. "Actual" files can be files // accessed by Rollup or produced by plugins further down the chain. @@ -105,7 +105,7 @@ gulp.task('rollup:umd', function () { // Bundle's entry point // See https://github.com/rollup/rollup/wiki/JavaScript-API#entry - entry: `${buildFolder}/index.js`, + input: `${buildFolder}/index.js`, // Allow mixing of hypothetical and actual files. "Actual" files can be files // accessed by Rollup or produced by plugins further down the chain. @@ -131,7 +131,7 @@ gulp.task('rollup:umd', function () { // The name to use for the module for UMD/IIFE bundles // (required for bundles with exports) // See https://github.com/rollup/rollup/wiki/JavaScript-API#modulename - moduleName: 'angular-oauth2-oidc', + name: 'angular-oauth2-oidc', // See https://github.com/rollup/rollup/wiki/JavaScript-API#globals globals: { diff --git a/package.json b/package.json index da4039fc..5fa67a54 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "del": "^2.2.2", "gulp": "^3.9.1", "gulp-rename": "^1.2.2", - "gulp-rollup": "^2.11.0", + "gulp-rollup": "^2.15.0", "jasmine-core": "~2.5.2", "jasmine-spec-reporter": "~3.2.0", "karma": "~1.4.1", @@ -54,7 +54,7 @@ "node-sass-tilde-importer": "^1.0.0", "node-watch": "^0.5.2", "protractor": "~5.1.0", - "rollup": "^0.41.6", + "rollup": "^0.50.0", "run-sequence": "^1.2.2", "rxjs": "^5.1.0", "ts-node": "~2.0.0",