blob: 6f6cf6b74888ab32e56fa902728ea7b49a27bd00 [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
simon07b4eb02022-09-29 17:50:26 -04006import cors from 'cors';
7import express, { NextFunction, Request, Response } from 'express';
8import session from 'express-session';
simond47ef9e2022-09-28 22:24:28 -04009import { promises as fs } from 'fs';
10import http from 'http';
simond47ef9e2022-09-28 22:24:28 -040011import passport from 'passport';
12import { Strategy as LocalStrategy } from 'passport-local';
simon07b4eb02022-09-29 17:50:26 -040013import path from 'path';
14import { Server, Socket } from 'socket.io';
15import { ExtendedError } from 'socket.io/dist/namespace';
16
17import JamiDaemon from './JamiDaemon.js';
18import Account from './model/Account';
Adrien Béraude74741b2021-04-19 13:22:54 -040019//import { createRequire } from 'module';
20//const require = createRequire(import.meta.url);
Adrien Béraud947e8792021-04-15 18:32:44 -040021//const redis = require('redis-url').connect()
22//const RedisStore = require('connect-redis')(session)
Adrien Béraud6ecaa402021-04-06 17:37:25 -040023/*const passportSocketIo = require('passport.socketio')*/
simond47ef9e2022-09-28 22:24:28 -040024import indexRouter from './routes/index.js';
simond47ef9e2022-09-28 22:24:28 -040025import JamiRestApi from './routes/jami.js';
idillon8e6c0062022-09-16 13:34:43 -040026// import { sentrySetUp } from './sentry.js'
Adrien Béraude74741b2021-04-19 13:22:54 -040027
simond47ef9e2022-09-28 22:24:28 -040028const configPath = 'jamiServerConfig.json';
Larbi Gharibe9af9732021-03-31 15:08:01 +010029
Adrien Béraud6ecaa402021-04-06 17:37:25 -040030//const sessionStore = new RedisStore({ client: redis })
simond47ef9e2022-09-28 22:24:28 -040031const sessionStore = new session.MemoryStore();
Larbi Gharibe9af9732021-03-31 15:08:01 +010032
simon7a7b4d52022-09-23 02:09:42 -040033interface UserConfig {
34 accounts: string;
35 password?: string;
36 username?: string;
simond47ef9e2022-09-28 22:24:28 -040037 type?: string;
simon7a7b4d52022-09-23 02:09:42 -040038}
39
40interface AppConfig {
simond47ef9e2022-09-28 22:24:28 -040041 users: Record<string, UserConfig>;
42 authMethods: any[];
simon7a7b4d52022-09-23 02:09:42 -040043}
44
45const loadConfig = async (filePath: string): Promise<AppConfig> => {
simond47ef9e2022-09-28 22:24:28 -040046 const config = { users: {}, authMethods: [] };
47 try {
48 return Object.assign(config, JSON.parse((await fs.readFile(filePath)).toString()));
49 } catch (e) {
50 console.log(e);
51 return config;
52 }
53};
Adrien Béraud824a7132021-04-17 17:25:27 -040054
simon7a7b4d52022-09-23 02:09:42 -040055const saveConfig = (filePath: string, config: AppConfig) => {
simond47ef9e2022-09-28 22:24:28 -040056 return fs.writeFile(filePath, JSON.stringify(config));
57};
Adrien Béraude74741b2021-04-19 13:22:54 -040058
Larbi Gharibe9af9732021-03-31 15:08:01 +010059/*
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040060Share sessions between Passport.js and Socket.io
Larbi Gharibe9af9732021-03-31 15:08:01 +010061*/
62
63function logSuccess() {
simond47ef9e2022-09-28 22:24:28 -040064 console.log('passportSocketIo authorized user with Success 😁');
Larbi Gharibe9af9732021-03-31 15:08:01 +010065}
66
67function logFail() {
simond47ef9e2022-09-28 22:24:28 -040068 console.log('passportSocketIo failed to authorized user 👺');
Larbi Gharibe9af9732021-03-31 15:08:01 +010069}
70
71/*
Larbi Gharibe9af9732021-03-31 15:08:01 +010072
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040073tempAccounts holds users accounts while tempting to authenticate them on Jams.
74connectedUsers holds users accounts after they got authenticated by Jams.
Larbi Gharibe9af9732021-03-31 15:08:01 +010075
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040076Users should be removed from connectedUsers when receiving a disconnect
77web socket call
Larbi Gharibe9af9732021-03-31 15:08:01 +010078
79*/
simond47ef9e2022-09-28 22:24:28 -040080const tempAccounts: Record<string, any> = {};
81const connectedUsers: Record<string, any> = {};
Larbi Gharibe9af9732021-03-31 15:08:01 +010082
simon7a7b4d52022-09-23 02:09:42 -040083const createServer = async (appConfig: AppConfig) => {
simond47ef9e2022-09-28 22:24:28 -040084 const node_env = process.env.NODE_ENV || 'development';
85 const app = express();
86 console.log(`Loading server for ${node_env} with config:`);
87 console.log(appConfig);
Larbi Gharibe9af9732021-03-31 15:08:01 +010088
simond47ef9e2022-09-28 22:24:28 -040089 const corsOptions = {
90 origin: 'http://127.0.0.1:3000',
91 };
Adrien Béraud4e287b92021-04-24 16:15:56 -040092
simond47ef9e2022-09-28 22:24:28 -040093 if (node_env === 'development') {
94 const webpack = await import('webpack');
95 const webpackDev = await import('webpack-dev-middleware');
96 const webpackHot = await import('webpack-hot-middleware');
97 const { default: webpackConfig } = await import('jami-web-client/webpack.config.js');
simonc7d52452022-09-23 02:09:42 -040098
simond47ef9e2022-09-28 22:24:28 -040099 const compiler = webpack.default(webpackConfig);
100 app.use(
101 webpackDev.default(compiler, {
102 publicPath: webpackConfig.output?.publicPath,
103 })
104 );
105 app.use(webpackHot.default(compiler));
106 }
Larbi Gharibe9af9732021-03-31 15:08:01 +0100107
simond47ef9e2022-09-28 22:24:28 -0400108 /*
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400109 Configuation for Passeport Js
110 */
simond47ef9e2022-09-28 22:24:28 -0400111 app.disable('x-powered-by');
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400112
simond47ef9e2022-09-28 22:24:28 -0400113 const secret_key = process.env.SECRET_KEY_BASE;
simon7a7b4d52022-09-23 02:09:42 -0400114
simond47ef9e2022-09-28 22:24:28 -0400115 if (!secret_key) {
116 throw new Error('SECRET_KEY_BASE undefined');
117 }
118
119 const sessionMiddleware = session({
120 store: sessionStore,
121 resave: false,
122 saveUninitialized: true,
123 cookie: {
124 secure: false, //!development,
125 maxAge: 2419200000,
126 },
127 secret: secret_key,
128 });
129
130 app.use(sessionMiddleware);
131 app.use(passport.initialize());
132 app.use(passport.session());
133 // app.use(app.router)
134 app.use(cors(corsOptions));
135
136 const jami = new JamiDaemon((account: Account, conversation: any, message: any) => {
137 console.log('JamiDaemon onMessage');
138
139 if (conversation.listeners) {
140 Object.values(conversation.listeners).forEach((listener: any) => {
141 listener.socket.emit('newMessage', message);
142 });
simon7a7b4d52022-09-23 02:09:42 -0400143 }
simond47ef9e2022-09-28 22:24:28 -0400144 });
145 const apiRouter = new JamiRestApi(jami).getRouter();
simon7a7b4d52022-09-23 02:09:42 -0400146
simond47ef9e2022-09-28 22:24:28 -0400147 /*
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400148 io.use(passportSocketIo.authorize({
149 key: 'connect.sid',
150 secret: process.env.SECRET_KEY_BASE,
151 store: sessionStore,
152 passport: passport,
153 cookieParser: cookieParser,
154 //success: logSuccess(),
155 // fail: logFail(),
Adrien Béraude74741b2021-04-19 13:22:54 -0400156 }))
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400157 */
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400158
simond47ef9e2022-09-28 22:24:28 -0400159 const isSetupComplete = () => {
160 return 'admin' in appConfig.users;
161 };
162
163 const accountFilter = (filter: string | any[]) => {
164 if (typeof filter === 'string') {
165 if (filter === '*') return undefined;
166 else return (account: Account) => account.getId() === filter;
167 } else if (Array.isArray(filter)) {
168 return (account: Account) => filter.includes(account.getId());
169 } else {
170 throw new Error('Invalid account filter string');
Adrien Béraude74741b2021-04-19 13:22:54 -0400171 }
simond47ef9e2022-09-28 22:24:28 -0400172 };
Adrien Béraude74741b2021-04-19 13:22:54 -0400173
simond47ef9e2022-09-28 22:24:28 -0400174 const user = (id: string, config: UserConfig) => {
175 return {
176 id,
177 config,
178 username: config.username || id,
179 accountFilter: accountFilter(config.accounts),
180 };
181 };
182
183 passport.serializeUser((user: any, done) => {
184 connectedUsers[user.id] = user.config;
185 console.log('=============================SerializeUser called ' + user.id);
186 console.log(user);
187 done(null, user.id);
188 });
189
190 const deserializeUser = (id: string, done: (err: any, user?: Express.User | false | null) => void) => {
191 console.log('=============================DeserializeUser called on: ' + id);
192 const userConfig = connectedUsers[id];
193 console.log(userConfig);
194 if (userConfig) {
195 done(null, user(id, userConfig));
196 } else done(404, null);
197 };
198 passport.deserializeUser(deserializeUser);
199
200 const jamsStrategy = new LocalStrategy(async (username, password, done) => {
201 const accountId = await jami.addAccount({
202 managerUri: 'https://jams.savoirfairelinux.com',
203 managerUsername: username,
204 archivePassword: password,
205 });
206 const id = `jams_${username}`;
207 const userConfig = { username, type: 'jams', accounts: accountId };
208 const newUser = user(id, userConfig);
209 console.log('AccountId: ' + accountId);
210 tempAccounts[accountId] = { done, newUser };
211 });
212 jamsStrategy.name = 'jams';
213
214 const localStrategy = new LocalStrategy((username, password, done) => {
215 console.log('localStrategy: ' + username + ' ' + password);
216
217 const id = username;
218 const userConfig = appConfig.users[username];
219 if (!userConfig) {
220 return done(null, false, { message: 'Incorrect username.' });
Adrien Béraude74741b2021-04-19 13:22:54 -0400221 }
simond47ef9e2022-09-28 22:24:28 -0400222 if (userConfig.password !== password) {
223 return done(null, false, { message: 'Incorrect password.' });
Adrien Béraude74741b2021-04-19 13:22:54 -0400224 }
simond47ef9e2022-09-28 22:24:28 -0400225 userConfig.type = 'local';
Adrien Béraude74741b2021-04-19 13:22:54 -0400226
simond47ef9e2022-09-28 22:24:28 -0400227 done(null, user(id, userConfig));
228 });
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400229
simond47ef9e2022-09-28 22:24:28 -0400230 passport.use(jamsStrategy);
231 passport.use(localStrategy);
232
233 const secured = (req: Request, res: Response, next: NextFunction) => {
234 if (req.user) {
235 return next();
Adrien Béraude74741b2021-04-19 13:22:54 -0400236 }
simond47ef9e2022-09-28 22:24:28 -0400237 res.status(401).end();
238 };
239 const securedRedirect = (req: Request, res: Response, next: NextFunction) => {
240 if (req.user && (req.user as any)?.accountId) {
241 return next();
Adrien Béraude74741b2021-04-19 13:22:54 -0400242 }
simond47ef9e2022-09-28 22:24:28 -0400243 (req.session as any).returnTo = req.originalUrl;
244 res.redirect('/login');
245 };
246
247 app.use(express.json());
248 app.post('/setup', (req, res) => {
249 if (isSetupComplete()) {
250 return res.status(404).end();
Adrien Béraude74741b2021-04-19 13:22:54 -0400251 }
simond47ef9e2022-09-28 22:24:28 -0400252 if (!req.body.password) {
253 return res.status(400).end();
Adrien Béraude5cad982021-06-07 10:05:50 -0400254 }
simond47ef9e2022-09-28 22:24:28 -0400255 console.log(req.body);
256 appConfig.users.admin = {
257 accounts: '*',
258 password: req.body.password,
259 };
260 res.status(200).end();
261 saveConfig(configPath, appConfig);
262 });
263 app.post('/auth/jams', passport.authenticate('jams'), (req, res) => {
264 res.json({ loggedin: true });
265 });
266 app.post('/auth/local', passport.authenticate('local'), (req, res) => {
267 res.json({ loggedin: true, user: (req.user as any)?.id });
268 });
Adrien Béraude5cad982021-06-07 10:05:50 -0400269
simond47ef9e2022-09-28 22:24:28 -0400270 const getState = (req: Request) => {
271 if (req.user) {
272 const user = (req.user || {}) as UserConfig;
273 return { loggedin: true, username: user.username, type: user.type };
274 } else if (isSetupComplete()) {
275 return {};
276 } else {
277 return { setupComplete: false };
278 }
279 };
idillon452e2102022-09-16 13:23:28 -0400280
simond47ef9e2022-09-28 22:24:28 -0400281 // sentrySetUp(app);
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400282
simond47ef9e2022-09-28 22:24:28 -0400283 app.get('/auth', (req, res) => {
284 const state = getState(req);
285 if (req.user) {
286 res.json(state);
287 } else {
288 res.status(401).json(state);
289 }
290 });
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400291
simond47ef9e2022-09-28 22:24:28 -0400292 app.use('/api', secured, apiRouter);
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400293
simond47ef9e2022-09-28 22:24:28 -0400294 app.use('/', indexRouter);
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400295
simond47ef9e2022-09-28 22:24:28 -0400296 /* GET React App */
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400297
simond47ef9e2022-09-28 22:24:28 -0400298 const cwd = process.cwd();
299 app.use(express.static(path.join(cwd, 'client/dist')));
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400300
simond47ef9e2022-09-28 22:24:28 -0400301 app.use((req, res) => {
302 res.render(path.join(cwd, 'client/dist/index.ejs'), {
303 initdata: JSON.stringify(getState(req)),
304 });
305 });
idillon452e2102022-09-16 13:23:28 -0400306
simond47ef9e2022-09-28 22:24:28 -0400307 // @ts-ignore TODO: Fix the typescript error
308 const server = http.Server(app);
Adrien Béraud4e287b92021-04-24 16:15:56 -0400309
simond47ef9e2022-09-28 22:24:28 -0400310 const io = new Server(server, { cors: corsOptions });
311 const wrap = (middleware: any) => (socket: Socket, next: (err?: ExtendedError) => void) =>
312 middleware(socket.request, {}, next);
313 io.use(wrap(sessionMiddleware));
314 io.use(wrap(passport.initialize()));
315 io.use(wrap(passport.session()));
316 io.use((socket, next) => {
317 if ((socket.request as any).user) {
318 next();
319 } else {
320 next(new Error('unauthorized'));
321 }
322 });
323 io.on('connect', (socket) => {
324 console.log(`new connection ${socket.id}`);
325 const session = (socket.request as any).session;
326 console.log(`saving sid ${socket.id} in session ${session.id}`);
327 session.socketId = socket.id;
328 session.save();
Adrien Béraudabba2e52021-04-24 21:39:56 -0400329
simond47ef9e2022-09-28 22:24:28 -0400330 socket.on('conversation', (data) => {
331 console.log('io conversation');
332 console.log(data);
333 if (session.conversation) {
334 console.log(`disconnect from old conversation ${session.conversation.conversationId}`);
335 const conversation = jami.getConversation(session.conversation.accountId, session.conversation.conversationId);
336 delete conversation.listeners[socket.id];
337 }
338 session.conversation = { accountId: data.accountId, conversationId: data.conversationId };
339 const conversation = jami.getConversation(data.accountId, data.conversationId);
340 if (!conversation.listeners) conversation.listeners = {};
341 conversation.listeners[socket.id] = {
342 socket,
343 session,
344 };
345 session.save();
346 });
347 });
Adrien Béraud4e287b92021-04-24 16:15:56 -0400348
simond47ef9e2022-09-28 22:24:28 -0400349 return server;
350};
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400351
Adrien Béraude74741b2021-04-19 13:22:54 -0400352loadConfig(configPath)
simond47ef9e2022-09-28 22:24:28 -0400353 .then(createServer)
354 .then((server) => {
355 server.listen(3000);
356 });