Skip to content

Commit

Permalink
Merge pull request #1066 from AletheiaFact/implement-namespace-feature
Browse files Browse the repository at this point in the history
  • Loading branch information
thesocialdev authored Oct 30, 2023
2 parents 75c9b09 + 13c2e93 commit a2c0d71
Show file tree
Hide file tree
Showing 142 changed files with 2,274 additions and 400 deletions.
Binary file modified .yarn/install-state.gz
Binary file not shown.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:18.14.0-alpine AS package
FROM node:18.17.0-alpine AS package

ARG NEXT_PUBLIC_UMAMI_SITE_ID
ARG NEXT_PUBLIC_RECAPTCHA_SITEKEY
Expand Down Expand Up @@ -33,7 +33,7 @@ RUN NEXT_PUBLIC_UMAMI_SITE_ID=$NEXT_PUBLIC_UMAMI_SITE_ID \
NEXT_PUBLIC_RECAPTCHA_SITEKEY=$NEXT_PUBLIC_RECAPTCHA_SITEKEY \
yarn build

FROM node:18.14.0-alpine
FROM node:18.17.0-alpine

LABEL maintainer="Giovanni Rossini <[email protected]>"

Expand Down
7 changes: 7 additions & 0 deletions migrations/20231020152642-adding-name-space-property.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Db } from "mongodb";

export async function up(db: Db) {
await db
.collection("claims")
.updateMany({}, { $set: { nameSpace: "main" } });
}
16 changes: 16 additions & 0 deletions migrations/20231021165513-update-user-role-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Db } from "mongodb";

export async function up(db: Db) {
const usersCursor = await db.collection("users").find();

while (await usersCursor.hasNext()) {
const user = await usersCursor.next();

await db
.collection("users")
.updateOne(
{ _id: user._id },
{ $set: { role: { main: user.role } } }
);
}
}
3 changes: 2 additions & 1 deletion public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"sourcesRequiredFieldError": "Source is required",
"supportEmail": "[email protected]",
"contactEmail": "[email protected]",
"captchaError": "There was an error validating the captcha"
"captchaError": "There was an error validating the captcha",
"change": "Change"
}
3 changes: 2 additions & 1 deletion public/locales/en/menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
"adminItem": "Admin",
"personalityItem": "Personalities",
"claimItem": "Claims",
"kanbanItem": "Kanban"
"kanbanItem": "Kanban",
"nameSpaceItem": "Namespace"
}
8 changes: 8 additions & 0 deletions public/locales/en/namespaces.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"nameColumn": "Name",
"title": "Namespaces area",
"addNameSpace": "Add namespace",
"editNameSpace": "Edit namespace",
"nameSpaceSaved": "Namespace was saved successfully",
"selectNameSpaces": "Select the namespaces"
}
3 changes: 2 additions & 1 deletion public/locales/pt/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"sourcesRequiredFieldError": "Fonte obrigatória",
"supportEmail": "[email protected]",
"contactEmail": "[email protected]",
"captchaError": "Erro na validação do captcha"
"captchaError": "Erro na validação do captcha",
"change": "Mudar"
}
3 changes: 2 additions & 1 deletion public/locales/pt/menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
"adminItem": "Admin",
"personalityItem": "Personalidades",
"claimItem": "Afirmações",
"kanbanItem": "Kanban"
"kanbanItem": "Kanban",
"nameSpaceItem": "Namespace"
}
9 changes: 9 additions & 0 deletions public/locales/pt/namespaces.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"nameColumn": "Nome",
"title": "Área de namespaces",
"addNameSpace": "Adicionar namespace",
"editNameSpace": "Editar namespace",
"nameSpaceSaved": "Namespace salvo com sucesso",
"selectNameSpaces": "Selecione os namespaces"
}

8 changes: 8 additions & 0 deletions server/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import { BadgeModule } from "./badge/badge.module";
import { EditorParseModule } from "./editor-parse/editor-parse.module";
import { NotificationModule } from "./notifications/notifications.module";
import { CommentModule } from "./claim-review-task/comment/comment.module";
import { NameSpaceModule } from "./auth/name-space/name-space.module";
import { NameSpaceGuard } from "./auth/name-space/name-space.guard";

