diff --git a/explorer/next-auth.d.ts b/explorer/next-auth.d.ts index 59a321891..fd3dbf8dd 100644 --- a/explorer/next-auth.d.ts +++ b/explorer/next-auth.d.ts @@ -11,6 +11,7 @@ declare module 'next-auth' { DIDs: string[] subspace?: SubspaceToken discord?: DiscordToken + github?: GitHubToken } export interface Session { @@ -31,6 +32,7 @@ declare module 'next-auth/client' { DIDs: string[] subspace?: SubspaceToken discord?: DiscordToken + github?: GitHubToken } export interface Session { @@ -46,6 +48,7 @@ declare module 'next-auth/jwt' { DIDs: string[] subspace?: SubspaceToken discord?: DiscordToken + github?: GitHubToken } interface JWT { @@ -53,5 +56,6 @@ declare module 'next-auth/jwt' { DIDs: string[] subspace?: SubspaceToken discord?: DiscordToken + github?: GitHubToken } } diff --git a/explorer/src/components/WalletSideKick/Actions/ConnectGitHub.tsx b/explorer/src/components/WalletSideKick/Actions/ConnectGitHub.tsx new file mode 100644 index 000000000..dd38a3f2d --- /dev/null +++ b/explorer/src/components/WalletSideKick/Actions/ConnectGitHub.tsx @@ -0,0 +1,31 @@ +import { StyledListItem } from 'components/common/List' +import { StyledButton } from 'components/common/StyledButton' +import { CheckMarkIcon } from 'components/icons/CheckMarkIcon' +import { signIn, useSession } from 'next-auth/react' +import { FC, useCallback } from 'react' + +export const ConnectGitHub: FC = () => { + const { data: session } = useSession() + + const handleConnectGitHub = useCallback( + async () => await signIn('github', { redirect: false }), + [], + ) + + if (!session || !session.user) return null + + return ( + + {session?.user?.github?.vcs ? ( + <> + + + Refresh + + + ) : ( + Connect + )} + + ) +} diff --git a/explorer/src/components/WalletSideKick/GetDiscordRoles.tsx b/explorer/src/components/WalletSideKick/GetDiscordRoles.tsx index 5fbcbcb6f..aa1e061a4 100644 --- a/explorer/src/components/WalletSideKick/GetDiscordRoles.tsx +++ b/explorer/src/components/WalletSideKick/GetDiscordRoles.tsx @@ -7,6 +7,7 @@ import Link from 'next/link' import { FC, useMemo, useState } from 'react' // import { ClaimStakingToken } from './Actions/ClaimStakingToken' import { ConnectDiscord } from './Actions/ConnectDiscord' +import { ConnectGitHub } from './Actions/ConnectGitHub' import { JoinDiscord } from './Actions/JoinDiscord' import { VerifyWalletOwnership } from './Actions/VerifyWalletOwnership' @@ -85,6 +86,7 @@ export const GetDiscordRoles: FC = () => { )} + {/* */} @@ -99,6 +101,7 @@ export const GetDiscordRoles: FC = () => { + diff --git a/explorer/src/constants/session.ts b/explorer/src/constants/session.ts index c10e3af71..a65fa5669 100644 --- a/explorer/src/constants/session.ts +++ b/explorer/src/constants/session.ts @@ -1,4 +1,4 @@ -import type { DiscordToken, SubspaceToken } from 'types/jwt' +import type { DiscordToken, GitHubToken, SubspaceToken } from 'types/jwt' export const TOKEN_EXPIRATION = 60 * 60 * 24 // 1 day @@ -34,3 +34,8 @@ export const DEFAULT_DISCORD_TOKEN: DiscordToken = { }, }, } + +export const DEFAULT_GITHUB_TOKEN: GitHubToken = { + id: '', + username: '', +} diff --git a/explorer/src/types/jwt.ts b/explorer/src/types/jwt.ts index 2798c51e2..eab532dba 100644 --- a/explorer/src/types/jwt.ts +++ b/explorer/src/types/jwt.ts @@ -24,3 +24,8 @@ export type DiscordToken = { } } } + +export type GitHubToken = { + id: string + username: string +} diff --git a/explorer/src/utils/auth/authOptions.ts b/explorer/src/utils/auth/authOptions.ts index 2898508de..0932524c9 100644 --- a/explorer/src/utils/auth/authOptions.ts +++ b/explorer/src/utils/auth/authOptions.ts @@ -27,6 +27,7 @@ export const authOptions: AuthOptions = { token.DIDs = user.DIDs token.subspace = user.subspace token.discord = user.discord + token.github = user.github } return token }, diff --git a/explorer/src/utils/auth/providers/discord.ts b/explorer/src/utils/auth/providers/discord.ts index 23a915a3d..e149f5228 100644 --- a/explorer/src/utils/auth/providers/discord.ts +++ b/explorer/src/utils/auth/providers/discord.ts @@ -1,4 +1,4 @@ -import { AuthProvider } from 'constants/session' +import { AuthProvider, DEFAULT_GITHUB_TOKEN } from 'constants/session' import type { TokenSet } from 'next-auth' import { User } from 'next-auth' import type { DiscordProfile } from 'next-auth/providers/discord' @@ -82,6 +82,7 @@ export const Discord = () => { }, }, }, + github: session.github || DEFAULT_GITHUB_TOKEN, } if (!savedUser || savedUser.length === 0) { diff --git a/explorer/src/utils/auth/providers/github.ts b/explorer/src/utils/auth/providers/github.ts new file mode 100644 index 000000000..7416e7d84 --- /dev/null +++ b/explorer/src/utils/auth/providers/github.ts @@ -0,0 +1,46 @@ +import * as jsonwebtoken from 'jsonwebtoken' +import type { TokenSet } from 'next-auth' +import { JWT } from 'next-auth/jwt' +import GitHubProvider, { GithubProfile } from 'next-auth/providers/github' +import { cookies } from 'next/headers' + +const { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } = process.env + +export const GitHub = () => { + return GitHubProvider({ + // client credentials + clientId: GITHUB_CLIENT_ID || '', + clientSecret: GITHUB_CLIENT_SECRET || '', + + // fetch discord profile + profile: async (profile: GithubProfile, token: TokenSet) => { + try { + if (!token.access_token) throw new Error('No access token') + + if (!process.env.NEXTAUTH_SECRET) throw new Error('No secret') + const { NEXTAUTH_SECRET } = process.env + + const { get } = cookies() + const sessionToken = get('next-auth.session-token')?.value || '' + const session = jsonwebtoken.verify(sessionToken, NEXTAUTH_SECRET, { + algorithms: ['HS256'], + }) as JWT + const did = 'did:openid:github:' + profile.id + + return { + id: session.id || did, + DIDs: [...session.DIDs, did], + subspace: session.subspace, + discord: session.discord, + github: { + id: profile.id, + username: profile.login, + }, + } + } catch (error) { + console.error('Error fetching Discord profile:', error) + throw new Error('Failed to fetch Discord profile') + } + }, + }) +} diff --git a/explorer/src/utils/auth/providers/index.ts b/explorer/src/utils/auth/providers/index.ts index 5a7410ca0..17c7a07e5 100644 --- a/explorer/src/utils/auth/providers/index.ts +++ b/explorer/src/utils/auth/providers/index.ts @@ -1,6 +1,7 @@ import { Provider } from 'next-auth/providers' import { Discord } from './discord' +import { GitHub } from './github' import { Nova } from './nova' import { Subspace } from './subspace' -export const providers: Provider[] = [Discord(), Subspace(), Nova()] +export const providers: Provider[] = [Discord(), GitHub(), Subspace(), Nova()] diff --git a/explorer/src/utils/auth/providers/subspace.ts b/explorer/src/utils/auth/providers/subspace.ts index dd61389ac..72e14606a 100644 --- a/explorer/src/utils/auth/providers/subspace.ts +++ b/explorer/src/utils/auth/providers/subspace.ts @@ -1,5 +1,5 @@ -import { cryptoWaitReady, signatureVerify } from '@autonomys/auto-utils' -import { AuthProvider, DEFAULT_DISCORD_TOKEN } from 'constants/session' +import { cryptoWaitReady, signatureVerify } from '@polkadot/util-crypto' +import { AuthProvider, DEFAULT_DISCORD_TOKEN, DEFAULT_GITHUB_TOKEN } from 'constants/session' import { User } from 'next-auth' import type { Provider } from 'next-auth/providers' import CredentialsProvider from 'next-auth/providers/credentials' @@ -55,6 +55,7 @@ export const Subspace = () => { }, }, discord: DEFAULT_DISCORD_TOKEN, + github: DEFAULT_GITHUB_TOKEN, } if (!savedUser || savedUser.length === 0) {