setup unifi wlans

This commit is contained in:
2026-02-22 19:12:34 -06:00
parent 70284bc14e
commit 3c89f24189
66 changed files with 7393 additions and 110 deletions
+23
View File
@@ -0,0 +1,23 @@
import { createRoute } from "../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../managers/unifiSites";
import { apiResponse } from "../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../middleware/authorization";
/* GET /v1/unifi/site/:id/access-points */
export default createRoute(
"get",
["/site/:id/access-points"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const accessPoints = await unifiSites.getAccessPoints(site.siteId);
const response = apiResponse.successful(
"UniFi Access Points Fetched Successfully!",
accessPoints,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({
permissions: ["unifi.access", "unifi.site.access-points"],
}),
);
+23
View File
@@ -0,0 +1,23 @@
import { createRoute } from "../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../managers/unifiSites";
import { apiResponse } from "../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../middleware/authorization";
/* GET /v1/unifi/site/:id/ap-groups */
export default createRoute(
"get",
["/site/:id/ap-groups"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const apGroups = await unifiSites.getApGroups(site.siteId);
const response = apiResponse.successful(
"UniFi AP Groups Fetched Successfully!",
apGroups,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({
permissions: ["unifi.access", "unifi.site.ap-groups"],
}),
);
+21
View File
@@ -0,0 +1,21 @@
import { createRoute } from "../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../managers/unifiSites";
import { apiResponse } from "../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../middleware/authorization";
/* GET /v1/unifi/site/:id/devices */
export default createRoute(
"get",
["/site/:id/devices"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const devices = await unifiSites.getDevices(site.siteId);
const response = apiResponse.successful(
"UniFi Devices Fetched Successfully!",
devices,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({ permissions: ["unifi.access", "unifi.site.devices"] }),
);
+20
View File
@@ -0,0 +1,20 @@
import { createRoute } from "../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../managers/unifiSites";
import { apiResponse } from "../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../middleware/authorization";
/* GET /v1/unifi/site/:id */
export default createRoute(
"get",
["/site/:id"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const response = apiResponse.successful(
"UniFi Site Fetched Successfully!",
site,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({ permissions: ["unifi.access", "unifi.sites.fetch"] }),
);
+26
View File
@@ -0,0 +1,26 @@
import { createRoute } from "../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../managers/unifiSites";
import { apiResponse } from "../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../middleware/authorization";
import { z } from "zod";
/* POST /v1/unifi/site/:id/link */
export default createRoute(
"post",
["/site/:id/link"],
async (c) => {
const siteId = c.req.param("id");
const body = await c.req.json();
const schema = z.object({ companyId: z.string() }).strict();
const { companyId } = schema.parse(body);
const site = await unifiSites.linkToCompany(siteId, companyId);
const response = apiResponse.successful(
"UniFi Site Linked to Company Successfully!",
site,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({ permissions: ["unifi.access", "unifi.sites.link"] }),
);
+21
View File
@@ -0,0 +1,21 @@
import { createRoute } from "../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../managers/unifiSites";
import { apiResponse } from "../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../middleware/authorization";
/* GET /v1/unifi/site/:id/networks */
export default createRoute(
"get",
["/site/:id/networks"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const networks = await unifiSites.getNetworks(site.siteId);
const response = apiResponse.successful(
"UniFi Networks Fetched Successfully!",
networks,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({ permissions: ["unifi.access", "unifi.site.networks"] }),
);
+21
View File
@@ -0,0 +1,21 @@
import { createRoute } from "../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../managers/unifiSites";
import { apiResponse } from "../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../middleware/authorization";
/* GET /v1/unifi/site/:id/overview */
export default createRoute(
"get",
["/site/:id/overview"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const overview = await unifiSites.getSiteOverview(site.siteId);
const response = apiResponse.successful(
"UniFi Site Overview Fetched Successfully!",
overview,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({ permissions: ["unifi.access", "unifi.site.overview"] }),
);
+40
View File
@@ -0,0 +1,40 @@
import { createRoute } from "../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../managers/unifiSites";
import { apiResponse } from "../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../middleware/authorization";
import { z } from "zod";
/* POST /v1/unifi/site/:id/speed-profiles */
export default createRoute(
"post",
["/site/:id/speed-profiles"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const body = await c.req.json();
const schema = z
.object({
name: z.string(),
downloadLimitKbps: z.number().optional(),
uploadLimitKbps: z.number().optional(),
})
.strict();
const parsed = schema.parse(body);
const profile = await unifiSites.createUserGroup(site.siteId, parsed);
const response = apiResponse.created(
"UniFi Speed Profile Created Successfully!",
profile,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({
permissions: [
"unifi.access",
"unifi.site.speed-profiles",
"unifi.site.speed-profiles.create",
],
}),
);
@@ -0,0 +1,23 @@
import { createRoute } from "../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../managers/unifiSites";
import { apiResponse } from "../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../middleware/authorization";
/* GET /v1/unifi/site/:id/speed-profiles */
export default createRoute(
"get",
["/site/:id/speed-profiles"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const profiles = await unifiSites.getUserGroups(site.siteId);
const response = apiResponse.successful(
"UniFi Speed Profiles Fetched Successfully!",
profiles,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({
permissions: ["unifi.access", "unifi.site.speed-profiles"],
}),
);
+21
View File
@@ -0,0 +1,21 @@
import { createRoute } from "../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../managers/unifiSites";
import { apiResponse } from "../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../middleware/authorization";
/* POST /v1/unifi/site/:id/unlink */
export default createRoute(
"post",
["/site/:id/unlink"],
async (c) => {
const siteId = c.req.param("id");
const site = await unifiSites.unlinkFromCompany(siteId);
const response = apiResponse.successful(
"UniFi Site Unlinked from Company Successfully!",
site,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({ permissions: ["unifi.access", "unifi.sites.link"] }),
);
+31
View File
@@ -0,0 +1,31 @@
import { createRoute } from "../../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../../managers/unifiSites";
import { apiResponse } from "../../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../../middleware/authorization";
import { processObjectValuePerms } from "../../../../modules/permission-utils/processObjectPermissions";
/* GET /v1/unifi/site/:id/wifi */
export default createRoute(
"get",
["/site/:id/wifi"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const wlans = await unifiSites.getWlanConf(site.siteId);
const processWlans = await Promise.all(
wlans.map((wlan) =>
processObjectValuePerms(wlan, "unifi.site.wifi.read", c.get("user")),
),
);
console.log(processWlans);
const response = apiResponse.successful(
"UniFi WiFi Networks Fetched Successfully!",
processWlans,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({ permissions: ["unifi.access", "unifi.site.wifi"] }),
);
+47
View File
@@ -0,0 +1,47 @@
import { createRoute } from "../../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../../managers/unifiSites";
import { apiResponse } from "../../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../../middleware/authorization";
import { z } from "zod";
/* POST /v1/unifi/site/:id/wifi/:wlanId/ppsk */
export default createRoute(
"post",
["/site/:id/wifi/:wlanId/ppsk"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const wlanId = c.req.param("wlanId");
const body = await c.req.json();
const schema = z
.object({
key: z.string().min(8, "PSK must be at least 8 characters"),
name: z.string().min(1, "Name is required"),
mac: z.string().optional(),
vlanId: z.number().optional(),
})
.strict();
const parsed = schema.parse(body);
const ppsks = await unifiSites.createPrivatePSK(
site.siteId,
wlanId,
parsed,
);
const response = apiResponse.created(
"UniFi Private PSK Created Successfully!",
ppsks,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({
permissions: [
"unifi.access",
"unifi.site.wifi",
"unifi.site.wifi.ppsk",
"unifi.site.wifi.ppsk.create",
],
}),
);
+24
View File
@@ -0,0 +1,24 @@
import { createRoute } from "../../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../../managers/unifiSites";
import { apiResponse } from "../../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../../middleware/authorization";
/* GET /v1/unifi/site/:id/wifi/:wlanId/ppsk */
export default createRoute(
"get",
["/site/:id/wifi/:wlanId/ppsk"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const wlanId = c.req.param("wlanId");
const ppsks = await unifiSites.getPrivatePSKs(site.siteId, wlanId);
const response = apiResponse.successful(
"UniFi Private PSKs Fetched Successfully!",
ppsks,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({
permissions: ["unifi.access", "unifi.site.wifi", "unifi.site.wifi.ppsk"],
}),
);
+117
View File
@@ -0,0 +1,117 @@
import { createRoute } from "../../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../../managers/unifiSites";
import { apiResponse } from "../../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../../middleware/authorization";
import { toWlanConfUpdate } from "../../../../modules/unifi-api/unifiTypes";
import { z } from "zod";
/* PATCH /v1/unifi/site/:id/wifi/:wlanId */
export default createRoute(
"patch",
["/site/:id/wifi/:wlanId"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const wlanId = c.req.param("wlanId");
const body = await c.req.json();
const schema = z
.object({
name: z.string().optional(),
passphrase: z.string().optional(),
enabled: z.boolean().optional(),
security: z.enum(["wpapsk", "wpaeap", "open", "osen"]).optional(),
wpaMode: z.enum(["wpa2", "wpa3", "wpa2wpa3"]).optional(),
wpaEnc: z.enum(["ccmp", "gcmp", "ccmp-gcmp"]).optional(),
hideSSID: z.boolean().optional(),
macFilterEnabled: z.boolean().optional(),
macFilterPolicy: z.enum(["allow", "deny"]).optional(),
isGuest: z.boolean().optional(),
l2Isolation: z.boolean().optional(),
fastRoamingEnabled: z.boolean().optional(),
bssTransition: z.boolean().optional(),
uapsdEnabled: z.boolean().optional(),
groupRekey: z.number().optional(),
dtimMode: z.enum(["default", "custom"]).optional(),
dtimNg: z.number().optional(),
dtimNa: z.number().optional(),
minrateNgEnabled: z.boolean().optional(),
minrateNaEnabled: z.boolean().optional(),
radiusDasEnabled: z.boolean().optional(),
radiusMacAuthEnabled: z.boolean().optional(),
pmfMode: z.enum(["disabled", "optional", "required"]).optional(),
band: z.enum(["both", "2g", "5g"]).optional(),
usergroupId: z.string().optional(),
proxyArp: z.boolean().optional(),
mcastenhanceEnabled: z.boolean().optional(),
macFilterList: z.array(z.string()).optional(),
no2ghzOui: z.boolean().optional(),
apGroupIds: z.array(z.string()).optional(),
apGroupMode: z.enum(["all", "groups", "devices"]).optional(),
deviceMacs: z.array(z.string()).optional(),
})
.strict();
const parsed = schema.parse(body);
// If deviceMacs is provided, manage the devices_ap_group:
// - If already in devices mode with a for_wlanconf group, update that group
// - Otherwise create a new for_wlanconf group and switch to devices mode
if (parsed.deviceMacs && parsed.deviceMacs.length > 0) {
const wlans = await unifiSites.getWlanConf(site.siteId);
const currentWlan = wlans.find((w) => w.id === wlanId);
let existingGroupId: string | undefined;
if (
currentWlan?.apGroupMode === "devices" &&
currentWlan.apGroupIds.length > 0
) {
// Check if the current group is a for_wlanconf group we can update
const apGroups = await unifiSites.getApGroups(site.siteId);
const currentGroup = apGroups.find(
(g) => g.id === currentWlan.apGroupIds[0] && g.forWlanconf,
);
if (currentGroup) existingGroupId = currentGroup.id;
}
if (existingGroupId) {
// Update the existing for_wlanconf group's device list
await unifiSites.updateApGroup(
site.siteId,
existingGroupId,
parsed.deviceMacs,
);
parsed.apGroupMode = "devices";
parsed.apGroupIds = [existingGroupId];
} else {
// Create a new for_wlanconf group
const apGroup = await unifiSites.createApGroup(
site.siteId,
"devices_ap_group",
parsed.deviceMacs,
true, // for_wlanconf must be true for devices mode
);
parsed.apGroupMode = "devices";
parsed.apGroupIds = [apGroup.id];
}
}
// Remove deviceMacs before converting — it's not a UniFi field
const { deviceMacs: _, ...updateInput } = parsed;
const updates = toWlanConfUpdate(updateInput);
const result = await unifiSites.updateWlanConf(
site.siteId,
wlanId,
updates,
);
const response = apiResponse.successful(
"UniFi WiFi Network Updated Successfully!",
result,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({
permissions: ["unifi.access", "unifi.site.wifi", "unifi.site.wifi.update"],
}),
);
+23
View File
@@ -0,0 +1,23 @@
import { createRoute } from "../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../managers/unifiSites";
import { apiResponse } from "../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../middleware/authorization";
/* GET /v1/unifi/site/:id/wifi-limits */
export default createRoute(
"get",
["/site/:id/wifi-limits"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const limits = await unifiSites.getWifiLimits(site.siteId);
const response = apiResponse.successful(
"UniFi WiFi Limits Fetched Successfully!",
limits,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({
permissions: ["unifi.access", "unifi.site.wifi-limits"],
}),
);
+23
View File
@@ -0,0 +1,23 @@
import { createRoute } from "../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../managers/unifiSites";
import { apiResponse } from "../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../middleware/authorization";
/* GET /v1/unifi/site/:id/wlan-groups */
export default createRoute(
"get",
["/site/:id/wlan-groups"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const wlanGroups = await unifiSites.getWlanGroups(site.siteId);
const response = apiResponse.successful(
"UniFi WLAN Groups Fetched Successfully!",
wlanGroups,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({
permissions: ["unifi.access", "unifi.site.wlan-groups"],
}),
);
+38
View File
@@ -0,0 +1,38 @@
import { createRoute } from "../../../modules/api-utils/createRoute";
import { unifiSites } from "../../../managers/unifiSites";
import { apiResponse } from "../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../middleware/authorization";
import { z } from "zod";
/* POST /v1/unifi/site/:id/wlan-groups */
export default createRoute(
"post",
["/site/:id/wlan-groups"],
async (c) => {
const site = await unifiSites.fetch(c.req.param("id"));
const body = await c.req.json();
const schema = z
.object({
name: z.string().min(1, "Name is required"),
})
.strict();
const parsed = schema.parse(body);
const group = await unifiSites.createWlanGroup(site.siteId, parsed);
const response = apiResponse.created(
"UniFi WLAN Group Created Successfully!",
group,
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({
permissions: [
"unifi.access",
"unifi.site.wlan-groups",
"unifi.site.wlan-groups.create",
],
}),
);