-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #446 from bandada-infra/feat/apikeys
Move API key logic from `GroupsService` to `AdminsService`
- Loading branch information
Showing
39 changed files
with
3,220 additions
and
467 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Body, Controller, Get, Param, Post, Put } from "@nestjs/common" | ||
import { ApiCreatedResponse } from "@nestjs/swagger" | ||
import { CreateAdminDTO } from "./dto/create-admin.dto" | ||
import { AdminsService } from "./admins.service" | ||
import { Admin } from "./entities/admin.entity" | ||
import { UpdateApiKeyDTO } from "./dto/update-apikey.dto" | ||
|
||
@Controller("admins") | ||
export class AdminsController { | ||
constructor(private readonly adminsService: AdminsService) {} | ||
|
||
@Post() | ||
async createAdmin(@Body() dto: CreateAdminDTO): Promise<Admin> { | ||
return this.adminsService.create(dto) | ||
} | ||
|
||
@Get(":admin") | ||
@ApiCreatedResponse({ type: Admin }) | ||
async getAdmin(@Param("admin") adminId: string) { | ||
return this.adminsService.findOne({ id: adminId }) | ||
} | ||
|
||
@Put(":admin/apikey") | ||
async updateApiKey( | ||
@Param("admin") adminId: string, | ||
@Body() dto: UpdateApiKeyDTO | ||
): Promise<string> { | ||
return this.adminsService.updateApiKey(adminId, dto.action) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,14 @@ | ||
import { Global, Module } from "@nestjs/common" | ||
import { TypeOrmModule } from "@nestjs/typeorm" | ||
import { Admin } from "./entities/admin.entity" | ||
import { AdminService } from "./admins.service" | ||
import { AdminsService } from "./admins.service" | ||
import { AdminsController } from "./admins.controller" | ||
|
||
@Global() | ||
@Module({ | ||
imports: [TypeOrmModule.forFeature([Admin])], | ||
exports: [AdminService], | ||
providers: [AdminService], | ||
controllers: [] | ||
exports: [AdminsService], | ||
providers: [AdminsService], | ||
controllers: [AdminsController] | ||
}) | ||
export class AdminsModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import { id as idToHash } from "@ethersproject/hash" | ||
import { ScheduleModule } from "@nestjs/schedule" | ||
import { Test } from "@nestjs/testing" | ||
import { TypeOrmModule } from "@nestjs/typeorm" | ||
import { ApiKeyActions } from "@bandada/utils" | ||
import { AdminsService } from "./admins.service" | ||
import { Admin } from "./entities/admin.entity" | ||
|
||
describe("AdminsService", () => { | ||
const id = "1" | ||
const hashedId = idToHash(id) | ||
const address = "0x000000" | ||
let admin: Admin | ||
let adminsService: AdminsService | ||
|
||
beforeAll(async () => { | ||
const module = await Test.createTestingModule({ | ||
imports: [ | ||
TypeOrmModule.forRootAsync({ | ||
useFactory: () => ({ | ||
type: "sqlite", | ||
database: ":memory:", | ||
dropSchema: true, | ||
entities: [Admin], | ||
synchronize: true | ||
}) | ||
}), | ||
TypeOrmModule.forFeature([Admin]), | ||
ScheduleModule.forRoot() | ||
], | ||
providers: [AdminsService] | ||
}).compile() | ||
adminsService = await module.resolve(AdminsService) | ||
}) | ||
|
||
describe("# create", () => { | ||
it("Should create an admin", async () => { | ||
admin = await adminsService.create({ id, address }) | ||
|
||
expect(admin.id).toBe(idToHash(id)) | ||
expect(admin.address).toBe(address) | ||
expect(admin.username).toBe(address.slice(-5)) | ||
expect(admin.apiEnabled).toBeFalsy() | ||
expect(admin.apiKey).toBeNull() | ||
}) | ||
|
||
it("Should create an admin given the username", async () => { | ||
const id2 = "2" | ||
const address2 = "0x000002" | ||
const username = "admn2" | ||
|
||
const admin = await adminsService.create({ | ||
id: id2, | ||
address: address2, | ||
username | ||
}) | ||
|
||
expect(admin.id).toBe(idToHash(id2)) | ||
expect(admin.address).toBe(address2) | ||
expect(admin.username).toBe(username) | ||
expect(admin.apiEnabled).toBeFalsy() | ||
expect(admin.apiKey).toBeNull() | ||
}) | ||
}) | ||
|
||
describe("# findOne", () => { | ||
it("Should return the admin given the identifier", async () => { | ||
const found = await adminsService.findOne({ id: hashedId }) | ||
|
||
expect(found.id).toBe(admin.id) | ||
expect(found.address).toBe(admin.address) | ||
expect(found.username).toBe(admin.username) | ||
expect(found.apiEnabled).toBeFalsy() | ||
expect(found.apiKey).toBe(admin.apiKey) | ||
}) | ||
|
||
it("Should return null if the given identifier does not belong to an admin", async () => { | ||
expect(await adminsService.findOne({ id: "3" })).toBeNull() | ||
}) | ||
}) | ||
|
||
describe("# updateApiKey", () => { | ||
it("Should create an apikey for the admin", async () => { | ||
const apiKey = await adminsService.updateApiKey( | ||
admin.id, | ||
ApiKeyActions.Generate | ||
) | ||
|
||
admin = await adminsService.findOne({ id: hashedId }) | ||
|
||
expect(admin.apiEnabled).toBeTruthy() | ||
expect(admin.apiKey).toBe(apiKey) | ||
}) | ||
|
||
it("Should generate another apikey for the admin", async () => { | ||
const previousApiKey = admin.apiKey | ||
|
||
const apiKey = await adminsService.updateApiKey( | ||
admin.id, | ||
ApiKeyActions.Generate | ||
) | ||
|
||
admin = await adminsService.findOne({ id: hashedId }) | ||
|
||
expect(admin.apiEnabled).toBeTruthy() | ||
expect(admin.apiKey).toBe(apiKey) | ||
expect(admin.apiKey).not.toBe(previousApiKey) | ||
}) | ||
|
||
it("Should disable the apikey for the admin", async () => { | ||
const { apiKey } = admin | ||
|
||
await adminsService.updateApiKey(hashedId, ApiKeyActions.Disable) | ||
|
||
admin = await adminsService.findOne({ id: hashedId }) | ||
|
||
expect(admin.apiEnabled).toBeFalsy() | ||
expect(admin.apiKey).toBe(apiKey) | ||
}) | ||
|
||
it("Should enable the apikey for the admin", async () => { | ||
const { apiKey } = admin | ||
|
||
await adminsService.updateApiKey(hashedId, ApiKeyActions.Enable) | ||
|
||
admin = await adminsService.findOne({ id: hashedId }) | ||
|
||
expect(admin.apiEnabled).toBeTruthy() | ||
expect(admin.apiKey).toBe(apiKey) | ||
}) | ||
|
||
it("Should not create the apikey when the given id does not belog to an admin", async () => { | ||
const wrongId = "wrongId" | ||
|
||
const fun = adminsService.updateApiKey( | ||
wrongId, | ||
ApiKeyActions.Disable | ||
) | ||
|
||
await expect(fun).rejects.toThrow( | ||
`The '${wrongId}' does not belong to an admin` | ||
) | ||
}) | ||
|
||
it("Should not enable the apikey before creation", async () => { | ||
const tempAdmin = await adminsService.create({ | ||
id: "id2", | ||
address: "address2" | ||
}) | ||
|
||
const fun = adminsService.updateApiKey( | ||
tempAdmin.id, | ||
ApiKeyActions.Enable | ||
) | ||
|
||
await expect(fun).rejects.toThrow( | ||
`The '${tempAdmin.id}' does not have an apikey` | ||
) | ||
}) | ||
|
||
it("Shoul throw if the action does not exist", async () => { | ||
const wrongAction = "wrong-action" | ||
|
||
const fun = adminsService.updateApiKey( | ||
hashedId, | ||
// @ts-ignore | ||
wrongAction | ||
) | ||
|
||
await expect(fun).rejects.toThrow( | ||
`Unsupported ${wrongAction} apikey` | ||
) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { ApiKeyActions } from "@bandada/utils" | ||
import { IsEnum } from "class-validator" | ||
|
||
export class UpdateApiKeyDTO { | ||
@IsEnum(ApiKeyActions) | ||
action: ApiKeyActions | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.