diff --git a/src/alokasi-topik/alokasi-topik.controller.ts b/src/alokasi-topik/alokasi-topik.controller.ts
index 8c095da4d27d04e961dce48cc605c5df62bd0f8d..069ae9fb2290d3831476d20cb7068beae81fbed4 100644
--- a/src/alokasi-topik/alokasi-topik.controller.ts
+++ b/src/alokasi-topik/alokasi-topik.controller.ts
@@ -1,4 +1,5 @@
 import {
+  BadRequestException,
   Body,
   Controller,
   Delete,
@@ -8,12 +9,15 @@ import {
   Post,
   Put,
   Query,
+  Req,
   UseGuards,
 } from "@nestjs/common";
 import {
   ApiBearerAuth,
   ApiCookieAuth,
+  ApiCreatedResponse,
   ApiOkResponse,
+  ApiOperation,
   ApiTags,
 } from "@nestjs/swagger";
 import { RoleEnum } from "src/entities/pengguna.entity";
@@ -22,7 +26,7 @@ import { Roles } from "src/middlewares/roles.decorator";
 import { RolesGuard } from "src/middlewares/roles.guard";
 import {
   CreateBulkTopikDto,
-  CreateRespDto,
+  TopikIdRespDto,
   CreateTopikDto,
   GetAllRespDto,
   OmittedTopik,
@@ -32,6 +36,9 @@ import {
   createBulkRespDto,
 } from "./alokasi-topik.dto";
 import { AlokasiTopikService } from "./alokasi-topik.service";
+import { Request } from "express";
+import { AuthDto } from "src/auth/auth.dto";
+import { HIGH_AUTHORITY_ROLES, isHighAuthority } from "src/helper/roles";
 
 @ApiTags("Alokasi Topik")
 @ApiCookieAuth()
@@ -41,22 +48,41 @@ import { AlokasiTopikService } from "./alokasi-topik.service";
 export class AlokasiTopikController {
   constructor(private alokasiTopikService: AlokasiTopikService) {}
 
-  @ApiOkResponse({ type: CreateRespDto })
-  @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN)
+  @ApiOperation({
+    summary: "Create new topik. Roles: S2_TIM_TESIS, ADMIN, S2_PEMBIMBING",
+  })
+  @ApiCreatedResponse({ type: TopikIdRespDto })
+  @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_PEMBIMBING)
   @Post()
-  async create(@Body() createDto: CreateTopikDto) {
-    return await this.alokasiTopikService.create({ ...createDto });
+  async create(
+    @Body() createDto: CreateTopikDto,
+    @Req() req: Request,
+  ): Promise<TopikIdRespDto> {
+    const { roles, id } = req.user as AuthDto;
+    // user only has S2_PEMBIMBING role
+    if (!isHighAuthority(roles) && createDto.idPengaju !== id) {
+      throw new BadRequestException("Pengaju ID harus sama dengan user ID");
+    }
+
+    return await this.alokasiTopikService.create(createDto);
   }
 
+  @ApiOperation({
+    summary: "Create multiple topik. Roles: S2_TIM_TESIS, ADMIN",
+  })
   @ApiOkResponse({ type: createBulkRespDto })
-  @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN)
+  @Roles(...HIGH_AUTHORITY_ROLES)
   @Post("/bulk")
   async createBulk(@Body() createDto: CreateBulkTopikDto) {
     return await this.alokasiTopikService.createBulk(createDto);
   }
 
+  @ApiOperation({
+    summary:
+      "Get topik by ID. Roles: S2_TIM_TESIS, ADMIN, S2_PEMBIMBING, S2_MAHASISWA",
+  })
   @ApiOkResponse({ type: OmittedTopik })
-  @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN)
+  @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_PEMBIMBING, RoleEnum.S2_MAHASISWA)
   @Get("/:id")
   async getById(@Param() params: TopikParamDto) {
     const res = await this.alokasiTopikService.findActiveTopikById(params.id);
@@ -64,8 +90,12 @@ export class AlokasiTopikController {
     return res as OmittedTopik;
   }
 
+  @ApiOperation({
+    summary:
+      "Get all topik. Roles: S2_TIM_TESIS, ADMIN, S2_MAHASISWA, S2_PEMBIMBING",
+  })
   @ApiOkResponse({ type: GetAllRespDto })
-  @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN, RoleEnum.S2_MAHASISWA)
+  @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_MAHASISWA, RoleEnum.S2_PEMBIMBING)
   @Get()
   async getAll(
     @Query()
@@ -73,28 +103,72 @@ export class AlokasiTopikController {
   ) {
     return await this.alokasiTopikService.findAllActiveTopikCreatedByPembimbing(
       {
-        page: query.page || 1,
         ...query,
+        page: query.page || 1,
       },
     );
   }
 
-  @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN)
+  @ApiOperation({
+    summary: "Update topik. Roles: S2_TIM_TESIS, ADMIN, S2_PEMBIMBING",
+  })
+  @ApiOkResponse({ type: TopikIdRespDto })
+  @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_PEMBIMBING)
   @Put("/:id")
   async update(
     @Param() params: TopikParamDto,
     @Body() updateDto: UpdateTopikDto,
-  ) {
-    const res = await this.alokasiTopikService.update(params.id, updateDto);
-    if (!res.affected) throw new NotFoundException();
-    return res;
+    @Req() req: Request,
+  ): Promise<TopikIdRespDto> {
+    let idPengaju = undefined;
+    const { roles, id } = req.user as AuthDto;
+    // user only has S2_PEMBIMBING role
+    if (!isHighAuthority(roles)) {
+      if (updateDto.idPengaju !== id) {
+        throw new BadRequestException("Pengaju ID harus sama dengan user ID");
+      }
+      idPengaju = id;
+    }
+
+    const res = await this.alokasiTopikService.update(
+      params.id,
+      updateDto,
+      idPengaju,
+    );
+    if (!res.affected)
+      throw new NotFoundException(
+        "Topik tidak ditemukan di antara topik yang dapat Anda akses",
+      );
+
+    const resp: TopikIdRespDto = { id: params.id };
+
+    return resp;
   }
 
