Skip to content

Commit

Permalink
bring back discord auth
Browse files Browse the repository at this point in the history
  • Loading branch information
nichtsam committed Jan 15, 2025
1 parent 2fce5be commit 330dc6f
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 1 deletion.
2 changes: 2 additions & 0 deletions app/utils/auth/connections.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Authenticator } from 'remix-auth'
import { env } from '#app/utils/env.server.ts'
import { type ServerTiming } from '../timings.server.ts'
import { type ProviderName } from './connections.tsx'
import { DiscordProvider } from './providers/discord.server.ts'
import { GitHubProvider } from './providers/github.server.ts'
import { type AuthProvider, type ProviderUser } from './providers/model.ts'

Expand All @@ -19,6 +20,7 @@ export const connectionSessionStorage = createCookieSessionStorage({

export const providers: Record<ProviderName, AuthProvider> = {
github: new GitHubProvider(),
discord: new DiscordProvider(),
}

export const createAuthenticator = (request: Request) => {
Expand Down
10 changes: 9 additions & 1 deletion app/utils/auth/connections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ import { Icon } from '#app/components/ui/icon.tsx'
import { useIsPending } from '../ui.ts'

export const GITHUB_PROVIDER_NAME = 'github'
export const DISCORD_PROVIDER_NAME = 'discord'

export const providerNames = [GITHUB_PROVIDER_NAME] as const
export const providerNames = [
GITHUB_PROVIDER_NAME,
DISCORD_PROVIDER_NAME,
] as const
export const ProviderNameSchema = z.enum(providerNames)
export type ProviderName = z.infer<typeof ProviderNameSchema>

Expand All @@ -19,6 +23,10 @@ export const providerConfigs: Record<ProviderName, ProviderConfig> = {
label: 'Github',
icon: <Icon name="github-logo" className="inline-block" />,
},
[DISCORD_PROVIDER_NAME]: {
label: 'Discord',
icon: <Icon name="discord-logo" className="inline-block" />,
},
}

export const ProviderConnectionForm = ({
Expand Down
109 changes: 109 additions & 0 deletions app/utils/auth/providers/discord.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { DiscordStrategy } from '@nichtsam/remix-auth-discord'
import { z } from 'zod'
import { cachified, longLivedCache } from '#app/utils/cache.server.ts'
import { env } from '#app/utils/env.server.ts'
import { type ServerTiming } from '#app/utils/timings.server.ts'
import { type AuthProvider } from './model.ts'

const getEmail = (profile: Profile) =>
profile.verified && profile.email ? profile.email : undefined
const getDisplayName = (profile: Profile) =>
profile.global_name ?? profile.username
const getAvatarUrl = (profile: Profile) =>
profile.avatar
? `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.png`
: undefined

// pick needed from here: https://discord.com/developers/docs/resources/user#user-object
type Profile = z.infer<typeof ProfileSchema>
const ProfileSchema = z.object({
id: z.string(),
username: z.string(),
global_name: z.string().nullable(),
email: z.string().nullable().optional(),
verified: z.boolean().optional(),
avatar: z.string().nullable(),
})
const getProfile = async (accessToken: string) => {
const response = await fetch('https://discord.com/api/v10/users/@me', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})

const result = ProfileSchema.safeParse(await response.json())
if (!result.success) {
throw new Error(
'Corrupted discord profile, make sure the schema is in sync with lastet github api schema',
)
}
return result.data
}

export class DiscordProvider implements AuthProvider {
getAuthStrategy(request: Request) {
return new DiscordStrategy(
{
clientId: env.DISCORD_CLIENT_ID,
clientSecret: env.DISCORD_CLIENT_SECRET,
redirectURI: new URL('/auth/discord/callback', request.url),
scopes: ['identify', 'email'],
},
async ({ tokens }) => {
const accessToken = tokens.accessToken()
const profile = await getProfile(accessToken)

return {
id: profile.id,
email: getEmail(profile),
username: profile.username,
name: getDisplayName(profile),
imageUrl: getAvatarUrl(profile),
}
},
)
}

async resolveConnectionInfo(
providerId: string,
{ timing }: { timing?: ServerTiming } = {},
) {
const result = await cachified({
key: `connection-info:discord:${providerId}`,
cache: longLivedCache,
ttl: 1000 * 60,
swr: 1000 * 60 * 60 * 24 * 7,
timing,
getFreshValue: async (context) => {
const response = await fetch(
`https://discord.com/api/v10/users/${providerId}`,
{
headers: {
Authorization: `Bot ${env.DISCORD_BOT_TOKEN}`,
},
},
)
const rawJson = await response.json()
const result = ProfileSchema.safeParse(rawJson)

if (!result.success) {
context.metadata.ttl = 0
}

return result
},
})

if (!result.success) {
return {
connectionUserDisplayName: 'Unknown' as const,
profileLink: null,
}
}

return {
connectionUserDisplayName: getDisplayName(result.data),
profileLink: null,
}
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@epic-web/remember": "1.1.0",
"@libsql/client": "0.14.0",
"@nasa-gcn/remix-seo": "2.0.1",
"@nichtsam/remix-auth-discord": "3.0.0",
"@radix-ui/react-aspect-ratio": "1.1.1",
"@radix-ui/react-avatar": "1.1.2",
"@radix-ui/react-checkbox": "1.1.3",
Expand Down
17 changes: 17 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 330dc6f

Please sign in to comment.