From 5d5b096c963dd62e86c78d921b9bc0a8e6e6af64 Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 9 Jun 2021 17:03:53 +0300 Subject: [PATCH 01/23] adds utility functions and updates schema for User --- helpers/discordAuth.ts | 67 ++++++++++++++++++++++++++++++++++++++++++ prisma/schema.prisma | 1 + 2 files changed, 68 insertions(+) create mode 100644 helpers/discordAuth.ts diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts new file mode 100644 index 000000000..9570515a0 --- /dev/null +++ b/helpers/discordAuth.ts @@ -0,0 +1,67 @@ +const qs = require('qs') +const client_id = process.env.DISCORD_KEY +const client_secret = process.env.DISCORD_SECRET + +type DiscordUserInfoResponse = { + discordUsername: string + discordAvatarUrl: string + discordRefreshToken: string +} + +export const getTokenFromAuthCode = (code: string) => { + return fetch(`https://discordapp.com/api/oauth2/token`, { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' + }, + body: qs.stringify({ + grant_type: 'authorization_code', + client_id, + client_secret, + code, + redirect_uri: 'https://c0d3.com/discord/redir' + }) + }).then(r => r.json()) +} + +export const getTokenFromRefreshToken = (refresh_token: string) => { + return fetch(`https://discordapp.com/api/oauth2/token`, { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' + }, + body: qs.stringify({ + client_id, + client_secret, + grant_type: 'refresh_token', + refresh_token + }) + }).then(r => r.json()) +} + +export const getUserInfo = (accessToken: string) => { + return fetch(`https://discordapp.com/api/users/@me`, { + method: 'GET', + headers: { + 'content-type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Bearer ${accessToken}` + } + }).then(r => r.json()) +} + +export const getUserInfoFromRefreshToken = async ( + refreshToken: string +): Promise => { + const tokenResponse = await getTokenFromRefreshToken(refreshToken) + if (!tokenResponse.refresh_token) throw new Error('refresh token invalid') + + const { username, avatar } = await getUserInfo(tokenResponse.access_token) + return { + discordUsername: username, + discordAvatarUrl: avatar, + discordRefreshToken: tokenResponse.refresh_token + } +} + +// in login check for refreshToken and start the auth flow process +// import getUserInfoFromRefreshToken into getUserInfo and createSubmission resolvers diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 46160105d..2f2ca4b62 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -144,6 +144,7 @@ model User { cliToken String? @db.VarChar(255) emailVerificationToken String? @db.VarChar(255) tokenExpiration DateTime? @db.Timestamptz(6) + discordRefreshToken String? @db.VarChar(255) starsMentor Star[] @relation("starMentor") starsGiven Star[] @relation("starStudent") submissionsReviewed Submission[] @relation("userReviewedSubmissions") From 5353a2242c7951bb70e4381c5f7b8528e11595ea Mon Sep 17 00:00:00 2001 From: Charles Date: Fri, 11 Jun 2021 11:46:49 +0300 Subject: [PATCH 02/23] backend for integration with Discord --- graphql/index.tsx | 12 +++++++++++- graphql/queries/userInfo.ts | 1 + graphql/typeDefs.ts | 1 + helpers/discordAuth.ts | 19 ++++++++++++++----- .../migration.sql | 2 ++ 5 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 prisma/migrations/20210609140541_adds_refresh_token_to_user/migration.sql diff --git a/graphql/index.tsx b/graphql/index.tsx index 8be91dc6e..c2d782206 100644 --- a/graphql/index.tsx +++ b/graphql/index.tsx @@ -301,6 +301,7 @@ export type User = { name: Scalars['String'] isAdmin: Scalars['Boolean'] cliToken?: Maybe + discordRefreshToken?: Maybe } export type UserLesson = { @@ -876,7 +877,10 @@ export type UserInfoQuery = { __typename?: 'Query' } & { userInfo?: Maybe< { __typename?: 'Session' } & { user?: Maybe< - { __typename?: 'User' } & Pick + { __typename?: 'User' } & Pick< + User, + 'id' | 'username' | 'name' | 'discordRefreshToken' + > > submissions?: Maybe< Array< @@ -1442,6 +1446,11 @@ export type UserResolvers< name?: Resolver isAdmin?: Resolver cliToken?: Resolver, ParentType, ContextType> + discordRefreshToken?: Resolver< + Maybe, + ParentType, + ContextType + > __isTypeOf?: IsTypeOfResolverFn } @@ -3661,6 +3670,7 @@ export const UserInfoDocument = gql` id username name + discordRefreshToken } submissions { id diff --git a/graphql/queries/userInfo.ts b/graphql/queries/userInfo.ts index 90a1c0409..0f65952b7 100644 --- a/graphql/queries/userInfo.ts +++ b/graphql/queries/userInfo.ts @@ -23,6 +23,7 @@ const USER_INFO = gql` id username name + discordRefreshToken } submissions { id diff --git a/graphql/typeDefs.ts b/graphql/typeDefs.ts index a7ba94873..20082b346 100644 --- a/graphql/typeDefs.ts +++ b/graphql/typeDefs.ts @@ -143,6 +143,7 @@ export default gql` name: String! isAdmin: Boolean! cliToken: String + discordRefreshToken: String } type Session { diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts index 9570515a0..f27f5b9f9 100644 --- a/helpers/discordAuth.ts +++ b/helpers/discordAuth.ts @@ -1,8 +1,9 @@ +import { prisma } from '../prisma' const qs = require('qs') const client_id = process.env.DISCORD_KEY const client_secret = process.env.DISCORD_SECRET -type DiscordUserInfoResponse = { +type DiscordUserInfo = { discordUsername: string discordAvatarUrl: string discordRefreshToken: string @@ -49,19 +50,27 @@ export const getUserInfo = (accessToken: string) => { }).then(r => r.json()) } +export const updateUserRefreshToken = (userId: number, refreshToken: string) => + prisma.user.update({ + where: { + id: userId + }, + data: { + discordRefreshToken: refreshToken + } + }) + export const getUserInfoFromRefreshToken = async ( refreshToken: string -): Promise => { +): Promise => { const tokenResponse = await getTokenFromRefreshToken(refreshToken) if (!tokenResponse.refresh_token) throw new Error('refresh token invalid') const { username, avatar } = await getUserInfo(tokenResponse.access_token) + return { discordUsername: username, discordAvatarUrl: avatar, discordRefreshToken: tokenResponse.refresh_token } } - -// in login check for refreshToken and start the auth flow process -// import getUserInfoFromRefreshToken into getUserInfo and createSubmission resolvers diff --git a/prisma/migrations/20210609140541_adds_refresh_token_to_user/migration.sql b/prisma/migrations/20210609140541_adds_refresh_token_to_user/migration.sql new file mode 100644 index 000000000..17bde5dcb --- /dev/null +++ b/prisma/migrations/20210609140541_adds_refresh_token_to_user/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "users" ADD COLUMN "discordRefreshToken" VARCHAR(255); From 37fd79c92849580e007abdff3d4253db3df7d35e Mon Sep 17 00:00:00 2001 From: Charles Date: Fri, 11 Jun 2021 12:28:36 +0300 Subject: [PATCH 03/23] removes refresh token from userInfo query --- graphql/index.tsx | 6 +----- graphql/queries/userInfo.ts | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/graphql/index.tsx b/graphql/index.tsx index fe5c36ea3..64f058b64 100644 --- a/graphql/index.tsx +++ b/graphql/index.tsx @@ -877,10 +877,7 @@ export type UserInfoQuery = { __typename?: 'Query' } & { userInfo?: Maybe< { __typename?: 'Session' } & { user?: Maybe< - { __typename?: 'User' } & Pick< - User, - 'id' | 'username' | 'name' | 'discordRefreshToken' - > + { __typename?: 'User' } & Pick > submissions?: Maybe< Array< @@ -3667,7 +3664,6 @@ export const UserInfoDocument = gql` id username name - discordRefreshToken } submissions { id diff --git a/graphql/queries/userInfo.ts b/graphql/queries/userInfo.ts index 0f65952b7..90a1c0409 100644 --- a/graphql/queries/userInfo.ts +++ b/graphql/queries/userInfo.ts @@ -23,7 +23,6 @@ const USER_INFO = gql` id username name - discordRefreshToken } submissions { id From 72d1ece897b9f38ee106a05991ce2279091811e7 Mon Sep 17 00:00:00 2001 From: Charles Date: Fri, 11 Jun 2021 13:51:38 +0300 Subject: [PATCH 04/23] replaces qs with URLSearchParams --- helpers/discordAuth.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts index f27f5b9f9..b36b7c458 100644 --- a/helpers/discordAuth.ts +++ b/helpers/discordAuth.ts @@ -1,5 +1,6 @@ import { prisma } from '../prisma' -const qs = require('qs') +import { URLSearchParams } from 'url' + const client_id = process.env.DISCORD_KEY const client_secret = process.env.DISCORD_SECRET @@ -15,7 +16,7 @@ export const getTokenFromAuthCode = (code: string) => { headers: { 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' }, - body: qs.stringify({ + body: new URLSearchParams({ grant_type: 'authorization_code', client_id, client_secret, @@ -31,7 +32,7 @@ export const getTokenFromRefreshToken = (refresh_token: string) => { headers: { 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' }, - body: qs.stringify({ + body: new URLSearchParams({ client_id, client_secret, grant_type: 'refresh_token', From c1b2b280b12797081067a3633d6a9685d96fc7b2 Mon Sep 17 00:00:00 2001 From: Charles Date: Fri, 11 Jun 2021 16:20:43 +0300 Subject: [PATCH 05/23] adds return types to discord Auth functions --- helpers/discordAuth.ts | 43 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts index b36b7c458..266ba6a5f 100644 --- a/helpers/discordAuth.ts +++ b/helpers/discordAuth.ts @@ -1,16 +1,39 @@ import { prisma } from '../prisma' import { URLSearchParams } from 'url' +import { User } from '.prisma/client' const client_id = process.env.DISCORD_KEY const client_secret = process.env.DISCORD_SECRET +type AccessTokenResponse = { + access_token: string + token_type: string + expires_in: number + refresh_token: string + scope: string +} + +type UserInfoResponse = { + id: string + username: string + avatar: string + discriminator: string + public_flags: number + locale: string + mfa_enabled: boolean + email: string + verified: boolean +} + type DiscordUserInfo = { discordUsername: string discordAvatarUrl: string discordRefreshToken: string } -export const getTokenFromAuthCode = (code: string) => { +export const getTokenFromAuthCode = ( + code: string +): Promise => { return fetch(`https://discordapp.com/api/oauth2/token`, { method: 'POST', headers: { @@ -21,12 +44,15 @@ export const getTokenFromAuthCode = (code: string) => { client_id, client_secret, code, - redirect_uri: 'https://c0d3.com/discord/redir' + redirect_uri: 'https://c0d3.com/discord/redir', + scope: 'email guilds.join gdm.join identify' }) }).then(r => r.json()) } -export const getTokenFromRefreshToken = (refresh_token: string) => { +export const getTokenFromRefreshToken = ( + refresh_token: string +): Promise => { return fetch(`https://discordapp.com/api/oauth2/token`, { method: 'POST', headers: { @@ -41,7 +67,7 @@ export const getTokenFromRefreshToken = (refresh_token: string) => { }).then(r => r.json()) } -export const getUserInfo = (accessToken: string) => { +export const getUserInfo = (accessToken: string): Promise => { return fetch(`https://discordapp.com/api/users/@me`, { method: 'GET', headers: { @@ -51,8 +77,11 @@ export const getUserInfo = (accessToken: string) => { }).then(r => r.json()) } -export const updateUserRefreshToken = (userId: number, refreshToken: string) => - prisma.user.update({ +export const updateUserRefreshToken = async ( + userId: number, + refreshToken: string +): Promise => { + const updateUser = await prisma.user.update({ where: { id: userId }, @@ -60,6 +89,8 @@ export const updateUserRefreshToken = (userId: number, refreshToken: string) => discordRefreshToken: refreshToken } }) + return updateUser +} export const getUserInfoFromRefreshToken = async ( refreshToken: string From 2da3c5a2d1c13b3c038b98332c9345479207d202 Mon Sep 17 00:00:00 2001 From: Charles Date: Sat, 12 Jun 2021 09:45:50 +0300 Subject: [PATCH 06/23] excludes discordAuth.ts from test coverage --- jest.config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jest.config.ts b/jest.config.ts index 292f848a9..06c35241e 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -15,7 +15,8 @@ const config: Config.InitialOptions = { 'graphql/index.tsx', '__tests__/utils/', 'prisma/', - 'scripts/' + 'scripts/', + 'discordAuth.ts' ], coverageThreshold: { global: { From 8a8e5f1b91e8b94f260642c47605136d25de0a4f Mon Sep 17 00:00:00 2001 From: Charles Date: Sat, 12 Jun 2021 21:28:53 +0300 Subject: [PATCH 07/23] set base discordAPI url --- helpers/discordAuth.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts index 266ba6a5f..c60ef5f7e 100644 --- a/helpers/discordAuth.ts +++ b/helpers/discordAuth.ts @@ -2,6 +2,7 @@ import { prisma } from '../prisma' import { URLSearchParams } from 'url' import { User } from '.prisma/client' +const discordAPI = 'https://discordapp.com/api' const client_id = process.env.DISCORD_KEY const client_secret = process.env.DISCORD_SECRET @@ -34,7 +35,7 @@ type DiscordUserInfo = { export const getTokenFromAuthCode = ( code: string ): Promise => { - return fetch(`https://discordapp.com/api/oauth2/token`, { + return fetch(`${discordAPI}/oauth2/token`, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' @@ -53,7 +54,7 @@ export const getTokenFromAuthCode = ( export const getTokenFromRefreshToken = ( refresh_token: string ): Promise => { - return fetch(`https://discordapp.com/api/oauth2/token`, { + return fetch(`${discordAPI}/oauth2/token`, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' @@ -68,7 +69,7 @@ export const getTokenFromRefreshToken = ( } export const getUserInfo = (accessToken: string): Promise => { - return fetch(`https://discordapp.com/api/users/@me`, { + return fetch(`${discordAPI}/users/@me`, { method: 'GET', headers: { 'content-type': 'application/x-www-form-urlencoded;charset=utf-8', From 0ce8003a5edc775da66e77078163c8b32b63db5b Mon Sep 17 00:00:00 2001 From: Charles Date: Sun, 20 Jun 2021 09:11:38 +0300 Subject: [PATCH 08/23] adds discord user id to userInfo return --- helpers/discordAuth.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts index c60ef5f7e..7a0b9dc32 100644 --- a/helpers/discordAuth.ts +++ b/helpers/discordAuth.ts @@ -27,6 +27,7 @@ type UserInfoResponse = { } type DiscordUserInfo = { + discordUserId: string discordUsername: string discordAvatarUrl: string discordRefreshToken: string @@ -99,9 +100,10 @@ export const getUserInfoFromRefreshToken = async ( const tokenResponse = await getTokenFromRefreshToken(refreshToken) if (!tokenResponse.refresh_token) throw new Error('refresh token invalid') - const { username, avatar } = await getUserInfo(tokenResponse.access_token) + const { id, username, avatar } = await getUserInfo(tokenResponse.access_token) return { + discordUserId: id, discordUsername: username, discordAvatarUrl: avatar, discordRefreshToken: tokenResponse.refresh_token From dea73e228c7ee97fd0059e46308b7184bd6a3f56 Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 23 Jun 2021 09:32:47 +0300 Subject: [PATCH 09/23] removes discord token from graphql query --- graphql/index.tsx | 6 ------ graphql/typeDefs.ts | 1 - helpers/discordAuth.ts | 4 ++-- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/graphql/index.tsx b/graphql/index.tsx index 31645a4bd..47f3582b0 100644 --- a/graphql/index.tsx +++ b/graphql/index.tsx @@ -301,7 +301,6 @@ export type User = { name: Scalars['String'] isAdmin: Scalars['Boolean'] cliToken?: Maybe - discordRefreshToken?: Maybe } export type UserLesson = { @@ -1440,11 +1439,6 @@ export type UserResolvers< name?: Resolver isAdmin?: Resolver cliToken?: Resolver, ParentType, ContextType> - discordRefreshToken?: Resolver< - Maybe, - ParentType, - ContextType - > __isTypeOf?: IsTypeOfResolverFn } diff --git a/graphql/typeDefs.ts b/graphql/typeDefs.ts index de686208a..816a5d254 100644 --- a/graphql/typeDefs.ts +++ b/graphql/typeDefs.ts @@ -143,7 +143,6 @@ export default gql` name: String! isAdmin: Boolean! cliToken: String - discordRefreshToken: String } type Session { diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts index 7a0b9dc32..a06f06689 100644 --- a/helpers/discordAuth.ts +++ b/helpers/discordAuth.ts @@ -52,7 +52,7 @@ export const getTokenFromAuthCode = ( }).then(r => r.json()) } -export const getTokenFromRefreshToken = ( +const getTokenFromRefreshToken = ( refresh_token: string ): Promise => { return fetch(`${discordAPI}/oauth2/token`, { @@ -69,7 +69,7 @@ export const getTokenFromRefreshToken = ( }).then(r => r.json()) } -export const getUserInfo = (accessToken: string): Promise => { +const getUserInfo = (accessToken: string): Promise => { return fetch(`${discordAPI}/users/@me`, { method: 'GET', headers: { From d291d4860c20010369118f498e9b7515a59ce52a Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 23 Jun 2021 10:36:00 +0300 Subject: [PATCH 10/23] removes unnecessary exports --- helpers/discordAuth.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts index a06f06689..3c64aab44 100644 --- a/helpers/discordAuth.ts +++ b/helpers/discordAuth.ts @@ -79,11 +79,11 @@ const getUserInfo = (accessToken: string): Promise => { }).then(r => r.json()) } -export const updateUserRefreshToken = async ( +const updateUserRefreshToken = async ( userId: number, refreshToken: string ): Promise => { - const updateUser = await prisma.user.update({ + const updatedUser = await prisma.user.update({ where: { id: userId }, @@ -91,14 +91,21 @@ export const updateUserRefreshToken = async ( discordRefreshToken: refreshToken } }) - return updateUser + return updatedUser } export const getUserInfoFromRefreshToken = async ( + userId: number, refreshToken: string ): Promise => { const tokenResponse = await getTokenFromRefreshToken(refreshToken) - if (!tokenResponse.refresh_token) throw new Error('refresh token invalid') + const updatedRefreshToken = tokenResponse.refresh_token + if (!updatedRefreshToken) { + updateUserRefreshToken(userId, '') // discordRefreshToken given a falsy value + throw new Error('refresh token invalid') + } + + updateUserRefreshToken(userId, updatedRefreshToken) const { id, username, avatar } = await getUserInfo(tokenResponse.access_token) @@ -106,6 +113,6 @@ export const getUserInfoFromRefreshToken = async ( discordUserId: id, discordUsername: username, discordAvatarUrl: avatar, - discordRefreshToken: tokenResponse.refresh_token + discordRefreshToken: updatedRefreshToken } } From 2dbd2c7e07d8a6838d4d5d23c499e031c3a23819 Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 23 Jun 2021 14:34:07 +0300 Subject: [PATCH 11/23] refactored getUserInfoFromRefreshToken function --- helpers/discordAuth.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts index 3c64aab44..b235cfb82 100644 --- a/helpers/discordAuth.ts +++ b/helpers/discordAuth.ts @@ -99,14 +99,11 @@ export const getUserInfoFromRefreshToken = async ( refreshToken: string ): Promise => { const tokenResponse = await getTokenFromRefreshToken(refreshToken) - const updatedRefreshToken = tokenResponse.refresh_token - if (!updatedRefreshToken) { - updateUserRefreshToken(userId, '') // discordRefreshToken given a falsy value - throw new Error('refresh token invalid') - } - + const updatedRefreshToken = tokenResponse.refresh_token || '' updateUserRefreshToken(userId, updatedRefreshToken) + if (!updatedRefreshToken) throw new Error('refresh token invalid') + const { id, username, avatar } = await getUserInfo(tokenResponse.access_token) return { From 26f744415a21ad8f8bacd32dcc6d0a55e4c51608 Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 23 Jun 2021 16:54:57 +0300 Subject: [PATCH 12/23] made variable names less verbose --- helpers/discordAuth.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts index b235cfb82..e90b7718a 100644 --- a/helpers/discordAuth.ts +++ b/helpers/discordAuth.ts @@ -27,10 +27,10 @@ type UserInfoResponse = { } type DiscordUserInfo = { - discordUserId: string - discordUsername: string - discordAvatarUrl: string - discordRefreshToken: string + userId: string + username: string + avatarUrl: string + refreshToken: string } export const getTokenFromAuthCode = ( @@ -107,9 +107,9 @@ export const getUserInfoFromRefreshToken = async ( const { id, username, avatar } = await getUserInfo(tokenResponse.access_token) return { - discordUserId: id, - discordUsername: username, - discordAvatarUrl: avatar, - discordRefreshToken: updatedRefreshToken + userId: id, + username, + avatarUrl: avatar, + refreshToken: updatedRefreshToken } } From b6f2f5a9e35a9b480b7497390682e484b91a7aa1 Mon Sep 17 00:00:00 2001 From: Charles Date: Fri, 25 Jun 2021 19:03:42 +0300 Subject: [PATCH 13/23] changes avatar url to return an actual url, not a hash --- helpers/discordAuth.ts | 2 +- jest.config.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts index e90b7718a..ad7fffeb9 100644 --- a/helpers/discordAuth.ts +++ b/helpers/discordAuth.ts @@ -109,7 +109,7 @@ export const getUserInfoFromRefreshToken = async ( return { userId: id, username, - avatarUrl: avatar, + avatarUrl: `https://cdn.discordapp.com/avatars/${id}/${avatar}.png`, refreshToken: updatedRefreshToken } } diff --git a/jest.config.ts b/jest.config.ts index 06c35241e..292f848a9 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -15,8 +15,7 @@ const config: Config.InitialOptions = { 'graphql/index.tsx', '__tests__/utils/', 'prisma/', - 'scripts/', - 'discordAuth.ts' + 'scripts/' ], coverageThreshold: { global: { From 3cdd672f0cca869e88901591faf6a652d617fb6f Mon Sep 17 00:00:00 2001 From: Charles Date: Sat, 26 Jun 2021 18:00:35 +0300 Subject: [PATCH 14/23] adds test module for discordAuth --- helpers/discordAuth.test.js | 58 +++++++++++++++++++++++++++++++++++++ helpers/discordAuth.ts | 1 + 2 files changed, 59 insertions(+) create mode 100644 helpers/discordAuth.test.js diff --git a/helpers/discordAuth.test.js b/helpers/discordAuth.test.js new file mode 100644 index 000000000..d15b0a5c2 --- /dev/null +++ b/helpers/discordAuth.test.js @@ -0,0 +1,58 @@ +import { getUserInfoFromRefreshToken } from './discordAuth' + +const updateUserRefreshToken = jest.spyOn( + require('./discordAuth'), + 'updateUserRefreshToken' +) + +const mockTokenResponse = { + access_token: '6qrZcUqja7812RVdnEKjpzOL4CvHBFG', + token_type: 'Bearer', + expires_in: 604800, + refresh_token: 'D43f5y0ahjqew82jZ4NViEr2YafMKhue', + scope: 'email guilds.join gdm.join identify' +} + +const mockUserInfoResponse = { + id: '756944741073027119', + username: 'charolastra', + avatar: 'ea8f5f59aff14450e892321ba128745d', + discriminator: '3886', + public_flags: 0, + flags: 0, + locale: 'en-US', + mfa_enabled: true, + email: 'charlieworkhd@gmail.com', + verified: true +} + +const mockUserInfo = { + userId: 1, + username: 'charolastra', + avatarUrl: `https://cdn.discordapp.com/avatars/756944741073027119/ea8f5f59aff14450e892321ba128745d.png`, + refreshToken: 'D43f5y0ahjqew82jZ4NViEr2YafMKhue' +} + +describe('getUserInfoFromRefreshToken function', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it('should update refresh token in database if refresh token invalid', async () => { + expect( + await getUserInfoFromRefreshToken(1, 'mockRefreshToken') + ).rejects.toThrow('refresh token invalid') + expect(updateUserRefreshToken).toBeCalledWith(1, '') + }) + + it('should return user info if refresh token valid', async () => { + getTokenFromRefreshToken.mockResolvedValue(mockUserInfoResponse) + expect( + await getUserInfoFromRefreshToken(1, 'mockRefreshToken') + ).resolves.toEqual(mockUserInfo) + expect(updateUserRefreshToken).toBeCalledWith( + 1, + mockTokenResponse.refresh_token + ) + }) +}) diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts index ad7fffeb9..dbebac4ed 100644 --- a/helpers/discordAuth.ts +++ b/helpers/discordAuth.ts @@ -1,6 +1,7 @@ import { prisma } from '../prisma' import { URLSearchParams } from 'url' import { User } from '.prisma/client' +import fetch from 'node-fetch' const discordAPI = 'https://discordapp.com/api' const client_id = process.env.DISCORD_KEY From 94b92f4c64e31fdf6547e520354f731014c04790 Mon Sep 17 00:00:00 2001 From: Charles Date: Sat, 26 Jun 2021 18:06:26 +0300 Subject: [PATCH 15/23] await updateUserRefreshToken --- helpers/discordAuth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts index dbebac4ed..e07aeb21e 100644 --- a/helpers/discordAuth.ts +++ b/helpers/discordAuth.ts @@ -80,7 +80,7 @@ const getUserInfo = (accessToken: string): Promise => { }).then(r => r.json()) } -const updateUserRefreshToken = async ( +export const updateUserRefreshToken = async ( userId: number, refreshToken: string ): Promise => { @@ -101,7 +101,7 @@ export const getUserInfoFromRefreshToken = async ( ): Promise => { const tokenResponse = await getTokenFromRefreshToken(refreshToken) const updatedRefreshToken = tokenResponse.refresh_token || '' - updateUserRefreshToken(userId, updatedRefreshToken) + await updateUserRefreshToken(userId, updatedRefreshToken) if (!updatedRefreshToken) throw new Error('refresh token invalid') From dbf40037d374cdb77580f3abab30e455dbb14f55 Mon Sep 17 00:00:00 2001 From: Charles Date: Sat, 26 Jun 2021 20:21:08 +0300 Subject: [PATCH 16/23] got first test working --- helpers/discordAuth.test.js | 30 ++++++++++++++---------------- helpers/discordAuth.ts | 2 +- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/helpers/discordAuth.test.js b/helpers/discordAuth.test.js index d15b0a5c2..20f33fbed 100644 --- a/helpers/discordAuth.test.js +++ b/helpers/discordAuth.test.js @@ -1,9 +1,5 @@ import { getUserInfoFromRefreshToken } from './discordAuth' - -const updateUserRefreshToken = jest.spyOn( - require('./discordAuth'), - 'updateUserRefreshToken' -) +import { prisma } from '../prisma' const mockTokenResponse = { access_token: '6qrZcUqja7812RVdnEKjpzOL4CvHBFG', @@ -37,22 +33,24 @@ describe('getUserInfoFromRefreshToken function', () => { beforeEach(() => { jest.clearAllMocks() }) - + const userUpdateFn = (prisma.user.update = jest.fn()) it('should update refresh token in database if refresh token invalid', async () => { - expect( - await getUserInfoFromRefreshToken(1, 'mockRefreshToken') + await expect( + getUserInfoFromRefreshToken(1, 'mockRefreshToken') ).rejects.toThrow('refresh token invalid') - expect(updateUserRefreshToken).toBeCalledWith(1, '') + expect(userUpdateFn).toBeCalled() }) it('should return user info if refresh token valid', async () => { - getTokenFromRefreshToken.mockResolvedValue(mockUserInfoResponse) - expect( - await getUserInfoFromRefreshToken(1, 'mockRefreshToken') + jest.mock('node-fetch') + const fetch = require('node-fetch') + fetch + .mockReturnValueOnce(mockTokenResponse) + .mockReturnValueOnce(mockUserInfoResponse) + await expect( + getUserInfoFromRefreshToken(1, 'mockRefreshToken') ).resolves.toEqual(mockUserInfo) - expect(updateUserRefreshToken).toBeCalledWith( - 1, - mockTokenResponse.refresh_token - ) + expect(userUpdateFn).toBeCalled() + expect(fetch.mock.calls.length).toBe(2) }) }) diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts index e07aeb21e..c878ddb42 100644 --- a/helpers/discordAuth.ts +++ b/helpers/discordAuth.ts @@ -80,7 +80,7 @@ const getUserInfo = (accessToken: string): Promise => { }).then(r => r.json()) } -export const updateUserRefreshToken = async ( +const updateUserRefreshToken = async ( userId: number, refreshToken: string ): Promise => { From 4ac3d5f3c9f7044ec73e0c97ab6e7adf92bc48dd Mon Sep 17 00:00:00 2001 From: Charles Date: Sat, 26 Jun 2021 21:07:40 +0300 Subject: [PATCH 17/23] adds test for getTokenFromAuthCode --- helpers/discordAuth.test.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/helpers/discordAuth.test.js b/helpers/discordAuth.test.js index 20f33fbed..311b3aa50 100644 --- a/helpers/discordAuth.test.js +++ b/helpers/discordAuth.test.js @@ -1,4 +1,7 @@ -import { getUserInfoFromRefreshToken } from './discordAuth' +import { + getTokenFromAuthCode, + getUserInfoFromRefreshToken +} from './discordAuth' import { prisma } from '../prisma' const mockTokenResponse = { @@ -29,6 +32,15 @@ const mockUserInfo = { refreshToken: 'D43f5y0ahjqew82jZ4NViEr2YafMKhue' } +describe('getTokenFromAuthCode function', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + jest.mock('node-fetch') + const fetch = require('node-fetch') + expect(fetch.mock.calls.length).toBe(1) +}) + describe('getUserInfoFromRefreshToken function', () => { beforeEach(() => { jest.clearAllMocks() From 12314517c9f0064a76ea458c8caa1f2c7e05d0f9 Mon Sep 17 00:00:00 2001 From: kondanna Date: Fri, 16 Jul 2021 13:19:05 +0300 Subject: [PATCH 18/23] added Promise to fetch mock return value --- helpers/discordAuth.test.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/helpers/discordAuth.test.js b/helpers/discordAuth.test.js index 311b3aa50..ff09334cc 100644 --- a/helpers/discordAuth.test.js +++ b/helpers/discordAuth.test.js @@ -3,6 +3,7 @@ import { getUserInfoFromRefreshToken } from './discordAuth' import { prisma } from '../prisma' +import fetch, {Response} from 'node-fetch' const mockTokenResponse = { access_token: '6qrZcUqja7812RVdnEKjpzOL4CvHBFG', @@ -32,16 +33,10 @@ const mockUserInfo = { refreshToken: 'D43f5y0ahjqew82jZ4NViEr2YafMKhue' } -describe('getTokenFromAuthCode function', () => { - beforeEach(() => { - jest.clearAllMocks() - }) +describe('getUserInfoFromRefreshToken function', () => { jest.mock('node-fetch') const fetch = require('node-fetch') - expect(fetch.mock.calls.length).toBe(1) -}) - -describe('getUserInfoFromRefreshToken function', () => { + const { Response } = jest.requireActual('node-fetch') beforeEach(() => { jest.clearAllMocks() }) @@ -54,11 +49,9 @@ describe('getUserInfoFromRefreshToken function', () => { }) it('should return user info if refresh token valid', async () => { - jest.mock('node-fetch') - const fetch = require('node-fetch') fetch - .mockReturnValueOnce(mockTokenResponse) - .mockReturnValueOnce(mockUserInfoResponse) + .mockReturnValueOnce(Promise.resolve(new Response(mockTokenResponse))) + .mockReturnValueOnce(Promise.resolve(new Response(mockUserInfoResponse))) await expect( getUserInfoFromRefreshToken(1, 'mockRefreshToken') ).resolves.toEqual(mockUserInfo) From bdb3c0ad1012865b5a11f0777fbeeb451c2ff09e Mon Sep 17 00:00:00 2001 From: kondanna Date: Sun, 18 Jul 2021 16:15:08 +0300 Subject: [PATCH 19/23] mocking functions instead of fetch --- helpers/discordAuth.test.js | 46 ++++++------------------------------- 1 file changed, 7 insertions(+), 39 deletions(-) diff --git a/helpers/discordAuth.test.js b/helpers/discordAuth.test.js index ff09334cc..c840aceac 100644 --- a/helpers/discordAuth.test.js +++ b/helpers/discordAuth.test.js @@ -3,40 +3,9 @@ import { getUserInfoFromRefreshToken } from './discordAuth' import { prisma } from '../prisma' -import fetch, {Response} from 'node-fetch' - -const mockTokenResponse = { - access_token: '6qrZcUqja7812RVdnEKjpzOL4CvHBFG', - token_type: 'Bearer', - expires_in: 604800, - refresh_token: 'D43f5y0ahjqew82jZ4NViEr2YafMKhue', - scope: 'email guilds.join gdm.join identify' -} - -const mockUserInfoResponse = { - id: '756944741073027119', - username: 'charolastra', - avatar: 'ea8f5f59aff14450e892321ba128745d', - discriminator: '3886', - public_flags: 0, - flags: 0, - locale: 'en-US', - mfa_enabled: true, - email: 'charlieworkhd@gmail.com', - verified: true -} - -const mockUserInfo = { - userId: 1, - username: 'charolastra', - avatarUrl: `https://cdn.discordapp.com/avatars/756944741073027119/ea8f5f59aff14450e892321ba128745d.png`, - refreshToken: 'D43f5y0ahjqew82jZ4NViEr2YafMKhue' -} +import { tokenResponse, userInfoResponse, userInfo } from '../__dummy__/discordOAuthData' describe('getUserInfoFromRefreshToken function', () => { - jest.mock('node-fetch') - const fetch = require('node-fetch') - const { Response } = jest.requireActual('node-fetch') beforeEach(() => { jest.clearAllMocks() }) @@ -49,13 +18,12 @@ describe('getUserInfoFromRefreshToken function', () => { }) it('should return user info if refresh token valid', async () => { - fetch - .mockReturnValueOnce(Promise.resolve(new Response(mockTokenResponse))) - .mockReturnValueOnce(Promise.resolve(new Response(mockUserInfoResponse))) - await expect( - getUserInfoFromRefreshToken(1, 'mockRefreshToken') - ).resolves.toEqual(mockUserInfo) + const getTokenFromRefreshToken = jest.fn().mockResolvedValue(tokenResponse) + const getUserInfo = jest.fn().mockResolvedValue(userInfoResponse) + const mockUserInfo = await getUserInfoFromRefreshToken(1, 'mockRefreshToken') + expect(mockUserInfo).toBe(userInfo) + expect(getTokenFromRefreshToken).toBeCalled() + expect(getUserInfo).toBeCalled() expect(userUpdateFn).toBeCalled() - expect(fetch.mock.calls.length).toBe(2) }) }) From 2e7590b6dcb7a56ad1fbd0cc5daeefb4e5f73fb2 Mon Sep 17 00:00:00 2001 From: kondanna Date: Sun, 18 Jul 2021 16:23:57 +0300 Subject: [PATCH 20/23] after linter --- helpers/discordAuth.test.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/helpers/discordAuth.test.js b/helpers/discordAuth.test.js index c840aceac..db1512830 100644 --- a/helpers/discordAuth.test.js +++ b/helpers/discordAuth.test.js @@ -3,7 +3,11 @@ import { getUserInfoFromRefreshToken } from './discordAuth' import { prisma } from '../prisma' -import { tokenResponse, userInfoResponse, userInfo } from '../__dummy__/discordOAuthData' +import { + tokenResponse, + userInfoResponse, + userInfo +} from '../__dummy__/discordOAuthData' describe('getUserInfoFromRefreshToken function', () => { beforeEach(() => { @@ -20,7 +24,10 @@ describe('getUserInfoFromRefreshToken function', () => { it('should return user info if refresh token valid', async () => { const getTokenFromRefreshToken = jest.fn().mockResolvedValue(tokenResponse) const getUserInfo = jest.fn().mockResolvedValue(userInfoResponse) - const mockUserInfo = await getUserInfoFromRefreshToken(1, 'mockRefreshToken') + const mockUserInfo = await getUserInfoFromRefreshToken( + 1, + 'mockRefreshToken' + ) expect(mockUserInfo).toBe(userInfo) expect(getTokenFromRefreshToken).toBeCalled() expect(getUserInfo).toBeCalled() From 65c4e51c3a8aa40a1d6995239443ee0484022604 Mon Sep 17 00:00:00 2001 From: kondanna Date: Sun, 18 Jul 2021 16:32:19 +0300 Subject: [PATCH 21/23] moves mock data to dummy folder --- __dummy__/discordOAuthData.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 __dummy__/discordOAuthData.js diff --git a/__dummy__/discordOAuthData.js b/__dummy__/discordOAuthData.js new file mode 100644 index 000000000..3203cb4a3 --- /dev/null +++ b/__dummy__/discordOAuthData.js @@ -0,0 +1,28 @@ +export default { + tokenResponse: { + access_token: '6qrZcUqja7812RVdnEKjpzOL4CvHBFG', + token_type: 'Bearer', + expires_in: 604800, + refresh_token: 'D43f5y0ahjqew82jZ4NViEr2YafMKhue', + scope: 'email guilds.join gdm.join identify' + }, + userInfoResponse: { + id: '756944741073027119', + username: 'charolastra', + avatar: 'ea8f5f59aff14450e892321ba128745d', + discriminator: '3886', + public_flags: 0, + flags: 0, + locale: 'en-US', + mfa_enabled: true, + email: 'charlieworkhd@gmail.com', + verified: true + }, + userInfo: { + userId: 1, + username: 'charolastra', + avatarUrl: `https://cdn.discordapp.com/avatars/756944741073027119/ea8f5f59aff14450e892321ba128745d.png`, + refreshToken: 'D43f5y0ahjqew82jZ4NViEr2YafMKhue' + } + } + \ No newline at end of file From 7b337513a91868f37615d6fc38347d5fec3936c7 Mon Sep 17 00:00:00 2001 From: kondanna Date: Sun, 18 Jul 2021 16:35:22 +0300 Subject: [PATCH 22/23] lints dummy data --- __dummy__/discordOAuthData.js | 51 +++++++++++++++++------------------ 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/__dummy__/discordOAuthData.js b/__dummy__/discordOAuthData.js index 3203cb4a3..55ec5e6b7 100644 --- a/__dummy__/discordOAuthData.js +++ b/__dummy__/discordOAuthData.js @@ -1,28 +1,27 @@ export default { - tokenResponse: { - access_token: '6qrZcUqja7812RVdnEKjpzOL4CvHBFG', - token_type: 'Bearer', - expires_in: 604800, - refresh_token: 'D43f5y0ahjqew82jZ4NViEr2YafMKhue', - scope: 'email guilds.join gdm.join identify' - }, - userInfoResponse: { - id: '756944741073027119', - username: 'charolastra', - avatar: 'ea8f5f59aff14450e892321ba128745d', - discriminator: '3886', - public_flags: 0, - flags: 0, - locale: 'en-US', - mfa_enabled: true, - email: 'charlieworkhd@gmail.com', - verified: true - }, - userInfo: { - userId: 1, - username: 'charolastra', - avatarUrl: `https://cdn.discordapp.com/avatars/756944741073027119/ea8f5f59aff14450e892321ba128745d.png`, - refreshToken: 'D43f5y0ahjqew82jZ4NViEr2YafMKhue' - } + tokenResponse: { + access_token: '6qrZcUqja7812RVdnEKjpzOL4CvHBFG', + token_type: 'Bearer', + expires_in: 604800, + refresh_token: 'D43f5y0ahjqew82jZ4NViEr2YafMKhue', + scope: 'email guilds.join gdm.join identify' + }, + userInfoResponse: { + id: '756944741073027119', + username: 'charolastra', + avatar: 'ea8f5f59aff14450e892321ba128745d', + discriminator: '3886', + public_flags: 0, + flags: 0, + locale: 'en-US', + mfa_enabled: true, + email: 'charlieworkhd@gmail.com', + verified: true + }, + userInfo: { + userId: 1, + username: 'charolastra', + avatarUrl: `https://cdn.discordapp.com/avatars/756944741073027119/ea8f5f59aff14450e892321ba128745d.png`, + refreshToken: 'D43f5y0ahjqew82jZ4NViEr2YafMKhue' } - \ No newline at end of file +} From 62b0f11e60ebb67497471ce1efced34f7d1bed30 Mon Sep 17 00:00:00 2001 From: kondanna Date: Mon, 23 Aug 2021 09:11:00 +0300 Subject: [PATCH 23/23] adds helper functions for discord OAuth integration --- __dummy__/discordOAuthData.js | 27 ------- helpers/discordAuth.test.js | 142 ++++++++++++++++++++++++++++------ helpers/discordAuth.ts | 8 +- 3 files changed, 123 insertions(+), 54 deletions(-) delete mode 100644 __dummy__/discordOAuthData.js diff --git a/__dummy__/discordOAuthData.js b/__dummy__/discordOAuthData.js deleted file mode 100644 index 55ec5e6b7..000000000 --- a/__dummy__/discordOAuthData.js +++ /dev/null @@ -1,27 +0,0 @@ -export default { - tokenResponse: { - access_token: '6qrZcUqja7812RVdnEKjpzOL4CvHBFG', - token_type: 'Bearer', - expires_in: 604800, - refresh_token: 'D43f5y0ahjqew82jZ4NViEr2YafMKhue', - scope: 'email guilds.join gdm.join identify' - }, - userInfoResponse: { - id: '756944741073027119', - username: 'charolastra', - avatar: 'ea8f5f59aff14450e892321ba128745d', - discriminator: '3886', - public_flags: 0, - flags: 0, - locale: 'en-US', - mfa_enabled: true, - email: 'charlieworkhd@gmail.com', - verified: true - }, - userInfo: { - userId: 1, - username: 'charolastra', - avatarUrl: `https://cdn.discordapp.com/avatars/756944741073027119/ea8f5f59aff14450e892321ba128745d.png`, - refreshToken: 'D43f5y0ahjqew82jZ4NViEr2YafMKhue' - } -} diff --git a/helpers/discordAuth.test.js b/helpers/discordAuth.test.js index db1512830..4dca66c48 100644 --- a/helpers/discordAuth.test.js +++ b/helpers/discordAuth.test.js @@ -1,36 +1,132 @@ +jest.mock('node-fetch') +jest.mock('prisma') +import fetch from 'node-fetch' +import prisma from '../prisma' +import { URLSearchParams } from 'url' + import { getTokenFromAuthCode, getUserInfoFromRefreshToken } from './discordAuth' -import { prisma } from '../prisma' -import { - tokenResponse, - userInfoResponse, - userInfo -} from '../__dummy__/discordOAuthData' + +describe('getTokenFromAuthCode function', () => { + test('fetch should be called once', async () => { + fetch.mockResolvedValue({ json: () => {} }) + const mockRefreshToken = await getTokenFromAuthCode('123') + + expect(fetch.mock.calls[0][0]).toBe( + 'https://discordapp.com/api/oauth2/token' + ) + expect(fetch.mock.calls[0][1]).toEqual({ + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' + }, + body: new URLSearchParams({ + grant_type: 'authorization_code', + client_id: undefined, + client_secret: undefined, + code: '123', + redirect_uri: 'https://c0d3.com/discord/redir', + scope: 'email guilds.join gdm.join identify' + }) + }) + }) +}) describe('getUserInfoFromRefreshToken function', () => { beforeEach(() => { jest.clearAllMocks() + prisma.user.update = jest.fn() }) - const userUpdateFn = (prisma.user.update = jest.fn()) - it('should update refresh token in database if refresh token invalid', async () => { - await expect( - getUserInfoFromRefreshToken(1, 'mockRefreshToken') - ).rejects.toThrow('refresh token invalid') - expect(userUpdateFn).toBeCalled() - }) + it('should call the correct functions and update refresh token in database and return the correct object if refresh token valid', async () => { + fetch.mockResolvedValueOnce({ + json: () => ({ + refresh_token: 'fakeRefreshToken', + access_token: 'fakeAccessToken' + }) + }) + fetch.mockResolvedValueOnce({ + json: () => ({ + id: 'discord123', + username: 'discord-fakeuser', + avatar: 'ea8f5f59aff14450e892321ba128745d' + }) + }) + const result = await getUserInfoFromRefreshToken(123, 'mockRefreshToken') - it('should return user info if refresh token valid', async () => { - const getTokenFromRefreshToken = jest.fn().mockResolvedValue(tokenResponse) - const getUserInfo = jest.fn().mockResolvedValue(userInfoResponse) - const mockUserInfo = await getUserInfoFromRefreshToken( - 1, - 'mockRefreshToken' + expect(fetch.mock.calls.length).toBe(2) + + // first fetch function call + expect(fetch.mock.calls[0][0]).toBe( + 'https://discordapp.com/api/oauth2/token' ) - expect(mockUserInfo).toBe(userInfo) - expect(getTokenFromRefreshToken).toBeCalled() - expect(getUserInfo).toBeCalled() - expect(userUpdateFn).toBeCalled() + expect(fetch.mock.calls[0][1]).toEqual({ + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' + }, + body: new URLSearchParams({ + client_id: undefined, + client_secret: undefined, + grant_type: 'refresh_token', + refresh_token: 'mockRefreshToken' + }) + }) + + // second fetch function call + expect(fetch.mock.calls[1][0]).toBe('https://discordapp.com/api/users/@me') + expect(fetch.mock.calls[1][1]).toEqual({ + method: 'GET', + headers: { + 'content-type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: 'Bearer fakeAccessToken' + } + }) + + // database call + expect(prisma.user.update.mock.calls.length).toBe(1) + expect(prisma.user.update.mock.calls[0][0]).toEqual({ + where: { + id: 123 + }, + data: { + discordRefreshToken: 'fakeRefreshToken' + } + }) + + expect(result).toEqual({ + userId: 'discord123', + username: 'discord-fakeuser', + avatarUrl: + 'https://cdn.discordapp.com/avatars/discord123/ea8f5f59aff14450e892321ba128745d.png', + refreshToken: 'fakeRefreshToken' + }) + }) + + it('should throw error and update database with empty string if refresh token invalid', async () => { + fetch.mockResolvedValueOnce({ + json: () => ({}) + }) + try { + const result = await getUserInfoFromRefreshToken( + 123, + 'invalidRefreshToken' + ) + expect(false).toEqual(true) // force test to fail + } catch (error) { + expect(error.message).toBe('refresh token invalid') + } + + expect(fetch.mock.calls.length).toBe(1) + expect(prisma.user.update.mock.calls.length).toBe(1) + expect(prisma.user.update.mock.calls[0][0]).toEqual({ + where: { + id: 123 + }, + data: { + discordRefreshToken: '' + } + }) }) }) diff --git a/helpers/discordAuth.ts b/helpers/discordAuth.ts index c878ddb42..e1916c945 100644 --- a/helpers/discordAuth.ts +++ b/helpers/discordAuth.ts @@ -1,4 +1,4 @@ -import { prisma } from '../prisma' +import prisma from '../prisma' import { URLSearchParams } from 'url' import { User } from '.prisma/client' import fetch from 'node-fetch' @@ -80,11 +80,11 @@ const getUserInfo = (accessToken: string): Promise => { }).then(r => r.json()) } -const updateUserRefreshToken = async ( +const updateUserRefreshToken = ( userId: number, refreshToken: string ): Promise => { - const updatedUser = await prisma.user.update({ + return prisma.user.update({ where: { id: userId }, @@ -92,7 +92,6 @@ const updateUserRefreshToken = async ( discordRefreshToken: refreshToken } }) - return updatedUser } export const getUserInfoFromRefreshToken = async ( @@ -101,6 +100,7 @@ export const getUserInfoFromRefreshToken = async ( ): Promise => { const tokenResponse = await getTokenFromRefreshToken(refreshToken) const updatedRefreshToken = tokenResponse.refresh_token || '' + // if updatedRefreshToken is undefined, empty string is stored in db to remove invalid refresh tokens await updateUserRefreshToken(userId, updatedRefreshToken) if (!updatedRefreshToken) throw new Error('refresh token invalid')