blob: 9e2c313fc602f4aa5b988ab8387fbfc686dacc93 [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);
17import { fileURLToPath } from 'url';
18const __filename = fileURLToPath(import.meta.url);
19const __dirname = path.dirname(__filename);
Larbi Gharibe9af9732021-03-31 15:08:01 +010020
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')*/
Larbi Gharibe9af9732021-03-31 15:08:01 +010024
Adrien Béraude74741b2021-04-19 13:22:54 -040025import indexRouter from './routes/index.js'
Adrien Béraud6ecaa402021-04-06 17:37:25 -040026
Adrien Béraud4e287b92021-04-24 16:15:56 -040027import cors from 'cors'
Larbi Gharibe9af9732021-03-31 15:08:01 +010028
Adrien Béraude74741b2021-04-19 13:22:54 -040029import JamiRestApi from './routes/jami.js'
30import JamiDaemon from './JamiDaemon.js'
idillon8e6c0062022-09-16 13:34:43 -040031// import { sentrySetUp } from './sentry.js'
Adrien Béraude74741b2021-04-19 13:22:54 -040032
33const configPath = 'jamiServerConfig.json'
Larbi Gharibe9af9732021-03-31 15:08:01 +010034
Adrien Béraud6ecaa402021-04-06 17:37:25 -040035//const sessionStore = new RedisStore({ client: redis })
36const sessionStore = new session.MemoryStore()
Larbi Gharibe9af9732021-03-31 15:08:01 +010037
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040038const loadConfig = async (filePath) => {
Adrien Béraude74741b2021-04-19 13:22:54 -040039 const config = {users: {}, authMethods: []}
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040040 try {
Adrien Béraude74741b2021-04-19 13:22:54 -040041 return Object.assign(config, JSON.parse(await fs.readFile(filePath)))
42 } catch(e) {
43 console.log(e)
44 return config
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040045 }
Adrien Béraud824a7132021-04-17 17:25:27 -040046}
47
Adrien Béraude74741b2021-04-19 13:22:54 -040048const saveConfig = (filePath, config) => {
49 return fs.writeFile(filePath, JSON.stringify(config))
50}
51
Larbi Gharibe9af9732021-03-31 15:08:01 +010052/*
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040053Share sessions between Passport.js and Socket.io
Larbi Gharibe9af9732021-03-31 15:08:01 +010054*/
55
56function logSuccess() {
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -040057 console.log('passportSocketIo authorized user with Success 😁')
Larbi Gharibe9af9732021-03-31 15:08:01 +010058}
59
60function logFail() {
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -040061 console.log('passportSocketIo failed to authorized user 👺')
Larbi Gharibe9af9732021-03-31 15:08:01 +010062}
63
64/*
Larbi Gharibe9af9732021-03-31 15:08:01 +010065
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040066tempAccounts holds users accounts while tempting to authenticate them on Jams.
67connectedUsers holds users accounts after they got authenticated by Jams.
Larbi Gharibe9af9732021-03-31 15:08:01 +010068
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040069Users should be removed from connectedUsers when receiving a disconnect
70web socket call
Larbi Gharibe9af9732021-03-31 15:08:01 +010071
72*/
Adrien Béraude74741b2021-04-19 13:22:54 -040073const tempAccounts = {}
74const connectedUsers = {}
Larbi Gharibe9af9732021-03-31 15:08:01 +010075
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040076const createServer = async (appConfig) => {
77 const app = express()
78 console.log(`Loading server for ${app.get('env')} with config:`)
79 console.log(appConfig)
80 const development = app.get('env') === 'development'
Larbi Gharibe9af9732021-03-31 15:08:01 +010081
Adrien Béraud4e287b92021-04-24 16:15:56 -040082 var corsOptions = {
83 origin: 'http://127.0.0.1:3000'
84 }
85
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040086 if (development) {
Adrien Béraude74741b2021-04-19 13:22:54 -040087 const [ webpack, webpackDev, webpackHot, webpackConfig ] = await Promise.all([
88 import('webpack'),
89 import('webpack-dev-middleware'),
90 import('webpack-hot-middleware'),
91 import('./client/webpack.config.js')
92 ])
93 const compiler = webpack.default(webpackConfig.default)
94 app.use(webpackDev.default(compiler, {
95 publicPath: webpackConfig.default.output.publicPath
96 }))
97 app.use(webpackHot.default(compiler))
Larbi Gharibe9af9732021-03-31 15:08:01 +010098 }
Larbi Gharibe9af9732021-03-31 15:08:01 +010099
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400100 /*
101 Configuation for Passeport Js
102 */
Adrien Béraude74741b2021-04-19 13:22:54 -0400103 app.disable('x-powered-by')
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400104
Adrien Béraud4e287b92021-04-24 16:15:56 -0400105 const sessionMiddleware = session({
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400106 store: sessionStore,
107 resave: false,
108 saveUninitialized: true,
109 cookie: {
110 secure: false,//!development,
111 maxAge: 2419200000
112 },
113 secret: process.env.SECRET_KEY_BASE
Adrien Béraud4e287b92021-04-24 16:15:56 -0400114 })
115
116 app.use(sessionMiddleware)
Adrien Béraude74741b2021-04-19 13:22:54 -0400117 app.use(passport.initialize())
118 app.use(passport.session())
119 // app.use(app.router)
Adrien Béraud4e287b92021-04-24 16:15:56 -0400120 app.use(cors(corsOptions))
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400121
Adrien Béraudabba2e52021-04-24 21:39:56 -0400122 const jami = new JamiDaemon((account, conversation, message) => {
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400123 console.log('JamiDaemon onMessage')
Adrien Béraudabba2e52021-04-24 21:39:56 -0400124
125 if (conversation.listeners) {
126 Object.values(conversation.listeners).forEach(listener => {
127 listener.socket.emit('newMessage', message)
128 })
129 }
130 })
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400131 const apiRouter = new JamiRestApi(jami).getRouter()
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400132
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400133 /*
134 io.use(passportSocketIo.authorize({
135 key: 'connect.sid',
136 secret: process.env.SECRET_KEY_BASE,
137 store: sessionStore,
138 passport: passport,
139 cookieParser: cookieParser,
140 //success: logSuccess(),
141 // fail: logFail(),
Adrien Béraude74741b2021-04-19 13:22:54 -0400142 }))
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400143 */
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400144
Adrien Béraude74741b2021-04-19 13:22:54 -0400145 const isSetupComplete = () => {
146 return 'admin' in appConfig.users
147 }
148
149 const accountFilter = filter => {
150 if (typeof filter === 'string') {
151 if (filter === '*')
152 return undefined
153 else
154 return account => account.getId() === filter
155 } else if (Array.isArray(filter)) {
156 return account => filter.includes(account.getId())
157 } else {
158 throw new Error('Invalid account filter string')
159 }
160 }
161
162 const user = (id, config) => {
163 return {
164 id,
165 config,
166 username: config.username || id,
167 accountFilter: accountFilter(config.accounts)
168 }
169 }
170
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400171 passport.serializeUser((user, done) => {
Adrien Béraude74741b2021-04-19 13:22:54 -0400172 connectedUsers[user.id] = user.config
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400173 console.log('=============================SerializeUser called ' + user.id)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400174 console.log(user)
Adrien Béraude74741b2021-04-19 13:22:54 -0400175 done(null, user.id)
176 })
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400177
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400178 const deserializeUser = (id, done) => {
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400179 console.log('=============================DeserializeUser called on: ' + id)
Adrien Béraude74741b2021-04-19 13:22:54 -0400180 const userConfig = connectedUsers[id]
181 console.log(userConfig)
182 if (userConfig) {
183 done(null, user(id, userConfig))
184 } else
185 done(404, null)
186 }
187 passport.deserializeUser(deserializeUser)
Larbi Gharibe9af9732021-03-31 15:08:01 +0100188
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400189 const jamsStrategy = new LocalStrategy(
190 (username, password, done) => {
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400191 const accountId = jami.addAccount({
192 'managerUri': 'https://jams.savoirfairelinux.com',
193 'managerUsername': username,
194 'archivePassword': password
Adrien Béraude74741b2021-04-19 13:22:54 -0400195 })
196 const id = `jams_${username}`
197 const userConfig = { username, type: 'jams', accounts: accountId }
198 const newUser = user(id, userConfig)
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400199 console.log('AccountId: ' + accountId)
Adrien Béraude74741b2021-04-19 13:22:54 -0400200 tempAccounts[accountId] = { done, newUser }
Larbi Gharibe9af9732021-03-31 15:08:01 +0100201
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400202 }
Adrien Béraude74741b2021-04-19 13:22:54 -0400203 )
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400204 jamsStrategy.name = 'jams'
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400205
206 const localStrategy = new LocalStrategy(
207 (username, password, done) => {
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400208 console.log('localStrategy: ' + username + ' ' + password)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400209
Adrien Béraude74741b2021-04-19 13:22:54 -0400210 const id = username
211 const userConfig = appConfig.users[username]
212 if (!userConfig) {
213 return done(null, false, { message: 'Incorrect username.' })
214 }
215 if (userConfig.password !== password) {
216 return done(null, false, { message: 'Incorrect password.' })
217 }
218 userConfig.type = 'local'
219
220 done(null, user(id, userConfig))
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400221 }
Adrien Béraude74741b2021-04-19 13:22:54 -0400222 )
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400223
Adrien Béraude74741b2021-04-19 13:22:54 -0400224 passport.use(jamsStrategy)
225 passport.use(localStrategy)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400226
227 const secured = (req, res, next) => {
Adrien Béraude74741b2021-04-19 13:22:54 -0400228 if (req.user) {
229 return next()
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400230 }
231 res.status(401).end()
Adrien Béraude74741b2021-04-19 13:22:54 -0400232 }
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400233 const securedRedirect = (req, res, next) => {
234 if (req.user && req.user.accountId) {
Adrien Béraude74741b2021-04-19 13:22:54 -0400235 return next()
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400236 }
Adrien Béraude74741b2021-04-19 13:22:54 -0400237 req.session.returnTo = req.originalUrl
238 res.redirect('/login')
239 }
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400240
Adrien Béraude74741b2021-04-19 13:22:54 -0400241 app.use(express.json())
242 app.post('/setup', (req, res) => {
243 if (isSetupComplete()) {
244 return res.status(404).end()
245 }
246 if (!req.body.password) {
247 return res.status(400).end()
248 }
249 console.log(req.body)
250 appConfig.users.admin = {
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400251 'accounts': '*',
Adrien Béraude74741b2021-04-19 13:22:54 -0400252 password: req.body.password
253 }
254 res.status(200).end()
255 saveConfig(configPath, appConfig)
256 })
257 app.post('/auth/jams', passport.authenticate('jams'), (req, res) => {
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400258 res.json({ loggedin: true })
Adrien Béraude74741b2021-04-19 13:22:54 -0400259 })
260 app.post('/auth/local', passport.authenticate('local'), (req, res) => {
261 res.json({ loggedin: true, user: req.user.id })
262 })
Adrien Béraude5cad982021-06-07 10:05:50 -0400263
264 const getState = req => {
Adrien Béraude74741b2021-04-19 13:22:54 -0400265 if (req.user) {
Adrien Béraude5cad982021-06-07 10:05:50 -0400266 return { loggedin: true, username: req.user.username, type: req.user.type }
Adrien Béraude74741b2021-04-19 13:22:54 -0400267 } else if (isSetupComplete()) {
Adrien Béraude5cad982021-06-07 10:05:50 -0400268 return {}
Adrien Béraude74741b2021-04-19 13:22:54 -0400269 } else {
Adrien Béraude5cad982021-06-07 10:05:50 -0400270 return { setupComplete: false }
271 }
272 }
273
idillon8e6c0062022-09-16 13:34:43 -0400274 // sentrySetUp(app);
idillon452e2102022-09-16 13:23:28 -0400275
Adrien Béraude5cad982021-06-07 10:05:50 -0400276 app.get('/auth', (req, res) => {
277 const state = getState(req)
278 if (req.user) {
279 res.json(state)
280 } else {
281 res.status(401).json(state)
Adrien Béraude74741b2021-04-19 13:22:54 -0400282 }
283 })
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400284
Adrien Béraude74741b2021-04-19 13:22:54 -0400285 app.use('/api', secured, apiRouter)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400286
Adrien Béraude74741b2021-04-19 13:22:54 -0400287 app.use('/', indexRouter)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400288
289 /* GET React App */
290
291 app.use(express.static(path.join(__dirname, 'client', 'dist')))
292
293 app.use((req, res, next) => {
Adrien Béraude5cad982021-06-07 10:05:50 -0400294 res.render(path.join(__dirname, 'client', 'dist', 'index.ejs'), {
295 initdata: JSON.stringify(getState(req))
296 })
Adrien Béraude74741b2021-04-19 13:22:54 -0400297 })
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400298
idillon452e2102022-09-16 13:23:28 -0400299
Adrien Béraud4e287b92021-04-24 16:15:56 -0400300 const server = http.Server(app)
301
302 const io = new Server(server, { cors: corsOptions })
303 const wrap = middleware => (socket, next) => middleware(socket.request, {}, next)
304 io.use(wrap(sessionMiddleware))
305 io.use(wrap(passport.initialize()))
306 io.use(wrap(passport.session()))
307 io.use((socket, next) => {
308 if (socket.request.user) {
309 next()
310 } else {
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400311 next(new Error('unauthorized'))
Adrien Béraud4e287b92021-04-24 16:15:56 -0400312 }
313 })
314 io.on('connect', (socket) => {
315 console.log(`new connection ${socket.id}`)
316 const session = socket.request.session
317 console.log(`saving sid ${socket.id} in session ${session.id}`)
318 session.socketId = socket.id
319 session.save()
Adrien Béraudabba2e52021-04-24 21:39:56 -0400320
Adrien Béraud2b3c2cd2022-09-18 14:24:33 -0400321 socket.on('conversation', (data) => {
322 console.log('io conversation')
323 console.log(data)
Adrien Béraudabba2e52021-04-24 21:39:56 -0400324 if (session.conversation) {
325 console.log(`disconnect from old conversation ${session.conversation.conversationId}`)
326 const conversation = jami.getConversation(session.conversation.accountId, session.conversation.conversationId)
327 delete conversation.listeners[socket.id]
328 }
329 session.conversation = { accountId: data.accountId, conversationId: data.conversationId }
330 const conversation = jami.getConversation(data.accountId, data.conversationId)
331 if (!conversation.listeners)
332 conversation.listeners = {}
333 conversation.listeners[socket.id] = {
334 socket, session
335 }
336 session.save()
337 })
Adrien Béraud4e287b92021-04-24 16:15:56 -0400338 })
339
340 return server
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400341}
342
Adrien Béraude74741b2021-04-19 13:22:54 -0400343loadConfig(configPath)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400344 .then(createServer)
345 .then(server => {
Adrien Béraude74741b2021-04-19 13:22:54 -0400346 server.listen(3000)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400347 })