/nphotos/auth.php: The central engine. Handles session_start(), verifies credentials against users.txt, and serves the Orange Login UI. Now logs 🔴 LOGOUT actions before destroying sessions.
/nphotos/config.php: Configuration hub. Sets $MY_FOLDER_ID = "nphotos" and physical path constants. Required for every PHP script.
/nphotos/protect.php: Content Gatekeeper. Specifically for /protected_html/. Includes the Dynamic Logout logic which unsets last_page and redirects back to the current URI.
/nphotos/change_password.php: User Utility. Updates users.txt via LOCK_EX and forces a re-login to ensure credential synchronization.
/nphotos/user_info.php: AJAX endpoint for h5ai. Read-only session identity provider.
/nphotos/monitor.php: Admin Dashboard. Includes Auto-Trim (2000 lines), Smart Dates, and Scrollable History (Last 100 hits).
/nphotos/logout.php: Global session terminator. Supports ?from_monitor=1 context.
/nphotos/users.txt: Case-insensitive user database (user:pass). Restricted access.
/nphotos/global_activity.log: Activity log (timestamp|folder|user|ip|action|login_start).
/nphotos/avatar: Directory to place pictures of users (username.jpg or username. png) to be used to show a small picture in the logoff widget
/protected_html: Targeted directory for custom HTML projects. Uses protect.php to wrap content fragments (e.g., _content.php) with the gallery's security session.
/nphotos/albums: Directory for all the original h5ai stuff including your album directories and the modifications done for this project.
/nphotos/albums/_h5ai/public/ext/custom_ui.js: Injection script for h5ai
/nphotos/albums/_h5ai/private/conf/options.json: json for the h5ai injection
/nphotos/incl_docs/: The SSI included files of this documentation, included by docs.shtml
The system operates on a Synchronous Gatekeeping strategy. Instead of a heavy SQL database, it uses the server's memory (Sessions) and the file system (.htaccess) to create a multi-layered perimeter.
Protection begins before PHP even initializes. The server level handles the "Hard Perimeter":
auth.php.users.txt and global_activity.log. Even if a user is logged in, the server denies direct HTTP requests to these data files.To allow multiple independent galleries (e.g., /nphotos/ and /vacation/) to exist on the same server without "cross-talking," the system uses Namespace Isolation. Every session key is prefixed with a unique folder ID.
// Variables are dynamically built using $MY_FOLDER_ID $auth_key = "auth_" . $MY_FOLDER_ID; // Validation Token $user_key = "active_user_" . $MY_FOLDER_ID; // Identity Token $path_key = "last_page_" . $MY_FOLDER_ID; // Return-Path Memory
The "Kill" Protocol: Every protected page includes the auth.php engine at Line 1. If the $auth_key is missing or expired (default 24h), the script executes exit;. This kills the process instantly, ensuring no content bytes are sent to the browser until authentication is confirmed.
The "Safe" is a simple, high-speed text parser that follows strict integrity protocols:
explode(':', $line). This allows for rapid credential lookups without a persistent database connection.LOCK_EX (Exclusive Lock). This prevents "Race Conditions" where simultaneous updates could result in a zero-byte (empty) user file.strtolower(), while passwords maintain strict case-sensitivity and Unicode/Swedish character support.The protection is applied differently based on the content type:
_content.php fragments and providing the REQUEST_URI back to the login form so users return exactly where they left off after logging in.To troubleshoot access issues, follow the path of a single image request:
/albums/photo.jpg..htaccess checks the HTTP_REFERER.
access_denied.php.auth.php checks for $_SESSION['auth_nphotos'].
$lines = file("users.txt", FILE_IGNORE_NEW_LINES);
foreach ($lines as $line) {
if (strpos($line, '#') === 0 || empty($line)) continue;
list($u, $p) = explode(':', $line);
if (strtolower($input_user) === strtolower($u) && $input_pass === $p) {
$_SESSION['auth_nphotos'] = true;
$_SESSION['active_user_nphotos'] = $u;
}
}
$_SERVER['REQUEST_URI'] is used in the login form action to capture the full deep-link path.$MY_FOLDER_ID in config.php; if it doesn't match the actual folder name, the system will look in the wrong namespace.Because the system uses AJAX to fetch user info for the h5ai widget, aggressive browser caching (especially in Microsoft Edge) can cause old code or typos to persist even after server-side fixes.
custom_ui.js or custom_style.css do not appear, force the browser to bypass its cache:
Ctrl + F5 or Ctrl + Shift + R.Cmd + Shift + R.options.json (e.g., "custom_ui.js?v=10.62").
if (isset($_GET['from_monitor'])) {
session_destroy();
header("Location: monitor.php");
} else {
$redirect = $_SESSION['last_page_nphotos'] ?? '/';
session_destroy();
header("Location: " . $redirect);
}
user_info.php is prohibited from writing to $_SESSION['last_page_nphotos'].
Uses toggleModal(true) and backdrop-filter: blur(10px).
Priority: .png -> .jpg -> CSS Initials.
Debug via ?admin_view=true to dump $_SESSION.
$(function () {
const pollUser = () => {
$.getJSON('/nphotosjj/user_info.php', function (data) {
if (data.user && data.user !== 'Guest') {
// Update h5ai identity widget
}
});
};
pollUser();
setInterval(pollUser, 60000);
});
"resources": {
"scripts": ["../nphotosjj/custom_ui"],
"styles": ["../nphotosjj/custom_style"]
}
Auto-Trim: Slices global_activity.log to 2000 lines if count exceeds 2200.
Smart Date: "H:i" for today, "d M, H:i" for older entries.
Primary: #005A9C. Accent: #d35400.
| Component | Path | Access Rule |
|---|---|---|
| Master Gate | /nphotosjj/auth.php | Public |
| Protect Script | /nphotosjj/protect.php | Internal Include |
| Credentials | /nphotosjj/users.txt | DENIED (403) |
/.htaccess)Options -Indexes # Direct 403 errors to the authentication gate ErrorDocument 403 /nphotosjj/auth.php?error=403
/nphotosjj/.htaccess)
# 1. BLOCK SENSITIVE DATA
# Prevents direct URL access to credentials and activity logs
<FilesMatch "\.(txt|log|conf|sh|inc)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order Allow,Deny
Deny from all
</IfModule>
</FilesMatch>
# 2. ALLOW WEB ASSETS
# Specifically allow the assets needed for the UI and Avatars
<FilesMatch "\.(php|jpg|JPG|png|css|js|svg)$">
<IfModule mod_authz_core.c>
Require all granted
</IfModule>
<IfModule !mod_authz_core.c>
Order Deny,Allow
Allow from all
</IfModule>
</FilesMatch>
# 3. ANTI-CACHING FOR DYNAMIC ASSETS
# Forces browsers to fetch fresh versions of UI logic and Gatekeepers.
# Prevents the "Old Widget" or "Old Styles" issue in Edge/Mobile.
<FilesMatch "\.(php|js|css)$">
Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires 0
</FilesMatch>
/nphotosjj/.htaccess)
# ----------------------------------------------------------------------
# h5ai stuff
# ----------------------------------------------------------------------
AddType text/html .shtml
AddType image/svg+xml .svg .svgz
AddHandler server-parsed .shtml
Options Indexes FollowSymLinks Includes
# 1. SET DEFAULT DIRECTORY PAGE
DirectoryIndex index.shtml index.html index.php /nphotosjj/albums/_h5ai/public/index.php
SSILegacyExprParser on
# 2. PROTECT SENSITIVE CORE FILES
# This blocks people from seeing your password list and logs
<FilesMatch "\.(txt|log|conf|sh|inc)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order Allow,Deny
Deny from all
</IfModule>
</FilesMatch>
# 3. ALLOW SHELL AND GATEKEEPER FILES
# We specifically allow .php and .jpg (for avatars)
<FilesMatch "\.(php|jpg|JPG|png|css|js)$">
<IfModule mod_authz_core.c>
Require all granted
</IfModule>
<IfModule !mod_authz_core.c>
Order Deny,Allow
Allow from all
</IfModule>
</FilesMatch>
# 4. PREVENT CACHING ON THE GATEKEEPER
# Ensures the logout/login status is always fresh
<Files "protect.php">
Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires 0
</Files>
/protected_html/.htaccess)
# 1. SET DEFAULT DIRECTORY PAGE
DirectoryIndex index.php index.html
# 2. PROTECT SENSITIVE CORE FILES
# Blocks direct access to passwords, logs, and include fragments
<FilesMatch "\.(txt|log|conf|sh|inc)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order Allow,Deny
Deny from all
</IfModule>
</FilesMatch>
# 3. FRAGMENT PROTECTION (Gatekeeper Bypass Prevention)
# Specifically blocks files like "about_content.php" from direct browsing
<FilesMatch "_content\.php$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order Allow,Deny
Deny from all
</IfModule>
</FilesMatch>
# 4. PREVENT CACHING ON THE GATEKEEPER
<Files "protect.php">
Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires 0
</Files>
/protected_html/.htaccess)
# 1. SET DEFAULT DIRECTORY PAGE
DirectoryIndex index.php index.html
# 2. PROTECT SENSITIVE CORE FILES
# This blocks people from seeing your password list and logs
<FilesMatch "\.(txt|log|conf|sh|inc)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order Allow,Deny
Deny from all
</IfModule>
</FilesMatch>
# 3. ALLOW SHELL AND GATEKEEPER FILES
# We specifically allow .php and .jpg (for avatars)
<FilesMatch "\.(php|jpg|JPG|png|css|js)$">
<IfModule mod_authz_core.c>
Require all granted
</IfModule>
<IfModule !mod_authz_core.c>
Order Deny,Allow
Allow from all
</IfModule>
</FilesMatch>
# SPECIFIC PROTECTION: Block files containing "_content" (e.g., test_content.php)
# This prevents direct URL access but ALLOWS PHP "include" to work.
<FilesMatch "_content\.php$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order Allow,Deny
Deny from all
</IfModule>
</FilesMatch>
# 4. PREVENT CACHING ON THE GATEKEEPER
# Ensures the logout/login status is always fresh
<Files "protect.php">
Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires 0
</Files>
/nphotosjj/albums/.htaccess)
# ----------------------------------------------------------------------
# ANTI-HOTLINKING & DEEP-LINK PROTECTION
# ----------------------------------------------------------------------
# Protect all common file types (Images, Video, Documents, Audio, Archives)
<FilesMatch "(?i)\.(jpg|jpeg|png|gif|webp|avif|heic|pdf|doc|docx|xls|xlsx|ppt|pptx|txt|mov|mp4|avi|mkv|wmv|mp3|wav|flac|zip|rar|7z|exe|msi|cr2|nef|arw)$">
# 1. Block if the referrer doesn't match your specific domain
<If "%{HTTP_REFERER} !~ /alf\.homelinux\.net/">
Redirect 302 https://alf.homelinux.net/nphotosjj/access_denied.php
</If>
# 2. Block if the referrer is completely empty (Direct URL access/Sneak peek)
<If "-z %{HTTP_REFERER}">
Redirect 302 https://alf.homelinux.net/nphotosjj/access_denied.php
</If>
</FilesMatch>
/nphotosjj/albums/_h5ai/public/ext/custom_ui.js
/nphotosjj/albums/_h5ai/private/conf/options.json (Refer to Section 12 for the specific configuration)
The change_password.php utility and auth.php UI implement a v10.60 Unicode-Aware Validation layer to support Swedish national characters while maintaining strict database integrity.
mb_strlen Rule: Uses mb_strlen($p, 'UTF-8') to ensure actual characters are counted. This correctly identifies "brö" as 3 characters (failing the 4-char limit), whereas standard strlen would incorrectly see 4 bytes.: character to prevent breaking the flat-file explode() logic in the user database./u modifier /^[^\s:]+$/u to permit åäöÅÄÖ while strictly blocking spaces, tabs, and newlines.The system utilizes a parameter-based JavaScript function to handle multiple password fields (Login, Old, New, Confirm) without ID conflicts:
// auth.php - Universal Toggle Function
function togglePass(fieldId, eyeId) {
var p = document.getElementById(fieldId);
var e = document.getElementById(eyeId);
if (!p || !e) return;
p.type = (p.type === "password") ? "text" : "password";
e.innerText = (p.type === "password") ? "👁️" : "🙈";
}
// change_password.php - Atomic Write with Error Logging if ($updated) { if (file_put_contents($USER_FILE, implode("\n", $new_content), LOCK_EX)) { // Log success for monitor.php file_put_contents('global_activity.log', $log_line, FILE_APPEND | LOCK_EX); } }
Accounts marked with a * suffix in users.txt are recognized as "Read-Only" by the change utility. The script strips the * for login comparison but detects it during change requests to block unauthorized password modifications to admin accounts.
When duplicating this system to a new directory (e.g., from /nphotosjj/ to /travel/), use this checklist to ensure all hardcoded paths are updated. Search and Replace is your best tool for this process.
$MY_FOLDER_ID = "newfolder"; to match the new directory name.header("Location: ...") redirects.ErrorDocument 403 /newfolder/auth.php?error=403.Redirect 302 path in the Hotlink Protection section to point to the new access_denied.php.DirectoryIndex paths point to the correct _h5ai location if absolute paths were used."resources" section. Ensure "scripts" and "styles" paths are relative or updated.fetch() URL.
../../../user_info.php to make the script folder-agnostic.users.txt and global_activity.log are writable (chmod 664/666) in the new location./_h5ai/public/cache/ in the new folder to force h5ai to generate new thumbnails specific to the new content.