feat: init and fork some code from angular-auth-oidc-client
Some checks are pending
Build, Lint & Test Lib / Built, Lint and Test Library (push) Waiting to run
Build, Lint & Test Lib / Angular latest (push) Blocked by required conditions
Build, Lint & Test Lib / Angular latest & Schematics Job (push) Blocked by required conditions
Build, Lint & Test Lib / Angular latest Standalone & Schematics Job (push) Blocked by required conditions
Build, Lint & Test Lib / Angular 16 & RxJs 6 (push) Blocked by required conditions
Build, Lint & Test Lib / Angular V16 (push) Blocked by required conditions
Docs / Build and Deploy Docs Job (push) Waiting to run
Docs / Close Pull Request Job (push) Waiting to run
Some checks are pending
Build, Lint & Test Lib / Built, Lint and Test Library (push) Waiting to run
Build, Lint & Test Lib / Angular latest (push) Blocked by required conditions
Build, Lint & Test Lib / Angular latest & Schematics Job (push) Blocked by required conditions
Build, Lint & Test Lib / Angular latest Standalone & Schematics Job (push) Blocked by required conditions
Build, Lint & Test Lib / Angular 16 & RxJs 6 (push) Blocked by required conditions
Build, Lint & Test Lib / Angular V16 (push) Blocked by required conditions
Docs / Build and Deploy Docs Job (push) Waiting to run
Docs / Close Pull Request Job (push) Waiting to run
This commit is contained in:
237
src/config/auth-well-known/auth-well-known-data.service.spec.ts
Normal file
237
src/config/auth-well-known/auth-well-known-data.service.spec.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { mockProvider } from '../../../test/auto-mock';
|
||||
import { createRetriableStream } from '../../../test/create-retriable-stream.helper';
|
||||
import { DataService } from '../../api/data.service';
|
||||
import { LoggerService } from '../../logging/logger.service';
|
||||
import { AuthWellKnownDataService } from './auth-well-known-data.service';
|
||||
import { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
||||
|
||||
const DUMMY_WELL_KNOWN_DOCUMENT = {
|
||||
issuer: 'https://identity-server.test/realms/main',
|
||||
authorization_endpoint:
|
||||
'https://identity-server.test/realms/main/protocol/openid-connect/auth',
|
||||
token_endpoint:
|
||||
'https://identity-server.test/realms/main/protocol/openid-connect/token',
|
||||
userinfo_endpoint:
|
||||
'https://identity-server.test/realms/main/protocol/openid-connect/userinfo',
|
||||
end_session_endpoint:
|
||||
'https://identity-server.test/realms/main/master/protocol/openid-connect/logout',
|
||||
jwks_uri:
|
||||
'https://identity-server.test/realms/main/protocol/openid-connect/certs',
|
||||
check_session_iframe:
|
||||
'https://identity-server.test/realms/main/protocol/openid-connect/login-status-iframe.html',
|
||||
introspection_endpoint:
|
||||
'https://identity-server.test/realms/main/protocol/openid-connect/token/introspect',
|
||||
};
|
||||
|
||||
describe('AuthWellKnownDataService', () => {
|
||||
let service: AuthWellKnownDataService;
|
||||
let dataService: DataService;
|
||||
let loggerService: LoggerService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
AuthWellKnownDataService,
|
||||
mockProvider(DataService),
|
||||
mockProvider(LoggerService),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(AuthWellKnownDataService);
|
||||
loggerService = TestBed.inject(LoggerService);
|
||||
dataService = TestBed.inject(DataService);
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('getWellKnownDocument', () => {
|
||||
it('should add suffix if it does not exist on current URL', waitForAsync(() => {
|
||||
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
|
||||
of(null)
|
||||
);
|
||||
const urlWithoutSuffix = 'myUrl';
|
||||
const urlWithSuffix = `${urlWithoutSuffix}/.well-known/openid-configuration`;
|
||||
|
||||
(service as any)
|
||||
.getWellKnownDocument(urlWithoutSuffix, { configId: 'configId1' })
|
||||
.subscribe(() => {
|
||||
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
|
||||
configId: 'configId1',
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not add suffix if it does exist on current url', waitForAsync(() => {
|
||||
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
|
||||
of(null)
|
||||
);
|
||||
const urlWithSuffix = `myUrl/.well-known/openid-configuration`;
|
||||
|
||||
(service as any)
|
||||
.getWellKnownDocument(urlWithSuffix, { configId: 'configId1' })
|
||||
.subscribe(() => {
|
||||
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
|
||||
configId: 'configId1',
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not add suffix if it does exist in the middle of current url', waitForAsync(() => {
|
||||
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
|
||||
of(null)
|
||||
);
|
||||
const urlWithSuffix = `myUrl/.well-known/openid-configuration/and/some/more/stuff`;
|
||||
|
||||
(service as any)
|
||||
.getWellKnownDocument(urlWithSuffix, { configId: 'configId1' })
|
||||
.subscribe(() => {
|
||||
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
|
||||
configId: 'configId1',
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should use the custom suffix provided in the config', waitForAsync(() => {
|
||||
const dataServiceSpy = spyOn(dataService, 'get').and.returnValue(
|
||||
of(null)
|
||||
);
|
||||
const urlWithoutSuffix = `myUrl`;
|
||||
const urlWithSuffix = `${urlWithoutSuffix}/.well-known/test-openid-configuration`;
|
||||
|
||||
(service as any)
|
||||
.getWellKnownDocument(urlWithoutSuffix, {
|
||||
configId: 'configId1',
|
||||
authWellknownUrlSuffix: '/.well-known/test-openid-configuration',
|
||||
})
|
||||
.subscribe(() => {
|
||||
expect(dataServiceSpy).toHaveBeenCalledOnceWith(urlWithSuffix, {
|
||||
configId: 'configId1',
|
||||
authWellknownUrlSuffix: '/.well-known/test-openid-configuration',
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should retry once', waitForAsync(() => {
|
||||
spyOn(dataService, 'get').and.returnValue(
|
||||
createRetriableStream(
|
||||
throwError(() => new Error('one')),
|
||||
of(DUMMY_WELL_KNOWN_DOCUMENT)
|
||||
)
|
||||
);
|
||||
|
||||
(service as any)
|
||||
.getWellKnownDocument('anyurl', { configId: 'configId1' })
|
||||
.subscribe({
|
||||
next: (res: unknown) => {
|
||||
expect(res).toBeTruthy();
|
||||
expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT);
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
it('should retry twice', waitForAsync(() => {
|
||||
spyOn(dataService, 'get').and.returnValue(
|
||||
createRetriableStream(
|
||||
throwError(() => new Error('one')),
|
||||
throwError(() => new Error('two')),
|
||||
of(DUMMY_WELL_KNOWN_DOCUMENT)
|
||||
)
|
||||
);
|
||||
|
||||
(service as any)
|
||||
.getWellKnownDocument('anyurl', { configId: 'configId1' })
|
||||
.subscribe({
|
||||
next: (res: any) => {
|
||||
expect(res).toBeTruthy();
|
||||
expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT);
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
it('should fail after three tries', waitForAsync(() => {
|
||||
spyOn(dataService, 'get').and.returnValue(
|
||||
createRetriableStream(
|
||||
throwError(() => new Error('one')),
|
||||
throwError(() => new Error('two')),
|
||||
throwError(() => new Error('three')),
|
||||
of(DUMMY_WELL_KNOWN_DOCUMENT)
|
||||
)
|
||||
);
|
||||
|
||||
(service as any).getWellKnownDocument('anyurl', 'configId').subscribe({
|
||||
error: (err: unknown) => {
|
||||
expect(err).toBeTruthy();
|
||||
},
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('getWellKnownEndPointsForConfig', () => {
|
||||
it('calling internal getWellKnownDocument and maps', waitForAsync(() => {
|
||||
spyOn(dataService, 'get').and.returnValue(of({ jwks_uri: 'jwks_uri' }));
|
||||
|
||||
const spy = spyOn(
|
||||
service as any,
|
||||
'getWellKnownDocument'
|
||||
).and.callThrough();
|
||||
|
||||
service
|
||||
.getWellKnownEndPointsForConfig({
|
||||
configId: 'configId1',
|
||||
authWellknownEndpointUrl: 'any-url',
|
||||
})
|
||||
.subscribe((result) => {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect((result as any).jwks_uri).toBeUndefined();
|
||||
expect(result.jwksUri).toBe('jwks_uri');
|
||||
});
|
||||
}));
|
||||
|
||||
it('throws error and logs if no authwellknownUrl is given', waitForAsync(() => {
|
||||
const loggerSpy = spyOn(loggerService, 'logError');
|
||||
const config = {
|
||||
configId: 'configId1',
|
||||
authWellknownEndpointUrl: undefined,
|
||||
};
|
||||
|
||||
service.getWellKnownEndPointsForConfig(config).subscribe({
|
||||
error: (error) => {
|
||||
expect(loggerSpy).toHaveBeenCalledOnceWith(
|
||||
config,
|
||||
'no authWellknownEndpoint given!'
|
||||
);
|
||||
expect(error.message).toEqual('no authWellknownEndpoint given!');
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
it('should merge the mapped endpoints with the provided endpoints', waitForAsync(() => {
|
||||
spyOn(dataService, 'get').and.returnValue(of(DUMMY_WELL_KNOWN_DOCUMENT));
|
||||
|
||||
const expected: AuthWellKnownEndpoints = {
|
||||
endSessionEndpoint: 'config-endSessionEndpoint',
|
||||
revocationEndpoint: 'config-revocationEndpoint',
|
||||
jwksUri: DUMMY_WELL_KNOWN_DOCUMENT.jwks_uri,
|
||||
};
|
||||
|
||||
service
|
||||
.getWellKnownEndPointsForConfig({
|
||||
configId: 'configId1',
|
||||
authWellknownEndpointUrl: 'any-url',
|
||||
authWellknownEndpoints: {
|
||||
endSessionEndpoint: 'config-endSessionEndpoint',
|
||||
revocationEndpoint: 'config-revocationEndpoint',
|
||||
},
|
||||
})
|
||||
.subscribe((result) => {
|
||||
expect(result).toEqual(jasmine.objectContaining(expected));
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
68
src/config/auth-well-known/auth-well-known-data.service.ts
Normal file
68
src/config/auth-well-known/auth-well-known-data.service.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { inject, Injectable } from 'injection-js';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { map, retry } from 'rxjs/operators';
|
||||
import { DataService } from '../../api/data.service';
|
||||
import { LoggerService } from '../../logging/logger.service';
|
||||
import { OpenIdConfiguration } from '../openid-configuration';
|
||||
import { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
||||
|
||||
const WELL_KNOWN_SUFFIX = `/.well-known/openid-configuration`;
|
||||
|
||||
@Injectable()
|
||||
export class AuthWellKnownDataService {
|
||||
private readonly loggerService = inject(LoggerService);
|
||||
|
||||
private readonly http = inject(DataService);
|
||||
|
||||
getWellKnownEndPointsForConfig(
|
||||
config: OpenIdConfiguration
|
||||
): Observable<AuthWellKnownEndpoints> {
|
||||
const { authWellknownEndpointUrl, authWellknownEndpoints = {} } = config;
|
||||
|
||||
if (!authWellknownEndpointUrl) {
|
||||
const errorMessage = 'no authWellknownEndpoint given!';
|
||||
|
||||
this.loggerService.logError(config, errorMessage);
|
||||
|
||||
return throwError(() => new Error(errorMessage));
|
||||
}
|
||||
|
||||
return this.getWellKnownDocument(authWellknownEndpointUrl, config).pipe(
|
||||
map(
|
||||
(wellKnownEndpoints) =>
|
||||
({
|
||||
issuer: wellKnownEndpoints.issuer,
|
||||
jwksUri: wellKnownEndpoints.jwks_uri,
|
||||
authorizationEndpoint: wellKnownEndpoints.authorization_endpoint,
|
||||
tokenEndpoint: wellKnownEndpoints.token_endpoint,
|
||||
userInfoEndpoint: wellKnownEndpoints.userinfo_endpoint,
|
||||
endSessionEndpoint: wellKnownEndpoints.end_session_endpoint,
|
||||
checkSessionIframe: wellKnownEndpoints.check_session_iframe,
|
||||
revocationEndpoint: wellKnownEndpoints.revocation_endpoint,
|
||||
introspectionEndpoint: wellKnownEndpoints.introspection_endpoint,
|
||||
parEndpoint:
|
||||
wellKnownEndpoints.pushed_authorization_request_endpoint,
|
||||
} as AuthWellKnownEndpoints)
|
||||
),
|
||||
map((mappedWellKnownEndpoints) => ({
|
||||
...mappedWellKnownEndpoints,
|
||||
...authWellknownEndpoints,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
private getWellKnownDocument(
|
||||
wellKnownEndpoint: string,
|
||||
config: OpenIdConfiguration
|
||||
): Observable<any> {
|
||||
let url = wellKnownEndpoint;
|
||||
|
||||
const wellKnownSuffix = config.authWellknownUrlSuffix || WELL_KNOWN_SUFFIX;
|
||||
|
||||
if (!wellKnownEndpoint.includes(wellKnownSuffix)) {
|
||||
url = `${wellKnownEndpoint}${wellKnownSuffix}`;
|
||||
}
|
||||
|
||||
return this.http.get(url, config).pipe(retry(2));
|
||||
}
|
||||
}
|
||||
12
src/config/auth-well-known/auth-well-known-endpoints.ts
Normal file
12
src/config/auth-well-known/auth-well-known-endpoints.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface AuthWellKnownEndpoints {
|
||||
issuer?: string;
|
||||
jwksUri?: string;
|
||||
authorizationEndpoint?: string;
|
||||
tokenEndpoint?: string;
|
||||
userInfoEndpoint?: string;
|
||||
endSessionEndpoint?: string;
|
||||
checkSessionIframe?: string;
|
||||
revocationEndpoint?: string;
|
||||
introspectionEndpoint?: string;
|
||||
parEndpoint?: string;
|
||||
}
|
||||
110
src/config/auth-well-known/auth-well-known.service.spec.ts
Normal file
110
src/config/auth-well-known/auth-well-known.service.spec.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { mockProvider } from '../../../test/auto-mock';
|
||||
import { EventTypes } from '../../public-events/event-types';
|
||||
import { PublicEventsService } from '../../public-events/public-events.service';
|
||||
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
||||
import { AuthWellKnownDataService } from './auth-well-known-data.service';
|
||||
import { AuthWellKnownService } from './auth-well-known.service';
|
||||
|
||||
describe('AuthWellKnownService', () => {
|
||||
let service: AuthWellKnownService;
|
||||
let dataService: AuthWellKnownDataService;
|
||||
let storagePersistenceService: StoragePersistenceService;
|
||||
let publicEventsService: PublicEventsService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
AuthWellKnownService,
|
||||
PublicEventsService,
|
||||
mockProvider(AuthWellKnownDataService),
|
||||
mockProvider(StoragePersistenceService),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(AuthWellKnownService);
|
||||
dataService = TestBed.inject(AuthWellKnownDataService);
|
||||
storagePersistenceService = TestBed.inject(StoragePersistenceService);
|
||||
publicEventsService = TestBed.inject(PublicEventsService);
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('getAuthWellKnownEndPoints', () => {
|
||||
it('getAuthWellKnownEndPoints throws an error if not config provided', waitForAsync(() => {
|
||||
service.queryAndStoreAuthWellKnownEndPoints(null).subscribe({
|
||||
error: (error) => {
|
||||
expect(error).toEqual(
|
||||
new Error(
|
||||
'Please provide a configuration before setting up the module'
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
it('getAuthWellKnownEndPoints calls always dataservice', waitForAsync(() => {
|
||||
const dataServiceSpy = spyOn(
|
||||
dataService,
|
||||
'getWellKnownEndPointsForConfig'
|
||||
).and.returnValue(of({ issuer: 'anything' }));
|
||||
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||
.and.returnValue({ issuer: 'anything' });
|
||||
|
||||
service
|
||||
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
||||
.subscribe((result) => {
|
||||
expect(storagePersistenceService.read).not.toHaveBeenCalled();
|
||||
expect(dataServiceSpy).toHaveBeenCalled();
|
||||
expect(result).toEqual({ issuer: 'anything' });
|
||||
});
|
||||
}));
|
||||
|
||||
it('getAuthWellKnownEndPoints stored the result if http call is made', waitForAsync(() => {
|
||||
const dataServiceSpy = spyOn(
|
||||
dataService,
|
||||
'getWellKnownEndPointsForConfig'
|
||||
).and.returnValue(of({ issuer: 'anything' }));
|
||||
|
||||
spyOn(storagePersistenceService, 'read')
|
||||
.withArgs('authWellKnownEndPoints', { configId: 'configId1' })
|
||||
.and.returnValue(null);
|
||||
const storeSpy = spyOn(service, 'storeWellKnownEndpoints');
|
||||
|
||||
service
|
||||
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
||||
.subscribe((result) => {
|
||||
expect(dataServiceSpy).toHaveBeenCalled();
|
||||
expect(storeSpy).toHaveBeenCalled();
|
||||
expect(result).toEqual({ issuer: 'anything' });
|
||||
});
|
||||
}));
|
||||
|
||||
it('throws `ConfigLoadingFailed` event when error happens from http', waitForAsync(() => {
|
||||
spyOn(dataService, 'getWellKnownEndPointsForConfig').and.returnValue(
|
||||
throwError(() => new Error('error'))
|
||||
);
|
||||
const publicEventsServiceSpy = spyOn(publicEventsService, 'fireEvent');
|
||||
|
||||
service
|
||||
.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' })
|
||||
.subscribe({
|
||||
error: (err) => {
|
||||
expect(err).toBeTruthy();
|
||||
expect(publicEventsServiceSpy).toHaveBeenCalledTimes(1);
|
||||
expect(publicEventsServiceSpy).toHaveBeenCalledOnceWith(
|
||||
EventTypes.ConfigLoadingFailed,
|
||||
null
|
||||
);
|
||||
},
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
58
src/config/auth-well-known/auth-well-known.service.ts
Normal file
58
src/config/auth-well-known/auth-well-known.service.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { inject, Injectable } from 'injection-js';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError, tap } from 'rxjs/operators';
|
||||
import { EventTypes } from '../../public-events/event-types';
|
||||
import { PublicEventsService } from '../../public-events/public-events.service';
|
||||
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
|
||||
import { OpenIdConfiguration } from '../openid-configuration';
|
||||
import { AuthWellKnownDataService } from './auth-well-known-data.service';
|
||||
import { AuthWellKnownEndpoints } from './auth-well-known-endpoints';
|
||||
|
||||
@Injectable()
|
||||
export class AuthWellKnownService {
|
||||
private readonly dataService = inject(AuthWellKnownDataService);
|
||||
|
||||
private readonly publicEventsService = inject(PublicEventsService);
|
||||
|
||||
private readonly storagePersistenceService = inject(
|
||||
StoragePersistenceService
|
||||
);
|
||||
|
||||
storeWellKnownEndpoints(
|
||||
config: OpenIdConfiguration,
|
||||
mappedWellKnownEndpoints: AuthWellKnownEndpoints
|
||||
): void {
|
||||
this.storagePersistenceService.write(
|
||||
'authWellKnownEndPoints',
|
||||
mappedWellKnownEndpoints,
|
||||
config
|
||||
);
|
||||
}
|
||||
|
||||
queryAndStoreAuthWellKnownEndPoints(
|
||||
config: OpenIdConfiguration | null
|
||||
): Observable<AuthWellKnownEndpoints> {
|
||||
if (!config) {
|
||||
return throwError(
|
||||
() =>
|
||||
new Error(
|
||||
'Please provide a configuration before setting up the module'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return this.dataService.getWellKnownEndPointsForConfig(config).pipe(
|
||||
tap((mappedWellKnownEndpoints) =>
|
||||
this.storeWellKnownEndpoints(config, mappedWellKnownEndpoints)
|
||||
),
|
||||
catchError((error) => {
|
||||
this.publicEventsService.fireEvent(
|
||||
EventTypes.ConfigLoadingFailed,
|
||||
null
|
||||
);
|
||||
|
||||
return throwError(() => new Error(error));
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user