• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

damienbod / angular-auth-oidc-client / 8316854975

17 Mar 2024 04:37PM UTC coverage: 100.0% (+2.9%) from 97.057%
8316854975

Pull #1848

github

web-flow
Merge 487b99570 into 6c778a4a2
Pull Request #1848: moving to strict mode

228 of 293 new or added lines in 48 files covered. (77.82%)

11 existing lines in 6 files now uncovered.

0 of 0 relevant lines covered (NaN%)

0.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.29
/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts
1
import { HttpHeaders } from '@angular/common/http';
2
import { Injectable } from '@angular/core';
3
import { Observable, of, throwError } from 'rxjs';
4
import { catchError, concatMap, retry, switchMap } from 'rxjs/operators';
5
import { DataService } from '../api/data.service';
6
import { LogoutAuthOptions } from '../auth-options';
7
import { OpenIdConfiguration } from '../config/openid-configuration';
8
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
9
import { CheckSessionService } from '../iframe/check-session.service';
10
import { LoggerService } from '../logging/logger.service';
11
import { StoragePersistenceService } from '../storage/storage-persistence.service';
12
import { removeNullAndUndefinedValues } from '../utils/object/object.helper';
13
import { RedirectService } from '../utils/redirect/redirect.service';
14
import { UrlService } from '../utils/url/url.service';
15

