From 7983a3fb49e47b1010bfad388f2f3b7ee468deef Mon Sep 17 00:00:00 2001 From: w3K Date: Sat, 11 Oct 2025 16:07:36 -0400 Subject: [PATCH] v2.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Complete Enhancement Package - Major Feature Update Comprehensive enhancement package for docker-ddns-server including security features, modern authentication, UI/UX improvements, and production-ready deployment features. ## πŸ”’ Security & Authentication ### IP Blocking System - Implemented automatic IP blocking after 3 failed authentication attempts within 72 hours - Added 7-day block duration with automatic expiration - Created `blocked_ips` database table for tracking blocked addresses - Added automatic cleanup of expired blocks - Implemented manual IP unblock capability via security dashboard ### Failed Authentication Logging - Added comprehensive failed authentication logging system - Created `failed_auths` database table storing IP, timestamp, username, and password - Implemented threat intelligence features for password pattern analysis - Added automatic cleanup of old authentication records - Logs intentionally include passwords for single-user security analysis ### Session-Based Authentication - Replaced HTTP Basic Auth with modern session-based authentication for admin panel - Integrated gorilla/sessions library for secure session management - Added configurable session secrets via `DDNS_SESSION_SECRET` environment variable - Implemented "Remember Me" functionality with 30-day session duration - Added proper session destruction on logout - Session cookies configured with HttpOnly, Secure, and SameSite attributes - Maintained HTTP Basic Auth for DynDNS API endpoints (device compatibility) ### HTTPS Enforcement - Added intelligent HTTPS detection via multiple header checks - Implemented automatic HTTPS redirect for admin panel when available - Graceful HTTP fallback when HTTPS unavailable - Supports reverse proxy configurations (nginx, Caddy, Traefik) - Detects SSL via X-Forwarded-Proto, X-Forwarded-Ssl, X-Url-Scheme headers - API endpoints remain HTTP-compatible for device support ## 🎨 UI/UX Enhancements ### Authentication UI - Created modern login page with gradient background and clean design - Added HTTPS security indicator (βœ“ green / ⚠ yellow) - Implemented auto-focus on username field - Added clear error messages for failed login attempts - Created logout confirmation page with redirect options - Removed browser authentication dialog popups ### Navigation & Layout - Changed admin panel URL from `/admin` to `/@` for uniqueness - Updated navigation with unicode icons (🏠 Dashboard, πŸ”’ Security, ⏏️ Logout) - Added tooltips to all navigation icons - Implemented sticky header that remains visible on scroll - Enhanced responsive design for mobile/tablet access ### Logo Support - Added automatic logo detection and display - Supports PNG, WebP, and SVG formats - Checks `/static/icons/` for logo files - Graceful fallback to text title if no logo found - Maintains aspect ratio and responsive sizing ### Security Dashboard - Created comprehensive security overview page at `/@/security` - Added statistics cards showing active blocks, failed attempts, and total blocks - Implemented recent failed attempts table with sortable columns - Added password reveal/hide functionality with confirmation prompts - Created detailed blocked IPs management page with unblock capability - Created detailed failed authentication logs page with full history - Added visual indicators for security status ## πŸ“Š Data Management ### Data Consistency & Normalization - Implemented automatic lowercase conversion for all usernames and hostnames - Prevents case-sensitivity issues in DNS lookups and authentication - Ensures consistent data storage and retrieval - Handles mixed-case legacy data gracefully ### Automatic Migration - Added on-the-fly migration system for legacy uppercase entries - Migration triggers automatically on first `/@/hosts` page visit - Handles hostname conflicts by appending sequential numbers - Provides detailed migration report in UI showing all changes - Non-destructive migration preserves all host data - One-time execution with persistent migration status tracking ### Validation Updates - Reduced minimum hostname length to 1 character (allows single-letter subdomains) - Reduced minimum username length to 1 character - Reduced minimum password length to 6 characters - Maintained security while improving flexibility ### Username Uniqueness - Removed uniqueness constraint on usernames - Allows multiple hosts to share the same username - Supports different passwords for same username across hosts - Enables more flexible credential management strategies ## πŸ›‘οΈ Middleware & Request Handling ### IP Blocker Middleware - Created IPBlockerMiddleware to check requests against blocked IPs - Automatic redirect to 127.0.0.1 for blocked addresses - Lightweight performance impact with database lookup - Positioned early in middleware chain for efficiency ### Session Authentication Middleware - Created SessionAuthMiddleware for admin panel protection - Skips authentication check for /login and /logout routes - Redirects unauthenticated users to login page - Validates session integrity on every request - Compatible with reverse proxy configurations ### HTTPS Redirect Middleware - Created HTTPSRedirectMiddleware for admin panel security - Intelligent detection of HTTPS availability - Skips redirect for API endpoints - Handles X-Forwarded-* headers from reverse proxies - Graceful operation when HTTPS unavailable ## πŸ—„οΈ Database & Models ### New Tables - Added `failed_auths` table for authentication logging - Added `blocked_ips` table for IP block tracking - Proper foreign key relationships and indexes - Automatic timestamps on all records ### Cleanup Functions - Implemented automatic cleanup of expired IP blocks - Implemented automatic cleanup of old authentication logs - Configurable retention periods - Background cleanup execution ## πŸ”§ Technical Improvements ### Dependencies - Added `github.com/gorilla/sessions@v1.2.2` for session management - Updated go.mod with proper version constraints - Maintained compatibility with existing dependencies ### Handler Architecture - Separated security logic into dedicated handler files - Created `security.go` for blocking logic and logging - Created `security_dashboard.go` for UI handlers - Created `auth.go` for login/logout and session management - Created `session.go` for session store implementation - Improved code organization and maintainability ### Main Application - Updated routing to support session-based authentication - Added session initialization on startup - Configured route groups for admin panel and API - Middleware ordering optimized for performance and security ## 🐳 Docker & CI/CD ### Multi-Platform Builds & Automated Releases - Created GitHub Actions workflow (`BuildEmAll.yml`) for automated Docker builds - Supports linux/amd64, linux/386, linux/arm/v7, and linux/arm64 platforms - Automatic builds on push to master with dyndns/ directory changes - Intelligent version tagging system: - Extracts version from commit message (e.g., "v1.2.3 Feature description") - Auto-increments patch version from latest git tag - Falls back to date-based versioning (vYY.MM.DD-HHMM) if no tags exist - Tags images with both `:latest` and semantic version tags (`:vX.Y.Z`) - Automatic GitHub release creation with each build - Release includes Docker image reference and commit message as notes - Publishes to Docker Hub (w3kllc/ddns) - Cross-platform compatibility for ARM devices (Raspberry Pi, etc.) - Workflow can be triggered manually via GitHub Actions UI ### Deployment - Enhanced docker-compose.yml example with all new features - Added documentation for environment variable configuration - Included reverse proxy configuration examples - Added security best practices for production deployment - Semantic versioning with automatic release management ## πŸ“ Documentation ### README Enhancements - Added comprehensive Security Features section - Added Environment Variables reference with descriptions - Added Admin Panel Access documentation - Added Data Consistency & Migration guide - Added API Endpoints documentation - Added UI/UX Enhancements overview - Added Reverse Proxy Configuration examples - Added Docker Configuration best practices - Added CI/CD & Multi-Platform Support details with versioning strategy - Added Semantic Versioning documentation - Added GitHub Release automation details - Added Security Best Practices recommendations - Added Threat Intelligence rationale - Added Migration Guide from original project - Added Troubleshooting section - Added API Reference documentation - Added Roadmap for future features - Updated Credits section - Added Support and Community links ## πŸ”„ Backward Compatibility ### Maintained Features - DynDNS API endpoints remain unchanged (/update, /nic/update, etc.) - HTTP Basic Auth still supported for API (device compatibility) - Existing host configurations continue working without changes - Database schema additions are non-breaking - All original functionality preserved ### Breaking Changes - Admin panel URL changed from `/admin` to `/@` (intentional, more unique) - Admin authentication method changed (sessions vs basic auth) - Requires `DDNS_SESSION_SECRET` environment variable for session security ## ⚑ Performance Considerations - IP blocker checks are optimized with database indexing - Session validation cached in memory - Automatic cleanup runs asynchronously - Minimal overhead on API endpoint performance - Efficient middleware ordering ## 🎯 Testing Considerations Recommended testing areas: - Login/logout flow with and without HTTPS - IP blocking after 3 failed attempts - Session persistence with remember me - API endpoint authentication (device compatibility) - HTTPS redirect with reverse proxy headers - Password reveal/hide in security dashboard - Hostname migration for legacy uppercase entries - Multi-platform Docker image functionality --- **Total Changes:** - **21 files modified** - **20 new files created** - **~2000+ lines of code added** - **100+ hours of development time** **Compatibility:** - βœ… Backward compatible for DynDNS API - ⚠️ Admin panel URL changed (bookmark update needed) - βœ… All existing hosts continue working - βœ… Database schema additions are additive **Credits:** - Original project: dprandzioch/docker-ddns - Web UI Fork: benjaminbear/docker-ddns-server - Enhanced fork: w3K-one/docker-ddns-server - Major enhancements and security features added This represents a significant enhancement to the original project while maintaining the core DynDNS functionality and adding modern security, authentication, and user experience improvements suitable for production deployment. --- .github/workflows/BuildEmAll.yml | 105 ++++ LICENSE | 1 + README.md | 779 +++++++++++++++++++++++-- deployment/docker-compose.yml | 4 +- dyndns/go.mod | 4 +- dyndns/go.sum | 6 + dyndns/handler/auth.go | 225 +++++++ dyndns/handler/cname.go | 27 +- dyndns/handler/handler.go | 53 +- dyndns/handler/host.go | 157 +++-- dyndns/handler/log.go | 16 +- dyndns/handler/middleware.go | 248 ++++++++ dyndns/handler/security.go | 208 +++++++ dyndns/handler/security_dashboard.go | 96 +++ dyndns/handler/session.go | 81 +++ dyndns/main.go | 107 +++- dyndns/model/cname.go | 2 +- dyndns/model/failed_auth.go | 40 ++ dyndns/model/host.go | 6 +- dyndns/nswrapper/ip.go | 2 +- dyndns/static/css/narrow-jumbotron.css | 2 +- dyndns/static/icons/dns.ico | Bin 0 -> 15406 bytes dyndns/static/icons/eye.svg | 4 + dyndns/static/icons/favicon.ico | Bin 15406 -> 82350 bytes dyndns/static/icons/logo.png | Bin 0 -> 69847 bytes dyndns/static/js/actions-1.0.0.js | 286 +++++---- dyndns/views/blocked_ips.html | 98 ++++ dyndns/views/failed_auths.html | 362 ++++++++++++ dyndns/views/layouts/master.html | 76 ++- dyndns/views/listhosts.html | 10 +- dyndns/views/login.html | 211 +++++++ dyndns/views/logout.html | 202 +++++++ dyndns/views/security_dashboard.html | 433 ++++++++++++++ img/addcname.png | Bin 0 -> 118968 bytes img/addhost.png | Bin 47070 -> 138461 bytes img/listcnames.png | Bin 0 -> 104511 bytes img/listhosts.png | Bin 33921 -> 120387 bytes img/listlogs.png | Bin 34311 -> 146244 bytes img/login.png | Bin 0 -> 180583 bytes img/logout.png | Bin 0 -> 211256 bytes img/security.png | Bin 0 -> 265810 bytes 41 files changed, 3521 insertions(+), 330 deletions(-) create mode 100644 .github/workflows/BuildEmAll.yml create mode 100644 dyndns/handler/auth.go create mode 100644 dyndns/handler/middleware.go create mode 100644 dyndns/handler/security.go create mode 100644 dyndns/handler/security_dashboard.go create mode 100644 dyndns/handler/session.go create mode 100644 dyndns/model/failed_auth.go create mode 100644 dyndns/static/icons/dns.ico create mode 100644 dyndns/static/icons/eye.svg create mode 100644 dyndns/static/icons/logo.png create mode 100644 dyndns/views/blocked_ips.html create mode 100644 dyndns/views/failed_auths.html create mode 100644 dyndns/views/login.html create mode 100644 dyndns/views/logout.html create mode 100644 dyndns/views/security_dashboard.html create mode 100644 img/addcname.png create mode 100644 img/listcnames.png create mode 100644 img/login.png create mode 100644 img/logout.png create mode 100644 img/security.png diff --git a/.github/workflows/BuildEmAll.yml b/.github/workflows/BuildEmAll.yml new file mode 100644 index 0000000..58732d0 --- /dev/null +++ b/.github/workflows/BuildEmAll.yml @@ -0,0 +1,105 @@ +name: Docker Multi-Platform Build, Push & Release + +on: + # Trigger the workflow on pushes to the master branch that change files in the dyndns directory + push: + branches: [ "master" ] + paths: + - 'dyndns/**' + + # Allow this workflow to be run manually from the Actions tab + workflow_dispatch: + +# Define environment variables for the entire workflow for easy configuration +env: + DOCKER_IMAGE_NAME: w3kllc/ddns + +jobs: + Build-Em-All: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + # Fetch all history for all tags and branches + with: + fetch-depth: 0 + + - name: Determine Version Tag + id: get_version + run: | + # Get the most recent commit message reliably + LATEST_COMMIT_MSG=$(git log -1 --pretty=%B) + + # 1. Try to get version from commit message (e.g., "v1.2.3 Something something") + # The '|| true' ensures that if grep finds nothing, it doesn't cause the script to exit with an error. + COMMIT_MSG_VERSION=$(echo "$LATEST_COMMIT_MSG" | grep -oP '^v[0-9]+\.[0-9]+(\.[0-9]+)?' || true) + + if [[ -n "$COMMIT_MSG_VERSION" ]]; then + echo "Found version in commit message: $COMMIT_MSG_VERSION" + echo "TAG=$COMMIT_MSG_VERSION" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "No version found in commit message. Checking for existing Git tags." + + # 2. If no version in commit, get the latest git tag + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null) + + if [[ -n "$LATEST_TAG" ]]; then + echo "Found latest tag: $LATEST_TAG" + # Increment the patch version of the tag (e.g., v1.2.3 -> v1.2.4 or v1.2 -> v1.3) + NEW_TAG=$(echo "$LATEST_TAG" | awk -F. -v OFS=. '{$NF = $NF + 1;} 1') + echo "Incremented tag to: $NEW_TAG" + echo "TAG=$NEW_TAG" >> "$GITHUB_OUTPUT" + else + # 3. If no tags exist, use a date-based version + DATE_TAG="v$(date -u +'%y.%m.%d-%H%M')" + echo "No tags found. Using date-based tag: $DATE_TAG" + echo "TAG=$DATE_TAG" >> "$GITHUB_OUTPUT" + fi + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.DOCKER_IMAGE_NAME }} + tags: | + # Create a tag with the version from the previous step + type=raw,value=${{ steps.get_version.outputs.TAG }} + # Create the 'latest' tag + type=raw,value=latest,enable={{is_default_branch}} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v5 + with: + context: . + file: ./deployment/Dockerfile + platforms: linux/amd64,linux/386,linux/arm/v7,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Create GitHub Release + if: github.event_name != 'pull_request' # Only run on push, not PR + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ steps.get_version.outputs.TAG }} + name: Release ${{ steps.get_version.outputs.TAG }} + body: | + Docker Image: `${{ env.DOCKER_IMAGE_NAME }}:${{ steps.get_version.outputs.TAG }}` + + ${{ github.event.head_commit.message }} + # The action automatically attaches source code archives (zip and tar.gz) diff --git a/LICENSE b/LICENSE index ed744c3..2d15bd7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ MIT License +Copyright (c) 2025 w3K LLC Copyright (c) 2020 Benjamin BΓ€rthlein Copyright (c) 2016 David Prandzioch diff --git a/README.md b/README.md index 1d5f814..09796e3 100644 --- a/README.md +++ b/README.md @@ -1,106 +1,789 @@ # Dynamic DNS Server for Docker with Web UI written in Go -![Build status](https://img.shields.io/github/actions/workflow/status/benjaminbear/docker-ddns-server/build.yml) +![Build status](https://img.shields.io/github/actions/workflow/status/w3K-one/docker-ddns-server/BuildEmAll.yml) +![GitHub release (latest by date)](https://img.shields.io/github/v/release/w3K-one/docker-ddns-server) +![Go version](https://img.shields.io/github/go-mod/go-version/w3K-one/docker-ddns-server?filename=dyndns%2Fgo.mod) +![License](https://img.shields.io/github/license/w3K-one/docker-ddns-server) -![GitHub release (latest by date)](https://img.shields.io/github/v/release/benjaminbear/docker-ddns-server) -![Go version](https://img.shields.io/github/go-mod/go-version/benjaminbear/docker-ddns-server?filename=dyndns%2Fgo.mod) -![License](https://img.shields.io/github/license/benjaminbear/docker-ddns-server) - -With docker-ddns-server you can set up your own dynamic DNS server. This project is inspired by https://github.com/dprandzioch/docker-ddns . In addition to the original version, you can setup and maintain your dyndns entries via simple web ui. +With docker-ddns-server you can set up your own dynamic DNS server. This project is inspired by https://github.com/dprandzioch/docker-ddns. In addition to the original version, you can setup and maintain your dyndns entries via a simple web UI with comprehensive security features, modern authentication, and threat monitoring.

- - - + + + + + + + +

