From 1551580b671cd5e2c1a392ad1e19a3d0c87dd1bc Mon Sep 17 00:00:00 2001 From: Robin Wieruch Date: Thu, 16 Apr 2020 15:04:03 +0200 Subject: [PATCH] coupon resolver authorization --- src/api/authorization/index.ts | 4 ++-- src/api/middleware/resolver/isAdmin.ts | 20 +++++++++++++++++++ .../middleware/resolver/isAuthenticated.ts | 2 +- src/api/resolvers/coupon/index.ts | 17 ++++++++-------- src/api/resolvers/session/index.ts | 10 ++++++---- src/api/resolvers/user/index.ts | 18 ++++++++++------- src/validation/admin.ts | 2 +- 7 files changed, 50 insertions(+), 23 deletions(-) create mode 100644 src/api/middleware/resolver/isAdmin.ts diff --git a/src/api/authorization/index.ts b/src/api/authorization/index.ts index a00b8bac..063b3e42 100644 --- a/src/api/authorization/index.ts +++ b/src/api/authorization/index.ts @@ -7,8 +7,8 @@ import { isFreeCourse } from './isFreeCourse'; export default shield({ Query: { - me: isAuthenticated, - discountedPrice: isAuthenticated, + // me: isAuthenticated, + // discountedPrice: isAuthenticated, // Partner diff --git a/src/api/middleware/resolver/isAdmin.ts b/src/api/middleware/resolver/isAdmin.ts new file mode 100644 index 00000000..87cc8279 --- /dev/null +++ b/src/api/middleware/resolver/isAdmin.ts @@ -0,0 +1,20 @@ +import { MiddlewareFn } from 'type-graphql'; +import { ForbiddenError } from 'apollo-server'; + +import { ResolverContext } from '@typeDefs/resolver'; +import { hasAdminRole } from '@validation/admin'; + +export const isAdmin: MiddlewareFn = async ( + { context }, + next +) => { + if (!context.me) { + throw new ForbiddenError('Not authenticated as user.'); + } + + if (!hasAdminRole(context.me)) { + throw new ForbiddenError('No admin user.'); + } + + return next(); +}; diff --git a/src/api/middleware/resolver/isAuthenticated.ts b/src/api/middleware/resolver/isAuthenticated.ts index 61eb28fb..86026e99 100644 --- a/src/api/middleware/resolver/isAuthenticated.ts +++ b/src/api/middleware/resolver/isAuthenticated.ts @@ -8,7 +8,7 @@ export const isAuthenticated: MiddlewareFn = async ( next ) => { if (!context.me) { - return new ForbiddenError('Not authenticated as user.'); + throw new ForbiddenError('Not authenticated as user.'); } return next(); diff --git a/src/api/resolvers/coupon/index.ts b/src/api/resolvers/coupon/index.ts index b8f38bd1..12528f2c 100644 --- a/src/api/resolvers/coupon/index.ts +++ b/src/api/resolvers/coupon/index.ts @@ -6,6 +6,7 @@ import { Resolver, Query, Mutation, + UseMiddleware, } from 'type-graphql'; import { ResolverContext } from '@typeDefs/resolver'; @@ -13,6 +14,8 @@ import { priceWithDiscount } from '@services/discount'; import storefront from '@data/course-storefront'; import { COURSE } from '@data/course-keys-types'; import { BUNDLE } from '@data/bundle-keys-types'; +import { isAuthenticated } from '@api/middleware/resolver/isAuthenticated'; +import { isAdmin } from '@api/middleware/resolver/isAdmin'; @ObjectType() class Discount { @Field() @@ -25,19 +28,16 @@ class Discount { @Resolver() export default class CouponResolver { @Query(() => Discount) + @UseMiddleware(isAuthenticated) async discountedPrice( @Arg('courseId') courseId: string, @Arg('bundleId') bundleId: string, @Arg('coupon') coupon: string, @Ctx() ctx: ResolverContext - ) { + ): Promise { const course = storefront[courseId as COURSE]; const bundle = course.bundles[bundleId as BUNDLE]; - if (!ctx.me) { - return bundle.price; - } - const price = await priceWithDiscount( ctx.couponConnector, ctx.courseConnector @@ -46,7 +46,7 @@ export default class CouponResolver { bundleId as BUNDLE, bundle.price, coupon, - ctx.me.uid + ctx.me!.uid ); return { @@ -55,13 +55,14 @@ export default class CouponResolver { }; } - @Mutation(() => Boolean, { nullable: true }) + @Mutation(() => Boolean) + @UseMiddleware(isAuthenticated, isAdmin) async couponCreate( @Arg('coupon') coupon: string, @Arg('discount') discount: number, @Arg('count') count: number, @Ctx() ctx: ResolverContext - ) { + ): Promise { try { await ctx.couponConnector.createCoupons( coupon, diff --git a/src/api/resolvers/session/index.ts b/src/api/resolvers/session/index.ts index 00d112c0..8c727b83 100644 --- a/src/api/resolvers/session/index.ts +++ b/src/api/resolvers/session/index.ts @@ -66,7 +66,7 @@ export default class SessionResolver { @Arg('username') username: string, @Arg('email') email: string, @Arg('password') password: string - ) { + ): Promise { try { await firebaseAdmin.auth().createUser({ email, @@ -112,7 +112,9 @@ export default class SessionResolver { } @Mutation(() => Boolean) - async passwordForgot(@Arg('email') email: string) { + async passwordForgot( + @Arg('email') email: string + ): Promise { try { await firebase.auth().sendPasswordResetEmail(email); } catch (error) { @@ -127,7 +129,7 @@ export default class SessionResolver { async passwordChange( @Arg('password') password: string, @Ctx() ctx: ResolverContext - ) { + ): Promise { try { await firebaseAdmin.auth().updateUser(ctx.me!.uid, { password, @@ -144,7 +146,7 @@ export default class SessionResolver { async emailChange( @Arg('email') email: string, @Ctx() ctx: ResolverContext - ) { + ): Promise { try { await firebaseAdmin.auth().updateUser(ctx.me!.uid, { email, diff --git a/src/api/resolvers/user/index.ts b/src/api/resolvers/user/index.ts index 6794fda9..3f0a4707 100644 --- a/src/api/resolvers/user/index.ts +++ b/src/api/resolvers/user/index.ts @@ -4,17 +4,19 @@ import { Ctx, Resolver, Query, + UseMiddleware, } from 'type-graphql'; import { ResolverContext } from '@typeDefs/resolver'; +import { isAuthenticated } from '@api/middleware/resolver/isAuthenticated'; @ObjectType() class User { @Field() - email: string; + uid: string; @Field() - uid: string; + email: string; @Field() username: string; @@ -26,16 +28,18 @@ class User { @Resolver() export default class UserResolver { @Query(() => User) - async me(@Ctx() ctx: ResolverContext) { - const rolesObject = ctx.me?.customClaims || {}; + @UseMiddleware(isAuthenticated) + async me(@Ctx() ctx: ResolverContext): Promise { + const rolesObject = ctx.me!.customClaims || {}; + const roles = Object.keys(rolesObject).filter( key => rolesObject[key] ); return { - email: ctx.me?.email, - uid: ctx.me?.uid, - username: ctx.me?.displayName, + uid: ctx.me!.uid, + email: ctx.me!.email || '', + username: ctx.me!.displayName || '', roles, }; } diff --git a/src/validation/admin.ts b/src/validation/admin.ts index a3c809cf..cb05e9d5 100644 --- a/src/validation/admin.ts +++ b/src/validation/admin.ts @@ -1,5 +1,5 @@ import * as ROLES from '@constants/roles'; import { User } from '@typeDefs/user'; -export const hasAdminRole = (user: User) => +export const hasAdminRole = (user: User | null | undefined) => user && user.customClaims && user.customClaims[ROLES.ADMIN];