Skip to content

Commit

Permalink
Change generate invitation and sharing token
Browse files Browse the repository at this point in the history
  • Loading branch information
devleejb committed Jan 22, 2024
1 parent 95adbc3 commit f056f81
Show file tree
Hide file tree
Showing 16 changed files with 84 additions and 87 deletions.
9 changes: 9 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@prisma/client": "^5.8.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"moment": "^2.30.1",
"passport-github": "^1.1.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.1.13",
Expand Down
5 changes: 3 additions & 2 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ model Document {

model WorkspaceInvitationToken {
id String @id @default(auto()) @map("_id") @db.ObjectId
token String
token String @unique
workspace Workspace @relation(fields: [workspaceId], references: [id])
workspaceId String @map("workspace_id") @db.ObjectId
expiredAt DateTime? @map("expired_at")
Expand All @@ -77,7 +77,8 @@ model WorkspaceInvitationToken {

model DocumentSharingToken {
id String @id @default(auto()) @map("_id") @db.ObjectId
token String
token String @unique
role String
document Document @relation(fields: [documentId], references: [id])
documentId String @map("document_id") @db.ObjectId
expiredAt DateTime? @map("expired_at")
Expand Down
13 changes: 1 addition & 12 deletions backend/src/documents/documents.module.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
import { Module } from "@nestjs/common";
import { DocumentsController } from "./documents.controller";
import { DocumentsService } from "./documents.service";
import { JwtModule } from "@nestjs/jwt";
import { ConfigService } from "@nestjs/config";
import { PrismaService } from "src/db/prisma.service";

@Module({
imports: [
JwtModule.registerAsync({
useFactory: async (configService: ConfigService) => {
return {
secret: configService.get<string>("JWT_SHARING_SECRET"),
};
},
inject: [ConfigService],
}),
],
imports: [],
controllers: [DocumentsController],
providers: [DocumentsService, PrismaService],
})
Expand Down
18 changes: 9 additions & 9 deletions backend/src/documents/documents.service.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { Injectable, NotFoundException, UnauthorizedException } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { Document } from "@prisma/client";
import { PrismaService } from "src/db/prisma.service";
import { SharingPayload } from "src/utils/types/sharing.type";
import { FindDocumentFromSharingTokenResponse } from "./types/find-document-from-sharing-token-response.type";
import { ShareRoleEnum } from "src/utils/constants/share-role";

@Injectable()
export class DocumentsService {
constructor(
private prismaService: PrismaService,
private jwtService: JwtService
) {}
constructor(private prismaService: PrismaService) {}

async findDocumentFromSharingToken(
sharingToken: string
): Promise<FindDocumentFromSharingTokenResponse> {
let documentId: string, role: ShareRoleEnum;

try {
const payload = this.jwtService.verify<SharingPayload>(sharingToken);
documentId = payload.documentId;
role = payload.role;
const documentSharingToken =
await this.prismaService.documentSharingToken.findFirstOrThrow({
where: {
token: sharingToken,
},
});
documentId = documentSharingToken.documentId;
role = documentSharingToken.role as ShareRoleEnum;
} catch (e) {
throw new UnauthorizedException("Invalid sharing token");
}
Expand Down
3 changes: 3 additions & 0 deletions backend/src/utils/functions/random-string.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const generateRandomKey = () => {
return Math.random().toString(36).substring(7);
};
6 changes: 0 additions & 6 deletions backend/src/utils/types/sharing.type.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ export class CreateWorkspaceDocumentShareTokenDto {
@ApiProperty({ type: Date, description: "Share link expiration date" })
@Type(() => Date)
@IsDate()
expirationDate: Date;
expiredAt: Date;
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export class WorkspaceDocumentsController {
workspaceId,
documentId,
createWorkspaceDocumentShareTokenDto.role,
createWorkspaceDocumentShareTokenDto.expirationDate
createWorkspaceDocumentShareTokenDto.expiredAt
);
}
}
13 changes: 1 addition & 12 deletions backend/src/workspace-documents/workspace-documents.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,9 @@ import { Module } from "@nestjs/common";
import { WorkspaceDocumentsService } from "./workspace-documents.service";
import { WorkspaceDocumentsController } from "./workspace-documents.controller";
import { PrismaService } from "src/db/prisma.service";
import { JwtModule } from "@nestjs/jwt";
import { ConfigService } from "@nestjs/config";

@Module({
imports: [
JwtModule.registerAsync({
useFactory: async (configService: ConfigService) => {
return {
secret: configService.get<string>("JWT_SHARING_SECRET"),
};
},
inject: [ConfigService],
}),
],
imports: [],
providers: [WorkspaceDocumentsService, PrismaService],
controllers: [WorkspaceDocumentsController],
})
Expand Down
22 changes: 10 additions & 12 deletions backend/src/workspace-documents/workspace-documents.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@ import { Injectable, NotFoundException } from "@nestjs/common";
import { Document, Prisma } from "@prisma/client";
import { PrismaService } from "src/db/prisma.service";
import { FindWorkspaceDocumentsResponse } from "./types/find-workspace-documents-response.type";
import { JwtService } from "@nestjs/jwt";
import { CreateWorkspaceDocumentShareTokenResponse } from "./types/create-workspace-document-share-token-response.type";
import { ShareRole } from "src/utils/types/share-role.type";
import slugify from "slugify";
import { generateRandomKey } from "src/utils/functions/random-string";

@Injectable()
export class WorkspaceDocumentsService {
constructor(
private prismaService: PrismaService,
private jwtService: JwtService
) {}
constructor(private prismaService: PrismaService) {}

async create(userId: string, workspaceId: string, title: string) {
try {
Expand Down Expand Up @@ -134,18 +131,19 @@ export class WorkspaceDocumentsService {
throw new NotFoundException();
}

const sharingToken = this.jwtService.sign(
{
const token = generateRandomKey();

await this.prismaService.documentSharingToken.create({
data: {
documentId: document.id,
token,
expiredAt: expirationDate,
role,
},
{
expiresIn: expirationDate.getTime() - Date.now(),
}
);
});

return {
sharingToken,
sharingToken: token,
};
}
}
8 changes: 8 additions & 0 deletions backend/src/workspaces/dto/create-invitation-token.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";

export class CreateInvitationTokenDto {
@ApiProperty({ description: "Expiration date of invitation token", type: Date })
@Type(() => Date)
expiredAt: Date;
}
4 changes: 0 additions & 4 deletions backend/src/workspaces/types/inviation-token-payload.type.ts

This file was deleted.

10 changes: 8 additions & 2 deletions backend/src/workspaces/workspaces.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { FindWorkspacesResponse } from "./types/find-workspaces-response.type";
import { CreateInvitationTokenResponse } from "./types/create-inviation-token-response.type";
import { JoinWorkspaceDto } from "./dto/join-workspace.dto";
import { JoinWorkspaceResponse } from "./types/join-workspace-response.type";
import { CreateInvitationTokenDto } from "./dto/create-invitation-token.dto";

@ApiTags("Workspaces")
@ApiBearerAuth()
Expand Down Expand Up @@ -115,9 +116,14 @@ export class WorkspacesController {
})
async createInvitationToken(
@Req() req: AuthroizedRequest,
@Param("workspace_id") workspaceId: string
@Param("workspace_id") workspaceId: string,
@Body() createInvitationTokenDto: CreateInvitationTokenDto
): Promise<CreateInvitationTokenResponse> {
return this.workspacesService.createInvitationToken(req.user.id, workspaceId);
return this.workspacesService.createInvitationToken(
req.user.id,
workspaceId,
createInvitationTokenDto.expiredAt
);
}

@Post("join")
Expand Down
14 changes: 1 addition & 13 deletions backend/src/workspaces/workspaces.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,9 @@ import { Module } from "@nestjs/common";
import { WorkspacesController } from "./workspaces.controller";
import { WorkspacesService } from "./workspaces.service";
import { PrismaService } from "src/db/prisma.service";
import { JwtModule } from "@nestjs/jwt";
import { ConfigService } from "@nestjs/config";

@Module({
imports: [
JwtModule.registerAsync({
useFactory: async (configService: ConfigService) => {
return {
signOptions: { expiresIn: "12h" },
secret: configService.get<string>("JWT_INVITATION_SECRET"),
};
},
inject: [ConfigService],
}),
],
imports: [],
controllers: [WorkspacesController],
providers: [WorkspacesService, PrismaService],
})
Expand Down
41 changes: 28 additions & 13 deletions backend/src/workspaces/workspaces.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,15 @@ import { Injectable, NotFoundException, UnauthorizedException } from "@nestjs/co
import { Prisma, Workspace } from "@prisma/client";
import { PrismaService } from "src/db/prisma.service";
import { FindWorkspacesResponse } from "./types/find-workspaces-response.type";
import { JwtService } from "@nestjs/jwt";
import { CreateInvitationTokenResponse } from "./types/create-inviation-token-response.type";
import { InvitationTokenPayload } from "./types/inviation-token-payload.type";
import { WorkspaceRoleConstants } from "src/utils/constants/auth-role";
import slugify from "slugify";
import { generateRandomKey } from "src/utils/functions/random-string";
import moment from "moment";

@Injectable()
export class WorkspacesService {
constructor(
private prismaService: PrismaService,
private jwtService: JwtService
) {}
constructor(private prismaService: PrismaService) {}

async create(userId: string, title: string): Promise<Workspace> {
let slug = slugify(title);
Expand Down Expand Up @@ -103,7 +100,8 @@ export class WorkspacesService {

async createInvitationToken(
userId: string,
workspaceId: string
workspaceId: string,
expiredAt: Date
): Promise<CreateInvitationTokenResponse> {
try {
await this.prismaService.userWorkspace.findFirstOrThrow({
Expand All @@ -116,23 +114,40 @@ export class WorkspacesService {
throw new NotFoundException();
}

const invitationToken = this.jwtService.sign({
sub: userId,
workspaceId,
const token = generateRandomKey();

await this.prismaService.workspaceInvitationToken.create({
data: {
workspaceId,
token,
expiredAt,
},
});

return {
invitationToken,
invitationToken: token,
};
}

async join(userId: string, invitationToken: string) {
let workspaceId: string;

try {
const payload = this.jwtService.verify<InvitationTokenPayload>(invitationToken);
const workspaceInvitationToken =
await this.prismaService.workspaceInvitationToken.findFirst({
where: {
token: invitationToken,
},
});

workspaceId = workspaceInvitationToken.workspaceId;

workspaceId = payload.workspaceId;
if (
workspaceInvitationToken.expiredAt &&
moment().isAfter(workspaceInvitationToken.expiredAt)
) {
throw new Error();
}
} catch (err) {
throw new UnauthorizedException("Invitation token is invalid or expired.");
}
Expand Down

0 comments on commit f056f81

Please sign in to comment.