diff --git a/Makefile b/Makefile index b128833..019785c 100644 --- a/Makefile +++ b/Makefile @@ -1,33 +1,207 @@ -IMAGE_NAME = laravel-app -CONTAINER_NAME = laravel-app +IMAGE_NAME = authentikate +CONTAINER_NAME = authentikate PORT = 8889 -VERSION = "latest" +VERSION = latest +TEST_PORT = 8890 +TEST_CONTAINER_NAME = authentikate-test -.PHONY: build run rebuild +# Default target +.DEFAULT_GOAL := help # Make sure to get set up with octane and frankenPHP # since that's what the base docker image expects -setup: +setup: ## Install Laravel Octane with FrankenPHP composer require laravel/octane php artisan octane:install --server=frankenphp -# Build the Docker image -build: - npm run build - docker build -t $(IMAGE_NAME) . +# Docker build targets +build: ## Build the Docker image + @echo "Building Docker image: $(IMAGE_NAME):$(VERSION)" + docker build -t $(IMAGE_NAME):$(VERSION) . + @echo "โœ… Image built successfully" -# Run the container -run: - docker run -it --rm -p $(PORT):8000 -e APP_DEBUG=true --name $(CONTAINER_NAME) $(IMAGE_NAME) +build-no-cache: ## Build the Docker image without cache + @echo "Building Docker image without cache: $(IMAGE_NAME):$(VERSION)" + docker build --no-cache -t $(IMAGE_NAME):$(VERSION) . + @echo "โœ… Image built successfully" -# Rebuild (force rebuild without cache) -rebuild: - docker build --no-cache -t $(IMAGE_NAME) . +# Run targets +run: ## Run the container (production-like) + @echo "Starting container: $(CONTAINER_NAME)" + @docker stop $(CONTAINER_NAME) 2>/dev/null || true + @docker rm $(CONTAINER_NAME) 2>/dev/null || true + docker run -d \ + --name $(CONTAINER_NAME) \ + -p $(PORT):8000 \ + -e APP_NAME="AuthentiKate" \ + -e APP_ENV=production \ + -e APP_DEBUG=false \ + -e APP_KEY=base64:$$(openssl rand -base64 32) \ + -e APP_URL=http://localhost:$(PORT) \ + -e DB_CONNECTION=sqlite \ + -e DB_DATABASE=/app/database/database.sqlite \ + -v authentikate_keys:/app/storage/oauth \ + -v authentikate_db:/app/database \ + -v authentikate_storage:/app/storage \ + $(IMAGE_NAME):$(VERSION) + @echo "โœ… Container started on http://localhost:$(PORT)" + @echo "๐Ÿ“‹ Check logs with: make logs" + @echo "๐Ÿ”‘ Admin credentials will be shown in the logs" -docker-publish: - docker image tag flowtodo:latest gitgud.foo/thegrind/flowtodo:$(VERSION) - docker push gitgud.foo/thegrind/flowtodo:$(VERSION) +run-test: ## Run a test container with different settings + @echo "Starting test container: $(TEST_CONTAINER_NAME)" + @docker stop $(TEST_CONTAINER_NAME) 2>/dev/null || true + @docker rm $(TEST_CONTAINER_NAME) 2>/dev/null || true + docker run -d \ + --name $(TEST_CONTAINER_NAME) \ + -p $(TEST_PORT):8000 \ + -e APP_NAME="AuthentiKate Test" \ + -e APP_ENV=testing \ + -e APP_DEBUG=true \ + -e APP_KEY=base64:$$(openssl rand -base64 32) \ + -e APP_URL=http://localhost:$(TEST_PORT) \ + -e DB_CONNECTION=sqlite \ + -e DB_DATABASE=/app/database/test.sqlite \ + -v authentikate_test_keys:/app/storage/oauth \ + -v authentikate_test_db:/app/database \ + -v authentikate_test_storage:/app/storage \ + $(IMAGE_NAME):$(VERSION) + @echo "โœ… Test container started on http://localhost:$(TEST_PORT)" + @echo "๐Ÿ“‹ Check logs with: make logs-test" -test-remote-image: - docker pull gitgud.foo/thegrind/flowtodo:latest - docker run --rm -p 8889:8000 gitgud.foo/thegrind/flowtodo:latest +run-interactive: ## Run container interactively for debugging + @echo "Starting interactive container" + @docker stop $(CONTAINER_NAME)-debug 2>/dev/null || true + @docker rm $(CONTAINER_NAME)-debug 2>/dev/null || true + docker run -it --rm \ + --name $(CONTAINER_NAME)-debug \ + -p $(PORT):8000 \ + -e APP_ENV=local \ + -e APP_DEBUG=true \ + -e APP_KEY=base64:$$(openssl rand -base64 32) \ + -e APP_URL=http://localhost:$(PORT) \ + -e DB_CONNECTION=sqlite \ + -e DB_DATABASE=/app/database/debug.sqlite \ + $(IMAGE_NAME):$(VERSION) \ + /bin/bash + +# Container management +stop: ## Stop the running container + @echo "Stopping container: $(CONTAINER_NAME)" + @docker stop $(CONTAINER_NAME) 2>/dev/null || echo "Container not running" + @docker rm $(CONTAINER_NAME) 2>/dev/null || echo "Container already removed" + +stop-test: ## Stop the test container + @echo "Stopping test container: $(TEST_CONTAINER_NAME)" + @docker stop $(TEST_CONTAINER_NAME) 2>/dev/null || echo "Container not running" + @docker rm $(TEST_CONTAINER_NAME) 2>/dev/null || echo "Container already removed" + +stop-all: ## Stop all containers + @echo "Stopping all containers" + @docker stop $(CONTAINER_NAME) $(TEST_CONTAINER_NAME) 2>/dev/null || true + @docker rm $(CONTAINER_NAME) $(TEST_CONTAINER_NAME) 2>/dev/null || true + +restart: stop run ## Restart the container + +restart-test: stop-test run-test ## Restart the test container + +# Logs and debugging +logs: ## Show container logs + docker logs -f $(CONTAINER_NAME) + +logs-test: ## Show test container logs + docker logs -f $(TEST_CONTAINER_NAME) + +logs-tail: ## Show last 50 lines of container logs + docker logs --tail 50 $(CONTAINER_NAME) + +shell: ## Open a shell in the running container + docker exec -it $(CONTAINER_NAME) /bin/bash + +shell-test: ## Open a shell in the test container + docker exec -it $(TEST_CONTAINER_NAME) /bin/bash + +# Full workflow targets +rebuild: build stop run ## Rebuild image and restart container + +rebuild-test: build stop-test run-test ## Rebuild image and restart test container + +rebuild-no-cache: build-no-cache stop run ## Rebuild without cache and restart + +test-full: build run-test ## Build and run test container + @echo "โณ Waiting for container to start..." + @sleep 10 + @echo "๐Ÿงช Running health check..." + @curl -f http://localhost:$(TEST_PORT) >/dev/null 2>&1 && echo "โœ… Health check passed" || echo "โŒ Health check failed" + @echo "๐Ÿ“‹ Test container logs:" + @docker logs $(TEST_CONTAINER_NAME) | tail -20 + +# Cleanup targets +clean: ## Remove containers and images + @echo "Cleaning up containers and images" + @docker stop $(CONTAINER_NAME) $(TEST_CONTAINER_NAME) 2>/dev/null || true + @docker rm $(CONTAINER_NAME) $(TEST_CONTAINER_NAME) 2>/dev/null || true + @docker rmi $(IMAGE_NAME):$(VERSION) 2>/dev/null || echo "Image not found" + +clean-volumes: ## Remove all volumes (WARNING: destroys data) + @echo "โš ๏ธ WARNING: This will destroy all data in volumes!" + @read -p "Are you sure? (y/N) " -n 1 -r; \ + if [[ $$REPLY =~ ^[Yy]$$ ]]; then \ + echo; \ + docker volume rm authentikate_keys authentikate_db authentikate_storage 2>/dev/null || true; \ + docker volume rm authentikate_test_keys authentikate_test_db authentikate_test_storage 2>/dev/null || true; \ + echo "โœ… Volumes removed"; \ + else \ + echo; \ + echo "โŒ Cancelled"; \ + fi + +clean-all: clean clean-volumes ## Remove everything (containers, images, volumes) + +# Development targets +dev-build-test: ## Quick development build and test cycle + @echo "๐Ÿ”„ Development build and test cycle" + @make build + @make run-test + @echo "โณ Waiting for container to start..." + @sleep 5 + @make logs-tail + @echo "๐ŸŒ Test instance available at: http://localhost:$(TEST_PORT)" + +# Status and info +status: ## Show container status + @echo "=== Container Status ===" + @docker ps -a --filter name=$(CONTAINER_NAME) --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + @docker ps -a --filter name=$(TEST_CONTAINER_NAME) --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + @echo + @echo "=== Volume Status ===" + @docker volume ls --filter name=authentikate + +info: ## Show image and container information + @echo "=== Image Information ===" + @docker images $(IMAGE_NAME):$(VERSION) --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}" + @echo + @echo "=== Container Information ===" + @docker inspect $(CONTAINER_NAME) 2>/dev/null | grep -E '"IPAddress"|"Ports"' || echo "Container not running" + +urls: ## Show accessible URLs + @echo "=== Available URLs ===" + @echo "Production: http://localhost:$(PORT)" + @echo "Test: http://localhost:$(TEST_PORT)" + @echo "Health: http://localhost:$(PORT)/up" + +# Help target +help: ## Show this help message + @echo "AuthentiKate Docker Makefile" + @echo "=============================" + @echo + @echo "Usage: make [target]" + @echo + @echo "Targets:" + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST) + @echo + @echo "Quick Start:" + @echo " make build run # Build and run production container" + @echo " make dev-build-test # Quick dev cycle" + @echo " make logs # View logs" + @echo " make clean # Clean up" \ No newline at end of file diff --git a/README.md b/README.md index 036ec88..84dda8a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,350 @@ ![banner](banner.png) -The SSO/OIDC solution for homelabbers. No more Authentik overcomplicating your life -(and taking 2G of ram) just so you can give your friends convenient access to your -services. \ No newline at end of file +The SSO/OIDC solution for homelabbers. No more Authentik overcomplicating your life (and taking 2G of ram) just so you can give your friends convenient access to your services. + +## What is AuthentiKate? + +AuthentiKate is a lightweight, self-hosted Single Sign-On (SSO) and OpenID Connect (OIDC) provider designed specifically for homelab enthusiasts. Built with Laravel and Livewire, it provides enterprise-grade authentication features without the complexity and resource overhead of larger solutions. + +### Key Features + +- **OpenID Connect Provider**: Full OIDC implementation with JWT tokens signed with RSA256 +- **PKCE Support**: Secure authorization code flow with Proof Key for Code Exchange +- **User Management**: Admin interface for managing users and invitations +- **Application Management**: Easy registration and management of OAuth applications +- **Auto-Approval**: Skip consent screens for previously authorized applications +- **Avatar Support**: User profile pictures with file upload +- **Responsive Design**: Modern UI built with Flux components and Tailwind CSS +- **Lightweight**: Minimal resource usage compared to alternatives +- **Docker Ready**: Containerized deployment with automatic setup + +## Quick Start with Docker + +The easiest way to run AuthentiKate is using Docker: + +```bash +# Build the container +docker build -t authentikate . + +# Run with basic configuration +docker run -p 8000:8000 \ + -e APP_NAME="AuthentiKate" \ + -e APP_ENV=production \ + -e APP_KEY=base64:$(openssl rand -base64 32) \ + -e APP_URL=https://auth.yourdomain.com \ + -e DB_CONNECTION=sqlite \ + -e DB_DATABASE=/app/database/database.sqlite \ + -v authentikate_keys:/app/storage/oauth \ # Important to keys persist between restarts + -v authentikate_db:/app/database \ + authentikate +``` + +### What Happens on First Run + +When the container starts for the first time, the following setup commands run automatically via `hook.sh`: + +1. **RSA Key Generation** (`php artisan app:generate-keys`) + - Generates 2048-bit RSA private/public key pair + - Keys stored in `/app/storage/oauth/private.pem` and `/app/storage/oauth/public.pem` + - Used for signing JWT tokens with RS256 algorithm + +2. **Initial Admin Creation** (`php artisan authentikate:create-admin`) + - Creates the first admin user account + - Prompts for email and name (or uses defaults) + - Generates a secure random password + - **Important**: Check the container logs for login credentials! + +### After First Run + +1. **Check the logs** for your admin credentials: + ```bash + docker logs + ``` + Look for output like: + ``` + โœ… Initial admin user created successfully! + + ๐Ÿ” Login Credentials: + ๐Ÿ“ง Email: admin@authentikate.local + ๐Ÿ”‘ Password: Xy9$kL2m@Qp3nR8t + + โš ๏ธ Please log in and change your password immediately! + ``` + +2. **Log in and change your password**: + - Navigate to your AuthentiKate URL + - Log in with the generated credentials + - Go to Settings โ†’ Profile to change your password + +3. **Configure your first application**: + - Go to Applications in the admin panel + - Create a new OAuth application + - Note the Client ID and Client Secret for your services + +## Manual Installation + +### Requirements + +- PHP 8.2 or higher +- Composer +- Node.js 18+ and npm +- Database (SQLite, MySQL, or PostgreSQL) +- Web server (Apache, Nginx, etc.) + +### Installation Steps + +1. **Clone the repository**: + ```bash + git clone authentikate + cd authentikate + ``` + +2. **Install dependencies**: + ```bash + composer install --no-dev --optimize-autoloader + npm install && npm run build + ``` + +3. **Environment configuration**: + ```bash + cp .env.example .env + php artisan key:generate + ``` + +4. **Configure your `.env` file**: + ```env + APP_NAME=AuthentiKate + APP_ENV=production + APP_DEBUG=false + APP_URL=https://auth.yourdomain.com + + # Database (SQLite example) + DB_CONNECTION=sqlite + DB_DATABASE=/path/to/database.sqlite + + # Mail configuration (for invitations) + MAIL_MAILER=smtp + MAIL_HOST=your-smtp-server + MAIL_PORT=587 + MAIL_USERNAME=your-email + MAIL_PASSWORD=your-password + MAIL_ENCRYPTION=tls + MAIL_FROM_ADDRESS=noreply@yourdomain.com + MAIL_FROM_NAME="AuthentiKate" + ``` + +5. **Set up the database**: + ```bash + php artisan migrate + ``` + +6. **Generate OIDC keys**: + ```bash + php artisan app:generate-keys + ``` + +7. **Create initial admin user**: + ```bash + php artisan authentikate:create-admin + ``` + +8. **Set up the web server**: + Configure your web server to serve the `public` directory. For Nginx: + ```nginx + server { + listen 80; + server_name auth.yourdomain.com; + root /path/to/authentikate/public; + index index.php; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + include fastcgi_params; + } + } + ``` + +9. **Set up queue worker** (optional but recommended): + ```bash + php artisan queue:work --tries=3 --daemon + ``` + +## Configuration + +### Environment Variables + +Key environment variables for AuthentiKate: + +- `APP_URL`: Your AuthentiKate instance URL (used in OIDC issuer) +- `DB_*`: Database connection settings +- `MAIL_*`: Email configuration for sending invitations +- `SESSION_DRIVER`: Session storage (database recommended for production) +- `CACHE_DRIVER`: Cache storage (redis recommended for production) + +### File Permissions + +Ensure proper permissions for: +- `storage/` directory: writable by web server +- `storage/oauth/`: contains RSA keys (600 for private key) +- `bootstrap/cache/`: writable by web server + +### SSL/TLS + +**Important**: AuthentiKate should always run behind HTTPS in production. OIDC requires secure connections for security. Configure your reverse proxy or web server to handle SSL termination. + +## Usage + +### Managing Applications + +1. Log in as an admin +2. Navigate to "Applications" in the sidebar +3. Click "New Application" +4. Fill in: + - **Name**: Display name for your application + - **Redirect URI**: The callback URL for your application +5. Save and note the generated Client ID and Client Secret + +### Managing Users + +1. Navigate to "User Management" +2. Create invitations for new users by email +3. Optionally send invitation emails automatically +4. Users can register using the invitation code +5. Promote users to admin status as needed + +### User Features + +- **Profile Management**: Users can update their name, email, and avatar +- **Auto-Approval**: Users can enable auto-approval for previously authorized apps +- **Token Management**: View and revoke active authentication tokens + +## Integration Examples + +### Grafana + +```yaml +# grafana.yml +auth: + generic_oauth: + enabled: true + name: AuthentiKate + client_id: your-client-id + client_secret: your-client-secret + scopes: openid profile email + auth_url: https://auth.yourdomain.com/auth/authorize + token_url: https://auth.yourdomain.com/auth/token + api_url: https://auth.yourdomain.com/auth/userinfo + use_pkce: true +``` + +### Traefik ForwardAuth + +```yaml +# docker-compose.yml +version: '3.7' +services: + traefik-forward-auth: + image: thomseddon/traefik-forward-auth:2 + environment: + - DEFAULT_PROVIDER=oidc + - PROVIDERS_OIDC_ISSUER_URL=https://auth.yourdomain.com + - PROVIDERS_OIDC_CLIENT_ID=your-client-id + - PROVIDERS_OIDC_CLIENT_SECRET=your-client-secret + - SECRET=your-secret-key + - AUTH_HOST=auth.yourdomain.com + - COOKIE_DOMAIN=yourdomain.com +``` + +## Troubleshooting + +### Common Issues + +1. **"Invalid client" errors**: + - Verify Client ID and Client Secret + - Check that redirect URI matches exactly + +2. **JWT verification failures**: + - Ensure RSA keys are properly generated + - Check that `/auth/keys` endpoint is accessible + +3. **Email not working**: + - Verify MAIL_* environment variables + - Check Laravel logs in `storage/logs/laravel.log` + +4. **Permission denied errors**: + - Check file permissions on storage directories + - Ensure web server can write to storage/cache + +### Logs + +Check the following logs for debugging: +- Application logs: `storage/logs/laravel.log` +- Web server logs: Check your web server's error logs +- Docker logs: `docker logs ` + +## Development + +### Running for Development + +```bash +composer install +npm install +cp .env.example .env +php artisan key:generate +php artisan migrate +php artisan app:generate-keys +php artisan authentikate:create-admin + +# Start development servers +composer run dev # Starts server, queue, logs, and vite +``` + +### Testing + +```bash +composer run test +``` + +The test suite includes comprehensive coverage for: +- OIDC endpoints and JWT generation +- User authentication and authorization +- Application management +- UUID-based user identification + +## Security + +- JWT tokens use RS256 signing with 2048-bit RSA keys +- PKCE support for secure authorization flows +- UUID-based user identification in tokens (not sequential IDs) +- Secure session management +- Input validation and CSRF protection +- Email verification for new users + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes with tests +4. Submit a pull request + +## License + +[Add your license information here] + +## Support + +For issues and questions: +- Check the troubleshooting section above +- Review logs for error details +- Open an issue on the repository + +--- + +**Remember**: Always run AuthentiKate behind HTTPS in production and keep your instance updated with security patches. \ No newline at end of file