Skip to content
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
6 changes: 4 additions & 2 deletions packages/server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -40,7 +41,8 @@ import { AuthModule } from './auth/auth.module';
EntryModule,
TagModule,
SharedModule,
AuthModule
JwtModule,
PermissionModule
]
})
export class AppModule {}
49 changes: 0 additions & 49 deletions packages/server/src/auth/auth.module.ts

This file was deleted.

46 changes: 0 additions & 46 deletions packages/server/src/auth/auth.service.ts

This file was deleted.

5 changes: 3 additions & 2 deletions packages/server/src/dataset/dataset.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
})
Expand Down
12 changes: 6 additions & 6 deletions packages/server/src/dataset/dataset.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -33,7 +33,7 @@ export class DatasetResolver {
async createDataset(
@Args('dataset') dataset: DatasetCreate,
@OrganizationContext() organization: Organization,
@UserContext() user: TokenPayload
@TokenContext() user: TokenPayload
): Promise<Dataset> {
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');
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/dataset/dataset.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 4 additions & 2 deletions packages/server/src/entry/entry.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -24,7 +25,8 @@ import { AuthModule } from '../auth/auth.module';
]),
DatasetModule,
GcpModule,
AuthModule
PermissionModule,
JwtModule
],
providers: [
EntryResolver,
Expand Down
12 changes: 6 additions & 6 deletions packages/server/src/entry/resolvers/entry.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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<Entry[]> {
if (!(await this.enforcer.enforce(user.id, DatasetPermissions.READ, dataset._id))) {
throw new UnauthorizedException('User cannot read entries on this dataset');
Expand Down
20 changes: 10 additions & 10 deletions packages/server/src/entry/resolvers/upload-session.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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<UploadSession> {
if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, dataset._id))) {
throw new UnauthorizedException('User cannot write entries on this dataset');
Expand All @@ -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<UploadResult> {
if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, uploadSession.dataset))) {
throw new UnauthorizedException('User cannot write entries on this dataset');
Expand All @@ -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<string> {
if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, uploadSession.dataset))) {
throw new UnauthorizedException('User cannot write entries on this dataset');
Expand All @@ -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<UploadResult> {
if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, uploadSession.dataset))) {
throw new UnauthorizedException('User cannot write entries on this dataset');
Expand All @@ -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<string> {
if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, uploadSession.dataset))) {
throw new UnauthorizedException('User cannot write entries on this dataset');
Expand Down
42 changes: 42 additions & 0 deletions packages/server/src/jwt/jwt.module.ts
Original file line number Diff line number Diff line change
@@ -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 {}
28 changes: 28 additions & 0 deletions packages/server/src/jwt/jwt.service.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
const query = this.configService.getOrThrow('auth.publicKeyUrl');

const response = await firstValueFrom(this.httpService.get(query));
return response.data[0];
}

async getPublicKey(): Promise<string> {
if (this.publicKey === null) {
this.publicKey = await this.queryForPublicKey();
}

return this.publicKey;
}
}
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
});
2 changes: 1 addition & 1 deletion packages/server/src/organization/organization.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
10 changes: 10 additions & 0 deletions packages/server/src/permission/permission.module.ts
Original file line number Diff line number Diff line change
@@ -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 {}
Loading