Add composable for handling Discord OAuth integration

This commit is contained in:
2026-01-29 15:21:25 +00:00
parent 8093cc8d2a
commit 9ce8422596

View File

@@ -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<Object>} 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
};
}