From 33d78338add8bd716afb7d21a242b39c379a973d Mon Sep 17 00:00:00 2001 From: Rafael Bradley <84998222+Nekidev@users.noreply.github.com> Date: Fri, 26 Jan 2024 00:00:00 -0300 Subject: [PATCH] update(web): Add support for AniList OAuth2 login --- src/web/.env.example | 4 + src/web/package.json | 9 +- src/web/pnpm-lock.yaml | 47 +++ .../api/auth/oauth2/anilist/callback/route.ts | 46 +++ .../src/app/api/auth/oauth2/anilist/route.ts | 31 ++ src/web/src/app/code/page.js | 66 ++++ src/web/src/app/layout.js | 22 +- src/web/src/app/page.js | 284 +++++++++--------- src/web/tsconfig.json | 34 +++ 9 files changed, 396 insertions(+), 147 deletions(-) create mode 100644 src/web/.env.example create mode 100644 src/web/src/app/api/auth/oauth2/anilist/callback/route.ts create mode 100644 src/web/src/app/api/auth/oauth2/anilist/route.ts create mode 100644 src/web/src/app/code/page.js create mode 100644 src/web/tsconfig.json diff --git a/src/web/.env.example b/src/web/.env.example new file mode 100644 index 0000000..4578028 --- /dev/null +++ b/src/web/.env.example @@ -0,0 +1,4 @@ +# AniList's OAuth2 +PROVIDER_ANILIST_CLIENT_ID= +PROVIDER_ANILIST_CLIENT_SECRET= +PROVIDER_ANILIST_REDIRECT_URI= \ No newline at end of file diff --git a/src/web/package.json b/src/web/package.json index c51d833..e0ce2ec 100644 --- a/src/web/package.json +++ b/src/web/package.json @@ -9,8 +9,13 @@ "lint": "next lint" }, "dependencies": { + "next": "14.1.0", "react": "^18", - "react-dom": "^18", - "next": "14.1.0" + "react-dom": "^18" + }, + "devDependencies": { + "@types/node": "^20.11.6", + "@types/react": "18.2.48", + "typescript": "5.3.3" } } diff --git a/src/web/pnpm-lock.yaml b/src/web/pnpm-lock.yaml index 795678f..b938f57 100644 --- a/src/web/pnpm-lock.yaml +++ b/src/web/pnpm-lock.yaml @@ -15,6 +15,17 @@ dependencies: specifier: ^18 version: 18.2.0(react@18.2.0) +devDependencies: + '@types/node': + specifier: ^20.11.6 + version: 20.11.6 + '@types/react': + specifier: 18.2.48 + version: 18.2.48 + typescript: + specifier: 5.3.3 + version: 5.3.3 + packages: /@next/env@14.1.0: @@ -108,6 +119,28 @@ packages: tslib: 2.6.2 dev: false + /@types/node@20.11.6: + resolution: {integrity: sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q==} + dependencies: + undici-types: 5.26.5 + dev: true + + /@types/prop-types@15.7.11: + resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==} + dev: true + + /@types/react@18.2.48: + resolution: {integrity: sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==} + dependencies: + '@types/prop-types': 15.7.11 + '@types/scheduler': 0.16.8 + csstype: 3.1.3 + dev: true + + /@types/scheduler@0.16.8: + resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} + dev: true + /busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -123,6 +156,10 @@ packages: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} dev: false + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dev: true + /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} dev: false @@ -249,3 +286,13 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} dev: false + + /typescript@5.3.3: + resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true diff --git a/src/web/src/app/api/auth/oauth2/anilist/callback/route.ts b/src/web/src/app/api/auth/oauth2/anilist/callback/route.ts new file mode 100644 index 0000000..ef71230 --- /dev/null +++ b/src/web/src/app/api/auth/oauth2/anilist/callback/route.ts @@ -0,0 +1,46 @@ +import * as crypto from "crypto"; +import { cookies } from "next/headers"; +import { redirect } from "next/navigation"; +import { type NextRequest } from "next/server"; + +export const dynamic = "force-dynamic"; // defaults to auto + +function encrypt(string: string, key: string, iv: string): string { + const cipher = crypto.createCipheriv("aes-256-cbc", key, iv); + let encryptedString = cipher.update(string, "utf-8", "hex"); + encryptedString += cipher.final("hex"); + return encryptedString; +} + +export async function GET(request: NextRequest) { + const res = await fetch("https://anilist.co/api/v2/oauth/token", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ + grant_type: "authorization_code", + client_id: process.env.PROVIDER_ANILIST_CLIENT_ID, + client_secret: process.env.PROVIDER_ANILIST_CLIENT_SECRET, + redirect_uri: process.env.PROVIDER_ANILIST_REDIRECT_URI, + code: request.nextUrl.searchParams.get("code"), + }), + }); + + if (!res.ok) { + redirect(`/code?status=fail`); + } + + const json = await res.json(); + + const cookieStore = cookies(); + + redirect( + `/code?status=success&key=${encrypt( + json.access_token, + cookieStore.get("key")?.value!, + cookieStore.get("iv")?.value! + )}` + ); +} diff --git a/src/web/src/app/api/auth/oauth2/anilist/route.ts b/src/web/src/app/api/auth/oauth2/anilist/route.ts new file mode 100644 index 0000000..bde5426 --- /dev/null +++ b/src/web/src/app/api/auth/oauth2/anilist/route.ts @@ -0,0 +1,31 @@ +import * as crypto from "crypto"; +import { cookies } from "next/headers"; +import { redirect } from "next/navigation"; +import { type NextRequest } from "next/server"; + +export const dynamic = "force-dynamic"; // defaults to auto + +export async function GET(request: NextRequest) { + const key = request.nextUrl.searchParams.get("key"); + const iv = request.nextUrl.searchParams.get("iv"); + + if (!key || !iv) { + redirect("/code?status=fail"); + } + + const cookieStore = cookies(); + cookieStore.set("key", key, { + expires: new Date(new Date().getTime() + 5 * 60 * 1000), + }); + cookieStore.set("iv", iv, { + expires: new Date(new Date().getTime() + 5 * 60 * 1000), + }); + + redirect( + `https://anilist.co/api/v2/oauth/authorize?client_id=${encodeURIComponent( + process.env.PROVIDER_ANILIST_CLIENT_ID + )}&redirect_uri=${encodeURIComponent( + process.env.PROVIDER_ANILIST_REDIRECT_URI + )}&response_type=code` + ); +} diff --git a/src/web/src/app/code/page.js b/src/web/src/app/code/page.js new file mode 100644 index 0000000..7116f6c --- /dev/null +++ b/src/web/src/app/code/page.js @@ -0,0 +1,66 @@ +"use client"; + +import { useSearchParams } from "next/navigation"; + +export default function Code() { + const searchParams = useSearchParams(); + + if (searchParams.get("status") !== "success") { + return ( + <> +
+ For some reason you couldn't be authenticated. You can close + this tab. +
++ + If the issue persists, ask for help in our Discord + server. You can find the link here. + +
+ > + ); + } + return ( + <> +Go back to Naoka and paste the following code:
++ You can close this tab after pasting the code in Naoka. +
+ > + ); +} diff --git a/src/web/src/app/layout.js b/src/web/src/app/layout.js index 6eed206..5d40ced 100644 --- a/src/web/src/app/layout.js +++ b/src/web/src/app/layout.js @@ -9,13 +9,33 @@ export default function RootLayout({ children }) { justifyContent: "center", }} > +