@Module({})
export class AppModule implements NestModule {

Check warning on line 56 in server/app.module.ts

View workflow job for this annotation

GitHub Actions / build (18.14.0)

'AppModule' is defined but never used
Expand Down Expand Up @@ -106,6 +108,7 @@ export class AppModule implements NestModule {
EditorParseModule,
NotificationModule,
CommentModule,
NameSpaceModule,
];
if (options.config.feature_flag) {
imports.push(
Expand Down Expand Up @@ -141,6 +144,11 @@ export class AppModule implements NestModule {
provide: APP_GUARD,
useExisting: SessionGuard,
},
{
provide: APP_GUARD,
useExisting: NameSpaceGuard,
},
NameSpaceGuard,
SessionGuard,
],
};
Expand Down
7 changes: 6 additions & 1 deletion server/auth/ability/abilities.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Reflector } from "@nestjs/core";
import { Configuration, FrontendApi } from "@ory/client";
import { CHECK_ABILITY, RequiredRule } from "./ability.decorator";
import { AbilityFactory } from "./ability.factory";
import { NameSpaceEnum } from "../../auth/name-space/schemas/name-space.schema";

@Injectable()
export class AbilitiesGuard implements CanActivate {

Check warning on line 16 in server/auth/ability/abilities.guard.ts

View workflow job for this annotation

GitHub Actions / build (18.14.0)

'AbilitiesGuard' is defined but never used
Expand All @@ -36,7 +37,11 @@ export class AbilitiesGuard implements CanActivate {
cookie: request.header("Cookie"),
});
const user = session.identity.traits;
const ability = this.caslAbilityFactor.defineAbility(user);
const nameSpaceSlug = request.params.namespace || NameSpaceEnum.Main;
const ability = this.caslAbilityFactor.defineAbility(
user,
nameSpaceSlug
);
try {
rules.forEach((rule) =>
ForbiddenError.from(ability).throwUnlessCan(
Expand Down
11 changes: 7 additions & 4 deletions server/auth/ability/ability.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,19 @@ export type AppAbility = Ability<[Action, Subjects]>;

@Injectable()
export class AbilityFactory {
defineAbility(user: User) {
defineAbility(user: User, nameSpace: string) {
const { can, cannot, build } = new AbilityBuilder(
Ability as AbilityClass<AppAbility>
);

if (user.role === Roles.Admin || user.role === Roles.SuperAdmin) {
if (
user.role[nameSpace] === Roles.Admin ||
user.role[nameSpace] === Roles.SuperAdmin
) {
can(Action.Manage, "all");
} else if (
user.role === Roles.FactChecker ||
user.role === Roles.Reviewer
user.role[nameSpace] === Roles.FactChecker ||
user.role[nameSpace] === Roles.Reviewer
) {
can(Action.Read, "all");
can(Action.Update, "all");
Expand Down
15 changes: 15 additions & 0 deletions server/auth/name-space/dto/create-namespace.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsArray, IsNotEmpty, IsOptional, IsString } from "class-validator";
import { User } from "../../../users/schemas/user.schema";

export class CreateNameSpaceDTO {
@IsString()
@IsNotEmpty()
@ApiProperty()
name: string;

@IsArray()
@IsOptional()
@ApiProperty()
users: User[];
}
4 changes: 4 additions & 0 deletions server/auth/name-space/dto/update-name-space.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PartialType } from "@nestjs/mapped-types";
import { CreateNameSpaceDTO } from "./create-namespace.dto";

export class UpdateNameSpaceDTO extends PartialType(CreateNameSpaceDTO) {}
65 changes: 65 additions & 0 deletions server/auth/name-space/name-space.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
Body,
Controller,
Get,
Param,
Post,
Put,
Req,
Res,
UseGuards,
} from "@nestjs/common";
import { NameSpaceService } from "./name-space.service";
import type { Request, Response } from "express";
import { ApiTags } from "@nestjs/swagger";
import { UsersService } from "../../users/users.service";
import { parse } from "url";
import { ViewService } from "../../view/view.service";
import { CreateNameSpaceDTO } from "./dto/create-namespace.dto";
import { UpdateNameSpaceDTO } from "./dto/update-name-space.dto";
import {
AdminUserAbility,
CheckAbilities,
} from "../../auth/ability/ability.decorator";
import { AbilitiesGuard } from "../../auth/ability/abilities.guard";

@Controller()
export class NameSpaceController {
constructor(
private nameSpaceService: NameSpaceService,
private usersService: UsersService,
private viewService: ViewService
) {}

@ApiTags("name-space")
@Post("api/name-space")
@UseGuards(AbilitiesGuard)
@CheckAbilities(new AdminUserAbility())
async create(@Body() namespace: CreateNameSpaceDTO) {
return await this.nameSpaceService.create(namespace);
}

@ApiTags("name-space")
@Put("api/name-space/:id")
@UseGuards(AbilitiesGuard)
@CheckAbilities(new AdminUserAbility())
async update(@Param("id") id, @Body() namespace: UpdateNameSpaceDTO) {
return await this.nameSpaceService.update(id, namespace);
}

@ApiTags("name-space")
@Get("admin/name-spaces")
public async adminNameSpaces(@Req() req: Request, @Res() res: Response) {
const nameSpaces = await this.nameSpaceService.listAll();
const users = await this.usersService.findAll({});
const parsedUrl = parse(req.url, true);
await this.viewService
.getNextServer()
.render(
req,
res,
"/admin-namespaces",
Object.assign(parsedUrl.query, { nameSpaces, users })
);
}
}
60 changes: 60 additions & 0 deletions server/auth/name-space/name-space.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
CanActivate,
ExecutionContext,
Injectable,
NotFoundException,
UnauthorizedException,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Configuration, FrontendApi } from "@ory/client";
import { NameSpaceService } from "./name-space.service";

@Injectable()
export class NameSpaceGuard implements CanActivate {
constructor(
private configService: ConfigService,
private nameSpaceService: NameSpaceService
) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const oryConfig = new Configuration({
basePath: this.configService.get<string>("ory.url"),
accessToken: this.configService.get<string>("access_token"),
});

const namespaceSlug = request.params.namespace;
const cookie = request.header("Cookie");

if (!cookie && namespaceSlug) {
throw new UnauthorizedException();
}

if (namespaceSlug && cookie) {
const { data: session } = await new FrontendApi(
oryConfig
).toSession({ cookie });

const user_id = session.identity.traits.user_id;
const namespace = await this.nameSpaceService.findOne({
slug: namespaceSlug,
});

if (!namespace) {
throw new NotFoundException();
}

const userHasAccess = namespace.users.some(
//@ts-ignore
(user) => user._id.toString() === user_id
);
if (!userHasAccess) {
throw new UnauthorizedException();
}

return true;
}

return true;
}
}
30 changes: 30 additions & 0 deletions server/auth/name-space/name-space.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Module } from "@nestjs/common";
import { MongooseModule } from "@nestjs/mongoose";
import { NameSpace, NameSpaceSchema } from "./schemas/name-space.schema";
import { NameSpaceController } from "./name-space.controller";
import { NameSpaceService } from "./name-space.service";
import { UsersModule } from "../../users/users.module";
import { ViewModule } from "../../view/view.module";
import { AbilityModule } from "../../auth/ability/ability.module";
import { ConfigModule } from "@nestjs/config";

const NameSpaceModel = MongooseModule.forFeature([
{
name: NameSpace.name,
schema: NameSpaceSchema,
},
]);

@Module({
imports: [
NameSpaceModel,
UsersModule,
ViewModule,
AbilityModule,
ConfigModule,
],
providers: [NameSpaceService],
exports: [NameSpaceService],
controllers: [NameSpaceController],
})
export class NameSpaceModule {}
Loading

0 comments on commit a2c0d71

Please sign in to comment.