mirror of
https://github.com/diced/zipline.git
synced 2025-12-06 04:41:12 -08:00
feat: custom redirect uris for oauth
This commit is contained in:
@@ -75,18 +75,22 @@ model Zipline {
|
||||
|
||||
oauthDiscordClientId String?
|
||||
oauthDiscordClientSecret String?
|
||||
oauthDiscordRedirectUri String?
|
||||
|
||||
oauthGoogleClientId String?
|
||||
oauthGoogleClientSecret String?
|
||||
oauthGoogleRedirectUri String?
|
||||
|
||||
oauthGithubClientId String?
|
||||
oauthGithubClientSecret String?
|
||||
oauthGithubRedirectUri String?
|
||||
|
||||
oauthOidcClientId String?
|
||||
oauthOidcClientSecret String?
|
||||
oauthOidcAuthorizeUrl String?
|
||||
oauthOidcTokenUrl String?
|
||||
oauthOidcUserinfoUrl String?
|
||||
oauthOidcRedirectUri String?
|
||||
|
||||
mfaTotpEnabled Boolean @default(false)
|
||||
mfaTotpIssuer String @default("Zipline")
|
||||
|
||||
@@ -19,18 +19,22 @@ export default function ServerSettingsOauth({
|
||||
|
||||
oauthDiscordClientId: '',
|
||||
oauthDiscordClientSecret: '',
|
||||
oauthDiscordRedirectUri: '',
|
||||
|
||||
oauthGoogleClientId: '',
|
||||
oauthGoogleClientSecret: '',
|
||||
oauthGoogleRedirectUri: '',
|
||||
|
||||
oauthGithubClientId: '',
|
||||
oauthGithubClientSecret: '',
|
||||
oauthGithubRedirectUri: '',
|
||||
|
||||
oauthOidcClientId: '',
|
||||
oauthOidcClientSecret: '',
|
||||
oauthOidcAuthorizeUrl: '',
|
||||
oauthOidcTokenUrl: '',
|
||||
oauthOidcUserinfoUrl: '',
|
||||
oauthOidcRedirectUri: '',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -61,18 +65,22 @@ export default function ServerSettingsOauth({
|
||||
|
||||
oauthDiscordClientId: data?.oauthDiscordClientId ?? '',
|
||||
oauthDiscordClientSecret: data?.oauthDiscordClientSecret ?? '',
|
||||
oauthDiscordRedirectUri: data?.oauthDiscordRedirectUri ?? '',
|
||||
|
||||
oauthGoogleClientId: data?.oauthGoogleClientId ?? '',
|
||||
oauthGoogleClientSecret: data?.oauthGoogleClientSecret ?? '',
|
||||
oauthGoogleRedirectUri: data?.oauthGoogleRedirectUri ?? '',
|
||||
|
||||
oauthGithubClientId: data?.oauthGithubClientId ?? '',
|
||||
oauthGithubClientSecret: data?.oauthGithubClientSecret ?? '',
|
||||
oauthGithubRedirectUri: data?.oauthGithubRedirectUri ?? '',
|
||||
|
||||
oauthOidcClientId: data?.oauthOidcClientId ?? '',
|
||||
oauthOidcClientSecret: data?.oauthOidcClientSecret ?? '',
|
||||
oauthOidcAuthorizeUrl: data?.oauthOidcAuthorizeUrl ?? '',
|
||||
oauthOidcTokenUrl: data?.oauthOidcTokenUrl ?? '',
|
||||
oauthOidcUserinfoUrl: data?.oauthOidcUserinfoUrl ?? '',
|
||||
oauthOidcRedirectUri: data?.oauthOidcRedirectUri ?? '',
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
@@ -104,6 +112,11 @@ export default function ServerSettingsOauth({
|
||||
|
||||
<TextInput label='Discord Client ID' {...form.getInputProps('oauthDiscordClientId')} />
|
||||
<TextInput label='Discord Client Secret' {...form.getInputProps('oauthDiscordClientSecret')} />
|
||||
<TextInput
|
||||
label='Discord Redirect URL'
|
||||
description='The redirect URL to use instead of the host when logging in. This must end with /api/auth/oauth/discord'
|
||||
{...form.getInputProps('oauthDiscordRedirectUri')}
|
||||
/>
|
||||
</Paper>
|
||||
<Paper withBorder p='sm'>
|
||||
<Title order={4} mb='sm'>
|
||||
@@ -112,6 +125,11 @@ export default function ServerSettingsOauth({
|
||||
|
||||
<TextInput label='Google Client ID' {...form.getInputProps('oauthGoogleClientId')} />
|
||||
<TextInput label='Google Client Secret' {...form.getInputProps('oauthGoogleClientSecret')} />
|
||||
<TextInput
|
||||
label='Google Redirect URL'
|
||||
description='The redirect URL to use instead of the host when logging in. This must end with /api/auth/oauth/google'
|
||||
{...form.getInputProps('oauthGoogleRedirectUri')}
|
||||
/>
|
||||
</Paper>
|
||||
</SimpleGrid>
|
||||
|
||||
@@ -121,6 +139,11 @@ export default function ServerSettingsOauth({
|
||||
<SimpleGrid mt='md' cols={{ base: 1, md: 2 }} spacing='lg'>
|
||||
<TextInput label='GitHub Client ID' {...form.getInputProps('oauthGithubClientId')} />
|
||||
<TextInput label='GitHub Client Secret' {...form.getInputProps('oauthGithubClientSecret')} />
|
||||
<TextInput
|
||||
label='GitHub Redirect URL'
|
||||
description='The redirect URL to use instead of the host when logging in. This must end with /api/auth/oauth/github'
|
||||
{...form.getInputProps('oauthGithubRedirectUri')}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
</Paper>
|
||||
|
||||
@@ -133,6 +156,11 @@ export default function ServerSettingsOauth({
|
||||
<TextInput label='OIDC Authorize URL' {...form.getInputProps('oauthOidcAuthorizeUrl')} />
|
||||
<TextInput label='OIDC Token URL' {...form.getInputProps('oauthOidcTokenUrl')} />
|
||||
<TextInput label='OIDC Userinfo URL' {...form.getInputProps('oauthOidcUserinfoUrl')} />
|
||||
<TextInput
|
||||
label='OIDC Redirect URL'
|
||||
description='The redirect URL to use instead of the host when logging in. This must end with /api/auth/oauth/oidc'
|
||||
{...form.getInputProps('oauthOidcRedirectUri')}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
</Paper>
|
||||
|
||||
|
||||
@@ -223,18 +223,22 @@ export const DATABASE_TO_PROP = {
|
||||
|
||||
oauthDiscordClientId: 'oauth.discord.clientId',
|
||||
oauthDiscordClientSecret: 'oauth.discord.clientSecret',
|
||||
oauthDiscordRedirectUri: 'oauth.discord.redirectUri',
|
||||
|
||||
oauthGoogleClientId: 'oauth.google.clientId',
|
||||
oauthGoogleClientSecret: 'oauth.google.clientSecret',
|
||||
oauthGoogleRedirectUri: 'oauth.google.redirectUri',
|
||||
|
||||
oauthGithubClientId: 'oauth.github.clientId',
|
||||
oauthGithubClientSecret: 'oauth.github.clientSecret',
|
||||
oauthGithubRedirectUri: 'oauth.github.redirectUri',
|
||||
|
||||
oauthOidcClientId: 'oauth.oidc.clientId',
|
||||
oauthOidcClientSecret: 'oauth.oidc.clientSecret',
|
||||
oauthOidcAuthorizeUrl: 'oauth.oidc.authorizeUrl',
|
||||
oauthOidcUserinfoUrl: 'oauth.oidc.userinfoUrl',
|
||||
oauthOidcTokenUrl: 'oauth.oidc.tokenUrl',
|
||||
oauthOidcRedirectUri: 'oauth.oidc.redirectUri',
|
||||
|
||||
mfaTotpEnabled: 'mfa.totp.enabled',
|
||||
mfaTotpIssuer: 'mfa.totp.issuer',
|
||||
|
||||
@@ -213,33 +213,39 @@ export const schema = z.object({
|
||||
.object({
|
||||
clientId: z.string(),
|
||||
clientSecret: z.string(),
|
||||
redirectUri: z.string().url().nullable().default(null),
|
||||
})
|
||||
.or(
|
||||
z.object({
|
||||
clientId: z.undefined(),
|
||||
clientSecret: z.undefined(),
|
||||
redirectUri: z.undefined(),
|
||||
}),
|
||||
),
|
||||
github: z
|
||||
.object({
|
||||
clientId: z.string(),
|
||||
clientSecret: z.string(),
|
||||
redirectUri: z.string().url().nullable().default(null),
|
||||
})
|
||||
.or(
|
||||
z.object({
|
||||
clientId: z.undefined(),
|
||||
clientSecret: z.undefined(),
|
||||
redirectUri: z.undefined(),
|
||||
}),
|
||||
),
|
||||
google: z
|
||||
.object({
|
||||
clientId: z.string(),
|
||||
clientSecret: z.string(),
|
||||
redirectUri: z.string().url().nullable().default(null),
|
||||
})
|
||||
.or(
|
||||
z.object({
|
||||
clientId: z.undefined(),
|
||||
clientSecret: z.undefined(),
|
||||
redirectUri: z.undefined(),
|
||||
}),
|
||||
),
|
||||
oidc: z
|
||||
@@ -249,6 +255,7 @@ export const schema = z.object({
|
||||
authorizeUrl: z.string().url(),
|
||||
userinfoUrl: z.string().url(),
|
||||
tokenUrl: z.string().url(),
|
||||
redirectUri: z.string().url().nullable().default(null),
|
||||
})
|
||||
.or(
|
||||
z.object({
|
||||
@@ -257,6 +264,7 @@ export const schema = z.object({
|
||||
authorizeUrl: z.undefined(),
|
||||
userinfoUrl: z.undefined(),
|
||||
tokenUrl: z.undefined(),
|
||||
redirectUri: z.undefined(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
@@ -362,7 +370,7 @@ function handleError(error: ZodIssue) {
|
||||
const path =
|
||||
error.path[1] === 'externalLinks'
|
||||
? `WEBSITE_EXTERNAL_LINKS[${error.path[2]}]`
|
||||
: PROP_TO_ENV[<keyof typeof PROP_TO_ENV>error.path.join('.')] ?? error.path.join('.');
|
||||
: (PROP_TO_ENV[<keyof typeof PROP_TO_ENV>error.path.join('.')] ?? error.path.join('.'));
|
||||
|
||||
logger.error(`${path}: ${error.message}`);
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ export function findProvider(
|
||||
}
|
||||
|
||||
export const githubAuth = {
|
||||
url: (clientId: string, state?: string) =>
|
||||
url: (clientId: string, state?: string, redirectUri?: string) =>
|
||||
`https://github.com/login/oauth/authorize?client_id=${clientId}&scope=read:user${
|
||||
state ? `&state=${state}` : ''
|
||||
}`,
|
||||
}${redirectUri ? `&redirect_uri=${encodeURIComponent(redirectUri)}` : ''}`,
|
||||
user: async (accessToken: string) => {
|
||||
const res = await fetch('https://api.github.com/user', {
|
||||
headers: {
|
||||
@@ -26,9 +26,9 @@ export const githubAuth = {
|
||||
};
|
||||
|
||||
export const discordAuth = {
|
||||
url: (clientId: string, origin: string, state?: string) =>
|
||||
url: (clientId: string, origin: string, state?: string, redirectUri?: string) =>
|
||||
`https://discord.com/api/oauth2/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(
|
||||
`${origin}/api/auth/oauth/discord`,
|
||||
redirectUri ?? `${origin}/api/auth/oauth/discord`,
|
||||
)}&response_type=code&scope=identify${state ? `&state=${state}` : ''}`,
|
||||
user: async (accessToken: string) => {
|
||||
const res = await fetch('https://discord.com/api/users/@me', {
|
||||
@@ -43,9 +43,9 @@ export const discordAuth = {
|
||||
};
|
||||
|
||||
export const googleAuth = {
|
||||
url: (clientId: string, origin: string, state?: string) =>
|
||||
url: (clientId: string, origin: string, state?: string, redirectUri?: string) =>
|
||||
`https://accounts.google.com/o/oauth2/auth?client_id=${clientId}&redirect_uri=${encodeURIComponent(
|
||||
`${origin}/api/auth/oauth/google`,
|
||||
redirectUri ?? `${origin}/api/auth/oauth/google`,
|
||||
)}&response_type=code&access_type=offline&scope=https://www.googleapis.com/auth/userinfo.profile${
|
||||
state ? `&state=${state}` : ''
|
||||
}`,
|
||||
@@ -62,9 +62,9 @@ export const googleAuth = {
|
||||
};
|
||||
|
||||
export const oidcAuth = {
|
||||
url: (clientId: string, origin: string, authorizeUrl: string, state?: string) =>
|
||||
url: (clientId: string, origin: string, authorizeUrl: string, state?: string, redirectUri?: string) =>
|
||||
`${authorizeUrl}?client_id=${clientId}&redirect_uri=${encodeURIComponent(
|
||||
`${origin}/api/auth/oauth/oidc`,
|
||||
redirectUri ?? `${origin}/api/auth/oauth/oidc`,
|
||||
)}&response_type=code&scope=openid+email+profile+offline_access${state ? `&state=${state}` : ''}`,
|
||||
user: async (accessToken: string, userInfoUrl: string) => {
|
||||
const res = await fetch(userInfoUrl, {
|
||||
|
||||
@@ -28,6 +28,7 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
config.oauth.discord.clientId!,
|
||||
`${config.core.returnHttpsUrls ? 'https' : 'http'}://${host}`,
|
||||
state,
|
||||
config.oauth.discord.redirectUri ?? undefined,
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,11 @@ async function handler({ code, state }: OAuthQuery, logger: Logger): Promise<OAu
|
||||
|
||||
if (!code)
|
||||
return {
|
||||
redirect: githubAuth.url(config.oauth.github.clientId!, state),
|
||||
redirect: githubAuth.url(
|
||||
config.oauth.github.clientId!,
|
||||
state,
|
||||
config.oauth.github.redirectUri ?? undefined,
|
||||
),
|
||||
};
|
||||
|
||||
const body = JSON.stringify({
|
||||
|
||||
@@ -28,6 +28,7 @@ async function handler({ code, state, host }: OAuthQuery, _logger: Logger): Prom
|
||||
config.oauth.google.clientId!,
|
||||
`${config.core.returnHttpsUrls ? 'https' : 'http'}://${host}`,
|
||||
state,
|
||||
config.oauth.google.redirectUri ?? undefined,
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ async function handler({ code, state, host }: OAuthQuery, _logger: Logger): Prom
|
||||
`${config.core.returnHttpsUrls ? 'https' : 'http'}://${host}`,
|
||||
config.oauth.oidc.authorizeUrl!,
|
||||
state,
|
||||
config.oauth.oidc.redirectUri ?? undefined,
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
@@ -180,15 +180,22 @@ export default fastifyPlugin(
|
||||
|
||||
oauthDiscordClientId: z.string().nullable(),
|
||||
oauthDiscordClientSecret: z.string().nullable(),
|
||||
oauthDiscordRedirectUri: z.string().url().endsWith('/api/auth/oauth/discord').nullable(),
|
||||
|
||||
oauthGoogleClientId: z.string().nullable(),
|
||||
oauthGoogleClientSecret: z.string().nullable(),
|
||||
oauthGoogleRedirectUri: z.string().url().endsWith('/api/auth/oauth/google').nullable(),
|
||||
|
||||
oauthGithubClientId: z.string().nullable(),
|
||||
oauthGithubClientSecret: z.string().nullable(),
|
||||
oauthGithubRedirectUri: z.string().url().endsWith('/api/auth/oauth/github').nullable(),
|
||||
|
||||
oauthOidcClientId: z.string().nullable(),
|
||||
oauthOidcClientSecret: z.string().nullable(),
|
||||
oauthOidcAuthorizeUrl: z.string().url().nullable(),
|
||||
oauthOidcTokenUrl: z.string().url().nullable(),
|
||||
oauthOidcUserinfoUrl: z.string().url().nullable(),
|
||||
oauthOidcRedirectUri: z.string().url().endsWith('/api/auth/oauth/oidc').nullable(),
|
||||
|
||||
mfaTotpEnabled: z.boolean(),
|
||||
mfaTotpIssuer: z.string(),
|
||||
|
||||
Reference in New Issue
Block a user