Skip to content

Commit

Permalink
feat: add new config strategy to backend
Browse files Browse the repository at this point in the history
  • Loading branch information
stonith404 committed Nov 28, 2022
1 parent 13f98cc commit 1b5e53f
Show file tree
Hide file tree
Showing 19 changed files with 304 additions and 50 deletions.
3 changes: 3 additions & 0 deletions backend/package.json
Expand Up @@ -9,6 +9,9 @@
"format": "prettier --write 'src/**/*.ts'",
"test:system": "npx prisma migrate reset -f && nest start & sleep 10 && newman run ./test/system/newman-system-tests.json"
},
"prisma": {
"seed": "ts-node prisma/seed/config.seed.ts"
},
"dependencies": {
"@nestjs/common": "^9.1.2",
"@nestjs/config": "^2.2.0",
Expand Down
11 changes: 11 additions & 0 deletions backend/prisma/schema.prisma
Expand Up @@ -77,3 +77,14 @@ model ShareSecurity {
shareId String? @unique
share Share? @relation(fields: [shareId], references: [id], onDelete: Cascade)
}

model Config {
updatedAt DateTime @updatedAt
key String @id
type String
value String?
default String
secret Boolean @default(true)
locked Boolean @default(false)
}
118 changes: 118 additions & 0 deletions backend/prisma/seed/config.seed.ts
@@ -0,0 +1,118 @@
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

const configVariables = [
{
key: "setupFinished",
type: "boolean",
default: "false",
secret: false,
locked: true
},
{
key: "appUrl",
type: "string",
default: "http://localhost:3000",
secret: false,
},
{
key: "showHomePage",
type: "boolean",
default: "true",
secret: false,
},
{
key: "allowRegistration",
type: "boolean",
default: "true",
secret: false,
},
{
key: "allowUnauthenticatedShares",
type: "boolean",
default: "false",
secret: false,
},
{
key: "maxFileSize",
type: "number",
default: "1000000000",
secret: false,
},
{
key: "jwtSecret",
type: "string",
default: "long-random-string",
locked: true
},
{
key: "emailRecipientsEnabled",
type: "boolean",
default: "false",
secret: false,
},
{
key: "smtpHost",
type: "string",
default: "",
},
{
key: "smtpPort",
type: "number",
default: "",
},
{
key: "smtpEmail",
type: "string",
default: "",
},
{
key: "smtpPassword",
type: "string",
default: "",
},
];

async function main() {
for (const variable of configVariables) {
const existingConfigVariable = await prisma.config.findUnique({
where: { key: variable.key },
});

// Create a new config variable if it doesn't exist
if (!existingConfigVariable) {
await prisma.config.create({
data: variable,
});
} else {
// Update the config variable if the default value has changed
if (existingConfigVariable.default != variable.default) {
await prisma.config.update({
where: { key: variable.key },
data: { default: variable.default },
});
}
}
}

// Delete the config variable if it doesn't exist anymore
const configVariablesFromDatabase = await prisma.config.findMany();

for (const configVariableFromDatabase of configVariablesFromDatabase) {
if (!configVariables.find((v) => v.key == configVariableFromDatabase.key)) {
await prisma.config.delete({
where: { key: configVariableFromDatabase.key },
});
}
}
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
16 changes: 13 additions & 3 deletions backend/src/app.module.ts
@@ -1,19 +1,21 @@
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";

import { ScheduleModule } from "@nestjs/schedule";
import { AuthModule } from "./auth/auth.module";
import { JobsService } from "./jobs/jobs.service";

import { APP_GUARD } from "@nestjs/core";
import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler";
import { ConfigModule } from "./config/config.module";
import { ConfigService } from "./config/config.service";
import { EmailModule } from "./email/email.module";
import { FileController } from "./file/file.controller";
import { FileModule } from "./file/file.module";
import { PrismaModule } from "./prisma/prisma.module";
import { PrismaService } from "./prisma/prisma.service";
import { ShareController } from "./share/share.controller";
import { ShareModule } from "./share/share.module";
import { UserController } from "./user/user.controller";
import { EmailModule } from "./email/email.module";

@Module({
imports: [
Expand All @@ -22,16 +24,24 @@ import { EmailModule } from "./email/email.module";
FileModule,
EmailModule,
PrismaModule,
ConfigModule.forRoot({ isGlobal: true }),
ConfigModule,
ThrottlerModule.forRoot({
ttl: 60,
limit: 100,
}),
ScheduleModule.forRoot(),
],
providers: [
ConfigService,
PrismaService,
JobsService,
{
provide: "CONFIG_VARIABLES",
useFactory: async (prisma: PrismaService) => {
return await prisma.config.findMany();
},
inject: [PrismaService],
},
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
Expand Down
6 changes: 3 additions & 3 deletions backend/src/auth/auth.controller.ts
Expand Up @@ -5,8 +5,8 @@ import {
HttpCode,
Post,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Throttle } from "@nestjs/throttler";
import { ConfigService } from "src/config/config.service";
import { AuthService } from "./auth.service";
import { AuthRegisterDTO } from "./dto/authRegister.dto";
import { AuthSignInDTO } from "./dto/authSignIn.dto";
Expand All @@ -21,8 +21,8 @@ export class AuthController {

@Throttle(10, 5 * 60)
@Post("signUp")
signUp(@Body() dto: AuthRegisterDTO) {
if (this.config.get("ALLOW_REGISTRATION") == "false")
async signUp(@Body() dto: AuthRegisterDTO) {
if (!this.config.get("allowRegistration"))
throw new ForbiddenException("Registration is not allowed");
return this.authService.signUp(dto);
}
Expand Down
4 changes: 2 additions & 2 deletions backend/src/auth/auth.service.ts
Expand Up @@ -3,12 +3,12 @@ import {
Injectable,
UnauthorizedException,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { JwtService } from "@nestjs/jwt";
import { User } from "@prisma/client";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
import * as argon from "argon2";
import * as moment from "moment";
import { ConfigService } from "src/config/config.service";
import { PrismaService } from "src/prisma/prisma.service";
import { AuthRegisterDTO } from "./dto/authRegister.dto";
import { AuthSignInDTO } from "./dto/authSignIn.dto";
Expand Down Expand Up @@ -68,7 +68,7 @@ export class AuthService {
},
{
expiresIn: "15min",
secret: this.config.get("JWT_SECRET"),
secret: this.config.get("jwtSecret"),
}
);
}
Expand Down
8 changes: 5 additions & 3 deletions backend/src/auth/guard/jwt.guard.ts
@@ -1,15 +1,17 @@
import { ExecutionContext } from "@nestjs/common";
import { ExecutionContext, Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { ConfigService } from "src/config/config.service";

@Injectable()
export class JwtGuard extends AuthGuard("jwt") {
constructor() {
constructor(private config: ConfigService) {
super();
}
async canActivate(context: ExecutionContext): Promise<boolean> {
try {
return (await super.canActivate(context)) as boolean;
} catch {
return process.env.ALLOW_UNAUTHENTICATED_SHARES == "true";
return this.config.get("allowUnauthenticatedShares");
}
}
}
9 changes: 6 additions & 3 deletions backend/src/auth/strategy/jwt.strategy.ts
@@ -1,24 +1,27 @@
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { PassportStrategy } from "@nestjs/passport";
import { User } from "@prisma/client";
import { ExtractJwt, Strategy } from "passport-jwt";
import { ConfigService } from "src/config/config.service";
import { PrismaService } from "src/prisma/prisma.service";

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(config: ConfigService, private prisma: PrismaService) {
console.log(config.get("jwtSecret"));
config.get("jwtSecret");
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.get("JWT_SECRET"),
secretOrKey: config.get("jwtSecret"),
});
}

async validate(payload: { sub: string }) {
console.log("vali");
const user: User = await this.prisma.user.findUnique({
where: { id: payload.sub },
});

console.log({ user });
return user;
}
}
18 changes: 18 additions & 0 deletions backend/src/config/config.controller.ts
@@ -0,0 +1,18 @@
import { Controller, Get } from "@nestjs/common";
import { ConfigService } from "./config.service";
import { ConfigDTO } from "./dto/config.dto";

@Controller("configs")
export class ConfigController {
constructor(private configService: ConfigService) {}

@Get()
async list() {
return new ConfigDTO().fromList(await this.configService.list())
}

@Get("admin")
async listForAdmin() {
return await this.configService.listForAdmin();
}
}
21 changes: 21 additions & 0 deletions backend/src/config/config.module.ts
@@ -0,0 +1,21 @@
import { Global, Module } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service";
import { ConfigController } from "./config.controller";
import { ConfigService } from "./config.service";

@Global()
@Module({
providers: [
{
provide: "CONFIG_VARIABLES",
useFactory: async (prisma: PrismaService) => {
return await prisma.config.findMany();
},
inject: [PrismaService],
},
ConfigService,
],
controllers: [ConfigController],
exports: [ConfigService],
})
export class ConfigModule {}
41 changes: 41 additions & 0 deletions backend/src/config/config.service.ts
@@ -0,0 +1,41 @@
import { Inject, Injectable } from "@nestjs/common";
import { Config } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service";

@Injectable()
export class ConfigService {
constructor(
@Inject("CONFIG_VARIABLES") private configVariables: Config[],
private prisma: PrismaService
) {}

get(key: string): any {
const configVariable = this.configVariables.filter(
(variable) => variable.key == key
)[0];

if (!configVariable) throw new Error(`Config variable ${key} not found`);

const value = configVariable.value ?? configVariable.default;

if (configVariable.type == "number") return parseInt(value);
if (configVariable.type == "boolean") return value == "true";
if (configVariable.type == "string") return value;
}

async listForAdmin() {
return await this.prisma.config.findMany();
}

async list() {
const configVariables = await this.prisma.config.findMany({
where: { secret: { equals: false } },
});

return configVariables.map((configVariable) => {
if (!configVariable.value) configVariable.value = configVariable.default;

return configVariable;
});
}
}
18 changes: 18 additions & 0 deletions backend/src/config/dto/config.dto.ts
@@ -0,0 +1,18 @@
import { Expose, plainToClass } from "class-transformer";

export class ConfigDTO {
@Expose()
key: string;

@Expose()
value: string;

@Expose()
type: string;

fromList(partial: Partial<ConfigDTO>[]) {
return partial.map((part) =>
plainToClass(ConfigDTO, part, { excludeExtraneousValues: true })
);
}
}

0 comments on commit 1b5e53f

Please sign in to comment.