Skip to content

Commit

Permalink
Merge pull request #138 from YukiOnishi1129/feature/graphql-auth
Browse files Browse the repository at this point in the history
feat(web, bff): graphql authentication
  • Loading branch information
YukiOnishi1129 authored Sep 4, 2024
2 parents 580cf23 + 31de4ab commit bba8ffd
Show file tree
Hide file tree
Showing 16 changed files with 221 additions and 19 deletions.
84 changes: 82 additions & 2 deletions bff/apollo-gateway/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions bff/apollo-gateway/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 8 additions & 2 deletions bff/apollo-gateway/src/app/article/article.resolver.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -13,10 +16,13 @@ export class ArticleResolver {
// }

@Query(() => ArticleConnection)
@UseGuards(SupabaseAuthGuard)
async articles(
@Args('articlesInput') articlesInput: ArticlesInput,
@Context() context: GraphQLContext,
): Promise<ArticleConnection> {
return await this.articleService.getArticles(articlesInput);
const user = context.req.user;
return await this.articleService.getArticles(user.id, articlesInput);
}

// @Query('article')
Expand Down
8 changes: 6 additions & 2 deletions bff/apollo-gateway/src/app/article/article.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ export class ArticleService implements OnModuleInit {
// return `This action adds a new article ${createArticleInput}`;
// }

async getArticles(input: ArticlesInput): Promise<ArticleConnection> {
async getArticles(
userId: string,
input: ArticlesInput,
): Promise<ArticleConnection> {
const req = new GetArticlesRequest();
if (input?.first) req.setLimit(input.first);
if (input?.after) req.setCursor(input.after);
Expand All @@ -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) => {
Expand Down
40 changes: 40 additions & 0 deletions bff/apollo-gateway/src/app/auth/auth.guard.ts
Original file line number Diff line number Diff line change
@@ -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<boolean> {
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;
}
}
6 changes: 6 additions & 0 deletions bff/apollo-gateway/src/graphql/context.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { User } from '@supabase/supabase-js';
import { Request } from 'express';

export interface GraphQLContext {
req: Request & { user?: User };
}
6 changes: 5 additions & 1 deletion bff/apollo-gateway/src/main.ts
Original file line number Diff line number Diff line change
@@ -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();
3 changes: 3 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 12 additions & 2 deletions web/client-v2/src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
export default function Home() {
return <div>Dashboard</div>;
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 <ArticleDashboardTemplate />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type LoggedBaseLayoutProps = {
children: ReactNode;
};

export const LoggedBaseLayout: FC<LoggedBaseLayoutProps> = async ({
export const LoggedBaseLayout: FC<LoggedBaseLayoutProps> = ({
user,
children,
}) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type DesktopHeaderProps = {
user?: User;
};

export const DesktopHeader: FC<DesktopHeaderProps> = async ({
export const DesktopHeader: FC<DesktopHeaderProps> = ({
user,
}: DesktopHeaderProps) => {
return (
Expand Down
2 changes: 1 addition & 1 deletion web/client-v2/src/components/layout/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type HeaderProps = {
user?: User;
};

export const Header: FC<HeaderProps> = async ({ user }) => {
export const Header: FC<HeaderProps> = ({ user }) => {
// const myFeedFolderRes = await fetchMyFeedFoldersAPI({});
// const favoriteArticleFolderRes = await fetchFavoriteArticleFoldersAPI({});
return (
Expand Down
18 changes: 17 additions & 1 deletion web/client-v2/src/components/provider/ApolloProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
"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({
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: token ? `Bearer ${token}` : "",
},
};
});

return new ApolloClient({
cache: new InMemoryCache(),
link: httpLink,
link: authLink.concat(httpLink),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,7 @@ export const ArticleDashboardTemplate: FC<
if (error) {
return <div>{error.message}</div>;
}
console.log(data);

return <div>Article Dashboard</div>;
};
12 changes: 11 additions & 1 deletion web/client-v2/src/features/auth/actions/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
Loading

0 comments on commit bba8ffd

Please sign in to comment.