-## Installation +## ✨ Key Features -You can either take the docker image or build it on your own. +- **Web-Based Management** - Easy-to-use web interface for managing DNS entries +- **Security & IP Blocking** - Automatic protection against brute-force attacks +- **Modern Authentication** - Session-based admin login with HTTPS support +- **Security Dashboard** - Real-time monitoring of threats and blocked IPs +- **Multi-Platform Support** - Runs on amd64, arm64, arm (Raspberry Pi compatible) +- **Automatic Migration** - Handles legacy data with automatic normalization +- **Reverse Proxy Ready** - Works seamlessly with nginx, Caddy, Traefik +- **Threat Intelligence** - Comprehensive logging for attack pattern analysis -### Using the docker image +--- -https://registry.hub.docker.com/r/bbaerthlein/docker-ddns-server +## πŸ“¦ Installation -Just customize this to your needs and run: +You can either use the pre-built Docker image or build it yourself. -``` +### Using the Docker Image + +Docker Hub: https://hub.docker.com/r/w3kllc/ddns + +**Quick Start:** +```bash docker run -it -d \ -p 8080:8080 \ -p 53:53 \ -p 53:53/udp \ -v /somefolder:/var/cache/bind \ -v /someotherfolder:/root/database \ - -e DDNS_ADMIN_LOGIN=admin:123455546. \ + -e DDNS_ADMIN_LOGIN=admin:$$2y$$05$$... \ -e DDNS_DOMAINS=dyndns.example.com \ -e DDNS_PARENT_NS=ns.example.com \ -e DDNS_DEFAULT_TTL=3600 \ + -e DDNS_SESSION_SECRET=your-random-32-char-secret \ --name=dyndns \ - bbaerthlein/docker-ddns-server:latest + w3kllc/ddns:latest ``` -### Using docker-compose +### Using docker-compose (Recommended) -You can also use Docker Compose to set up this project. For an example `docker-compose.yml`, please refer to this file: https://github.com/benjaminbear/docker-ddns-server/blob/master/deployment/docker-compose.yml +For a complete setup example, see: [docker-compose.yml](https://github.com/w3K-one/docker-ddns-server/blob/master/deployment/docker-compose.yml) -### Configuration +**Example docker-compose.yml:** +```yaml +version: '3.8' -`DDNS_ADMIN_LOGIN` is a htpasswd username password combination used for the web ui. You can create one by using htpasswd: +services: + ddns: + image: w3kllc/ddns:latest + container_name: dyndns + ports: + - "8080:8080" + - "53:53" + - "53:53/udp" + volumes: + - ./bind:/var/cache/bind + - ./database:/root/database + - ./static:/app/static # Optional: for custom logo + environment: + # Required + - DDNS_ADMIN_LOGIN=admin:$$2y$$05$$hashed_password_here + - DDNS_DOMAINS=dyndns.example.com + - DDNS_PARENT_NS=ns.example.com + - DDNS_DEFAULT_TTL=3600 + + # Security (Recommended) + - DDNS_SESSION_SECRET=your-random-32-character-secret-key + + # Optional + - DDNS_TITLE=My DynDNS Server + - DDNS_CLEAR_LOG_INTERVAL=30 + - DDNS_ALLOW_WILDCARD=true + - DDNS_LOGOUT_URL=https://example.com + - DDNS_POWERED_BY=ACME Inc + - DDNS_POWERED_BY_URL=https://acme.inc + restart: unless-stopped ``` -htpasswd -nb user password + +--- + +## βš™οΈ Configuration + +### Environment Variables + +#### Required Variables + +**`DDNS_ADMIN_LOGIN`** +Admin credentials in htpasswd format for web UI access. + +Generate with: +```bash +htpasswd -nb username password ``` -If you want to embed this into a docker-compose.yml you have to double the dollar signs for escaping: + +For docker-compose.yml (escape dollar signs): +```bash +echo $(htpasswd -nb username password) | sed -e s/\\$/\\$\\$/g ``` -echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g + +If not set, all `/@/` routes are accessible without authentication (useful with auth proxy). + +**`DDNS_DOMAINS`** +Comma-separated list of domains managed by the server. +Example: `dyndns.example.com,dyndns.example.org` + +**`DDNS_PARENT_NS`** +Parent nameserver of your domain. +Example: `ns.example.com` + +**`DDNS_DEFAULT_TTL`** +Default TTL (Time To Live) for DNS records in seconds. +Example: `3600` (1 hour) + +#### Security Variables (Recommended) + +**`DDNS_SESSION_SECRET`** +Secret key for session encryption. Should be 32+ random characters. + +Generate with: +```bash +# Linux/Mac +openssl rand -base64 32 + +# Or using Python +python3 -c "import secrets; print(secrets.token_urlsafe(32))" ``` -If `DDNS_ADMIN_LOGIN` is not set, all /admin routes are without protection. (use case: auth proxy) -`DDNS_DOMAINS` are the domains of the webservice and the domain zones of your dyndns server (see DNS Setup) i.e. `dyndns.example.com,dyndns.example.org` (comma separated list) +⚠️ **Important:** Without this variable, sessions won't persist across container restarts. + +#### Optional Variables + +**`DDNS_TITLE`** +Custom site title displayed in the web UI. +Default: `"w3K DynDNS"` -`DDNS_PARENT_NS` is the parent name server of your domain i.e. `ns.example.com` +**`DDNS_CLEAR_LOG_INTERVAL`** +Automatically clear log entries older than specified days. +Example: `30` (keep 30 days of logs) -`DDNS_DEFAULT_TTL` is the default TTL of your dyndns server. +**`DDNS_ALLOW_WILDCARD`** +Enable wildcard DNS resolution (e.g., `*.subdomain.dyndns.example.com`). +Values: `true` or `false` -`DDNS_CLEAR_LOG_INTERVAL` optional: clear log entries automatically in days (integer) e.g. `DDNS_CLEAR_LOG_INTERVAL:30` +**`DDNS_LOGOUT_URL`** +Redirect to this URL after logout. +Example: `https://example.com` -`DDNS_ALLOW_WILDCARD` optional: allows all `*.subdomain.dyndns.example.com` to point to your ip (boolean) e.g. `true` +**`DDNS_POWERED_BY`** +Show this in the footer credits. +Example: `ACME Inc` -`DDNS_LOGOUT_URL` optional: allows a logout redirect to certain url by clicking the logout button (string) e.g. `https://example.com` +**`DDNS_POWERED_BY_URL`** +The URL to _ACME Inc_. +Example: `https:/acme.inc` -### DNS setup +--- -If your parent domain is `example.com` and you want your dyndns domain to be `dyndns.example.com`, -an example domain of your dyndns server would be `blog.dyndns.example.com`. +## 🌐 DNS Setup + +If your parent domain is `example.com` and you want your DynDNS domain to be `dyndns.example.com`, your DynDNS hosts would be like `blog.dyndns.example.com`. + +Add these entries to your parent DNS server: + +``` +dyndns IN NS ns +ns IN A +ns IN AAAA (optional) +``` -You have to add these entries to your parent dns server: +**Example:** ``` dyndns IN NS ns -ns IN A -ns IN AAAA +ns IN A 203.0.113.10 +ns IN AAAA 2001:db8::10 ``` -## Updating entry +--- -After you have added a host via the web ui you can setup your router. -Example update URL: +## πŸ” Security Features + +### IP Blocking & Threat Protection + +- **Automatic IP Blocking**: IPs are blocked after 3 failed authentication attempts within 72 hours +- **7-Day Block Duration**: Blocked IPs are automatically unblocked after 7 days +- **Failed Authentication Logging**: Comprehensive logs including IP, timestamp, username, and password +- **Threat Intelligence**: Analyze attack patterns and password attempts +- **Manual Unblock**: Security dashboard allows manual IP unblocking +- **Automatic Cleanup**: Expired blocks and old logs are cleaned up automatically + +### Session-Based Authentication + +- **Modern Login Page**: No browser popup dialogs +- **Secure Sessions**: HttpOnly, Secure, and SameSite cookie attributes +- **Remember Me**: Optional 30-day session duration +- **Proper Logout**: Destroys sessions completely +- **HTTPS Enforcement**: Automatic redirect to HTTPS when available +- **Reverse Proxy Support**: Detects SSL via X-Forwarded-Proto headers + +### Security Dashboard + +Access the security dashboard at `/@/security` to: +- Monitor blocked IPs and active threats +- Review failed authentication attempts +- Analyze password patterns in attack attempts +- Manually unblock IP addresses +- View statistics and historical data + +**Password Logging Rationale:** +This is a single-user system where the admin is the only legitimate user. All other login attempts are malicious by definition. Password logging enables threat intelligence analysis to determine if attackers are getting close to your actual password. Ensure your database volume is properly secured. + +--- + +## πŸ–₯️ Admin Panel Access + +The admin panel is accessible at `/@/` (not `/admin/` - more unique, less common). + +### Main Features + +- 🏠 **Dashboard** (`/@/`) - Overview and quick access +- πŸ“ **Hosts** (`/@/hosts`) - Manage DNS hosts with automatic lowercase migration +- πŸ”— **CNAMEs** (`/@/cnames`) - Manage CNAME records +- πŸ“Š **Logs** (`/@/logs`) - View update history +- πŸ”’ **Security** (`/@/security`) - Monitor threats and blocked IPs +- ⏏️ **Logout** (`/@/logout`) - End session securely + +### Authentication Flow + +1. Navigate to `/@/` (or any admin route) +2. Redirected to `/@/login` if not authenticated +3. Enter admin credentials +4. Optionally check "Remember Me" for 30-day session +5. Access admin panel +6. Click logout icon (⏏️) when done + +**HTTPS Detection:** +If running behind a reverse proxy with SSL, the system automatically detects HTTPS and enforces it for the admin panel while keeping API endpoints accessible via HTTP for device compatibility. + +--- + +## πŸ”„ Updating DNS Entries + +After adding a host via the web UI, configure your router or device to update its IP address. + +### Update URLs + +The server accepts updates on multiple endpoints: +- `/update` +- `/nic/update` +- `/v2/update` +- `/v3/update` + +### With IP Address Specified ``` http://dyndns.example.com:8080/update?hostname=blog.dyndns.example.com&myip=1.2.3.4 -or +``` + +Or with authentication in URL: +``` http://username:password@dyndns.example.com:8080/update?hostname=blog.dyndns.example.com&myip=1.2.3.4 ``` -this updates the host `blog.dyndns.example.com` with the IP 1.2.3.4. You have to setup basic authentication with the username and password from the web ui. +### Without IP Address (Auto-detect) -If your router doensn't support sending the ip address (OpenWRT) you don't have to set myip field: +If your router/device doesn't support sending the IP address (e.g., OpenWRT), omit the `myip` parameter: ``` http://dyndns.example.com:8080/update?hostname=blog.dyndns.example.com -or +``` + +Or with authentication: +``` http://username:password@dyndns.example.com:8080/update?hostname=blog.dyndns.example.com ``` -The handler will also listen on: -* /nic/update -* /v2/update -* /v3/update +The server will automatically use the client's IP address from the request. + +### Authentication + +API endpoints use **HTTP Basic Authentication** with the username and password you set for each host in the web UI (not the admin credentials). + +**Important:** +- **Admin credentials** (`DDNS_ADMIN_LOGIN`) - For web UI access at `/@/` +- **Host credentials** - For API updates, set per-host in the web UI + +--- + +## 🎨 UI/UX Features + +### Automatic Logo Detection + +Place a logo file in the static directory to automatically display it: + +**Supported formats:** +- `static/icons/logo.png` +- `static/icons/logo.webp` +- `static/icons/logo.svg` + +If no logo is found, the system displays the text title (`DDNS_TITLE`). + +**Docker volume mount for custom logo:** +```yaml +volumes: + - ./static:/app/static +``` + +Then place your logo at: `./static/icons/logo.png` + +### Visual Improvements + +- **Sticky Header**: Navigation remains visible while scrolling +- **Unicode Icons**: 🏠 Dashboard, πŸ”’ Security, ⏏️ Logout (with tooltips) +- **Modern Design**: Clean, professional interface +- **HTTPS Indicator**: Visual confirmation of secure connection on login page +- **Password Controls**: Hide/reveal functionality with confirmation prompts +- **Responsive Layout**: Works on desktop, tablet, and mobile + +--- + +## πŸ”§ Data Management + +### Automatic Hostname Normalization + +All usernames and hostnames are automatically converted to lowercase to prevent case-sensitivity issues: +- Database storage is always lowercase +- Lookups are case-insensitive +- Prevents duplicate entries with different cases + +### Legacy Data Migration + +When accessing `/@/hosts` for the first time, the system automatically migrates any uppercase entries: +- Converts hostnames to lowercase +- Handles conflicts by appending numbers (e.g., `host-1`, `host-2`) +- Displays migration report in the UI +- One-time process, status persisted in database +- Non-destructive, preserves all host data + +### Username Flexibility + +- **Non-Unique Usernames**: Multiple hosts can share the same username +- Enables flexible credential management strategies +- Each host can have the same or different password + +### Validation Rules + +- **Hostnames**: Minimum 1 character (allows single-letter subdomains) +- **Usernames**: Minimum 1 character +- **Passwords**: Minimum 6 characters + +--- + +## πŸ”€ Reverse Proxy Configuration + +The application intelligently detects HTTPS availability and adjusts behavior accordingly. + +### HTTPS Detection Methods + +1. Direct TLS connection (`request.TLS`) +2. `X-Forwarded-Proto` header +3. `X-Forwarded-Ssl` header +4. `X-Url-Scheme` header + +### Behavior + +**Admin Panel (`/@/*`):** +- Auto-redirects to HTTPS when available +- Graceful HTTP fallback if HTTPS unavailable +- Session cookies use Secure flag with HTTPS + +**API Endpoints (`/update`, `/nic/update`, etc.):** +- Always accept HTTP connections +- No forced HTTPS redirect (device compatibility) +- Works with devices that don't support HTTPS + +### Example Nginx Configuration + +```nginx +server { + listen 443 ssl; + server_name dyndns.example.com; + + ssl_certificate /path/to/fullchain.pem; + ssl_certificate_key /path/to/privkey.pem; + + # Recommended SSL settings + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + location / { + proxy_pass http://127.0.0.1:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} + +# Optional: HTTP to HTTPS redirect +server { + listen 80; + server_name dyndns.example.com; + return 301 https://$server_name$request_uri; +} +``` + +### Example Caddy Configuration + +``` +dyndns.example.com { + reverse_proxy localhost:8080 +} +``` + +Caddy automatically handles SSL certificates and sets appropriate headers. + +--- + +## 🐳 Multi-Platform Docker Support + +### Automated Builds + +Docker images are automatically built via GitHub Actions for multiple platforms: + +**Supported Platforms:** +- `linux/amd64` - Intel/AMD 64-bit (standard servers, PCs) +- `linux/386` - Intel/AMD 32-bit (older systems) +- `linux/arm/v7` - ARM 32-bit (Raspberry Pi 2/3, older ARM devices) +- `linux/arm64` - ARM 64-bit (Raspberry Pi 4+, modern ARM servers) + +### Version Tags + +Docker images are tagged using semantic versioning: + +**`:latest`** - Always points to the most recent stable build + +**`:vX.Y.Z`** - Semantic version tags (e.g., `:v1.2.3`) +- Version from commit message (if commit starts with `vX.Y.Z`) +- OR auto-incremented from last git tag +- OR date-based tag if no version tags exist + +**Example:** +```bash +# Pull latest version +docker pull w3kllc/ddns:latest + +# Pull specific version +docker pull w3kllc/ddns:v1.2.3 + +# Pull specific platform +docker pull --platform linux/arm64 w3kllc/ddns:latest +``` + +### Versioning Strategy + +The build system automatically determines version tags using this priority order: + +1. **Commit Message Version** (Highest Priority): If your commit message title starts with `vX.Y.Z` (e.g., `v1.2.3`), that exact version is used +2. **Auto-Increment from Last Tag**: If no version in commit message, finds the latest git tag and increments the patch version (e.g., `v1.2.3` β†’ `v1.2.4`) +3. **Date-Based Fallback**: If no git tags exist at all, uses timestamp format `vYY.MM.DD-HHMM` (e.g., `v25.10.11-1430`) + +**Example commit messages:** +```bash +# Explicit version (workflow extracts "v1.3.0" from start of commit message) +git commit -m "v1.3.0 Add new security features" + +# Auto-increment (no version found, so increments last tag: v1.2.3 β†’ v1.2.4) +git commit -m "Fix bug in authentication" + +# Date-based (no tags exist yet, uses timestamp: v25.10.11-1430) +git commit -m "Initial release" +``` + +**How version extraction works:** +- Workflow searches for pattern `vX.Y.Z` or `vX.Y` at the **start** of commit message +- Must begin with `v` followed by numbers and dots +- Examples that work: `v1.0.0`, `v2.1.3`, `v1.2` +- Examples that won't work: `version 1.0.0` (missing `v`), `Release v1.0.0` (doesn't start with `v`) + +### GitHub Releases + +Each build automatically creates a GitHub release with: +- Version tag +- Docker image reference +- Commit message as release notes +- Source code archives (zip and tar.gz) + +--- + +## πŸš€ Migration from Original Project + +If migrating from `dprandzioch/docker-ddns` or older versions of this fork: + +### Before Migration + +1. **Backup your data:** + ```bash + docker cp dyndns:/root/database ./backup-database + docker cp dyndns:/var/cache/bind ./backup-bind + ``` + +2. **Note your current configuration** (environment variables) + +### Breaking Changes + +1. **Admin Panel URL**: Changed from `/admin` to `/@/` + - Update bookmarks and links + - Use `/@/login` for login page + +2. **Authentication Method**: Admin panel now uses sessions + - Add `DDNS_SESSION_SECRET` environment variable + - Login via web form instead of browser popup + +3. **New Recommended Variable**: `DDNS_SESSION_SECRET` + - Required for session persistence + - Generate: `openssl rand -base64 32` + +### Migration Steps + +1. **Update docker-compose.yml** or docker command with new variables +2. **Add `DDNS_SESSION_SECRET`** to environment +3. **Update bookmarks** from `/admin` to `/@/` +4. **Restart container** with new configuration +5. **Visit `/@/hosts`** to trigger automatic data migration +6. **Review security dashboard** for any blocked IPs + +### Backward Compatibility + +βœ… **Fully Compatible:** +- DynDNS API endpoints unchanged +- HTTP Basic Auth still works for device updates +- Existing host configurations work without changes +- Database schema additions are non-breaking +- All original functionality preserved + +⚠️ **Manual Update Required:** +- Bookmark/link updates for admin panel +- Addition of session secret (recommended) + +--- + +## πŸ” Troubleshooting + +### Login Issues + +**Problem:** Login redirects back to login page +**Solution:** Ensure `DDNS_SESSION_SECRET` is set. Without it, sessions won't persist. + +**Problem:** Can't remember admin password +**Solution:** Regenerate password with `htpasswd -nb username newpassword` and update `DDNS_ADMIN_LOGIN` + +### HTTPS Issues + +**Problem:** HTTPS redirect loop +**Solution:** Verify reverse proxy sends `X-Forwarded-Proto: https` header + +**Problem:** "Not Secure" warning +**Solution:** Check SSL certificate configuration in your reverse proxy + +### IP Blocking + +**Problem:** Locked out after failed login attempts +**Solution:** +- Wait 7 days for automatic unblock +- OR manually remove from `blocked_ips` table in database +- OR access database with SQLite: `DELETE FROM blocked_ips WHERE ip_address='YOUR_IP';` + +### API Updates + +**Problem:** Device updates not working +**Solution:** +- API uses host credentials (from web UI), not admin credentials +- Check username/password for specific host in `/@/hosts` +- Verify device is sending correct Basic Auth headers + +**Problem:** "nochg" response from server +**Solution:** IP address hasn't changed, this is normal behavior + +### Build Issues + +**Problem:** `missing go.sum entry for gorilla/sessions` +**Solution:** +```bash +go get github.com/gorilla/sessions@v1.2.2 +go mod tidy +``` + +### Database Issues + +**Problem:** Database locked errors +**Solution:** Ensure only one container instance is running + +**Problem:** Lost all data after update +**Solution:** Check volume mounts are correct in docker-compose.yml + +--- + +## πŸ›‘οΈ Security Best Practices + +1. **Always Set Session Secret** + Generate a strong random secret: `openssl rand -base64 32` + +2. **Use HTTPS with Reverse Proxy** + Never expose the admin panel over plain HTTP in production + +3. **Secure Database Volume** + Set appropriate file permissions: + ```bash + chmod 700 /path/to/database + ``` + +4. **Regular Updates** + Keep Docker image updated: `docker pull w3kllc/ddns:latest` + +5. **Monitor Security Dashboard** + Check `/@/security` regularly for attack patterns + +6. **Strong Admin Password** + Use a password manager to generate and store strong credentials + +7. **Separate Credentials** + Use different passwords for admin and each host + +8. **Firewall Configuration** + Limit access to web UI (port 8080) to trusted networks if possible + +9. **Database Backups** + Regularly backup the database volume + +10. **Password Logging Awareness** + Remember that failed auth logs include passwords - secure your database + +--- + +## πŸ“š API Reference + +### Update Endpoints + +All endpoints accept the same parameters: + +**Endpoints:** +- `GET /update` +- `GET /nic/update` +- `GET /v2/update` +- `GET /v3/update` + +**Parameters:** +- `hostname` (required) - Fully qualified domain name to update +- `myip` (optional) - IP address to set (auto-detected if omitted) + +**Authentication:** +- HTTP Basic Auth using host credentials (username/password from web UI) + +**Response Codes:** +- `good ` - Update successful +- `nochg ` - IP address hasn't changed +- `badauth` - Authentication failed +- `notfqdn` - Hostname is not a valid FQDN +- `nohost` - Hostname doesn't exist +- `abuse` - IP address has been blocked + +**Example:** +```bash +curl -u username:password \ + "http://dyndns.example.com:8080/update?hostname=test.dyndns.example.com&myip=1.2.3.4" +``` + +--- + +## 🀝 Contributing + +Contributions are welcome! Whether it's bug fixes, new features, documentation improvements, or reporting issues. + +### How to Contribute + +1. **Fork the repository** +2. **Create a feature branch** (`git checkout -b feature/amazing-feature`) +3. **Make your changes** +4. **Test thoroughly** +5. **Commit your changes** (`git commit -m 'Add amazing feature'`) +6. **Push to your fork** (`git push origin feature/amazing-feature`) +7. **Open a Pull Request** + +### Development Setup + +```bash +# Clone the repository +git clone https://github.com/w3K-one/docker-ddns-server.git +cd docker-ddns-server + +# Build the application +cd dyndns +go build + +# Run tests (if available) +go test ./... + +# Build Docker image locally +cd .. +docker build -t ddns:dev -f deployment/Dockerfile . +``` + +### Code Style + +- Follow Go conventions and best practices +- Use `gofmt` for code formatting +- Add comments for complex logic +- Write meaningful commit messages + +--- + +## πŸ“„ License + +This project is licensed under the MIT License - see the LICENSE file for details. + +--- + +## πŸ™ Credits + +**Original Project:** +[dprandzioch/docker-ddns](https://github.com/dprandzioch/docker-ddns) - Original DynDNS server implementation + +**Web UI Fork:** +[benjaminbear/docker-ddns-server](https://github.com/benjaminbear/docker-ddns-server) - Added web UI for management + +**Enhanced Fork:** +[w3K-one/docker-ddns-server](https://github.com/w3K-one/docker-ddns-server) - Security features, modern auth, multi-platform support + +### Major Enhancements in This Fork + +- πŸ”’ IP blocking and threat protection system +- πŸ” Session-based authentication with modern login +- πŸ“Š Security dashboard for monitoring attacks +- 🌐 HTTPS enforcement with reverse proxy support +- 🎨 Enhanced UI/UX with logo support and sticky header +- πŸ“¦ Multi-platform Docker builds (amd64, arm64, arm, 386) +- πŸ”„ Automatic data migration and normalization +- πŸ“ Comprehensive documentation +- πŸ€– Automated CI/CD with GitHub Actions +- 🏷️ Semantic versioning with automatic releases + +--- + +## πŸ’¬ Support + +- **Issues:** [GitHub Issues](https://github.com/w3K-one/docker-ddns-server/issues) +- **Discussions:** [GitHub Discussions](https://github.com/w3K-one/docker-ddns-server/discussions) +- **Docker Hub:** [w3kllc/ddns](https://hub.docker.com/r/w3kllc/ddns) + +--- + +## πŸ—ΊοΈ Roadmap + +Potential future enhancements: +- Email notifications for security events +- Two-factor authentication (2FA) +- API rate limiting +- Web-based configuration wizard +- DNS over HTTPS (DoH) support +- Prometheus metrics export +- Docker Swarm / Kubernetes support +- Advanced search and filtering in logs +- Bulk host management + +Have an idea? [Open an issue](https://github.com/w3K-one/docker-ddns-server/issues) or start a [discussion](https://github.com/w3K-one/docker-ddns-server/discussions)! + +--- + +**Made with ❀️ by the community** diff --git a/deployment/docker-compose.yml b/deployment/docker-compose.yml index 3844e77..08faff0 100644 --- a/deployment/docker-compose.yml +++ b/deployment/docker-compose.yml @@ -1,8 +1,8 @@ version: '3' services: ddns: - image: bbaerthlein/docker-ddns-server:latest - restart: always + image: w3kllc/ddns:latest + restart: unless-stopped environment: DDNS_ADMIN_LOGIN: 'admin:$$3$$abcdefg' DDNS_DOMAINS: 'dyndns.example.com' diff --git a/dyndns/go.mod b/dyndns/go.mod index a45f6a0..f07c8c5 100644 --- a/dyndns/go.mod +++ b/dyndns/go.mod @@ -1,10 +1,11 @@ -module github.com/benjaminbear/docker-ddns-server/dyndns +module github.com/w3K-one/docker-ddns-server/dyndns go 1.22 require ( github.com/foolin/goview v0.3.0 github.com/go-playground/validator/v10 v10.20.0 + github.com/gorilla/sessions v1.2.2 github.com/labstack/echo/v4 v4.12.0 github.com/labstack/gommon v0.4.2 github.com/tg123/go-htpasswd v1.2.2 @@ -18,6 +19,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/gorilla/securecookie v1.1.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/dyndns/go.sum b/dyndns/go.sum index c995b7d..32e2955 100644 --- a/dyndns/go.sum +++ b/dyndns/go.sum @@ -25,6 +25,12 @@ github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaC github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= diff --git a/dyndns/handler/auth.go b/dyndns/handler/auth.go new file mode 100644 index 0000000..9a32560 --- /dev/null +++ b/dyndns/handler/auth.go @@ -0,0 +1,225 @@ +package handler + +import ( + "crypto/rand" + "encoding/base64" + "net/http" + "time" + + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/log" +) + +// Session key constants +const ( + SessionName = "ddns_session" + SessionUserKey = "user" + SessionAuthKey = "authenticated" + SessionCreatedAt = "created_at" + SessionExpiresAt = "expires_at" +) + +// ShowLoginPage renders the login page +func (h *Handler) ShowLoginPage(c echo.Context) error { + // Check if already authenticated + if h.IsAuthenticated(c) { + return c.Redirect(http.StatusFound, "/@/hosts") + } + + // Check if there's an error message from failed login + errorMsg := c.QueryParam("error") + + return c.Render(http.StatusOK, "login", echo.Map{ + "title": h.Title, + "logoPath": h.LogoPath, + "poweredBy": h.PoweredBy, + "poweredByUrl": h.PoweredByUrl, + "error": errorMsg, + }) +} + +// HandleLogin processes login form submission +func (h *Handler) HandleLogin(c echo.Context) error { + username := c.FormValue("username") + password := c.FormValue("password") + rememberMe := c.FormValue("remember_me") == "on" + + // Get client IP for logging + clientIP := ExtractIPFromRequest( + c.Request().RemoteAddr, + c.Request().Header.Get("X-Forwarded-For"), + c.Request().Header.Get("X-Real-IP"), + ) + + // Validate credentials + authenticated, err := h.authByEnv(username, password) + if err != nil { + log.Error("Authentication error:", err) + h.LogFailedAuth(clientIP, c.Request().UserAgent(), c.Path(), username, password) + return c.Redirect(http.StatusFound, "/@/login?error=authentication_error") + } + + if !authenticated { + log.Warnf("Failed login attempt from IP %s, username: %s", clientIP, username) + h.LogFailedAuth(clientIP, c.Request().UserAgent(), c.Path(), username, password) + return c.Redirect(http.StatusFound, "/@/login?error=invalid_credentials") + } + + // Authentication successful - create session + sess, err := h.GetSession(c) + if err != nil { + log.Error("Session creation error:", err) + return c.Redirect(http.StatusFound, "/@/login?error=session_error") + } + + // Set session values + sess.Values[SessionUserKey] = username + sess.Values[SessionAuthKey] = true + sess.Values[SessionCreatedAt] = time.Now().Unix() + + // Set expiration based on remember me + if rememberMe { + sess.Options.MaxAge = 30 * 24 * 60 * 60 // 30 days + sess.Values[SessionExpiresAt] = time.Now().Add(30 * 24 * time.Hour).Unix() + } else { + sess.Options.MaxAge = 24 * 60 * 60 // 24 hours + sess.Values[SessionExpiresAt] = time.Now().Add(24 * time.Hour).Unix() + } + + // Set secure flag if using HTTPS + if h.IsHTTPS(c) { + sess.Options.Secure = true + } + + // Save session + if err := sess.Save(c.Request(), c.Response()); err != nil { + log.Error("Session save error:", err) + return c.Redirect(http.StatusFound, "/@/login?error=session_error") + } + + log.Infof("Successful login from IP %s, username: %s", clientIP, username) + + // Redirect to originally requested page or default to hosts + redirect := c.QueryParam("redirect") + if redirect == "" || redirect == "/@/login" { + redirect = "/@/hosts" + } + return c.Redirect(http.StatusFound, redirect) +} + +// HandleLogout destroys the session and logs out the user +func (h *Handler) HandleLogout(c echo.Context) error { + sess, err := h.GetSession(c) + if err == nil { + // Get username before destroying session + username := "unknown" + if user, ok := sess.Values[SessionUserKey].(string); ok { + username = user + } + + // Destroy session + sess.Options.MaxAge = -1 + sess.Values = make(map[interface{}]interface{}) + sess.Save(c.Request(), c.Response()) + + if username != "" { + log.Infof("User %s logged out", username) + } + } + + // Clear session cookie + c.SetCookie(&http.Cookie{ + Name: SessionName, + Value: "", + Path: "/", + MaxAge: -1, + HttpOnly: true, + Secure: h.IsHTTPS(c), + SameSite: http.SameSiteStrictMode, + }) + + // ALWAYS render logout page (not redirect) + // Pass LogoutUrl so JavaScript can handle delayed redirect + return c.Render(http.StatusOK, "logout", echo.Map{ + "title": h.Title, + "logoPath": h.LogoPath, + "logoutUrl": h.LogoutUrl, // Pass the logout URL to template + "poweredBy": h.PoweredBy, + "poweredByUrl": h.PoweredByUrl, + }) +} + +// IsAuthenticated checks if the current session is authenticated +func (h *Handler) IsAuthenticated(c echo.Context) bool { + if h.DisableAdminAuth { + return true + } + + sess, err := h.GetSession(c) + if err != nil { + return false + } + + // Check if authenticated + authenticated, ok := sess.Values[SessionAuthKey].(bool) + if !ok || !authenticated { + return false + } + + // Check if session has expired + if expiresAt, ok := sess.Values[SessionExpiresAt].(int64); ok { + if time.Now().Unix() > expiresAt { + log.Info("Session expired") + return false + } + } + + return true +} + +// GetSession retrieves or creates a session for the request +func (h *Handler) GetSession(c echo.Context) (*Session, error) { + return h.SessionStore.Get(c.Request(), SessionName) +} + +// GenerateCSRFToken generates a random CSRF token +func GenerateCSRFToken() (string, error) { + b := make([]byte, 32) + if _, err := rand.Read(b); err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(b), nil +} + +// IsHTTPS checks if the request came via HTTPS +// Checks both direct HTTPS and reverse proxy headers +func (h *Handler) IsHTTPS(c echo.Context) bool { + // Check if direct HTTPS + if c.Request().TLS != nil { + return true + } + + // Check reverse proxy headers + proto := c.Request().Header.Get("X-Forwarded-Proto") + if proto == "https" { + return true + } + + // Check other common headers + if c.Request().Header.Get("X-Forwarded-Ssl") == "on" { + return true + } + + if c.Request().Header.Get("X-Url-Scheme") == "https" { + return true + } + + return false +} + +// GetHTTPSRedirectURL constructs the HTTPS version of the current URL +func (h *Handler) GetHTTPSRedirectURL(c echo.Context) string { + host := c.Request().Host + uri := c.Request().RequestURI + return "https://" + host + uri +} diff --git a/dyndns/handler/cname.go b/dyndns/handler/cname.go index 82423b8..f7e03b7 100644 --- a/dyndns/handler/cname.go +++ b/dyndns/handler/cname.go @@ -5,18 +5,15 @@ import ( "net/http" "strconv" - "github.com/benjaminbear/docker-ddns-server/dyndns/model" - "github.com/benjaminbear/docker-ddns-server/dyndns/nswrapper" + "github.com/w3K-one/docker-ddns-server/dyndns/model" + "github.com/w3K-one/docker-ddns-server/dyndns/nswrapper" "github.com/labstack/echo/v4" "gorm.io/gorm" ) // ListCNames fetches all cnames from database and lists them on the website. func (h *Handler) ListCNames(c echo.Context) (err error) { - if !h.AuthAdmin { - return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED}) - } - + // Auth check removed - middleware handles this cnames := new([]model.CName) if err = h.DB.Preload("Target").Find(cnames).Error; err != nil { return c.JSON(http.StatusBadRequest, &Error{err.Error()}) @@ -25,16 +22,15 @@ func (h *Handler) ListCNames(c echo.Context) (err error) { return c.Render(http.StatusOK, "listcnames", echo.Map{ "cnames": cnames, "title": h.Title, + "logoPath": h.LogoPath, + "poweredBy": h.PoweredBy, + "poweredByUrl": h.PoweredByUrl, }) } // AddCName just renders the "add cname" website. // Therefore all host entries from the database are being fetched. func (h *Handler) AddCName(c echo.Context) (err error) { - if !h.AuthAdmin { - return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED}) - } - hosts := new([]model.Host) if err = h.DB.Find(hosts).Error; err != nil { return c.JSON(http.StatusBadRequest, &Error{err.Error()}) @@ -44,6 +40,9 @@ func (h *Handler) AddCName(c echo.Context) (err error) { "config": h.Config, "hosts": hosts, "title": h.Title, + "logoPath": h.LogoPath, + "poweredBy": h.PoweredBy, + "poweredByUrl": h.PoweredByUrl, }) } @@ -51,10 +50,6 @@ func (h *Handler) AddCName(c echo.Context) (err error) { // adds the cname entry to the database, // and adds the entry to the DNS server. func (h *Handler) CreateCName(c echo.Context) (err error) { - if !h.AuthAdmin { - return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED}) - } - cname := &model.CName{} if err = c.Bind(cname); err != nil { return c.JSON(http.StatusBadRequest, &Error{err.Error()}) @@ -89,10 +84,6 @@ func (h *Handler) CreateCName(c echo.Context) (err error) { // DeleteCName fetches a cname entry from the database by "id" // and deletes the database and DNS server entry to it. func (h *Handler) DeleteCName(c echo.Context) (err error) { - if !h.AuthAdmin { - return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED}) - } - id, err := strconv.Atoi(c.Param("id")) if err != nil { return c.JSON(http.StatusBadRequest, &Error{err.Error()}) diff --git a/dyndns/handler/handler.go b/dyndns/handler/handler.go index 6827e4a..ff2e402 100644 --- a/dyndns/handler/handler.go +++ b/dyndns/handler/handler.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/benjaminbear/docker-ddns-server/dyndns/model" + "github.com/w3K-one/docker-ddns-server/dyndns/model" "github.com/go-playground/validator/v10" "github.com/labstack/echo/v4" "github.com/tg123/go-htpasswd" @@ -28,6 +28,10 @@ type Handler struct { ClearInterval uint64 AllowWildcard bool LogoutUrl string + LogoPath string + SessionStore *SessionStore + PoweredBy string + PoweredByUrl string } type Envs struct { @@ -52,15 +56,16 @@ type Error struct { // To gather admin rights the username password combination must match with the credentials given by the env var. func (h *Handler) AuthenticateUpdate(username, password string, c echo.Context) (bool, error) { h.CheckClearInterval() - reqParameter := c.QueryParam("hostname") + reqParameter := strings.ToLower(c.QueryParam("hostname")) reqArr := strings.SplitN(reqParameter, ".", 2) if len(reqArr) != 2 { log.Error("Error: Something wrong with the hostname parameter") return false, nil } + lowerUsername := strings.ToLower(username) host := &model.Host{} - if err := h.DB.Where(&model.Host{UserName: username, Password: password, Hostname: reqArr[0], Domain: reqArr[1]}).First(host).Error; err != nil { + if err := h.DB.Where(&model.Host{UserName: lowerUsername, Password: password, Hostname: reqArr[0], Domain: reqArr[1]}).First(host).Error; err != nil { log.Error("Error: ", err) return false, nil } @@ -119,8 +124,20 @@ func (h *Handler) ParseEnvs() (adminAuth bool, err error) { var ok bool h.Title, ok = os.LookupEnv("DDNS_TITLE") if !ok { - h.Title = "TheBBCloud DynDNS" + h.Title = "w3K DynDNS" + } + + // ADDED: Check for logo files in the static icons directory upon startup. + logoExtensions := []string{"png", "webp", "svg"} + for _, ext := range logoExtensions { + path := fmt.Sprintf("static/icons/logo.%s", ext) + if _, err := os.Stat(path); err == nil { + h.LogoPath = "/" + path // Store the valid path if found + log.Info("Found logo at: ", h.LogoPath) + break + } } + allowWildcard, ok := os.LookupEnv("DDNS_ALLOW_WILDCARD") if ok { h.AllowWildcard, err = strconv.ParseBool(allowWildcard) @@ -136,6 +153,20 @@ func (h *Handler) ParseEnvs() (adminAuth bool, err error) { } } + h.PoweredBy, ok = os.LookupEnv("DDNS_POWERED_BY") + if !ok || h.PoweredBy == "" { + h.PoweredBy = "w3K LLC" + } else { + log.Info("Powered by set: ", h.PoweredBy) + } + + h.PoweredByUrl, ok = os.LookupEnv("DDNS_POWERED_BY_URL") + if !ok || h.PoweredByUrl == "" { + h.PoweredByUrl = "https://w3K.one/" + } else { + log.Info("Powered by URL set: ", h.PoweredByUrl) + } + clearEnv := os.Getenv("DDNS_CLEAR_LOG_INTERVAL") clearInterval, err := strconv.ParseUint(clearEnv, 10, 32) if err != nil { @@ -153,6 +184,11 @@ func (h *Handler) ParseEnvs() (adminAuth bool, err error) { return adminAuth, fmt.Errorf("environment variable DDNS_DOMAINS has to be set") } + // Initialize session store + if err := h.InitSessionStore(); err != nil { + return adminAuth, fmt.Errorf("failed to initialize session store: %v", err) + } + return adminAuth, nil } @@ -170,7 +206,14 @@ func (h *Handler) InitDB() (err error) { return err } - err = h.DB.AutoMigrate(&model.Host{}, &model.CName{}, &model.Log{}) + // Migrate all models including new security models + err = h.DB.AutoMigrate( + &model.Host{}, + &model.CName{}, + &model.Log{}, + &model.FailedAuth{}, // NEW: Failed authentication tracking + &model.BlockedIP{}, // NEW: Blocked IP tracking + ) return err } diff --git a/dyndns/handler/host.go b/dyndns/handler/host.go index 0bdb6c4..0840125 100644 --- a/dyndns/handler/host.go +++ b/dyndns/handler/host.go @@ -5,13 +5,13 @@ import ( "net" "net/http" "strconv" + "strings" "time" l "github.com/labstack/gommon/log" - "github.com/benjaminbear/docker-ddns-server/dyndns/nswrapper" - - "github.com/benjaminbear/docker-ddns-server/dyndns/model" + "github.com/w3K-one/docker-ddns-server/dyndns/model" + "github.com/w3K-one/docker-ddns-server/dyndns/nswrapper" "github.com/labstack/echo/v4" "gorm.io/gorm" ) @@ -22,10 +22,6 @@ const ( // GetHost fetches a host from the database by "id". func (h *Handler) GetHost(c echo.Context) (err error) { - if !h.AuthAdmin { - return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED}) - } - id, err := strconv.Atoi(c.Param("id")) if err != nil { return c.JSON(http.StatusBadRequest, &Error{err.Error()}) @@ -40,42 +36,116 @@ func (h *Handler) GetHost(c echo.Context) (err error) { return c.JSON(http.StatusOK, id) } -// ListHosts fetches all hosts from database and lists them on the website. +// ListHosts fetches all hosts from database, performs an on-the-fly migration to lowercase, and lists them on the website. func (h *Handler) ListHosts(c echo.Context) (err error) { - if !h.AuthAdmin { - return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED}) + var hosts []model.Host + if err = h.DB.Find(&hosts).Error; err != nil { + return c.JSON(http.StatusBadRequest, &Error{err.Error()}) } - hosts := new([]model.Host) - if err = h.DB.Find(hosts).Error; err != nil { - return c.JSON(http.StatusBadRequest, &Error{err.Error()}) + var changesMade []string + needsMigration := false + + // Use a map to track existing lowercase hostnames to detect conflicts + existingLowercaseHosts := make(map[string]bool) + for _, host := range hosts { + // Key for host map is a combination of hostname and domain + hostKey := fmt.Sprintf("%s.%s", host.Hostname, host.Domain) + existingLowercaseHosts[hostKey] = true + } + + // Transaction to perform all updates at once for data integrity + err = h.DB.Transaction(func(tx *gorm.DB) error { + for i := range hosts { + originalHostname := hosts[i].Hostname + originalUsername := hosts[i].UserName + + lowerHostname := strings.ToLower(originalHostname) + lowerUsername := strings.ToLower(originalUsername) + + isHostnameLower := originalHostname == lowerHostname + isUsernameLower := originalUsername == lowerUsername + + if isHostnameLower && isUsernameLower { + continue // Skip if already lowercase + } + + needsMigration = true + hostToUpdate := &hosts[i] + + // --- Handle Hostname Migration --- + if !isHostnameLower { + finalHostname := lowerHostname + hostKey := fmt.Sprintf("%s.%s", finalHostname, hostToUpdate.Domain) + if _, exists := existingLowercaseHosts[hostKey]; exists { + for j := 1; ; j++ { + newHostname := fmt.Sprintf("%s%d", lowerHostname, j) + newHostKey := fmt.Sprintf("%s.%s", newHostname, hostToUpdate.Domain) + if _, existsInner := existingLowercaseHosts[newHostKey]; !existsInner { + finalHostname = newHostname + break + } + } + } + hostToUpdate.Hostname = finalHostname + // Add new name to map to prevent collisions within the same run + existingLowercaseHosts[fmt.Sprintf("%s.%s", finalHostname, hostToUpdate.Domain)] = true + changesMade = append(changesMade, fmt.Sprintf("Hostname '%s' was changed to '%s'.", originalHostname, finalHostname)) + } + + // --- Handle Username Migration --- + if !isUsernameLower { + hostToUpdate.UserName = lowerUsername // Simply convert to lowercase + changesMade = append(changesMade, fmt.Sprintf("Username '%s' for host '%s' was changed to '%s'.", originalUsername, hostToUpdate.Hostname, lowerUsername)) + } + + if err := tx.Save(hostToUpdate).Error; err != nil { + return err // Rollback on error + } + } + return nil // Commit + }) + + if err != nil { + return c.JSON(http.StatusInternalServerError, Error{Message: "Failed to migrate database entries: " + err.Error()}) + } + + // If a migration happened, re-query to show the final, updated list + if needsMigration { + if err = h.DB.Find(&hosts).Error; err != nil { + return c.JSON(http.StatusBadRequest, &Error{err.Error()}) + } + } + + migrationReport := "" + if len(changesMade) > 0 { + migrationReport = strings.Join(changesMade, "\n") } return c.Render(http.StatusOK, "listhosts", echo.Map{ - "hosts": hosts, - "title": h.Title, + "hosts": &hosts, + "title": h.Title, + "logoPath": h.LogoPath, + "migrationReport": migrationReport, + "poweredBy": h.PoweredBy, + "poweredByUrl": h.PoweredByUrl, }) } // AddHost just renders the "add host" website. func (h *Handler) AddHost(c echo.Context) (err error) { - if !h.AuthAdmin { - return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED}) - } - return c.Render(http.StatusOK, "edithost", echo.Map{ - "addEdit": "add", - "config": h.Config, - "title": h.Title, + "addEdit": "add", + "config": h.Config, + "title": h.Title, + "logoPath": h.LogoPath, + "poweredBy": h.PoweredBy, + "poweredByUrl": h.PoweredByUrl, }) } // EditHost fetches a host by "id" and renders the "edit host" website. func (h *Handler) EditHost(c echo.Context) (err error) { - if !h.AuthAdmin { - return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED}) - } - id, err := strconv.Atoi(c.Param("id")) if err != nil { return c.JSON(http.StatusBadRequest, &Error{err.Error()}) @@ -87,10 +157,13 @@ func (h *Handler) EditHost(c echo.Context) (err error) { } return c.Render(http.StatusOK, "edithost", echo.Map{ - "host": host, - "addEdit": "edit", - "config": h.Config, - "title": h.Title, + "host": host, + "addEdit": "edit", + "config": h.Config, + "title": h.Title, + "logoPath": h.LogoPath, + "poweredBy": h.PoweredBy, + "poweredByUrl": h.PoweredByUrl, }) } @@ -98,15 +171,15 @@ func (h *Handler) EditHost(c echo.Context) (err error) { // adds the host entry to the database, // and adds the entry to the DNS server. func (h *Handler) CreateHost(c echo.Context) (err error) { - if !h.AuthAdmin { - return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED}) - } - host := &model.Host{} if err = c.Bind(host); err != nil { return c.JSON(http.StatusBadRequest, &Error{err.Error()}) } + // Enforce lowercase for new entries + host.Hostname = strings.ToLower(host.Hostname) + host.UserName = strings.ToLower(host.UserName) + if err = c.Validate(host); err != nil { return c.JSON(http.StatusBadRequest, &Error{err.Error()}) } @@ -138,15 +211,15 @@ func (h *Handler) CreateHost(c echo.Context) (err error) { // and compares the host data with the entry in the database by "id". // If anything has changed the database and DNS entries for the host will be updated. func (h *Handler) UpdateHost(c echo.Context) (err error) { - if !h.AuthAdmin { - return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED}) - } - hostUpdate := &model.Host{} if err = c.Bind(hostUpdate); err != nil { return c.JSON(http.StatusBadRequest, &Error{err.Error()}) } + // Enforce lowercase for updated entries + hostUpdate.Hostname = strings.ToLower(hostUpdate.Hostname) + hostUpdate.UserName = strings.ToLower(hostUpdate.UserName) + id, err := strconv.Atoi(c.Param("id")) if err != nil { return c.JSON(http.StatusBadRequest, &Error{err.Error()}) @@ -184,10 +257,6 @@ func (h *Handler) UpdateHost(c echo.Context) (err error) { // DeleteHost fetches a host entry from the database by "id" // and deletes the database and DNS server entry to it. func (h *Handler) DeleteHost(c echo.Context) (err error) { - if !h.AuthAdmin { - return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED}) - } - id, err := strconv.Atoi(c.Param("id")) if err != nil { return c.JSON(http.StatusBadRequest, &Error{err.Error()}) @@ -250,8 +319,8 @@ func (h *Handler) UpdateIP(c echo.Context) (err error) { } } - // Validate hostname - hostname := c.QueryParam("hostname") + // Validate hostname (already lowercased during authentication) + hostname := strings.ToLower(c.QueryParam("hostname")) if hostname == "" || hostname != host.Hostname+"."+host.Domain { log.Message = "Hostname or combination of authenticated user and hostname is invalid" if err = h.CreateLogEntry(log); err != nil { diff --git a/dyndns/handler/log.go b/dyndns/handler/log.go index 569c110..e2d8757 100644 --- a/dyndns/handler/log.go +++ b/dyndns/handler/log.go @@ -6,7 +6,7 @@ import ( "strconv" "time" - "github.com/benjaminbear/docker-ddns-server/dyndns/model" + "github.com/w3K-one/docker-ddns-server/dyndns/model" "github.com/labstack/echo/v4" ) @@ -21,10 +21,6 @@ func (h *Handler) CreateLogEntry(log *model.Log) (err error) { // ShowLogs fetches all log entries from all hosts and renders them to the website. func (h *Handler) ShowLogs(c echo.Context) (err error) { - if !h.AuthAdmin { - return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED}) - } - logs := new([]model.Log) if err = h.DB.Preload("Host").Limit(30).Order("created_at desc").Find(logs).Error; err != nil { return c.JSON(http.StatusBadRequest, &Error{err.Error()}) @@ -33,15 +29,14 @@ func (h *Handler) ShowLogs(c echo.Context) (err error) { return c.Render(http.StatusOK, "listlogs", echo.Map{ "logs": logs, "title": h.Title, + "logoPath": h.LogoPath, + "poweredBy": h.PoweredBy, + "poweredByUrl": h.PoweredByUrl, }) } // ShowHostLogs fetches all log entries of a specific host by "id" and renders them to the website. func (h *Handler) ShowHostLogs(c echo.Context) (err error) { - if !h.AuthAdmin { - return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED}) - } - id, err := strconv.Atoi(c.Param("id")) if err != nil { return c.JSON(http.StatusBadRequest, &Error{err.Error()}) @@ -55,6 +50,9 @@ func (h *Handler) ShowHostLogs(c echo.Context) (err error) { return c.Render(http.StatusOK, "listlogs", echo.Map{ "logs": logs, "title": h.Title, + "logoPath": h.LogoPath, + "poweredBy": h.PoweredBy, + "poweredByUrl": h.PoweredByUrl, }) } diff --git a/dyndns/handler/middleware.go b/dyndns/handler/middleware.go new file mode 100644 index 0000000..304cc16 --- /dev/null +++ b/dyndns/handler/middleware.go @@ -0,0 +1,248 @@ +package handler + +import ( + "net" + "net/http" + "strings" + "time" + + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/log" +) + +// IPBlockerMiddleware checks if the requesting IP is blocked +func (h *Handler) IPBlockerMiddleware() echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + // Extract the client IP + clientIP := ExtractIPFromRequest( + c.Request().RemoteAddr, + c.Request().Header.Get("X-Forwarded-For"), + c.Request().Header.Get("X-Real-IP"), + ) + + // Check if IP is blocked + isBlocked, blockedIP, err := h.IsIPBlocked(clientIP) + if err != nil { + log.Errorf("Error checking blocked IP %s: %v", clientIP, err) + // Continue on error to avoid breaking the site + return next(c) + } + + if isBlocked { + log.Warnf("Blocked IP %s attempted to access %s", clientIP, c.Path()) + + // Update last attempt time + if blockedIP != nil { + blockedIP.LastAttemptAt = time.Now() + h.DB.Save(blockedIP) + } + + // Redirect to 127.0.0.1 + return c.Redirect(http.StatusFound, "http://127.0.0.1") + } + + return next(c) + } + } +} + +// SessionAuthMiddleware checks if user is authenticated via session +func (h *Handler) SessionAuthMiddleware() echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + // Skip auth if disabled + if h.DisableAdminAuth { + return next(c) + } + + // Check if authenticated + if !h.IsAuthenticated(c) { + // Store the original URL for redirect after login + originalURL := c.Request().URL.Path + if c.Request().URL.RawQuery != "" { + originalURL += "?" + c.Request().URL.RawQuery + } + + // Redirect to login page + return c.Redirect(http.StatusFound, "/@/login?redirect="+originalURL) + } + + return next(c) + } + } +} + +// HTTPSRedirectMiddleware redirects HTTP to HTTPS for admin routes +// Only applies to admin routes (/@/*) and only if HTTPS is available +func (h *Handler) HTTPSRedirectMiddleware() echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + // Only apply to admin routes + if !strings.HasPrefix(c.Path(), "/@/") { + return next(c) + } + + // Skip login page to avoid redirect loop + if c.Path() == "/@/login" { + return next(c) + } + + // Check if already HTTPS + if h.IsHTTPS(c) { + return next(c) + } + + // Check if HTTPS is available by checking X-Forwarded-Proto header exists + // This indicates we're behind a reverse proxy that supports HTTPS + if c.Request().Header.Get("X-Forwarded-Proto") != "" { + // Redirect to HTTPS + httpsURL := h.GetHTTPSRedirectURL(c) + return c.Redirect(http.StatusMovedPermanently, httpsURL) + } + + // No HTTPS available, continue with HTTP + return next(c) + } + } +} + +// UpdateAuthMiddleware wraps BasicAuth for update endpoints +// CRITICAL: Only logs failed auth when credentials are ACTUALLY WRONG, not system errors +func (h *Handler) UpdateAuthMiddleware() echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + // Extract credentials + username, password, ok := c.Request().BasicAuth() + + if !ok { + // No credentials provided - this is NOT a failed auth attempt + // It's a misconfigured client or direct browser access + return c.String(http.StatusUnauthorized, "badauth\n") + } + + // Attempt authentication + authenticated, authError := h.AuthenticateUpdate(username, password, c) + + // If there was a system error (not wrong credentials), don't log as failed auth + if authError != nil { + log.Errorf("Authentication system error: %v", authError) + return c.String(http.StatusUnauthorized, "badauth\n") + } + + // Only log failed auth if authentication explicitly failed + // This means: credentials were provided, checked, and found to be WRONG + if !authenticated { + clientIP := ExtractIPFromRequest( + c.Request().RemoteAddr, + c.Request().Header.Get("X-Forwarded-For"), + c.Request().Header.Get("X-Real-IP"), + ) + + log.Warnf("Failed DynDNS API authentication from IP %s, username: %s", clientIP, username) + + // Log the failed attempt (but DON'T trigger IP blocking for API endpoints) + h.LogFailedAuth(clientIP, c.Request().UserAgent(), c.Path(), username, password) + + return c.String(http.StatusUnauthorized, "badauth\n") + } + + // Authentication successful + return next(c) + } + } +} + +// CleanupMiddleware periodically cleans up expired blocks and old records +func (h *Handler) CleanupMiddleware() echo.MiddlewareFunc { + // Track last cleanup time + lastCleanup := &time.Time{} + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + // Run cleanup once per hour + if lastCleanup.IsZero() || time.Since(*lastCleanup) > time.Hour { + go func() { + h.CleanupExpiredBlocks() + h.CleanupOldFailedAuths() + }() + now := time.Now() + lastCleanup = &now + } + + return next(c) + } + } +} + +// ExtractIPFromRequest safely extracts IP from various headers +func ExtractIPFromRequest(remoteAddr string, xForwardedFor string, xRealIP string) string { + // Try X-Real-IP first + if xRealIP != "" { + ip := net.ParseIP(xRealIP) + if ip != nil { + return xRealIP + } + } + + // Try X-Forwarded-For + if xForwardedFor != "" { + // X-Forwarded-For can contain multiple IPs, get the first one + ips := splitAndTrim(xForwardedFor, ",") + if len(ips) > 0 { + ip := net.ParseIP(ips[0]) + if ip != nil { + return ips[0] + } + } + } + + // Fall back to RemoteAddr + ip, _, err := net.SplitHostPort(remoteAddr) + if err != nil { + return remoteAddr + } + + return ip +} + +func splitAndTrim(s string, sep string) []string { + var result []string + for _, part := range split(s, sep) { + trimmed := trim(part) + if trimmed != "" { + result = append(result, trimmed) + } + } + return result +} + +func split(s string, sep string) []string { + // Simple split implementation + var result []string + start := 0 + for i := 0; i < len(s); i++ { + if string(s[i]) == sep { + result = append(result, s[start:i]) + start = i + 1 + } + } + result = append(result, s[start:]) + return result +} + +func trim(s string) string { + // Simple trim implementation + start := 0 + end := len(s) + + for start < end && (s[start] == ' ' || s[start] == '\t') { + start++ + } + + for end > start && (s[end-1] == ' ' || s[end-1] == '\t') { + end-- + } + + return s[start:end] +} diff --git a/dyndns/handler/security.go b/dyndns/handler/security.go new file mode 100644 index 0000000..300fe78 --- /dev/null +++ b/dyndns/handler/security.go @@ -0,0 +1,208 @@ +package handler + +import ( + "strings" + "time" + + "github.com/w3K-one/docker-ddns-server/dyndns/model" + "github.com/labstack/gommon/log" + "gorm.io/gorm" +) + +const ( + MaxFailedAttempts = 3 + BlockDuration = 168 * time.Hour // 7 days (1 week) in hours + LookbackPeriod = 72 * time.Hour // Check failures in last 3 days +) + +// LogFailedAuth records a failed authentication attempt +// WARNING: This logs passwords which is a security risk. Ensure database is properly secured. +// IMPORTANT: IP blocking only applies to admin panel attempts (/@/*), not API endpoints +func (h *Handler) LogFailedAuth(ipAddress, userAgent, path, username, password string) error { + failedAuth := &model.FailedAuth{ + IPAddress: ipAddress, + UserAgent: userAgent, + Timestamp: time.Now(), + Path: path, + Username: username, + Password: password, // SECURITY WARNING: Storing attempted passwords + } + + if err := h.DB.Create(failedAuth).Error; err != nil { + log.Error("Failed to log authentication failure:", err) + return err + } + + // CRITICAL: Only check for IP blocking if this was an admin panel attempt + // API endpoints (like /nic/update, /update, /v2/update, /v3/update) should NOT trigger blocking + if strings.HasPrefix(path, "/@/") { + log.Infof("Admin panel failed auth from %s - checking for IP block", ipAddress) + go h.CheckAndBlockIP(ipAddress) + } else { + log.Infof("API endpoint failed auth from %s on %s - NOT checking for IP block", ipAddress, path) + } + + return nil +} + +// CheckAndBlockIP checks if an IP has exceeded failed attempts and blocks it +// ONLY COUNTS FAILURES TO ADMIN PANEL (/@/*), NOT API ENDPOINTS +func (h *Handler) CheckAndBlockIP(ipAddress string) error { + // Count failed attempts to ADMIN PANEL ONLY in the lookback period + var count int64 + lookbackTime := time.Now().Add(-LookbackPeriod) + + err := h.DB.Model(&model.FailedAuth{}). + Where("ip_address = ? AND timestamp > ? AND path LIKE '/@/%'", ipAddress, lookbackTime). + Count(&count).Error + + if err != nil { + log.Error("Failed to count authentication failures:", err) + return err + } + + log.Infof("IP %s has %d failed ADMIN PANEL attempts in last %v", ipAddress, count, LookbackPeriod) + + // If exceeded threshold, block the IP + if count >= MaxFailedAttempts { + return h.BlockIP(ipAddress, int(count), "Exceeded maximum failed admin authentication attempts") + } + + return nil +} + +// BlockIP adds an IP to the blocked list +func (h *Handler) BlockIP(ipAddress string, failureCount int, reason string) error { + // Check if IP is already blocked + var existingBlock model.BlockedIP + err := h.DB.Where("ip_address = ?", ipAddress).First(&existingBlock).Error + + if err == nil { + // Update existing block + existingBlock.FailureCount = failureCount + existingBlock.LastAttemptAt = time.Now() + existingBlock.BlockedUntil = time.Now().Add(BlockDuration) + existingBlock.Reason = reason + + if err := h.DB.Save(&existingBlock).Error; err != nil { + log.Error("Failed to update blocked IP:", err) + return err + } + + log.Warnf("Updated block for IP %s (failures: %d)", ipAddress, failureCount) + return nil + } + + // Create new block + blockedIP := &model.BlockedIP{ + IPAddress: ipAddress, + BlockedAt: time.Now(), + BlockedUntil: time.Now().Add(BlockDuration), + FailureCount: failureCount, + IsPermanent: false, + LastAttemptAt: time.Now(), + Reason: reason, + } + + if err := h.DB.Create(blockedIP).Error; err != nil { + log.Error("Failed to block IP:", err) + return err + } + + log.Warnf("Blocked IP %s for %v (failures: %d, reason: %s)", + ipAddress, BlockDuration, failureCount, reason) + + return nil +} + +// IsIPBlocked checks if an IP address is currently blocked +func (h *Handler) IsIPBlocked(ipAddress string) (bool, *model.BlockedIP, error) { + var blockedIP model.BlockedIP + err := h.DB.Where("ip_address = ?", ipAddress).First(&blockedIP).Error + + if err != nil { + if err == gorm.ErrRecordNotFound { + return false, nil, nil + } + return false, nil, err + } + + // Check if block is still active + if blockedIP.IsBlocked() { + return true, &blockedIP, nil + } + + return false, nil, nil +} + +// UnblockIP removes an IP from the blocked list +func (h *Handler) UnblockIP(ipAddress string) error { + result := h.DB.Where("ip_address = ?", ipAddress).Delete(&model.BlockedIP{}) + if result.Error != nil { + log.Error("Failed to unblock IP:", result.Error) + return result.Error + } + + log.Infof("Unblocked IP %s", ipAddress) + return nil +} + +// GetClientIP extracts the real client IP from the request +func GetClientIP(r interface{}) string { + // This function can be enhanced to check X-Forwarded-For, X-Real-IP headers + // For now, we'll use a simple extraction + + // You'll need to pass the Echo context here + // This is a helper that should be called from middleware + return "" +} + +// CleanupExpiredBlocks removes expired blocks from the database +func (h *Handler) CleanupExpiredBlocks() error { + result := h.DB.Where("is_permanent = ? AND blocked_until < ?", false, time.Now()). + Delete(&model.BlockedIP{}) + + if result.Error != nil { + log.Error("Failed to cleanup expired blocks:", result.Error) + return result.Error + } + + if result.RowsAffected > 0 { + log.Infof("Cleaned up %d expired IP blocks", result.RowsAffected) + } + + return nil +} + +// CleanupOldFailedAuths removes old failed authentication records +func (h *Handler) CleanupOldFailedAuths() error { + // Keep records for 30 days + cutoffTime := time.Now().Add(-30 * 24 * time.Hour) + + result := h.DB.Where("timestamp < ?", cutoffTime).Delete(&model.FailedAuth{}) + + if result.Error != nil { + log.Error("Failed to cleanup old failed auths:", result.Error) + return result.Error + } + + if result.RowsAffected > 0 { + log.Infof("Cleaned up %d old failed authentication records", result.RowsAffected) + } + + return nil +} + +// GetBlockedIPs returns all currently blocked IPs +func (h *Handler) GetBlockedIPs() ([]model.BlockedIP, error) { + var blockedIPs []model.BlockedIP + err := h.DB.Order("blocked_at DESC").Find(&blockedIPs).Error + return blockedIPs, err +} + +// GetRecentFailedAuths returns recent failed authentication attempts +func (h *Handler) GetRecentFailedAuths(limit int) ([]model.FailedAuth, error) { + var failedAuths []model.FailedAuth + err := h.DB.Order("timestamp DESC").Limit(limit).Find(&failedAuths).Error + return failedAuths, err +} diff --git a/dyndns/handler/security_dashboard.go b/dyndns/handler/security_dashboard.go new file mode 100644 index 0000000..f040c25 --- /dev/null +++ b/dyndns/handler/security_dashboard.go @@ -0,0 +1,96 @@ +package handler + +import ( + "net/http" + "net/url" + + "github.com/labstack/echo/v4" +) + +// ShowSecurityDashboard displays the security overview page +func (h *Handler) ShowSecurityDashboard(c echo.Context) error { + + // Get recent failed auths + failedAuths, err := h.GetRecentFailedAuths(50) + if err != nil { + return c.JSON(http.StatusBadRequest, &Error{err.Error()}) + } + + // Get blocked IPs + blockedIPs, err := h.GetBlockedIPs() + if err != nil { + return c.JSON(http.StatusBadRequest, &Error{err.Error()}) + } + + // Count active blocks + activeBlocks := 0 + for _, blocked := range blockedIPs { + if blocked.IsBlocked() { + activeBlocks++ + } + } + + return c.Render(http.StatusOK, "security_dashboard", echo.Map{ + "failedAuths": failedAuths, + "blockedIPs": blockedIPs, + "activeBlocks": activeBlocks, + "title": h.Title, + "logoPath": h.LogoPath, + "poweredBy": h.PoweredBy, + "poweredByUrl": h.PoweredByUrl, + }) +} + +// ShowBlockedIPs displays all blocked IPs +func (h *Handler) ShowBlockedIPs(c echo.Context) error { + + blockedIPs, err := h.GetBlockedIPs() + if err != nil { + return c.JSON(http.StatusBadRequest, &Error{err.Error()}) + } + + return c.Render(http.StatusOK, "blocked_ips", echo.Map{ + "blockedIPs": blockedIPs, + "title": h.Title, + "logoPath": h.LogoPath, + "poweredBy": h.PoweredBy, + "poweredByUrl": h.PoweredByUrl, + }) +} + +// ShowFailedAuths displays recent failed authentication attempts +func (h *Handler) ShowFailedAuths(c echo.Context) error { + // Auth check removed - middleware handles this + + failedAuths, err := h.GetRecentFailedAuths(100) + if err != nil { + return c.JSON(http.StatusBadRequest, &Error{err.Error()}) + } + + return c.Render(http.StatusOK, "failed_auths", echo.Map{ + "failedAuths": failedAuths, + "title": h.Title, + "logoPath": h.LogoPath, + "poweredBy": h.PoweredBy, + "poweredByUrl": h.PoweredByUrl, + }) +} + +// UnblockIPHandler handles the unblock IP request +func (h *Handler) UnblockIPHandler(c echo.Context) error { + // Get IP from URL parameter and decode it + encodedIP := c.Param("ip") + ipAddress, err := url.QueryUnescape(encodedIP) + if err != nil { + return c.JSON(http.StatusBadRequest, &Error{"Invalid IP address format"}) + } + + if err := h.UnblockIP(ipAddress); err != nil { + return c.JSON(http.StatusBadRequest, &Error{err.Error()}) + } + + return c.JSON(http.StatusOK, echo.Map{ + "message": "IP unblocked successfully", + "ip": ipAddress, + }) +} diff --git a/dyndns/handler/session.go b/dyndns/handler/session.go new file mode 100644 index 0000000..314b9fc --- /dev/null +++ b/dyndns/handler/session.go @@ -0,0 +1,81 @@ +package handler + +import ( + "encoding/gob" + "net/http" + "os" + + "github.com/gorilla/sessions" + "github.com/labstack/gommon/log" +) + +// Session wraps gorilla session +type Session struct { + *sessions.Session +} + +// SessionStore wraps gorilla session store +type SessionStore struct { + store *sessions.CookieStore +} + +// InitSessionStore creates a new session store with a secret key +func (h *Handler) InitSessionStore() error { + // Generate or get session secret from environment + secret := []byte(h.GetSessionSecret()) + + // Create cookie store + store := sessions.NewCookieStore(secret) + + // Configure session options + store.Options = &sessions.Options{ + Path: "/", + MaxAge: 24 * 60 * 60, // 24 hours default + HttpOnly: true, + Secure: false, // Will be set to true per-request if HTTPS + SameSite: http.SameSiteStrictMode, + } + + h.SessionStore = &SessionStore{store: store} + + // Register types for session encoding + gob.Register(map[string]interface{}{}) + gob.Register([]interface{}{}) + + return nil +} + +// Get retrieves a session +func (s *SessionStore) Get(r *http.Request, name string) (*Session, error) { + sess, err := s.store.Get(r, name) + if err != nil { + return nil, err + } + return &Session{Session: sess}, nil +} + +// GetSessionSecret returns the session secret key +// Uses environment variable or generates a random one +func (h *Handler) GetSessionSecret() string { + // Try to get from environment + secret := h.GetEnv("DDNS_SESSION_SECRET", "") + + if secret != "" { + return secret + } + + // If not set, generate a warning and use admin password as base + log.Warn("DDNS_SESSION_SECRET not set! Using derived key. Set this in production!") + + // Use admin login hash as base for session secret + return h.Config.AdminLogin + "-session-secret-key" +} + +// GetEnv gets environment variable with default +func (h *Handler) GetEnv(key, defaultValue string) string { + value := os.Getenv(key) + if value == "" { + return defaultValue + } + return value +} diff --git a/dyndns/main.go b/dyndns/main.go index f63d104..8f7c396 100644 --- a/dyndns/main.go +++ b/dyndns/main.go @@ -3,9 +3,10 @@ package main import ( "html/template" "net/http" + "strings" "time" - "github.com/benjaminbear/docker-ddns-server/dyndns/handler" + "github.com/w3K-one/docker-ddns-server/dyndns/handler" "github.com/foolin/goview" "github.com/foolin/goview/supports/echoview-v4" "github.com/go-playground/validator/v10" @@ -22,7 +23,7 @@ func main() { e.Use(middleware.Logger()) - // Set Renderer + // Set Renderer with custom template functions e.Renderer = echoview.New(goview.Config{ Root: "views", Master: "layouts/master", @@ -31,6 +32,24 @@ func main() { "year": func() string { return time.Now().Format("2006") }, + "hasPrefix": func(s, prefix string) bool { + return strings.HasPrefix(s, prefix) + }, + "slice": func(s string, start, end int) string { + if start < 0 { + start = 0 + } + if end > len(s) { + end = len(s) + } + if start > end { + return "" + } + return s[start:end] + }, + "mod": func(i, j int) int { + return i % j + }, }, DisableCache: true, }) @@ -49,63 +68,87 @@ func main() { e.Logger.Fatal(err) } + // Parse environment variables and initialize session store authAdmin, err := h.ParseEnvs() if err != nil { e.Logger.Fatal(err) } - // UI Routes - groupPublic := e.Group("/") - groupPublic.GET("*", func(c echo.Context) error { - //redirect to admin - return c.Redirect(301, "./admin/") + // Apply IP blocker middleware globally + e.Use(h.IPBlockerMiddleware()) + + // Apply cleanup middleware + e.Use(h.CleanupMiddleware()) + + // Public redirect (root redirects to admin) + e.GET("/", func(c echo.Context) error { + return c.Redirect(http.StatusMovedPermanently, "/@/") }) - groupAdmin := e.Group("/admin") + + // Admin routes with session-based authentication and HTTPS redirect + groupAdmin := e.Group("/@") + + // Apply HTTPS redirect middleware (only for admin routes) + groupAdmin.Use(h.HTTPSRedirectMiddleware()) + + // Login routes (no auth required) + groupAdmin.GET("/login", h.ShowLoginPage) + groupAdmin.POST("/login", h.HandleLogin) + + // Logout route (no auth required - handles its own session check) + groupAdmin.GET("/logout", h.HandleLogout) + + // Protected admin routes (require authentication) if authAdmin { - groupAdmin.Use(middleware.BasicAuth(h.AuthenticateAdmin)) + groupAdmin.Use(h.SessionAuthMiddleware()) } + // Main admin pages groupAdmin.GET("/", h.ListHosts) + groupAdmin.GET("/hosts", h.ListHosts) groupAdmin.GET("/hosts/add", h.AddHost) groupAdmin.GET("/hosts/edit/:id", h.EditHost) - groupAdmin.GET("/hosts", h.ListHosts) - groupAdmin.GET("/cnames/add", h.AddCName) - groupAdmin.GET("/cnames", h.ListCNames) - groupAdmin.GET("/logs", h.ShowLogs) - groupAdmin.GET("/logs/host/:id", h.ShowHostLogs) - - // Rest Routes groupAdmin.POST("/hosts/add", h.CreateHost) groupAdmin.POST("/hosts/edit/:id", h.UpdateHost) groupAdmin.GET("/hosts/delete/:id", h.DeleteHost) - //redirect to logout - groupAdmin.GET("/logout", func(c echo.Context) error { - // either custom url - if len(h.LogoutUrl) > 0 { - return c.Redirect(302, h.LogoutUrl) - } - // or standard url - return c.Redirect(302, "../") - }) + + // CName routes + groupAdmin.GET("/cnames", h.ListCNames) + groupAdmin.GET("/cnames/add", h.AddCName) groupAdmin.POST("/cnames/add", h.CreateCName) groupAdmin.GET("/cnames/delete/:id", h.DeleteCName) - // dyndns compatible api - // (avoid breaking changes and create groups for each update endpoint) + // Log routes + groupAdmin.GET("/logs", h.ShowLogs) + groupAdmin.GET("/logs/host/:id", h.ShowHostLogs) + + // Security management routes + if authAdmin { + groupAdmin.GET("/security", h.ShowSecurityDashboard) + groupAdmin.GET("/security/blocked-ips", h.ShowBlockedIPs) + groupAdmin.GET("/security/failed-auths", h.ShowFailedAuths) + groupAdmin.POST("/security/unblock/:ip", h.UnblockIPHandler) + } + + // DynDNS API endpoints (HTTP allowed, BasicAuth required) + // These endpoints are used by routers/NVRs and need BasicAuth updateRoute := e.Group("/update") - updateRoute.Use(middleware.BasicAuth(h.AuthenticateUpdate)) + updateRoute.Use(h.UpdateAuthMiddleware()) updateRoute.GET("", h.UpdateIP) + nicRoute := e.Group("/nic") - nicRoute.Use(middleware.BasicAuth(h.AuthenticateUpdate)) + nicRoute.Use(h.UpdateAuthMiddleware()) nicRoute.GET("/update", h.UpdateIP) + v2Route := e.Group("/v2") - v2Route.Use(middleware.BasicAuth(h.AuthenticateUpdate)) + v2Route.Use(h.UpdateAuthMiddleware()) v2Route.GET("/update", h.UpdateIP) + v3Route := e.Group("/v3") - v3Route.Use(middleware.BasicAuth(h.AuthenticateUpdate)) + v3Route.Use(h.UpdateAuthMiddleware()) v3Route.GET("/update", h.UpdateIP) - // health-check + // Health-check endpoint (no auth) e.GET("/ping", func(c echo.Context) error { u := &handler.Error{ Message: "OK", diff --git a/dyndns/model/cname.go b/dyndns/model/cname.go index 81e64b8..ec68aaa 100644 --- a/dyndns/model/cname.go +++ b/dyndns/model/cname.go @@ -7,7 +7,7 @@ import ( // CName is a dns cname entry. type CName struct { gorm.Model - Hostname string `gorm:"not null" form:"hostname" validate:"required,hostname"` + Hostname string `gorm:"not null" form:"hostname" validate:"required,min=1"` //Alow 1 character cnames Target Host `validate:"required"` TargetID uint Ttl int `form:"ttl" validate:"required,min=20,max=86400"` diff --git a/dyndns/model/failed_auth.go b/dyndns/model/failed_auth.go new file mode 100644 index 0000000..49265cb --- /dev/null +++ b/dyndns/model/failed_auth.go @@ -0,0 +1,40 @@ +package model + +import ( + "time" + + "gorm.io/gorm" +) + +// FailedAuth tracks failed authentication attempts +// WARNING: This includes password logging which is a security risk. +// Passwords should be handled carefully and never displayed without authorization. +type FailedAuth struct { + gorm.Model + IPAddress string `gorm:"index;not null"` + UserAgent string + Timestamp time.Time `gorm:"index"` + Path string // The path they tried to access + Username string // Username they attempted (if provided) + Password string // Password they attempted (SECURITY RISK - handle carefully) +} + +// BlockedIP represents an IP that has been blocked +type BlockedIP struct { + gorm.Model + IPAddress string `gorm:"uniqueIndex;not null"` + BlockedAt time.Time `gorm:"index"` + BlockedUntil time.Time `gorm:"index"` // For temporary blocks + FailureCount int + IsPermanent bool // Flag for permanent blocks + LastAttemptAt time.Time + Reason string +} + +// IsBlocked checks if a block is still active +func (b *BlockedIP) IsBlocked() bool { + if b.IsPermanent { + return true + } + return time.Now().Before(b.BlockedUntil) +} diff --git a/dyndns/model/host.go b/dyndns/model/host.go index 9a1d3a6..136220c 100644 --- a/dyndns/model/host.go +++ b/dyndns/model/host.go @@ -9,13 +9,13 @@ import ( // Host is a dns host entry. type Host struct { gorm.Model - Hostname string `gorm:"unique_index:idx_host_domain;not null" form:"hostname" validate:"required,hostname"` + Hostname string `gorm:"unique_index:idx_host_domain;not null" form:"hostname" validate:"required,min=1"` // Allow 1 character hostnames Domain string `gorm:"unique_index:idx_host_domain;not null" form:"domain" validate:"required,fqdn"` Ip string `form:"ip" validate:"omitempty,ipv4|ipv6"` Ttl int `form:"ttl" validate:"required,min=20,max=86400"` LastUpdate time.Time `form:"lastupdate"` - UserName string `gorm:"unique" form:"username" validate:"min=3"` - Password string `form:"password" validate:"min=8"` + UserName string `gorm:"not null" form:"username" validate:"min=1"` // Allow 1 character usernames + Password string `form:"password" validate:"min=6"` // Minimum 6 character passwords } // UpdateHost updates all fields of a host entry diff --git a/dyndns/nswrapper/ip.go b/dyndns/nswrapper/ip.go index 90fba50..4dd94c4 100644 --- a/dyndns/nswrapper/ip.go +++ b/dyndns/nswrapper/ip.go @@ -8,7 +8,7 @@ import ( "net/http" "strings" - "github.com/benjaminbear/docker-ddns-server/dyndns/ipparser" + "github.com/w3K-one/docker-ddns-server/dyndns/ipparser" ) // GetIPType finds out if the IP is IPv4 or IPv6 diff --git a/dyndns/static/css/narrow-jumbotron.css b/dyndns/static/css/narrow-jumbotron.css index f348bbe..501c079 100644 --- a/dyndns/static/css/narrow-jumbotron.css +++ b/dyndns/static/css/narrow-jumbotron.css @@ -76,4 +76,4 @@ body { .jumbotron { border-bottom: 0; } -} \ No newline at end of file +} diff --git a/dyndns/static/icons/dns.ico b/dyndns/static/icons/dns.ico new file mode 100644 index 0000000000000000000000000000000000000000..3749d37bb8357c7ca4a2811520831da3aacc9b51 GIT binary patch literal 15406 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 literal 0 HcmV?d00001 diff --git a/dyndns/static/icons/eye.svg b/dyndns/static/icons/eye.svg new file mode 100644 index 0000000..b0d9e9c --- /dev/null +++ b/dyndns/static/icons/eye.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dyndns/static/icons/favicon.ico b/dyndns/static/icons/favicon.ico index 3749d37bb8357c7ca4a2811520831da3aacc9b51..3672f5b790c216c7f1816ca008ae168503ffdac5 100644 GIT binary patch literal 82350 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

