diff --git a/packages/server/src/app.module.ts b/packages/server/src/app.module.ts index 5e022e71..23bf25eb 100644 --- a/packages/server/src/app.module.ts +++ b/packages/server/src/app.module.ts @@ -11,7 +11,8 @@ import { StudyModule } from './study/study.module'; import { EntryModule } from './entry/entry.module'; import { TagModule } from './tag/tag.module'; import { SharedModule } from './shared/shared.module'; -import { AuthModule } from './auth/auth.module'; +import { JwtModule } from './jwt/jwt.module'; +import { PermissionModule } from './permission/permission.module'; @Module({ imports: [ @@ -40,7 +41,8 @@ import { AuthModule } from './auth/auth.module'; EntryModule, TagModule, SharedModule, - AuthModule + JwtModule, + PermissionModule ] }) export class AppModule {} diff --git a/packages/server/src/auth/auth.module.ts b/packages/server/src/auth/auth.module.ts deleted file mode 100644 index b47c497a..00000000 --- a/packages/server/src/auth/auth.module.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { forwardRef, Module } from '@nestjs/common'; -import { JwtModule, JwtModuleOptions } from '@nestjs/jwt'; -import { AuthService } from './auth.service'; -import { PassportModule } from '@nestjs/passport'; -import { JwtStrategy } from './jwt.strategy'; -import { JwtAuthGuard } from './jwt.guard'; -import { OrganizationModule } from '../organization/organization.module'; -import { HttpModule } from '@nestjs/axios'; -import { casbinProvider } from './casbin.provider'; -import { AuthResolver } from './auth.resolver'; -import { OrganizationService } from '../organization/organization.service'; - -@Module({ - imports: [ - PassportModule, - OrganizationModule, - HttpModule, - JwtModule.registerAsync({ - imports: [forwardRef(() => AuthModule)], - inject: [AuthService], - useFactory: async (authService: AuthService) => { - const options: JwtModuleOptions = { - publicKey: await authService.getPublicKey(), - signOptions: { - algorithm: 'RS256' - } - }; - return options; - } - }), - forwardRef(() => OrganizationModule) - ], - providers: [ - AuthService, - JwtAuthGuard, - casbinProvider, - AuthResolver, - { - provide: JwtStrategy, - inject: [AuthService, OrganizationService], - useFactory: async (authService: AuthService, organizationService: OrganizationService) => { - const key = await authService.getPublicKey(); - return new JwtStrategy(key, organizationService); - } - } - ], - exports: [AuthService, casbinProvider] -}) -export class AuthModule {} diff --git a/packages/server/src/auth/auth.service.ts b/packages/server/src/auth/auth.service.ts deleted file mode 100644 index e17c8da5..00000000 --- a/packages/server/src/auth/auth.service.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Injectable, Inject, UnauthorizedException } from '@nestjs/common'; -import { HttpService } from '@nestjs/axios'; -import { ConfigService } from '@nestjs/config'; -import { firstValueFrom } from 'rxjs'; -import { CASBIN_PROVIDER } from './casbin.provider'; -import * as casbin from 'casbin'; -import { Roles } from './roles'; - -@Injectable() -export class AuthService { - private publicKey: string | null = null; - - constructor( - private readonly httpService: HttpService, - private readonly configService: ConfigService, - @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer - ) {} - - // TODO: In the future this will be replaced by a library which handles - // key rotation - async queryForPublicKey(): Promise { - const query = this.configService.getOrThrow('auth.publicKeyUrl'); - - const response = await firstValueFrom(this.httpService.get(query)); - return response.data[0]; - } - - /** requestingUser must be an owner themselves */ - async grantOwner(targetUser: string, requestingUser: string, organization: string): Promise { - // Make sure the requesting user is an owner - const isOwner = await this.enforcer.enforce(requestingUser, Roles.OWNER, organization); - if (!isOwner) { - throw new UnauthorizedException('Requesting user is not an owner'); - } - - await this.enforcer.addPolicy(targetUser, Roles.OWNER, organization); - } - - async getPublicKey(): Promise { - if (this.publicKey === null) { - this.publicKey = await this.queryForPublicKey(); - } - - return this.publicKey; - } -} diff --git a/packages/server/src/dataset/dataset.module.ts b/packages/server/src/dataset/dataset.module.ts index 387e4267..898e65ad 100644 --- a/packages/server/src/dataset/dataset.module.ts +++ b/packages/server/src/dataset/dataset.module.ts @@ -4,10 +4,11 @@ import { DatasetService } from './dataset.service'; import { MongooseModule } from '@nestjs/mongoose'; import { Dataset, DatasetSchema } from './dataset.model'; import { DatasetPipe } from './pipes/dataset.pipe'; -import { AuthModule } from '../auth/auth.module'; +import { PermissionModule } from '../permission/permission.module'; +import { JwtModule } from '../jwt/jwt.module'; @Module({ - imports: [MongooseModule.forFeature([{ name: Dataset.name, schema: DatasetSchema }]), AuthModule], + imports: [MongooseModule.forFeature([{ name: Dataset.name, schema: DatasetSchema }]), PermissionModule, JwtModule], providers: [DatasetResolver, DatasetService, DatasetPipe], exports: [DatasetService, DatasetPipe] }) diff --git a/packages/server/src/dataset/dataset.resolver.ts b/packages/server/src/dataset/dataset.resolver.ts index 35d83acd..1629174e 100644 --- a/packages/server/src/dataset/dataset.resolver.ts +++ b/packages/server/src/dataset/dataset.resolver.ts @@ -6,12 +6,12 @@ import { OrganizationContext } from '../organization/organization.context'; import { DatasetCreate } from './dtos/create.dto'; import { DatasetPipe } from './pipes/dataset.pipe'; import { BadRequestException, UseGuards, Inject, UnauthorizedException } from '@nestjs/common'; -import { JwtAuthGuard } from '../auth/jwt.guard'; -import { CASBIN_PROVIDER } from '../auth/casbin.provider'; +import { JwtAuthGuard } from '../jwt/jwt.guard'; +import { CASBIN_PROVIDER } from '../permission/casbin.provider'; import * as casbin from 'casbin'; -import { UserContext } from '../auth/user.decorator'; -import { TokenPayload } from '../auth/user.dto'; -import { DatasetPermissions } from '../auth/permissions/dataset'; +import { TokenContext } from '../jwt/token.context'; +import { TokenPayload } from '../jwt/token.dto'; +import { DatasetPermissions } from '../permission/permissions/dataset'; // TODO: Add authentication @UseGuards(JwtAuthGuard) @@ -33,7 +33,7 @@ export class DatasetResolver { async createDataset( @Args('dataset') dataset: DatasetCreate, @OrganizationContext() organization: Organization, - @UserContext() user: TokenPayload + @TokenContext() user: TokenPayload ): Promise { if (!(await this.enforcer.enforce(user.id, DatasetPermissions.CREATE, organization._id))) { throw new UnauthorizedException('User does not have permission to create a dataset in this organization'); diff --git a/packages/server/src/dataset/dataset.service.ts b/packages/server/src/dataset/dataset.service.ts index cf616797..8e2c2097 100644 --- a/packages/server/src/dataset/dataset.service.ts +++ b/packages/server/src/dataset/dataset.service.ts @@ -4,7 +4,7 @@ import { InjectModel } from '@nestjs/mongoose'; import { Dataset } from './dataset.model'; import { DatasetCreate } from './dtos/create.dto'; import { ConfigService } from '@nestjs/config'; -import { CASBIN_PROVIDER } from '../auth/casbin.provider'; +import { CASBIN_PROVIDER } from '../permission/casbin.provider'; import * as casbin from 'casbin'; @Injectable() diff --git a/packages/server/src/entry/entry.module.ts b/packages/server/src/entry/entry.module.ts index 0c59c266..a94d7be5 100644 --- a/packages/server/src/entry/entry.module.ts +++ b/packages/server/src/entry/entry.module.ts @@ -13,7 +13,8 @@ import { EntryUploadService } from './services/entry-upload.service'; import { UploadSessionPipe } from './pipes/upload-session.pipe'; import { GcpModule } from '../gcp/gcp.module'; import { CsvValidationService } from './services/csv-validation.service'; -import { AuthModule } from '../auth/auth.module'; +import { PermissionModule } from '../permission/permission.module'; +import { JwtModule } from '../jwt/jwt.module'; @Module({ imports: [ @@ -24,7 +25,8 @@ import { AuthModule } from '../auth/auth.module'; ]), DatasetModule, GcpModule, - AuthModule + PermissionModule, + JwtModule ], providers: [ EntryResolver, diff --git a/packages/server/src/entry/resolvers/entry.resolver.ts b/packages/server/src/entry/resolvers/entry.resolver.ts index 55ffdee6..f2a76ba8 100644 --- a/packages/server/src/entry/resolvers/entry.resolver.ts +++ b/packages/server/src/entry/resolvers/entry.resolver.ts @@ -4,12 +4,12 @@ import { Entry } from '../models/entry.model'; import { EntryService } from '../services/entry.service'; import { DatasetPipe } from '../../dataset/pipes/dataset.pipe'; import { UseGuards, Inject, UnauthorizedException } from '@nestjs/common'; -import { JwtAuthGuard } from '../../auth/jwt.guard'; -import { DatasetPermissions } from '../../auth/permissions/dataset'; -import { CASBIN_PROVIDER } from '../../auth/casbin.provider'; +import { JwtAuthGuard } from '../../jwt/jwt.guard'; +import { DatasetPermissions } from '../../permission/permissions/dataset'; +import { CASBIN_PROVIDER } from '../../permission/casbin.provider'; import * as casbin from 'casbin'; -import { TokenPayload } from '../../auth/user.dto'; -import { UserContext } from '../../auth/user.decorator'; +import { TokenPayload } from '../../jwt/token.dto'; +import { TokenContext } from '../../jwt/token.context'; @UseGuards(JwtAuthGuard) @Resolver(() => Entry) @@ -22,7 +22,7 @@ export class EntryResolver { @Query(() => [Entry]) async entryForDataset( @Args('dataset', { type: () => ID }, DatasetPipe) dataset: Dataset, - @UserContext() user: TokenPayload + @TokenContext() user: TokenPayload ): Promise { if (!(await this.enforcer.enforce(user.id, DatasetPermissions.READ, dataset._id))) { throw new UnauthorizedException('User cannot read entries on this dataset'); diff --git a/packages/server/src/entry/resolvers/upload-session.resolver.ts b/packages/server/src/entry/resolvers/upload-session.resolver.ts index 65f6b4f1..48f5db74 100644 --- a/packages/server/src/entry/resolvers/upload-session.resolver.ts +++ b/packages/server/src/entry/resolvers/upload-session.resolver.ts @@ -6,12 +6,12 @@ import { Args, ID, Mutation, Query } from '@nestjs/graphql'; import { UploadSessionPipe } from '../pipes/upload-session.pipe'; import { DatasetPipe } from '../../dataset/pipes/dataset.pipe'; import { UploadResult } from '../dtos/upload-result.dto'; -import { JwtAuthGuard } from '../../auth/jwt.guard'; -import { DatasetPermissions } from '../../auth/permissions/dataset'; -import { CASBIN_PROVIDER } from '../../auth/casbin.provider'; +import { JwtAuthGuard } from '../../jwt/jwt.guard'; +import { DatasetPermissions } from '../../permission/permissions/dataset'; +import { CASBIN_PROVIDER } from '../../permission/casbin.provider'; import * as casbin from 'casbin'; -import { UserContext } from 'src/auth/user.decorator'; -import { TokenPayload } from 'src/auth/user.dto'; +import { TokenContext } from '../../jwt/token.context'; +import { TokenPayload } from '../../jwt/token.dto'; @UseGuards(JwtAuthGuard) @Injectable() @@ -25,7 +25,7 @@ export class UploadSessionResolver { @Mutation(() => UploadSession) async createUploadSession( @Args('dataset', { type: () => ID }, DatasetPipe) dataset: Dataset, - @UserContext() user: TokenPayload + @TokenContext() user: TokenPayload ): Promise { if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, dataset._id))) { throw new UnauthorizedException('User cannot write entries on this dataset'); @@ -38,7 +38,7 @@ export class UploadSessionResolver { @Mutation(() => UploadResult) async completeUploadSession( @Args('session', { type: () => ID }, UploadSessionPipe) uploadSession: UploadSession, - @UserContext() user: TokenPayload + @TokenContext() user: TokenPayload ): Promise { if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, uploadSession.dataset))) { throw new UnauthorizedException('User cannot write entries on this dataset'); @@ -50,7 +50,7 @@ export class UploadSessionResolver { @Query(() => String, { description: 'Get the presigned URL for where to upload the CSV against' }) async getCSVUploadURL( @Args('session', { type: () => ID }, UploadSessionPipe) uploadSession: UploadSession, - @UserContext() user: TokenPayload + @TokenContext() user: TokenPayload ): Promise { if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, uploadSession.dataset))) { throw new UnauthorizedException('User cannot write entries on this dataset'); @@ -62,7 +62,7 @@ export class UploadSessionResolver { @Query(() => UploadResult) async validateCSV( @Args('session', { type: () => ID }, UploadSessionPipe) uploadSession: UploadSession, - @UserContext() user: TokenPayload + @TokenContext() user: TokenPayload ): Promise { if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, uploadSession.dataset))) { throw new UnauthorizedException('User cannot write entries on this dataset'); @@ -77,7 +77,7 @@ export class UploadSessionResolver { @Args('session', { type: () => ID }, UploadSessionPipe) uploadSession: UploadSession, @Args('filename') filename: string, @Args('contentType') contentType: string, - @UserContext() user: TokenPayload + @TokenContext() user: TokenPayload ): Promise { if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, uploadSession.dataset))) { throw new UnauthorizedException('User cannot write entries on this dataset'); diff --git a/packages/server/src/auth/jwt.guard.ts b/packages/server/src/jwt/jwt.guard.ts similarity index 100% rename from packages/server/src/auth/jwt.guard.ts rename to packages/server/src/jwt/jwt.guard.ts diff --git a/packages/server/src/jwt/jwt.module.ts b/packages/server/src/jwt/jwt.module.ts new file mode 100644 index 00000000..98e65c2b --- /dev/null +++ b/packages/server/src/jwt/jwt.module.ts @@ -0,0 +1,42 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { JwtModule as NestJwtModule, JwtModuleOptions as NestJwtModuleOptions } from '@nestjs/jwt'; +import { JwtService } from './jwt.service'; +import { HttpModule } from '@nestjs/axios'; +import { JwtAuthGuard } from './jwt.guard'; +import { JwtStrategy } from './jwt.strategy'; +import { OrganizationService } from '../organization/organization.service'; +import { OrganizationModule } from '../organization/organization.module'; + +@Module({ + imports: [ + NestJwtModule.registerAsync({ + imports: [forwardRef(() => JwtModule)], + inject: [JwtService], + useFactory: async (jwtService: JwtService) => { + const options: NestJwtModuleOptions = { + publicKey: await jwtService.getPublicKey(), + signOptions: { + algorithm: 'RS256' + } + }; + return options; + } + }), + HttpModule, + forwardRef(() => OrganizationModule) + ], + providers: [ + JwtService, + JwtAuthGuard, + { + provide: JwtStrategy, + inject: [JwtService, OrganizationService], + useFactory: async (jwtService: JwtService, organizationService: OrganizationService) => { + const key = await jwtService.getPublicKey(); + return new JwtStrategy(key, organizationService); + } + } + ], + exports: [JwtService] +}) +export class JwtModule {} diff --git a/packages/server/src/jwt/jwt.service.ts b/packages/server/src/jwt/jwt.service.ts new file mode 100644 index 00000000..a18d974f --- /dev/null +++ b/packages/server/src/jwt/jwt.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; +import { firstValueFrom } from 'rxjs'; + +@Injectable() +export class JwtService { + private publicKey: string | null = null; + + constructor(private readonly httpService: HttpService, private readonly configService: ConfigService) {} + + // TODO: In the future this will be replaced by a library which handles + // key rotation + async queryForPublicKey(): Promise { + const query = this.configService.getOrThrow('auth.publicKeyUrl'); + + const response = await firstValueFrom(this.httpService.get(query)); + return response.data[0]; + } + + async getPublicKey(): Promise { + if (this.publicKey === null) { + this.publicKey = await this.queryForPublicKey(); + } + + return this.publicKey; + } +} diff --git a/packages/server/src/auth/jwt.strategy.ts b/packages/server/src/jwt/jwt.strategy.ts similarity index 96% rename from packages/server/src/auth/jwt.strategy.ts rename to packages/server/src/jwt/jwt.strategy.ts index 360dfd05..e4396434 100644 --- a/packages/server/src/auth/jwt.strategy.ts +++ b/packages/server/src/jwt/jwt.strategy.ts @@ -1,7 +1,7 @@ import { Injectable, BadGatewayException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; -import { TokenPayload } from './user.dto'; +import { TokenPayload } from './token.dto'; import { OrganizationService } from '../organization/organization.service'; import { Organization } from 'src/organization/organization.model'; diff --git a/packages/server/src/auth/user.decorator.ts b/packages/server/src/jwt/token.context.ts similarity index 70% rename from packages/server/src/auth/user.decorator.ts rename to packages/server/src/jwt/token.context.ts index 27a37e42..fece3f78 100644 --- a/packages/server/src/auth/user.decorator.ts +++ b/packages/server/src/jwt/token.context.ts @@ -1,7 +1,7 @@ import { GqlExecutionContext } from '@nestjs/graphql'; import { ExecutionContext, createParamDecorator } from '@nestjs/common'; -export const UserContext = createParamDecorator((_data: unknown, ctx: ExecutionContext) => { +export const TokenContext = createParamDecorator((_data: unknown, ctx: ExecutionContext) => { const gqlCtx = GqlExecutionContext.create(ctx); return gqlCtx.getContext().req.user; }); diff --git a/packages/server/src/auth/user.dto.ts b/packages/server/src/jwt/token.dto.ts similarity index 100% rename from packages/server/src/auth/user.dto.ts rename to packages/server/src/jwt/token.dto.ts diff --git a/packages/server/src/organization/organization.resolver.ts b/packages/server/src/organization/organization.resolver.ts index f34bae36..cb53d629 100644 --- a/packages/server/src/organization/organization.resolver.ts +++ b/packages/server/src/organization/organization.resolver.ts @@ -4,7 +4,7 @@ import { Organization } from './organization.model'; import { OrganizationService } from './organization.service'; import { CreateOrganizationPipe } from './pipes/create.pipe'; import { UseGuards } from '@nestjs/common'; -import { JwtAuthGuard } from '../auth/jwt.guard'; +import { JwtAuthGuard } from '../jwt/jwt.guard'; @Resolver(() => Organization) export class OrganizationResolver { diff --git a/packages/server/src/auth/casbin.provider.ts b/packages/server/src/permission/casbin.provider.ts similarity index 95% rename from packages/server/src/auth/casbin.provider.ts rename to packages/server/src/permission/casbin.provider.ts index d559b149..8442c259 100644 --- a/packages/server/src/auth/casbin.provider.ts +++ b/packages/server/src/permission/casbin.provider.ts @@ -2,7 +2,7 @@ import { Provider } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as casbin from 'casbin'; import { MongooseAdapter } from 'casbin-mongoose-adapter'; -import { roleHierarchy } from './roles'; +import { roleHierarchy } from './permissions/roles'; import { roleToStudyPermissions } from './permissions/study'; import { roleToProjectPermissions } from './permissions/project'; import { roleToTagPermissions } from './permissions/tag'; diff --git a/packages/server/src/permission/permission.module.ts b/packages/server/src/permission/permission.module.ts new file mode 100644 index 00000000..534e61f0 --- /dev/null +++ b/packages/server/src/permission/permission.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { casbinProvider } from './casbin.provider'; +import { PermissionResolver } from './permission.resolver'; +import { PermissionService } from './permission.service'; + +@Module({ + providers: [casbinProvider, PermissionResolver, PermissionService], + exports: [casbinProvider] +}) +export class PermissionModule {} diff --git a/packages/server/src/auth/auth.resolver.ts b/packages/server/src/permission/permission.resolver.ts similarity index 52% rename from packages/server/src/auth/auth.resolver.ts rename to packages/server/src/permission/permission.resolver.ts index ee78914e..a3b7692c 100644 --- a/packages/server/src/auth/auth.resolver.ts +++ b/packages/server/src/permission/permission.resolver.ts @@ -1,24 +1,24 @@ import { Resolver, Mutation, Args, ID } from '@nestjs/graphql'; -import { JwtAuthGuard } from './jwt.guard'; +import { JwtAuthGuard } from '../jwt/jwt.guard'; import { UseGuards } from '@nestjs/common'; -import { UserContext } from './user.decorator'; -import { TokenPayload } from './user.dto'; -import { AuthService } from './auth.service'; +import { TokenContext } from '../jwt/token.context'; +import { TokenPayload } from '../jwt/token.dto'; +import { PermissionService } from './permission.service'; import { OrganizationContext } from 'src/organization/organization.context'; import { Organization } from 'src/organization/organization.model'; @UseGuards(JwtAuthGuard) @Resolver() -export class AuthResolver { - constructor(private readonly authService: AuthService) {} +export class PermissionResolver { + constructor(private readonly permissionService: PermissionService) {} @Mutation(() => Boolean) async grantOwner( @Args('targetUser', { type: () => ID }) targetUser: string, - @UserContext() requestingUser: TokenPayload, + @TokenContext() requestingUser: TokenPayload, @OrganizationContext() organization: Organization ): Promise { - await this.authService.grantOwner(targetUser, requestingUser.id, organization._id); + await this.permissionService.grantOwner(targetUser, requestingUser.id, organization._id); return true; } } diff --git a/packages/server/src/permission/permission.service.ts b/packages/server/src/permission/permission.service.ts new file mode 100644 index 00000000..d07f8cbd --- /dev/null +++ b/packages/server/src/permission/permission.service.ts @@ -0,0 +1,20 @@ +import { Injectable, Inject, UnauthorizedException } from '@nestjs/common'; +import { CASBIN_PROVIDER } from './casbin.provider'; +import * as casbin from 'casbin'; +import { Roles } from './permissions/roles'; + +@Injectable() +export class PermissionService { + constructor(@Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer) {} + + /** requestingUser must be an owner themselves */ + async grantOwner(targetUser: string, requestingUser: string, organization: string): Promise { + // Make sure the requesting user is an owner + const isOwner = await this.enforcer.enforce(requestingUser, Roles.OWNER, organization); + if (!isOwner) { + throw new UnauthorizedException('Requesting user is not an owner'); + } + + await this.enforcer.addPolicy(targetUser, Roles.OWNER, organization); + } +} diff --git a/packages/server/src/auth/permissions/dataset.ts b/packages/server/src/permission/permissions/dataset.ts similarity index 95% rename from packages/server/src/auth/permissions/dataset.ts rename to packages/server/src/permission/permissions/dataset.ts index 81c0d549..f0f7e370 100644 --- a/packages/server/src/auth/permissions/dataset.ts +++ b/packages/server/src/permission/permissions/dataset.ts @@ -1,4 +1,4 @@ -import { Roles } from '../roles'; +import { Roles } from './roles'; export enum DatasetPermissions { CREATE = 'dataset:create', diff --git a/packages/server/src/auth/permissions/project.ts b/packages/server/src/permission/permissions/project.ts similarity index 94% rename from packages/server/src/auth/permissions/project.ts rename to packages/server/src/permission/permissions/project.ts index 327f4d6d..0327f6e8 100644 --- a/packages/server/src/auth/permissions/project.ts +++ b/packages/server/src/permission/permissions/project.ts @@ -1,4 +1,4 @@ -import { Roles } from '../roles'; +import { Roles } from './roles'; /** Permissions associated with projects */ export enum ProjectPermissions { diff --git a/packages/server/src/auth/roles.ts b/packages/server/src/permission/permissions/roles.ts similarity index 100% rename from packages/server/src/auth/roles.ts rename to packages/server/src/permission/permissions/roles.ts diff --git a/packages/server/src/auth/permissions/study.ts b/packages/server/src/permission/permissions/study.ts similarity index 95% rename from packages/server/src/auth/permissions/study.ts rename to packages/server/src/permission/permissions/study.ts index abc0d8bf..df831450 100644 --- a/packages/server/src/auth/permissions/study.ts +++ b/packages/server/src/permission/permissions/study.ts @@ -1,4 +1,4 @@ -import { Roles } from '../roles'; +import { Roles } from './roles'; /** Permissions associated with studies */ export enum StudyPermissions { diff --git a/packages/server/src/auth/permissions/tag.ts b/packages/server/src/permission/permissions/tag.ts similarity index 93% rename from packages/server/src/auth/permissions/tag.ts rename to packages/server/src/permission/permissions/tag.ts index c0a4e671..81715590 100644 --- a/packages/server/src/auth/permissions/tag.ts +++ b/packages/server/src/permission/permissions/tag.ts @@ -1,4 +1,4 @@ -import { Roles } from '../roles'; +import { Roles } from './roles'; export enum TagPermissions { CREATE = 'tag:create', diff --git a/packages/server/src/project/project.module.ts b/packages/server/src/project/project.module.ts index c793d7ec..61a6c339 100644 --- a/packages/server/src/project/project.module.ts +++ b/packages/server/src/project/project.module.ts @@ -6,7 +6,8 @@ import { Project, ProjectSchema } from './project.model'; import { ProjectPipe } from './pipes/project.pipe'; import { MongooseMiddlewareService } from 'src/shared/service/mongoose-callback.service'; import { SharedModule } from 'src/shared/shared.module'; -import { AuthModule } from '../auth/auth.module'; +import { JwtModule } from '../jwt/jwt.module'; +import { PermissionModule } from '../permission/permission.module'; @Module({ imports: [ @@ -27,7 +28,8 @@ import { AuthModule } from '../auth/auth.module'; inject: [MongooseMiddlewareService] } ]), - AuthModule + JwtModule, + PermissionModule ], providers: [ProjectResolver, ProjectService, ProjectPipe], exports: [ProjectPipe, ProjectService] diff --git a/packages/server/src/project/project.resolver.ts b/packages/server/src/project/project.resolver.ts index 628ac15a..71ae22d4 100644 --- a/packages/server/src/project/project.resolver.ts +++ b/packages/server/src/project/project.resolver.ts @@ -6,12 +6,12 @@ import { ProjectCreate } from './dtos/create.dto'; import { Project } from './project.model'; import { ProjectService } from './project.service'; import { ProjectPipe } from './pipes/project.pipe'; -import { JwtAuthGuard } from '../auth/jwt.guard'; -import { UserContext } from '../auth/user.decorator'; -import { TokenPayload } from '../auth/user.dto'; -import { CASBIN_PROVIDER } from '../auth/casbin.provider'; +import { JwtAuthGuard } from '../jwt/jwt.guard'; +import { TokenContext } from '../jwt/token.context'; +import { TokenPayload } from '../jwt/token.dto'; +import { CASBIN_PROVIDER } from '../permission/casbin.provider'; import * as casbin from 'casbin'; -import { ProjectPermissions } from '../auth/permissions/project'; +import { ProjectPermissions } from '../permission/permissions/project'; @UseGuards(JwtAuthGuard) @Resolver(() => Project) @@ -25,7 +25,7 @@ export class ProjectResolver { async signLabCreateProject( @Args('project') project: ProjectCreate, @OrganizationContext() organization: Organization, - @UserContext() user: TokenPayload + @TokenContext() user: TokenPayload ): Promise { // Make sure the user is allowed to create projects if (!(await this.enforcer.enforce(user.id, ProjectPermissions.CREATE, organization._id))) { @@ -43,7 +43,7 @@ export class ProjectResolver { async projectExists( @Args('name') name: string, @OrganizationContext() organization: Organization, - @UserContext() _user: TokenPayload + @TokenContext() _user: TokenPayload ): Promise { return this.projectService.exists(name, organization._id); } @@ -52,7 +52,7 @@ export class ProjectResolver { @Mutation(() => Boolean) async deleteProject( @Args('project', { type: () => ID }, ProjectPipe) project: Project, - @UserContext() user: TokenPayload, + @TokenContext() user: TokenPayload, @OrganizationContext() organization: Organization ): Promise { if (!(await this.enforcer.enforce(user.id, ProjectPermissions.DELETE, organization._id))) { @@ -67,7 +67,7 @@ export class ProjectResolver { @Query(() => [Project]) async getProjects( @OrganizationContext() organization: Organization, - @UserContext() user: TokenPayload + @TokenContext() user: TokenPayload ): Promise { if (!(await this.enforcer.enforce(user.id, ProjectPermissions.READ, organization._id))) { throw new UnauthorizedException('User does not have permission to read projects'); diff --git a/packages/server/src/project/project.service.ts b/packages/server/src/project/project.service.ts index 226d429e..a0e60a10 100644 --- a/packages/server/src/project/project.service.ts +++ b/packages/server/src/project/project.service.ts @@ -3,7 +3,7 @@ import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { Project, ProjectDocument } from './project.model'; import { ProjectCreate } from './dtos/create.dto'; -import { CASBIN_PROVIDER } from '../auth/casbin.provider'; +import { CASBIN_PROVIDER } from '../permission/casbin.provider'; import * as casbin from 'casbin'; @Injectable() diff --git a/packages/server/src/study/study.module.ts b/packages/server/src/study/study.module.ts index 6167d974..8ff10dd2 100644 --- a/packages/server/src/study/study.module.ts +++ b/packages/server/src/study/study.module.ts @@ -8,7 +8,8 @@ import { StudyPipe } from './pipes/study.pipe'; import { StudyCreatePipe } from './pipes/create.pipe'; import { MongooseMiddlewareService } from '../shared/service/mongoose-callback.service'; import { SharedModule } from '../shared/shared.module'; -import { AuthModule } from '../auth/auth.module'; +import { JwtModule } from '../jwt/jwt.module'; +import { PermissionModule } from '../permission/permission.module'; @Module({ imports: [ @@ -31,7 +32,8 @@ import { AuthModule } from '../auth/auth.module'; ]), ProjectModule, SharedModule, - AuthModule + JwtModule, + PermissionModule ], providers: [StudyService, StudyResolver, StudyPipe, StudyCreatePipe], exports: [StudyService, StudyPipe] diff --git a/packages/server/src/study/study.resolver.ts b/packages/server/src/study/study.resolver.ts index 9543be5f..a26c2d3e 100644 --- a/packages/server/src/study/study.resolver.ts +++ b/packages/server/src/study/study.resolver.ts @@ -7,12 +7,12 @@ import { StudyCreate } from './dtos/create.dto'; import { StudyService } from './study.service'; import { StudyCreatePipe } from './pipes/create.pipe'; import { UseGuards, Inject, UnauthorizedException } from '@nestjs/common'; -import { JwtAuthGuard } from '../auth/jwt.guard'; -import { CASBIN_PROVIDER } from '../auth/casbin.provider'; +import { JwtAuthGuard } from '../jwt/jwt.guard'; +import { CASBIN_PROVIDER } from '../permission/casbin.provider'; import * as casbin from 'casbin'; -import { StudyPermissions } from '../auth/permissions/study'; -import { UserContext } from 'src/auth/user.decorator'; -import { TokenPayload } from 'src/auth/user.dto'; +import { StudyPermissions } from '../permission/permissions/study'; +import { TokenContext } from '../jwt/token.context'; +import { TokenPayload } from '../jwt/token.dto'; @UseGuards(JwtAuthGuard) @Resolver(() => Study) @@ -25,7 +25,7 @@ export class StudyResolver { @Mutation(() => Study) async createStudy( @Args('study', { type: () => StudyCreate }, StudyCreatePipe) study: StudyCreate, - @UserContext() user: TokenPayload + @TokenContext() user: TokenPayload ): Promise { if (!(await this.enforcer.enforce(user.id, StudyPermissions.CREATE, study.project))) { throw new UnauthorizedException('User cannot create studies on this project'); diff --git a/packages/server/src/study/study.service.ts b/packages/server/src/study/study.service.ts index 8a5689de..1b8e75a6 100644 --- a/packages/server/src/study/study.service.ts +++ b/packages/server/src/study/study.service.ts @@ -6,7 +6,7 @@ import { StudyCreate } from './dtos/create.dto'; import { Validator } from 'jsonschema'; import { Project } from 'src/project/project.model'; import { MongooseMiddlewareService } from '../shared/service/mongoose-callback.service'; -import { CASBIN_PROVIDER } from '../auth/casbin.provider'; +import { CASBIN_PROVIDER } from '../permission/casbin.provider'; import * as casbin from 'casbin'; @Injectable() diff --git a/packages/server/src/tag/tag.resolver.ts b/packages/server/src/tag/tag.resolver.ts index 56d8a06c..44cb49fc 100644 --- a/packages/server/src/tag/tag.resolver.ts +++ b/packages/server/src/tag/tag.resolver.ts @@ -8,7 +8,7 @@ import { Entry } from '../entry/models/entry.model'; import { TagPipe } from './pipes/tag.pipe'; import JSON from 'graphql-type-json'; import { UseGuards } from '@nestjs/common'; -import { JwtAuthGuard } from '../auth/jwt.guard'; +import { JwtAuthGuard } from '../jwt/jwt.guard'; // TODO: Add permissioning @UseGuards(JwtAuthGuard)