|
| 1 | +import axios from 'axios'; |
| 2 | +import {action, observable, runInAction} from 'mobx'; |
| 3 | +import * as config from './config'; |
| 4 | +import {SnackReporter} from './snack/SnackManager'; |
| 5 | +import {CurrentUser} from './CurrentUser'; |
| 6 | + |
| 7 | +export class ElevateStore { |
| 8 | + @observable accessor elevated = false; |
| 9 | + @observable accessor oidcElevatePending = false; |
| 10 | + private oidcPollIntervalId: number | undefined = undefined; |
| 11 | + private oidcPopup: Window | null = null; |
| 12 | + |
| 13 | + public constructor( |
| 14 | + private readonly snack: SnackReporter, |
| 15 | + private readonly currentUser: CurrentUser |
| 16 | + ) {} |
| 17 | + |
| 18 | + @action |
| 19 | + public refreshElevated = (): number => { |
| 20 | + const elevatedUntil = this.currentUser.user.elevatedUntil; |
| 21 | + if (!elevatedUntil) { |
| 22 | + this.elevated = false; |
| 23 | + return 0; |
| 24 | + } |
| 25 | + const ms = new Date(elevatedUntil).getTime() - 30_000 - Date.now(); |
| 26 | + if (ms <= 0) { |
| 27 | + this.elevated = false; |
| 28 | + return 0; |
| 29 | + } |
| 30 | + this.elevated = true; |
| 31 | + return ms; |
| 32 | + }; |
| 33 | + |
| 34 | + public localElevate = async (password: string, durationSeconds: number): Promise<void> => { |
| 35 | + await axios.create().request({ |
| 36 | + url: config.get('url') + 'client:elevate', |
| 37 | + method: 'POST', |
| 38 | + data: {id: this.currentUser.user.clientId, durationSeconds}, |
| 39 | + headers: { |
| 40 | + Authorization: 'Basic ' + btoa(this.currentUser.user.name + ':' + password), |
| 41 | + }, |
| 42 | + }); |
| 43 | + await this.currentUser.tryAuthenticate(); |
| 44 | + this.cleanupOidcElevate(); |
| 45 | + }; |
| 46 | + |
| 47 | + public oidcElevate = (durationSeconds: number): void => { |
| 48 | + // prevent double execution |
| 49 | + if (this.oidcElevatePending) return; |
| 50 | + |
| 51 | + const url = |
| 52 | + config.get('url') + |
| 53 | + 'auth/oidc/elevate?id=' + |
| 54 | + this.currentUser.user.clientId + |
| 55 | + '&durationSeconds=' + |
| 56 | + durationSeconds; |
| 57 | + |
| 58 | + this.oidcPopup = window.open(url, 'gotify-oidc-elevate', 'width=600,height=700'); |
| 59 | + if (!this.oidcPopup) { |
| 60 | + this.snack('Popup was blocked. Please allow popups for this site and try again.'); |
| 61 | + return; |
| 62 | + } |
| 63 | + |
| 64 | + runInAction(() => (this.oidcElevatePending = true)); |
| 65 | + |
| 66 | + this.oidcPollIntervalId = window.setInterval(this.checkOidcPopup, 500); |
| 67 | + }; |
| 68 | + |
| 69 | + private checkOidcPopup = async () => { |
| 70 | + if (this.oidcPopup && !this.oidcPopup.closed) { |
| 71 | + // waiting for the popup to close. |
| 72 | + return; |
| 73 | + } |
| 74 | + |
| 75 | + window.clearInterval(this.oidcPollIntervalId); |
| 76 | + this.oidcPollIntervalId = undefined; |
| 77 | + |
| 78 | + try { |
| 79 | + await this.currentUser.tryAuthenticate(); |
| 80 | + } catch { |
| 81 | + // errors handled in tryAuthenticate |
| 82 | + } |
| 83 | + |
| 84 | + if (!this.elevated) { |
| 85 | + this.snack('OIDC elevation was not completed.'); |
| 86 | + } |
| 87 | + this.cleanupOidcElevate(); |
| 88 | + }; |
| 89 | + |
| 90 | + public cleanupOidcElevate = () => { |
| 91 | + window.clearInterval(this.oidcPollIntervalId); |
| 92 | + this.oidcPollIntervalId = undefined; |
| 93 | + |
| 94 | + if (this.oidcPopup && !this.oidcPopup.closed) { |
| 95 | + this.oidcPopup.close(); |
| 96 | + } |
| 97 | + this.oidcPopup = null; |
| 98 | + runInAction(() => { |
| 99 | + this.oidcElevatePending = false; |
| 100 | + }); |
| 101 | + }; |
| 102 | +} |
0 commit comments