Find out which accounts are superadmins
cd ~/ethora-install-shared/deploy
set -a
source .deploy.env
set +a
docker-compose -f docker-compose.enterprise.yml exec -T mongo \
mongosh --quiet "$MONGO_DB" --eval 'db.users.find({"isSuperAdmin.read":true},{email:1,appId:1}).limit(20).toArray()'Check if user with your email exists
cd ~/ethora-install-shared/deploy
set -a
source .deploy.env
set +a
docker-compose -f docker-compose.enterprise.yml exec -T mongo \
mongosh --quiet "$MONGO_DB" --eval 'db.users.find({email:"YOUR_EMAIL_HERE"},{email:1,appId:1,isSuperAdmin:1}).toArray()'Promote user to superadmin manually
docker-compose -f docker-compose.enterprise.yml exec -T mongo \
mongosh --quiet "$MONGO_DB" --eval '
db.users.updateOne(
{email:"YOUR_OLD_PROD_EMAIL_HERE"},
{$set:{isSuperAdmin:{read:true,write:true}}}
)
'Update superadmin email and password
cd ~/ethora-install-shared/deploy
set -a
source .deploy.env
set +a
OLD_EMAIL="old-superadmin@example.com"
NEW_EMAIL="new-email@example.com"
NEW_PASSWORD='NewStrongPasswordHere'
HASH=$(
node -e "const bcrypt=require('/home/ubuntu/ethora/ethora-backend/services/api/node_modules/bcryptjs'); bcrypt.hash(process.argv[1], 10).then(h=>console.log(h))" "$NEW_PASSWORD"
)
docker-compose -f docker-compose.enterprise.yml exec -T mongo \
mongosh --quiet "$MONGO_DB" --eval "
const result = db.users.updateOne(
{ email: '$OLD_EMAIL' },
{
\$set: {
email: '$NEW_EMAIL',
password: '$HASH',
tempPassword: null,
resetPasswordToken: null,
resetPasswordExpires: null
}
}
);
printjson(result);
printjson(db.users.find({ email: '$NEW_EMAIL' }, { email: 1, appId: 1, isSuperAdmin: 1 }).toArray());
"Make uptime and deploy-generated admin use the new credentials
cd ~/ethora-install-shared/deploy
yq -i '.admin.email = "'"$NEW_EMAIL"'" | .admin.password = "'"$NEW_PASSWORD"'"' config/deploy.yml
./scripts/setup-env.sh
docker-compose -f docker-compose.uptime.yml up -d --force-recreate uptimeCreate a new superadmin user
cd /home/ubuntu/ethora/ethora-backend/services/api
set -a
source .env
set +a
export NEW_ADMIN_EMAIL="your-new-email@example.com"
export NEW_ADMIN_PASSWORD='YourNewStrongPasswordHere'
node <<'EOF'
const mongoose = require('mongoose')
const crypto = require('crypto')
const bcrypt = require('bcryptjs')
const config = require('./src/config/app')
const User = require('./src/models/user')
const App = require('./src/models/apps')
const AppAcl = require('./src/models/appAcl')
const { registerXmppuser } = require('./src/services/xmpp.service')
;(async () => {
await mongoose.connect(config.database)
const email = String(process.env.NEW_ADMIN_EMAIL || '').trim().toLowerCase()
const password = String(process.env.NEW_ADMIN_PASSWORD || '')
if (!email || !password) throw new Error('NEW_ADMIN_EMAIL and NEW_ADMIN_PASSWORD are required')
const baseApp =
await App.findOne({ isBaseApp: true }) ||
await App.findOne({ domainName: process.env.BASE_APP_DOMAIN_NAME || 'app' })
if (!baseApp) throw new Error('Base app not found')
let user = await User.findOne({ email, appId: String(baseApp._id) })
const xmppPassword =
(user && user.xmppPassword) ||
crypto.randomBytes(12).toString('base64').replace(/[^a-zA-Z0-9]/g, '').slice(0, 16) ||
'AdminPass123'
if (!user) {
user = new User({
email,
password,
firstName: 'Admin',
lastName: 'User',
authMethod: 'email',
xmppPassword,
isProfileOpen: false,
isAssetsOpen: false,
appId: String(baseApp._id),
isSuperAdmin: { read: true, write: true },
})
user.xmppUsername = `${baseApp._id}_${user._id}`
user = await User.createUserAsync(user)
} else {
const hash = await bcrypt.hash(password, 10)
const xmppUsername = `${baseApp._id}_${user._id}`
await User.updateOne(
{ _id: user._id },
{
$set: {
email,
password: hash,
authMethod: 'email',
appId: String(baseApp._id),
xmppUsername,
xmppPassword,
isSuperAdmin: { read: true, write: true },
tempPassword: '',
resetPasswordToken: null,
resetPasswordExpires: null,
},
}
)
user = await User.findById(user._id)
}
await AppAcl.updateOne(
{ userId: String(user._id), appId: String(baseApp._id) },
{
$set: {
userId: String(user._id),
appId: String(baseApp._id),
network: { netStats: { read: true } },
application: {
appCreate: { create: true },
appSettings: { read: true, update: true, admin: true },
appUsers: { create: true, read: true, update: true, delete: true, admin: true },
appTokens: { create: true, read: true, update: true, admin: true },
appPush: { create: true, read: true, update: true, admin: true },
appStats: { read: true, admin: true },
},
},
},
{ upsert: true }
)
try {
await registerXmppuser(user.xmppUsername, user.xmppPassword)
} catch (e) {
console.warn('XMPP registration warning:', e?.message || e)
}
console.log(
JSON.stringify(
{
ok: true,
email: user.email,
userId: String(user._id),
appId: String(baseApp._id),
xmppUsername: user.xmppUsername,
isSuperAdmin: user.isSuperAdmin,
},
null,
2
)
)
await mongoose.disconnect()
})().catch(async (err) => {
console.error(err)
try { await mongoose.disconnect() } catch {}
process.exit(1)
})
EOFDowngrade superadmin to a standard user (w/o ACL)
cd /home/ubuntu/ethora-install-shared/deploy
set -a
source .deploy.env
set +a
TARGET_EMAIL="user-to-downgrade@example.com"
docker-compose -f docker-compose.enterprise.yml exec -T mongo \
mongosh --quiet "$MONGO_DB" --eval "
printjson(
db.users.updateOne(
{ email: '$TARGET_EMAIL' },
{ \$set: { isSuperAdmin: { read: false, write: false } } }
)
);
printjson(db.users.find({ email: '$TARGET_EMAIL' }, { email: 1, isSuperAdmin: 1, appId: 1 }).toArray());
"Strict downgrade supeardmin (includes removing their apps ACL records)
This makes sure they no longer retain app-admin rights through ACLs.
cd ~/ethora-install-shared/deploy
set -a
source .deploy.env
set +a
TARGET_EMAIL="user-to-downgrade@example.com"
docker-compose -f docker-compose.enterprise.yml exec -T mongo \
mongosh --quiet "$MONGO_DB" --eval "
const user = db.users.findOne({ email: '$TARGET_EMAIL' });
if (!user) {
print('USER_NOT_FOUND');
quit(1);
}
print('BEFORE');
printjson({
user: {
_id: user._id,
email: user.email,
appId: user.appId,
isSuperAdmin: user.isSuperAdmin
},
appAcls: db.appacls.find({ userId: String(user._id) }).toArray()
});
db.users.updateOne(
{ _id: user._id },
{
\$set: {
isSuperAdmin: { read: false, write: false }
}
}
);
db.appacls.deleteMany({ userId: String(user._id) });
print('AFTER');
printjson({
user: db.users.findOne(
{ _id: user._id },
{ email: 1, appId: 1, isSuperAdmin: 1 }
),
remainingAppAcls: db.appacls.find({ userId: String(user._id) }).toArray()
});
" [Optional] Reversible backup step before doing the above
If you want a reversible backup step first, run this before the strict downgrade:
cd ~/ethora-install-shared/deploy
set -a
source .deploy.env
set +a
TARGET_EMAIL="user-to-downgrade@example.com"
docker-compose -f docker-compose.enterprise.yml exec -T mongo \
mongosh --quiet "$MONGO_DB" --eval "
const user = db.users.findOne({ email: '$TARGET_EMAIL' });
if (!user) { print('USER_NOT_FOUND'); quit(1); }
printjson({
user,
appAcls: db.appacls.find({ userId: String(user._id) }).toArray(),
owner2apps: db.owner2apps.find({ userId: String(user._id) }).toArray()
});
"