blob: 29c5ffc187b25005395e338e635eb3605465f17e [file] [log] [blame]
simon26e79f72022-10-05 22:16:08 -04001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
18
simon80b7b3b2022-09-28 17:50:10 -040019/* eslint-disable no-undef */
20// TODO: This hides eslint errors for this file. This should be removed once this file is cleaned up.
21
simon20076982022-10-11 15:04:13 -040022import { PromiseExecutor } from 'jami-web-common';
simon218d3d12022-10-01 17:27:01 -040023
simon218d3d12022-10-01 17:27:01 -040024interface AuthManagerState {
25 initialized: boolean;
26 authenticated: boolean;
27 setupComplete: boolean;
28 error: boolean;
29}
30
31interface AuthManagerTask extends PromiseExecutor<Response> {
32 url: string;
33 init?: RequestInit;
34}
35
36interface InitData {
37 loggedin?: true;
38 username?: string;
39 type?: string;
40 setupComplete?: boolean;
41}
42
43type OnAuthChanged = (auth: AuthManagerState) => void;
44
Adrien Béraud6ecaa402021-04-06 17:37:25 -040045class AuthManager {
simon218d3d12022-10-01 17:27:01 -040046 private authenticating: boolean;
47 private readonly _state: AuthManagerState;
48 private tasks: AuthManagerTask[];
49 private onAuthChanged: OnAuthChanged | undefined;
50
simond47ef9e2022-09-28 22:24:28 -040051 constructor() {
52 console.log('AuthManager()');
53 this.authenticating = false;
Adrien Béraude74741b2021-04-19 13:22:54 -040054
simon218d3d12022-10-01 17:27:01 -040055 this._state = {
simond47ef9e2022-09-28 22:24:28 -040056 initialized: false,
57 authenticated: true,
58 setupComplete: true,
59 error: false,
60 };
Adrien Béraude74741b2021-04-19 13:22:54 -040061
simond47ef9e2022-09-28 22:24:28 -040062 this.tasks = [];
63 this.onAuthChanged = undefined;
Adrien Béraude5cad982021-06-07 10:05:50 -040064
simon218d3d12022-10-01 17:27:01 -040065 // @ts-ignore
simond47ef9e2022-09-28 22:24:28 -040066 if (initData) {
67 console.log('Using static initData');
simon218d3d12022-10-01 17:27:01 -040068 // @ts-ignore
simond47ef9e2022-09-28 22:24:28 -040069 this.setInitData(initData);
70 return;
Adrien Béraud6ecaa402021-04-06 17:37:25 -040071 }
simond47ef9e2022-09-28 22:24:28 -040072 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -040073
simond47ef9e2022-09-28 22:24:28 -040074 isAuthenticated() {
simon218d3d12022-10-01 17:27:01 -040075 return this._state.authenticated;
simond47ef9e2022-09-28 22:24:28 -040076 }
77
78 getState() {
simon218d3d12022-10-01 17:27:01 -040079 return this._state;
simond47ef9e2022-09-28 22:24:28 -040080 }
81
simon218d3d12022-10-01 17:27:01 -040082 setInitData(data: InitData) {
simond47ef9e2022-09-28 22:24:28 -040083 this.authenticating = false;
simon218d3d12022-10-01 17:27:01 -040084 this._state.initialized = true;
simond47ef9e2022-09-28 22:24:28 -040085 if (data.username) {
simon218d3d12022-10-01 17:27:01 -040086 Object.assign(this._state, {
simond47ef9e2022-09-28 22:24:28 -040087 authenticated: true,
88 setupComplete: true,
89 error: false,
90 user: { username: data.username, type: data.type },
91 });
92 } else {
simon218d3d12022-10-01 17:27:01 -040093 Object.assign(this._state, {
simond47ef9e2022-09-28 22:24:28 -040094 authenticated: false,
simon218d3d12022-10-01 17:27:01 -040095 setupComplete: data.setupComplete ?? true,
simond47ef9e2022-09-28 22:24:28 -040096 error: false,
97 });
Adrien Béraud6ecaa402021-04-06 17:37:25 -040098 }
simond47ef9e2022-09-28 22:24:28 -040099 console.log('Init ended');
100 /*if (this.onAuthChanged)
simon218d3d12022-10-01 17:27:01 -0400101 this.onAuthChanged(this._state)*/
simond47ef9e2022-09-28 22:24:28 -0400102 }
Adrien Béraude5cad982021-06-07 10:05:50 -0400103
simon218d3d12022-10-01 17:27:01 -0400104 init(cb: OnAuthChanged) {
simond47ef9e2022-09-28 22:24:28 -0400105 this.onAuthChanged = cb;
simon218d3d12022-10-01 17:27:01 -0400106 if (this._state.initialized || this.authenticating) return;
simond47ef9e2022-09-28 22:24:28 -0400107 /*if (initData) {
Adrien Béraude5cad982021-06-07 10:05:50 -0400108 console.log("Using static initData")
109 this.setInitData(initData)
110 return
111 }*/
simond47ef9e2022-09-28 22:24:28 -0400112 this.authenticating = true;
113 fetch('/auth')
114 .then(async (response) => {
115 this.authenticating = false;
simon218d3d12022-10-01 17:27:01 -0400116 this._state.initialized = true;
simond47ef9e2022-09-28 22:24:28 -0400117 if (response.status === 200) {
118 this.setInitData(await response.json());
119 } else if (response.status === 401) {
120 this.setInitData(await response.json());
Adrien Béraude74741b2021-04-19 13:22:54 -0400121 } else {
simon218d3d12022-10-01 17:27:01 -0400122 this._state.error = true;
123 if (this.onAuthChanged) this.onAuthChanged(this._state);
Adrien Béraude74741b2021-04-19 13:22:54 -0400124 }
simond47ef9e2022-09-28 22:24:28 -0400125 })
126 .catch((e) => {
127 this.authenticating = false;
128 console.log(e);
129 });
130 }
Adrien Béraude74741b2021-04-19 13:22:54 -0400131
simond47ef9e2022-09-28 22:24:28 -0400132 deinit() {
133 console.log('Deinit');
134 this.onAuthChanged = undefined;
135 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400136
simon218d3d12022-10-01 17:27:01 -0400137 async setup(password: string) {
138 if (this.authenticating || this._state.setupComplete) return;
simond47ef9e2022-09-28 22:24:28 -0400139 console.log('Starting setup');
140 this.authenticating = true;
141 const response = await fetch(`/setup`, {
142 method: 'POST',
143 headers: {
144 Accept: 'application/json',
145 'Content-Type': 'application/json',
146 },
147 body: JSON.stringify({ password }),
148 });
149 console.log(response);
150 if (response.ok) {
151 console.log('Success, going home');
152 //navigate('/')
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400153 }
simon80b7b3b2022-09-28 17:50:10 -0400154
simond47ef9e2022-09-28 22:24:28 -0400155 this.authenticating = false;
simon218d3d12022-10-01 17:27:01 -0400156 this._state.setupComplete = true;
157 if (this.onAuthChanged) this.onAuthChanged(this._state);
simond47ef9e2022-09-28 22:24:28 -0400158 return response.ok;
159 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400160
simon218d3d12022-10-01 17:27:01 -0400161 authenticate(username: string, password: string) {
simond47ef9e2022-09-28 22:24:28 -0400162 if (this.authenticating) return;
163 console.log('Starting authentication');
164 this.authenticating = true;
165 fetch(`/auth/local?username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`, {
166 method: 'POST',
167 })
168 .then((response) => {
169 console.log(response);
170 this.authenticating = false;
simon218d3d12022-10-01 17:27:01 -0400171 this._state.authenticated = response.ok && response.status === 200;
172 if (this.onAuthChanged) this.onAuthChanged(this._state);
173 while (true) {
simond47ef9e2022-09-28 22:24:28 -0400174 const task = this.tasks.shift();
simon218d3d12022-10-01 17:27:01 -0400175 if (!task) {
176 break;
177 }
178 if (this._state.authenticated) {
simond47ef9e2022-09-28 22:24:28 -0400179 fetch(task.url, task.init)
180 .then((res) => task.resolve(res))
181 .catch((e) => console.log('Error executing pending task: ' + e));
simon218d3d12022-10-01 17:27:01 -0400182 } else {
183 task.reject(new Error('Authentication failed'));
184 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400185 }
simond47ef9e2022-09-28 22:24:28 -0400186 })
187 .catch((e) => {
188 this.authenticating = false;
189 console.log(e);
190 });
191 }
192
193 disconnect() {
194 console.log('Disconnect');
simon218d3d12022-10-01 17:27:01 -0400195 this._state.authenticated = false;
196 if (this.onAuthChanged) this.onAuthChanged(this._state);
simond47ef9e2022-09-28 22:24:28 -0400197 }
198
simon218d3d12022-10-01 17:27:01 -0400199 fetch(url: string, init?: RequestInit): Promise<Response> {
simond47ef9e2022-09-28 22:24:28 -0400200 console.log(`fetch ${url}`);
simon218d3d12022-10-01 17:27:01 -0400201 if (!this._state.authenticated) {
simond47ef9e2022-09-28 22:24:28 -0400202 if (!init || !init.method || init.method === 'GET') {
simon218d3d12022-10-01 17:27:01 -0400203 return new Promise<Response>((resolve, reject) => this.tasks.push({ url, init, resolve, reject }));
simond47ef9e2022-09-28 22:24:28 -0400204 } else {
simon218d3d12022-10-01 17:27:01 -0400205 return new Promise<Response>((resolve, reject) => reject('Not authenticated'));
simond47ef9e2022-09-28 22:24:28 -0400206 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400207 }
simond47ef9e2022-09-28 22:24:28 -0400208 return fetch(url, init).then((response) => {
209 if (response.status === 401) {
210 this.disconnect();
211 return this.fetch(url, init);
212 }
213 return response;
214 });
215 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400216}
217
simond47ef9e2022-09-28 22:24:28 -0400218export default new AuthManager();