diff --git a/package.json b/package.json index 0ebf79725..352c64f61 100644 --- a/package.json +++ b/package.json @@ -157,6 +157,7 @@ "@types/validator": "13.1.4", "antd": "4.16.5", "apollo-link-timeout": "4.0.0", + "apollo-server-errors": "^3.3.1", "apollo-server-koa": "2.25.2", "axios": "0.21.1", "babel-loader": "8.2.2", diff --git a/src/api-gateway/api-gateway.graphql b/src/api-gateway/api-gateway.graphql index d6b75a4a0..6041bebcb 100644 --- a/src/api-gateway/api-gateway.graphql +++ b/src/api-gateway/api-gateway.graphql @@ -12,4 +12,13 @@ type Query { # is the server healthy? health: String + + # get the user + userProfile(userId: String): UserProfileResponse +} + +type UserProfileResponse { + email: String + id: String + locale: String } diff --git a/src/api-gateway/api-gateway.ts b/src/api-gateway/api-gateway.ts index be3dbb221..d066a2ac5 100644 --- a/src/api-gateway/api-gateway.ts +++ b/src/api-gateway/api-gateway.ts @@ -5,10 +5,17 @@ import { buildSchema } from "type-graphql"; import { AddNumResolver } from "@/api-gateway/resolvers/add-num-resolver"; import { MyServer } from "@/server/start-server"; import { NonEmptyArray } from "type-graphql/dist/interfaces/NonEmptyArray"; +import { IContext } from "@/api-gateway/context"; +import { UserResolver } from "@/api-gateway/resolvers/user-resolver"; +import { customAuthChecker } from "@/api-gateway/auth-checker"; import { MetaResolver } from "./resolvers/meta-resolver"; export async function setApiGateway(server: MyServer): Promise { - const resolvers: NonEmptyArray = [MetaResolver, AddNumResolver]; + const resolvers: NonEmptyArray = [ + MetaResolver, + AddNumResolver, + UserResolver, + ]; server.resolvers = resolvers; const sdlPath = path.resolve(__dirname, "api-gateway.graphql"); @@ -18,6 +25,7 @@ export async function setApiGateway(server: MyServer): Promise { path: sdlPath, commentDescriptions: true, }, + authChecker: customAuthChecker, validate: false, nullableByDefault: true, }); @@ -30,7 +38,20 @@ export async function setApiGateway(server: MyServer): Promise { "request.credentials": "include", }, }, - context: async () => ({}), + + context: async ({ ctx }): Promise => { + const token = server.auth.tokenFromCtx(ctx); + const userId = await server.auth.jwt.verify(token); + + return { + userId, + session: ctx.session, + model: server.model, + gateways: server.gateways, + auth: server.auth, + reqHeaders: ctx.headers, + }; + }, }); const gPath = `${server.config.server.routePrefix || ""}/api-gateway/`; apollo.applyMiddleware({ app: server.app, path: gPath }); diff --git a/src/api-gateway/auth-checker.ts b/src/api-gateway/auth-checker.ts new file mode 100644 index 000000000..3f62cf152 --- /dev/null +++ b/src/api-gateway/auth-checker.ts @@ -0,0 +1,15 @@ +import { AuthenticationError } from "apollo-server-errors"; +import { AuthChecker } from "type-graphql"; +import { IContext } from "./context"; + +export const customAuthChecker: AuthChecker = ({ + context, +}: { + context: IContext; +}) => { + const { userId } = context; + if (!userId) { + throw new AuthenticationError("Access denied! Please login to continue!"); + } + return true; // or false if access is denied +}; diff --git a/src/api-gateway/context.ts b/src/api-gateway/context.ts new file mode 100644 index 000000000..fa0819f20 --- /dev/null +++ b/src/api-gateway/context.ts @@ -0,0 +1,13 @@ +/* tslint:disable:no-any */ +import { OnefxAuth } from "onefx-auth"; +import { Model } from "@/model/model"; +import { Gateways } from "@/server/gateway/gateway"; + +export interface IContext { + userId: string; + session: any; + model: Model; + gateways: Gateways; + auth: OnefxAuth; + reqHeaders: Record; +} diff --git a/src/api-gateway/resolvers/user-resolver.ts b/src/api-gateway/resolvers/user-resolver.ts new file mode 100644 index 000000000..50a99c7f7 --- /dev/null +++ b/src/api-gateway/resolvers/user-resolver.ts @@ -0,0 +1,57 @@ +import { + Args, + ArgsType, + Authorized, + Ctx, + Field, + ObjectType, + Query, +} from "type-graphql"; +import { AuthenticationError } from "apollo-server-errors"; +import { IContext } from "@/api-gateway/context"; + +@ArgsType() +class UserProfileRequest { + @Field((_) => String, { nullable: true }) + userId?: string; +} + +@ObjectType() +class UserProfileResponse { + @Field((_) => String) + id: string; + + @Field((_) => String) + email: string; + + @Field((_) => String) + locale: string; +} + +export class UserResolver { + @Authorized() + @Query((_) => UserProfileResponse, { + description: "get the user", + nullable: true, + }) + public async userProfile( + @Args() + args: UserProfileRequest, + @Ctx() + ctx: IContext + ): Promise { + const userId = args.userId ?? ctx.userId; + if (String(userId) !== String(ctx.userId)) { + throw new AuthenticationError("not authorized user"); + } + const user = await ctx.auth.user.getById(userId); + if (!user) { + return null; + } + return { + id: user.id, + email: user.email, + locale: user.locale, + }; + } +} diff --git a/src/model/model.ts b/src/model/model.ts index 368226597..bcc315cf1 100644 --- a/src/model/model.ts +++ b/src/model/model.ts @@ -1,5 +1,7 @@ import { MyServer } from "@/server/start-server"; +export type Model = {}; + export function setModel(server: MyServer): void { server.model = server.model || {}; } diff --git a/src/server/start-server.ts b/src/server/start-server.ts index f51527c59..761fbc43b 100644 --- a/src/server/start-server.ts +++ b/src/server/start-server.ts @@ -1,6 +1,6 @@ import config from "config"; import { Config, Server } from "onefx/lib/server"; -import { setModel } from "@/model"; +import { Model, setModel } from "@/model"; import { OnefxAuth, authConfig } from "onefx-auth"; import { Gateways, setGateways } from "./gateway/gateway"; import { setMiddleware } from "./middleware"; @@ -18,13 +18,12 @@ export type MyServer = Server & { }; // eslint-disable-next-line @typescript-eslint/no-explicit-any resolvers: any; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - model: any; + model: Model; }; export async function startServer(): Promise { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const server: MyServer = new Server((config as any) as Config) as MyServer; + const server: MyServer = new Server(config as any as Config) as MyServer; server.app.proxy = Boolean(config.get("server.proxy")); setGateways(server); server.auth = new OnefxAuth(server.gateways.mongoose, authConfig); diff --git a/yarn.lock b/yarn.lock index e47731400..cff787bf6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5176,6 +5176,11 @@ apollo-server-errors@^2.0.2, apollo-server-errors@^2.5.0: resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.5.0.tgz#5d1024117c7496a2979e3e34908b5685fe112b68" integrity sha512-lO5oTjgiC3vlVg2RKr3RiXIIQ5pGXBFxYGGUkKDhTud3jMIhs+gel8L8zsEjKaKxkjHhCQAA/bcEfYiKkGQIvA== +apollo-server-errors@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-3.3.1.tgz#ba5c00cdaa33d4cbd09779f8cb6f47475d1cd655" + integrity sha512-xnZJ5QWs6FixHICXHxUfm+ZWqqxrNuPlQ+kj5m6RtEgIpekOPssH/SD9gf2B4HuWV0QozorrygwZnux8POvyPA== + apollo-server-koa@2.25.2: version "2.25.2" resolved "https://registry.yarnpkg.com/apollo-server-koa/-/apollo-server-koa-2.25.2.tgz#d4ba4958683e2268217aedb9eca480ec66b74568"