3.9 KiB
3.9 KiB
UniFi MongoDB Admin Schema (v10.0.162)
Database: ace
admin Collection — Admin Accounts
| Field | Type | Required | Description |
|---|---|---|---|
_id |
ObjectId | auto | MongoDB ID |
name |
string | yes | Display name |
email |
string | yes | Login email (username) |
x_shadow |
string | yes | SHA-512 crypt password hash ($6$salt$hash) |
time_created |
int32 (epoch) | yes | Account creation timestamp |
last_site_name |
string | no | Last site accessed (set on login) |
super_site_permissions |
array | yes | Must be ["super"] for super admin |
super_site_role |
string | yes | Must be "super" |
is_super |
bool | yes | Must be true |
last_login_timestamp |
int64 (epoch ms) | auto | Set on login |
last_login_ip |
string | auto | Set on login |
ui_settings |
object | auto | {"preferredLanguage":"en"} etc. |
ui_version |
string | auto | UI version string |
email_alert_enabled |
bool | no | Email notifications |
is_owner |
bool | auto | Set to true on first admin |
Critical: Without super_site_permissions: ["super"], super_site_role: "super", and is_super: true, the admin will log in but see an empty dashboard even if the database has devices and configs.
privilege Collection — Site Access Control
| Field | Type | Required | Description |
|---|---|---|---|
_id |
ObjectId | auto | MongoDB ID |
admin_id |
ObjectId | yes | References admin._id — MUST be ObjectId, NOT string |
site_id |
ObjectId | yes | References site._id — MUST be ObjectId, NOT string |
role |
string | yes | "SUPER_ADMIN" or "ADMIN" |
permissions |
array | yes | ["ALL"] for full access |
is_super |
bool | yes | true for super admin |
Critical pitfall: Using .str on ObjectIds when inserting privileges (e.g., admin._id.str instead of admin._id) creates string references that don't match. The admin logs in but sees NO data — exactly the "no content on screen" symptom.
site Collection
| Field | Type | Description |
|---|---|---|
_id |
ObjectId | Site ID |
name |
string | "super" or "default" |
desc |
string | Description |
attr_hidden_id |
string | Internal site key |
attr_hidden |
bool | true for "super" site |
attr_no_delete |
bool | true for both default sites |
A fresh jacobalberty/unifi deploy creates two sites automatically: "super" (hidden) and "default".
Full Admin Creation Script
db = db.getSiblingDB("ace");
// 1. Generate hash externally: openssl passwd -6 'password'
var hash = "$6$salt$hash";
var now = Math.floor(new Date().getTime() / 1000);
// 2. Create admin
var result = db.admin.insertOne({
name: "Admin Name",
email: "admin@example.com",
x_shadow: hash,
time_created: now,
last_site_name: "default",
super_site_permissions: ["super"],
super_site_role: "super",
is_super: true
});
// 3. Create privileges for ALL sites
var admin = db.admin.findOne({email: "admin@example.com"});
var sites = db.site.find({}).toArray();
sites.forEach(function(site) {
db.privilege.insertOne({
admin_id: admin._id, // ObjectId, NOT .str
site_id: site._id, // ObjectId, NOT .str
role: "SUPER_ADMIN",
permissions: ["ALL"],
is_super: true
});
});
print("Admin created: " + admin._id);
print("Privileges: " + db.privilege.count());
Verifying Permissions After Login
# Login
curl -sk -c /tmp/uf-cookie -X POST https://localhost:8443/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin@example.com","password":"password"}'
# Check self — should show is_super:true
curl -sk -b /tmp/uf-cookie https://localhost:8443/api/self | python3 -m json.tool
If is_super is false or super_site_permissions is [], the MongoDB update didn't take — likely the admin document was overwritten by the login process or the update used the wrong query.