Repository: thevladbog/cider-code-backend
Pull request #10: feat: changed algorithm
Author: login: thevladbog
Labels: name: enhancement
description: New feature or request
name: released on @release-beta
name: released on @release-stable
Created: 2025-05-02T23:48:30Z | Merged: 2025-05-03T00:21:34Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added a script to generate RSA key pairs for JWT authentication.

- **Chores**
  - Updated ignored files to include additional certificate directories.

- **Refactor**
  - Switched JWT authentication to use RSA key pairs for enhanced security.
  - Updated JWT configuration to load keys dynamically from the filesystem.
  - Improved JWT guard and token generation to utilize RSA keys.
  - Removed unnecessary debug logging from operator service.

- **Documentation**
  - Restructured README with clearer, step-by-step setup instructions and separated commands for different environments.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/.gitignore b/.gitignore
index 7688a67..1bbb518 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,4 +58,5 @@ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
 # Sentry Config File
 .sentryclirc
 
-/src/config/cert/*
\ No newline at end of file
+/src/config/cert/*
+/config/cert/*
\ No newline at end of file
diff --git a/README.md b/README.md
index b5b4bac..ad512ad 100644
--- a/README.md
+++ b/README.md
@@ -8,32 +8,49 @@ Backend for web app BOTTLE [CODE]
 
 ## Project setup
 
+1. Install dependencies
+
 ```bash
-$ npm install
+npm install
 ```
 
-## Compile and run the project
+2. Install local certificates
+
+- Unix
 
 ```bash
-# development
-$ npm run start
+LOCAL='api.test.in.bottlecode.app' npm run cert:create:local
+```
 
-# watch mode
-$ npm run start:dev
+- Windows
 
-# production mode
-$ npm run start:prod
+```bash
+$env:LOCAL='api.test.in.bottlecode.app'
+npm run cert:create:local
 ```
 
-## Run tests
+3.Install jwt certificates
 
 ```bash
-# unit tests
-$ npm run test
+npm run cert:create:jwt
+```
+
+## Compile and run the project
 
-# e2e tests
-$ npm run test:e2e
+- development
 
-# test coverage
-$ npm run test:cov
+```bash
+npm run start
+```
+
+- watch mode
+
+```bash
+npm run start:dev
+```
+
+- production mode
+
+```bash
+npm run start:prod
 ```
diff --git a/package.json b/package.json
index 1cd6880..26b353a 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,8 @@
     "prisma:studio": "npx prisma studio",
     "prisma:seed": "npx prisma db seed",
     "sentry:sourcemaps": "sentry-cli sourcemaps inject --org v-b --project bottle-code-backend ./dist && sentry-cli --url https://sentry.v-b.tech sourcemaps upload --org v-b --project bottle-code-backend ./dist",
-    "cert:create:local": "mkcert -key-file ./src/config/cert/key.pem -cert-file ./src/config/cert/cert.pem api.test.in.bottlecode.app"
+    "cert:create:local": "mkcert -key-file ./src/config/cert/key.pem -cert-file ./src/config/cert/cert.pem $LOCAL",
+    "cert:create:jwt": "openssl genpkey -algorithm RSA -out ./config/cert/jwt_private_key.pem -pkeyopt rsa_keygen_bits:2048 && openssl rsa -pubout -in ./config/cert/jwt_private_key.pem -out ./config/cert/jwt_public_key.pem"
   },
   "dependencies": {
     "@nestjs/common": "^11.0.17",
diff --git a/src/app.module.ts b/src/app.module.ts
index d4bfef2..9a84e27 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -8,14 +8,16 @@ import {
   PrismaModule,
   PrismaService,
 } from 'nestjs-prisma';
-import { ConfigModule } from '@nestjs/config';
+import { ConfigModule, ConfigService } from '@nestjs/config';
 import { ProductModule } from './product/product.module';
 import { UserModule } from './user/user.module';
 import { LoggerModule } from 'nestjs-pino';
 import { loggerOptions } from './config/logger.config';
-import { JwtModule } from '@nestjs/jwt';
+import { JwtModule, JwtModuleOptions } from '@nestjs/jwt';
 import { CodeModule } from './code/code.module';
 import { OperatorModule } from './operator/operator.module';
+import { join } from 'path';
+import { readFileSync, existsSync } from 'fs';
 
 @Module({
   imports: [
@@ -29,10 +31,36 @@ import { OperatorModule } from './operator/operator.module';
     UserModule,
     SentryModule.forRoot(),
     LoggerModule.forRoot(loggerOptions),
-    JwtModule.register({
+    JwtModule.registerAsync({
+      imports: [ConfigModule],
+      useFactory: (configService: ConfigService) => {
+        const pubKeyPath =
+          configService.get<string>('JWT_PUBLIC_KEY_PATH') ??
+          join(process.cwd(), 'config', 'cert', 'jwt_public_key.pem');
+        const privKeyPath =
+          configService.get<string>('JWT_PRIVATE_KEY_PATH') ??
+          join(process.cwd(), 'config', 'cert', 'jwt_private_key.pem');
+
+        if (!existsSync(pubKeyPath) || !existsSync(privKeyPath)) {
+          throw new Error(
+            `Missing JWT key file: ${
+              !existsSync(pubKeyPath) ? pubKeyPath : privKeyPath
+            }`,
+          );
+        }
+
+        const options: JwtModuleOptions = {
+          publicKey: readFileSync(pubKeyPath),
+          privateKey: readFileSync(privKeyPath),
+          signOptions: {
+            expiresIn: configService.get<string>('JWT_EXPIRES'),
+            algorithm: 'RS256',
+          },
+        };
+        return options;
+      },
       global: true,
-      secret: process.env.JWT_SECRET,
-      signOptions: { expiresIn: process.env.JWT_EXPIRES },
+      inject: [ConfigService],
     }),
     CodeModule,
     OperatorModule,
diff --git a/src/guards/auth/auth.guard.ts b/src/guards/auth/auth.guard.ts
index f436113..8155b89 100644
--- a/src/guards/auth/auth.guard.ts
+++ b/src/guards/auth/auth.guard.ts
@@ -12,6 +12,8 @@ import { Reflector } from '@nestjs/core';
 
 import { JwtPayload } from 'src/contracts/jwt-payload/jwt-payload.interface';
 import { JWT_TYPE } from 'src/constants/jwt.constants';
+import * as fs from 'fs';
+import { ConfigService } from '@nestjs/config';
 
 @Injectable()
 export class AuthGuard implements CanActivate {
@@ -19,6 +21,7 @@ export class AuthGuard implements CanActivate {
     private readonly jwtService: JwtService,
     private readonly prisma: PrismaService,
     private readonly reflector: Reflector,
+    private readonly configService: ConfigService,
   ) {}
   private readonly logger = new Logger(AuthGuard.name);
 
@@ -46,8 +49,13 @@ export class AuthGuard implements CanActivate {
         this.logger.error('JWT_SECRET env var is not set');
         throw new UnauthorizedException();
       }
+      const publicKey = fs.readFileSync(
+        __dirname + '/../../../../config/cert/jwt_public_key.pem',
+        'utf8',
+      );
       const payload: JwtPayload = await this.jwtService.verifyAsync(token, {
-        secret: process.env.JWT_SECRET,
+        publicKey,
+        algorithms: ['RS256'],
       });
 
       const revokedToken: { id: string; jti: string } | null =
diff --git a/src/operator/operator.service.ts b/src/operator/operator.service.ts
index bd5235b..6c060d4 100644
--- a/src/operator/operator.service.ts
+++ b/src/operator/operator.service.ts
@@ -116,7 +116,6 @@ export class OperatorService {
   }
 
   async getOne(id: string | undefined): Promise<IOperatorFindOne> {
-    console.log({ id });
     const data = await this.prisma.operator.findUniqueOrThrow({
       where: {
         id,
diff --git a/src/user/user.service.ts b/src/user/user.service.ts
index 78be169..19119aa 100644
--- a/src/user/user.service.ts
+++ b/src/user/user.service.ts
@@ -11,6 +11,8 @@ import { Prisma } from '@prisma/client';
 import { JwtService } from '@nestjs/jwt';
 import { SignInDto } from './dto/sign-in.dto';
 import { nanoid } from 'nanoid';
+import { readFileSync } from 'fs';
+import * as path from 'path';
 
 @Injectable()
 export class UserService {
@@ -19,6 +21,10 @@ export class UserService {
     private readonly jwtService: JwtService,
   ) {}
   private readonly logger = new Logger(UserService.name);
+  private readonly privateKey = readFileSync(
+    path.resolve(__dirname, '../../../config/cert/jwt_private_key.pem'),
+    'utf8',
+  );
 
   async create(
     createUserDto: CreateUserDto,
@@ -214,12 +220,15 @@ export class UserService {
   }
 
   async getJwtToken(id: string) {
+    console.log(__dirname);
     const token = await this.jwtService.signAsync(
       {},
       {
         jwtid: nanoid(),
         subject: id,
         expiresIn: process.env.JWT_EXPIRES ?? '1h',
+        privateKey: this.privateKey,
+        algorithm: 'RS256',
       },
     );
 


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>.gitignore (1)</summary><blockquote>

`61-62`: **Duplicate ignore patterns — consider consolidating**  

You’re now ignoring both `/src/config/cert/*` and `/config/cert/*`.  
If the repo will only ever have **one** certificate directory (moved from `src/…` to `config/…`) keep just the active path; otherwise document why two locations are needed so future contributors aren’t confused.

</blockquote></details>
<details>
<summary>src/guards/auth/auth.guard.ts (1)</summary><blockquote>

`15-17`: **ConfigService injected but never used**  

`ConfigService` is added to the constructor yet no property/method is called. Remove the dependency or start using it for the key path or other config values.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 15deb53b9d1d73874c49dc6395d8537210932b85 and 7c53569ad69189aae947fea35de8e96e6e0eb05f.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `.gitignore` (1 hunks)
* `package.json` (1 hunks)
* `src/app.module.ts` (2 hunks)
* `src/guards/auth/auth.guard.ts` (2 hunks)
* `src/operator/operator.service.ts` (0 hunks)
* `src/user/user.service.ts` (2 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* src/operator/operator.service.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>src/guards/auth/auth.guard.ts (1)</summary><blockquote>

<details>
<summary>src/contracts/jwt-payload/jwt-payload.interface.ts (1)</summary>

* `JwtPayload` (1-6)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>src/app.module.ts (2)</summary>

`11-11`: **Appropriate imports for JWT configuration.**

The addition of ConfigService import is necessary for the async JWT configuration.

---

`16-16`: **Good use of type for JWT options.**

Adding the JwtModuleOptions type import provides better type safety for the JWT configuration.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-02T23:51:52Z

Review comments:
diff_hunk: @@ -25,7 +25,8 @@
     "prisma:studio": "npx prisma studio",
     "prisma:seed": "npx prisma db seed",
     "sentry:sourcemaps": "sentry-cli sourcemaps inject --org v-b --project bottle-code-backend ./dist && sentry-cli --url https://sentry.v-b.tech sourcemaps upload --org v-b --project bottle-code-backend ./dist",
-    "cert:create:local": "mkcert -key-file ./src/config/cert/key.pem -cert-file ./src/config/cert/cert.pem api.test.in.bottlecode.app"
+    "cert:create:local": "mkcert -key-file ./src/config/cert/key.pem -cert-file ./src/config/cert/cert.pem api.test.in.bottlecode.app",
+    "cert:create:jwt": "openssl genrsa -out ./config/cert/jwt_private_key.pem 2048 && openssl rsa -pubout -in ./config/cert/jwt_private_key.pem -out ./config/cert/jwt_public_key.pem"
path: package.json
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Key-generation script is brittle on fresh clones & on Windows shells**  

1. `openssl … > ./config/cert/*` will fail when `./config/cert` doesn’t exist.  
2. Both commands rely on Unix‐style shells; Windows users with plain `cmd.exe` will break.  
3. Missing `-outform PEM`/explicit permissions may create world-readable private keys.

Proposed hard-reset-free fix:

```diff
"cert:create:jwt": "mkdir -p ./config/cert && \
- openssl genrsa -out ./config/cert/jwt_private_key.pem 2048 && \
- openssl rsa -pubout -in ./config/cert/jwt_private_key.pem \
-   -out ./config/cert/jwt_public_key.pem"
+ openssl genpkey -algorithm RSA -out ./config/cert/jwt_private_key.pem \
+   -pkeyopt rsa_keygen_bits:2048 && \
+ openssl rsa -pubout -in ./config/cert/jwt_private_key.pem \
+   -out ./config/cert/jwt_public_key.pem"
```

If cross-platform support is required, ship the same logic in a small Node script (`node ./tools/gen-jwt-keypair.js`) instead.




<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        "cert:create:local": "mkcert -key-file ./src/config/cert/key.pem -cert-file ./src/config/cert/cert.pem api.test.in.bottlecode.app",
        "cert:create:jwt": "mkdir -p ./config/cert && \
          openssl genpkey -algorithm RSA -out ./config/cert/jwt_private_key.pem \
            -pkeyopt rsa_keygen_bits:2048 && \
          openssl rsa -pubout -in ./config/cert/jwt_private_key.pem \
            -out ./config/cert/jwt_public_key.pem"
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T23:51:51Z
author_association: NONE
diff_hunk: @@ -11,6 +11,7 @@ import { Prisma } from '@prisma/client';
 import { JwtService } from '@nestjs/jwt';
 import { SignInDto } from './dto/sign-in.dto';
 import { nanoid } from 'nanoid';
+import * as fs from 'fs';
path: src/user/user.service.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

_⚠️ Potential issue_

**Repeated synchronous FS reads & missing algorithm flag; stray console.log**  

• `fs.readFileSync` runs on every token issuance. Under load this blocks the event loop and hurts latency.  
• `signAsync` receives `privateKey`, but the `algorithm: 'RS256'` option is absent—Nest/JWT will default to **HS256**, silently signing the RSA key as a _secret_ string.  
• Hard-coded `__dirname + '/../../../config/…'` is fragile when transpiled to `dist/`; prefer `path.join` with a configurable base dir.  
• The debug `console.log(__dirname);` should be removed before merging.

Suggested patch (caches the key & fixes algorithm):

```diff
-import * as fs from 'fs';
+import { readFileSync } from 'fs';
+import * as path from 'path';

@@
-  async getJwtToken(id: string) {
-    const privateKey = fs.readFileSync(
-      __dirname + '/../../../config/cert/jwt_private_key.pem',
-      'utf8',
-    );
-
-    console.log(__dirname);
+  private readonly privateKey = readFileSync(
+    path.resolve(__dirname, '../../../config/cert/jwt_private_key.pem'),
+    'utf8',
+  );
+
+  async getJwtToken(id: string) {
     const token = await this.jwtService.signAsync(
       {},
       {
         jwtid: nanoid(),
         subject: id,
         expiresIn: process.env.JWT_EXPIRES ?? '1h',
-        privateKey,
+        privateKey: this.privateKey,
+        algorithm: 'RS256',
       },
     );
```

 


Also applies to: 218-224, 230-230

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T23:51:51Z
author_association: NONE
diff_hunk: @@ -46,8 +49,13 @@ export class AuthGuard implements CanActivate {
         this.logger.error('JWT_SECRET env var is not set');
         throw new UnauthorizedException();
       }
+      const cert = fs.readFileSync(
+        __dirname + '/../../../../config/cert/jwt_private_key.pem',
+        'utf8',
+      );
       const payload: JwtPayload = await this.jwtService.verifyAsync(token, {
-        secret: process.env.JWT_SECRET,
+        publicKey: cert,
+        algorithms: ['RS256'],
       });
path: src/guards/auth/auth.guard.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

_⚠️ Potential issue_

**Verifying with the _private_ key & unnecessary `JWT_SECRET` check**  

1. You read `jwt_private_key.pem` but pass it as `publicKey`; verification should use the **public** key (`jwt_public_key.pem`).  
2. Synchronous file IO on every request harms throughput; cache the key once.  
3. The preceding `JWT_SECRET` environment-variable check is obsolete after migrating to RSA—delete to avoid confusion.

Quick fix:

```diff
- if (!process.env.JWT_SECRET) {
-   this.logger.error('JWT_SECRET env var is not set');
-   throw new UnauthorizedException();
- }
- const cert = fs.readFileSync(
-   __dirname + '/../../../../config/cert/jwt_private_key.pem',
-   'utf8',
- );
+ const publicKey = fs.readFileSync(
+   path.resolve(__dirname, '../../../../config/cert/jwt_public_key.pem'),
+   'utf8',
+ );
   const payload: JwtPayload = await this.jwtService.verifyAsync(token, {
-    publicKey: cert,
+    publicKey,
     algorithms: ['RS256'],
   });
```

Consider placing the key in a static/private readonly field to avoid per-request disk hits.

 


> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T23:51:51Z
author_association: NONE
diff_hunk: @@ -29,10 +29,21 @@ import { OperatorModule } from './operator/operator.module';
     UserModule,
     SentryModule.forRoot(),
     LoggerModule.forRoot(loggerOptions),
-    JwtModule.register({
+    JwtModule.registerAsync({
+      imports: [ConfigModule],
+      useFactory: (configService: ConfigService) => {
+        const options: JwtModuleOptions = {
+          publicKey: '../config/cert/jwt_public_key.pem',
+          privateKey: '../config/cert/jwt_private_key.pem',
+          signOptions: {
+            expiresIn: configService.get<string>('JWT_EXPIRES'),
+            algorithm: 'RS256',
+          },
+        };
+        return options;
+      },
       global: true,
-      secret: process.env.JWT_SECRET,
-      signOptions: { expiresIn: process.env.JWT_EXPIRES },
+      inject: [ConfigService],
     }),
path: src/app.module.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Consider using absolute paths for key files.**

The implementation of async JWT configuration with RSA key-based signing is a security improvement over symmetric secret-based signing. However, the relative file paths could cause issues in production environments.

```diff
const options: JwtModuleOptions = {
-  publicKey: '../config/cert/jwt_public_key.pem',
-  privateKey: '../config/cert/jwt_private_key.pem',
+  publicKey: configService.get<string>('JWT_PUBLIC_KEY_PATH') || 
+    join(process.cwd(), 'config/cert/jwt_public_key.pem'),
+  privateKey: configService.get<string>('JWT_PRIVATE_KEY_PATH') || 
+    join(process.cwd(), 'config/cert/jwt_private_key.pem'),
  signOptions: {
    expiresIn: configService.get<string>('JWT_EXPIRES'),
    algorithm: 'RS256',
  },
};
```

Also, consider adding error handling for cases when key files don't exist.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if the path module is already imported in the file
grep -q "import.*{.*join.*}.*from.*'path'" src/app.module.ts || echo "Need to add: import { join } from 'path';"

# Check the existence of the certificate files in the repository
ls -la config/cert/ 2>/dev/null || echo "Certificate directory not found - this could cause runtime errors"
```

Length of output: 330

---



</details>

**Use absolute paths for JWT RSA keys and add file-existence checks**

In `src/app.module.ts`, add the necessary imports and replace hard-coded relative paths with absolute paths (with optional env overrides), then verify at startup that the key files exist:

- **Imports (top of file)**  
  ```ts
  import { join } from 'path';
  import { readFileSync, existsSync } from 'fs';
  ```
- **Replace inside `JwtModule.registerAsync`**  
  ```diff
  - const options: JwtModuleOptions = {
  -   publicKey: '../config/cert/jwt_public_key.pem',
  -   privateKey: '../config/cert/jwt_private_key.pem',
  + const pubKeyPath = configService.get<string>('JWT_PUBLIC_KEY_PATH')
  +   || join(process.cwd(), 'config', 'cert', 'jwt_public_key.pem');
  + const privKeyPath = configService.get<string>('JWT_PRIVATE_KEY_PATH')
  +   || join(process.cwd(), 'config', 'cert', 'jwt_private_key.pem');
  +
  + if (!existsSync(pubKeyPath) || !existsSync(privKeyPath)) {
  +   throw new Error(`Missing JWT key file: ${
  +     !existsSync(pubKeyPath) ? pubKeyPath : privKeyPath
  +   }`);
  + }
  +
  + const options: JwtModuleOptions = {
  +   publicKey: readFileSync(pubKeyPath),
  +   privateKey: readFileSync(privKeyPath),
    signOptions: {
      expiresIn: configService.get<string>('JWT_EXPIRES'),
      algorithm: 'RS256',
    },
  };
  ```
- **Ensure** the `config/cert` directory (with `jwt_public_key.pem`/`jwt_private_key.pem`) is present in your build/deployment artifacts or provided via env paths.

This change avoids brittle relative paths in production and surfaces missing-key errors early.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T23:51:52Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-02T23:48:36Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes introduce RSA-based JWT authentication by updating the configuration to use public and private key files instead of a symmetric secret. The `.gitignore` is expanded to exclude certificate files in a new directory. A new npm script is added for generating JWT RSA keys. The JWT module in the application is reconfigured to load keys asynchronously using the configuration service. The authentication guard and user service are updated to read RSA keys from the filesystem for verification and signing. A debug log is removed from the operator service. The README is restructured to clarify setup and running instructions.

## Changes

| File(s)                                    | Change Summary                                                                                                                |
|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------|
| .gitignore                                 | Added `/config/cert/*` to ignore certificate files at the root config directory.                                              |
| package.json                               | Added `cert:create:jwt` npm script for generating RSA keys; updated `cert:create:local` script to use `$LOCAL` environment variable. |
| src/app.module.ts                          | Changed JWT module registration to async; now loads RSA keys from files using `ConfigService` and sets algorithm to RS256.    |
| src/guards/auth/auth.guard.ts              | Updated to use RSA public key for JWT verification, read from file; injects `ConfigService`.                                 |
| src/user/user.service.ts                   | Modified `getJwtToken` to read RSA private key from file and use it for signing JWTs; added debug log for directory path.    |
| src/operator/operator.service.ts           | Removed a debug `console.log` statement from `getOne` method.                                                                |
| README.md                                  | Restructured setup instructions into numbered steps; separated certificate installation commands; moved run commands into a distinct section; removed test instructions. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant UserService
    participant FileSystem
    participant JwtService

    User->>UserService: Request JWT token
    UserService->>FileSystem: Read private key file
    FileSystem-->>UserService: Return private key content
    UserService->>JwtService: Sign payload with private key (RS256)
    JwtService-->>UserService: Return JWT token
    UserService-->>User: Return JWT token
```

```mermaid
sequenceDiagram
    participant Client
    participant AuthGuard
    participant FileSystem
    participant JwtService

    Client->>AuthGuard: Send request with JWT token
    AuthGuard->>FileSystem: Read public key file
    FileSystem-->>AuthGuard: Return public key content
    AuthGuard->>JwtService: Verify JWT with public key (RS256)
    JwtService-->>AuthGuard: Verification result
    AuthGuard-->>Client: Allow or deny request
```

## Poem

> RSA keys now hop in place,  
> Secrets gone without a trace.  
> Guard and user read from files,  
> JWTs signed with bunny smiles.  
> Certs ignored, scripts anew,  
> A safer warren built for you!  
> 🐇🔑

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9e42e63dc229ebaeb5c09ad3a5ac594ff2566bda and 263a70261d7affe6dc0f95545d5f0c0cbd49fd44.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `src/app.module.ts` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* src/app.module.ts

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=thevladbog/cider-code-backend&utm_content=10):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-05-03T00:20:02Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=thevladbog_cider-code-backend&pullRequest=10) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=thevladbog_cider-code-backend&pullRequest=10&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=thevladbog_cider-code-backend&pullRequest=10&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=thevladbog_cider-code-backend&pullRequest=10&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=thevladbog_cider-code-backend&pullRequest=10&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=thevladbog_cider-code-backend&pullRequest=10&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=thevladbog_cider-code-backend&pullRequest=10)


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-09T20:14:31Z
body: :tada: This PR is included in version 1.0.0-beta.1 :tada:

The release is available on [GitHub release](https://github.com/thevladbog/cider-code-backend/releases/tag/v1.0.0-beta.1)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-10T18:26:58Z
body: :tada: This PR is included in version 1.0.0-beta.1 :tada:

The release is available on [GitHub release](https://github.com/thevladbog/cider-code-backend/releases/tag/v1.0.0-beta.1)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-06-09T22:04:49Z
body: :tada: This PR is included in version 1.0.0 :tada:

The release is available on [GitHub release](https://github.com/thevladbog/cider-code-backend/releases/tag/v1.0.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: NONE