Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BaseService with generic auth #16

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import gitCommitInfo from 'git-commit-info'
import * as Utils from './utils'
import { APP_GUARD } from '@nestjs/core'
import { GqlAuthGuard } from './auth/gql-auth.guard'
import { PoliciesGuard } from './casl/policy.guard'
// import { PoliciesGuard } from './casl/policy.guard'

// import {
// ApolloErrorConverter, // required: core export
Expand Down Expand Up @@ -94,10 +94,10 @@ import { CaslModule } from './casl/casl.module'
provide: APP_GUARD,
useClass: GqlAuthGuard,
},
{
provide: APP_GUARD,
useClass: PoliciesGuard,
},
// {
// provide: APP_GUARD,
// useClass: PoliciesGuard,
// },
// ObjectidScalar,
// Logger,
// JwtStrategy,
Expand Down
3 changes: 0 additions & 3 deletions src/casl/casl-ability.factory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Injectable } from '@nestjs/common'
import {
// ForbiddenError,
InferSubjects,
Expand All @@ -7,7 +6,6 @@ import {
AbilityClass,
ExtractSubjectType,
} from '@casl/ability'
import { plainToClass } from 'class-transformer'

import { User } from '../entities/user/user.model'
import { School } from '../entities/school/school.model'
Expand All @@ -32,7 +30,6 @@ export enum Action {

export type AppAbility = Ability<[Action, Subjects]>

// @Injectable()
export class CaslAbilityFactory {
static createForUser(user: User | undefined) {
const { can, cannot, build } = new AbilityBuilder<
Expand Down
20 changes: 1 addition & 19 deletions src/entities/school/school.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@ import {
ID,
} from '@nestjs/graphql'
import { Types } from 'mongoose'
import { plainToClass } from 'class-transformer'

// Auth
import { ForbiddenError } from '@casl/ability'
import { CheckPolicies } from '../../casl/policy.guard'
import { Action } from '../../casl/casl-ability.factory'
import { CaslAbilityFactory } from '../../casl/casl-ability.factory'
import { CurrentUser } from '../../auth/currentUser'

// School
import { School, SchoolDocument } from './school.model'
Expand All @@ -37,39 +29,29 @@ export class SchoolResolver {
constructor(private service: SchoolService) {}

@Query(() => School)
@CheckPolicies((a) => true)
async school(@Args('_id', { type: () => ID }) _id: Types.ObjectId) {
return this.service.getById(_id)
}

@Query(() => [School])
@CheckPolicies((a) => true)
async schools(
@Args('filters', { nullable: true }) filters?: ListSchoolInput,
) {
const schoolList = await this.service.list(filters)
for (const school of schoolList) {
await this.service.checkPermissons(Action.Read, school._id)
}
return schoolList
return this.service.list(filters)
}

@Mutation(() => School)
@CheckPolicies((a) => true)
async createSchool(@Args('payload') payload: CreateSchoolInput) {
return this.service.create(payload)
}

@Mutation(() => School)
@CheckPolicies((a) => true)
async updateSchool(@Args('payload') payload: UpdateSchoolInput) {
return this.service.update(payload)
}

@Mutation(() => School)
@CheckPolicies((a) => true)
async deleteSchool(@Args('_id', { type: () => ID }) _id: Types.ObjectId) {
//await this.service.checkPermissons(Action.Delete, _id)
return this.service.delete(_id)
}

Expand Down
72 changes: 29 additions & 43 deletions src/entities/school/school.service.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import { Injectable, Inject } from '@nestjs/common'
import { Args, ID } from '@nestjs/graphql'
import { REQUEST } from '@nestjs/core'
import { CONTEXT } from '@nestjs/graphql'
import { InjectModel } from '@nestjs/mongoose'
import { Model, Types } from 'mongoose'
import { plainToClass } from 'class-transformer'

// Service
import { BaseService } from '../../utils/BaseService'

// Auth
import { ForbiddenError } from '@casl/ability'
import { CaslAbilityFactory } from '../../casl/casl-ability.factory'
import { CheckPolicies } from '../../casl/policy.guard'
import { Action } from '../../casl/casl-ability.factory'

// User
import { User } from '../user/user.model'

// School
import { School, SchoolDocument } from './school.model'
import {
Expand All @@ -22,65 +17,56 @@ import {
UpdateSchoolInput,
} from './school.inputs'

const MODEL_CLASS = School
type TModelDocType = SchoolDocument

@Injectable()
export class SchoolService {
constructor(
@InjectModel(School.name)
private model: Model<SchoolDocument>,
@Inject(REQUEST) private request: any,
) {}
export class SchoolService extends BaseService {
modelClass = MODEL_CLASS
dbModel: Model<TModelDocType>
context: any

async checkPermissons(action: Action, _id?: Types.ObjectId) {
// Checks permissons for a single record
const currentUser = this.request.req?.user
const ability = CaslAbilityFactory.createForUser(currentUser)
if (_id) {
const doc = await this.model.findById(_id).exec()
const model = plainToClass(School, doc?.toObject())
ForbiddenError.from(ability).throwUnlessCan(action, model)
return doc
} else {
ForbiddenError.from(ability).throwUnlessCan(action, School)
return true // Necessary?
}
constructor(
@InjectModel(MODEL_CLASS.name)
dbModel: Model<TModelDocType>,
@Inject(CONTEXT) context: any,
) {
super()
this.dbModel = dbModel
this.context = context
}

async create(payload: CreateSchoolInput) {
await this.checkPermissons(Action.Create)
const updatedPayload = {
createdBy: this.request.req.user,
createdBy: this.currentUser,
...payload,
}
const model = new this.model(updatedPayload)
const model = new this.dbModel(updatedPayload)
return model.save()
}

getById(_id: Types.ObjectId) {
return this.checkPermissons(Action.Read, _id)
}

list(filters: ListSchoolInput) {
return this.model.find({ ...filters }).exec()
async list(filters: ListSchoolInput) {
const docs = await this.dbModel.find({ ...filters }).exec()
for (const doc of docs) {
await this.checkPermissons(Action.Read, doc._id)
}
return docs
}

async update(payload: UpdateSchoolInput) {
await this.checkPermissons(Action.Update, payload._id)
return this.model
return this.dbModel
.findByIdAndUpdate(payload._id, payload, { new: true })
.exec()
}

async delete(_id: Types.ObjectId) {
await this.checkPermissons(Action.Delete, _id)
let model: any // Model<SchoolDocument>

try {
model = await this.model.findByIdAndDelete(_id).exec()
} catch (error) {
console.error(error)
return
}

return model
return this.dbModel.findByIdAndDelete(_id).exec()
}
}
2 changes: 1 addition & 1 deletion src/entities/user/user.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class User extends Utils.BaseModel {
passwordHash: string

@GQL.Field(() => String, { nullable: true })
@DB.Prop({ required: false, min: 3, max: 60, unique: false }) // unique: true,
@DB.Prop({ required: false, min: 3, max: 60 }) // unique: true,
mobile: string

@GQL.Field(() => [String], { nullable: true })
Expand Down
3 changes: 2 additions & 1 deletion src/entities/user/user.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { User, UserSchema } from './user.model'
import { UserResolver } from './user.resolver'
// import { AuthModule } from '../auth/auth.module'

import { PoliciesGuard } from '../../casl/policy.guard'
import { CaslAbilityFactory } from '../../casl/casl-ability.factory'
// import { PoliciesGuard } from '../../casl/policy.guard'

@Module({
imports: [
// AuthModule,
Expand Down
41 changes: 41 additions & 0 deletions src/utils/BaseService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Injectable } from '@nestjs/common'
import { plainToClass } from 'class-transformer'
import mongoose from 'mongoose'

// Auth
import { ForbiddenError } from '@casl/ability'
import { Action } from '../casl/casl-ability.factory'
import { CaslAbilityFactory } from '../casl/casl-ability.factory'

// User
import { User } from '../entities/user/user.model'

@Injectable()
export abstract class BaseService {
abstract dbModel: any // mongoose.Model<mongoose.Document>
abstract modelClass: any //{ new (): any }
abstract context: any //{ new (): any }

get currentUser(): User | undefined {
return this.context.req?.user
}

async checkPermissons(
action: Action,
id?: mongoose.Types.ObjectId,
): Promise<mongoose.Model<mongoose.Document>> {
// Checks permissons for a single record
const ability = CaslAbilityFactory.createForUser(this.currentUser)
if (id) {
const doc = await this.dbModel.findById(id).exec()
const model = plainToClass(this.modelClass, doc?.toObject()) as any
ForbiddenError.from(ability).throwUnlessCan(action, model)
return doc
} else {
ForbiddenError.from(ability).throwUnlessCan(
action,
this.modelClass as any,
)
}
}
}