mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-08-23 00:57:21 -05:00
feat: implement discord oauth flow
This commit is contained in:
@@ -45,12 +45,15 @@ export default defineNuxtConfig({
|
||||
smtpFromEmail: '',
|
||||
smtpFromName: '',
|
||||
discordBotToken: '',
|
||||
discordClientId: '',
|
||||
discordClientSecret: '',
|
||||
discordGuildId: '',
|
||||
discordTesterRoleId: '',
|
||||
discordSupporterRoleId: '',
|
||||
discourseSsoSecret: '',
|
||||
|
||||
public: {
|
||||
baseUrl: 'https://pretendo.network',
|
||||
apiBase: 'https://api.pretendo.cc',
|
||||
hCaptchaSitekey: '',
|
||||
cookieSecure: false
|
||||
|
||||
23
server/api/account/discord-link.get.ts
Normal file
23
server/api/account/discord-link.get.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useDiscord } from "~~/server/utils/discord";
|
||||
import { ApiAccountDiscordLink } from "~~/shared/api-types";
|
||||
|
||||
export default defineEventHandler(async (event): Promise<ApiAccountDiscordLink> => {
|
||||
enforceLoggedIn(event);
|
||||
const discord = useDiscord(event);
|
||||
if (!discord) throw createError({
|
||||
status: 400,
|
||||
message: 'Discord integration not configured',
|
||||
})
|
||||
|
||||
const redirectUrl = discord.makeCallbackUrl();
|
||||
const url = new URL("https://discord.com/oauth2/authorize");
|
||||
url.searchParams.set('response_type', 'code');
|
||||
url.searchParams.set('client_id', 'code');
|
||||
url.searchParams.set('scope', 'identify');
|
||||
url.searchParams.set('redirect_uri', redirectUrl);
|
||||
url.searchParams.set('prompt', 'scope');
|
||||
url.searchParams.set('integration_type', '1');
|
||||
return {
|
||||
url: url.toString(),
|
||||
}
|
||||
});
|
||||
15
server/api/account/discord-unlink.post.ts
Normal file
15
server/api/account/discord-unlink.post.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export default defineEventHandler(async (event): Promise<void> => {
|
||||
const auth = enforceLoggedIn(event);
|
||||
const discord = useDiscord(event);
|
||||
if (!discord) throw createError({
|
||||
status: 400,
|
||||
message: 'Discord integration not configured',
|
||||
})
|
||||
|
||||
const grpc = useApiGrpcWithToken(event, auth.token);
|
||||
await grpc.setDiscordConnectionData({
|
||||
id: '',
|
||||
});
|
||||
|
||||
// TODO set roles based on stripe info
|
||||
});
|
||||
53
server/routes/account/connect/discord.get.ts
Normal file
53
server/routes/account/connect/discord.get.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
type DiscordTokenResponse = {
|
||||
"access_token": string,
|
||||
"token_type": string,
|
||||
"expires_in": number,
|
||||
"refresh_token": string,
|
||||
"scope": string
|
||||
}
|
||||
|
||||
type DiscordUserResponse = {
|
||||
"user"?: {
|
||||
id: string,
|
||||
}
|
||||
}
|
||||
|
||||
// Discord oauth callback
|
||||
export default defineEventHandler(async (event) => {
|
||||
const discord = useDiscord(event);
|
||||
const discordFetch = $fetch.create({
|
||||
baseURL: discord.baseUrl,
|
||||
});
|
||||
const accessTokenCookie = getCookie(event, 'access_token');
|
||||
const query = getQuery(event);
|
||||
|
||||
const authCode = query.code?.toString();
|
||||
if (!authCode || !accessTokenCookie) {
|
||||
return sendRedirect(event, '/');
|
||||
}
|
||||
|
||||
const tokens = await discordFetch<DiscordTokenResponse>('/oauth2/token', {
|
||||
body: new URLSearchParams({
|
||||
'grant_type': 'authorization_code',
|
||||
'code': authCode,
|
||||
'redirect_uri': discord.makeCallbackUrl(),
|
||||
})
|
||||
});
|
||||
const authInfo = await discordFetch<DiscordUserResponse>('/oauth2/@me', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${tokens.access_token}`
|
||||
}
|
||||
});
|
||||
if (!authInfo.user) {
|
||||
return sendRedirect(event, '/'); // No identify scope
|
||||
}
|
||||
|
||||
const grpc = useApiGrpcWithToken(event, accessTokenCookie ?? '');
|
||||
await grpc.setDiscordConnectionData({
|
||||
id: authInfo.user.id
|
||||
});
|
||||
|
||||
// TODO set roles based on stripe info
|
||||
|
||||
return sendRedirect(event, '/account');
|
||||
});
|
||||
@@ -6,7 +6,11 @@ type DiscordIds = { guildId: string, supporterRoleId: string | null, testerRoleI
|
||||
|
||||
type DiscordInstance = {
|
||||
rest: REST,
|
||||
clientId: string,
|
||||
clientSecret: string,
|
||||
baseUrl: string,
|
||||
ids: DiscordIds,
|
||||
makeCallbackUrl: () => string,
|
||||
}
|
||||
|
||||
let discordInstance: DiscordInstance | null = null;
|
||||
@@ -14,14 +18,20 @@ let discordInstance: DiscordInstance | null = null;
|
||||
export function useDiscord(event: H3Event): DiscordInstance | null {
|
||||
if (!discordInstance) {
|
||||
const config = useRuntimeConfig(event);
|
||||
if (config.discordBotToken && config.discordGuildId) {
|
||||
if (config.discordBotToken && config.discordGuildId && config.discordClientId && config.discordClientSecret) {
|
||||
discordInstance = {
|
||||
rest: new REST({ version: '10' }).setToken(config.discordBotToken),
|
||||
baseUrl: "https://discord.com/api/v10",
|
||||
clientId: config.discordClientId,
|
||||
clientSecret: config.discordClientSecret,
|
||||
ids: {
|
||||
guildId: config.discordGuildId,
|
||||
testerRoleId: config.discordTesterRoleId ? config.discordTesterRoleId : null,
|
||||
supporterRoleId: config.discordSupporterRoleId ? config.discordSupporterRoleId : null,
|
||||
}
|
||||
},
|
||||
makeCallbackUrl() {
|
||||
return new URL('/account/connect/discord', config.public.baseUrl).toString()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ async function sendToNotificationEmails(mailer: Transporter, notificationEmails:
|
||||
}
|
||||
}
|
||||
|
||||
// Handles incoming webhooks from stripe, does not validate.
|
||||
// This does not use account server GRPC since the GRPC method can't change the access levels yet
|
||||
export async function handleStripeEvent(event: H3Event, stripe: Stripe, webhook: Stripe.Event, notificationEmails: string[]) {
|
||||
const mailer = useMailer(event);
|
||||
const discord = useDiscord(event);
|
||||
|
||||
@@ -52,3 +52,7 @@ export function useAccountGrpc(event: H3Event): Client<AccountServiceDefinition>
|
||||
export function useLegacyApiGrpcWithToken(event: H3Event, token: string): Client<APIDefinition> {
|
||||
return getGrpcClient(event, APIDefinition, token);
|
||||
}
|
||||
|
||||
export function useApiGrpcWithToken(event: H3Event, token: string): Client<APIDefinition> {
|
||||
return getGrpcClient(event, APIDefinition, token);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ export type ApiAuthLogin = {
|
||||
refreshToken: string;
|
||||
};
|
||||
|
||||
export type ApiAccountDiscordLink = {
|
||||
url: string
|
||||
};
|
||||
|
||||
export const LoginSchema = z.object({
|
||||
username: z.string(),
|
||||
password: z.string()
|
||||
|
||||
Reference in New Issue
Block a user