REST API v1 endpoints for schedules (14 endpoints) and escalation policies (10 endpoints) with full CRUD for nested resources (layers, members, overrides, rules, targets). GraphQL types, resolvers, and schema mutations for both resource trees.
481 lines
17 KiB
Elixir
481 lines
17 KiB
Elixir
defmodule ToweropsWeb.Router do
|
|
@moduledoc false
|
|
|
|
use ToweropsWeb, :router
|
|
use Honeybadger.Plug
|
|
use ErrorTracker.Web, :router
|
|
|
|
import Oban.Web.Router
|
|
import Phoenix.LiveDashboard.Router
|
|
import ToweropsWeb.UserAuth
|
|
|
|
alias ToweropsWeb.Api.V1
|
|
alias ToweropsWeb.Plugs.RateLimit
|
|
|
|
pipeline :browser do
|
|
plug :accepts, ["html"]
|
|
plug :fetch_session
|
|
plug :fetch_live_flash
|
|
plug :put_root_layout, html: {ToweropsWeb.Layouts, :root}
|
|
plug :protect_from_forgery
|
|
|
|
plug :put_secure_browser_headers, %{
|
|
# Content-Security-Policy for LiveView applications
|
|
# Note: 'unsafe-inline' for scripts is required for LiveView to function
|
|
# WebSocket connection required for LiveView real-time updates
|
|
"content-security-policy" =>
|
|
"default-src 'self'; " <>
|
|
"script-src 'self' 'unsafe-inline'; " <>
|
|
"style-src 'self' 'unsafe-inline'; " <>
|
|
"img-src 'self' data: https:; " <>
|
|
"font-src 'self' data:; " <>
|
|
"connect-src 'self' ws: wss:; " <>
|
|
"frame-ancestors 'none';"
|
|
}
|
|
|
|
plug ToweropsWeb.Plugs.CaptureTimezone
|
|
plug :fetch_current_scope_for_user
|
|
plug :set_user_locale
|
|
plug ToweropsWeb.Plugs.UpdateSessionActivity
|
|
plug :store_return_to_for_liveview
|
|
plug ToweropsWeb.Plugs.DetectEUUser
|
|
plug ToweropsWeb.Plugs.CheckPolicyConsent
|
|
end
|
|
|
|
pipeline :api do
|
|
plug :accepts, ["json"]
|
|
end
|
|
|
|
pipeline :mobile_api do
|
|
plug :accepts, ["json"]
|
|
plug ToweropsWeb.Plugs.MobileAuth
|
|
end
|
|
|
|
pipeline :api_v1 do
|
|
plug :accepts, ["json"]
|
|
plug ToweropsWeb.Plugs.ApiAuth
|
|
end
|
|
|
|
# Rate limiting pipelines
|
|
pipeline :rate_limit_auth do
|
|
plug RateLimit, type: :auth
|
|
end
|
|
|
|
pipeline :rate_limit_api do
|
|
plug RateLimit, type: :api
|
|
end
|
|
|
|
pipeline :graphql_context do
|
|
plug ToweropsWeb.Plugs.GraphQLIntrospection
|
|
plug ToweropsWeb.GraphQL.Context
|
|
end
|
|
|
|
# Health check endpoint for Kubernetes probes (no authentication required)
|
|
scope "/", ToweropsWeb do
|
|
get "/health", HealthController, :index
|
|
end
|
|
|
|
scope "/", ToweropsWeb do
|
|
pipe_through :browser
|
|
|
|
get "/", PageController, :home
|
|
get "/privacy", PageController, :privacy
|
|
get "/terms", PageController, :terms
|
|
get "/docs/api", ApiDocsController, :index
|
|
get "/docs/graphql", GraphQLDocsController, :index
|
|
get "/invitations/:token", InvitationController, :show
|
|
live "/help", HelpLive.Index, :index
|
|
end
|
|
|
|
# Account API routes (requires browser authentication)
|
|
scope "/api/v1/account", ToweropsWeb.Api do
|
|
pipe_through [:browser, :require_authenticated_user]
|
|
|
|
get "/data", AccountDataController, :show
|
|
end
|
|
|
|
# Agent communication via WebSocket channel at /socket/agent
|
|
# See ToweropsWeb.AgentChannel for implementation
|
|
|
|
# Mobile Auth API routes (no authentication required for login flow)
|
|
# Rate limited to prevent brute-force attacks on QR token verification
|
|
scope "/api/v1/mobile/auth", ToweropsWeb.Api do
|
|
pipe_through [:api, :rate_limit_auth]
|
|
|
|
post "/qr/verify", MobileAuthController, :verify_qr_token
|
|
post "/qr/complete", MobileAuthController, :complete_qr_login
|
|
end
|
|
|
|
# Mobile API routes (requires mobile authentication)
|
|
# Rate limited to prevent API abuse
|
|
scope "/api/v1/mobile", ToweropsWeb.Api do
|
|
pipe_through [:mobile_api, :rate_limit_api]
|
|
|
|
# Auth session management
|
|
get "/auth/session", MobileAuthController, :get_session
|
|
delete "/auth/session", MobileAuthController, :revoke_session
|
|
|
|
# Data endpoints
|
|
get "/organizations", MobileController, :list_organizations
|
|
get "/organizations/:organization_id/sites", MobileController, :list_sites
|
|
get "/organizations/:organization_id/devices", MobileController, :list_devices
|
|
get "/organizations/:organization_id/alerts", MobileController, :list_alerts
|
|
get "/devices/:id", MobileController, :get_equipment
|
|
end
|
|
|
|
# Stripe webhook endpoint (no authentication, verified by signature)
|
|
scope "/api/v1", V1 do
|
|
pipe_through :api
|
|
|
|
post "/webhooks/stripe", StripeWebhookController, :create
|
|
end
|
|
|
|
# GraphQL API endpoint (requires API token authentication)
|
|
scope "/api/graphql" do
|
|
pipe_through [:api_v1, :rate_limit_api, :graphql_context]
|
|
|
|
forward "/", Absinthe.Plug,
|
|
schema: ToweropsWeb.GraphQL.Schema,
|
|
analyze_complexity: true,
|
|
max_complexity: 500,
|
|
max_depth: 10
|
|
end
|
|
|
|
# API v1 routes (requires API token authentication)
|
|
# Rate limited to prevent API abuse
|
|
scope "/api/v1", V1 do
|
|
pipe_through [:api_v1, :rate_limit_api]
|
|
|
|
resources "/sites", SitesController, except: [:new, :edit]
|
|
resources "/devices", DevicesController, except: [:new, :edit]
|
|
|
|
resources "/agents", AgentsController, except: [:new, :edit]
|
|
|
|
resources "/alerts", AlertsController, only: [:index, :show]
|
|
post "/alerts/:id/acknowledge", AlertsController, :acknowledge
|
|
post "/alerts/:id/resolve", AlertsController, :resolve
|
|
|
|
get "/organization", OrganizationController, :show
|
|
patch "/organization", OrganizationController, :update
|
|
|
|
resources "/members", MembersController, only: [:index, :delete, :update]
|
|
resources "/invitations", InvitationsController, only: [:index, :create, :delete]
|
|
|
|
resources "/integrations", IntegrationsController, except: [:new, :edit]
|
|
post "/integrations/:id/test", IntegrationsController, :test_connection
|
|
|
|
get "/devices/:device_id/checks", CheckResultsController, :checks
|
|
get "/devices/:device_id/metrics", CheckResultsController, :metrics
|
|
get "/devices/:device_id/interfaces", CheckResultsController, :interfaces
|
|
|
|
get "/activity", ActivityController, :index
|
|
|
|
# On-call schedules
|
|
resources "/schedules", SchedulesController, except: [:new, :edit]
|
|
get "/schedules/:id/on_call", SchedulesController, :on_call
|
|
post "/schedules/:id/layers", SchedulesController, :create_layer
|
|
patch "/schedules/:id/layers/:layer_id", SchedulesController, :update_layer
|
|
delete "/schedules/:id/layers/:layer_id", SchedulesController, :delete_layer
|
|
post "/schedules/:id/layers/:layer_id/members", SchedulesController, :create_member
|
|
delete "/schedules/:id/layers/:layer_id/members/:member_id", SchedulesController, :delete_member
|
|
post "/schedules/:id/overrides", SchedulesController, :create_override
|
|
delete "/schedules/:id/overrides/:override_id", SchedulesController, :delete_override
|
|
|
|
# Escalation policies
|
|
resources "/escalation_policies", EscalationPoliciesController, except: [:new, :edit]
|
|
post "/escalation_policies/:id/rules", EscalationPoliciesController, :create_rule
|
|
patch "/escalation_policies/:id/rules/:rule_id", EscalationPoliciesController, :update_rule
|
|
delete "/escalation_policies/:id/rules/:rule_id", EscalationPoliciesController, :delete_rule
|
|
post "/escalation_policies/:id/rules/:rule_id/targets", EscalationPoliciesController, :create_target
|
|
delete "/escalation_policies/:id/rules/:rule_id/targets/:target_id", EscalationPoliciesController, :delete_target
|
|
end
|
|
|
|
# Webhook routes (shared secret authentication)
|
|
pipeline :webhook do
|
|
plug :accepts, ["json"]
|
|
plug ToweropsWeb.Plugs.WebhookAuth
|
|
end
|
|
|
|
scope "/api/v1/webhooks", V1 do
|
|
pipe_through :webhook
|
|
|
|
post "/agent-release", AgentReleaseWebhookController, :create
|
|
end
|
|
|
|
# Gaiia webhook routes (per-organization secret verification in controller)
|
|
scope "/api/v1/webhooks", V1 do
|
|
pipe_through [:api, :rate_limit_api]
|
|
|
|
post "/gaiia/:organization_id", GaiiaWebhookController, :create
|
|
post "/pagerduty/:organization_id", PagerdutyWebhookController, :create
|
|
end
|
|
|
|
# Admin API routes (requires superuser API token)
|
|
scope "/admin/api", V1 do
|
|
pipe_through :api_v1
|
|
|
|
# MIB file management
|
|
get "/mibs", MibController, :index
|
|
post "/mibs", MibController, :upload
|
|
delete "/mibs/:vendor", MibController, :delete
|
|
|
|
# GeoIP database management
|
|
post "/geoip/import", GeoipController, :import_database
|
|
end
|
|
|
|
# Enable LiveDashboard in production with authentication
|
|
scope "/admin" do
|
|
pipe_through [:browser, :require_authenticated_user, :require_superuser]
|
|
|
|
live_dashboard "/dashboard",
|
|
metrics: ToweropsWeb.Telemetry,
|
|
ecto_repos: [Towerops.Repo],
|
|
ecto_psql_extras_options: [
|
|
# Exclude TimescaleDB internal tables from constraint checks
|
|
missing_fk_constraints: [
|
|
exclude: ~r/^_(hyper_|materialized_hypertable_)/
|
|
]
|
|
]
|
|
end
|
|
|
|
# Enable Oban Web (superuser only)
|
|
scope "/admin" do
|
|
pipe_through [:browser, :require_authenticated_user, :require_superuser]
|
|
|
|
oban_dashboard("/oban")
|
|
error_tracker_dashboard("/errors")
|
|
get "/headers", ToweropsWeb.DebugController, :headers
|
|
end
|
|
|
|
# Enable Swoosh mailbox preview in development
|
|
if Application.compile_env(:towerops, :dev_routes) do
|
|
scope "/dev" do
|
|
pipe_through :browser
|
|
|
|
forward "/mailbox", Plug.Swoosh.MailboxPreview
|
|
end
|
|
end
|
|
|
|
## Authentication routes
|
|
## Rate limited to prevent brute-force attacks
|
|
|
|
scope "/", ToweropsWeb do
|
|
pipe_through [:browser, :redirect_if_user_is_authenticated, :rate_limit_auth]
|
|
|
|
live "/users/register", UserRegistrationLive, :new
|
|
end
|
|
|
|
scope "/", ToweropsWeb do
|
|
pipe_through [:browser, :require_authenticated_user]
|
|
|
|
get "/users/settings/confirm-email/:token", UserSettingsController, :confirm_email
|
|
end
|
|
|
|
scope "/", ToweropsWeb do
|
|
pipe_through [:browser, :rate_limit_auth]
|
|
|
|
get "/users/log-in", UserSessionController, :new
|
|
get "/users/log-in/totp", UserSessionController, :totp_verification_form
|
|
post "/users/log-in/totp", UserSessionController, :verify_totp
|
|
get "/users/log-in/:token", UserSessionController, :confirm
|
|
post "/users/log-in", UserSessionController, :create
|
|
delete "/users/log-out", UserSessionController, :delete
|
|
|
|
get "/users/confirm/:token", UserConfirmationController, :confirm
|
|
get "/users/confirm", UserConfirmationController, :new
|
|
post "/users/confirm", UserConfirmationController, :create
|
|
|
|
get "/users/reset-password", UserResetPasswordController, :new
|
|
post "/users/reset-password", UserResetPasswordController, :create
|
|
live "/users/reset-password/:token", UserResetPasswordLive, :edit
|
|
end
|
|
|
|
# Sudo mode verification routes
|
|
scope "/", ToweropsWeb do
|
|
pipe_through [:browser, :require_authenticated_user]
|
|
|
|
get "/users/sudo-verify", UserSudoController, :new
|
|
post "/users/sudo-verify", UserSudoController, :verify
|
|
post "/orgs/switch", UserSessionController, :switch_org
|
|
end
|
|
|
|
## Admin routes (superuser only)
|
|
|
|
pipeline :admin_rate_limit do
|
|
plug RateLimit, type: :admin
|
|
end
|
|
|
|
scope "/", ToweropsWeb do
|
|
pipe_through [:browser, :require_authenticated_user, :require_superuser, :admin_rate_limit]
|
|
|
|
post "/admin/impersonate/:user_id", AdminController, :start_impersonate
|
|
delete "/admin/impersonate", AdminController, :stop_impersonate
|
|
post "/admin/oban/flush", AdminController, :flush_oban
|
|
end
|
|
|
|
live_session :require_superuser,
|
|
on_mount: [
|
|
{ToweropsWeb.UserAuth, :load_cookie_consent},
|
|
{ToweropsWeb.UserAuth, :require_authenticated_user},
|
|
{ToweropsWeb.UserAuth, :require_totp_enrollment},
|
|
{ToweropsWeb.UserAuth, :require_superuser},
|
|
{ToweropsWeb.UserAuth, :handle_policy_consent}
|
|
] do
|
|
scope "/admin", ToweropsWeb.Admin do
|
|
pipe_through [:browser, :require_authenticated_user, :require_superuser, :admin_rate_limit]
|
|
|
|
live "/", DashboardLive, :index
|
|
live "/users", UserLive.Index, :index
|
|
live "/organizations", OrgLive.Index, :index
|
|
live "/audit", AuditLive.Index, :index
|
|
live "/monitoring", MonitoringLive, :index
|
|
live "/agents", AgentLive.Index, :index
|
|
live "/security", SecurityLive.Index, :index
|
|
end
|
|
end
|
|
|
|
## Organization routes
|
|
|
|
live_session :require_sudo_mode,
|
|
on_mount: [
|
|
{ToweropsWeb.UserAuth, :load_cookie_consent},
|
|
{ToweropsWeb.UserAuth, :require_authenticated_user},
|
|
{ToweropsWeb.UserAuth, :require_totp_enrollment},
|
|
{ToweropsWeb.UserAuth, :require_sudo_mode},
|
|
{ToweropsWeb.UserAuth, :handle_policy_consent}
|
|
] do
|
|
scope "/", ToweropsWeb do
|
|
pipe_through [:browser, :require_authenticated_user]
|
|
|
|
live "/users/settings", UserSettingsLive, :index
|
|
live "/users/my-data", AccountLive.MyData, :index
|
|
live "/account/activity", AccountLive.Activity, :index
|
|
end
|
|
end
|
|
|
|
# TOTP enrollment page - requires auth but NOT TOTP (since user is enrolling)
|
|
live_session :totp_enrollment,
|
|
on_mount: [
|
|
{ToweropsWeb.UserAuth, :load_cookie_consent},
|
|
{ToweropsWeb.UserAuth, :require_authenticated_user}
|
|
] do
|
|
scope "/", ToweropsWeb do
|
|
pipe_through [:browser, :require_authenticated_user]
|
|
|
|
live "/account/totp-enrollment", AccountLive.TotpEnrollment, :index
|
|
end
|
|
end
|
|
|
|
live_session :require_authenticated_user,
|
|
on_mount: [
|
|
{ToweropsWeb.UserAuth, :load_cookie_consent},
|
|
{ToweropsWeb.UserAuth, :require_authenticated_user},
|
|
{ToweropsWeb.UserAuth, :require_totp_enrollment},
|
|
{ToweropsWeb.UserAuth, :handle_policy_consent}
|
|
] do
|
|
scope "/", ToweropsWeb do
|
|
pipe_through [:browser, :require_authenticated_user]
|
|
|
|
live "/mobile/qr-login", MobileQRLive, :index
|
|
live "/orgs", OrgLive.Index, :index
|
|
live "/orgs/new", OrgLive.New, :new
|
|
end
|
|
end
|
|
|
|
live_session :require_authenticated_user_and_organization,
|
|
on_mount: [
|
|
{ToweropsWeb.UserAuth, :load_cookie_consent},
|
|
{ToweropsWeb.UserAuth, :require_authenticated_user},
|
|
{ToweropsWeb.UserAuth, :require_totp_enrollment},
|
|
{ToweropsWeb.UserAuth, :load_current_organization},
|
|
{ToweropsWeb.UserAuth, :subscribe_to_status_updates},
|
|
{ToweropsWeb.UserAuth, :handle_policy_consent}
|
|
] do
|
|
scope "/orgs/:org_slug", ToweropsWeb do
|
|
pipe_through [:browser, :require_authenticated_user, :load_current_organization]
|
|
|
|
live "/settings", Org.SettingsLive, :index
|
|
live "/settings/integrations", Org.IntegrationsLive, :index
|
|
live "/settings/integrations/preseem/devices", Org.PreseemDevicesLive, :index
|
|
live "/settings/integrations/preseem/insights", Org.PreseemInsightsLive, :index
|
|
live "/settings/integrations/gaiia/mapping", Org.GaiiaMappingLive, :index
|
|
live "/settings/integrations/gaiia/reconciliation", Org.GaiiaReconciliationLive, :index
|
|
end
|
|
end
|
|
|
|
live_session :require_authenticated_user_with_default_org,
|
|
on_mount: [
|
|
{ToweropsWeb.UserAuth, :load_cookie_consent},
|
|
{ToweropsWeb.UserAuth, :require_authenticated_user},
|
|
{ToweropsWeb.UserAuth, :require_totp_enrollment},
|
|
{ToweropsWeb.UserAuth, :load_default_organization},
|
|
{ToweropsWeb.UserAuth, :subscribe_to_status_updates},
|
|
{ToweropsWeb.UserAuth, :handle_policy_consent}
|
|
] do
|
|
scope "/", ToweropsWeb do
|
|
pipe_through [:browser, :require_authenticated_user]
|
|
|
|
# Dashboard route
|
|
live "/dashboard", DashboardLive, :index
|
|
|
|
# Alert routes
|
|
live "/alerts", AlertLive.Index, :index
|
|
|
|
# Capacity route
|
|
live "/capacity", CapacityLive, :index
|
|
|
|
# Agent routes
|
|
live "/agents", AgentLive.Index, :index
|
|
live "/agents/:id/edit", AgentLive.Edit, :edit
|
|
live "/agents/:id", AgentLive.Show, :show
|
|
|
|
# Site routes
|
|
live "/sites", SiteLive.Index, :index
|
|
live "/sites/new", SiteLive.Form, :new
|
|
live "/sites/:id", SiteLive.Show, :show
|
|
live "/sites/:id/edit", SiteLive.Form, :edit
|
|
|
|
# Device routes
|
|
live "/devices", DeviceLive.Index, :index
|
|
live "/devices/new", DeviceLive.Form, :new
|
|
live "/devices/:id", DeviceLive.Show, :show
|
|
live "/devices/:id/edit", DeviceLive.Form, :edit
|
|
live "/devices/:id/graph/check", GraphLive.Show, :show
|
|
live "/devices/:id/graph/:sensor_type", GraphLive.Show, :show
|
|
live "/devices/:device_id/backups/compare", MikrotikBackupLive.Compare, :compare
|
|
live "/devices/:device_id/config-timeline", ConfigTimelineLive, :index
|
|
|
|
# Insights route
|
|
live "/insights", InsightsLive.Index, :index
|
|
|
|
# Subscriber trace
|
|
live "/trace", TraceLive.Index, :index
|
|
|
|
# Activity feed
|
|
live "/activity", ActivityFeedLive, :index
|
|
|
|
# On-call schedules
|
|
live "/schedules", ScheduleLive.Index, :index
|
|
live "/schedules/new", ScheduleLive.Form, :new
|
|
live "/schedules/escalation-policies", EscalationPolicyLive.Index, :index
|
|
live "/schedules/escalation-policies/new", EscalationPolicyLive.Form, :new
|
|
live "/schedules/escalation-policies/:id", EscalationPolicyLive.Show, :show
|
|
live "/schedules/escalation-policies/:id/edit", EscalationPolicyLive.Form, :edit
|
|
live "/schedules/:id", ScheduleLive.Show, :show
|
|
live "/schedules/:id/edit", ScheduleLive.Form, :edit
|
|
|
|
# Maintenance windows
|
|
live "/maintenance", MaintenanceLive.Index, :index
|
|
live "/maintenance/new", MaintenanceLive.Form, :new
|
|
live "/maintenance/:id", MaintenanceLive.Show, :show
|
|
live "/maintenance/:id/edit", MaintenanceLive.Form, :edit
|
|
|
|
# Changelog
|
|
live "/changelog", ChangelogLive, :index
|
|
|
|
# Network map routes
|
|
live "/network-map", NetworkMapLive, :index
|
|
live "/sites-map", MapLive.Index, :index
|
|
end
|
|
end
|
|
end
|