From d547f801efa522d06b6862febda75526f682b951 Mon Sep 17 00:00:00 2001 From: CODEVO Date: Tue, 24 Jan 2023 07:52:55 +0000 Subject: [PATCH 1/5] updated --- example.env | 4 - package.json | 6 +- prisma/dev.db | Bin 24576 -> 24576 bytes prisma/dev.db-journal | Bin 0 -> 8720 bytes .../20230117090416_users/migration.sql | 27 ----- .../migration.sql | 4 +- src/controllers/auth.controller.ts | 56 ---------- src/routes/auth.route.ts | 5 - src/routes/session.route.ts | 10 +- src/routes/user.route.ts | 1 - src/services/session.service.ts | 96 ------------------ yarn.lock | 36 +++---- 12 files changed, 26 insertions(+), 219 deletions(-) create mode 100644 prisma/dev.db-journal delete mode 100644 prisma/migrations/20230117090416_users/migration.sql rename prisma/migrations/{20230117090020_init => 20230124063002_init}/migration.sql (83%) diff --git a/example.env b/example.env index e6e8a47..2113a6c 100644 --- a/example.env +++ b/example.env @@ -4,10 +4,6 @@ GOOGLE_OAUTH_CLIENT_ID= GOOGLE_OAUTH_CLIENT_SECRET= GOOGLE_OAUTH_REDIRECT=http://localhost:8000/api/sessions/oauth/google -GITHUB_OAUTH_CLIENT_ID= -GITHUB_OAUTH_CLIENT_SECRET= -GITHUB_OAUTH_REDIRECT_URL=http://localhost:8000/api/sessions/oauth/github - NODE_ENV=development JWT_SECRET=my_ultra_secure_secret TOKEN_EXPIRES_IN=60 diff --git a/package.json b/package.json index 8a0c11b..6129512 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "ts-node-dev --respawn --transpile-only src/app.ts", "db:migrate": "npx prisma migrate dev --name 'users' --create-only", - "db:generate": " npx prisma generate", + "db:generate": "npx prisma generate", "db:push": "npx prisma db push" }, "devDependencies": { @@ -18,12 +18,12 @@ "@types/node": "^18.11.18", "@types/qs": "^6.9.7", "morgan": "^1.10.0", - "prisma": "^4.8.1", + "prisma": "^4.9.0", "ts-node-dev": "^2.0.0", "typescript": "^4.9.4" }, "dependencies": { - "@prisma/client": "^4.8.1", + "@prisma/client": "^4.9.0", "axios": "^1.2.2", "cookie-parser": "^1.4.6", "cors": "^2.8.5", diff --git a/prisma/dev.db b/prisma/dev.db index cdc731bd1437b3ac83a8cb2209e21d95f3d5e93c..9fb1ba239d4e6c1774f383a65681923836155141 100644 GIT binary patch delta 253 zcma)%Jqp4=6oj)xQV0?)tV9&-#K7D4HrcnQ(fx5Djg^I+Y3%hV>IKqy2`f)xENsPr zDTWV*nWhy@E6$Tqnj}fq`K$D@B9UG<)7ddZ?~sEnoK#RKE?vQuj78L%Lkw)ORHbrW zSKguusK7E;M*AowknzUGfJPgz7D{46jD{c>y9JPeBs%HD>2+9)5s*N31r-6{y57}$ daq4v>z0IHb@}D7t&w=56G=JomGiCi>@eOQnKHC5Q delta 883 zcma)4&ubGw6wY>=Vy(?uByB~CkscK4G_$j_vong=+N!8kK^luf(b<{VO(fY3-A!70 zNeiY2FWNuBKR`r#w)Uvk_Ts^UH;?sZFM_)P5&QuenD>~+$NRqby;tULWg5z3yHP%_TkG*%qXj~^MyW(9JjXD8Djn+Slp zZ~>PTCr^qzBy7i{-p#Na6lO#9B*`*VQ+|zCs*@tD)p-;xhfC5#RwGhNQhB^ljVJ4Y zUkwGXYV8l-_V+vSOVBe=8ktB*TM4Ul5(7L40Wv88*g_OgYAawXgr&qdF^WvfS|#9V znCist2oIW16T*Y$=K4kLSm}&2m|ti8C$m`u9YsxQ1JrUU16Y{?xWX}khqD!OB8Bg0+>mvA6XkE}p?c@I9uT zf%lc86!PZ+gINzy&G)Jw1OgC%00bZa0SG_<0uX=z1Rwx`uL_*T)pT{5DWlYAl1qv# zIn+^Gs;xA!mR6U#I48Me3EbGm#^l-**k-mAL|X4s(VC1;K9tPfc?y9VBi;ymL)nK7 zk3+2}M7-{{-L5*n4$I;FIDhojK7RnU_-aWUg8&2|009U<00Izz00bZa O0SG|g#{_QI=j9DDHz@A_ literal 0 HcmV?d00001 diff --git a/prisma/migrations/20230117090416_users/migration.sql b/prisma/migrations/20230117090416_users/migration.sql deleted file mode 100644 index dd11c97..0000000 --- a/prisma/migrations/20230117090416_users/migration.sql +++ /dev/null @@ -1,27 +0,0 @@ -/* - Warnings: - - - You are about to drop the `User` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "User"; -PRAGMA foreign_keys=on; - --- CreateTable -CREATE TABLE "users" ( - "id" TEXT NOT NULL PRIMARY KEY, - "name" TEXT NOT NULL, - "email" TEXT NOT NULL, - "password" TEXT NOT NULL, - "role" TEXT NOT NULL DEFAULT 'user', - "photo" TEXT NOT NULL DEFAULT 'default.png', - "verified" BOOLEAN NOT NULL DEFAULT false, - "provider" TEXT NOT NULL DEFAULT 'local', - "createdAt" DATETIME NOT NULL, - "updatedAt" DATETIME NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); diff --git a/prisma/migrations/20230117090020_init/migration.sql b/prisma/migrations/20230124063002_init/migration.sql similarity index 83% rename from prisma/migrations/20230117090020_init/migration.sql rename to prisma/migrations/20230124063002_init/migration.sql index 1279bf9..984032f 100644 --- a/prisma/migrations/20230117090020_init/migration.sql +++ b/prisma/migrations/20230124063002_init/migration.sql @@ -1,5 +1,5 @@ -- CreateTable -CREATE TABLE "User" ( +CREATE TABLE "users" ( "id" TEXT NOT NULL PRIMARY KEY, "name" TEXT NOT NULL, "email" TEXT NOT NULL, @@ -13,4 +13,4 @@ CREATE TABLE "User" ( ); -- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); +CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 5775dc9..2c7eb4c 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -1,8 +1,6 @@ import { NextFunction, Request, Response } from "express"; import { CreateUserInput, LoginUserInput } from "../schema/user.schema"; import { - getGithubOathToken, - getGithubUser, getGoogleOauthToken, getGoogleUser, } from "../services/session.service"; @@ -171,57 +169,3 @@ export const googleOauthHandler = async (req: Request, res: Response) => { return res.redirect(`${FRONTEND_ORIGIN}/oauth/error`); } }; - -export const githubOauthHandler = async (req: Request, res: Response) => { - const FRONTEND_ORIGIN = process.env.FRONTEND_ORIGIN as unknown as string; - - try { - const code = req.query.code as string; - const pathUrl = (req.query.state as string) ?? "/"; - - if (req.query.error) { - return res.redirect(`${FRONTEND_ORIGIN}/login`); - } - - if (!code) { - return res.status(401).json({ - status: "error", - message: "Authorization code not provided!", - }); - } - - const { access_token } = await getGithubOathToken({ code }); - - const { email, avatar_url, login } = await getGithubUser({ access_token }); - - const user = await prisma.user.upsert({ - where: { email }, - create: { - createdAt: new Date(), - name: login, - email, - photo: avatar_url, - password: "", - verified: true, - provider: "GitHub", - }, - update: { name: login, email, photo: avatar_url, provider: "GitHub" }, - }); - - if (!user) return res.redirect(`${FRONTEND_ORIGIN}/oauth/error`); - - const TOKEN_EXPIRES_IN = process.env.TOKEN_EXPIRES_IN as unknown as number; - const TOKEN_SECRET = process.env.JWT_SECRET as unknown as string; - const token = jwt.sign({ sub: user.id }, TOKEN_SECRET, { - expiresIn: `${TOKEN_EXPIRES_IN}m`, - }); - - res.cookie("token", token, { - expires: new Date(Date.now() + TOKEN_EXPIRES_IN * 60 * 1000), - }); - - res.redirect(`${FRONTEND_ORIGIN}${pathUrl}`); - } catch (err: any) { - return res.redirect(`${FRONTEND_ORIGIN}/oauth/error`); - } -}; diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index 17df0ab..f9cddf5 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -11,13 +11,8 @@ import { createUserSchema, loginUserSchema } from "../schema/user.schema"; const router = express.Router(); -// Register user route router.post("/register", validate(createUserSchema), registerHandler); - -// Login user route router.post("/login", validate(loginUserSchema), loginHandler); - -// Logout User router.get("/logout", deserializeUser, requireUser, logoutHandler); export default router; diff --git a/src/routes/session.route.ts b/src/routes/session.route.ts index 2f1e0ca..e787b05 100644 --- a/src/routes/session.route.ts +++ b/src/routes/session.route.ts @@ -1,12 +1,8 @@ -import express from 'express'; -import { - githubOauthHandler, - googleOauthHandler, -} from '../controllers/auth.controller'; +import express from "express"; +import { googleOauthHandler } from "../controllers/auth.controller"; const router = express.Router(); -router.get('/oauth/google', googleOauthHandler); -router.get('/oauth/github', githubOauthHandler); +router.get("/oauth/google", googleOauthHandler); export default router; diff --git a/src/routes/user.route.ts b/src/routes/user.route.ts index 1bfcd0c..b09f83f 100644 --- a/src/routes/user.route.ts +++ b/src/routes/user.route.ts @@ -7,7 +7,6 @@ const router = express.Router(); router.use(deserializeUser, requireUser); -// Get my info route router.get("/me", getMeHandler); export default router; diff --git a/src/services/session.service.ts b/src/services/session.service.ts index bfef8b0..bb55d26 100644 --- a/src/services/session.service.ts +++ b/src/services/session.service.ts @@ -8,11 +8,6 @@ const GOOGLE_OAUTH_CLIENT_SECRET = process.env const GOOGLE_OAUTH_REDIRECT = process.env .GOOGLE_OAUTH_REDIRECT as unknown as string; -const GITHUB_OAUTH_CLIENT_ID = process.env - .GITHUB_OAUTH_CLIENT_ID as unknown as string; -const GITHUB_OAUTH_CLIENT_SECRET = process.env - .GITHUB_OAUTH_CLIENT_SECRET as unknown as string; - interface GoogleOauthToken { access_token: string; id_token: string; @@ -88,94 +83,3 @@ export async function getGoogleUser({ throw Error(err); } } - -// 👇 GitHub OAuth - -type GitHubOauthToken = { - access_token: string; -}; - -interface GitHubUser { - login: string; - id: number; - node_id: string; - avatar_url: string; - gravatar_id: string; - url: string; - html_url: string; - followers_url: string; - following_url: string; - gists_url: string; - starred_url: string; - subscriptions_url: string; - organizations_url: string; - repos_url: string; - events_url: string; - received_events_url: string; - type: string; - site_admin: boolean; - name: string; - company: string; - blog: string; - location: null; - email: string; - hireable: boolean; - bio: string; - twitter_username: string; - public_repos: number; - public_gists: number; - followers: number; - following: number; - created_at: Date; - updated_at: Date; -} - -export const getGithubOathToken = async ({ - code, -}: { - code: string; -}): Promise => { - const rootUrl = "https://github.com/login/oauth/access_token"; - const options = { - client_id: GITHUB_OAUTH_CLIENT_ID, - client_secret: GITHUB_OAUTH_CLIENT_SECRET, - code, - }; - - const queryString = qs.stringify(options); - - try { - const { data } = await axios.post(`${rootUrl}?${queryString}`, { - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - }); - - const decoded = qs.parse(data) as GitHubOauthToken; - - return decoded; - } catch (err: any) { - throw Error(err); - } -}; - -export const getGithubUser = async ({ - access_token, -}: { - access_token: string; -}): Promise => { - try { - const { data } = await axios.get( - "https://api.github.com/user", - { - headers: { - Authorization: `Bearer ${access_token}`, - }, - } - ); - - return data; - } catch (err: any) { - throw Error(err); - } -}; diff --git a/yarn.lock b/yarn.lock index cadcb27..445f2b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27,22 +27,22 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@prisma/client@^4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.8.1.tgz#51c16488dfac4e74a275a2753bf20262a65f2a2b" - integrity sha512-d4xhZhETmeXK/yZ7K0KcVOzEfI5YKGGEr4F5SBV04/MU4ncN/HcE28sy3e4Yt8UFW0ZuImKFQJE+9rWt9WbGSQ== +"@prisma/client@^4.9.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.9.0.tgz#4a4068f3540732ea5723c008d49ed684d20f9340" + integrity sha512-bz6QARw54sWcbyR1lLnF2QHvRW5R/Jxnbbmwh3u+969vUKXtBkXgSgjDA85nji31ZBlf7+FrHDy5x+5ydGyQDg== dependencies: - "@prisma/engines-version" "4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe" + "@prisma/engines-version" "4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5" -"@prisma/engines-version@4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe": - version "4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe.tgz#30401aba1029e7d32e3cb717e705a7c92ccc211e" - integrity sha512-MHSOSexomRMom8QN4t7bu87wPPD+pa+hW9+71JnVcF3DqyyO/ycCLhRL1we3EojRpZxKvuyGho2REQsMCvxcJw== +"@prisma/engines-version@4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5": + version "4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5.tgz#9d817a5779fc05b107eb02f63d197ad296d60b3c" + integrity sha512-M16aibbxi/FhW7z1sJCX8u+0DriyQYY5AyeTH7plQm9MLnURoiyn3CZBqAyIoQ+Z1pS77usCIibYJWSgleBMBA== -"@prisma/engines@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.8.1.tgz#8428f7dcd7912c6073024511376595017630dc85" - integrity sha512-93tctjNXcIS+i/e552IO6tqw17sX8liivv8WX9lDMCpEEe3ci+nT9F+1oHtAafqruXLepKF80i/D20Mm+ESlOw== +"@prisma/engines@4.9.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.9.0.tgz#05a1411964e047c1bc43f777c7a1c69f86a2a26c" + integrity sha512-t1pt0Gsp+HcgPJrHFc+d/ZSAaKKWar2G/iakrE07yeKPNavDP3iVKPpfXP22OTCHZUWf7OelwKJxQgKAm5hkgw== "@tsconfig/node10@^1.0.7": version "1.0.9" @@ -816,12 +816,12 @@ picomatch@^2.0.4, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -prisma@^4.8.1: - version "4.8.1" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.8.1.tgz#ef93cd908809b7d02e9f4bead5eea7733ba50c68" - integrity sha512-ZMLnSjwulIeYfaU1O6/LF6PEJzxN5par5weykxMykS9Z6ara/j76JH3Yo2AH3bgJbPN4Z6NeCK9s5fDkzf33cg== +prisma@^4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.9.0.tgz#295954b2a89cd35a0e6bcf66b2b036dbf80c75ee" + integrity sha512-bS96oZ5oDFXYgoF2l7PJ3Mp1wWWfLOo8B/jAfbA2Pn0Wm5Z/owBHzaMQKS3i1CzVBDWWPVnOohmbJmjvkcHS5w== dependencies: - "@prisma/engines" "4.8.1" + "@prisma/engines" "4.9.0" proxy-addr@~2.0.7: version "2.0.7" From 66dffde6f4dd37101a34a84d8a61ee67fef7a5f4 Mon Sep 17 00:00:00 2001 From: CODEVO Date: Tue, 24 Jan 2023 12:08:18 +0000 Subject: [PATCH 2/5] updated --- src/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.ts b/src/app.ts index 0883063..4f5aef9 100644 --- a/src/app.ts +++ b/src/app.ts @@ -31,7 +31,7 @@ app.use("/api/sessions", sessionRouter); app.get("/api/healthChecker", (req: Request, res: Response) => { res.status(200).json({ status: "success", - message: "Implement OAuth in Node.js", + message: "Implement Google OAuth2 in Node.js", }); }); From 12c69c004ced00eef26264443013509a63e3cb16 Mon Sep 17 00:00:00 2001 From: CODEVO Date: Tue, 24 Jan 2023 13:34:25 +0000 Subject: [PATCH 3/5] Create README.md --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4f336d --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# How to Implement Google OAuth2 in Node.js + +In this article, I'll walk you through the process of setting up Google OAuth2 in a Node.js application, including creating the OAuth project on the Google API console, configuring the OAuth Client ID and secret, and implementing the necessary code in the Node.js project. + +![How to Implement Google OAuth2 in Node.js](https://codevoweb.com/wp-content/uploads/2023/01/How-to-Implement-Google-OAuth2-in-Node.js.webp) + +## Topics Covered + +- Run the Node.js Google OAuth2 Project +- Run the Node.js API with a React.js App +- Setup the Node.js Project +- Get the Google OAuth2 Credentials +- Setup the Database with Prisma +- Create the Validation Schemas +- Get the Google OAuth Access Token and User's Info + - Get the OAuth Access Token + - Get the Google Account User +- Implement the Google OAuth2 in Node.js + - Register User Route Handler + - Login User Route Handler + - Logout User Route Handler + - Authenticate with Google OAuth2 Route Handler +- Create a User Route Handler +- Create the Authentication Guards + - Authentication Middleware + - Require User Middleware +- Create the API Routes +- Setup CORS and Register the API Routers + +Read the entire article here: [https://codevoweb.com/how-to-implement-google-oauth2-in-nodejs/](https://codevoweb.com/how-to-implement-google-oauth2-in-nodejs/) From bb8351ef09bec337629202d0c279b412d7d9faef Mon Sep 17 00:00:00 2001 From: CODEVO Date: Tue, 24 Jan 2023 16:51:51 +0000 Subject: [PATCH 4/5] updated --- prisma/dev.db | Bin 24576 -> 24576 bytes prisma/dev.db-journal | Bin 8720 -> 0 bytes src/controllers/auth.controller.ts | 9 ++------- 3 files changed, 2 insertions(+), 7 deletions(-) delete mode 100644 prisma/dev.db-journal diff --git a/prisma/dev.db b/prisma/dev.db index 9fb1ba239d4e6c1774f383a65681923836155141..bfeb93b14602ae25e537cb26deb1c8b781a4a62f 100644 GIT binary patch delta 305 zcmZoTz}Rqrae_1>%S0JxMwX2UOZ3?o`JEW}oi+;!Sn$_3S~K#5Dw^|(Hd=B@va%+b zm>HNPnn2&4S?C&>8mA_uBpMo6npisLr=*tUD}<*eCFkdrBqoQj;<+gA@&7k z;eKUNsrf-ZA?e5h4&kuG8Bp0>r7X1dAlV51mYpKQoJ m1SU^xQn^F$eEM&^wPOZ1r-1U3r>T;rcOL4}Qr0SIt$0snds761SM diff --git a/prisma/dev.db-journal b/prisma/dev.db-journal deleted file mode 100644 index 670b94679c094af2be30a4ee6d73971605519806..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8720 zcmeI$JqiLb5C!0@BBCIA2kV8&B)gd$#9H!WU>hqD!OB8Bg0+>mvA6XkE}p?c@I9uT zf%lc86!PZ+gINzy&G)Jw1OgC%00bZa0SG_<0uX=z1Rwx`uL_*T)pT{5DWlYAl1qv# zIn+^Gs;xA!mR6U#I48Me3EbGm#^l-**k-mAL|X4s(VC1;K9tPfc?y9VBi;ymL)nK7 zk3+2}M7-{{-L5*n4$I;FIDhojK7RnU_-aWUg8&2|009U<00Izz00bZa O0SG|g#{_QI=j9DDHz@A_ diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 2c7eb4c..bad9eea 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -66,15 +66,10 @@ export const loginHandler = async ( }); } - if (user.provider === "Google") { + if (user.provider === "Google" || user.provider === "GitHub") { return res.status(401).json({ status: "fail", - message: "Use Google OAuth2 instead", - }); - } else if (user.provider === "GitHub") { - return res.status(401).json({ - status: "fail", - message: "Use GitHub OAuth instead", + message: `Use ${user.provider} OAuth2 instead`, }); } From 453026d92d3d9b169725435db3b124a15ddb55cc Mon Sep 17 00:00:00 2001 From: CODEVO Date: Tue, 24 Jan 2023 16:52:05 +0000 Subject: [PATCH 5/5] updated --- src/controllers/auth.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index bad9eea..dd60143 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -66,7 +66,7 @@ export const loginHandler = async ( }); } - if (user.provider === "Google" || user.provider === "GitHub") { + if (user.provider === "Google") { return res.status(401).json({ status: "fail", message: `Use ${user.provider} OAuth2 instead`,