Skip to content

Commit

Permalink
feat: add setup wizard
Browse files Browse the repository at this point in the history
  • Loading branch information
stonith404 committed Dec 1, 2022
1 parent 493705e commit b579b8f
Show file tree
Hide file tree
Showing 32 changed files with 687 additions and 177 deletions.
@@ -0,0 +1,37 @@
/*
Warnings:
- You are about to drop the column `firstName` on the `User` table. All the data in the column will be lost.
- You are about to drop the column `lastName` on the `User` table. All the data in the column will be lost.
- Added the required column `username` to the `User` table without a default value. This is not possible if the table is not empty.
*/
-- CreateTable
CREATE TABLE "Config" (
"updatedAt" DATETIME NOT NULL,
"key" TEXT NOT NULL PRIMARY KEY,
"type" TEXT NOT NULL,
"value" TEXT NOT NULL,
"description" TEXT NOT NULL,
"secret" BOOLEAN NOT NULL DEFAULT true,
"locked" BOOLEAN NOT NULL DEFAULT false
);

-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_User" (
"id" TEXT NOT NULL PRIMARY KEY,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"username" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password" TEXT NOT NULL,
"isAdmin" BOOLEAN NOT NULL DEFAULT false
);
INSERT INTO "new_User" ("createdAt", "email", "id", "password", "updatedAt") SELECT "createdAt", "email", "id", "password", "updatedAt" FROM "User";
DROP TABLE "User";
ALTER TABLE "new_User" RENAME TO "User";
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;
21 changes: 10 additions & 11 deletions backend/prisma/schema.prisma
Expand Up @@ -12,11 +12,10 @@ model User {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
email String @unique
password String
isAdministrator Boolean @default(false)
firstName String?
lastName String?
username String @unique
email String @unique
password String
isAdmin Boolean @default(false)
shares Share[]
refreshTokens RefreshToken[]
Expand Down Expand Up @@ -81,10 +80,10 @@ model ShareSecurity {
model Config {
updatedAt DateTime @updatedAt
key String @id
type String
value String?
default String
secret Boolean @default(true)
locked Boolean @default(false)
key String @id
type String
value String
description String
secret Boolean @default(true)
locked Boolean @default(false)
}
81 changes: 1 addition & 80 deletions backend/prisma/seed/config.seed.ts
@@ -1,79 +1,8 @@
import { PrismaClient } from "@prisma/client";
import configVariables from "../../src/configVariables";

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({
Expand All @@ -85,14 +14,6 @@ async function main() {
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 },
});
}
}
}

Expand Down
22 changes: 21 additions & 1 deletion backend/src/app.module.ts
@@ -1,11 +1,13 @@
import { Module } from "@nestjs/common";
import { HttpException, HttpStatus, Module } from "@nestjs/common";

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

