blob: da9e365e826876852ced4c86a7d1634d220df5e2 [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'
31
32const configPath = 'jamiServerConfig.json'
Larbi Gharibe9af9732021-03-31 15:08:01 +010033
Adrien Béraud6ecaa402021-04-06 17:37:25 -040034//const sessionStore = new RedisStore({ client: redis })
35const sessionStore = new session.MemoryStore()
Larbi Gharibe9af9732021-03-31 15:08:01 +010036
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040037const loadConfig = async (filePath) => {
Adrien Béraude74741b2021-04-19 13:22:54 -040038 const config = {users: {}, authMethods: []}
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040039 try {
Adrien Béraude74741b2021-04-19 13:22:54 -040040 return Object.assign(config, JSON.parse(await fs.readFile(filePath)))
41 } catch(e) {
42 console.log(e)
43 return config
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040044 }
Adrien Béraud824a7132021-04-17 17:25:27 -040045}
46
Adrien Béraude74741b2021-04-19 13:22:54 -040047const saveConfig = (filePath, config) => {
48 return fs.writeFile(filePath, JSON.stringify(config))
49}
50
Larbi Gharibe9af9732021-03-31 15:08:01 +010051/*
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040052Share sessions between Passport.js and Socket.io
Larbi Gharibe9af9732021-03-31 15:08:01 +010053*/
54
55function logSuccess() {
Adrien Béraude74741b2021-04-19 13:22:54 -040056 console.log("passportSocketIo authorized user with Success 😁")
Larbi Gharibe9af9732021-03-31 15:08:01 +010057}
58
59function logFail() {
Adrien Béraude74741b2021-04-19 13:22:54 -040060 console.log("passportSocketIo failed to authorized user 👺")
Larbi Gharibe9af9732021-03-31 15:08:01 +010061}
62
63/*
Larbi Gharibe9af9732021-03-31 15:08:01 +010064
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040065tempAccounts holds users accounts while tempting to authenticate them on Jams.
66connectedUsers holds users accounts after they got authenticated by Jams.
Larbi Gharibe9af9732021-03-31 15:08:01 +010067
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040068Users should be removed from connectedUsers when receiving a disconnect
69web socket call
Larbi Gharibe9af9732021-03-31 15:08:01 +010070
71*/
Adrien Béraude74741b2021-04-19 13:22:54 -040072const tempAccounts = {}
73const connectedUsers = {}
Larbi Gharibe9af9732021-03-31 15:08:01 +010074
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040075const createServer = async (appConfig) => {
76 const app = express()
77 console.log(`Loading server for ${app.get('env')} with config:`)
78 console.log(appConfig)
79 const development = app.get('env') === 'development'
Larbi Gharibe9af9732021-03-31 15:08:01 +010080
Adrien Béraud4e287b92021-04-24 16:15:56 -040081 var corsOptions = {
82 origin: 'http://127.0.0.1:3000'
83 }
84
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040085 if (development) {
Adrien Béraude74741b2021-04-19 13:22:54 -040086 const [ webpack, webpackDev, webpackHot, webpackConfig ] = await Promise.all([
87 import('webpack'),
88 import('webpack-dev-middleware'),
89 import('webpack-hot-middleware'),
90 import('./client/webpack.config.js')
91 ])
92 const compiler = webpack.default(webpackConfig.default)
93 app.use(webpackDev.default(compiler, {
94 publicPath: webpackConfig.default.output.publicPath
95 }))
96 app.use(webpackHot.default(compiler))
Larbi Gharibe9af9732021-03-31 15:08:01 +010097 }
Larbi Gharibe9af9732021-03-31 15:08:01 +010098
Adrien Béraud3b5d9a62021-04-17 18:40:27 -040099 /*
100 Configuation for Passeport Js
101 */
Adrien Béraude74741b2021-04-19 13:22:54 -0400102 app.disable('x-powered-by')
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400103
Adrien Béraud4e287b92021-04-24 16:15:56 -0400104 const sessionMiddleware = session({
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400105 store: sessionStore,
106 resave: false,
107 saveUninitialized: true,
108 cookie: {
109 secure: false,//!development,
110 maxAge: 2419200000
111 },
112 secret: process.env.SECRET_KEY_BASE
Adrien Béraud4e287b92021-04-24 16:15:56 -0400113 })
114
115 app.use(sessionMiddleware)
Adrien Béraude74741b2021-04-19 13:22:54 -0400116 app.use(passport.initialize())
117 app.use(passport.session())
118 // app.use(app.router)
Adrien Béraud4e287b92021-04-24 16:15:56 -0400119 app.use(cors(corsOptions))
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400120
Adrien Béraudabba2e52021-04-24 21:39:56 -0400121 const jami = new JamiDaemon((account, conversation, message) => {
122 console.log("JamiDaemon onMessage")
123
124 if (conversation.listeners) {
125 Object.values(conversation.listeners).forEach(listener => {
126 listener.socket.emit('newMessage', message)
127 })
128 }
129 })
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400130 const apiRouter = new JamiRestApi(jami).getRouter()
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400131
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400132 /*
133 io.use(passportSocketIo.authorize({
134 key: 'connect.sid',
135 secret: process.env.SECRET_KEY_BASE,
136 store: sessionStore,
137 passport: passport,
138 cookieParser: cookieParser,
139 //success: logSuccess(),
140 // fail: logFail(),
Adrien Béraude74741b2021-04-19 13:22:54 -0400141 }))
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400142 */
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400143
Adrien Béraude74741b2021-04-19 13:22:54 -0400144 const isSetupComplete = () => {
145 return 'admin' in appConfig.users
146 }
147
148 const accountFilter = filter => {
149 if (typeof filter === 'string') {
150 if (filter === '*')
151 return undefined
152 else
153 return account => account.getId() === filter
154 } else if (Array.isArray(filter)) {
155 return account => filter.includes(account.getId())
156 } else {
157 throw new Error('Invalid account filter string')
158 }
159 }
160
161 const user = (id, config) => {
162 return {
163 id,
164 config,
165 username: config.username || id,
166 accountFilter: accountFilter(config.accounts)
167 }
168 }
169
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400170 passport.serializeUser((user, done) => {
Adrien Béraude74741b2021-04-19 13:22:54 -0400171 connectedUsers[user.id] = user.config
172 console.log("=============================SerializeUser called " + user.id)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400173 console.log(user)
Adrien Béraude74741b2021-04-19 13:22:54 -0400174 done(null, user.id)
175 })
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400176
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400177 const deserializeUser = (id, done) => {
Adrien Béraude74741b2021-04-19 13:22:54 -0400178 console.log("=============================DeserializeUser called on: " + id)
179 const userConfig = connectedUsers[id]
180 console.log(userConfig)
181 if (userConfig) {
182 done(null, user(id, userConfig))
183 } else
184 done(404, null)
185 }
186 passport.deserializeUser(deserializeUser)
Larbi Gharibe9af9732021-03-31 15:08:01 +0100187
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400188 const jamsStrategy = new LocalStrategy(
189 (username, password, done) => {
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400190 const accountId = jami.addAccount({
191 'managerUri': 'https://jams.savoirfairelinux.com',
192 'managerUsername': username,
193 'archivePassword': password
Adrien Béraude74741b2021-04-19 13:22:54 -0400194 })
195 const id = `jams_${username}`
196 const userConfig = { username, type: 'jams', accounts: accountId }
197 const newUser = user(id, userConfig)
198 console.log("AccountId: " + accountId)
199 tempAccounts[accountId] = { done, newUser }
Larbi Gharibe9af9732021-03-31 15:08:01 +0100200
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400201 }
Adrien Béraude74741b2021-04-19 13:22:54 -0400202 )
203 jamsStrategy.name = "jams"
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400204
205 const localStrategy = new LocalStrategy(
206 (username, password, done) => {
Adrien Béraude74741b2021-04-19 13:22:54 -0400207 console.log("localStrategy: " + username + " " + password)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400208
Adrien Béraude74741b2021-04-19 13:22:54 -0400209 const id = username
210 const userConfig = appConfig.users[username]
211 if (!userConfig) {
212 return done(null, false, { message: 'Incorrect username.' })
213 }
214 if (userConfig.password !== password) {
215 return done(null, false, { message: 'Incorrect password.' })
216 }
217 userConfig.type = 'local'
218
219 done(null, user(id, userConfig))
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400220 }
Adrien Béraude74741b2021-04-19 13:22:54 -0400221 )
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400222
Adrien Béraude74741b2021-04-19 13:22:54 -0400223 passport.use(jamsStrategy)
224 passport.use(localStrategy)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400225
226 const secured = (req, res, next) => {
Adrien Béraude74741b2021-04-19 13:22:54 -0400227 if (req.user) {
228 return next()
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400229 }
230 res.status(401).end()
Adrien Béraude74741b2021-04-19 13:22:54 -0400231 }
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400232 const securedRedirect = (req, res, next) => {
233 if (req.user && req.user.accountId) {
Adrien Béraude74741b2021-04-19 13:22:54 -0400234 return next()
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400235 }
Adrien Béraude74741b2021-04-19 13:22:54 -0400236 req.session.returnTo = req.originalUrl
237 res.redirect('/login')
238 }
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400239
Adrien Béraude74741b2021-04-19 13:22:54 -0400240 app.use(express.json())
241 app.post('/setup', (req, res) => {
242 if (isSetupComplete()) {
243 return res.status(404).end()
244 }
245 if (!req.body.password) {
246 return res.status(400).end()
247 }
248 console.log(req.body)
249 appConfig.users.admin = {
250 "accounts": "*",
251 password: req.body.password
252 }
253 res.status(200).end()
254 saveConfig(configPath, appConfig)
255 })
256 app.post('/auth/jams', passport.authenticate('jams'), (req, res) => {
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400257 res.json({ loggedin: true })
Adrien Béraude74741b2021-04-19 13:22:54 -0400258 })
259 app.post('/auth/local', passport.authenticate('local'), (req, res) => {
260 res.json({ loggedin: true, user: req.user.id })
261 })
Adrien Béraude5cad982021-06-07 10:05:50 -0400262
263 const getState = req => {
Adrien Béraude74741b2021-04-19 13:22:54 -0400264 if (req.user) {
Adrien Béraude5cad982021-06-07 10:05:50 -0400265 return { loggedin: true, username: req.user.username, type: req.user.type }
Adrien Béraude74741b2021-04-19 13:22:54 -0400266 } else if (isSetupComplete()) {
Adrien Béraude5cad982021-06-07 10:05:50 -0400267 return {}
Adrien Béraude74741b2021-04-19 13:22:54 -0400268 } else {
Adrien Béraude5cad982021-06-07 10:05:50 -0400269 return { setupComplete: false }
270 }
271 }
272
273 app.get('/auth', (req, res) => {
274 const state = getState(req)
275 if (req.user) {
276 res.json(state)
277 } else {
278 res.status(401).json(state)
Adrien Béraude74741b2021-04-19 13:22:54 -0400279 }
280 })
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400281
Adrien Béraude74741b2021-04-19 13:22:54 -0400282 app.use('/api', secured, apiRouter)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400283
Adrien Béraude74741b2021-04-19 13:22:54 -0400284 app.use('/', indexRouter)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400285
286 /* GET React App */
287
288 app.use(express.static(path.join(__dirname, 'client', 'dist')))
289
290 app.use((req, res, next) => {
Adrien Béraude5cad982021-06-07 10:05:50 -0400291 res.render(path.join(__dirname, 'client', 'dist', 'index.ejs'), {
292 initdata: JSON.stringify(getState(req))
293 })
Adrien Béraude74741b2021-04-19 13:22:54 -0400294 })
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400295
Adrien Béraud4e287b92021-04-24 16:15:56 -0400296 const server = http.Server(app)
297
298 const io = new Server(server, { cors: corsOptions })
299 const wrap = middleware => (socket, next) => middleware(socket.request, {}, next)
300 io.use(wrap(sessionMiddleware))
301 io.use(wrap(passport.initialize()))
302 io.use(wrap(passport.session()))
303 io.use((socket, next) => {
304 if (socket.request.user) {
305 next()
306 } else {
307 next(new Error("unauthorized"))
308 }
309 })
310 io.on('connect', (socket) => {
311 console.log(`new connection ${socket.id}`)
312 const session = socket.request.session
313 console.log(`saving sid ${socket.id} in session ${session.id}`)
314 session.socketId = socket.id
315 session.save()
Adrien Béraudabba2e52021-04-24 21:39:56 -0400316
317 socket.on("conversation", (data) => {
318 console.log(`io conversation`)
319 console.log(data);
320 if (session.conversation) {
321 console.log(`disconnect from old conversation ${session.conversation.conversationId}`)
322 const conversation = jami.getConversation(session.conversation.accountId, session.conversation.conversationId)
323 delete conversation.listeners[socket.id]
324 }
325 session.conversation = { accountId: data.accountId, conversationId: data.conversationId }
326 const conversation = jami.getConversation(data.accountId, data.conversationId)
327 if (!conversation.listeners)
328 conversation.listeners = {}
329 conversation.listeners[socket.id] = {
330 socket, session
331 }
332 session.save()
333 })
Adrien Béraud4e287b92021-04-24 16:15:56 -0400334 })
335
336 return server
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400337}
338
Adrien Béraude74741b2021-04-19 13:22:54 -0400339loadConfig(configPath)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400340 .then(createServer)
341 .then(server => {
Adrien Béraude74741b2021-04-19 13:22:54 -0400342 server.listen(3000)
Adrien Béraud3b5d9a62021-04-17 18:40:27 -0400343 })