mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-09-29 14:22:18 +00:00
WIP
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
import {
|
||||
AuthorizationServiceConfiguration,
|
||||
TokenResponse,
|
||||
} from '@openid/appauth';
|
||||
import {firstValueFrom} from 'rxjs';
|
||||
|
||||
import {
|
||||
AuthActions,
|
||||
AuthProvider,
|
||||
} from './auth.provider';
|
||||
|
||||
describe('AuthProvider', () => {
|
||||
let provider: AuthProvider;
|
||||
|
||||
beforeEach(() => {
|
||||
const platform = {
|
||||
is: () => false,
|
||||
} as any;
|
||||
|
||||
const configProvider = {} as any;
|
||||
|
||||
provider = new AuthProvider(
|
||||
platform,
|
||||
configProvider,
|
||||
);
|
||||
|
||||
(provider as any).authConfigValue = {
|
||||
server_host: 'https://idp.example.org',
|
||||
client_id: 'test-client',
|
||||
redirect_url: 'https://app.example.org/callback',
|
||||
scopes: 'openid profile',
|
||||
pkce: true,
|
||||
};
|
||||
|
||||
(provider as any).localConfiguration =
|
||||
new AuthorizationServiceConfiguration({
|
||||
authorization_endpoint:
|
||||
'https://idp.example.org/authorize',
|
||||
|
||||
token_endpoint:
|
||||
'https://idp.example.org/token',
|
||||
|
||||
revocation_endpoint:
|
||||
'https://idp.example.org/revoke',
|
||||
});
|
||||
});
|
||||
|
||||
it('refreshes the token when the access token is expired', async () => {
|
||||
const expiredToken = new TokenResponse({
|
||||
access_token: 'expired-access-token',
|
||||
refresh_token: 'valid-refresh-token',
|
||||
token_type: 'bearer',
|
||||
});
|
||||
|
||||
spyOn(
|
||||
expiredToken,
|
||||
'isValid',
|
||||
).and.returnValue(false);
|
||||
|
||||
(provider as any).tokenSubject.next(
|
||||
expiredToken,
|
||||
);
|
||||
|
||||
const refreshedToken = new TokenResponse({
|
||||
access_token: 'new-access-token',
|
||||
refresh_token: 'new-refresh-token',
|
||||
token_type: 'bearer',
|
||||
expires_in: '3600',
|
||||
});
|
||||
|
||||
spyOn(
|
||||
refreshedToken,
|
||||
'isValid',
|
||||
).and.returnValue(true);
|
||||
|
||||
const performTokenRequest =
|
||||
spyOn(
|
||||
(provider as any).tokenHandler,
|
||||
'performTokenRequest',
|
||||
).and.resolveTo(
|
||||
refreshedToken,
|
||||
);
|
||||
|
||||
spyOn(
|
||||
(provider as any).storage,
|
||||
'setItem',
|
||||
).and.resolveTo();
|
||||
|
||||
const token =
|
||||
await provider.getValidToken();
|
||||
|
||||
expect(
|
||||
performTokenRequest,
|
||||
).toHaveBeenCalled();
|
||||
|
||||
expect(
|
||||
token.accessToken,
|
||||
).toBe(
|
||||
'new-access-token',
|
||||
);
|
||||
|
||||
expect(
|
||||
token.refreshToken,
|
||||
).toBe(
|
||||
'new-refresh-token',
|
||||
);
|
||||
});
|
||||
|
||||
it('fails when the refresh token is expired', async () => {
|
||||
const expiredToken = new TokenResponse({
|
||||
access_token: 'expired-access-token',
|
||||
refresh_token: 'expired-refresh-token',
|
||||
token_type: 'bearer',
|
||||
});
|
||||
|
||||
spyOn(
|
||||
expiredToken,
|
||||
'isValid',
|
||||
).and.returnValue(false);
|
||||
|
||||
(provider as any).tokenSubject.next(
|
||||
expiredToken,
|
||||
);
|
||||
|
||||
spyOn(
|
||||
(provider as any).tokenHandler,
|
||||
'performTokenRequest',
|
||||
).and.returnValue(
|
||||
Promise.reject(
|
||||
new Error('invalid_grant'),
|
||||
),
|
||||
);
|
||||
|
||||
await expectAsync(
|
||||
provider.getValidToken(),
|
||||
).toBeRejectedWithError(
|
||||
'invalid_grant',
|
||||
);
|
||||
});
|
||||
|
||||
it('changes isLoggedIn to false when the refresh token fails', async () => {
|
||||
const expiredToken = new TokenResponse({
|
||||
access_token: 'expired-access-token',
|
||||
refresh_token: 'expired-refresh-token',
|
||||
token_type: 'bearer',
|
||||
});
|
||||
|
||||
spyOn(
|
||||
expiredToken,
|
||||
'isValid',
|
||||
).and.returnValue(false);
|
||||
|
||||
(provider as any).notify({
|
||||
action:
|
||||
AuthActions.LoadTokenFromStorageSuccess,
|
||||
tokenResponse:
|
||||
expiredToken,
|
||||
});
|
||||
|
||||
expect(
|
||||
await firstValueFrom(
|
||||
provider.isLoggedIn$,
|
||||
),
|
||||
).toBeTrue();
|
||||
|
||||
spyOn(
|
||||
(provider as any).tokenHandler,
|
||||
'performTokenRequest',
|
||||
).and.returnValue(
|
||||
Promise.reject(
|
||||
new Error('invalid_grant'),
|
||||
),
|
||||
);
|
||||
|
||||
await expectAsync(
|
||||
provider.getValidToken(),
|
||||
).toBeRejectedWithError(
|
||||
'invalid_grant',
|
||||
);
|
||||
|
||||
expect(
|
||||
await firstValueFrom(
|
||||
provider.isLoggedIn$,
|
||||
),
|
||||
).toBeFalse();
|
||||
});
|
||||
});
|
||||
@@ -554,7 +554,33 @@ export class AuthProvider {
|
||||
* requestTokenRefresh() stores the newly returned token
|
||||
* and updates tokenSubject via RefreshSuccess.
|
||||
*/
|
||||
await this.requestTokenRefresh();
|
||||
try {
|
||||
await this.requestTokenRefresh();
|
||||
} catch (error) {
|
||||
/*
|
||||
* An invalid_grant response means that the refresh token
|
||||
* is no longer usable, for example because it expired or
|
||||
* was revoked.
|
||||
*
|
||||
* Network errors must not log the user out.
|
||||
*/
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.message.includes(
|
||||
'invalid_grant',
|
||||
)
|
||||
) {
|
||||
this.authenticatedSubject.next(
|
||||
false,
|
||||
);
|
||||
|
||||
this.loggedInSubject.next(
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
const refreshedToken =
|
||||
this.tokenSubject.value;
|
||||
@@ -875,7 +901,6 @@ export class AuthProvider {
|
||||
.completeAuthorizationRequestIfPossible();
|
||||
}
|
||||
|
||||
|
||||
private async internalEndSessionCallback():
|
||||
Promise<void> {
|
||||
await this.closeBrowser();
|
||||
|
||||
@@ -1,70 +1,167 @@
|
||||
import {ConfigProvider} from '../config/config.provider';
|
||||
import {IdCardsProvider} from './id-cards.provider';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {AuthHelperService} from '../auth/auth-helper.service';
|
||||
import {BehaviorSubject, firstValueFrom, of} from 'rxjs';
|
||||
|
||||
import {AuthHelperService} from '../auth/auth-helper.service';
|
||||
import {AuthProvider} from '../auth/auth.provider';
|
||||
import {ConfigProvider} from '../config/config.provider';
|
||||
import {EncryptedStorageProvider} from '../storage/encrypted-storage.provider';
|
||||
import {IdCardsProvider} from './id-cards.provider';
|
||||
|
||||
class FakeAuth {
|
||||
isLoggedIn$ = new BehaviorSubject(false);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
getValidToken() {}
|
||||
getValidToken = jasmine.createSpy('getValidToken');
|
||||
}
|
||||
|
||||
describe('IdCards', () => {
|
||||
let authHelper: AuthHelperService;
|
||||
let configProvider: ConfigProvider;
|
||||
let httpClient: HttpClient;
|
||||
let authHelper: AuthHelperService;
|
||||
let authProvider: AuthProvider;
|
||||
let encryptedStorageProvider: EncryptedStorageProvider;
|
||||
let fakeAuth: FakeAuth;
|
||||
|
||||
beforeEach(() => {
|
||||
configProvider = jasmine.createSpyObj('ConfigProvider', ['config']);
|
||||
authHelper = jasmine.createSpyObj(
|
||||
'AuthHelperService',
|
||||
['getUserFromUserInfo'],
|
||||
);
|
||||
|
||||
configProvider = jasmine.createSpyObj(
|
||||
'ConfigProvider',
|
||||
['config'],
|
||||
);
|
||||
|
||||
configProvider.config = {
|
||||
app: {features: {extern: {idCards: {url: 'http://id-cards.local', authProvider: 'fakeAuth'}}}},
|
||||
app: {
|
||||
features: {
|
||||
extern: {
|
||||
idCards: {
|
||||
url: 'http://id-cards.local',
|
||||
authProvider: 'fakeAuth',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as never;
|
||||
httpClient = jasmine.createSpyObj('HttpClient', ['get']);
|
||||
|
||||
httpClient = jasmine.createSpyObj(
|
||||
'HttpClient',
|
||||
['get'],
|
||||
);
|
||||
|
||||
fakeAuth = new FakeAuth();
|
||||
authHelper = jasmine.createSpyObj('AuthHelperService', ['getProvider']);
|
||||
authHelper.getProvider = jasmine.createSpy().and.returnValue(fakeAuth);
|
||||
encryptedStorageProvider = jasmine.createSpyObj('EncryptedStorageProvider', ['get', 'set', 'delete']);
|
||||
encryptedStorageProvider.get = jasmine.createSpy().and.resolveTo();
|
||||
encryptedStorageProvider.set = jasmine.createSpy().and.resolveTo();
|
||||
encryptedStorageProvider.delete = jasmine.createSpy().and.resolveTo();
|
||||
|
||||
authProvider =
|
||||
fakeAuth as unknown as AuthProvider;
|
||||
|
||||
encryptedStorageProvider =
|
||||
jasmine.createSpyObj(
|
||||
'EncryptedStorageProvider',
|
||||
['get', 'set', 'delete'],
|
||||
);
|
||||
|
||||
encryptedStorageProvider.get =
|
||||
jasmine.createSpy().and.resolveTo();
|
||||
|
||||
encryptedStorageProvider.set =
|
||||
jasmine.createSpy().and.resolveTo();
|
||||
|
||||
encryptedStorageProvider.delete =
|
||||
jasmine.createSpy().and.resolveTo();
|
||||
});
|
||||
|
||||
it('should emit undefined if not logged in', async () => {
|
||||
const provider = new IdCardsProvider(authHelper, configProvider, httpClient, encryptedStorageProvider);
|
||||
expect(await firstValueFrom(provider.getIdCards())).toEqual([]);
|
||||
expect(authHelper.getProvider).toHaveBeenCalledTimes(1);
|
||||
const provider = new IdCardsProvider(
|
||||
authHelper,
|
||||
authProvider,
|
||||
configProvider,
|
||||
httpClient,
|
||||
encryptedStorageProvider,
|
||||
);
|
||||
|
||||
expect(
|
||||
await firstValueFrom(
|
||||
provider.getIdCards(),
|
||||
),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('should emit network result when logged in', async () => {
|
||||
fakeAuth.isLoggedIn$.next(true);
|
||||
httpClient.get = jasmine.createSpy().and.returnValue(of(['abc']));
|
||||
fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'});
|
||||
const provider = new IdCardsProvider(authHelper, configProvider, httpClient, encryptedStorageProvider);
|
||||
expect(await firstValueFrom(provider.getIdCards())).toEqual(['abc' as never]);
|
||||
expect(authHelper.getProvider).toHaveBeenCalledTimes(1);
|
||||
// eslint-disable-next-line unicorn/no-null
|
||||
expect(httpClient.get).toHaveBeenCalledOnceWith('http://id-cards.local', {
|
||||
headers: {
|
||||
Authorization: 'Bearer fake-token',
|
||||
},
|
||||
responseType: 'json',
|
||||
|
||||
httpClient.get =
|
||||
jasmine.createSpy().and.returnValue(
|
||||
of(['abc']),
|
||||
);
|
||||
|
||||
fakeAuth.getValidToken.and.resolveTo({
|
||||
accessToken: 'fake-token',
|
||||
});
|
||||
|
||||
const provider = new IdCardsProvider(
|
||||
authHelper,
|
||||
authProvider,
|
||||
configProvider,
|
||||
httpClient,
|
||||
encryptedStorageProvider,
|
||||
);
|
||||
|
||||
expect(
|
||||
await firstValueFrom(
|
||||
provider.getIdCards(),
|
||||
),
|
||||
).toEqual([
|
||||
'abc' as never,
|
||||
]);
|
||||
|
||||
// eslint-disable-next-line unicorn/no-null
|
||||
expect(httpClient.get)
|
||||
.toHaveBeenCalledOnceWith(
|
||||
'http://id-cards.local',
|
||||
{
|
||||
headers: {
|
||||
Authorization:
|
||||
'Bearer fake-token',
|
||||
},
|
||||
responseType: 'json',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should react to logins', async () => {
|
||||
const provider = new IdCardsProvider(authHelper, configProvider, httpClient, encryptedStorageProvider);
|
||||
const observable = provider.getIdCards();
|
||||
expect(await firstValueFrom(observable)).toEqual([]);
|
||||
httpClient.get = jasmine.createSpy().and.returnValue(of(['abc']));
|
||||
fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'});
|
||||
const provider = new IdCardsProvider(
|
||||
authHelper,
|
||||
authProvider,
|
||||
configProvider,
|
||||
httpClient,
|
||||
encryptedStorageProvider,
|
||||
);
|
||||
|
||||
const observable =
|
||||
provider.getIdCards();
|
||||
|
||||
expect(
|
||||
await firstValueFrom(observable),
|
||||
).toEqual([]);
|
||||
|
||||
httpClient.get =
|
||||
jasmine.createSpy().and.returnValue(
|
||||
of(['abc']),
|
||||
);
|
||||
|
||||
fakeAuth.getValidToken.and.resolveTo({
|
||||
accessToken: 'fake-token',
|
||||
});
|
||||
|
||||
fakeAuth.isLoggedIn$.next(true);
|
||||
|
||||
// this is counter-intuitive, but because we unsubscribed above the first value
|
||||
// will now contain the network result.
|
||||
expect(await firstValueFrom(observable)).toEqual(['abc' as never]);
|
||||
expect(
|
||||
await firstValueFrom(observable),
|
||||
).toEqual([
|
||||
'abc' as never,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user