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

damienbod / angular-auth-oidc-client / 8316854975

17 Mar 2024 04:37PM CUT 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

93.39
/projects/angular-auth-oidc-client/src/lib/user-data/user.service.ts
1
import { Injectable } from '@angular/core';
2
import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
3
import { map, retry, switchMap } from 'rxjs/operators';
4
import { DataService } from '../api/data.service';
5
import { OpenIdConfiguration } from '../config/openid-configuration';
6
import { LoggerService } from '../logging/logger.service';
7
import { EventTypes } from '../public-events/event-types';
8
import { PublicEventsService } from '../public-events/public-events.service';
9
import { StoragePersistenceService } from '../storage/storage-persistence.service';
10
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
11
import { TokenHelperService } from '../utils/tokenHelper/token-helper.service';
12
import { ConfigUserDataResult, UserDataResult } from './userdata-result';
13

14
const DEFAULT_USERRESULT = { userData: null, allUserData: [] };
1✔
15

16
@Injectable({ providedIn: 'root' })
17
export class UserService {
1✔
18
  private readonly userDataInternal$ = new BehaviorSubject<UserDataResult>(
30✔
19
    DEFAULT_USERRESULT
20
  );
21

22
  get userData$(): Observable<UserDataResult> {
23
    return this.userDataInternal$.asObservable();
119✔
24
  }
25

26
  constructor(
27
    private readonly oidcDataService: DataService,
30✔
28
    private readonly storagePersistenceService: StoragePersistenceService,
30✔
29
    private readonly eventService: PublicEventsService,
30✔
30
    private readonly loggerService: LoggerService,
30✔
31
    private readonly tokenHelperService: TokenHelperService,
30✔
32
    private readonly flowHelper: FlowHelper
30✔
33
  ) {}
34

35
  getAndPersistUserDataInStore(
36
    currentConfiguration: OpenIdConfiguration,
37
    allConfigs: OpenIdConfiguration[],
38
    isRenewProcess = false,
×
39
    idToken?: any,
40
    decodedIdToken?: any
41
  ): Observable<any> {
42
    idToken =
7✔
43
      idToken ||
14✔
44
      this.storagePersistenceService.getIdToken(currentConfiguration);
45
    decodedIdToken =
7✔
46
      decodedIdToken ||
7!
47
      this.tokenHelperService.getPayloadFromToken(
48
        idToken,
49
        false,
50
        currentConfiguration
51
      );
52

53
    const existingUserDataFromStorage =
54
      this.getUserDataFromStore(currentConfiguration);
7✔
55
    const haveUserData = !!existingUserDataFromStorage;
7✔
56
    const isCurrentFlowImplicitFlowWithAccessToken =
57
      this.flowHelper.isCurrentFlowImplicitFlowWithAccessToken(
7✔
58
        currentConfiguration
59
      );
60
    const isCurrentFlowCodeFlow =
61
      this.flowHelper.isCurrentFlowCodeFlow(currentConfiguration);
7✔
62

63
    const accessToken =
64
      this.storagePersistenceService.getAccessToken(currentConfiguration);
7✔
65

66
    if (!(isCurrentFlowImplicitFlowWithAccessToken || isCurrentFlowCodeFlow)) {
7✔
67
      this.loggerService.logDebug(
2✔
68
        currentConfiguration,
69
        `authCallback idToken flow with accessToken ${accessToken}`
70
      );
71

72
      this.setUserDataToStore(decodedIdToken, currentConfiguration, allConfigs);
2✔
73

74
      return of(decodedIdToken);
2✔
75
    }
76

77
    const { renewUserInfoAfterTokenRenew } = currentConfiguration;
5✔
78

79
    if (!isRenewProcess || renewUserInfoAfterTokenRenew || !haveUserData) {
5✔
80
      return this.getUserDataOidcFlowAndSave(
4✔
81
        decodedIdToken.sub,
82
        currentConfiguration,
83
        allConfigs
84
      ).pipe(
85
        switchMap((userData) => {
86
          this.loggerService.logDebug(
4✔
87
            currentConfiguration,
88
            'Received user data: ',
89
            userData
90
          );
91
          if (!!userData) {
4✔
92
            this.loggerService.logDebug(
3✔
93
              currentConfiguration,
94
              'accessToken: ',
95
              accessToken
96
            );
97

98
            return of(userData);
3✔
99
          } else {
100
            return throwError(
1✔
101
              () => new Error('Received no user data, request failed')
1✔
102
            );
103
          }
104
        })
105
      );
106
    }
107

108
    return of(existingUserDataFromStorage);
1✔
109
  }
110

111
  getUserDataFromStore(currentConfiguration: OpenIdConfiguration | null): any {
112
    if (!currentConfiguration) {
3!
NEW
113
      return throwError(
×
114
        () =>
NEW
115
          new Error(
×
116
            'Please provide a configuration before setting up the module'
117
          )
118
      );
119
    }
120

121
    return (
3✔
122
      this.storagePersistenceService.read('userData', currentConfiguration) ||
4✔
123
      null
124
    );
125
  }
126

127
  publishUserDataIfExists(
128
    currentConfiguration: OpenIdConfiguration,
129
    allConfigs: OpenIdConfiguration[]
130
  ): void {
131
    const userData = this.getUserDataFromStore(currentConfiguration);
4✔
132

133
    if (userData) {
4✔
134
      this.fireUserDataEvent(currentConfiguration, allConfigs, userData);
3✔
135
    }
136
  }
137

138
  setUserDataToStore(
139
    userData: any,
140
    currentConfiguration: OpenIdConfiguration,
141
    allConfigs: OpenIdConfiguration[]
142
  ): void {
143
    this.storagePersistenceService.write(
7✔
144
      'userData',
145
      userData,
146
      currentConfiguration
147
    );
148
    this.fireUserDataEvent(currentConfiguration, allConfigs, userData);
7✔
149
  }
150

151
  resetUserDataInStore(
152
    currentConfiguration: OpenIdConfiguration,
153
    allConfigs: OpenIdConfiguration[]
154
  ): void {
155
    this.storagePersistenceService.remove('userData', currentConfiguration);
4✔
156
    this.fireUserDataEvent(currentConfiguration, allConfigs, null);
4✔
157
  }
158

159
  private getUserDataOidcFlowAndSave(
160
    idTokenSub: any,
161
    currentConfiguration: OpenIdConfiguration,
162
    allConfigs: OpenIdConfiguration[]
163
  ): Observable<any> {
164
    return this.getIdentityUserData(currentConfiguration).pipe(
4✔
165
      map((data: any) => {
166
        if (
4✔
167
          this.validateUserDataSubIdToken(
168
            currentConfiguration,
169
            idTokenSub,
170
            data?.sub
171
          )
172
        ) {
173
          this.setUserDataToStore(data, currentConfiguration, allConfigs);
3✔
174

175
          return data;
3✔
176
        } else {
177
          // something went wrong, user data sub does not match that from id_token
178
          this.loggerService.logWarning(
1✔
179
            currentConfiguration,
180
            `User data sub does not match sub in id_token, resetting`
181
          );
182
          this.resetUserDataInStore(currentConfiguration, allConfigs);
1✔
183

184
          return null;
1✔
185
        }
186
      })
187
    );
188
  }
189

190
  private getIdentityUserData(
191
    currentConfiguration: OpenIdConfiguration
192
  ): Observable<any> {
193
    const token =
194
      this.storagePersistenceService.getAccessToken(currentConfiguration);
6✔
195

196
    const authWellKnownEndPoints = this.storagePersistenceService.read(
6✔
197
      'authWellKnownEndPoints',
198
      currentConfiguration
199
    );
200

201
    if (!authWellKnownEndPoints) {
6✔
202
      this.loggerService.logWarning(
1✔
203
        currentConfiguration,
204
        'init check session: authWellKnownEndpoints is undefined'
205
      );
206

207
      return throwError(() => new Error('authWellKnownEndpoints is undefined'));
1✔
208
    }
209

210
    const userInfoEndpoint = authWellKnownEndPoints.userInfoEndpoint;
5✔
211

212
    if (!userInfoEndpoint) {
5✔
213
      this.loggerService.logError(
1✔
214
        currentConfiguration,
215
        'init check session: authWellKnownEndpoints.userinfo_endpoint is undefined; set auto_userinfo = false in config'
216
      );
217

218
      return throwError(
1✔
219
        () => new Error('authWellKnownEndpoints.userinfo_endpoint is undefined')
1✔
220
      );
221
    }
222

223
    return this.oidcDataService
4✔
224
      .get(userInfoEndpoint, currentConfiguration, token)
225
      .pipe(retry(2));
226
  }
227

228
  private validateUserDataSubIdToken(
229
    currentConfiguration: OpenIdConfiguration,
230
    idTokenSub: any,
231
    userDataSub: any
232
  ): boolean {
233
    if (!idTokenSub) {
7✔
234
      return false;
1✔
235
    }
236

237
    if (!userDataSub) {
6✔
238
      return false;
2✔
239
    }
240

241
    if (idTokenSub.toString() !== userDataSub.toString()) {
4✔
242
      this.loggerService.logDebug(
1✔
243
        currentConfiguration,
244
        'validateUserDataSubIdToken failed',
245
        idTokenSub,
246
        userDataSub
247
      );
248

249
      return false;
1✔
250
    }
251

252
    return true;
3✔
253
  }
254

255
  private fireUserDataEvent(
256
    currentConfiguration: OpenIdConfiguration,
257
    allConfigs: OpenIdConfiguration[],
258
    passedUserData: any
259
  ): void {
260
    const userData = this.composeSingleOrMultipleUserDataObject(
14✔
261
      currentConfiguration,
262
      allConfigs,
263
      passedUserData
264
    );
265

266
    this.userDataInternal$.next(userData);
14✔
267

268
    const { configId } = currentConfiguration;
14✔
269

270
    this.eventService.fireEvent(EventTypes.UserDataChanged, {
14✔
271
      configId,
272
      userData: passedUserData,
273
    });
274
  }
275

276
  private composeSingleOrMultipleUserDataObject(
277
    currentConfiguration: OpenIdConfiguration,
278
    allConfigs: OpenIdConfiguration[],
279
    passedUserData: any
280
  ): UserDataResult {
281
    const hasManyConfigs = allConfigs.length > 1;
14✔
282

283
    if (!hasManyConfigs) {
14✔
284
      const { configId } = currentConfiguration;
12✔
285

286
      return this.composeSingleUserDataResult(configId ?? '', passedUserData);
12!
287
    }
288

289
    const allUserData: ConfigUserDataResult[] = allConfigs.map((config) => {
2✔
290
      const currentConfigId = currentConfiguration.configId ?? '';
4!
291
      const configId = config.configId ?? '';
4!
292

293
      if (this.currentConfigIsToUpdate(currentConfigId, config)) {
4✔
294
        return { configId, userData: passedUserData };
2✔
295
      }
296

297
      const alreadySavedUserData =
298
        this.storagePersistenceService.read('userData', config) || null;
2✔
299

300
      return {
2✔
301
        configId,
302
        userData: alreadySavedUserData,
303
      };
304
    });
305

306
    return {
2✔
307
      userData: null,
308
      allUserData,
309
    };
310
  }
311

312
  private composeSingleUserDataResult(
313
    configId: string,
314
    userData: any
315
  ): UserDataResult {
316
    return {
12✔
317
      userData,
318
      allUserData: [{ configId, userData }],
319
    };
320
  }
321

322
  private currentConfigIsToUpdate(
323
    configId: string,
324
    config: OpenIdConfiguration
325
  ): boolean {
326
    return config.configId === configId;
4✔
327
  }
328
}
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