From d13391b542c51ed4f0f03c2961398166257160ca Mon Sep 17 00:00:00 2001
From: YukiOnishi <58220747+YukiOnishi1129@users.noreply.github.com>
Date: Wed, 4 Sep 2024 13:06:55 +0900
Subject: [PATCH 1/2] set access token
---
web/client-v2/src/app/dashboard/page.tsx | 14 +++++++--
.../layout/BaseLayout/LoggedBaseLayout.tsx | 2 +-
.../Header/DesktopHeader/DesktopHeader.tsx | 2 +-
.../src/components/layout/Header/Header.tsx | 2 +-
.../components/provider/ApolloProvider.tsx | 18 +++++++++++-
.../Template/ArticleDashboardTemplate.tsx | 2 ++
.../src/features/auth/actions/auth.ts | 12 +++++++-
web/client-v2/src/lib/apollo/client.ts | 29 +++++++++++++++----
8 files changed, 69 insertions(+), 12 deletions(-)
diff --git a/web/client-v2/src/app/dashboard/page.tsx b/web/client-v2/src/app/dashboard/page.tsx
index 9911a355..df4e84d3 100644
--- a/web/client-v2/src/app/dashboard/page.tsx
+++ b/web/client-v2/src/app/dashboard/page.tsx
@@ -1,3 +1,13 @@
-export default function Home() {
- return
Dashboard
;
+import { redirect } from "next/navigation";
+
+import { ArticleDashboardTemplate } from "@/features/articles/components/Template";
+import { getUser } from "@/features/auth/actions/user";
+
+export default async function Dashboard() {
+ const user = await getUser();
+ if (!user) {
+ redirect("/login");
+ }
+
+ return ;
}
diff --git a/web/client-v2/src/components/layout/BaseLayout/LoggedBaseLayout.tsx b/web/client-v2/src/components/layout/BaseLayout/LoggedBaseLayout.tsx
index 4fd126cb..53935c50 100644
--- a/web/client-v2/src/components/layout/BaseLayout/LoggedBaseLayout.tsx
+++ b/web/client-v2/src/components/layout/BaseLayout/LoggedBaseLayout.tsx
@@ -14,7 +14,7 @@ type LoggedBaseLayoutProps = {
children: ReactNode;
};
-export const LoggedBaseLayout: FC = async ({
+export const LoggedBaseLayout: FC = ({
user,
children,
}) => {
diff --git a/web/client-v2/src/components/layout/Header/DesktopHeader/DesktopHeader.tsx b/web/client-v2/src/components/layout/Header/DesktopHeader/DesktopHeader.tsx
index 28803eff..2d25ba27 100644
--- a/web/client-v2/src/components/layout/Header/DesktopHeader/DesktopHeader.tsx
+++ b/web/client-v2/src/components/layout/Header/DesktopHeader/DesktopHeader.tsx
@@ -10,7 +10,7 @@ type DesktopHeaderProps = {
user?: User;
};
-export const DesktopHeader: FC = async ({
+export const DesktopHeader: FC = ({
user,
}: DesktopHeaderProps) => {
return (
diff --git a/web/client-v2/src/components/layout/Header/Header.tsx b/web/client-v2/src/components/layout/Header/Header.tsx
index 7f9f40b9..4a914329 100644
--- a/web/client-v2/src/components/layout/Header/Header.tsx
+++ b/web/client-v2/src/components/layout/Header/Header.tsx
@@ -7,7 +7,7 @@ type HeaderProps = {
user?: User;
};
-export const Header: FC = async ({ user }) => {
+export const Header: FC = ({ user }) => {
// const myFeedFolderRes = await fetchMyFeedFoldersAPI({});
// const favoriteArticleFolderRes = await fetchFavoriteArticleFoldersAPI({});
return (
diff --git a/web/client-v2/src/components/provider/ApolloProvider.tsx b/web/client-v2/src/components/provider/ApolloProvider.tsx
index 2ed019f6..eca11d66 100644
--- a/web/client-v2/src/components/provider/ApolloProvider.tsx
+++ b/web/client-v2/src/components/provider/ApolloProvider.tsx
@@ -1,12 +1,15 @@
"use client";
import { HttpLink } from "@apollo/client";
+import { setContext } from "@apollo/client/link/context";
import {
ApolloNextAppProvider,
ApolloClient,
InMemoryCache,
} from "@apollo/experimental-nextjs-app-support";
+import { getSession } from "@/features/auth/actions/auth";
+
function makeClient() {
const graphqlUrl = process.env.BFF_API_URL || "http://localhost:3000";
const httpLink = new HttpLink({
@@ -14,9 +17,22 @@ function makeClient() {
fetchOptions: { cache: "no-store" },
});
+ const authLink = setContext(async (_, { headers }) => {
+ const { data: session } = await getSession();
+
+ const token = session.session?.access_token;
+
+ return {
+ headers: {
+ ...headers,
+ authorization: token ? `Bearer ${token}` : "",
+ },
+ };
+ });
+
return new ApolloClient({
cache: new InMemoryCache(),
- link: httpLink,
+ link: authLink.concat(httpLink),
});
}
diff --git a/web/client-v2/src/features/articles/components/Template/ArticleDashboardTemplate.tsx b/web/client-v2/src/features/articles/components/Template/ArticleDashboardTemplate.tsx
index f04efa1b..2ada0fc7 100644
--- a/web/client-v2/src/features/articles/components/Template/ArticleDashboardTemplate.tsx
+++ b/web/client-v2/src/features/articles/components/Template/ArticleDashboardTemplate.tsx
@@ -50,5 +50,7 @@ export const ArticleDashboardTemplate: FC<
if (error) {
return {error.message}
;
}
+ console.log(data);
+
return Article Dashboard
;
};
diff --git a/web/client-v2/src/features/auth/actions/auth.ts b/web/client-v2/src/features/auth/actions/auth.ts
index 7e44f664..2a585ff1 100644
--- a/web/client-v2/src/features/auth/actions/auth.ts
+++ b/web/client-v2/src/features/auth/actions/auth.ts
@@ -2,7 +2,10 @@
import { redirect } from "next/navigation";
-import { createServerSideClient } from "@/lib/supabase/client/serverClient";
+import {
+ createGetOnlyServerSideClient,
+ createServerSideClient,
+} from "@/lib/supabase/client/serverClient";
export async function loginWithGoogle() {
const supabase = await createServerSideClient();
@@ -38,3 +41,10 @@ export async function logoutToLoginPage() {
}
if (!error) redirect("/login");
}
+
+export async function getSession() {
+ const supabase = await createGetOnlyServerSideClient();
+ const session = supabase.auth.getSession();
+
+ return session;
+}
diff --git a/web/client-v2/src/lib/apollo/client.ts b/web/client-v2/src/lib/apollo/client.ts
index b207f5e3..0447936e 100644
--- a/web/client-v2/src/lib/apollo/client.ts
+++ b/web/client-v2/src/lib/apollo/client.ts
@@ -1,17 +1,36 @@
import { HttpLink } from "@apollo/client";
+import { setContext } from "@apollo/client/link/context";
import {
registerApolloClient,
ApolloClient,
InMemoryCache,
} from "@apollo/experimental-nextjs-app-support";
+import { getSession } from "@/features/auth/actions/auth";
+
+const graphqlUrl = process.env.BFF_API_URL || "http://localhost:3000";
+
+const httpLink = new HttpLink({
+ uri: `${graphqlUrl}/graphql`,
+ fetchOptions: { cache: "no-store" },
+});
+
+const authLink = setContext(async (_, { headers }) => {
+ const { data: session } = await getSession();
+
+ const token = session.session?.access_token;
+
+ return {
+ headers: {
+ ...headers,
+ authorization: `Bearer ${token}`,
+ },
+ };
+});
+
export const { getClient } = registerApolloClient(() => {
- const graphqlUrl = process.env.BFF_API_URL || "http://localhost:3000";
return new ApolloClient({
cache: new InMemoryCache(),
- link: new HttpLink({
- uri: `${graphqlUrl}/graphql`,
- fetchOptions: { cache: "no-store" },
- }),
+ link: authLink.concat(httpLink),
});
});
From 31de4ab41e4a45cdcdafc29e716c096bb3daee0d Mon Sep 17 00:00:00 2001
From: YukiOnishi <58220747+YukiOnishi1129@users.noreply.github.com>
Date: Wed, 4 Sep 2024 14:52:25 +0900
Subject: [PATCH 2/2] set bff auth guard
---
bff/apollo-gateway/package-lock.json | 84 ++++++++++++++++++-
bff/apollo-gateway/package.json | 2 +
.../src/app/article/article.resolver.ts | 10 ++-
.../src/app/article/article.service.ts | 8 +-
bff/apollo-gateway/src/app/auth/auth.guard.ts | 40 +++++++++
.../src/graphql/context.interface.ts | 6 ++
bff/apollo-gateway/src/main.ts | 6 +-
compose.yml | 3 +
8 files changed, 152 insertions(+), 7 deletions(-)
create mode 100644 bff/apollo-gateway/src/app/auth/auth.guard.ts
create mode 100644 bff/apollo-gateway/src/graphql/context.interface.ts
diff --git a/bff/apollo-gateway/package-lock.json b/bff/apollo-gateway/package-lock.json
index 83e483be..8e49867f 100644
--- a/bff/apollo-gateway/package-lock.json
+++ b/bff/apollo-gateway/package-lock.json
@@ -19,7 +19,9 @@
"@nestjs/mapped-types": "*",
"@nestjs/microservices": "^10.4.1",
"@nestjs/platform-express": "^10.0.0",
+ "@supabase/supabase-js": "^2.45.3",
"apollo-server-express": "^3.13.0",
+ "dotenv": "^16.4.5",
"google-protobuf": "^3.21.4",
"graphql": "^16.9.0",
"graphql-type-json": "^0.3.2",
@@ -5538,6 +5540,80 @@
"@sinonjs/commons": "^3.0.0"
}
},
+ "node_modules/@supabase/auth-js": {
+ "version": "2.65.0",
+ "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.65.0.tgz",
+ "integrity": "sha512-+wboHfZufAE2Y612OsKeVP4rVOeGZzzMLD/Ac3HrTQkkY4qXNjI6Af9gtmxwccE5nFvTiF114FEbIQ1hRq5uUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.14"
+ }
+ },
+ "node_modules/@supabase/functions-js": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.1.tgz",
+ "integrity": "sha512-8sZ2ibwHlf+WkHDUZJUXqqmPvWQ3UHN0W30behOJngVh/qHHekhJLCFbh0AjkE9/FqqXtf9eoVvmYgfCLk5tNA==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.14"
+ }
+ },
+ "node_modules/@supabase/node-fetch": {
+ "version": "2.6.15",
+ "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz",
+ "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ }
+ },
+ "node_modules/@supabase/postgrest-js": {
+ "version": "1.15.8",
+ "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.15.8.tgz",
+ "integrity": "sha512-YunjXpoQjQ0a0/7vGAvGZA2dlMABXFdVI/8TuVKtlePxyT71sl6ERl6ay1fmIeZcqxiuFQuZw/LXUuStUG9bbg==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.14"
+ }
+ },
+ "node_modules/@supabase/realtime-js": {
+ "version": "2.10.2",
+ "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.10.2.tgz",
+ "integrity": "sha512-qyCQaNg90HmJstsvr2aJNxK2zgoKh9ZZA8oqb7UT2LCh3mj9zpa3Iwu167AuyNxsxrUE8eEJ2yH6wLCij4EApA==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.14",
+ "@types/phoenix": "^1.5.4",
+ "@types/ws": "^8.5.10",
+ "ws": "^8.14.2"
+ }
+ },
+ "node_modules/@supabase/storage-js": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.0.tgz",
+ "integrity": "sha512-iZenEdO6Mx9iTR6T7wC7sk6KKsoDPLq8rdu5VRy7+JiT1i8fnqfcOr6mfF2Eaqky9VQzhP8zZKQYjzozB65Rig==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.14"
+ }
+ },
+ "node_modules/@supabase/supabase-js": {
+ "version": "2.45.3",
+ "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.45.3.tgz",
+ "integrity": "sha512-4wAux6cuVMrdH/qUjKn6p3p3L9AtAO3Une6ojIrtpCj1RaXKVoyIATiacSRAI+pKff6XZBVCGC29v+z4Jo/uSw==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/auth-js": "2.65.0",
+ "@supabase/functions-js": "2.4.1",
+ "@supabase/node-fetch": "2.6.15",
+ "@supabase/postgrest-js": "1.15.8",
+ "@supabase/realtime-js": "2.10.2",
+ "@supabase/storage-js": "2.7.0"
+ }
+ },
"node_modules/@ts-morph/common": {
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.22.0.tgz",
@@ -5828,6 +5904,12 @@
"form-data": "^4.0.0"
}
},
+ "node_modules/@types/phoenix": {
+ "version": "1.6.5",
+ "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz",
+ "integrity": "sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==",
+ "license": "MIT"
+ },
"node_modules/@types/qs": {
"version": "6.9.15",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
@@ -5895,7 +5977,6 @@
"version": "8.5.12",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz",
"integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
@@ -8732,7 +8813,6 @@
"version": "16.4.5",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
- "dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
diff --git a/bff/apollo-gateway/package.json b/bff/apollo-gateway/package.json
index b6304c4e..04c241ac 100644
--- a/bff/apollo-gateway/package.json
+++ b/bff/apollo-gateway/package.json
@@ -33,7 +33,9 @@
"@nestjs/mapped-types": "*",
"@nestjs/microservices": "^10.4.1",
"@nestjs/platform-express": "^10.0.0",
+ "@supabase/supabase-js": "^2.45.3",
"apollo-server-express": "^3.13.0",
+ "dotenv": "^16.4.5",
"google-protobuf": "^3.21.4",
"graphql": "^16.9.0",
"graphql-type-json": "^0.3.2",
diff --git a/bff/apollo-gateway/src/app/article/article.resolver.ts b/bff/apollo-gateway/src/app/article/article.resolver.ts
index 2ad067c9..c9c74687 100644
--- a/bff/apollo-gateway/src/app/article/article.resolver.ts
+++ b/bff/apollo-gateway/src/app/article/article.resolver.ts
@@ -1,7 +1,10 @@
-import { Resolver, Args, Query } from '@nestjs/graphql';
+import { UseGuards } from '@nestjs/common';
+import { Resolver, Args, Query, Context } from '@nestjs/graphql';
import { ArticleService } from './article.service';
+import { GraphQLContext } from '../../graphql/context.interface';
import { ArticleConnection, ArticlesInput } from '../../graphql/types/graphql';
+import { SupabaseAuthGuard } from '../auth/auth.guard';
@Resolver()
export class ArticleResolver {
@@ -13,10 +16,13 @@ export class ArticleResolver {
// }
@Query(() => ArticleConnection)
+ @UseGuards(SupabaseAuthGuard)
async articles(
@Args('articlesInput') articlesInput: ArticlesInput,
+ @Context() context: GraphQLContext,
): Promise {
- return await this.articleService.getArticles(articlesInput);
+ const user = context.req.user;
+ return await this.articleService.getArticles(user.id, articlesInput);
}
// @Query('article')
diff --git a/bff/apollo-gateway/src/app/article/article.service.ts b/bff/apollo-gateway/src/app/article/article.service.ts
index 791b99bd..907c93fa 100644
--- a/bff/apollo-gateway/src/app/article/article.service.ts
+++ b/bff/apollo-gateway/src/app/article/article.service.ts
@@ -29,7 +29,10 @@ export class ArticleService implements OnModuleInit {
// return `This action adds a new article ${createArticleInput}`;
// }
- async getArticles(input: ArticlesInput): Promise {
+ async getArticles(
+ userId: string,
+ input: ArticlesInput,
+ ): Promise {
const req = new GetArticlesRequest();
if (input?.first) req.setLimit(input.first);
if (input?.after) req.setCursor(input.after);
@@ -44,7 +47,8 @@ export class ArticleService implements OnModuleInit {
if (input?.languageStatus)
req.setLanguageStatus(new Int64Value().setValue(input.languageStatus));
if (input?.tag) req.setTag(new StringValue().setValue(input.tag));
- if (input?.userId) req.setUserId(new StringValue().setValue(input.userId));
+
+ req.setUserId(new StringValue().setValue(userId));
return new Promise((resolve, reject) => {
this.articleService.getArticles(req, (err, res) => {
diff --git a/bff/apollo-gateway/src/app/auth/auth.guard.ts b/bff/apollo-gateway/src/app/auth/auth.guard.ts
new file mode 100644
index 00000000..92bcf698
--- /dev/null
+++ b/bff/apollo-gateway/src/app/auth/auth.guard.ts
@@ -0,0 +1,40 @@
+import {
+ Injectable,
+ CanActivate,
+ ExecutionContext,
+ UnauthorizedException,
+} from '@nestjs/common';
+import { GqlExecutionContext } from '@nestjs/graphql';
+import { createClient } from '@supabase/supabase-js';
+
+@Injectable()
+export class SupabaseAuthGuard implements CanActivate {
+ private supabase = createClient(
+ process.env.SUPABASE_URL,
+ process.env.SUPABASE_ANON_KEY,
+ );
+
+ async canActivate(context: ExecutionContext): Promise {
+ const ctx = GqlExecutionContext.create(context).getContext();
+ const req = ctx.req || context.switchToHttp().getRequest();
+
+ const authHeader = req.headers.authorization;
+ const token = authHeader?.split(' ')[1];
+
+ if (!token) {
+ throw new UnauthorizedException('Authorization token not found');
+ }
+
+ const {
+ data: { user },
+ error,
+ } = await this.supabase.auth.getUser(token);
+
+ if (error || !user) {
+ throw new UnauthorizedException('Invalid or expired token');
+ }
+
+ req.user = user;
+ return true;
+ }
+}
diff --git a/bff/apollo-gateway/src/graphql/context.interface.ts b/bff/apollo-gateway/src/graphql/context.interface.ts
new file mode 100644
index 00000000..3e69ed61
--- /dev/null
+++ b/bff/apollo-gateway/src/graphql/context.interface.ts
@@ -0,0 +1,6 @@
+import { User } from '@supabase/supabase-js';
+import { Request } from 'express';
+
+export interface GraphQLContext {
+ req: Request & { user?: User };
+}
diff --git a/bff/apollo-gateway/src/main.ts b/bff/apollo-gateway/src/main.ts
index bcc101e0..2b267126 100644
--- a/bff/apollo-gateway/src/main.ts
+++ b/bff/apollo-gateway/src/main.ts
@@ -1,9 +1,13 @@
import { NestFactory } from '@nestjs/core';
+import * as dotenv from 'dotenv';
import { AppModule } from './app/app.module';
+dotenv.config();
+
async function bootstrap() {
const app = await NestFactory.create(AppModule);
- await app.listen(3000);
+ const port = Number(process.env.PORT) || 3000;
+ await app.listen(port);
}
bootstrap();
diff --git a/compose.yml b/compose.yml
index a3b66a94..ff97716d 100644
--- a/compose.yml
+++ b/compose.yml
@@ -7,6 +7,9 @@ services:
context: .
dockerfile: ./bff/apollo-gateway/Dockerfile.dev
environment:
+ - BFF_CONTAINER_PORT=${BFF_CONTAINER_PORT}
+ - SUPABASE_URL=${BFF_SUPABASE_URL}
+ - SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}
- TZ=Asia/Tokyo
volumes:
- ./bff/apollo-gateway:/app