mirror of
https://github.com/diced/zipline.git
synced 2025-12-12 07:40:45 -08:00
feat: discord oauth whitelist
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,6 +23,7 @@
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
.idea
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Zipline" ADD COLUMN "oauthDiscordWhitelistIds" TEXT[] DEFAULT ARRAY[]::TEXT[];
|
||||
@@ -82,6 +82,7 @@ model Zipline {
|
||||
oauthDiscordClientId String?
|
||||
oauthDiscordClientSecret String?
|
||||
oauthDiscordRedirectUri String?
|
||||
oauthDiscordWhitelistIds String[] @default([])
|
||||
|
||||
oauthGoogleClientId String?
|
||||
oauthGoogleClientSecret String?
|
||||
|
||||
@@ -30,6 +30,7 @@ export default function ServerSettingsOauth({
|
||||
oauthDiscordClientId: '',
|
||||
oauthDiscordClientSecret: '',
|
||||
oauthDiscordRedirectUri: '',
|
||||
oauthDiscordWhitelistIds: '',
|
||||
|
||||
oauthGoogleClientId: '',
|
||||
oauthGoogleClientSecret: '',
|
||||
@@ -50,7 +51,7 @@ export default function ServerSettingsOauth({
|
||||
|
||||
const onSubmit = async (values: typeof form.values) => {
|
||||
for (const key in values) {
|
||||
if (!['oauthBypassLocalLogin', 'oauthLoginOnly'].includes(key)) {
|
||||
if (!['oauthBypassLocalLogin', 'oauthLoginOnly', 'oauthDiscordWhitelistIds'].includes(key)) {
|
||||
if ((values[key as keyof typeof form.values] as string)?.trim() === '') {
|
||||
// @ts-ignore
|
||||
values[key as keyof typeof form.values] = null;
|
||||
@@ -61,6 +62,16 @@ export default function ServerSettingsOauth({
|
||||
)?.trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (key === 'oauthDiscordWhitelistIds') {
|
||||
if (Array.isArray(values['oauthDiscordWhitelistIds'])) continue;
|
||||
|
||||
// @ts-ignore
|
||||
values['oauthDiscordWhitelistIds'] = (values['oauthDiscordWhitelistIds'] as string)
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id !== '');
|
||||
}
|
||||
}
|
||||
|
||||
return settingsOnSubmit(router, form)(values);
|
||||
@@ -76,6 +87,9 @@ export default function ServerSettingsOauth({
|
||||
oauthDiscordClientId: data?.oauthDiscordClientId ?? '',
|
||||
oauthDiscordClientSecret: data?.oauthDiscordClientSecret ?? '',
|
||||
oauthDiscordRedirectUri: data?.oauthDiscordRedirectUri ?? '',
|
||||
oauthDiscordWhitelistIds: data?.oauthDiscordWhitelistIds
|
||||
? data?.oauthDiscordWhitelistIds.join(', ')
|
||||
: '',
|
||||
|
||||
oauthGoogleClientId: data?.oauthGoogleClientId ?? '',
|
||||
oauthGoogleClientSecret: data?.oauthGoogleClientSecret ?? '',
|
||||
@@ -129,6 +143,11 @@ export default function ServerSettingsOauth({
|
||||
|
||||
<TextInput label='Discord Client ID' {...form.getInputProps('oauthDiscordClientId')} />
|
||||
<TextInput label='Discord Client Secret' {...form.getInputProps('oauthDiscordClientSecret')} />
|
||||
<TextInput
|
||||
label='Discord Whitelist IDs'
|
||||
description='A comma-separated list of Discord user IDs that are allowed to log in. Leave empty to allow all users.'
|
||||
{...form.getInputProps('oauthDiscordWhitelistIds')}
|
||||
/>
|
||||
<TextInput
|
||||
label='Discord Redirect URL'
|
||||
description='The redirect URL to use instead of the host when logging in. This is not required if the URL generated by Zipline works as intended.'
|
||||
|
||||
@@ -235,6 +235,7 @@ export const DATABASE_TO_PROP = {
|
||||
oauthDiscordClientId: 'oauth.discord.clientId',
|
||||
oauthDiscordClientSecret: 'oauth.discord.clientSecret',
|
||||
oauthDiscordRedirectUri: 'oauth.discord.redirectUri',
|
||||
oauthDiscordWhitelistIds: 'oauth.discord.whitelistIds',
|
||||
|
||||
oauthGoogleClientId: 'oauth.google.clientId',
|
||||
oauthGoogleClientSecret: 'oauth.google.clientSecret',
|
||||
|
||||
@@ -221,12 +221,14 @@ export const schema = z.object({
|
||||
clientId: z.string(),
|
||||
clientSecret: z.string(),
|
||||
redirectUri: z.string().url().nullable().default(null),
|
||||
whitelistIds: z.array(z.string()).default([]),
|
||||
})
|
||||
.or(
|
||||
z.object({
|
||||
clientId: z.undefined(),
|
||||
clientSecret: z.undefined(),
|
||||
redirectUri: z.undefined(),
|
||||
whitelistIds: z.undefined().or(z.array(z.string()).default([])),
|
||||
}),
|
||||
),
|
||||
github: z
|
||||
|
||||
@@ -80,6 +80,16 @@ async function discordOauth({ code, host, state }: OAuthQuery, logger: Logger):
|
||||
|
||||
logger.debug('user', { '@me': userJson });
|
||||
|
||||
if (config.oauth.discord.whitelistIds?.length) {
|
||||
if (!config.oauth.discord.whitelistIds.includes(userJson.id)) {
|
||||
logger.warn('Discord user not whitelisted', { userId: userJson.id });
|
||||
return {
|
||||
error: 'You are not whitelisted to use Discord OAuth',
|
||||
error_code: 403,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const avatar = userJson.avatar
|
||||
? `https://cdn.discordapp.com/avatars/${userJson.id}/${userJson.avatar}.png`
|
||||
: `https://cdn.discordapp.com/embed/avatars/${userJson.discriminator % 5}.png`;
|
||||
|
||||
@@ -222,6 +222,16 @@ export default fastifyPlugin(
|
||||
oauthDiscordClientId: z.string().nullable(),
|
||||
oauthDiscordClientSecret: z.string().nullable(),
|
||||
oauthDiscordRedirectUri: z.string().url().endsWith('/api/auth/oauth/discord').nullable(),
|
||||
oauthDiscordWhitelistIds: z
|
||||
.union([
|
||||
z.array(z.string().refine((s) => /^\d+$/.test(s), 'Discord ID must be a number')),
|
||||
z
|
||||
.string()
|
||||
.refine((s) => s === '' || /^\d+(,\d+)*$/.test(s), 'Discord IDs must be comma-separated'),
|
||||
])
|
||||
.transform((value) =>
|
||||
typeof value === 'string' ? value.split(',').map((id) => id.trim()) : value,
|
||||
),
|
||||
|
||||
oauthGoogleClientId: z.string().nullable(),
|
||||
oauthGoogleClientSecret: z.string().nullable(),
|
||||
|
||||
Reference in New Issue
Block a user