diff --git a/src/alokasi-topik/alokasi-topik.controller.ts b/src/alokasi-topik/alokasi-topik.controller.ts
index 6fa4e26ac50478a46780cab0a29e11ccda0d7035..7de2c763247d9e7461c75f01d084f9ae74d1fc0b 100644
--- a/src/alokasi-topik/alokasi-topik.controller.ts
+++ b/src/alokasi-topik/alokasi-topik.controller.ts
@@ -9,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";
@@ -24,7 +27,7 @@ import { Roles } from "src/middlewares/roles.decorator";
 import { RolesGuard } from "src/middlewares/roles.guard";
 import {
   CreateBulkTopikDto,
-  CreateRespDto,
+  TopikIdRespDto,
   CreateTopikDto,
   GetAllRespDto,
   OmittedTopik,
@@ -34,6 +37,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()
@@ -46,53 +52,66 @@ export class AlokasiTopikController {
     private konfService: KonfigurasiService,
   ) {}
 
-  @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) {
-    const periode = await this.konfService.getKonfigurasiByKey(
-      process.env.KONF_PERIODE_KEY,
-    );
+  async create(
+    @Body() createDto: CreateTopikDto,
+    @Req() req: Request,
+  ): Promise<TopikIdRespDto> {
+    const periode = await this.konfService.getPeriodeOrFail();
 
-    if (!periode) throw new BadRequestException("Periode belum dikonfigurasi.");
+    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, periode });
   }
 
+  @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) {
-    const periode = await this.konfService.getKonfigurasiByKey(
-      process.env.KONF_PERIODE_KEY,
-    );
-
-    if (!periode) throw new BadRequestException("Periode belum dikonfigurasi.");
+    const periode = await this.konfService.getPeriodeOrFail();
 
     return await this.alokasiTopikService.createBulk(createDto, periode);
   }
 
+  @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.findById(params.id);
+    const periode = await this.konfService.getPeriodeOrFail();
+
+    const res = await this.alokasiTopikService.findById(params.id, periode);
     if (!res) throw new NotFoundException();
     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()
     query: TopikQueryDto,
   ) {
-    const periode = await this.konfService.getKonfigurasiByKey(
-      process.env.KONF_PERIODE_KEY,
-    );
-
-    if (!periode) throw new BadRequestException("Periode belum dikonfigurasi.");
+    const periode = await this.konfService.getPeriodeOrFail();
 
     return await this.alokasiTopikService.findAllCreatedByPembimbing({
       page: query.page || 1,
@@ -101,22 +120,75 @@ export class AlokasiTopikController {
     });
   }
 
-  @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> {
+    const periode = await this.konfService.getPeriodeOrFail();
+
+    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,
+      periode,
+      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> {
+    const periode = await this.konfService.getPeriodeOrFail();
+
+    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,
+      periode,
+      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 93e7dd82db19970390d4cef921e1a774ed2ffabc..27973c53bc5de171f0766565900b649a54d027be 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 b9056ab744f4cb42d6fc970e629cf3f189afcaec..2bc2ecb800445a77649f4130804cb14a32bc744f 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,
@@ -18,7 +18,7 @@ export class AlokasiTopikService {
 
   async create(
     createDto: CreateTopikDto & { periode: string },
-  ): Promise<CreateRespDto> {
+  ): Promise<TopikIdRespDto> {
     const ids = (await this.topikRepo.insert(createDto)).identifiers;
 
     return { id: ids[0].id };
@@ -37,8 +37,7 @@ export class AlokasiTopikService {
     return { ids: ids.map(({ id }) => id) };
   }
 
-  async findById(id: string) {
-    // not periode-protected
+  async findById(id: string, periode: string) {
     return await this.topikRepo.findOne({
       select: {
         id: true,
@@ -54,6 +53,7 @@ export class AlokasiTopikService {
       },
       where: {
         id,
+        periode,
       },
       relations: {
         pengaju: true,
@@ -142,13 +142,18 @@ export class AlokasiTopikService {
     }
   }
 
-  async update(id: string, updateDto: UpdateTopikDto) {
-    // not periode-protected
-    return await this.topikRepo.update(id, updateDto);
+  async update(
+    id: string,
+    updateDto: UpdateTopikDto,
+    periode: string,
+    idPengaju?: string,
+  ) {
+    const findOpt = idPengaju ? { id, periode, idPengaju } : { id, periode };
+    return await this.topikRepo.update(findOpt, updateDto);
   }
 
-  async remove(id: string) {
-    // not periode-protected
-    return await this.topikRepo.delete({ id }); // TODO: manage relation cascading option
+  async remove(id: string, periode: string, idPengaju?: string) {
+    const findOpt = idPengaju ? { id, periode, idPengaju } : { id, periode };
+    return await this.topikRepo.delete(findOpt);
   }
 }
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));
+}
diff --git a/src/helper/validation.ts b/src/helper/validation.ts
deleted file mode 100644
index cfc65eb722c4859bd1569749bccb0e8785721b77..0000000000000000000000000000000000000000
--- a/src/helper/validation.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { NotFoundException } from "@nestjs/common";
-import { validate as uuidValidate } from "uuid";
-
-interface ID {
-  id: string;
-  object: string;
-}
-
-export function validateId(items: ID[]) {
-  for (const item of items) {
-    const isValidUUID = uuidValidate(item.id);
-
-    if (!isValidUUID) {
-      throw new NotFoundException(`${item.object} not found.`);
-    }
-  }
-}