mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 06:40:31 +00:00
Merge branch 'api-keys' into develop
This commit is contained in:
@@ -26,3 +26,5 @@ export const SendResetPasswordMailJob = 'SendResetPasswordMailJob';
|
|||||||
export const SendSignupVerificationMailQueue =
|
export const SendSignupVerificationMailQueue =
|
||||||
'SendSignupVerificationMailQueue';
|
'SendSignupVerificationMailQueue';
|
||||||
export const SendSignupVerificationMailJob = 'SendSignupVerificationMailJob';
|
export const SendSignupVerificationMailJob = 'SendSignupVerificationMailJob';
|
||||||
|
|
||||||
|
export const AuthApiKeyPrefix = 'bc_';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
|
import { AuthApiKeyPrefix } from './Auth.constants';
|
||||||
|
|
||||||
export const hashPassword = (password: string): Promise<string> =>
|
export const hashPassword = (password: string): Promise<string> =>
|
||||||
new Promise((resolve) => {
|
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(
|
super(
|
||||||
{
|
{
|
||||||
header: 'x-api-key',
|
header: 'Authorization',
|
||||||
prefix: '',
|
prefix: 'Bearer ',
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
import { JwtAuthGuard } from '../guards/jwt.guard';
|
import { JwtAuthGuard } from '../guards/jwt.guard';
|
||||||
import { ApiKeyAuthGuard } from './AuthApiKey.guard';
|
import { ApiKeyAuthGuard } from './AuthApiKey.guard';
|
||||||
|
import { getAuthApiKey } from '../Auth.utils';
|
||||||
|
|
||||||
// mixed-auth.guard.ts
|
// mixed-auth.guard.ts
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -12,7 +13,7 @@ export class MixedAuthGuard implements CanActivate {
|
|||||||
|
|
||||||
canActivate(context: ExecutionContext) {
|
canActivate(context: ExecutionContext) {
|
||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest();
|
||||||
const apiKey = request.headers['x-api-key'];
|
const apiKey = getAuthApiKey(request.headers['authorization'] || '');
|
||||||
|
|
||||||
if (apiKey) {
|
if (apiKey) {
|
||||||
return this.apiKeyGuard.canActivate(context);
|
return this.apiKeyGuard.canActivate(context);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import { ApiKeyModel } from '../models/ApiKey.model';
|
import { ApiKeyModel } from '../models/ApiKey.model';
|
||||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
import { AuthApiKeyPrefix } from '../Auth.constants';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GenerateApiKey {
|
export class GenerateApiKey {
|
||||||
@@ -21,7 +22,7 @@ export class GenerateApiKey {
|
|||||||
const user = await this.tenancyContext.getSystemUser();
|
const user = await this.tenancyContext.getSystemUser();
|
||||||
|
|
||||||
// Generate a secure random API key
|
// 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
|
// Save the API key to the database
|
||||||
const apiKeyRecord = await this.apiKeyModel.query().insert({
|
const apiKeyRecord = await this.apiKeyModel.query().insert({
|
||||||
key,
|
key,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Reflector } from '@nestjs/core';
|
import { Reflector } from '@nestjs/core';
|
||||||
import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants';
|
import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants';
|
||||||
|
import { getAuthApiKey } from '../Auth/Auth.utils';
|
||||||
|
|
||||||
export const IS_TENANT_AGNOSTIC = 'IS_TENANT_AGNOSTIC';
|
export const IS_TENANT_AGNOSTIC = 'IS_TENANT_AGNOSTIC';
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ export class TenancyGlobalGuard implements CanActivate {
|
|||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest();
|
||||||
const organizationId = request.headers['organization-id'];
|
const organizationId = request.headers['organization-id'];
|
||||||
const authorization = request.headers['authorization']?.trim();
|
const authorization = request.headers['authorization']?.trim();
|
||||||
|
const isAuthApiKey = !!getAuthApiKey(authorization || '');
|
||||||
|
|
||||||
const isPublic = this.reflector.getAllAndOverride<boolean>(
|
const isPublic = this.reflector.getAllAndOverride<boolean>(
|
||||||
IS_PUBLIC_ROUTE,
|
IS_PUBLIC_ROUTE,
|
||||||
@@ -35,10 +37,10 @@ export class TenancyGlobalGuard implements CanActivate {
|
|||||||
IS_TENANT_AGNOSTIC,
|
IS_TENANT_AGNOSTIC,
|
||||||
[context.getHandler(), context.getClass()],
|
[context.getHandler(), context.getClass()],
|
||||||
);
|
);
|
||||||
if (isPublic || isTenantAgnostic) {
|
if (isPublic || isTenantAgnostic || isAuthApiKey) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!isEmpty(authorization) && !organizationId) {
|
if (!organizationId) {
|
||||||
throw new UnauthorizedException('Organization ID is required.');
|
throw new UnauthorizedException('Organization ID is required.');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user