const bcrypt = require( 'bcrypt' ); const fs = require( 'fs-extra' ); const path = require( 'path' ); const glob = require( 'glob' ); const Mustache = require( 'mustache' ); const execSync = require( 'child_process' ) .execSync; const dnsSync = require( 'dns-sync' ); const jwt = require( 'jwt-simple' ); const moment = require( 'moment' ); const UUID = require( 'uuid' ); const Pagans = require( './Pagans.js' ); const conf = require( `${process.env.dirtown}/conf.json`); const Checkjson = require( `./Checkjson.js`); /* tribeid manager /tribes/tribeid Manage a tribeid space * create * update by managing option and contract * delete a tribeid * check accountability and */ const Tribes = {}; Tribes.init = () => { console.group( 'init Tribes' ); let tribeids = []; let routes = glob.sync( './routes/*.js' ) .map( f => { return { url: `/${path.basename(f,'.js')}`, route: f } } ); let DOMs = []; let appname = {}; TribesGlobalConfig = glob.sync( `${conf.tribes}/**/clientconf.json` ) .map( f => { const conf = fs.readJSONSync( f ); // check if plugins exist and add it in .plugins of each tribeid conf conf.plugins = glob.sync( `${conf.tribes}/${conf.tribeid}/plugins/**/package.json` ) .map( p => { const pack = fs.readJsonSync( p, 'utf8' ); routes.push( { url: `/${pack.name}`, route: `${conf.tribes}/${conf.tribeid}/plugins/${pack.name}/route.js` } ); return pack; } ); //Add here any other info to get a global view and init //... tribeids.push( conf.tribeid ); DOMs = [ ...new Set( [ ...DOMs, ...conf.allowedDOMs ] ) ]; if( conf.website ) appname[ conf.tribeid ] = Object.keys( conf.website ) return conf; } ); // store global conf fofs.existsSync( `${conf.tmp}/clientconfglob.json` )r sharing to other api fs.outputJsonSync( `${conf.tmp}/clientconfglob.json`, TribesGlobalConfig, { spaces: 2 } ); return { tribeids, routes, DOMs, appname } } Tribes.create = ( data ) => { /* data = clientconf.json { "tribeid": "apxtrib", "genericpsw": "Trze3aze!", "website": { "presentation":"https://www.apxtrib.org", "webapp": "https://webapp.apxtrib.org" }, "allowedDOMs": ["local.fr", "localhost:9002", "ndda.fr", "apxtrib.org"], "clientname": "apxtrib", "clientlogo": "", "geoloc": [], "useradmin": {PUBKEY:"",EMAIL:"",LOGIN:"adminapxtrib",UUID:"adminapxtrib"}, "smtp": { "emailFrom": "support@apxtrib.org", "emailcc": [], "service": "gmail", "auth": { "user": "antonin.ha@gmail.com", "pass": "Ha06110" } }, "accepted-language": "fr,en", "langueReferential": ["fr"] } What about: "tribeid": same than the folder where all the client's file are stored "genericpsw": a generic password for new user need upper lowercase number ans special char "dnsname": a domain name belonging to the client "subdns": "www", a sub domain subdns.dnsname give a public web access to "website": { keywebsite:url}, give access to conf.tribes/tribeid/www/keywebsite/index.html, "allowedDOMs": ["local.fr", "localhost:9002", "nnda.fr"], //for CORS, @TODO generate from prévious URL this allow this apxtrib instance to be accessible "clientname": Name of the organisation if any, "clientlogo": logo of the organisation if any, "geoloc": [], if any "useradmin": { this is the 1st user create automaticaly to make gui available for the 1st user "PUBKEY":public key to be authentify without an email, "EMAIL":user email, we need at least one of authentification set up after the user can use both or only one "LOGIN": login to use for access admintribeid, "UUID": unique id normaly UUID but a uuid admintribeid is the same person in any apxtrib instance so we use it by convention. "xlang": lang used by this user }, "smtp": { smtp used to send email by nodemailer lib basic example with a google account "emailFrom": "support@xx.fr", "emailcc": [], "service": "gmail", "auth": { "user": "antonin.ha@gmail.com", "pass": "Ha06110" } }, "accepted-language": "fr,en", list of accepted-language in terme of http request. "langueReferential": ["fr"], list of the text that have to be translate in referentials } */ //update tmp/confglog.json const dataclient = Tribes.init(); //return in prod all instance apxinfo={tribeids:[],logins:[]} // in dev return only local //check tribeid name is unique console.log( 'liste des tribeid', dataclient.tribeids ) if( dataclient.tribeids.includes( data.tribeid ) ) { return { status: 403, payload: { model: "client", info: [ 'tribeidalreadyexist' ] } } } //loginsglob = {login:tribeid} let loginsglob = {}; if( fs.existsSync( `${conf.tmp}/loginsglob.json`, 'utf-8' ) ) { loginsglob = fs.readJsonSync( `${conf.tmp}/loginsglob.json`, 'utf-8' ); } const logins = Object.keys( loginsglob ); if( logins.includes( data.useradmin.login ) ) { return { status: 403, payload: { model: "client", info: [ 'loginalreadyexist' ] } } } fs.ensureDirSync( `${conf.tribes}/${data.tribeid}` ); [ 'users', 'www', 'referentials', 'nationchains' ].forEach( r => { fs.copySync( `${__dirapi}/setup/tribes/apxtrib/${r}`, `${conf.tribes}/${data.tribeid}/${r}` ); } ) fs.outputJsonSync( `${conf.tribes}/${data.tribeid}/clientconf.json`, data ); const confcli = JSON.parse( Mustache.render( fs.readFileSync( `${__dirapi}/setup/tribes/apxtrib/clientconf.mustache`, 'utf8' ), data ) ); fs.outputJsonSync( `${conf.tribes}/${data.tribeid}/clientconf.json`, confcli ); return Pagans.createUser( { xpaganid: "setup", xworkon: data.tribeid, xlang: data.useradmin.xlang }, data.useradmin ); }; Tribes.archive = ( tribeid ) => { //A faire zip un repertoire tribeid dans // remove tribeid de data ou devdata try { fs.moveSync( `${conf.tribes}/${tribeid}`, `${conf.archivefolder}/${tribeid}` ); //update apxtribenv Tribes.init(); return { status: 200, payload: { info: [ 'deletetribeidsuccessfull' ], models: 'Tribes', moreinfo: "TODO see in Tribes.archive" } } } catch ( err ) { console.log( "Erreur d'archivage", err ) return { status: 403, payload: { info: [ 'archiveerror' ], models: 'Tribes', moreinfo: err } } } } ////////////// Manage file for Tribes Tribes.checkaccessfolder = ( folder, typeaccessrequested, useraccessrights, useruuid ) => { // check folder right } Tribes.checkaccessfiles = ( listfile, typeaccessrequested, useraccessrights, useruuid ) => { // @listfile to check accessright on file or folder // @typeaccessrequested on files R read or download, U for pdate, D for delete , O for owned a Owner has all rights RUD on its files // @useraccessright from its account /userd/uuid.json // @useruuid public uuid user // return {'ok':[file auhtorized],'ko':[files not authorized]} const checkauthlistfile = { 'ok': [], 'ko': [] } let structf = [] let inforep = { file: {}, dir: {} } let done; for( const f of listfile ) { done = false; if( !fs.existsSync( `${conf.tribes}/${f}` ) ) { done = true; checkauthlistfile.ko.push( f ) console.log( `${f} file does not exist` ) } else { structf = f.split( '/' ); } //on ckeck tribeid existe / tribeid/object/ if( !done && useraccessrights.data[ structf[ 0 ] ] && useraccessrights.data[ structf[ 0 ] ][ structf[ 1 ] ] && useraccessrights.data[ structf[ 0 ] ][ structf[ 1 ] ].includes( typeaccessrequested ) ) { done = true; checkauthlistfile.ok.push( f ); } else { // check if in folder we have a.info.json .file[f].shared{useruuid:'CRUDO'} console.log( 'structf', structf ) if( fs.existsSync( `${conf.tribes}/${structf.slice(0,-1).join('/')}/.info.json` ) ) { inforep = fs.readJsonSync( `${conf.tribes}/${structf.slice(0,-1).join('/')}/.info.json`, 'utf8' ) } console.log( `no accessrights for ${f} for ${useruuid} ` ) } if( !done && inforep.file[ f ] && inforep.file[ f ] && inforep.file[ f ].shared && inforep.file[ f ].shared[ useruuid ] && inforep.file[ f ].shared[ useruuid ].includes( typeaccessrequested ) ) { done = true; checkauthlistfile.ok.push( f ) } // If no authorization then ko if( !done ) { checkauthlistfile.ko.push( f ) } } // end loop for //console.log( 'checkauthlistfile', checkauthlistfile ) return checkauthlistfile; } Tribes.dirls = ( tribeid, dir ) => { /* Return list of file into tribeid/dir */ let comment = { src: `${tribeid}/${dir}`, file: {}, dir: {} }; if( fs.existsSync( `${conf.tribes}/${tribeid}/${dir}/.info.json` ) ) { comment = fs.readJsonSync( `${conf.tribes}/${tribeid}/${dir}/.info.json`, 'utf-8' ); } const listfile = [] const listdir = [] glob.sync( `${conf.tribes}/${tribeid}/${dir}/*` ) .forEach( f => { //console.log( f ) const stats = fs.statSync( f ); // console.log( stats ) if( stats.isFile() ) { listfile.push( path.basename( f ) ) if( !comment.file[ path.basename( f ) ] ) { comment.file[ path.basename( f ) ] = { tags: [], info: "", thumbb64: "" }; } comment.file[ path.basename( f ) ].mtime = stats.mtime; comment.file[ path.basename( f ) ].ctime = stats.ctime; comment.file[ path.basename( f ) ].size = stats.size; } if( stats.isDirectory() ) { listdir.push( path.basename( f ) ) if( !comment.dir[ path.basename( f ) ] ) { comment.dir[ path.basename( f ) ] = { tags: [], info: "", thumbb64: "" } } comment.dir[ path.basename( f ) ].nbfile = glob.sync( `${f}/*.*` ) .length; comment.dir[ path.basename( f ) ].mtime = stats.mtime; comment.dir[ path.basename( f ) ].ctime = stats.mtime; console.log( 'comment.dir', comment.dir ) } } ); // on remove les file or dir that was deleted Object.keys( comment.file ) .forEach( f => { if( !listfile.includes( f ) ) delete comment.file[ f ] } ) Object.keys( comment.dir ) .forEach( d => { if( !listdir.includes( d ) ) delete comment.dir[ d ] } ) //console.log( comment ) fs.outputJson( `${conf.tribes}/${tribeid}/${dir}/.info.json`, comment, 'utf-8' ); return { status: 200, payload: { info: [ 'succestogetls' ], models: 'Tribes', moreinfo: comment } } }; Tribes.addspaceweb = ( data ) => { /* To create a public spaceweb accessible from https://dnsname/pageindex input: {dnsname:["archilinea.fr","www.archilinea.fr"], 1st is tha main dns other are just servername redirection tribeid:"archilinea", from req.session.header.xworkon website:"presentation", pageindex:"app_index_fr.html" mode:dev(local no ssl) | prod(IP + ssl) } output: nginx conf and ssl to serve each https://dnsname to /{tribeid}/www/app/{website} Carefull this action is executed with root and restart nginx + apxtrib to work */ data.configdomain = conf.tribes; data.porthttp = conf.porthttp; console.assert( conf.loglevel == "quiet", 'data to create spaceweb:', data ); // create spaceweb app for tribeid/www/app/website/pageindexname.html if( !fs.existsSync( `${conf.tribes}/${data.tribeid}/www/app/${data.website}` ) ) { fs.outputFileSync( `${conf.tribes}/${data.tribeid}/www/app/${data.website}/${data.pageindex}`, `

Hello ${data.tribeid} ${data.website} onto ${data.dnsname.join(',')}`, 'utf-8' ) } //create logs folder fs.ensureDirSync( `${conf.tribes}/${data.tribeid}/logs/nginx` ); // add nginx http config const confnginx = fs.readFileSync( 'setup/nginx/modelwebsite.conf.mustache', 'utf-8' ); fs.outputFileSync( `/etc/nginx/conf.d/${data.dnsname[0]}.conf`, Mustache.render( confnginx, data ), 'utf-8' ); if( data.dns == "unchain" ) { //add in /etc/hosts let hosts = fs.readFileSync( '/etc/hosts', 'utf8' ); let chg = false; data.dnsname.forEach( d => { if( !hosts.includes( `127.0.0.1 ${d}` ) ) { hosts += `\n127.0.0.1 ${d}`; chg = true; } if( chg ) { fs.outputFileSync( '/etc/hosts', hosts, 'utf8' ) } } ); }; //Ckeck dns respond data.dnsname.forEach( d => { if( !dnsSync.resolve( `${d}` ) ) { rep += `\nresolving ${d} will not responding valid IP, please setup domain redirection IP before runing this script` } } ) //update clienconf.json const clientconf = fs.readJsonSync( `${conf.tribes}/${data.tribeid}/clientconf.json` ); clientconf.website[ data.website ] = data.dnsname[ 0 ]; //merge allowedDOMs in unique concat clientconf.allowedDOMs = [ ...new Set( ...clientconf.allowedDOMs, ...data.dnsname ) ]; fs.outputJsonSync( `${conf.tribes}/${data.tribeid}/clientconf.json`, clientconf, 'utf-8' ); if( !data.setup ) { // in setup apxtrib is not running and it will be start manually at the 1st run // in other case need to plan a restart for CORS setTimeout( Tribes.restartapxtrib, 300000, data.clienId ); } const nginxrestart = execSync( `sudo systemctl restart nginx` ) .toString(); console.log( 'Restart nginx', nginxrestart ) if( data.mode != "unchain" ) { // get ssl certificate ATTENTION il faut ajouter -d devant chaque domain qui redirige vers l'espace web. const certbot = execSync( `sudo certbot --nginx -d ${data.dnsname.join(' -d ')}` ) .toString(); console.log( 'certbot is running A CHECKER POUR UNE VRAIE PROD ????', certbot ) } //sh execution to update change requested return { status: 200, payload: { model: "Tribes", info: [ 'webspacecreated' ], moreinfo: "Space web well created" } }; } Tribes.restartapxtrib = ( tribeid ) => { console.log( 'A restarting was requested 5mn ago from a new spacedev for ' + tribeid ) execSync( 'yarn restartpm2' ); } module.exports = Tribes;