=(C&vZCyU25Czh-JebFT+?@V zbXGPFxf7MIXXgouaZ|!5SaC5Waojrt@l;s z9aalbcNa2!d9;DXwDl{#t7pzXGqJ-jhofhD>yh(-9sD}V)CXTplQwknb8zr@t@doQ z^9;GN5L^_@!uikXeQ4oe zrUbt_dzicavz{8^2vaPs8t4 zUCP_|A$Z?l*l^bAqLHO!l{(6VL$m0?N-)HX&)YhvmwU-?c>J#C;^VhRSY9DR`m}XwJ=@aj9Us%jA9KGxAXnMCk*F|PC@O|aEk>54 zJVtBg?O{9;aMDl?{#Kr+=MxPrAy8-5jUxutLz2u2`8sN~ilhik3q%-KE1Rm6D_>-c zb|7sXMOR>>ggjT8u}l*Jm_Mt_cRRi+pO%=ZrP~#H)d1)lF}qXMj&pKIw8u&fdrD zn{w=%)&kg0-{!f$nrR`+WrnDb@8pis1ze(5y{g1FK5pCF>MR%O`VxYPeBSIP3ebb}q}Oj!i7x5ENw0r3%k|G3<)Y!f zytkaw>e;+t&ls2cRq(!P?ZOgM<|9JO)!-E4ie6`XMF%|(H>Xx=TEnv)i!P%xjtM!O8885 zPd@@-vgBjC&Lw>=I7!(*E^)6GNe18C6ItU!x&^;CR_{74zbwz=KK`CFzTi<&8?|LG zx2IpuoS7kxlLtG_e7yKL^Q4R%BIdtWm;60)gcw*nhH#8v`;fD927=G6Nm&z!J8JBluc%y}tom$AFCu3qqwD)EIWZuL-%H-gU-rCG zDO_jkdn*wg!_$KHx0}rnH{e-+U8%ZDibxHucV@dG64%%T^N?k1sRE-fjGSsriZ}82 zc79lG(-(031`(|T#dMkEP%f2rjzf6>8kg=^s?1g!Z13P9{K)8!#cw6!g?fw2=^z<rMf+Z(%&6RBWc=NlSUhl8X*ivsJJPk3DMNMJ zRXzZx@c5IR0*XSAHM2%efcO<6AiUOyUhMEXZ05U$_qQG+NUI;w+yeg4k= z5-gOTs#&6JyT4yQ-~Rc<#&Y>?6+ubeN$M$lX`*r&bg2Wc{?xV4w|@MxQT*|Gw0a5*s03{ zIJTFMenH0`S+6cxSb2Qul+|TshOgq3cO+ zx9mPN!Q}WJLq>q6p8nijAiDx{;kl}#Xq!D}Nf;$vBu(A|)4aY+kF-?f@)K#$M)t=) zBf|UNQ#eIjD^{?iMSdRTL{7M2$17%Y=O|<$(tJXlsS?;Z7JqmxB{3SZsxJ@mX%(d;Le6tDL`{&K zLgTfB`Pt}Q=Yuc9>uB0V1mhMYJlr}GdaJ8ca@=2w<{`w{xE14k2y7z6L17uzOJ+9xLnY;^2l+`%N%{`G86_!yb4Rv;^BkYs z;GTRny#P2e-h3s#=Z!xCRZQSYGE>S}= zpwm{(;U>UVEgK!EtRy5^4~`63{N<`Dy#m+ixagNOBv9?R4k-}AyIk<6VL7+6Dl1a= z-3O6qe<_Kcls;f_FE1 z)fWU@lwp)Vyxq{15?H-VjJ#P^B3Wo3id_j(;EQ%{iO=K1o?#cV?;~&YrX7R+Ius33 zhW%ENbEAsQQ*6(Yj&01f(`hR|=g6&l<`6X)I&O-F)hP8uG38Wl>!SK`XLoV&guE=z z60e{ph@hDMFeV`<&g=E%|Lb#$ZDA7M7HvUJXt&0a7CWUDu=-~3{`83%7YUd@<2z*L zC}m*<8S|?Tk0K|RTHW&fbK+3`(~~CA!>RO zbMycqrj4E(O)6X39kDv2r7a*I{S&YcT1sDMhse8j(_3IbT&2J8DA+Z%mNa%?vA>V^ z)+oxPX0e0~SevVAx$MtwaxkR4di;=hc(t;N zVzOd_yN#%>6Q3_~UWD2RLO}COAAcOJ|GO)8a%Vr>y|I-P(Xymc1*5&Dz-9dI&>C+Z zl-mTH*O~rC+$hw>v`n^)ft3l?N?&}(Fc}Up5~|YP)WF;7r{|K;SUhD7?;RdA ziV8Gz2Ko6dI@(WfW!Iw3tt_sX_vI*_QN`xA+MPE3ku%viJeSXH6DxdBbFmeIac?@; zELLBpIRFljr*&w<(P?7J{HDZwpZGe13_D}v;MzNIgakWIA+x5-;_~13tFjZ2e*1gk zE^KRC%AZWB)rBY(tl6v)6oJzwbmAD69S*7CdHlFirmxn9E%}f^QZ!rHM5t`Rx_%i3!Y;Q!P|r7^hRl)bie2*NV89Beipa~Zd&aO>xjYgs#U!u9A5S_krnMY7X33=s$cpzutato}UG=tV5 zn7*QKQ$-XCi-{6*-z^=U4}1sl#^o&P;Tt*=?o`Wr+CHg>%nM-5uDivfB6eOhaq+@1 z@?*b_@4wpGH5Ipmw1%c2BXkFPDLu{-b>*JpgRJzxF1dfMz5b4^W!(4g?_=gh0_%mY zMjg>c0V16!eo^QsecpL#Sl)PYsEU83u?o9@1LO#jST{ev{~7;LJSrtMR*DCKiP+XF zm?n{&_#h(4%%=?ZW9IS-rt~G}`{t9GLD>zvlO@+p1h$vWfUaV}6h?)q{(TOy%K=$G zWpHKnxzmB0=zRZs!`||+$9fm^>4$on2dmKbbt4g^o$E-#+_SCK4Ufy;Wy`1ET78}H z`|t7m_>~YSf{fZ&Hh&FD#^AqnDyRO9!}H7g{MP)UUJ&Is)%G8)fX9nKcLmVyl*CJE z@6+BmjH#W;X3|_cP;wNZ4f$#F_HGFRxqc!vbmTwtV~ZFc$?%s0Z5Jvb zeMV1=zrI39`dBLd{b*NINUOr0z|>m3^o^@yW!59n>%#5Hf~y-00YeL1nXn59>=E*{N zW*sx^`2%dydU*Jz&cgoZ=iuyi@w8&9{yG;Z4zl+g>Nx^)W|8R;Wof zTkA~Syw5*-va_7qGN(d<1G^d;rE-w=-=lPXF>`6v^A*tuxlgk6Q}*064rITpXLhSa z*WG->f@FTMKeFx{<-M*LjcO!fsp-IwRYv}fMAY0_;10#;AyIx$or<8&_WW7lc-b^i9Z z4GjODQg2%Lv`ZR?Dpes%kt7#Ln`8t=T*OtFUUW(P6?v)6-cxs3R z+lg`sh#w?c)#LM_omy6J*kdXw4lJIXor^ZN9sFtRhNT2iZ0V^Gkner|UH7-0Uqbh@ zrIHhqkTqZHp$vmSc2`Nz_HDzHUB^Cq>A{z*GiFi8<+F}ymRk{Vk(GOSWB)e>cg)+o zuj(-=Qlr1IVKH1ss&8IgPgZ{2Y!XbGob0}sRBb(#ET*P_e%m4k#Ot#C1M1b7cFI8D!B5iQRk*n?*5Aa5FfQt z=X85!quKB>ptZvfIvvbbQeOe`-k1y+pKunGw zE(cis#j}Vme`jIf>Viu}8tpZFsuygJq6YAdJ?F*dGh9PQmk>-;5r5L8_8{e9CCTgJUSXm6OLVvrOE7(hg3&fK8XJdm~zG4W+!^5D}NZ>7HfNwFK_ z3l105etHET$mEa``aT|x(CJ*rQi=pK0N?V@X|V2Gar<5YLXG)OUUNm@8 z8*U9Zm`=Sl#h*I~Jeu0r;&i-yFgvYN;DHFCMN+6wE9@m(Q>FsDT87m0!_}E_BXAcN zF454y#P3@GS{OQnp~Zwz>I!J2!5s&n9>o%2K|u`j;qVOl%MHCa+Wd+Xr|23)xAY zM}9A4HNR_fx~80FU42>c=bJT%r;tq^5{tRJ%PztkkaDHz>VtPWEV6KX>NyiTb#($y!^%^yUb1 znM7=$f)Id=g#~L9a8zJqPc(98`HqOi*9F8gzGuTn5m)2xrfvHkjtK$_l{Mghcq_jU z^5x6(Ag=g6HThpIsW-VZ%=yxic!d~6&O#yP^Tb_dwdu4S_Dfr!-e=d1Uvau|qUIC& zW2+hOQFZ;&1N^b=z2cJY9dpC@5t7j50xAe9AQ9 zgl_woZv8oQ61>0DTV4?K#9F>1vZyy&(_-+?JF8vSnZ6HWk?Dx0KJ=sVk&%*|&hrLE zZ0FI0q}9l_=}kg)GDx2b8tJ`7Q(1Q|c@T@^f7==_P`)rGJ%07vYl@M@8bDwZMjo+u z5hQ!g4*afxq0o>_GcRdSCu93b+5o1z{^>Y(SY z8&Ncwe_K<;MGKCU5>RC#d`rS6_mO(|jTK%+(Hk{^)boVDGC6o)qoOb@qcW0@NVtNq z1wOIN@nUksGq|?t2iTljxuCtU@%3s>rOxJE#MZ$Pa;zYy2!CQ6?JP1b#-4P*gTh+9 z-?7le0L612&oQWh7LF=oMU`ZH&$Zf@r-QRRDh^C_e11Po{TIFYWeraZ9x)4tnsN{MjcR1H!a!<12`iOq zEpBaA`z0~fCS}XJH`%RAyR~AE-7vO|HF0Dr=8IxUiN)VX5y0=zkF!I}-BC)WDUB&=;>1&!tRf8>DBc5e@@sXrQtbM-{U z^{o?68v74blKqh()Q$y(T=JUze_?NnS`1Z^XKlr@SfqpRbJ!tw8Ft4j$aas5zrLL{ z196^rgct+-Y=qZ};lb!A?2ldzzc0Mmdl?M?A{w)zQ2vO9li3Q^w-V$i?MpO1FF!40 zwFRi$eN?kj>Qs)Xu`83Mc3F-?tB?nw-AZ{dB$g+!73c@8-D5|fWxjN!nd)7l$*qT?a=9&+mFA|(Q&Ah5@ zs?HuaP0g@+c{EFRy~mnaX$^Us+KW-zqk-uzY&le%{hYUxy|Mt0+tByZ0#gktdl^PF!msGQwQDnd%OxK>wPxpew35!qH2AVjD2!U8{pXHZP-7TjxiKc8LhO|R z#dEPt&N_VXIoR}K)U3{iz>g?z`g9iM)2`eDW<@NT35qa6vs$RT44PP@Mh{ElctBPs z>z`^b8XfZW&CPX33WA$2k`?Mz%E}&lfzu3#3L5F8eHa=2I|^}<+>|?gye;Hn5jAgp z8!5qEOTZYwRt0ccX7)kQ6!7 zAt^qDMIbqtHV)TJhgMFh9%m(kVw)~0?V z9c;`q(N#meu%c3KGx=wNC_+F&Z}?=kU+ED7G!d{|E9IoH!rrP z15uq;*9)Ij>$9hGT)CiPQ`ET--WNTma1v{OTbcK5i1BulUJV6}S9yD66B|Gr&>Jp? zFt1N2dSs4sb6J)&@J~%70ysOYBM--?R$ny<@$+$r=S^_EyU}eo;1%>b z2y%bI!B%SMYcn;ize?=>unKZBa-n`hIn1@@-}52!DRE0B;5fK}oBj33{DM=B+=oug zKni4}bDIJXZppLy5jGrnQc1Q38i2J|@;{Xhn52y#VA3Ck{+K$^6x_LJ)j3UiXFy2P zZ4%gLVt?-?#PTt1@+W9>|5ze0E!HOa?Cx=0$=oHB09UD0wQ3%B5_MT@p{_hV)I7dn z3-27iijeX25)X}J`uusT`q1`qTW+Yb>#XIA%`=*EC#FI|S>8<9?t?26YI-LU4+Qy{ zphkaTf-cploc0>KQB#yuDZvjFg?=p!)k}xktx?ekJ7>i#?%lihhW9sB=k-8jByd)% z(Su*@%45A4aRq35c-1Cf{b8~IuLR%T#3e(zVfDi#t+Lq@J_~^t)uuQf_b0y(S0N3c z-B$rpY7(GBG{zH0Hkrb_49#lGE^a(*k*_RT#mh6_h_^TdB_>(%{Tf%E!ZE6Kz}*NI z@9&0a#!}&@-=HXeM@$3~C%AY+Xn8A+*0GhL=wcX70r4UlxO5~`+LmpkNC5#gAa0jj zb6O$$18k%Att!1mXK1AEA{@>3X{(t_+P$uRAFzYm1ikJ9=0=`RKZ`BN%5Im4xd_c4 zBv)^qPW^0I6oGi5>5(-4g<^+N9D$yS%fRk(a$*w0mz&9|W+5O8!UyLDr^|un4a9~e z#ZbShRKcM*vkgsa^{GERvz)+%++;Qu?U4CN>pDL7_eV8cd}^Yd_8dp=URpG+41--I zm28v0;(3`@XH93fZaum{UQAbOYwa_8gaC?p=I@3Br#1pR$*JuvjvH(AYHM&eqA3jz zX_X>M+IattGj7V-*S8s|^a0X}!6@0wHRg|ZzY|_mjD7^~$rrtfF`xaSvfr9=M#q>| zN{{1Jn8u6~vV;?~Q}+9JcL(6@Uh1tjq68I1{QLAXmZ~2%7j_!b`<9`A_qQ&fkuazAp#C2AcJ5{;k-e_#fFdhBjXRjz)FvWC{R_^c6f}I0 z2h^8y+kfY$3*_rLWJOJM5GuqhtkRHav+vo=kMF^6@S*a{OnB)C*XTK6gH8qVA>`Bbq>aw(Q#jgZ z1Th@ddVj@_1m+|UHbSom)qw>z%xU3|q?~hJXQ)eDYR8{B+_?OccWUtW5OR2)Ssh_~Tc;R5(s;sh53DNFlUB z9R>-9sDAwMQz?g!2s;@E>nuZ((+9lO`YTt#$;HFe(-s9Iz?tZ9RWu~uzY;1jnaRDq zm&e&)ujFp7U%xh2lGMgOy46Z~qlB##=JBKL*DsxM=-O#U%e6^LW?UgQnPLBpDsrYb zq|;O_Q}|{tq9WH@GIyjbuttK8hLyMN)xDY>E;+GYzkoS-13IHm#oTwKGXk5}Aojb_ z{y16%MwW`W9J=P_JG5qXkKD?HepE`Op1b0J$JriH=;<%@@4w8%L%hzH6L=p!G(_^Q z#27*&`TR4t9#^MC^M>`f!$&jPYy8Ar6oKSLMC_ZO7*y#^RFNx35)0@r0}vcwVh*Vl z0gj>RQ^gUM>0UG8F8*L-IF-2B^BONBt0aapEx;(G#4EH2DI|Dy9@qSy^jC&cHnR~b z>B6h2F;fxTdvap4=IvBq&gH`Pr(UcRn{JF#7#~MmU-MP`7cxw`KP?I@&2@Uekc`Zj zE%9P6_>E3O?PcDK=Gf?*g0+a@miEC^Uzi*PbAD0}GWM$*7ZCN>`M}%65PeUog8mJz z`7^%S59XfRyZ3sTB`!17=>qb9CiXBwU$Oarr{n@2Fq4-I`lF}xx`Y=Q2#nVd2R{po z7rD#E)8_uVU%z%v-HEJ?e9OTnq$plL_oU3c=HpPIE&qMriYVY9)9IikE4D_HDajyI}YKy$6yH^==;&)8`(>}=8h*ljQSLomO-xuY^}2p3Om)YX&bP0 z9>70Z-3;93sk!G>9Qr#{#Ie#iU_!5BJRV0g6JftZy4?Y;vT^UqY*SO<$Qo>*fzMOt zY2ap4ShAQuw@j%rH2hviFQq6>p$YIW&eH+0rR=44h49xVemxAJv z^>AqfMibGplv*Fy)q{q^!y=_>MI|6wJQ?GsB$bFF)zD{4zYQf#W*c}3T9ZXs1G8gD<4A+MI+T5#>t`vNRlYi7ba1=Wc2G16b(!nn%!XhC{Tror%B&GPhM%CnS_Hi9=05&k zRCh3#KLeSSRBrsL*S@`Rn$)0*#ewwB*Yd^c`s`M5*HSX^JFcwwj3#zF(F{Wp0U#n z;xCrtt|`96_ay#Zt`M0#HyzWtZW5iE!CL?N1E?i7zDk-?%rk%O-1ch~2x1kzAcWZT zMB&1GUVQ~4Ooe9yPJK#~#pc^H#<@p*1QD517fmY|-~wJ`!)65WrYecvv@LXoC|9_y+((rUNhrcuW>WBKvo<}_1bOB)`1tF z`pFA*_kG)1dcLUP%F2_da#xOnUfW;Za;=7!){e%jNLjuXVBDcxrk9?UmX$+JmNTj_ zF}_FmiN3cDqe1dkarfSfTjrzL6=7o0WjyBRxl&dYKInL&tczqSsJ=c}v}k$V_c`(m zss=Qow-8vF@SHFrbwQazQ}yD-20HFj-kj1VEaQ68d&4fJedmiIWNY96Mhf$L_~()3 zw-@Rl>T=@cz<(?3AhN@qlH)$hej)n!fTcwS8DBl&sWxg@vhCPv96-ytA0~olh9U1m)TZVgK^n`VQXn#L(Co8=#Ggm1(T7h|Ud<0ty- zlQbVxYI5RZ|BXR3bzH!5_@shE#eP7?bM_B&;LMfd1+D+Utu_f`0SD}o?A2l{SKj5@ zJ4;8l7w91BPq~)hl>3tZ zwO*LnUsSa*J{!|Bg%;HiB=`RR`sE$+_6y?a(a(KQaXg6lvq$pJ-VH89Md?p48 z8&JR?3g8t4GD82KxBt(V9}s8I&mcEYc1$2BDcBvH6zl=ci1G(l<|HFj=cgl8=cl1G zmgONgR~8}E6lQ|!OY^|3HD%zQmU_@gZzrgup$atE_51%7KgQD;Vth?uhG}y}5p!2lHCks=4S1ll6+AK2 z2kL8Y295Uj{NH3mxmke1oJ{}21x34Cfzu=WFacT0sbM}DSuugp1wSJ-3X`HG^AjV? z3zK8Y$})cZsme>ctuM>l{$2YkyQj5YaHzWtWu&hQJTox@;CF*&#z+4jGAuzq1Kq%h z5SRA}z7EOX{hiJLexA}J{ho5-L$8XGV`hJ4{rp*5l%-Txk|PKd=f?8Fp5HaUE`igv zwgFx|0R3?%hWmj@$pmP6d>FL6FbDdNm)QR)Hedjk61bP9Xm`u|cwYx7fDcQL^oIfX zP(Xhupf?oI`x4;2qqVxstgESp5h!1Ke>ZjvcD0?35A}VYogBjhbO+A@=ldo ztp2C?7^@>hdsy>1X~^xGi1R#s`y>c+P?3Q~yV-03_*(!k5C0nw zOLMadfbQVMxfxIlpntrtJqN(aLY%kVC15}2abEV&U>jq&i?$-%P=pKqpDYbF+CyV8 z-lk|*+p6;HlI$~M+kv6le-lS4eg07q^B z?~j1}JpBl8fdRGy%Zv_y`vp>}34a~To1COaKmosH(sSd`~1%-$SYSCR(@ z@ZkVHbbPQ63gG{3C@l=H$WEVIUtN)K)lvp!MnRAO?5qgqZ%{xE6yOP#8s-hpj0uG0 z{RoE@rNqI0WhTN)QWN0u-p(*vMQNB8Cj%U~AGn>8G`u)D0S449Y<#HyKYZwDfA3>P zWXRKKUyn<9W-1tBt_KcxGV`#M6L~Tc<$_wti(SQf+SLMWX(~T4^1dWB9$KEA1O?hF zaL|1oyskJ0o)!jy+bBr=&;1w(alw-Uyne1vLo(vP`+Rn`V<-ljDD`&dOz@~$hVQ3f8`@|)&DXrO^MqJwKav>D@nt2 zd0C(u>~xz3f}A#~p?=KO1sUFe-fK^O6!T>)21MKo2z5`ecJx>CwJ%XHquB}3sO&GMMif+1+1sB3f58k3to|#1dDREhFVAqU+eHN zH<*a=>19L)Ndq>M+*Vg{4768Re@6=(@M$mr_aDH!*W}0`W|XrnX>sz8j@9L*M*!b< zb!idY|GN&{+f-!>$an={tz~a5 zFW74UIPPw&`XBt^u2$GsZ#!(fzY~!23mRr;4%6Xf`Oj|dzDWt)=l_U-5BGM$0em>n zrU1?w9)Z6<0snsj?qM3Z2U5TWz}E8OpthG}H+_aUTr6~!!0RG8;_!9%& z@X6sm*!VyXyf`TVW+^KQv^6@okq9@eC@BF7@B*8go`3_qLsLTop6rxlt{cmWJz6Tt z5c9vsf+8I)L0)?5;3SBLh>1Agt{U@)r?1>hyAh6-se|oJRTG0f7gNLiuxUWg*@+Q& zS7R+KE!ZC(Xkl~-F*7*tYiqfipPq!REG<9*yS(~cUE#GbJB`~~RSudO9R~fUzrU$2 zsNj1PN|>#guO1)kjVd4m@ClcOLY(!f!G2kT?Jes7K6Gw!6t*xk30<0X# z^G}TqnE-fu|JCWn`r2Q>H!x>Lh9DXWae*9FKZ63z4M4fEVIV(aZE#si0v;eEO^=`L zMwN;B3Ah(1AmyuGC`gQ72k_4TyLaoX zufhD`>w)NJtONEj`uab8j5a3&s5&PVmb zD@{#=^|v*{rU1@s^0V&&KXC)de+;%U``b}h9c3mV5UtC{8V~4hW+1?!Zm%qh_@981 z(1(DqHFG~{^f^*id>nKnnHz#u6kyWm5E1!kDG8#OgNUWi7tZqClLx= zURDTz9SP{~Xe7+7=%^x(@PF|Cdv7So1qE3efdG340qqK;FTf54>H!Q~15OU`L2N87 zKmqtg?QU*B0=PoZ=4Ju|Z33Je=nD$?W(WfGKycGmL2y)+NAS|u1VhXWK`;>H{PJ1^g25zY8#WdI3cLf0qCCz5lNHU)TKszIzFj2cSHIj9>mQ=j8Q< literal 15406 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 diff --git a/dyndns/static/icons/logo.png b/dyndns/static/icons/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..545ec001716ed7ac053adfb65ef5f5456b385d4d GIT binary patch literal 69847 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

yHeL@866qxhoD(p`c8pY%b zT`G(4c>+Dt=~`8*@L5O>-m78Be*)&Op|pc=Dm3L{BP$ue%10RBU%5YI?WyP%gy2>q z)!;oI_#if~M9=-?=;Q=ljlWm75f#Hn7ZIT^oDvMmm}}p7QPx3MFuSPUB%#=8+~YQ5udgZM*ugFPY3PChv5yTY(9~ zog8wB#6fKh@8~;xLafC6`OOPPI-Pf9|7Vi_w2745S+=O72#zMMZ290t2`&LUg^ z+-bdgNav=_bnwTSGReKh^7yKJn7nSeSrE%Nl8FrG>aQT$z_nk5@n?85>y>ZSJMcJq z`RUmbw7P@pDho&bY`xcGwxjsxbF%}Fg>{H{k4#&L^iWSyFQ&+B=&-ZeiI-Z!zq|sO zPydj0!J}(jWhmu5!73em1&f=#61;zKuGeWYS-XhN?S9=2{Mwf5iOoW0+AXB4o1B*_ zNHNOuU~33>w~A3DzyMRma*7UL?evP)94IQp$_4Zd^&z1Dn9NlK*tV|0M+>)C#Y%{4 zgjxE7dA;jKRzbNTHK}cj>T!;auCyv9Bd#+c-mWzbSzE zDkHBJAvRkqD6zv5XoDT}G>9&mCxmVf!xW z{;&spPjqHRq`Y3K{U971492*NPKB+&KMr!xRLSjqtC=o-vhmjoKb$UI8J+NOFW||% zZUJQ7Jd};4!N(%XJ^you(u$AdIUs2?RtN|1MLfKnvv7|#G90MpX7#MO;(*n-c0xoa;opyzP0 z^X)|s^#)41h4*?sL3_JpVfPjvbB3<^h76}Gfmcoz=(OhRnLzLf*?p32cYP~abj6si zP6b%w(pFM5##l#Y@jp-DHZdYft!j@=?a{PT24T#zFat7k&wptuDwZeK>h@aHE}9V< z8GtHLyjni+E7qQz%9Klw1}EBY%q$ zP9~$IuoX3i^uE~CDC8lwF$1m2sGh8a=+zx4YAq=(c5V@UJkC<9>_@s?h1}gCBEBBHr+{m%`j`>KKjs?rp z|L!H2z+M_8+FYLys91R_j%#g(VN3&7{0ig%c=)UhC?_ zPp}27v(eH!)8&OA5}OacK(Hm#kJ5`j0sd%WpBgZm?4S4}LP?1FTr%vFLHx}pE~`xw{+rtQDLo5yK|Y_ZKS1X$SshbQ*lHmvR7sInE(!`__YkUiSRcoCQPk<;rB?*`+%q$}$*IyGRD4TpAfLHU13mi|u4G8xlN5xhY zMti;C2b|RRr&`IUJOfPr#L%}*@FQaf%9w(*a;=Amo?qhplXXmy9l-%y-}`P-0R$}G zrilUcfgeF{@jnIpTxPgkswNajgPE=;Iq%AiZQz%*jdH)%=Q9O3y~Q)|1tM1@hxU z6_oFPSedT~sIxp9aQK9u%nQIFefJuh9O#E_f#I12aP8Z1C27!r3v}vdKF-gsPlHG* z_sKypMF0kY=GZ?|>_0$KU~Z`(q2~@z)1G2L&N|jd2v;1JGn@!$Et`9{>*{)b3Rvu4TY zJyFl|+JEzG>TU$_55^E0vo8sRA{E8kXErr z3I;vOAG;r0s%66bsaK*eVw+y;n%hYHMSoePE1D;}C{55LKXp|f>tCukfRl%5)-hGi zAKeo;i7{e^HD0pLeyM$GHacq--=yzhd1qDOm?@%DRM*xP(;^`V_2;1_YG}_#QZ4=OL#JHHLJwUISF> z-t71WUOr!K64-~)vI4gen_m*Nm9x$h_Wi*-9hlOtWNULxZ>Lr-8B=z%On;m?N{Q5h7$fDOucK)^$;7B3un3_M5=QJ z%leKF?OEMdR;xVHu~$vuOk&`gs%qyE~FtOiBZ7K0Oyi{BX+ub^1?G(ui+%##!GW4wv;;H`h9WDF#H@)Qyd zp0+0x5ReA77}s@I?p58gB?D#M1lCBc8)kIEX4`GQ;8I%*LZs<$FR}A4-p{i~-;0L- z;QGHZ7CBUEy#IZEab*s|rNm?VZc#jQ2~0}NM4cgxJ6azS&`>li^FkD97Zk0iAj`rh z)2z1yt$;+m|80=sfKgQ3KRn;>Y%O6-IB_enG^|t}swtlQ)Ps^#@mYgHO@gIbz^*|z z%EvJ6q*S%R#|8M!tm-eLpr4H9w50B^sa_`C0PrRxxnJ2Q{I7KUj{x1h7E25t%cvn& zewL*Hp*^UcQh3TFT;a@6NPWS!-E}#J{*=|~8-ABhbdlZ#m?SC_fqUOXXG07Pz$?q1 z`2|u!61ek@6pmr&O{H0%XsTA>xAhQ%K_0$eIx;N2&mM}jj{!)iW+{_NBnSu_+G8kR zi(%)RrL~WmQ-Yfhi`4Hy6n?NP-81j^`s?i3jg#pEi`!FA#XT9+8a^QB#}(K;;~qin zp_9k}o&wzu5k9_KkS)L&35nKgdODhP{Trf>KH&{G@6947w=xoq(^+B_G344EGbZbB zh=y2=zpsP_3@E-Xf;%v4hezWSMX+C@4IX)!*~W8MB`))IUM%T+Uh}nvI;(5?if_Xs zJKdTk!3fBb(uL+OSJ91RtYw>TWkjKcKb|l$OKB8cm?^zXN5B?b7lwA6+iV**x?}-! zikAAF#0Q=MM5xXvt$%@ZpCHWTWzjqI!0OPVdD0UCT4O3I+*~|MH3*#(#HdMa>kVr1 z5e~Z*ia}}UsqrJCoZQ)Brm6;iz|Rwetj;hoWT$s5%J-+xA}OX11NrU;eRBs4avgsXUF&YWw)NR!~J#2JsdIA6X zvg+s}Gha$zLl%tsB@6J?Y`iuoCyisVvT6#*fFgfA-T-gjYKLyjOBy0LM$ z)wOR>6Nt%|hM@!s%K7NL#5i!CXIHov0Xi1W(q|ZU`In0STvY~e1IE1PVGT8ZkC{>1 zlN)=^g4+fK@H)N$0;*8?qPgnWRmFQh%k#)M;(9t!e8Wji=gA;fEqn}^`EfwU@AEsvFP%NzwtEt z*_2_E%`;$@(Zk6|7^@lB!4m2d6_H;+=1}rZqB~S}<#`M5Og2)Rx;>x03p)pG?=ew$VXpIDzKx6CVC@frfi9=EhBBEAE|Az3ox zfIro$rg_pkeNB-$h=&`$W}g06pDK zpxVP(XdB}oYtaw>bfH*XJKOQ6w-1*DCyJt~6WR&(hNol?@qJjyEkkDy$sg-<@*C*-GX9>JZbc)(;E%;gfO*3mCYR9;k_~JV$5(y?rIS)fP8<>ysVC&*_c2&l|^%JDElTei7 zf&iU`pN0KHG@~tcD4W?bRM*$A|2SubSou}(?e?(!eShHb>T`6AU}14?L8|X$Rg5IE z_&KSFfKK8k(BHa$vFe_)%ww68?l*@&(cPLCLK`lik4VfydU&Kl*~BQ22`akI{+7vX zi}SaMq+(y%&f6Y4)eSF+RY5>H$NQWB)q`65E>PEudK=b^r6V6#QzA~vp^x7Jp#0$X z_z!|PHevw?>9$>qtYvhhc>b*ZOeEa{kwF7$pUVamXXQ-G-TTlSdI*|K#wf=SBiJwP zk2GeHvX{ic66jXIv3OMzNdAiJ>0Zlo@@q(qITtrN zT^Mb$^t!%=L=4V;7k!D!*Lt zmEZ-BRMbRV#I%V|{9@U|$hOTV=@e{0uD2t_ecU2+B*GMa9+M7B;z6xt1ar7U>#P5& z#jBB|$tI@wywNxT41N(BuEly;dd6Zqse1>NfRx~325nuyaB4UK>c=d7@OR%`(&?1g z5rJXc!wGweg&V7kFXZ0c;Ls`Hm!L=a gKVJ+Eaq$oskW+9(vUP5|4gfyKes{L5vGz^+FL)A{A^-pY literal 0 HcmV?d00001 diff --git a/dyndns/static/js/actions-1.0.0.js b/dyndns/static/js/actions-1.0.0.js index 1331251..ee0fd3f 100644 --- a/dyndns/static/js/actions-1.0.0.js +++ b/dyndns/static/js/actions-1.0.0.js @@ -1,173 +1,144 @@ -$("button.addHost").click(function () { - location.href='/admin/hosts/add'; -}); - -$("button.editHost").click(function () { - location.href='/admin/hosts/edit/' + $(this).attr('id'); -}); +// Wrap ALL event handlers in document.ready to ensure DOM is loaded +$(document).ready(function(){ + + // ===== HOST BUTTONS ===== + $("button.addHost").click(function () { + location.href='/@/hosts/add'; + }); -$("button.deleteHost").click(function () { - $.ajax({ - contentType: 'application/x-www-form-urlencoded; charset=UTF-8', - type: 'GET', - url: "/admin/hosts/delete/" + $(this).attr('id') - }).done(function(data, textStatus, jqXHR) { - location.href="/admin/hosts"; - }).fail(function(jqXHR, textStatus, errorThrown) { - alert("Error: " + $.parseJSON(jqXHR.responseText).message); - location.reload() + $("button.editHost").click(function () { + location.href='/@/hosts/edit/' + $(this).attr('id'); }); -}); -$("button.showHostLog").click(function () { - location.href='/admin/logs/host/' + $(this).attr('id'); -}); + $("button.deleteHost").click(function () { + $.ajax({ + contentType: 'application/x-www-form-urlencoded; charset=UTF-8', + type: 'GET', + url: "/@/hosts/delete/" + $(this).attr('id') + }).done(function(data, textStatus, jqXHR) { + location.href="/@/hosts"; + }).fail(function(jqXHR, textStatus, errorThrown) { + alert("Error: " + $.parseJSON(jqXHR.responseText).message); + location.reload() + }); + }); -$("button.add, button.edit").click(function () { - let id = $(this).attr('id'); - if (id !== "") { - id = "/"+id - } + $("button.showHostLog").click(function () { + location.href='/@/logs/host/' + $(this).attr('id'); + }); - let action; - if ($(this).hasClass("add")) { - action = "add"; - } + // ===== CNAME BUTTONS ===== + $("button.addCName").click(function () { + location.href='/@/cnames/add'; + }); - if ($(this).hasClass("edit")) { - action = "edit"; - } + $("button.deleteCName").click(function () { + $.ajax({ + contentType: 'application/x-www-form-urlencoded; charset=UTF-8', + type: 'GET', + url: "/@/cnames/delete/" + $(this).attr('id') + }).done(function(data, textStatus, jqXHR) { + location.href="/@/cnames"; + }).fail(function(jqXHR, textStatus, errorThrown) { + alert("Error: " + $.parseJSON(jqXHR.responseText).message); + location.reload() + }); + }); - let type; - if ($(this).hasClass("host")) { - type = "hosts"; - } + // ===== ADD/EDIT FORM BUTTONS ===== + $("button.add, button.edit").click(function () { + let id = $(this).attr('id'); + if (id !== "") { + id = "/"+id + } - if ($(this).hasClass("cname")) { - type = "cnames"; - } + let action; + if ($(this).hasClass("add")) { + action = "add"; + } - $('#domain').prop('disabled', false); - - $.ajax({ - contentType: 'application/x-www-form-urlencoded; charset=UTF-8', - data: $('#editHostForm').serialize(), - type: 'POST', - url: '/admin/'+type+'/'+action+id, - }).done(function(data, textStatus, jqXHR) { - location.href="/admin/"+type; - }).fail(function(jqXHR, textStatus, errorThrown) { - alert("Error: " + $.parseJSON(jqXHR.responseText).message); - }); + if ($(this).hasClass("edit")) { + action = "edit"; + } - return false; -}); + let type; + if ($(this).hasClass("host")) { + type = "hosts"; + } -$("#logout").click(function (){ - //document.execCommand("ClearAuthenticationCache"); - try { - // This is for Firefox - $.ajax({ - // This can be any path on your same domain which requires HTTPAuth - url: "", - username: 'reset', - password: 'reset', - // If the return is 401, refresh the page to request new details. - statusCode: { 401: function() { - // document.location = document.location; - } - } - }); - } catch (exception) { - // Firefox throws an exception since we didn't handle anything but a 401 above - // This line works only in IE - if (!document.execCommand("ClearAuthenticationCache")) { - // exeCommand returns false if it didn't work (which happens in Chrome) so as a last - // resort refresh the page providing new, invalid details. - // document.location = location.protocol+"//reset:reset@" + document.location.hostname + document.location.pathname; - } + if ($(this).hasClass("cname")) { + type = "cnames"; } - console.log("first logout") -}); -$("button.addCName").click(function () { - location.href='/admin/cnames/add'; -}); + $('#domain').prop('disabled', false); -$("button.deleteCName").click(function () { - $.ajax({ - contentType: 'application/x-www-form-urlencoded; charset=UTF-8', - type: 'GET', - url: "/admin/cnames/delete/" + $(this).attr('id') - }).done(function(data, textStatus, jqXHR) { - location.href="/admin/cnames"; - }).fail(function(jqXHR, textStatus, errorThrown) { - alert("Error: " + $.parseJSON(jqXHR.responseText).message); - location.reload() - }); -}); + $.ajax({ + contentType: 'application/x-www-form-urlencoded; charset=UTF-8', + data: $('#editHostForm').serialize(), + type: 'POST', + url: '/@/'+type+'/'+action+id, + }).done(function(data, textStatus, jqXHR) { + location.href="/@/"+type; + }).fail(function(jqXHR, textStatus, errorThrown) { + alert("Error: " + $.parseJSON(jqXHR.responseText).message); + }); -function newTargetSelected() { - var sel = document.getElementById("target_id"); - var x = sel.options[sel.selectedIndex].label.replace(sel.options[sel.selectedIndex].text, ''); - document.getElementById("domain_mirror").value = x; -} + return false; + }); -$("button.copyToClipboard").click(function () { - let id; - if ($(this).hasClass('username')) { - id = "username"; - } else if ($(this).hasClass('password')) { - id = "password"; - } + // ===== LOGOUT BUTTON ===== + $("#logout").click(function (){ + // Note: The old HTTP Basic Auth logout code is commented out since we use sessions now + // The logout link goes to /@/logout which handles session destruction + console.log("Logout clicked - redirecting to /@/logout"); + }); - let copyText = document.getElementById(id); - copyText.select(); - copyText.setSelectionRange(0, 99999); - document.execCommand("copy"); -}); -$("button.copyUrlToClipboard").click(function () { - let id = $(this).attr('id'); - let hostname = document.getElementById('host-hostname_'+id).innerHTML - let domain = document.getElementById('host-domain_'+id).innerHTML - let username = document.getElementById('host-username_'+id).innerHTML - let password = document.getElementById('host-password_'+id).innerHTML - let out = location.protocol + '//' +username.trim()+':'+password.trim()+'@'+ domain - out +='/update?hostname='+hostname - - let dummy = document.createElement("textarea"); - document.body.appendChild(dummy); - dummy.value = out; - dummy.select(); - document.execCommand("copy"); - document.body.removeChild(dummy); -}); + // ===== CLIPBOARD BUTTONS ===== + $("button.copyToClipboard").click(function () { + let id; + if ($(this).hasClass('username')) { + id = "username"; + } else if ($(this).hasClass('password')) { + id = "password"; + } -function randomHash() { - let chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - var passwordLength = 16; - var password = ""; - for (var i = 0; i <= passwordLength; i++) { - var randomNumber = Math.floor(Math.random() * chars.length); - password += chars.substring(randomNumber, randomNumber +1); - } + let copyText = document.getElementById(id); + copyText.select(); + copyText.setSelectionRange(0, 99999); + document.execCommand("copy"); + }); - return password; -} + $("button.copyUrlToClipboard").click(function () { + let id = $(this).attr('id'); + let hostname = document.getElementById('host-hostname_'+id).innerHTML + let domain = document.getElementById('host-domain_'+id).innerHTML + let username = document.getElementById('host-username_'+id).innerHTML + let password = document.getElementById('host-password_'+id).innerHTML + let out = location.protocol + '//' +username.trim()+':'+password.trim()+'@'+ domain + out +='/update?hostname='+hostname + + let dummy = document.createElement("textarea"); + document.body.appendChild(dummy); + dummy.value = out; + dummy.select(); + document.execCommand("copy"); + document.body.removeChild(dummy); + }); -$("button.generateHash").click(function () { - let id; - if ($(this).hasClass('username')) { - id = "username"; - } else if ($(this).hasClass('password')) { - id = "password"; - } + // ===== GENERATE HASH BUTTONS ===== + $("button.generateHash").click(function () { + let id; + if ($(this).hasClass('username')) { + id = "username"; + } else if ($(this).hasClass('password')) { + id = "password"; + } - let input = document.getElementById(id); - input.value = randomHash(); -}); + let input = document.getElementById(id); + input.value = randomHash(); + }); -$(document).ready(function(){ + // ===== TOOLTIPS ===== $(".errorTooltip").tooltip({ track: true, content: function () { @@ -175,9 +146,28 @@ $(document).ready(function(){ } }); + // ===== NAVIGATION HIGHLIGHTING ===== urlPath = new URL(window.location.href).pathname.split("/")[2]; if (urlPath === "") { urlPath = "hosts" } document.getElementsByClassName("nav-"+urlPath)[0].classList.add("active"); }); + +// ===== UTILITY FUNCTIONS (outside document.ready is OK) ===== +function newTargetSelected() { + var sel = document.getElementById("target_id"); + var x = sel.options[sel.selectedIndex].label.replace(sel.options[sel.selectedIndex].text, ''); + document.getElementById("domain_mirror").value = x; +} + +function randomHash() { + let chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + var passwordLength = 16; + var password = ""; + for (var i = 0; i <= passwordLength; i++) { + var randomNumber = Math.floor(Math.random() * chars.length); + password += chars.substring(randomNumber, randomNumber +1); + } + return password; +} diff --git a/dyndns/views/blocked_ips.html b/dyndns/views/blocked_ips.html new file mode 100644 index 0000000..ac3ec00 --- /dev/null +++ b/dyndns/views/blocked_ips.html @@ -0,0 +1,98 @@ +{{define "content"}} +
+

Blocked IP Addresses

+ + + + + + + + + + + + + + + + + + {{range .blockedIPs}} + + + + + + + + + + + {{end}} + +
IP AddressBlocked AtBlocked UntilLast AttemptFailure CountReasonStatusActions
{{.IPAddress}}{{.BlockedAt.Format "01/02/2006 15:04"}}{{if .IsPermanent}} + Permanent + {{else}} + {{.BlockedUntil.Format "01/02/2006 15:04"}} + {{end}}{{.LastAttemptAt.Format "01/02/2006 15:04"}}{{.FailureCount}}{{.Reason}} + {{if .IsBlocked}} + Active + {{else}} + Expired + {{end}} + + {{if .IsBlocked}} + + {{else}} + N/A + {{end}} +
+ + {{if not .blockedIPs}} + + {{end}} +
+ + +{{end}} diff --git a/dyndns/views/failed_auths.html b/dyndns/views/failed_auths.html new file mode 100644 index 0000000..407ced5 --- /dev/null +++ b/dyndns/views/failed_auths.html @@ -0,0 +1,362 @@ +{{define "content"}} +
+

Failed Authentication Attempts

+ + + + + + + + + + + + + + + + + + + + + + {{range $index, $auth := .failedAuths}} + {{$isAPI := not (hasPrefix $auth.Path "/@/")}} + {{$rowClass := ""}} + {{if $isAPI}} + {{if eq (mod $index 2) 0}} + {{$rowClass = "table-warning"}} + {{else}} + {{$rowClass = "table-warning-light"}} + {{end}} + {{end}} + + + + + + + + + {{end}} + +
TimestampIP AddressUsernamePassword AttemptedPathUser Agent
{{$auth.Timestamp.Format "01/02/2006 15:04:05"}}{{$auth.IPAddress}}{{if $auth.Username}}{{$auth.Username}}{{else}}no credentials{{end}} + {{if $auth.Password}} +
+ +
+ {{$auth.Password}} +
+
+ {{else}} + no password + {{end}} +
+ {{$auth.Path}} + {{if hasPrefix $auth.Path "/@/"}} + Admin + {{else}} + API + {{end}} + + {{if $auth.UserAgent}} + {{$ua := $auth.UserAgent}} + {{if gt (len $ua) 10}} +
+ +
+ {{$ua}} +
+
+ {{else}} + {{$ua}} + {{end}} + {{else}} + none + {{end}} +
+ + {{if not .failedAuths}} + + {{end}} + + +
+ + +{{end}} diff --git a/dyndns/views/layouts/master.html b/dyndns/views/layouts/master.html index 8b84234..4aaecec 100644 --- a/dyndns/views/layouts/master.html +++ b/dyndns/views/layouts/master.html @@ -8,6 +8,16 @@ {{.title}} + + + + + + + + + @@ -17,6 +27,42 @@ + @@ -27,40 +73,38 @@ -

{{.title}}

+ + {{ if .logoPath }} + + {{ else }} +

{{.title}}

+ {{ end }} {{template "content" .}} - - - - - - - - - \ No newline at end of file diff --git a/dyndns/views/listhosts.html b/dyndns/views/listhosts.html index 7aaa62e..f2ebf2a 100644 --- a/dyndns/views/listhosts.html +++ b/dyndns/views/listhosts.html @@ -51,4 +51,12 @@

DNS Host Entries

-{{end}} \ No newline at end of file + +{{ if .migrationReport }} +
+ Database Migration Report: +

The following legacy entries were automatically converted to lowercase. You can copy this for your records.

+ +
+{{ end }} +{{end}} diff --git a/dyndns/views/login.html b/dyndns/views/login.html new file mode 100644 index 0000000..ba19e3f --- /dev/null +++ b/dyndns/views/login.html @@ -0,0 +1,211 @@ +{{define "content"}} + + + + + +{{end}} diff --git a/dyndns/views/logout.html b/dyndns/views/logout.html new file mode 100644 index 0000000..08b9bcc --- /dev/null +++ b/dyndns/views/logout.html @@ -0,0 +1,202 @@ +{{define "content"}} + + +
+
+ {{if .logoPath}} + + {{end}} + +
πŸ‘‹
+

Logged Out Successfully

+

Your session has been ended and you have been securely logged out.

+ + {{if .logoutUrl}} +
+

Redirecting in 7 seconds...

+

+ to: {{.logoutUrl}} +

+ +
+ {{end}} + + +
+
+ + +{{end}} diff --git a/dyndns/views/security_dashboard.html b/dyndns/views/security_dashboard.html new file mode 100644 index 0000000..99e69e8 --- /dev/null +++ b/dyndns/views/security_dashboard.html @@ -0,0 +1,433 @@ +{{define "content"}} +
+

Security Dashboard

+ + +
+
+
+
Active Blocks
+
+

{{.activeBlocks}}

+

IP addresses currently blocked

+
+
+
+
+
+
Failed Attempts
+
+

{{len .failedAuths}}

+

Recent failed login attempts

+
+
+
+
+
+
Total Blocks
+
+

{{len .blockedIPs}}

+

Total IP blocks (active + expired)

+
+
+
+
+ + +

Recently Blocked IPs

+ + + + + + + + + + + + + {{range .blockedIPs}} + {{if .IsBlocked}} + + + + + + + + + {{end}} + {{end}} + +
IP AddressBlocked AtBlocked UntilFailuresStatusActions
{{.IPAddress}}{{.BlockedAt.Format "01/02/2006 15:04"}}{{if .IsPermanent}}Permanent{{else}}{{.BlockedUntil.Format "01/02/2006 15:04"}}{{end}}{{.FailureCount}}Active + +
+ + +

Recent Failed Authentication Attempts

+ + + + + + + + + + + + + + + + + + {{range $index, $auth := .failedAuths}} + {{$isAPI := not (hasPrefix $auth.Path "/@/")}} + {{$rowClass := ""}} + {{if $isAPI}} + {{if eq (mod $index 2) 0}} + {{$rowClass = "table-warning"}} + {{else}} + {{$rowClass = "table-warning-light"}} + {{end}} + {{end}} + + + + + + + + + {{end}} + +
TimestampIP AddressUsernamePasswordPathUser Agent
{{$auth.Timestamp.Format "01/02/2006 15:04:05"}}{{$auth.IPAddress}}{{if $auth.Username}}{{$auth.Username}}{{else}}none{{end}} + {{if $auth.Password}} +
+ +
+ {{$auth.Password}} +
+
+ {{else}} + none + {{end}} +
+ {{$auth.Path}} + {{if hasPrefix $auth.Path "/@/"}} + Admin + {{else}} + API + {{end}} + + {{if $auth.UserAgent}} + {{$ua := $auth.UserAgent}} + {{if gt (len $ua) 10}} +
+ +
+ {{$ua}} +
+
+ {{else}} + {{$ua}} + {{end}} + {{else}} + none + {{end}} +
+ + +
+ + +{{end}} diff --git a/img/addcname.png b/img/addcname.png new file mode 100644 index 0000000000000000000000000000000000000000..e9c6a35e13f3b51201d2d8416ab30c5a3c20d802 GIT binary patch literal 118968 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 ?sE#h+N=Akqtuo`lxGfz8W zJWdKMJJiLWOd?*~Ge6GR+zH$|TF*ZDObX$9VOXz_M)U3}%%mQ5e7-!$J- z`sUo;nL~Y zq1%JQ8g?F^nk;ov<>;%B@(J@F-(#Fh#hsf&UU!CBhb8oMYIu)eFvkWUn5Yp1{Z1oh zxx67c%FWzcL1R5{!;`-yqLyH1n4{0{{P`ZlRQ+Dx#9D&}+a@(_`1uayaGRp!DF(5A z5wuOx0PJHhXua%{$G&X41@6lq7N$mu(Z4LzFqs8mBCn2e;5_4XsL*cjT=%<1+1%3) zM4JZh4tyrAd$~rk`~q(9xvDafJnZHlcbaFS?Ys7GXh+7G%_l?WlM6jRbsOSwl^-zA zHtnvu!HIeW2eNULIx2eS?YiWpcMr9|ih|2b*-KGs-$P=b@wc2nFnfDaR1+j*-cZzNO-Xwvi_DL$ZI*5HV1ZCGw7SYV<>`9i zwa+_o#c6}PJ>s;wkpQh;D~f6aPvs7G$L*&b8r0zwjFCR&lQ3P{XKP7~c-W{4^rd-2 z_9g6|1|Tdi9ItRH_&qRiwV*2AO+h6D5kJRnUuW4ClPc3DYOaOl&g6+2%H9NAaFz;G z?oI3{YdNm+E`n38;wkMsXdswoQMVhx5Ru$5JPLD`hKi#q&sx*TJQ3z%Gy ze2((++@b9bp=QmMW>%Nqf#=KU*MZ&XK5d)87q|i63!I2M$m!gfnGUQzTti1>)$bfc zav>LU0x5)p&J^Jh?2d3fRBtH%r(L~M@an5fxDNKane(qe+L;&9{8NESy#j5Q(k>Uj zH2QR?C9wbm4D*P1VVH!BR(ZKvNsbA>aZ+m%j6ZRV-}tG^8&-o8WYyF*E3?FQ%8mUm zx~BNiPW?bP4*|0!t@gBN~M^%X)5r6tb~A}0v1w>0O7p0g@y zwjOpbqm0(P=d-Bl#4s2CpSrYX?wW z4-!`=9bAZpiw6^=SsH-G003&bJJ6EV1gSXK!f*-KQEE|Y@7s62nw%EbMBw%{oB}UY zo{bTn>;tS~Q-|o!TX8oSy{KWjE9G`E7JCNNFu(_}Hzy_W8#!dcg^eN7oOE}4FX>5GyZl2FRYUt8PsNX({ zcxHc)fsVT9S⋘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