From 9ce84225965bb2efcca5e47390c5bb6efe103c6a Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Thu, 29 Jan 2026 15:21:25 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20composable=20for=20handling?= =?UTF-8?q?=20Discord=20OAuth=20integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/composables/useDiscordOAuth.js | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 code/websites/pokedex.online/src/composables/useDiscordOAuth.js diff --git a/code/websites/pokedex.online/src/composables/useDiscordOAuth.js b/code/websites/pokedex.online/src/composables/useDiscordOAuth.js new file mode 100644 index 0000000..fb19664 --- /dev/null +++ b/code/websites/pokedex.online/src/composables/useDiscordOAuth.js @@ -0,0 +1,138 @@ +/** + * Discord OAuth Composable + * + * Thin wrapper around useOAuth for Discord-specific flows + * Handles Discord user profile fetching and username access + * + * Usage: + * const discord = useDiscordOAuth(); + * discord.login(); + * // ... OAuth flow ... + * const username = discord.discordUsername; + */ + +import { ref, computed } from 'vue'; +import { useOAuth } from './useOAuth.js'; + +// Shared Discord user profile data +const discordUser = ref(null); + +export function useDiscordOAuth() { + const oauth = useOAuth('discord'); + + const hasDiscordAuth = computed(() => oauth.isAuthenticated.value); + + const discordUsername = computed(() => { + return discordUser.value?.username || null; + }); + + const discordId = computed(() => { + return discordUser.value?.id || null; + }); + + const discordTag = computed(() => { + if (!discordUser.value) return null; + // Format: username#discriminator or just username (newer Discord) + return discordUser.value.discriminator + ? `${discordUser.value.username}#${discordUser.value.discriminator}` + : discordUser.value.username; + }); + + /** + * Fetch Discord user profile from backend + * Backend will use the stored Discord token to fetch from Discord API + * + * @returns {Promise} Discord user profile + * @throws {Error} If fetch fails + */ + async function fetchUserProfile() { + try { + const token = oauth.accessToken.value; + if (!token) { + throw new Error('Not authenticated with Discord'); + } + + // Fetch from backend which has the Discord token + const response = await fetch('/api/auth/discord/profile', { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + + if (!response.ok) { + const error = await response.json().catch(() => ({})); + throw new Error(error.error || 'Failed to fetch Discord profile'); + } + + const data = await response.json(); + discordUser.value = data.user; + + console.log(`✅ Loaded Discord profile: ${data.user.username}`); + return data.user; + } catch (err) { + console.error('Failed to fetch Discord profile:', err); + throw err; + } + } + + /** + * Login with Discord + * Uses identify scope only for minimal permissions + * + * @param {Object} options - Optional options (return_to, etc.) + */ + function login(options = {}) { + oauth.login({ + ...options, + scope: 'identify' + }); + } + + /** + * Logout from Discord + */ + function logout() { + oauth.logout(); + discordUser.value = null; + } + + /** + * Check if user is allowed to access developer tools + * Compares Discord username against backend-managed allowlist + * + * @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; + } + + return { + // State + hasDiscordAuth, + discordUser: computed(() => discordUser.value), + discordUsername, + discordId, + discordTag, + isExpired: oauth.isExpired, + expiresIn: oauth.expiresIn, + loading: oauth.loading, + error: oauth.error, + + // Methods + login, + logout, + exchangeCode: oauth.exchangeCode, + refreshToken: oauth.refreshToken, + getValidToken: oauth.getValidToken, + fetchUserProfile, + hasDevAccess + }; +}