blob: 91ac9815f1693bc5bac2ed02666f5bc1e356cf90 [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;
simond47ef9e2022-09-28 22:24:28 -040064 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -040065
simond47ef9e2022-09-28 22:24:28 -040066 isAuthenticated() {
simon218d3d12022-10-01 17:27:01 -040067 return this._state.authenticated;
simond47ef9e2022-09-28 22:24:28 -040068 }
69
70 getState() {
simon218d3d12022-10-01 17:27:01 -040071 return this._state;
simond47ef9e2022-09-28 22:24:28 -040072 }
73
simon218d3d12022-10-01 17:27:01 -040074 setInitData(data: InitData) {
simond47ef9e2022-09-28 22:24:28 -040075 this.authenticating = false;
simon218d3d12022-10-01 17:27:01 -040076 this._state.initialized = true;
simond47ef9e2022-09-28 22:24:28 -040077 if (data.username) {
simon218d3d12022-10-01 17:27:01 -040078 Object.assign(this._state, {
simond47ef9e2022-09-28 22:24:28 -040079 authenticated: true,
80 setupComplete: true,
81 error: false,
82 user: { username: data.username, type: data.type },
83 });
84 } else {
simon218d3d12022-10-01 17:27:01 -040085 Object.assign(this._state, {
simond47ef9e2022-09-28 22:24:28 -040086 authenticated: false,
simon218d3d12022-10-01 17:27:01 -040087 setupComplete: data.setupComplete ?? true,
simond47ef9e2022-09-28 22:24:28 -040088 error: false,
89 });
Adrien Béraud6ecaa402021-04-06 17:37:25 -040090 }
simond47ef9e2022-09-28 22:24:28 -040091 console.log('Init ended');
92 /*if (this.onAuthChanged)
simon218d3d12022-10-01 17:27:01 -040093 this.onAuthChanged(this._state)*/
simond47ef9e2022-09-28 22:24:28 -040094 }
Adrien Béraude5cad982021-06-07 10:05:50 -040095
simon218d3d12022-10-01 17:27:01 -040096 init(cb: OnAuthChanged) {
simond47ef9e2022-09-28 22:24:28 -040097 this.onAuthChanged = cb;
simon218d3d12022-10-01 17:27:01 -040098 if (this._state.initialized || this.authenticating) return;
simond47ef9e2022-09-28 22:24:28 -040099 /*if (initData) {
Adrien Béraude5cad982021-06-07 10:05:50 -0400100 console.log("Using static initData")
101 this.setInitData(initData)
102 return
103 }*/
simond47ef9e2022-09-28 22:24:28 -0400104 this.authenticating = true;
105 fetch('/auth')
106 .then(async (response) => {
107 this.authenticating = false;
simon218d3d12022-10-01 17:27:01 -0400108 this._state.initialized = true;
simond47ef9e2022-09-28 22:24:28 -0400109 if (response.status === 200) {
110 this.setInitData(await response.json());
111 } else if (response.status === 401) {
112 this.setInitData(await response.json());
Adrien Béraude74741b2021-04-19 13:22:54 -0400113 } else {
simon218d3d12022-10-01 17:27:01 -0400114 this._state.error = true;
115 if (this.onAuthChanged) this.onAuthChanged(this._state);
Adrien Béraude74741b2021-04-19 13:22:54 -0400116 }
simond47ef9e2022-09-28 22:24:28 -0400117 })
118 .catch((e) => {
119 this.authenticating = false;
120 console.log(e);
121 });
122 }
Adrien Béraude74741b2021-04-19 13:22:54 -0400123
simond47ef9e2022-09-28 22:24:28 -0400124 deinit() {
125 console.log('Deinit');
126 this.onAuthChanged = undefined;
127 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400128
simon218d3d12022-10-01 17:27:01 -0400129 async setup(password: string) {
130 if (this.authenticating || this._state.setupComplete) return;
simond47ef9e2022-09-28 22:24:28 -0400131 console.log('Starting setup');
132 this.authenticating = true;
133 const response = await fetch(`/setup`, {
134 method: 'POST',
135 headers: {
136 Accept: 'application/json',
137 'Content-Type': 'application/json',
138 },
139 body: JSON.stringify({ password }),
140 });
141 console.log(response);
142 if (response.ok) {
143 console.log('Success, going home');
144 //navigate('/')
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400145 }
simon80b7b3b2022-09-28 17:50:10 -0400146
simond47ef9e2022-09-28 22:24:28 -0400147 this.authenticating = false;
simon218d3d12022-10-01 17:27:01 -0400148 this._state.setupComplete = true;
149 if (this.onAuthChanged) this.onAuthChanged(this._state);
simond47ef9e2022-09-28 22:24:28 -0400150 return response.ok;
151 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400152
simon218d3d12022-10-01 17:27:01 -0400153 authenticate(username: string, password: string) {
simond47ef9e2022-09-28 22:24:28 -0400154 if (this.authenticating) return;
155 console.log('Starting authentication');
156 this.authenticating = true;
157 fetch(`/auth/local?username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`, {
158 method: 'POST',
159 })
160 .then((response) => {
161 console.log(response);
162 this.authenticating = false;
simon218d3d12022-10-01 17:27:01 -0400163 this._state.authenticated = response.ok && response.status === 200;
164 if (this.onAuthChanged) this.onAuthChanged(this._state);
165 while (true) {
simond47ef9e2022-09-28 22:24:28 -0400166 const task = this.tasks.shift();
simon218d3d12022-10-01 17:27:01 -0400167 if (!task) {
168 break;
169 }
170 if (this._state.authenticated) {
simond47ef9e2022-09-28 22:24:28 -0400171 fetch(task.url, task.init)
172 .then((res) => task.resolve(res))
173 .catch((e) => console.log('Error executing pending task: ' + e));
simon218d3d12022-10-01 17:27:01 -0400174 } else {
175 task.reject(new Error('Authentication failed'));
176 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400177 }
simond47ef9e2022-09-28 22:24:28 -0400178 })
179 .catch((e) => {
180 this.authenticating = false;
181 console.log(e);
182 });
183 }
184
185 disconnect() {
186 console.log('Disconnect');
simon218d3d12022-10-01 17:27:01 -0400187 this._state.authenticated = false;
188 if (this.onAuthChanged) this.onAuthChanged(this._state);
simond47ef9e2022-09-28 22:24:28 -0400189 }
190
simon218d3d12022-10-01 17:27:01 -0400191 fetch(url: string, init?: RequestInit): Promise<Response> {
simond47ef9e2022-09-28 22:24:28 -0400192 console.log(`fetch ${url}`);
simon218d3d12022-10-01 17:27:01 -0400193 if (!this._state.authenticated) {
simond47ef9e2022-09-28 22:24:28 -0400194 if (!init || !init.method || init.method === 'GET') {
simon218d3d12022-10-01 17:27:01 -0400195 return new Promise<Response>((resolve, reject) => this.tasks.push({ url, init, resolve, reject }));
simond47ef9e2022-09-28 22:24:28 -0400196 } else {
simon218d3d12022-10-01 17:27:01 -0400197 return new Promise<Response>((resolve, reject) => reject('Not authenticated'));
simond47ef9e2022-09-28 22:24:28 -0400198 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400199 }
simond47ef9e2022-09-28 22:24:28 -0400200 return fetch(url, init).then((response) => {
201 if (response.status === 401) {
202 this.disconnect();
203 return this.fetch(url, init);
204 }
205 return response;
206 });
207 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400208}
209
simond47ef9e2022-09-28 22:24:28 -0400210export default new AuthManager();