Packages
phoenix_kit
1.6.13
1.7.209
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/modules/maintenance/web/plugs/maintenance_mode.ex
defmodule PhoenixKitWeb.Plugs.MaintenanceMode do
@moduledoc """
Plug that enforces maintenance mode for non-admin users.
When the Maintenance module is enabled, this plug will:
- Allow admins and owners to access the site normally
- Show maintenance page to all other users
- Work for both LiveView and regular controller routes
## Usage
This plug is automatically called by PhoenixKitWeb.Plugs.Integration which is
added to your browser pipeline during installation. No manual setup required.
## Internal Usage
The Integration plug calls this plug internally to check maintenance mode status.
"""
import Plug.Conn
import Phoenix.Controller
require Logger
alias PhoenixKit.Modules.Maintenance
alias PhoenixKit.Users.Auth
alias PhoenixKit.Users.Auth.Scope
@doc """
Initializes the plug with options.
"""
def init(opts), do: opts
@doc """
Checks if maintenance mode is enabled and renders maintenance page for non-admin users.
"""
def call(conn, _opts) do
# Only proceed if maintenance mode is enabled
if Maintenance.enabled?() do
handle_maintenance_mode(conn)
else
conn
end
end
# Handle maintenance mode logic
defp handle_maintenance_mode(conn) do
# Skip maintenance mode for auth routes and static assets
if should_skip_maintenance?(conn.request_path) do
conn
else
# Check if this is a LiveView route (Phoenix LiveView handles maintenance in-place)
# Regular controller routes get the maintenance page response
if live_view_route?(conn) do
Logger.debug("LiveView route detected, letting through: #{conn.request_path}")
# Let LiveView handle maintenance mode rendering in-place
# This allows users to stay on their current page when maintenance is disabled
conn
else
# Get user from session and check if admin/owner
user = get_user_from_session(conn)
if user_is_admin_or_owner?(user) do
# Admin/Owner bypasses maintenance mode
conn
else
# Non-admin user - render maintenance page (only for controller routes)
render_maintenance_page(conn)
end
end
end
end
# Check if the request is for a LiveView route
defp live_view_route?(conn) do
require Logger
# Get the configured URL prefix
url_prefix = PhoenixKit.Config.get_url_prefix()
# Check if this request is for a PhoenixKit route
# The path must actually start with the prefix to be a PhoenixKit route
is_phoenix_kit_route =
case url_prefix do
"" ->
# No prefix configured - check if path looks like a PhoenixKit route
# PhoenixKit routes typically have /users/, /admin/, etc.
String.contains?(conn.request_path, ["/users/", "/admin/", "/pages/", "/entities/"])
"/" ->
# Root prefix - check if path looks like a PhoenixKit route
String.contains?(conn.request_path, ["/users/", "/admin/", "/pages/", "/entities/"])
prefix ->
# Has a prefix (e.g., /phoenix_kit) - check if path starts with it
String.starts_with?(conn.request_path, prefix)
end
# Only let LiveView routes through if they're PhoenixKit routes
if is_phoenix_kit_route do
# LiveView routes use WebSocket upgrades or have specific markers
case get_req_header(conn, "x-requested-with") do
["live-view"] ->
true
_ ->
# Check if the request path matches LiveView patterns
# Most PhoenixKit pages are LiveViews, controller routes are mostly POST actions
conn.method == "GET" && !String.contains?(conn.request_path, ["/auth/", "/webhooks/"])
end
else
# Not a PhoenixKit route - don't let it through as LiveView
false
end
end
# Check if user is admin or owner
defp user_is_admin_or_owner?(nil), do: false
defp user_is_admin_or_owner?(user) do
scope = Scope.for_user(user)
Scope.admin?(scope) || Scope.owner?(scope)
end
# Skip maintenance mode for these paths
defp should_skip_maintenance?(path) do
# Static assets
# Authentication routes
static_asset?(path) or
authentication_route?(path)
end
defp static_asset?(path) do
String.starts_with?(path, "/assets/") or
String.starts_with?(path, "/images/") or
String.starts_with?(path, "/fonts/") or
String.contains?(path, "/favicon")
end
defp authentication_route?(path) do
# Get the configured URL prefix
url_prefix = PhoenixKit.Config.get_url_prefix()
# Build prefix-aware paths
prefix_path = fn route ->
case url_prefix do
"" -> route
"/" -> route
prefix -> prefix <> route
end
end
# Authentication routes that bypass maintenance
auth_routes = [
"/users/log-in",
"/users/reset-password",
"/users/confirm",
"/users/magic-link",
"/users/auth/"
]
# Check both with and without prefix for compatibility
Enum.any?(auth_routes, fn route ->
String.contains?(path, prefix_path.(route)) or
String.contains?(path, route)
end)
end
defp get_user_from_session(conn) do
if user_token = get_session(conn, :user_token) do
Auth.get_user_by_session_token(user_token)
else
nil
end
end
defp render_maintenance_page(conn) do
config = Maintenance.get_config()
html = """
<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="csrf-token" content="#{get_csrf_token()}" />
<title>#{config.header}</title>
<link rel="stylesheet" href="/assets/css/app.css" />
</head>
<body class="h-full bg-base-200">
<div class="flex items-center justify-center min-h-screen p-4">
<div class="card bg-base-100 shadow-2xl border-2 border-dashed border-base-300 max-w-2xl w-full">
<div class="card-body text-center py-12 px-6">
<div class="text-8xl mb-6 opacity-70">
🚧
</div>
<h1 class="text-5xl font-bold text-base-content mb-6">
#{config.header}
</h1>
<p class="text-xl text-base-content/70 mb-8 leading-relaxed">
#{config.subtext}
</p>
</div>
</div>
</div>
</body>
</html>
"""
conn
|> put_resp_content_type("text/html")
|> send_resp(:service_unavailable, html)
|> halt()
end
end