import { APP_GUARD } from "@nestjs/core";
import { MulterModule } from "@nestjs/platform-express";
import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler";
import { Request } from "express";
import { ConfigModule } from "./config/config.module";
import { ConfigService } from "./config/config.service";
import { EmailModule } from "./email/email.module";
Expand All @@ -25,6 +27,24 @@ import { UserController } from "./user/user.controller";
EmailModule,
PrismaModule,
ConfigModule,
MulterModule.registerAsync({
useFactory: (config: ConfigService) => ({
fileFilter: (req: Request, file, cb) => {
const maxFileSize = config.get("maxFileSize");
const requestFileSize = parseInt(req.headers["content-length"]);
const isValidFileSize = requestFileSize <= maxFileSize;
cb(
!isValidFileSize &&
new HttpException(
`File must be smaller than ${maxFileSize} bytes`,
HttpStatus.PAYLOAD_TOO_LARGE
),
isValidFileSize
);
},
}),
inject: [ConfigService],
}),
ThrottlerModule.forRoot({
ttl: 60,
limit: 100,
Expand Down
14 changes: 11 additions & 3 deletions backend/src/auth/auth.service.ts
Expand Up @@ -27,7 +27,9 @@ export class AuthService {
const user = await this.prisma.user.create({
data: {
email: dto.email,
username: dto.username,
password: hash,
isAdmin: !this.config.get("setupFinished"),
},
});

Expand All @@ -38,16 +40,22 @@ export class AuthService {
} catch (e) {
if (e instanceof PrismaClientKnownRequestError) {
if (e.code == "P2002") {
throw new BadRequestException("Credentials taken");
const duplicatedField: string = e.meta.target[0];
throw new BadRequestException(
`A user with this ${duplicatedField} already exists`
);
}
}
}
}

async signIn(dto: AuthSignInDTO) {
const user = await this.prisma.user.findUnique({
if (!dto.email && !dto.username)
throw new BadRequestException("Email or username is required");

const user = await this.prisma.user.findFirst({
where: {
email: dto.email,
OR: [{ email: dto.email }, { username: dto.username }],
},
});

Expand Down
16 changes: 15 additions & 1 deletion backend/src/auth/dto/authRegister.dto.ts
@@ -1,3 +1,17 @@
import { PickType } from "@nestjs/swagger";
import { Expose } from "class-transformer";
import { IsEmail, Length, Matches } from "class-validator";
import { UserDTO } from "src/user/dto/user.dto";

export class AuthRegisterDTO extends UserDTO {}
export class AuthRegisterDTO extends PickType(UserDTO, ["password"] as const) {
@Expose()
@Matches("^[a-zA-Z0-9_.]*$", undefined, {
message: "Username can only contain letters, numbers, dots and underscores",
})
@Length(3, 32)
username: string;

@Expose()
@IsEmail()
email: string;
}
1 change: 1 addition & 0 deletions backend/src/auth/dto/authSignIn.dto.ts
Expand Up @@ -2,6 +2,7 @@ import { PickType } from "@nestjs/swagger";
import { UserDTO } from "src/user/dto/user.dto";

export class AuthSignInDTO extends PickType(UserDTO, [
"username",
"email",
"password",
] as const) {}
6 changes: 4 additions & 2 deletions backend/src/auth/guard/isAdmin.guard.ts
Expand Up @@ -3,9 +3,11 @@ import { User } from "@prisma/client";

@Injectable()
export class AdministratorGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
canActivate(context: ExecutionContext) {
const { user }: { user: User } = context.switchToHttp().getRequest();

if (!user) return false;
return user.isAdministrator;

return user.isAdmin;
}
}
3 changes: 0 additions & 3 deletions backend/src/auth/strategy/jwt.strategy.ts
Expand Up @@ -8,7 +8,6 @@ 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(),
Expand All @@ -17,11 +16,9 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
}

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;
}
}
21 changes: 18 additions & 3 deletions backend/src/config/config.controller.ts
@@ -1,5 +1,14 @@
import { Body, Controller, Get, Param, Patch, UseGuards } from "@nestjs/common";
import {
Body,
Controller,
Get,
Param,
Patch,
Post,
UseGuards,
} from "@nestjs/common";
import { AdministratorGuard } from "src/auth/guard/isAdmin.guard";
import { JwtGuard } from "src/auth/guard/jwt.guard";
import { ConfigService } from "./config.service";
import { AdminConfigDTO } from "./dto/adminConfig.dto";
import { ConfigDTO } from "./dto/config.dto";
Expand All @@ -15,18 +24,24 @@ export class ConfigController {
}

@Get("admin")
@UseGuards(AdministratorGuard)
@UseGuards(JwtGuard, AdministratorGuard)
async listForAdmin() {
return new AdminConfigDTO().fromList(
await this.configService.listForAdmin()
);
}

@Patch("admin/:key")
@UseGuards(AdministratorGuard)
@UseGuards(JwtGuard, AdministratorGuard)
async update(@Param("key") key: string, @Body() data: UpdateConfigDTO) {
return new AdminConfigDTO().from(
await this.configService.update(key, data.value)
);
}

@Post("admin/finishSetup")
@UseGuards(JwtGuard, AdministratorGuard)
async finishSetup() {
return await this.configService.finishSetup();
}
}

0 comments on commit b579b8f

Please sign in to comment.