user.service.ts 9.16 KB
Newer Older
Lukas Burgey's avatar
Lukas Burgey committed
1
2
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
Lukas Burgey's avatar
Lukas Burgey committed
3

4
5
import { Observable, BehaviorSubject, of, EMPTY } from 'rxjs';
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
216

    console.log(error);
    this.snackBar.open('Error');
    this.fetch();
    return Observable.throw(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
236
  public logout(): void {
    this.http.post<State>('/backend/auth/v1/logout', {}).subscribe(
237
238
      (state: State) => this.updateState(state),
      this.logErrorAndFetch,
239
240
241
    );
  }

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

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

261
  public uploadSshKey(key: NewSSHKey) {
Lukas Burgey's avatar
Lukas Burgey committed
262
    console.log('Uploading key:', key);
Lukas Burgey's avatar
Lukas Burgey committed
263
264
265
266
    const body = {
      'type': 'add',
      'key': key,
    };
Lukas Burgey's avatar
Lukas Burgey committed
267
    return this.http.post<SSHKey>('/backend/api/sshkey', body).subscribe(
268
      (newKey: SSHKey) => {
Lukas Burgey's avatar
Lukas Burgey committed
269
270
        this.sshKeys.push(newKey);
        this.sshKeys$.next(this.sshKeys);
271
      },
272
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
273
274
275
    );
  }

276
  public removeSshKey(key: SSHKey) {
Lukas Burgey's avatar
Lukas Burgey committed
277
    console.log('Deleting key:', key);
Lukas Burgey's avatar
Lukas Burgey committed
278
279
280
281
282
283
284
285
286
287
288
    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);
        }
289
      },
290
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
291
292
    );
  }
293

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

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

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

314
315
  public subscribeUser(): Observable<User> {
    return this.user$.asObservable();
316
317
  }

318
319
  public subscribeDeployments(): Observable<Deployment[]> {
    return this.deployments$.asObservable();
320
321
  }

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

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

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