feat: use the same Authorization header for jwt and api key
This commit is contained in:
@@ -26,3 +26,5 @@ export const SendResetPasswordMailJob = 'SendResetPasswordMailJob';
|
||||
export const SendSignupVerificationMailQueue =
|
||||
'SendSignupVerificationMailQueue';
|
||||
export const SendSignupVerificationMailJob = 'SendSignupVerificationMailJob';
|
||||
|
||||
export const AuthApiKeyPrefix = 'bc_';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { AuthApiKeyPrefix } from './Auth.constants';
|
||||
|
||||
export const hashPassword = (password: string): Promise<string> =>
|
||||
new Promise((resolve) => {
|
||||
@@ -8,3 +9,12 @@ export const hashPassword = (password: string): Promise<string> =>
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Extracts and validates an API key from the Authorization header
|
||||
* @param {string} authorization - Full authorization header content.
|
||||
*/
|
||||
export const getAuthApiKey = (authorization: string) => {
|
||||
const apiKey = authorization.toLowerCase().replace('bearer ', '').trim();
|
||||
return apiKey.startsWith(AuthApiKeyPrefix) ? apiKey : '';
|
||||
};
|
||||
|
||||
@@ -13,8 +13,8 @@ export class ApiKeyStrategy extends PassportStrategy(
|
||||
) {
|
||||
super(
|
||||
{
|
||||
header: 'x-api-key',
|
||||
prefix: '',
|
||||
header: 'Authorization',
|
||||
prefix: 'Bearer ',
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { JwtAuthGuard } from '../guards/jwt.guard';
|
||||
import { ApiKeyAuthGuard } from './AuthApiKey.guard';
|
||||
import { getAuthApiKey } from '../Auth.utils';
|
||||
|
||||
// mixed-auth.guard.ts
|
||||
@Injectable()
|
||||
@@ -12,7 +13,7 @@ export class MixedAuthGuard implements CanActivate {
|
||||
|
||||
canActivate(context: ExecutionContext) {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const apiKey = request.headers['x-api-key'];
|
||||
const apiKey = getAuthApiKey(request.headers['authorization'] || '');
|
||||
|
||||
if (apiKey) {
|
||||
return this.apiKeyGuard.canActivate(context);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as crypto from 'crypto';
|
||||
import { ApiKeyModel } from '../models/ApiKey.model';
|
||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
import { AuthApiKeyPrefix } from '../Auth.constants';
|
||||
|
||||
@Injectable()
|
||||
export class GenerateApiKey {
|
||||
@@ -21,7 +22,7 @@ export class GenerateApiKey {
|
||||
const user = await this.tenancyContext.getSystemUser();
|
||||
|
||||
// Generate a secure random API key
|
||||
const key = crypto.randomBytes(48).toString('hex');
|
||||
const key = `${AuthApiKeyPrefix}${crypto.randomBytes(48).toString('hex')}`;
|
||||
// Save the API key to the database
|
||||
const apiKeyRecord = await this.apiKeyModel.query().insert({
|
||||
key,
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants';
|
||||
import { getAuthApiKey } from '../Auth/Auth.utils';
|
||||
|
||||
export const IS_TENANT_AGNOSTIC = 'IS_TENANT_AGNOSTIC';
|
||||
|
||||
@@ -26,6 +27,7 @@ export class TenancyGlobalGuard implements CanActivate {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const organizationId = request.headers['organization-id'];
|
||||
const authorization = request.headers['authorization']?.trim();
|
||||
const isAuthApiKey = !!getAuthApiKey(authorization || '');
|
||||
|
||||
const isPublic = this.reflector.getAllAndOverride<boolean>(
|
||||
IS_PUBLIC_ROUTE,
|
||||
@@ -35,10 +37,10 @@ export class TenancyGlobalGuard implements CanActivate {
|
||||
IS_TENANT_AGNOSTIC,
|
||||
[context.getHandler(), context.getClass()],
|
||||
);
|
||||
if (isPublic || isTenantAgnostic) {
|
||||
if (isPublic || isTenantAgnostic || isAuthApiKey) {
|
||||
return true;
|
||||
}
|
||||
if (!isEmpty(authorization) && !organizationId) {
|
||||
if (!organizationId) {
|
||||
throw new UnauthorizedException('Organization ID is required.');
|
||||
}
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user