first commit
This commit is contained in:
572
CLAUDE.md
Normal file
572
CLAUDE.md
Normal file
@@ -0,0 +1,572 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to AI assistants (like Claude Code) when working with this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
**Project Type**: Self-hosted authentication and collaboration platform
|
||||
**Primary Language**: Shell (deployment), YAML (configuration), Java (JSPWiki customization)
|
||||
**Architecture**: Docker Compose multi-container application
|
||||
**Deployment Model**: Remote deployment via SSH/rsync from local machine
|
||||
|
||||
### Core Services
|
||||
1. **lldap** - LDAP user directory (Rust application, pre-built image)
|
||||
2. **Authelia** - SSO/2FA server (Go application, pre-built image)
|
||||
3. **Gitea** - Git hosting (Go application, pre-built image)
|
||||
4. **JSPWiki** - Wiki platform (Java/Tomcat, custom Docker image)
|
||||
5. **Caddy** - Reverse proxy (Go application, pre-built image)
|
||||
|
||||
## Project Philosophy
|
||||
|
||||
### Single Source of Truth
|
||||
- `.env` file is the **only** place users configure the system
|
||||
- All service configs are generated from `.env` via templates
|
||||
- **Never** require users to edit multiple config files
|
||||
- **Never** hardcode values that should be configurable
|
||||
|
||||
### Zero Manual Steps
|
||||
- `./deploy.sh` should handle everything from secrets to deployment
|
||||
- User should only need to edit `.env` and run one command
|
||||
- Secrets auto-generated if missing
|
||||
- Configs auto-generated from templates
|
||||
- Service dependencies handled automatically
|
||||
|
||||
### Security First
|
||||
- File-based secrets (not environment variables)
|
||||
- Read-only mounts where possible
|
||||
- Secrets never committed to git
|
||||
- Modern authentication protocols (LDAP, OIDC, Forward-Auth)
|
||||
- Defense in depth architecture
|
||||
|
||||
### Educational Value
|
||||
- Code should teach authentication patterns
|
||||
- Comments explain "why" not just "what"
|
||||
- Documentation references RFCs and specs
|
||||
- Architecture demonstrates industry best practices
|
||||
|
||||
## File Structure and Responsibilities
|
||||
|
||||
```
|
||||
org-stack/
|
||||
├── .env.example # Configuration template (edit this to add new options)
|
||||
├── .env # User configuration (generated, not in git)
|
||||
├── .gitignore # Ensures secrets/ and .env not committed
|
||||
│
|
||||
├── deploy.sh # Main deployment script
|
||||
│ ├── Checks/creates .env
|
||||
│ ├── Generates secrets if missing
|
||||
│ ├── Derives LDAP_BASE_DN from BASE_DOMAIN
|
||||
│ ├── Hashes Gitea OIDC secret
|
||||
│ ├── Generates configs from templates
|
||||
│ └── Rsyncs to remote server and starts services
|
||||
│
|
||||
├── manage.sh # Management operations on remote server
|
||||
│ ├── logs, restart, update
|
||||
│ ├── backup, restore
|
||||
│ └── status, reset
|
||||
│
|
||||
├── compose.yml # Docker Compose service definitions
|
||||
│ ├── Service configs with educational comments
|
||||
│ ├── Volume mounts (configs, secrets, data)
|
||||
│ └── Network and dependency definitions
|
||||
│
|
||||
├── Caddyfile.production.template # Caddy config for Let's Encrypt
|
||||
├── Caddyfile.test.template # Caddy config for self-signed certs
|
||||
│ └── Generated to Caddyfile by deploy.sh based on USE_SELF_SIGNED_CERTS
|
||||
│
|
||||
├── authelia/
|
||||
│ └── configuration.yml.template # Authelia config template
|
||||
│ ├── Uses modern v4.38+ syntax (no deprecation warnings)
|
||||
│ ├── References secrets via _FILE environment variables
|
||||
│ └── Generated to configuration.yml by deploy.sh
|
||||
│
|
||||
├── jspwiki/
|
||||
│ ├── Dockerfile # Custom JSPWiki image with SSO support
|
||||
│ ├── RemoteUserFilter.java # Servlet filter for container auth
|
||||
│ ├── ldap-sync.sh # Syncs lldap users to JSPWiki XML
|
||||
│ ├── configure-web-xml.sh # Modifies web.xml for RemoteUserFilter
|
||||
│ └── entrypoint.sh # Container startup: sync LDAP, start Tomcat
|
||||
│
|
||||
├── jspwiki-custom.properties # JSPWiki config (container auth enabled)
|
||||
├── jspwiki.policy # JSPWiki security policy
|
||||
│
|
||||
├── secrets/ # Auto-generated secrets (NEVER commit)
|
||||
│ ├── lldap/
|
||||
│ │ ├── JWT_SECRET
|
||||
│ │ └── LDAP_USER_PASS
|
||||
│ └── authelia/
|
||||
│ ├── JWT_SECRET
|
||||
│ ├── SESSION_SECRET
|
||||
│ ├── STORAGE_ENCRYPTION_KEY
|
||||
│ ├── OIDC_HMAC_SECRET
|
||||
│ └── OIDC_PRIVATE_KEY
|
||||
│
|
||||
└── Documentation
|
||||
├── README.md # User-facing documentation and quick start
|
||||
├── ARCHITECTURE.md # Technical deep-dive
|
||||
└── CLAUDE.md # This file (AI assistant guidance)
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### LDAP Injection Prevention
|
||||
|
||||
The registration service handles user input that is used in LDAP operations. Always follow these practices:
|
||||
|
||||
**Input Validation (Defense Layer 1):**
|
||||
- Username: Strict validation - alphanumeric + underscore only, must start with letter, 2-64 chars
|
||||
- Email: Basic format validation
|
||||
- Names: Length limits (max 100 characters)
|
||||
- Apply validation at form submission AND before LDAP operations (defense in depth)
|
||||
|
||||
**LDAP DN Escaping (Defense Layer 2):**
|
||||
```python
|
||||
def escape_ldap_dn(value: str) -> str:
|
||||
"""Escape LDAP DN special characters per RFC 4514"""
|
||||
# Escape: , \ # + < > ; " =
|
||||
# Also escape leading/trailing spaces
|
||||
```
|
||||
|
||||
**Always escape when constructing DNs:**
|
||||
```python
|
||||
# BAD - Vulnerable to injection
|
||||
user_dn = f'uid={username},ou=people,dc=example,dc=com'
|
||||
|
||||
# GOOD - Escaped and validated
|
||||
if not validate_username_strict(username):
|
||||
raise ValueError("Invalid username")
|
||||
escaped_username = escape_ldap_dn(username)
|
||||
user_dn = f'uid={escaped_username},ou=people,dc=example,dc=com'
|
||||
```
|
||||
|
||||
**LDAP Filter Escaping:**
|
||||
If you add ldapsearch operations, escape filter values:
|
||||
```python
|
||||
def escape_ldap_filter(value: str) -> str:
|
||||
"""Escape LDAP filter special characters"""
|
||||
# Escape: * ( ) \ NULL
|
||||
value = value.replace('\\', '\\5c')
|
||||
value = value.replace('*', '\\2a')
|
||||
value = value.replace('(', '\\28')
|
||||
value = value.replace(')', '\\29')
|
||||
value = value.replace('\x00', '\\00')
|
||||
return value
|
||||
```
|
||||
|
||||
### SQL Injection Prevention
|
||||
|
||||
Always use parameterized queries:
|
||||
```python
|
||||
# GOOD - Parameterized
|
||||
db.execute('SELECT * FROM users WHERE username = ?', (username,))
|
||||
|
||||
# BAD - String interpolation
|
||||
db.execute(f'SELECT * FROM users WHERE username = "{username}"')
|
||||
```
|
||||
|
||||
### GraphQL Security
|
||||
|
||||
The lldap GraphQL API is safe from injection because it uses parameterized variables. Always pass user input as variables, never in the query string:
|
||||
```python
|
||||
# GOOD - Parameterized variables
|
||||
variables = {'user': {'id': username, 'email': email}}
|
||||
response = client.post(url, json={'query': mutation, 'variables': variables})
|
||||
|
||||
# BAD - String interpolation in query
|
||||
query = f'mutation {{ createUser(id: "{username}") }}' # Vulnerable!
|
||||
```
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Adding a New Configuration Option
|
||||
|
||||
1. **Add to `.env.example`** with clear comments and default value
|
||||
2. **Update template file** (Caddyfile or authelia config) to use `${NEW_VAR}`
|
||||
3. **Update `deploy.sh`** to export the variable for envsubst
|
||||
4. **Update documentation** (README.md) if user-facing
|
||||
5. **Test** by removing .env and running deploy.sh
|
||||
|
||||
Example:
|
||||
```bash
|
||||
# .env.example
|
||||
NEW_FEATURE_ENABLED=true # Enable new feature
|
||||
|
||||
# authelia/configuration.yml.template
|
||||
new_feature:
|
||||
enabled: ${NEW_FEATURE_ENABLED}
|
||||
|
||||
# deploy.sh (in export section)
|
||||
export NEW_FEATURE_ENABLED
|
||||
```
|
||||
|
||||
### Adding a New Service
|
||||
|
||||
1. **Add service to `compose.yml`** with clear comments
|
||||
2. **Add subdomain to `.env.example`** (e.g., `NEWSERVICE_SUBDOMAIN=new`)
|
||||
3. **Add to Caddyfile templates** with appropriate auth config
|
||||
4. **Update `deploy.sh`** summary section to show new URL
|
||||
5. **Update `manage.sh`** if service needs special handling
|
||||
6. **Document in README.md** and ARCHITECTURE.md
|
||||
|
||||
### Modifying Authelia Configuration
|
||||
|
||||
**Important**: Always use **modern v4.38+ syntax**. Check [Authelia docs](https://www.authelia.com/configuration/prologue/introduction/) for latest syntax.
|
||||
|
||||
Common deprecations to avoid:
|
||||
- ❌ `username_attribute` → ✅ `attributes.username`
|
||||
- ❌ `access_token_lifespan` → ✅ `lifespans.access_token`
|
||||
- ❌ `issuer_private_key: |` → ✅ Use `*_FILE` env var
|
||||
|
||||
When adding new features:
|
||||
1. Check if Authelia exposes secret via `*_FILE` env variable
|
||||
2. If yes, use file-based secret (add to deploy.sh generation)
|
||||
3. If no, use template variable from .env
|
||||
|
||||
### Working with Secrets
|
||||
|
||||
**Adding a new secret:**
|
||||
|
||||
```bash
|
||||
# 1. Add generation to deploy.sh
|
||||
generate_secret_file "secrets/authelia/NEW_SECRET" 32
|
||||
|
||||
# 2. Add environment variable to compose.yml
|
||||
environment:
|
||||
- AUTHELIA_NEW_SECRET_FILE=/secrets/NEW_SECRET
|
||||
|
||||
# 3. Add volume mount if in new directory
|
||||
volumes:
|
||||
- ./secrets/authelia:/secrets:ro
|
||||
|
||||
# 4. Reference in template with comment
|
||||
# authelia/configuration.yml.template
|
||||
# new_secret read from AUTHELIA_NEW_SECRET_FILE
|
||||
```
|
||||
|
||||
**Never:**
|
||||
- ❌ Commit secrets to git
|
||||
- ❌ Put secrets in environment variables (use _FILE pattern)
|
||||
- ❌ Echo secrets in logs
|
||||
- ❌ Use predictable secret values
|
||||
|
||||
### Debugging Authentication Issues
|
||||
|
||||
**Check in this order:**
|
||||
|
||||
1. **Service Status**
|
||||
```bash
|
||||
ssh user@host 'cd ~/org-stack && docker compose ps'
|
||||
```
|
||||
All services should be "Up" and "healthy" (Authelia)
|
||||
|
||||
2. **Authelia Logs**
|
||||
```bash
|
||||
ssh user@host 'cd ~/org-stack && docker compose logs authelia | tail -50'
|
||||
```
|
||||
Look for:
|
||||
- Configuration errors (missing secrets, bad syntax)
|
||||
- LDAP connection errors
|
||||
- Authentication failures
|
||||
|
||||
3. **Secret Files**
|
||||
```bash
|
||||
ssh user@host 'ls -la ~/org-stack/secrets/authelia/'
|
||||
```
|
||||
All files should exist with 600 permissions
|
||||
|
||||
4. **Network Connectivity**
|
||||
```bash
|
||||
ssh user@host 'cd ~/org-stack && docker compose exec gitea curl http://authelia:9091/.well-known/openid-configuration'
|
||||
```
|
||||
Should return JSON (OIDC discovery document)
|
||||
|
||||
5. **LDAP Connectivity**
|
||||
```bash
|
||||
ssh user@host 'cd ~/org-stack && docker compose exec authelia ldapsearch -H ldap://lldap:3890 -D "uid=admin,ou=people,dc=example,dc=com" -w "$(cat secrets/lldap/LDAP_USER_PASS)" -b "dc=example,dc=com" "(uid=admin)"'
|
||||
```
|
||||
Should return admin user entry
|
||||
|
||||
### Common Error Patterns
|
||||
|
||||
**"502 Bad Gateway" on all services except Gitea**
|
||||
- **Cause**: Authelia container not running or crashing
|
||||
- **Check**: `docker compose logs authelia`
|
||||
- **Common root cause**: Missing or incorrect secret file path
|
||||
|
||||
**"No such host" errors in Caddy logs**
|
||||
- **Cause**: Docker network issues or service not started
|
||||
- **Fix**: `docker compose down && docker compose up -d`
|
||||
|
||||
**"Certificate signed by unknown authority" in Gitea**
|
||||
- **Cause**: Gitea trying to use external HTTPS URL with self-signed cert
|
||||
- **Fix**: Use internal URL in OIDC config: `http://authelia:9091/...`
|
||||
|
||||
**Authelia deprecation warnings in logs**
|
||||
- **Cause**: Using old configuration syntax
|
||||
- **Fix**: Update `authelia/configuration.yml.template` to modern syntax
|
||||
- **Reference**: [Authelia Migration Guides](https://www.authelia.com/reference/guides/migration/)
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
### Shell Scripts (deploy.sh, manage.sh)
|
||||
|
||||
```bash
|
||||
# Good: Clear error messages with context
|
||||
if [ ! -f .env ]; then
|
||||
error ".env file not found. Copy .env.example to .env and configure."
|
||||
fi
|
||||
|
||||
# Good: Functions with clear names
|
||||
generate_secret_file() {
|
||||
local file_path=$1
|
||||
local length=${2:-32}
|
||||
# ...
|
||||
}
|
||||
|
||||
# Good: Comments explain "why"
|
||||
# Derive LDAP_BASE_DN from BASE_DOMAIN because most users won't know LDAP DN format
|
||||
if [ "$LDAP_BASE_DN" = "AUTO" ]; then
|
||||
DERIVED_DN=$(echo "$BASE_DOMAIN" | sed 's/\./,dc=/g' | sed 's/^/dc=/')
|
||||
# ...
|
||||
fi
|
||||
```
|
||||
|
||||
### YAML Configuration
|
||||
|
||||
```yaml
|
||||
# Good: Comments explain purpose and link to docs
|
||||
authentication_backend:
|
||||
ldap:
|
||||
# lldap connection settings
|
||||
# See: https://github.com/lldap/lldap
|
||||
address: 'ldap://lldap:3890'
|
||||
|
||||
# Good: Group related settings
|
||||
lifespans:
|
||||
access_token: ${ACCESS_TOKEN_LIFESPAN}
|
||||
authorize_code: ${AUTHORIZE_CODE_LIFESPAN}
|
||||
id_token: ${ID_TOKEN_LIFESPAN}
|
||||
refresh_token: ${REFRESH_TOKEN_LIFESPAN}
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
|
||||
```yaml
|
||||
# Good: Comments explain auth method and purpose
|
||||
gitea:
|
||||
# Self-hosted Git service
|
||||
# Uses OIDC to authenticate users through Authelia (SSO)
|
||||
image: gitea/gitea:latest
|
||||
environment:
|
||||
- GITEA__server__DOMAIN=${GITEA_SUBDOMAIN}.${BASE_DOMAIN}
|
||||
volumes:
|
||||
- gitea_data:/data # Git repos, database, config
|
||||
```
|
||||
|
||||
## Testing Approach
|
||||
|
||||
### Before Committing Changes
|
||||
|
||||
1. **Test fresh deployment**:
|
||||
```bash
|
||||
rm .env
|
||||
rm -rf secrets/
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
2. **Verify services start**:
|
||||
```bash
|
||||
ssh user@host 'cd ~/org-stack && docker compose ps'
|
||||
```
|
||||
All should be "Up"
|
||||
|
||||
3. **Test authentication flow**:
|
||||
- Create user in lldap
|
||||
- Login to wiki (tests Forward-Auth + 2FA)
|
||||
- Login to Gitea (tests OIDC + 2FA)
|
||||
|
||||
4. **Check for deprecation warnings**:
|
||||
```bash
|
||||
ssh user@host 'cd ~/org-stack && docker compose logs authelia' | grep -i deprecat
|
||||
```
|
||||
Should be zero warnings
|
||||
|
||||
5. **Verify secrets not in git**:
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
secrets/ and .env should not appear
|
||||
|
||||
### Test Scenarios
|
||||
|
||||
**Scenario 1: First-time deployment**
|
||||
- User has never deployed before
|
||||
- Should complete with zero manual steps
|
||||
- All secrets auto-generated
|
||||
|
||||
**Scenario 2: Configuration change**
|
||||
- User changes BASE_DOMAIN in .env
|
||||
- Runs deploy.sh
|
||||
- All services update to new domain
|
||||
|
||||
**Scenario 3: Secret rotation**
|
||||
- Delete a secret file
|
||||
- Run deploy.sh
|
||||
- Secret regenerated, services restart
|
||||
|
||||
**Scenario 4: Disaster recovery**
|
||||
- Backup volumes with manage.sh backup
|
||||
- Destroy everything with manage.sh reset
|
||||
- Restore from backup
|
||||
- Everything works
|
||||
|
||||
## Understanding the Authentication Flow
|
||||
|
||||
### When User Accesses Wiki
|
||||
|
||||
```
|
||||
1. Browser → Caddy: GET https://wiki.example.com/
|
||||
2. Caddy → Authelia: Forward-auth subrequest to /api/authz/forward-auth
|
||||
3. Authelia checks session cookie:
|
||||
a. If valid session: Return 200 + Remote-User header
|
||||
b. If no session: Return 401 + redirect to login
|
||||
4. If 401:
|
||||
- Caddy → Browser: 302 to Authelia login
|
||||
- User enters credentials
|
||||
- Authelia → lldap: Validate password via LDAP BIND
|
||||
- Authelia prompts for TOTP (if REQUIRE_2FA=true)
|
||||
- Authelia creates session, sets cookie
|
||||
- Authelia → Browser: 302 back to wiki
|
||||
5. Caddy forwards request to JSPWiki with Remote-User header
|
||||
6. JSPWiki trusts Remote-User (via RemoteUserFilter)
|
||||
7. User sees wiki page as authenticated user
|
||||
```
|
||||
|
||||
### When User Accesses Gitea
|
||||
|
||||
```
|
||||
1. Browser → Gitea: Click "Sign in with OpenID Connect"
|
||||
2. Gitea → Browser: 302 to Authelia /api/oidc/authorization
|
||||
3. Authelia checks session:
|
||||
a. If valid: Skip to step 5
|
||||
b. If no session: Show login + 2FA
|
||||
4. User authenticates (same as wiki flow)
|
||||
5. Authelia → Browser: 302 to Gitea callback with code
|
||||
6. Gitea → Authelia: POST /api/oidc/token with code
|
||||
7. Authelia → Gitea: Access token + ID token (JWT)
|
||||
8. Gitea validates JWT, extracts user info
|
||||
9. Gitea creates local session
|
||||
10. User logged into Gitea (SSO - no credential re-entry if already logged into wiki)
|
||||
```
|
||||
|
||||
## When to Ask User for Clarification
|
||||
|
||||
**Always ask before:**
|
||||
- Changing authentication behavior (2FA, SSO, etc.)
|
||||
- Modifying security-related code
|
||||
- Breaking changes to .env configuration
|
||||
- Changing default behavior
|
||||
- Adding new external dependencies
|
||||
|
||||
**No need to ask for:**
|
||||
- Bug fixes (broken features)
|
||||
- Documentation improvements
|
||||
- Code cleanup/refactoring (no behavior change)
|
||||
- Adding educational comments
|
||||
- Fixing deprecation warnings
|
||||
|
||||
## Important Constraints
|
||||
|
||||
### Do Not
|
||||
|
||||
- ❌ Add configuration that requires editing multiple files
|
||||
- ❌ Use deprecated Authelia configuration syntax
|
||||
- ❌ Put secrets in environment variables (use _FILE pattern)
|
||||
- ❌ Create services without proper health checks
|
||||
- ❌ Bypass authentication (all services must auth via Authelia)
|
||||
- ❌ Hardcode domains, IPs, or credentials
|
||||
- ❌ Use `latest` tags for critical services (prefer versioned tags)
|
||||
- ❌ Mix forward-auth and OIDC on same service
|
||||
|
||||
### Always
|
||||
|
||||
- ✅ Use file-based secrets
|
||||
- ✅ Mount secrets read-only (`:ro`)
|
||||
- ✅ Add educational comments to configs
|
||||
- ✅ Test full deployment flow
|
||||
- ✅ Keep `.env.example` in sync with actual usage
|
||||
- ✅ Update documentation when changing behavior
|
||||
- ✅ Use modern Authelia v4.38+ syntax
|
||||
- ✅ Reference official docs/RFCs in comments
|
||||
|
||||
## Resources
|
||||
|
||||
### Official Documentation
|
||||
- [lldap](https://github.com/lldap/lldap)
|
||||
- [Authelia](https://www.authelia.com/)
|
||||
- [Gitea](https://docs.gitea.io/)
|
||||
- [JSPWiki](https://jspwiki-wiki.apache.org/)
|
||||
- [Caddy](https://caddyserver.com/docs/)
|
||||
|
||||
### Protocol Specifications
|
||||
- [LDAP - RFC 4511](https://tools.ietf.org/html/rfc4511)
|
||||
- [OIDC Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
|
||||
- [OAuth 2.0 - RFC 6749](https://tools.ietf.org/html/rfc6749)
|
||||
- [TOTP - RFC 6238](https://tools.ietf.org/html/rfc6238)
|
||||
|
||||
### Helpful Articles
|
||||
- [Understanding OIDC](https://connect2id.com/learn/openid-connect)
|
||||
- [Forward-Auth Pattern](https://www.authelia.com/integration/proxies/fowarded-headers/)
|
||||
- [Docker Secrets Best Practices](https://docs.docker.com/engine/swarm/secrets/)
|
||||
|
||||
## Project Goals
|
||||
|
||||
This project aims to:
|
||||
1. **Demonstrate** modern authentication patterns (LDAP, OIDC, Forward-Auth)
|
||||
2. **Educate** about SSO, 2FA, and centralized user management
|
||||
3. **Provide** production-ready self-hosted alternative to cloud services
|
||||
4. **Showcase** Docker Compose best practices
|
||||
5. **Maintain** simplicity - one command deployment, zero manual config
|
||||
|
||||
When working on this project, prioritize these goals:
|
||||
- Make it easier to deploy
|
||||
- Make it more educational
|
||||
- Make it more secure
|
||||
- Make it well-documented
|
||||
|
||||
## Current State
|
||||
|
||||
**Production Ready**: Yes (with appropriate configuration)
|
||||
**Deployment Model**: Remote server via SSH/rsync
|
||||
**Supported Platforms**: Linux (Docker required)
|
||||
**Active Development**: Yes
|
||||
|
||||
**Known Limitations**:
|
||||
- SQLite databases (acceptable for <10k users)
|
||||
- Filesystem notifier (should be SMTP for production)
|
||||
- Single server deployment (no HA)
|
||||
|
||||
**Improvement Opportunities**:
|
||||
- Add Redis for Authelia session storage
|
||||
- Add PostgreSQL option for Gitea
|
||||
- Add Prometheus monitoring
|
||||
- Add automated backup scheduling
|
||||
- Add user import scripts (CSV → lldap)
|
||||
|
||||
## Final Notes
|
||||
|
||||
This is a **teaching project** as much as a production tool. Code should be:
|
||||
- **Readable** - clear variable names, educational comments
|
||||
- **Reliable** - error handling, validation, health checks
|
||||
- **Reproducible** - anyone should be able to deploy successfully
|
||||
- **Referenced** - link to RFCs, docs, and explanations
|
||||
|
||||
When in doubt, prioritize clarity over cleverness, and security over convenience.
|
||||
|
||||
If you're working on this project as an AI assistant, remember:
|
||||
- The user may not be an expert in authentication protocols
|
||||
- Comments should explain "why" not just "what"
|
||||
- Changes should work end-to-end (don't break deployment)
|
||||
- Documentation is as important as code
|
||||
|
||||
Good luck! 🚀
|
||||
Reference in New Issue
Block a user