blob: 8ec27f3b3f7cd44f06e95ea32a2f511d8b662473 [file] [log] [blame]
simond47ef9e2022-09-28 22:24:28 -04001'use strict';
Larbi Gharibe9af9732021-03-31 15:08:01 +01002
simond47ef9e2022-09-28 22:24:28 -04003import dotenv from 'dotenv';
4const env = dotenv.config();
Adrien Béraude74741b2021-04-19 13:22:54 -04005
simond47ef9e2022-09-28 22:24:28 -04006import { promises as fs } from 'fs';
7import http from 'http';
8import express, { NextFunction, Response, Request } from 'express';
9import session from 'express-session';
10import { Server, Socket } from 'socket.io';
11import path from 'path';
12import passport from 'passport';
13import { Strategy as LocalStrategy } from 'passport-local';
Adrien Béraude74741b2021-04-19 13:22:54 -040014//import { createRequire } from 'module';
15//const require = createRequire(import.meta.url);
Larbi Gharibe9af9732021-03-31 15:08:01 +010016
Adrien Béraud947e8792021-04-15 18:32:44 -040017//const redis = require('redis-url').connect()
18//const RedisStore = require('connect-redis')(session)
Adrien Béraud6ecaa402021-04-06 17:37:25 -040019/*const passportSocketIo = require('passport.socketio')*/
Larbi Gharibe9af9732021-03-31 15:08:01 +010020
simond47ef9e2022-09-28 22:24:28 -040021import indexRouter from './routes/index.js';
Adrien Béraud6ecaa402021-04-06 17:37:25 -040022
simond47ef9e2022-09-28 22:24:28 -040023import cors from 'cors';
Larbi Gharibe9af9732021-03-31 15:08:01 +010024
simond47ef9e2022-09-28 22:24:28 -040025import JamiRestApi from './routes/jami.js';
26import JamiDaemon from './JamiDaemon.js';
27import Account from './model/Account';
28import { ExtendedError } from 'socket.io/dist/namespace';
idillon8e6c0062022-09-16 13:34:43 -040029// import { sentrySetUp } from './sentry.js'
Adrien Béraude74741b2021-04-19 13:22:54 -040030
simond47ef9e2022-09-28 22:24:28 -040031const configPath = 'jamiServerConfig.json';
Larbi Gharibe9af9732021-03-31 15:08:01 +010032
Adrien Béraud6ecaa402021-04-06 17:37:25 -040033//const sessionStore = new RedisStore({ client: redis })
simond47ef9e2022-09-28 22:24:28 -040034const sessionStore = new session.MemoryStore();
Larbi Gharibe9af9732021-03-31 15:08:01 +010035
simon7a7b4d52022-09-23 02:09:42 -040036interface UserConfig {
37 accounts: string;
38 password?: string;
39 username?: string;
simond47ef9e2022-09-28 22:24:28 -040040 type?: string;
simon7a7b4d52022-09-23 02:09:42 -040041}
42
43interface AppConfig {
simond47ef9e2022-09-28 22:24:28 -040044 users: Record<string, UserConfig>;
45 authMethods: any[];
simon7a7b4d52022-09-23 02:09:42 -040046}
47
48const loadConfig = async (filePath: string): Promise<AppConfig> => {
simond47ef9e2022-09-28 22:24:28 -040049 const config = { users: {}, authMethods: [] };
50 try {
51 return Object.assign(config, JSON.parse((await fs.readFile(filePath)).toString()));
52 } catch (e) {
53 console.log(e);
54 return config;
55 }
56};
Adrien Béraud824a7132021-04-17 17:25:27 -040057
simon7a7b4d52022-09-23 02:09:42 -040058const saveConfig = (filePath: string, config: AppConfig) => {
simond47ef9e2022-09-28 22:24:28 -040059 return fs.writeFile(filePath, JSON.stringify(config));
60};
Adrien Béraude74741b2021-04-19 13:22:54 -040061
Larbi Gharibe9af9732021-03-31 15:08:01 +010062/*
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040063Share sessions between Passport.js and Socket.io
Larbi Gharibe9af9732021-03-31 15:08:01 +010064*/
65
66function logSuccess() {
simond47ef9e2022-09-28 22:24:28 -040067 console.log('passportSocketIo authorized user with Success 😁');
Larbi Gharibe9af9732021-03-31 15:08:01 +010068}
69
70function logFail() {
simond47ef9e2022-09-28 22:24:28 -040071 console.log('passportSocketIo failed to authorized user 👺');
Larbi Gharibe9af9732021-03-31 15:08:01 +010072}
73
74/*
Larbi Gharibe9af9732021-03-31 15:08:01 +010075
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040076tempAccounts holds users accounts while tempting to authenticate them on Jams.
77connectedUsers holds users accounts after they got authenticated by Jams.
Larbi Gharibe9af9732021-03-31 15:08:01 +010078
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040079Users should be removed from connectedUsers when receiving a disconnect
80web socket call
Larbi Gharibe9af9732021-03-31 15:08:01 +010081
82*/
simond47ef9e2022-09-28 22:24:28 -040083const tempAccounts: Record<string, any> = {};
84const connectedUsers: Record<string, any> = {};
Larbi Gharibe9af9732021-03-31 15:08:01 +010085
simon7a7b4d52022-09-23 02:09:42 -040086const createServer = async (appConfig: AppConfig) => {
simond47ef9e2022-09-28 22:24:28 -040087 const node_env = process.env.NODE_ENV || 'development';
88 const app = express();
89 console.log(`Loading server for ${node_env} with config:`);
90 console.log(appConfig);
Larbi Gharibe9af9732021-03-31 15:08:01 +010091
simond47ef9e2022-09-28 22:24:28 -040092 const corsOptions = {
93 origin: 'http://127.0.0.1:3000',
94 };
Adrien Béraud4e287b92021-04-24 16:15:56 -040095
simond47ef9e2022-09-28 22:24:28 -040096 if (node_env === 'development') {
97 const webpack = await import('webpack');
98 const webpackDev = await import('webpack-dev-middleware');
99 const webpackHot = await import('webpack-hot-middleware');
100 const { default: webpackConfig } = await import('jami-web-client/webpack.config.js');
simonc7d52452022-09-23 02:09:42 -0400101
simond47ef9e2022-09-28 22:24:28 -0400102 const compiler = webpack.default(webpackConfig);
103 app.use(
104 webpackDev.default(compiler, {
105 publicPath: webpackConfig.output?.publicPath,
106 })
107 );
108 app.use(webpackHot.default(compiler));
109 }
Larbi Gharibe9af9732021-03-31 15:08:01 +0100110
simond47ef9e2022-09-28 22:24:28 -0400111 /*
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400112 Configuation for Passeport Js
113 */
simond47ef9e2022-09-28 22:24:28 -0400114 app.disable('x-powered-by');
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400115
simond47ef9e2022-09-28 22:24:28 -0400116 const secret_key = process.env.SECRET_KEY_BASE;
simon7a7b4d52022-09-23 02:09:42 -0400117
simond47ef9e2022-09-28 22:24:28 -0400118 if (!secret_key) {
119 throw new Error('SECRET_KEY_BASE undefined');
120 }
121
122 const sessionMiddleware = session({
123 store: sessionStore,
124 resave: false,
125 saveUninitialized: true,
126 cookie: {
127 secure: false, //!development,
128 maxAge: 2419200000,
129 },
130 secret: secret_key,
131 });
132
133 app.use(sessionMiddleware);
134 app.use(passport.initialize());
135 app.use(passport.session());
136 // app.use(app.router)
137 app.use(cors(corsOptions));
138
139 const jami = new JamiDaemon((account: Account, conversation: any, message: any) => {
140 console.log('JamiDaemon onMessage');
141
142 if (conversation.listeners) {
143 Object.values(conversation.listeners).forEach((listener: any) => {
144 listener.socket.emit('newMessage', message);
145 });
simon7a7b4d52022-09-23 02:09:42 -0400146 }
simond47ef9e2022-09-28 22:24:28 -0400147 });
148 const apiRouter = new JamiRestApi(jami).getRouter();
simon7a7b4d52022-09-23 02:09:42 -0400149
simond47ef9e2022-09-28 22:24:28 -0400150 /*
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400151 io.use(passportSocketIo.authorize({
152 key: 'connect.sid',
153 secret: process.env.SECRET_KEY_BASE,
154 store: sessionStore,
155 passport: passport,
156 cookieParser: cookieParser,
157 //success: logSuccess(),
158 // fail: logFail(),
Adrien Béraude74741b2021-04-19 13:22:54 -0400159 }))
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400160 */
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400161
simond47ef9e2022-09-28 22:24:28 -0400162 const isSetupComplete = () => {
163 return 'admin' in appConfig.users;
164 };
165
166 const accountFilter = (filter: string | any[]) => {
167 if (typeof filter === 'string') {
168 if (filter === '*') return undefined;
169 else return (account: Account) => account.getId() === filter;
170 } else if (Array.isArray(filter)) {
171 return (account: Account) => filter.includes(account.getId());
172 } else {
173 throw new Error('Invalid account filter string');
Adrien Béraude74741b2021-04-19 13:22:54 -0400174 }
simond47ef9e2022-09-28 22:24:28 -0400175 };
Adrien Béraude74741b2021-04-19 13:22:54 -0400176
simond47ef9e2022-09-28 22:24:28 -0400177 const user = (id: string, config: UserConfig) => {
178 return {
179 id,
180 config,
181 username: config.username || id,
182 accountFilter: accountFilter(config.accounts),
183 };
184 };
185
186 passport.serializeUser((user: any, done) => {
187 connectedUsers[user.id] = user.config;
188 console.log('=============================SerializeUser called ' + user.id);
189 console.log(user);
190 done(null, user.id);
191 });
192
193 const deserializeUser = (id: string, done: (err: any, user?: Express.User | false | null) => void) => {
194 console.log('=============================DeserializeUser called on: ' + id);
195 const userConfig = connectedUsers[id];
196 console.log(userConfig);
197 if (userConfig) {
198 done(null, user(id, userConfig));
199 } else done(404, null);
200 };
201 passport.deserializeUser(deserializeUser);
202
203 const jamsStrategy = new LocalStrategy(async (username, password, done) => {
204 const accountId = await jami.addAccount({
205 managerUri: 'https://jams.savoirfairelinux.com',
206 managerUsername: username,
207 archivePassword: password,
208 });
209 const id = `jams_${username}`;
210 const userConfig = { username, type: 'jams', accounts: accountId };
211 const newUser = user(id, userConfig);
212 console.log('AccountId: ' + accountId);
213 tempAccounts[accountId] = { done, newUser };
214 });
215 jamsStrategy.name = 'jams';
216
217 const localStrategy = new LocalStrategy((username, password, done) => {
218 console.log('localStrategy: ' + username + ' ' + password);
219
220 const id = username;
221 const userConfig = appConfig.users[username];
222 if (!userConfig) {
223 return done(null, false, { message: 'Incorrect username.' });
Adrien Béraude74741b2021-04-19 13:22:54 -0400224 }
simond47ef9e2022-09-28 22:24:28 -0400225 if (userConfig.password !== password) {
226 return done(null, false, { message: 'Incorrect password.' });
Adrien Béraude74741b2021-04-19 13:22:54 -0400227 }
simond47ef9e2022-09-28 22:24:28 -0400228 userConfig.type = 'local';
Adrien Béraude74741b2021-04-19 13:22:54 -0400229
simond47ef9e2022-09-28 22:24:28 -0400230 done(null, user(id, userConfig));
231 });
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400232
simond47ef9e2022-09-28 22:24:28 -0400233 passport.use(jamsStrategy);
234 passport.use(localStrategy);
235
236 const secured = (req: Request, res: Response, next: NextFunction) => {
237 if (req.user) {
238 return next();
Adrien Béraude74741b2021-04-19 13:22:54 -0400239 }
simond47ef9e2022-09-28 22:24:28 -0400240 res.status(401).end();
241 };
242 const securedRedirect = (req: Request, res: Response, next: NextFunction) => {
243 if (req.user && (req.user as any)?.accountId) {
244 return next();
Adrien Béraude74741b2021-04-19 13:22:54 -0400245 }
simond47ef9e2022-09-28 22:24:28 -0400246 (req.session as any).returnTo = req.originalUrl;
247 res.redirect('/login');
248 };
249
250 app.use(express.json());
251 app.post('/setup', (req, res) => {
252 if (isSetupComplete()) {
253 return res.status(404).end();
Adrien Béraude74741b2021-04-19 13:22:54 -0400254 }
simond47ef9e2022-09-28 22:24:28 -0400255 if (!req.body.password) {
256 return res.status(400).end();
Adrien Béraude5cad982021-06-07 10:05:50 -0400257 }
simond47ef9e2022-09-28 22:24:28 -0400258 console.log(req.body);
259 appConfig.users.admin = {
260 accounts: '*',
261 password: req.body.password,
262 };
263 res.status(200).end();
264 saveConfig(configPath, appConfig);
265 });
266 app.post('/auth/jams', passport.authenticate('jams'), (req, res) => {
267 res.json({ loggedin: true });
268 });
269 app.post('/auth/local', passport.authenticate('local'), (req, res) => {
270 res.json({ loggedin: true, user: (req.user as any)?.id });
271 });
Adrien Béraude5cad982021-06-07 10:05:50 -0400272
simond47ef9e2022-09-28 22:24:28 -0400273 const getState = (req: Request) => {
274 if (req.user) {
275 const user = (req.user || {}) as UserConfig;
276 return { loggedin: true, username: user.username, type: user.type };
277 } else if (isSetupComplete()) {
278 return {};
279 } else {
280 return { setupComplete: false };
281 }
282 };
idillon452e2102022-09-16 13:23:28 -0400283
simond47ef9e2022-09-28 22:24:28 -0400284 // sentrySetUp(app);
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400285
simond47ef9e2022-09-28 22:24:28 -0400286 app.get('/auth', (req, res) => {
287 const state = getState(req);
288 if (req.user) {
289 res.json(state);
290 } else {
291 res.status(401).json(state);
292 }
293 });
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400294
simond47ef9e2022-09-28 22:24:28 -0400295 app.use('/api', secured, apiRouter);
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400296
simond47ef9e2022-09-28 22:24:28 -0400297 app.use('/', indexRouter);
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400298
simond47ef9e2022-09-28 22:24:28 -0400299 /* GET React App */
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400300
simond47ef9e2022-09-28 22:24:28 -0400301 const cwd = process.cwd();
302 app.use(express.static(path.join(cwd, 'client/dist')));
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400303
simond47ef9e2022-09-28 22:24:28 -0400304 app.use((req, res) => {
305 res.render(path.join(cwd, 'client/dist/index.ejs'), {
306 initdata: JSON.stringify(getState(req)),
307 });
308 });
idillon452e2102022-09-16 13:23:28 -0400309
simond47ef9e2022-09-28 22:24:28 -0400310 // @ts-ignore TODO: Fix the typescript error
311 const server = http.Server(app);
Adrien Béraud4e287b92021-04-24 16:15:56 -0400312
simond47ef9e2022-09-28 22:24:28 -0400313 const io = new Server(server, { cors: corsOptions });
314 const wrap = (middleware: any) => (socket: Socket, next: (err?: ExtendedError) => void) =>
315 middleware(socket.request, {}, next);
316 io.use(wrap(sessionMiddleware));
317 io.use(wrap(passport.initialize()));
318 io.use(wrap(passport.session()));
319 io.use((socket, next) => {
320 if ((socket.request as any).user) {
321 next();
322 } else {
323 next(new Error('unauthorized'));
324 }
325 });
326 io.on('connect', (socket) => {
327 console.log(`new connection ${socket.id}`);
328 const session = (socket.request as any).session;
329 console.log(`saving sid ${socket.id} in session ${session.id}`);
330 session.socketId = socket.id;
331 session.save();
Adrien Béraudabba2e52021-04-24 21:39:56 -0400332
simond47ef9e2022-09-28 22:24:28 -0400333 socket.on('conversation', (data) => {
334 console.log('io conversation');
335 console.log(data);
336 if (session.conversation) {
337 console.log(`disconnect from old conversation ${session.conversation.conversationId}`);
338 const conversation = jami.getConversation(session.conversation.accountId, session.conversation.conversationId);
339 delete conversation.listeners[socket.id];
340 }
341 session.conversation = { accountId: data.accountId, conversationId: data.conversationId };
342 const conversation = jami.getConversation(data.accountId, data.conversationId);
343 if (!conversation.listeners) conversation.listeners = {};
344 conversation.listeners[socket.id] = {
345 socket,
346 session,
347 };
348 session.save();
349 });
350 });
Adrien Béraud4e287b92021-04-24 16:15:56 -0400351
simond47ef9e2022-09-28 22:24:28 -0400352 return server;
353};
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400354
Adrien Béraude74741b2021-04-19 13:22:54 -0400355loadConfig(configPath)
simond47ef9e2022-09-28 22:24:28 -0400356 .then(createServer)
357 .then((server) => {
358 server.listen(3000);
359 });