-  @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN)
+  @ApiOperation({
+    summary: "Delete topik. Roles: S2_TIM_TESIS, ADMIN, S2_PEMBIMBING",
+  })
+  @ApiOkResponse({ type: TopikIdRespDto })
+  @Roles(...HIGH_AUTHORITY_ROLES, RoleEnum.S2_PEMBIMBING)
   @Delete("/:id")
-  async delete(@Param() params: TopikParamDto) {
-    const res = await this.alokasiTopikService.remove(params.id);
-    if (!res.affected) throw new NotFoundException();
-    return res;
+  async delete(
+    @Param() params: TopikParamDto,
+    @Req() req: Request,
+  ): Promise<TopikIdRespDto> {
+    let idPengaju = undefined;
+    const { roles, id } = req.user as AuthDto;
+    // user only has S2_PEMBIMBING role
+    if (!isHighAuthority(roles)) {
+      idPengaju = id;
+    }
+
+    const res = await this.alokasiTopikService.remove(params.id, idPengaju);
+    if (!res.affected)
+      throw new NotFoundException(
+        "Topik tidak ditemukan di antara topik yang dapat Anda akses",
+      );
+
+    const resp: TopikIdRespDto = { id: params.id };
+    return resp;
   }
 }
diff --git a/src/alokasi-topik/alokasi-topik.dto.ts b/src/alokasi-topik/alokasi-topik.dto.ts
index 90db74b42c003f32e2bccdcaeb7ace2a10acb265..5c41225c23ddf1fbefe996dfc7f961ad5ecebc01 100644
--- a/src/alokasi-topik/alokasi-topik.dto.ts
+++ b/src/alokasi-topik/alokasi-topik.dto.ts
@@ -81,7 +81,7 @@ export class createBulkRespDto {
   ids: string[];
 }
 
-export class CreateRespDto {
+export class TopikIdRespDto {
   @ApiProperty()
   id: string;
 }
diff --git a/src/alokasi-topik/alokasi-topik.service.ts b/src/alokasi-topik/alokasi-topik.service.ts
index 6a272d71cd1632ada903a4fcddae1a010af2e1b6..dd00add6e9de08317fbeff35de231b06c831e02f 100644
--- a/src/alokasi-topik/alokasi-topik.service.ts
+++ b/src/alokasi-topik/alokasi-topik.service.ts
@@ -5,7 +5,7 @@ import { Topik } from "src/entities/topik.entity";
 import { ArrayContains, Like, Repository } from "typeorm";
 import {
   CreateBulkTopikDto,
-  CreateRespDto,
+  TopikIdRespDto,
   CreateTopikDto,
   GetAllRespDto,
   UpdateTopikDto,
@@ -16,7 +16,7 @@ import {
 export class AlokasiTopikService {
   constructor(@InjectRepository(Topik) private topikRepo: Repository<Topik>) {}
 
-  async create(createDto: CreateTopikDto): Promise<CreateRespDto> {
+  async create(createDto: CreateTopikDto): Promise<TopikIdRespDto> {
     const ids = (await this.topikRepo.insert(createDto)).identifiers;
 
     return { id: ids[0].id };
@@ -131,11 +131,15 @@ export class AlokasiTopikService {
     }
   }
 
-  async update(id: string, updateDto: UpdateTopikDto) {
-    return await this.topikRepo.update({ id, aktif: true }, updateDto);
+  async update(id: string, updateDto: UpdateTopikDto, idPengaju?: string) {
+    const findOpt = idPengaju
+      ? { id, idPengaju, aktif: true }
+      : { id, aktif: true };
+    return await this.topikRepo.update(findOpt, updateDto);
   }
 
-  async remove(id: string) {
-    return await this.topikRepo.update({ id }, { aktif: false });
+  async remove(id: string, idPengaju?: string) {
+    const findOpt = idPengaju ? { id, idPengaju } : { id };
+    return await this.topikRepo.update(findOpt, { aktif: false });
   }
 }
diff --git a/src/helper/roles.ts b/src/helper/roles.ts
new file mode 100644
index 0000000000000000000000000000000000000000..690f29dd520df4c348a4520cfc75aee48540dca5
--- /dev/null
+++ b/src/helper/roles.ts
@@ -0,0 +1,7 @@
+import { RoleEnum } from "src/entities/pengguna.entity";
+
+export const HIGH_AUTHORITY_ROLES = [RoleEnum.ADMIN, RoleEnum.S2_TIM_TESIS];
+
+export function isHighAuthority(roles: RoleEnum[]) {
+  return roles.some((role) => HIGH_AUTHORITY_ROLES.includes(role));
+}