diff --git a/code/websites/pokedex.online/DISCORD_PERMISSIONS_SETUP.md b/code/websites/pokedex.online/DISCORD_PERMISSIONS_SETUP.md new file mode 100644 index 0000000..631a296 --- /dev/null +++ b/code/websites/pokedex.online/DISCORD_PERMISSIONS_SETUP.md @@ -0,0 +1,137 @@ +# Discord User Permissions Setup Guide + +## Overview + +The app now checks Discord usernames/IDs to grant developer tool access. Users must be in the allowlist to access developer features. + +## Configuration + +### 1. Find Your Discord Username/ID + +You can use any of the following to identify users: + +- **Username**: Your current Discord username (e.g., `fragginwagon`) +- **Global Name**: Your display name (if different from username) +- **Discord ID**: Your numeric Discord ID (e.g., `123456789012345678`) + +**How to Find Your Discord ID:** +1. Enable Developer Mode in Discord: Settings → Advanced → Developer Mode (ON) +2. Right-click your username anywhere → Copy User ID +3. Or use this Discord bot command: `/userinfo` or `!userinfo` + +### 2. Configure Environment Variables + +Add allowed users to your `.env` file: + +```env +# Discord User Permissions +# Comma-separated list of Discord usernames, display names, or IDs +DISCORD_ADMIN_USERS=fragginwagon,AnotherUser,123456789012345678 +``` + +**Multiple formats supported:** +```env +# Just usernames +DISCORD_ADMIN_USERS=fragginwagon,coolguy99 + +# Mix of usernames and IDs +DISCORD_ADMIN_USERS=fragginwagon,123456789012345678,coolguy99 + +# Using Discord IDs (most reliable) +DISCORD_ADMIN_USERS=123456789012345678,987654321098765432 +``` + +### 3. Location of Configuration + +**Development (.env file):** +```bash +/Users/fragginwagon/Developer/MemoryPalace/code/websites/pokedex.online/server/.env +``` + +**Production (Docker):** +Add to your `docker-compose.tmp.yml` or production environment: +```yaml +environment: + - DISCORD_ADMIN_USERS=fragginwagon,user2,123456789012345678 +``` + +Or in your server's `.env` file that gets loaded by Docker. + +## How It Works + +1. User logs in with Discord OAuth +2. Backend fetches user info from Discord API +3. Backend checks if username, global name, OR Discord ID matches the allowlist +4. Backend returns `permissions: ['developer_tools.view']` if user is authorized +5. Frontend checks `hasDevAccess()` to show/hide developer tools + +## Testing + +### Test if you're in the allowlist: + +1. Add your Discord username to `DISCORD_ADMIN_USERS` in `.env` +2. Restart the backend server: + ```bash + docker compose -f docker-compose.tmp.yml restart backend + ``` +3. Log in with Discord OAuth in the app +4. Open Developer Tools (should now be visible if authorized) + +### Check backend logs: + +Look for these messages: +``` +✅ Discord user authenticated { username: 'fragginwagon', id: '123456789012345678' } +✅ Discord user granted developer access { username: 'fragginwagon' } +``` + +Or if not authorized: +``` +✅ Discord user authenticated { username: 'unauthorized', id: '999999999999999999' } +``` + +## Security Notes + +- **Case-insensitive matching**: Usernames are compared in lowercase +- **Multiple formats**: Supports username, display name, and Discord ID +- **Fallback behavior**: If Discord user info fetch fails, no permissions are granted (fail-safe) +- **No permissions stored client-side**: Permissions are checked on every OAuth login + +## Troubleshooting + +**Developer tools not appearing after adding username:** +1. Check backend logs for "Discord user authenticated" message +2. Verify your username matches exactly (check for typos) +3. Try using your Discord ID instead of username (more reliable) +4. Ensure backend restarted after changing `.env` + +**"Failed to fetch Discord user info" in logs:** +- OAuth token may not have `identify` scope +- Check Discord OAuth app settings +- Verify `VITE_DISCORD_CLIENT_ID` and `DISCORD_CLIENT_SECRET` are correct + +## Example Configuration + +```env +# Development +NODE_ENV=development +PORT=3099 + +# Discord OAuth +VITE_DISCORD_CLIENT_ID=your_client_id_here +DISCORD_CLIENT_SECRET=your_client_secret_here +VITE_DISCORD_REDIRECT_URI=http://localhost:5173/oauth/callback + +# Allowed Users (add your Discord username or ID) +DISCORD_ADMIN_USERS=fragginwagon,123456789012345678 +``` + +## Permission Levels + +Currently implemented: +- `developer_tools.view` - Access to developer tools panel and feature flags + +Future permissions (not yet implemented): +- `admin` - Full admin access +- `gamemaster.edit` - Edit gamemaster data +- `tournaments.manage` - Manage tournaments diff --git a/code/websites/pokedex.online/server/utils/env-validator.js b/code/websites/pokedex.online/server/utils/env-validator.js index c416b73..e447add 100644 --- a/code/websites/pokedex.online/server/utils/env-validator.js +++ b/code/websites/pokedex.online/server/utils/env-validator.js @@ -180,6 +180,13 @@ export function getConfig() { // Security session: { secret: process.env.SESSION_SECRET || 'dev-secret-change-in-production' + }, + + // Discord User Permissions + discord: { + adminUsers: process.env.DISCORD_ADMIN_USERS + ? process.env.DISCORD_ADMIN_USERS.split(',').map(u => u.trim().toLowerCase()) + : [] } }; } diff --git a/code/websites/pokedex.online/src/composables/useDiscordOAuth.js b/code/websites/pokedex.online/src/composables/useDiscordOAuth.js index ea23b07..dba26bf 100644 --- a/code/websites/pokedex.online/src/composables/useDiscordOAuth.js +++ b/code/websites/pokedex.online/src/composables/useDiscordOAuth.js @@ -98,20 +98,14 @@ export function useDiscordOAuth() { /** * Check if user is allowed to access developer tools - * Compares Discord username against backend-managed allowlist + * Checks permissions returned from backend during OAuth * - * @param {Object} userPermissions - User permissions object from backend * @returns {boolean} True if user has developer access */ - function hasDevAccess(userPermissions = {}) { - // Check explicit permission - if (userPermissions?.includes?.('developer_tools.view')) { - return true; - } - - // Backend could also return discord_username_allowlist - // This would be checked server-side, but frontend can cache it - return false; + function hasDevAccess() { + // Check if tokens include permissions + const permissions = oauth.tokens.value?.permissions || []; + return permissions.includes('developer_tools.view'); } return {