blob: eea1786bd31003876b8f9367eed7e3aee8254926 [file] [log] [blame]
Adrien Béraude74741b2021-04-19 13:22:54 -04001'use strict'
Larbi Gharibe9af9732021-03-31 15:08:01 +01002
Adrien Béraude74741b2021-04-19 13:22:54 -04003import dotenv from 'dotenv'
Adrien Béraudab519ff2022-05-03 15:34:48 -04004const env = dotenv.config()
Adrien Béraude74741b2021-04-19 13:22:54 -04005
6import { promises as fs } from 'fs'
7import http from 'http'
8import express from 'express'
9import session from 'express-session'
Adrien Béraud4e287b92021-04-24 16:15:56 -040010import cookieParser from'cookie-parser'
11import { Server } from'socket.io'
Adrien Béraude74741b2021-04-19 13:22:54 -040012import path from 'path'
13import passport from 'passport'
14import { Strategy as LocalStrategy } from 'passport-local'
15//import { createRequire } from 'module';
16//const require = createRequire(import.meta.url);
Larbi Gharibe9af9732021-03-31 15:08:01 +010017
Adrien Béraud947e8792021-04-15 18:32:44 -040018//const redis = require('redis-url').connect()
19//const RedisStore = require('connect-redis')(session)
Adrien Béraud6ecaa402021-04-06 17:37:25 -040020/*const passportSocketIo = require('passport.socketio')*/
Larbi Gharibe9af9732021-03-31 15:08:01 +010021
Adrien Béraude74741b2021-04-19 13:22:54 -040022import indexRouter from './routes/index.js'
Adrien Béraud6ecaa402021-04-06 17:37:25 -040023
Adrien Béraud4e287b92021-04-24 16:15:56 -040024import cors from 'cors'
Larbi Gharibe9af9732021-03-31 15:08:01 +010025
Adrien Béraude74741b2021-04-19 13:22:54 -040026import JamiRestApi from './routes/jami.js'
27import JamiDaemon from './JamiDaemon.js'
idillon8e6c0062022-09-16 13:34:43 -040028// import { sentrySetUp } from './sentry.js'
Adrien Béraude74741b2021-04-19 13:22:54 -040029
30const configPath = 'jamiServerConfig.json'
Larbi Gharibe9af9732021-03-31 15:08:01 +010031
Adrien Béraud6ecaa402021-04-06 17:37:25 -040032//const sessionStore = new RedisStore({ client: redis })
33const sessionStore = new session.MemoryStore()
Larbi Gharibe9af9732021-03-31 15:08:01 +010034
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040035const loadConfig = async (filePath) => {
Adrien Béraude74741b2021-04-19 13:22:54 -040036 const config = {users: {}, authMethods: []}
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040037 try {
simonc7d52452022-09-23 02:09:42 -040038 return Object.assign(config, JSON.parse((await fs.readFile(filePath)).toString()))
Adrien Béraude74741b2021-04-19 13:22:54 -040039 } catch(e) {
40 console.log(e)
41 return config
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040042 }
Adrien Béraud824a7132021-04-17 17:25:27 -040043}
44
Adrien Béraude74741b2021-04-19 13:22:54 -040045const saveConfig = (filePath, config) => {
46 return fs.writeFile(filePath, JSON.stringify(config))
47}
48
Larbi Gharibe9af9732021-03-31 15:08:01 +010049/*
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040050Share sessions between Passport.js and Socket.io
Larbi Gharibe9af9732021-03-31 15:08:01 +010051*/
52
53function logSuccess() {
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -040054 console.log('passportSocketIo authorized user with Success 😁')
Larbi Gharibe9af9732021-03-31 15:08:01 +010055}
56
57function logFail() {
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -040058 console.log('passportSocketIo failed to authorized user 👺')
Larbi Gharibe9af9732021-03-31 15:08:01 +010059}
60
61/*
Larbi Gharibe9af9732021-03-31 15:08:01 +010062
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040063tempAccounts holds users accounts while tempting to authenticate them on Jams.
64connectedUsers holds users accounts after they got authenticated by Jams.
Larbi Gharibe9af9732021-03-31 15:08:01 +010065
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040066Users should be removed from connectedUsers when receiving a disconnect
67web socket call
Larbi Gharibe9af9732021-03-31 15:08:01 +010068
69*/
Adrien Béraude74741b2021-04-19 13:22:54 -040070const tempAccounts = {}
71const connectedUsers = {}
Larbi Gharibe9af9732021-03-31 15:08:01 +010072
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040073const createServer = async (appConfig) => {
simonc7d52452022-09-23 02:09:42 -040074 const node_env = process.env.NODE_ENV || 'development'
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040075 const app = express()
simonc7d52452022-09-23 02:09:42 -040076 console.log(`Loading server for ${node_env} with config:`)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040077 console.log(appConfig)
Larbi Gharibe9af9732021-03-31 15:08:01 +010078
Adrien Béraud4e287b92021-04-24 16:15:56 -040079 var corsOptions = {
80 origin: 'http://127.0.0.1:3000'
81 }
82
simonc7d52452022-09-23 02:09:42 -040083 if (node_env === 'development') {
84 const webpack = await import('webpack')
85 const webpackDev = await import('webpack-dev-middleware')
86 const webpackHot = await import ('webpack-hot-middleware')
87 const {default: webpackConfig} = await import ('jami-web-client/webpack.config.js') as any
88
89 const compiler = webpack.default(webpackConfig)
Adrien Béraude74741b2021-04-19 13:22:54 -040090 app.use(webpackDev.default(compiler, {
simonc7d52452022-09-23 02:09:42 -040091 publicPath: webpackConfig.output.publicPath
Adrien Béraude74741b2021-04-19 13:22:54 -040092 }))
93 app.use(webpackHot.default(compiler))
Larbi Gharibe9af9732021-03-31 15:08:01 +010094 }
Larbi Gharibe9af9732021-03-31 15:08:01 +010095
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040096 /*
97 Configuation for Passeport Js
98 */
Adrien Béraude74741b2021-04-19 13:22:54 -040099 app.disable('x-powered-by')
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400100
Adrien Béraud4e287b92021-04-24 16:15:56 -0400101 const sessionMiddleware = session({
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400102 store: sessionStore,
103 resave: false,
104 saveUninitialized: true,
105 cookie: {
106 secure: false,//!development,
107 maxAge: 2419200000
108 },
109 secret: process.env.SECRET_KEY_BASE
Adrien Béraud4e287b92021-04-24 16:15:56 -0400110 })
111
112 app.use(sessionMiddleware)
Adrien Béraude74741b2021-04-19 13:22:54 -0400113 app.use(passport.initialize())
114 app.use(passport.session())
115 // app.use(app.router)
Adrien Béraud4e287b92021-04-24 16:15:56 -0400116 app.use(cors(corsOptions))
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400117
Adrien Béraudabba2e52021-04-24 21:39:56 -0400118 const jami = new JamiDaemon((account, conversation, message) => {
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400119 console.log('JamiDaemon onMessage')
Adrien Béraudabba2e52021-04-24 21:39:56 -0400120
121 if (conversation.listeners) {
simonc7d52452022-09-23 02:09:42 -0400122 Object.values(conversation.listeners).forEach((listener: any) => {
Adrien Béraudabba2e52021-04-24 21:39:56 -0400123 listener.socket.emit('newMessage', message)
124 })
125 }
126 })
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400127 const apiRouter = new JamiRestApi(jami).getRouter()
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400128
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400129 /*
130 io.use(passportSocketIo.authorize({
131 key: 'connect.sid',
132 secret: process.env.SECRET_KEY_BASE,
133 store: sessionStore,
134 passport: passport,
135 cookieParser: cookieParser,
136 //success: logSuccess(),
137 // fail: logFail(),
Adrien Béraude74741b2021-04-19 13:22:54 -0400138 }))
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400139 */
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400140
Adrien Béraude74741b2021-04-19 13:22:54 -0400141 const isSetupComplete = () => {
142 return 'admin' in appConfig.users
143 }
144
145 const accountFilter = filter => {
146 if (typeof filter === 'string') {
147 if (filter === '*')
148 return undefined
149 else
150 return account => account.getId() === filter
151 } else if (Array.isArray(filter)) {
152 return account => filter.includes(account.getId())
153 } else {
154 throw new Error('Invalid account filter string')
155 }
156 }
157
158 const user = (id, config) => {
159 return {
160 id,
161 config,
162 username: config.username || id,
163 accountFilter: accountFilter(config.accounts)
164 }
165 }
166
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400167 passport.serializeUser((user, done) => {
Adrien Béraude74741b2021-04-19 13:22:54 -0400168 connectedUsers[user.id] = user.config
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400169 console.log('=============================SerializeUser called ' + user.id)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400170 console.log(user)
Adrien Béraude74741b2021-04-19 13:22:54 -0400171 done(null, user.id)
172 })
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400173
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400174 const deserializeUser = (id, done) => {
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400175 console.log('=============================DeserializeUser called on: ' + id)
Adrien Béraude74741b2021-04-19 13:22:54 -0400176 const userConfig = connectedUsers[id]
177 console.log(userConfig)
178 if (userConfig) {
179 done(null, user(id, userConfig))
180 } else
181 done(404, null)
182 }
183 passport.deserializeUser(deserializeUser)
Larbi Gharibe9af9732021-03-31 15:08:01 +0100184
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400185 const jamsStrategy = new LocalStrategy(
simonc7d52452022-09-23 02:09:42 -0400186 async (username, password, done) => {
187 const accountId = await jami.addAccount({
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400188 'managerUri': 'https://jams.savoirfairelinux.com',
189 'managerUsername': username,
190 'archivePassword': password
Adrien Béraude74741b2021-04-19 13:22:54 -0400191 })
192 const id = `jams_${username}`
193 const userConfig = { username, type: 'jams', accounts: accountId }
194 const newUser = user(id, userConfig)
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400195 console.log('AccountId: ' + accountId)
Adrien Béraude74741b2021-04-19 13:22:54 -0400196 tempAccounts[accountId] = { done, newUser }
Larbi Gharibe9af9732021-03-31 15:08:01 +0100197
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400198 }
Adrien Béraude74741b2021-04-19 13:22:54 -0400199 )
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400200 jamsStrategy.name = 'jams'
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400201
202 const localStrategy = new LocalStrategy(
203 (username, password, done) => {
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400204 console.log('localStrategy: ' + username + ' ' + password)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400205
Adrien Béraude74741b2021-04-19 13:22:54 -0400206 const id = username
207 const userConfig = appConfig.users[username]
208 if (!userConfig) {
209 return done(null, false, { message: 'Incorrect username.' })
210 }
211 if (userConfig.password !== password) {
212 return done(null, false, { message: 'Incorrect password.' })
213 }
214 userConfig.type = 'local'
215
216 done(null, user(id, userConfig))
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400217 }
Adrien Béraude74741b2021-04-19 13:22:54 -0400218 )
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400219
Adrien Béraude74741b2021-04-19 13:22:54 -0400220 passport.use(jamsStrategy)
221 passport.use(localStrategy)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400222
223 const secured = (req, res, next) => {
Adrien Béraude74741b2021-04-19 13:22:54 -0400224 if (req.user) {
225 return next()
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400226 }
227 res.status(401).end()
Adrien Béraude74741b2021-04-19 13:22:54 -0400228 }
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400229 const securedRedirect = (req, res, next) => {
230 if (req.user && req.user.accountId) {
Adrien Béraude74741b2021-04-19 13:22:54 -0400231 return next()
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400232 }
Adrien Béraude74741b2021-04-19 13:22:54 -0400233 req.session.returnTo = req.originalUrl
234 res.redirect('/login')
235 }
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400236
Adrien Béraude74741b2021-04-19 13:22:54 -0400237 app.use(express.json())
238 app.post('/setup', (req, res) => {
239 if (isSetupComplete()) {
240 return res.status(404).end()
241 }
242 if (!req.body.password) {
243 return res.status(400).end()
244 }
245 console.log(req.body)
246 appConfig.users.admin = {
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400247 'accounts': '*',
Adrien Béraude74741b2021-04-19 13:22:54 -0400248 password: req.body.password
249 }
250 res.status(200).end()
251 saveConfig(configPath, appConfig)
252 })
253 app.post('/auth/jams', passport.authenticate('jams'), (req, res) => {
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400254 res.json({ loggedin: true })
Adrien Béraude74741b2021-04-19 13:22:54 -0400255 })
256 app.post('/auth/local', passport.authenticate('local'), (req, res) => {
257 res.json({ loggedin: true, user: req.user.id })
258 })
Adrien Béraude5cad982021-06-07 10:05:50 -0400259
260 const getState = req => {
Adrien Béraude74741b2021-04-19 13:22:54 -0400261 if (req.user) {
Adrien Béraude5cad982021-06-07 10:05:50 -0400262 return { loggedin: true, username: req.user.username, type: req.user.type }
Adrien Béraude74741b2021-04-19 13:22:54 -0400263 } else if (isSetupComplete()) {
Adrien Béraude5cad982021-06-07 10:05:50 -0400264 return {}
Adrien Béraude74741b2021-04-19 13:22:54 -0400265 } else {
Adrien Béraude5cad982021-06-07 10:05:50 -0400266 return { setupComplete: false }
267 }
268 }
269
idillon8e6c0062022-09-16 13:34:43 -0400270 // sentrySetUp(app);
idillon452e2102022-09-16 13:23:28 -0400271
Adrien Béraude5cad982021-06-07 10:05:50 -0400272 app.get('/auth', (req, res) => {
273 const state = getState(req)
274 if (req.user) {
275 res.json(state)
276 } else {
277 res.status(401).json(state)
Adrien Béraude74741b2021-04-19 13:22:54 -0400278 }
279 })
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400280
Adrien Béraude74741b2021-04-19 13:22:54 -0400281 app.use('/api', secured, apiRouter)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400282
Adrien Béraude74741b2021-04-19 13:22:54 -0400283 app.use('/', indexRouter)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400284
285 /* GET React App */
286
simonc7d52452022-09-23 02:09:42 -0400287 const cwd = process.cwd()
288 app.use(express.static(path.join(cwd, 'client/dist')));
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400289
simonc7d52452022-09-23 02:09:42 -0400290 app.use((req, res) => {
291 res.render(path.join(cwd, 'client/dist/index.ejs'), {
Adrien Béraude5cad982021-06-07 10:05:50 -0400292 initdata: JSON.stringify(getState(req))
293 })
Adrien Béraude74741b2021-04-19 13:22:54 -0400294 })
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400295
idillon452e2102022-09-16 13:23:28 -0400296
simonc7d52452022-09-23 02:09:42 -0400297 // @ts-ignore TODO: Fix the typescript error
Adrien Béraud4e287b92021-04-24 16:15:56 -0400298 const server = http.Server(app)
299
300 const io = new Server(server, { cors: corsOptions })
301 const wrap = middleware => (socket, next) => middleware(socket.request, {}, next)
302 io.use(wrap(sessionMiddleware))
303 io.use(wrap(passport.initialize()))
304 io.use(wrap(passport.session()))
305 io.use((socket, next) => {
simonc7d52452022-09-23 02:09:42 -0400306 if ((socket.request as any).user) {
Adrien Béraud4e287b92021-04-24 16:15:56 -0400307 next()
308 } else {
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400309 next(new Error('unauthorized'))
Adrien Béraud4e287b92021-04-24 16:15:56 -0400310 }
311 })
312 io.on('connect', (socket) => {
313 console.log(`new connection ${socket.id}`)
simonc7d52452022-09-23 02:09:42 -0400314 const session = (socket.request as any).session
Adrien Béraud4e287b92021-04-24 16:15:56 -0400315 console.log(`saving sid ${socket.id} in session ${session.id}`)
316 session.socketId = socket.id
317 session.save()
Adrien Béraudabba2e52021-04-24 21:39:56 -0400318
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400319 socket.on('conversation', (data) => {
320 console.log('io conversation')
321 console.log(data)
Adrien Béraudabba2e52021-04-24 21:39:56 -0400322 if (session.conversation) {
323 console.log(`disconnect from old conversation ${session.conversation.conversationId}`)
324 const conversation = jami.getConversation(session.conversation.accountId, session.conversation.conversationId)
325 delete conversation.listeners[socket.id]
326 }
327 session.conversation = { accountId: data.accountId, conversationId: data.conversationId }
328 const conversation = jami.getConversation(data.accountId, data.conversationId)
329 if (!conversation.listeners)
330 conversation.listeners = {}
331 conversation.listeners[socket.id] = {
332 socket, session
333 }
334 session.save()
335 })
Adrien Béraud4e287b92021-04-24 16:15:56 -0400336 })
337
338 return server
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400339}
340
Adrien Béraude74741b2021-04-19 13:22:54 -0400341loadConfig(configPath)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400342 .then(createServer)
343 .then(server => {
Adrien Béraude74741b2021-04-19 13:22:54 -0400344 server.listen(3000)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400345 })