From d6163b73ecc896a98926ae3621fdd798968ae133 Mon Sep 17 00:00:00 2001 From: krakenhavoc Date: Fri, 27 Mar 2026 15:53:07 +0000 Subject: [PATCH 1/2] feat: implement scrypt --- backend/.env.example | 4 ++++ backend/src/auth/auth.service.spec.ts | 16 +++++++--------- backend/src/auth/auth.service.ts | 23 +++++++++++++++++++---- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 333b3a4..e33eb36 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -61,3 +61,7 @@ KK_AUTHENTIK_CLIENT_ID=krakenkey-backend KK_AUTHENTIK_CLIENT_SECRET=your_authentik_client_secret_here KK_AUTHENTIK_REDIRECT_URI=https://api-dev.krakenkey.io/auth/callback KK_AUTHENTIK_POST_ENROLLMENT_REDIRECT=https://api-dev.krakenkey.io/auth/login + +## HMAC Secret (API key hashing) ## +# Generate with: openssl rand -hex 32 +KK_HMAC_SECRET=your_hmac_secret_here diff --git a/backend/src/auth/auth.service.spec.ts b/backend/src/auth/auth.service.spec.ts index f17f895..ee12289 100644 --- a/backend/src/auth/auth.service.spec.ts +++ b/backend/src/auth/auth.service.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ConfigService } from '@nestjs/config'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { createHmac } from 'crypto'; +import { scryptSync } from 'crypto'; import { NotFoundException, UnauthorizedException } from '@nestjs/common'; import { AuthService } from './auth.service'; import { UserApiKey } from './entities/user-api-key.entity'; @@ -323,7 +323,7 @@ describe('AuthService', () => { expect(result.apiKey).toMatch(/^kk_/); }); - it('stores the HMAC-SHA256 hash, not the raw key', async () => { + it('stores the scrypt hash, not the raw key', async () => { let capturedHash = ''; mockUserApiKeyRepo.create.mockImplementation( ({ hash }: { hash: string }) => { @@ -334,9 +334,9 @@ describe('AuthService', () => { mockUserApiKeyRepo.save.mockResolvedValue({}); const result = await service.createApiKey('user-1', 'k'); - const expectedHash = createHmac('sha256', HMAC_SECRET) - .update(result.apiKey) - .digest('hex'); + const expectedHash = scryptSync(result.apiKey, HMAC_SECRET, 64).toString( + 'hex', + ); expect(capturedHash).toBe(expectedHash); }); @@ -364,15 +364,13 @@ describe('AuthService', () => { // validateApiKey // --------------------------------------------------------------------------- describe('validateApiKey', () => { - it('looks up the HMAC-SHA256 hash of the raw key', async () => { + it('looks up the scrypt hash of the raw key', async () => { mockUserApiKeyRepo.findOne.mockResolvedValue(null); const rawKey = 'kk_test_raw_key'; await service.validateApiKey(rawKey); - const expectedHash = createHmac('sha256', HMAC_SECRET) - .update(rawKey) - .digest('hex'); + const expectedHash = scryptSync(rawKey, HMAC_SECRET, 64).toString('hex'); expect(mockUserApiKeyRepo.findOne).toHaveBeenCalledWith({ where: { hash: expectedHash }, relations: ['user'], diff --git a/backend/src/auth/auth.service.ts b/backend/src/auth/auth.service.ts index 96ad43a..d150387 100644 --- a/backend/src/auth/auth.service.ts +++ b/backend/src/auth/auth.service.ts @@ -11,7 +11,7 @@ import { In, Repository } from 'typeorm'; import { UserApiKey } from './entities/user-api-key.entity'; import { ServiceApiKey } from './entities/service-api-key.entity'; import { InjectRepository } from '@nestjs/typeorm'; -import { createHmac, randomBytes } from 'crypto'; +import { scryptSync, randomBytes } from 'crypto'; import { User } from '../users/entities/user.entity'; import { Domain } from '../domains/entities/domain.entity'; import { TlsCrt } from '../certs/tls/entities/tls-crt.entity'; @@ -46,19 +46,28 @@ export class AuthService implements OnModuleInit { ) {} async onModuleInit() { + const secret = this.config.get('KK_HMAC_SECRET'); + if (!secret) { + this.logger.error( + 'KK_HMAC_SECRET is not set — API key creation and validation will fail. ' + + 'Add the secret to your environment before using key-based auth.', + ); + } await this.seedServiceKey(); } /** - * HMAC-SHA256 key hashing with a server-side secret. + * scrypt key hashing with a server-side secret as salt. * Prevents offline key verification if the DB is compromised without the secret. */ private hashKey(raw: string): string { const secret = this.config.get('KK_HMAC_SECRET'); if (!secret) { - throw new Error('KK_HMAC_SECRET must be set'); + throw new Error( + 'KK_HMAC_SECRET must be set to use key-based authentication', + ); } - return createHmac('sha256', secret).update(raw).digest('hex'); + return scryptSync(raw, secret, 64).toString('hex'); } /** @@ -69,6 +78,12 @@ export class AuthService implements OnModuleInit { const rawKey = this.config.get('KK_PROBE_API_KEY'); if (!rawKey) return; + const secret = this.config.get('KK_HMAC_SECRET'); + if (!secret) { + this.logger.warn('Skipping service key seed — KK_HMAC_SECRET is not set'); + return; + } + const hash = this.hashKey(rawKey); const existing = await this.serviceApiKeyRepo.findOne({ where: { hash } }); if (existing) return; From e7862d88612296ae8ab0cb375109c9437439eaa8 Mon Sep 17 00:00:00 2001 From: krakenhavoc Date: Fri, 27 Mar 2026 16:22:38 +0000 Subject: [PATCH 2/2] feat: add dockerhub auth --- .github/workflows/backend-docker-publish.yml | 6 ++++++ .github/workflows/frontend-docker-publish.yml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/backend-docker-publish.yml b/.github/workflows/backend-docker-publish.yml index 0e96533..7c6ce98 100644 --- a/.github/workflows/backend-docker-publish.yml +++ b/.github/workflows/backend-docker-publish.yml @@ -68,6 +68,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/frontend-docker-publish.yml b/.github/workflows/frontend-docker-publish.yml index ebb3cf6..938f4bb 100644 --- a/.github/workflows/frontend-docker-publish.yml +++ b/.github/workflows/frontend-docker-publish.yml @@ -67,6 +67,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3