Ethora Community

Community › Developers (platform specific questions) › Backend, API, Admin Dashboard

Troubleshooting your dedicated Ethora server: admin and superadmin users

Archived from the Ethora community forum. First posted on 13 April 2026; product details may have changed since. For current setup see Get Ethora and GitHub.

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 uptime

Create 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)
})
EOF

Downgrade 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()
    });
  "

Have a question about this? Ask the Ethora team.