16
@Injectable({ providedIn: 'root' })
17
export class LogoffRevocationService {
1✔
18
  constructor(
19
    private readonly dataService: DataService,
32✔
20
    private readonly storagePersistenceService: StoragePersistenceService,
32✔
21
    private readonly loggerService: LoggerService,
32✔
22
    private readonly urlService: UrlService,
32✔
23
    private readonly checkSessionService: CheckSessionService,
32✔
24
    private readonly resetAuthDataService: ResetAuthDataService,
32✔
25
    private readonly redirectService: RedirectService
32✔
26
  ) {}
27

28
  // Logs out on the server and the local client.
29
  // If the server state has changed, check session, then only a local logout.
30
  logoff(
31
    config: OpenIdConfiguration | null,
32
    allConfigs: OpenIdConfiguration[],
33
    logoutAuthOptions?: LogoutAuthOptions
34
  ): Observable<unknown> {
35
    if (!config) {
9!
NEW
36
      return throwError(
×
37
        () =>
NEW
38
          new Error(
×
39
            'Please provide a configuration before setting up the module'
40
          )
41
      );
42
    }
43

44
    this.loggerService.logDebug(
9✔
45
      config,
46
      'logoff, remove auth',
47
      logoutAuthOptions
48
    );
49

50
    const { urlHandler, customParams } = logoutAuthOptions || {};
9✔
51

52
    const endSessionUrl = this.urlService.getEndSessionUrl(
9✔
53
      config,
54
      customParams
55
    );
56

57
    if (!endSessionUrl) {
9✔
58
      this.loggerService.logDebug(
3✔
59
        config,
60
        'No endsessionUrl present. Logoff was only locally. Returning.'
61
      );
62

63
      return of(null);
3✔
64
    }
65

66
    if (this.checkSessionService.serverStateChanged(config)) {
6✔
67
      this.loggerService.logDebug(
1✔
68
        config,
69
        'Server State changed. Logoff was only locally. Returning.'
70
      );
71

72
      return of(null);
1✔
73
    }
74

75
    if (urlHandler) {
5✔
76
      this.loggerService.logDebug(
1✔
77
        config,
78
        `Custom UrlHandler found. Using this to handle logoff with url '${endSessionUrl}'`
79
      );
80
      urlHandler(endSessionUrl);
1✔
81
      this.resetAuthDataService.resetAuthorizationData(config, allConfigs);
1✔
82

83
      return of(null);
1✔
84
    }
85

86
    return this.logoffInternal(
4✔
87
      logoutAuthOptions,
88
      endSessionUrl,
89
      config,
90
      allConfigs
91
    );
92
  }
93

94
  logoffLocal(
95
    config: OpenIdConfiguration | null,
96
    allConfigs: OpenIdConfiguration[]
97
  ): void {
98
    this.resetAuthDataService.resetAuthorizationData(config, allConfigs);
3✔
99
    this.checkSessionService.stop();
3✔
100
  }
101

102
  logoffLocalMultiple(allConfigs: OpenIdConfiguration[]): void {
103
    allConfigs.forEach((configuration) =>
1✔
104
      this.logoffLocal(configuration, allConfigs)
2✔
105
    );
106
  }
107

108
  // The refresh token and and the access token are revoked on the server. If the refresh token does not exist
109
  // only the access token is revoked. Then the logout run.
110
  logoffAndRevokeTokens(
111
    config: OpenIdConfiguration | null,
112
    allConfigs: OpenIdConfiguration[],
113
    logoutAuthOptions?: LogoutAuthOptions
114
  ): Observable<any> {
115
    if (!config) {
6!
NEW
116
      return throwError(
×
117
        () =>
NEW
118
          new Error(
×
119
            'Please provide a configuration before setting up the module'
120
          )
121
      );
122
    }
123

124
    const { revocationEndpoint } =
125
      this.storagePersistenceService.read('authWellKnownEndPoints', config) ||
6✔
126
      {};
127

128
    if (!revocationEndpoint) {
6✔
129
      this.loggerService.logDebug(config, 'revocation endpoint not supported');
2✔
130

131
      return this.logoff(config, allConfigs, logoutAuthOptions);
2✔
132
    }
133

134
    if (this.storagePersistenceService.getRefreshToken(config)) {
4✔
135
      return this.revokeRefreshToken(config).pipe(
2✔
136
        switchMap((_) => this.revokeAccessToken(config)),
2✔
137
        catchError((error) => {
138
          const errorMessage = `revoke token failed`;
1✔
139

140
          this.loggerService.logError(config, errorMessage, error);
1✔
141

142
          return throwError(() => new Error(errorMessage));
1✔
143
        }),
144
        concatMap(() => this.logoff(config, allConfigs, logoutAuthOptions))
1✔
145
      );
146
    } else {
147
      return this.revokeAccessToken(config).pipe(
2✔
148
        catchError((error) => {
149
          const errorMessage = `revoke accessToken failed`;
1✔
150

151
          this.loggerService.logError(config, errorMessage, error);
1✔
152

153
          return throwError(() => new Error(errorMessage));
1✔
154
        }),
155
        concatMap(() => this.logoff(config, allConfigs, logoutAuthOptions))
1✔
156
      );
157
    }
158
  }
159

160
  // https://tools.ietf.org/html/rfc7009
161
  // revokes an access token on the STS. If no token is provided, then the token from
162
  // the storage is revoked. You can pass any token to revoke. This makes it possible to
163
  // manage your own tokens. The is a public API.
164
  revokeAccessToken(
165
    configuration: OpenIdConfiguration | null,
166
    accessToken?: any
167
  ): Observable<any> {
168
    if (!configuration) {
8!
NEW
169
      return throwError(
×
170
        () =>
NEW
171
          new Error(
×
172
            'Please provide a configuration before setting up the module'
173
          )
174
      );
175
    }
176

177
    const accessTok =
178
      accessToken ||
8✔
179
      this.storagePersistenceService.getAccessToken(configuration);
180
    const body = this.urlService.createRevocationEndpointBodyAccessToken(
8✔
181
      accessTok,
182
      configuration
183
    );
184

185
    return this.sendRevokeRequest(configuration, body);
8✔
186
  }
187

188
  // https://tools.ietf.org/html/rfc7009
189
  // revokes an refresh token on the STS. This is only required in the code flow with refresh tokens.
190
  // If no token is provided, then the token from the storage is revoked. You can pass any token to revoke.
191
  // This makes it possible to manage your own tokens.
192
  revokeRefreshToken(
193
    configuration: OpenIdConfiguration | null,
194
    refreshToken?: any
195
  ): Observable<any> {
196
    if (!configuration) {
8!
NEW
197
      return throwError(
×
198
        () =>
NEW
199
          new Error(
×
200
            'Please provide a configuration before setting up the module'
201
          )
202
      );
203
    }
204

205
    const refreshTok =
206
      refreshToken ||
8✔
207
      this.storagePersistenceService.getRefreshToken(configuration);
208
    const body = this.urlService.createRevocationEndpointBodyRefreshToken(
8✔
209
      refreshTok,
210
      configuration
211
    );
212

213
    return this.sendRevokeRequest(configuration, body);
8✔
214
  }
215

216
  private logoffInternal(
217
    logoutAuthOptions: LogoutAuthOptions | undefined,
218
    endSessionUrl: string,
219
    config: OpenIdConfiguration,
220
    allConfigs: OpenIdConfiguration[]
221
  ): Observable<unknown> {
222
    const { logoffMethod, customParams } = logoutAuthOptions || {};
4✔
223

224
    if (!logoffMethod || logoffMethod === 'GET') {
4✔
225
      this.redirectService.redirectTo(endSessionUrl);
2✔
226

227
      this.resetAuthDataService.resetAuthorizationData(config, allConfigs);
2✔
228

229
      return of(null);
2✔
230
    }
231

232
    const { state, logout_hint, ui_locales } = customParams || {};
2✔
233
    const { clientId } = config;
2✔
234
    const idToken = this.storagePersistenceService.getIdToken(config);
2✔
235
    const postLogoutRedirectUrl =
236
      this.urlService.getPostLogoutRedirectUrl(config);
2✔
237
    const headers = this.getHeaders();
2✔
238
    const { url } = this.urlService.getEndSessionEndpoint(config);
2✔
239
    const body = {
2✔
240
      id_token_hint: idToken,
241
      client_id: clientId,
242
      post_logout_redirect_uri: postLogoutRedirectUrl,
243
      state,
244
      logout_hint,
245
      ui_locales,
246
    };
247
    const bodyWithoutNullOrUndefined = removeNullAndUndefinedValues(body);
2✔
248

249
    this.resetAuthDataService.resetAuthorizationData(config, allConfigs);
2✔
250

251
    return this.dataService.post(
2✔
252
      url,
253
      bodyWithoutNullOrUndefined,
254
      config,
255
      headers
256
    );
257
  }
258

259
  private sendRevokeRequest(
260
    configuration: OpenIdConfiguration,
261
    body: string | null
262
  ): Observable<any> {
263
    const url = this.urlService.getRevocationEndpointUrl(configuration);
16✔
264
    const headers = this.getHeaders();
16✔
265

266
    return this.dataService.post(url, body, configuration, headers).pipe(
16✔
267
      retry(2),
268
      switchMap((response: any) => {
269
        this.loggerService.logDebug(
6✔
270
          configuration,
271
          'revocation endpoint post response: ',
272
          response
273
        );
274

275
        return of(response);
6✔
276
      }),
277
      catchError((error) => {
278
        const errorMessage = `Revocation request failed`;
4✔
279

280
        this.loggerService.logError(configuration, errorMessage, error);
4✔
281

282
        return throwError(() => new Error(errorMessage));
4✔
283
      })
284
    );
285
  }
286

287
  private getHeaders(): HttpHeaders {
288
    let headers: HttpHeaders = new HttpHeaders();
18✔
289

290
    headers = headers.set('Content-Type', 'application/x-www-form-urlencoded');
18✔
291

292
    return headers;
18✔
293
  }
294
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc