user.service.ts 9.16 KB
Newer Older
Lukas Burgey's avatar
Lukas Burgey committed
1
2

import {throwError as observableThrowError,  Observable, BehaviorSubject, of, EMPTY } from 'rxjs';
Lukas Burgey's avatar
Lukas Burgey committed
3
4
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
5
import { map, catchError } from 'rxjs/operators';
Lukas Burgey's avatar
Lukas Burgey committed
6

Lukas Burgey's avatar
Lukas Burgey committed
7
8
9
10
11
12
import { CookieService } from 'ngx-cookie-service';
import { StompConfig, StompRService } from '@stomp/ng2-stompjs';
import { Message } from '@stomp/stompjs';

import { SnackBarService } from './snackbar.service';
import { IdpService } from './idp.service';
Lukas Burgey's avatar
Lukas Burgey committed
13

14
import { User, Update, State, Deployment, SSHKey, NewSSHKey, Group, IdP, Service } from './types/types.module';
Lukas Burgey's avatar
Lukas Burgey committed
15

Lukas Burgey's avatar
Lukas Burgey committed
16
17
18

@Injectable()
export class UserService {
19
20
  private initialized = false;
  private loggedIn: boolean = false;
Lukas Burgey's avatar
Lukas Burgey committed
21

22
  private user: User;
Lukas Burgey's avatar
Lukas Burgey committed
23
  private user$: BehaviorSubject<User>;
Lukas Burgey's avatar
Lukas Burgey committed
24

25
  private sshKeys: SSHKey[] = new Array<SSHKey>();
Lukas Burgey's avatar
Lukas Burgey committed
26
  private sshKeys$ = new BehaviorSubject<SSHKey[]>([]);
Lukas Burgey's avatar
Lukas Burgey committed
27

28
  private deployments: Map<number, Deployment> = new Map([]);
Lukas Burgey's avatar
Lukas Burgey committed
29
  private deployments$ = new BehaviorSubject<Deployment[]>([]);
Lukas Burgey's avatar
Lukas Burgey committed
30

Lukas Burgey's avatar
Lukas Burgey committed
31
  public groupSelector = (user: User) => user ? user.groups : [];
32
  public serviceSelector = (user: User) => user ? user.services ? user.services : [] : [];
Lukas Burgey's avatar
Lukas Burgey committed
33
34

  constructor(
Lukas Burgey's avatar
Lukas Burgey committed
35
36
37
38
    private cookieService: CookieService,
    private http: HttpClient,
    private snackBar: SnackBarService,
    private idpService: IdpService,
Lukas Burgey's avatar
Lukas Burgey committed
39
    private stompService: StompRService,
Lukas Burgey's avatar
Lukas Burgey committed
40
  ) {
Lukas Burgey's avatar
Lukas Burgey committed
41
    this.user$ = new BehaviorSubject(null);
42
    this.connect();
43
44
  }

45
46
  // PRIVATE API
  private connect(): void {
Lukas Burgey's avatar
Lukas Burgey committed
47
48
    this.fetch();

49
50
    this.subscribeUser().subscribe(
      (newUser: User) => {
51
        console.log('user$:', newUser);
Lukas Burgey's avatar
Lukas Burgey committed
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

        if (newUser == undefined || newUser == null) {
          // LOGGED OUT
          // show the logout to the user
          if (this.loggedIn) {
            this.snackBar.open('Logged out');

            // purge all values
            this.stompService.disconnect();
            this.user = undefined;
            this.deployments = new Map([]);
            this.deployments$.next([]);
            this.sshKeys = [];
            this.sshKeys$.next([]);
          }


          this.loggedIn =  false;
        } else {
          // LOGGED IN
          // show the login to the user
          if (!this.loggedIn) {
            this.snackBar.open('Logged in');
          }

77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

          if (newUser.id) {
            this.connectLiveUpdates(newUser.id);
          }
          if (newUser.ssh_keys) {
            this.sshKeys = newUser.ssh_keys;
            this.sshKeys$.next(this.sshKeys);
          }
          if (newUser.deployments) {
            newUser.deployments.forEach(
              (newDep: Deployment) => {
                this.deployments.set(newDep.id,  newDep);
              }
            );
            this.deployments$.next(Array.from(this.deployments.values()));
          }

Lukas Burgey's avatar
Lukas Burgey committed
94
          this.loggedIn = true;
95
96
97
          this.initialized = true;
        }
      },
Lukas Burgey's avatar
Lukas Burgey committed
98
99
      this.handleError(true),
      () => console.log('user$ is complete'),
100
    );
Lukas Burgey's avatar
Lukas Burgey committed
101
102
  }

103
  private connectLiveUpdates(userID: number): void {
Lukas Burgey's avatar
Lukas Burgey committed
104
    // handle with care
105
    let login = userID
Lukas Burgey's avatar
Lukas Burgey committed
106
    let passcode = this.cookieService.get('sessionid');
Lukas Burgey's avatar
Lukas Burgey committed
107

Lukas Burgey's avatar
Lukas Burgey committed
108
109
    const stompConfig: StompConfig = {
      // Which server?
110
      url: 'wss://'+window.location.host+'/ws',
111

Lukas Burgey's avatar
Lukas Burgey committed
112
113
114
115
116
117
      // Headers
      // Typical keys: login, passcode, host
      headers: {
        login: 'webpage-client:' + login,
        passcode: passcode,
      },
Lukas Burgey's avatar
Lukas Burgey committed
118

Lukas Burgey's avatar
Lukas Burgey committed
119
120
121
122
123
124
      // How often to heartbeat?
      // Interval in milliseconds, set to 0 to disable
      heartbeat_in: 0, // Typical value 0 - disabled
      heartbeat_out: 20000, // Typical value 20000 - every 20 seconds
      // Wait in milliseconds before attempting auto reconnect
      // Set to 0 to disable
125
126
      // Typical value 15000 (15 seconds)
      reconnect_delay: 15000,
127

Lukas Burgey's avatar
Lukas Burgey committed
128
129
130
      // Will log diagnostics on console
      debug: false,
    };
Lukas Burgey's avatar
Lukas Burgey committed
131
132
    this.stompService.config = stompConfig;
    this.stompService.initAndConnect();
Lukas Burgey's avatar
Lukas Burgey committed
133

Lukas Burgey's avatar
Lukas Burgey committed
134
    let subscription = this.stompService.subscribe(
135
      '/exchange/users/' + userID.toString()
Lukas Burgey's avatar
Lukas Burgey committed
136
    );
137

Lukas Burgey's avatar
Lukas Burgey committed
138
139
    subscription.subscribe(
      (message: Message) => {
140
141
142
        let update: Update = JSON.parse(message.body);
        console.log('update:', update);

Lukas Burgey's avatar
Lukas Burgey committed
143
144
        if (update.error && update.error != '') {
          this.snackBar.open(update.error);
Lukas Burgey's avatar
Lukas Burgey committed
145
        }
146
147
148

        if (update.deployment) {
          this.updateDeployment(update.deployment);
Lukas Burgey's avatar
Lukas Burgey committed
149
        }
Lukas Burgey's avatar
Lukas Burgey committed
150
      },
151
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
152
    );
Lukas Burgey's avatar
Lukas Burgey committed
153
  }
154

Lukas Burgey's avatar
Lukas Burgey committed
155
156
157
158
159
160
  private updateDeployment(dep: Deployment): void {
    if (dep) {
      this.deployments.set(dep.id, dep);
      this.deployments$.next(Array.from(this.deployments.values()));
    }
  }
Lukas Burgey's avatar
Lukas Burgey committed
161

Lukas Burgey's avatar
Lukas Burgey committed
162
  private updateState(update: State): void {
163
164
165
    if (update) {
      // report an occured error
      if (update.error) {
166
        this.snackBar.open(update.error);
167
      }
Lukas Burgey's avatar
Lukas Burgey committed
168

Lukas Burgey's avatar
Lukas Burgey committed
169
170
      this.user = update.user;
      this.user$.next(this.user);
171
172

    } else {
Lukas Burgey's avatar
Lukas Burgey committed
173
      this.user$.next(undefined);
Lukas Burgey's avatar
Lukas Burgey committed
174
175
176
    }
  }

177
  private fetch(): void {
Lukas Burgey's avatar
Lukas Burgey committed
178
    this.http.get<State>('/backend/api/state').subscribe(
179
      (state: State) => this.updateState(state),
Lukas Burgey's avatar
Lukas Burgey committed
180
      this.handleError(false, 'Error fetching state. Try again later'),
Lukas Burgey's avatar
Lukas Burgey committed
181
182
183
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
184
185
186
187
188
189
  private handleError(fetch: boolean, msg?: string) {
    return (error: any) => {
      if (error.status == 403) {
        this.login();
        return
      }
190

Lukas Burgey's avatar
Lukas Burgey committed
191
192
193
194
195
196
197
      if (msg) {
        this.snackBar.open(msg);
      }
      console.log('fetch:', error);
      if (fetch) {
        this.fetch();
      }
198
199
    }
  }
Lukas Burgey's avatar
Lukas Burgey committed
200

Lukas Burgey's avatar
Lukas Burgey committed
201
202
203
  private catchError(fetch: boolean, msg?: string) {
    return (error: any) => {
      this.handleError(fetch, msg)(error);
204
      return EMPTY;
Lukas Burgey's avatar
Lukas Burgey committed
205
    }
Lukas Burgey's avatar
Lukas Burgey committed
206
207
  }

Lukas Burgey's avatar
Lukas Burgey committed
208
  private logErrorAndFetch(error: any)  {
209
210
    if (error.status === 403) {
      this.login();
Lukas Burgey's avatar
Lukas Burgey committed
211
    }
212
213
214
215

    console.log(error);
    this.snackBar.open('Error');
    this.fetch();
Lukas Burgey's avatar
Lukas Burgey committed
216
    return observableThrowError(error);
Lukas Burgey's avatar
Lukas Burgey committed
217
218
  }

Lukas Burgey's avatar
Lukas Burgey committed
219
220
221
222
223
224
225
226
  // PUBLIC API
  public serviceDescription(service: Service): string {
    if (service.description != "") {
      return service.description;
    }
    return "No description";
  }

227
228
229
230
  public login(idp?: IdP): void {
    if (idp) {
      this.idpService.setIdPPreference(idp);
    }
231

232
    window.location.href = '/backend/auth/v1/request';
233
234
  }

Lukas Burgey's avatar
Lukas Burgey committed
235
  public logout(): void {
Lukas Burgey's avatar
Lukas Burgey committed
236
    this.http.post<State>('/backend/auth/v1/logout', undefined).subscribe(
237
      (state: State) => this.updateState(state),
Lukas Burgey's avatar
Lukas Burgey committed
238
239
240
      (err: HttpErrorResponse) => {
        this.updateState(undefined);
      }
241
242
243
    );
  }

244
  public sentQuestionnaire(stateItemID: number, answers: Object) {
Lukas Burgey's avatar
Lukas Burgey committed
245
246
    return this.http.post<Deployment>('/backend/api/questionnaire?id='+String(stateItemID), answers).subscribe(
      (dep: Deployment) => this.updateDeployment(dep),
247
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
248
249
250
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
251
  public deleteUser() {
Lukas Burgey's avatar
Lukas Burgey committed
252
253
254
    return this.http.delete('/backend/api/delete_user').subscribe(
      (data: {deleted: boolean}) => {
        if (data && data.deleted) {
Lukas Burgey's avatar
Lukas Burgey committed
255
          this.user$.next(undefined);
Lukas Burgey's avatar
Lukas Burgey committed
256
257
          this.snackBar.open('Deleted user from server');
        }
Lukas Burgey's avatar
Lukas Burgey committed
258
      },
259
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
260
261
    );
  }
Lukas Burgey's avatar
Lukas Burgey committed
262

Lukas Burgey's avatar
Lukas Burgey committed
263
264
  public uploadSshKey(formData: FormData): void {
    this.http.post<SSHKey>('/backend/api/sshkey', formData).subscribe(
265
      (newKey: SSHKey) => {
Lukas Burgey's avatar
Lukas Burgey committed
266
267
        this.sshKeys.push(newKey);
        this.sshKeys$.next(this.sshKeys);
268
      },
269
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
270
271
272
    );
  }

273
  public removeSshKey(key: SSHKey) {
Lukas Burgey's avatar
Lukas Burgey committed
274
    console.log('Deleting key:', key);
Lukas Burgey's avatar
Lukas Burgey committed
275
276
277
278
279
280
281
282
283
284
285
    return this.http.post('/backend/api/sshkey', {
      'type': 'remove',
      'id': key.id,
    }).subscribe(
      (data: {deleted: boolean}) => {
        if (data && data.deleted) {
          this.sshKeys = this.sshKeys.filter(
            k => key.id != k.id
          );
          this.sshKeys$.next(this.sshKeys);
        }
286
      },
287
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
288
289
    );
  }
290

291
  public changeDeployment(action: string, group: Group): Observable<Deployment> {
292
293
294
295
    const body = {
      'type': action,
      'group': group.id,
    };
296
297
    return this.http.post<Deployment>('/backend/api/deployments', body).pipe(
      catchError(this.catchError(true, "Error changing deployment")),
298
299
300
301
302
    );
  }

  // DATA SERVICE API
  //
Lukas Burgey's avatar
Lukas Burgey committed
303
  public subscribeSpecific<T>(selector: (user: User) => T): Observable<T> {
304
    return this.subscribeUser().pipe(map(selector));
305
306
  }

Lukas Burgey's avatar
Lukas Burgey committed
307
308
  public subscribeSSHKeys(): Observable<SSHKey[]> {
    return this.sshKeys$.asObservable();
309
310
  }

311
312
  public subscribeUser(): Observable<User> {
    return this.user$.asObservable();
313
314
  }

315
316
  public subscribeDeployments(): Observable<Deployment[]> {
    return this.deployments$.asObservable();
317
318
  }

Lukas Burgey's avatar
Lukas Burgey committed
319
320
321
  public subscribeGroups(): Observable<Group[]> {
    let groupSelector = (user: User) => user ? user.groups : [];
    return this.subscribeSpecific<Group[]>(groupSelector);
322
323
  }

324
  public subscribeServiceDeployment(service: Service): Observable<Deployment> {
325
    return this.subscribeDeployments().pipe(
Lukas Burgey's avatar
Lukas Burgey committed
326
      map((deployments: Deployment[]) => {
327
328
          return deployments.find(
            (dep: Deployment) => dep.service ? dep.service.id == service.id : false
329
330
          );
        }
331
      ),
332
333
334
    );
  }

335
  public subscribeGroupDeployment(group: Group): Observable<Deployment> {
336
337
338
339
340
341
342
    return this.subscribeDeployments().pipe(
      map((deployments: Deployment[]) => {
          return deployments.find(
            (dep: Deployment) => dep.group == group.id
          );
        }
      ),
343
344
    );
  }
Lukas Burgey's avatar
Lukas Burgey committed
345
}