[core] Auth - allow using ?x-api-key as auth

This commit is contained in:
devlikepro committed 2026-05-07 14:14:19 +07:00
1 parent 21daf501be
commit 376676442e
6 files changed
+92 -6

No files matched your search

+6 -1
View File
@@ -34,6 +34,7 @@ import {
getPinoHttpUseLevel,
getPinoLogLevel,
getPinoTransport,
redactUrlParams,
} from '@waha/utils/logging';
import * as Joi from 'joi';
import { LoggerModule } from 'nestjs-pino';
@@ -91,11 +92,15 @@ export const IMPORTS_CORE = [
);
},
},
redact: {
paths: ['req.query["x-api-key"]'],
censor: '[REDACTED]',
},
serializers: {
req: (req) => ({
id: req.id,
method: req.method,
url: req.url,
url: redactUrlParams('x-api-key', req.url, req.query),
query: req.query,
params: req.params,
}),
@@ -0,0 +1,54 @@
import { Request } from 'express';
import { Strategy as PassportStrategy } from 'passport-strategy';
type VerifyCallback = (err: Error | null, user?: object, info?: object) => void;
type VerifyFunction = (
apiKey: string,
verified: VerifyCallback,
req?: Request,
) => void;
export class HeaderOrQueryApiKeyStrategy extends PassportStrategy {
// Declared here because passport injects these at runtime; the base type omits them.
declare fail: (info: object, status: unknown) => void;
declare error: (err: Error) => void;
declare success: (user: object, info?: object) => void;
name: string;
passReqToCallback: boolean;
verify: VerifyFunction;
constructor(passReqToCallback: boolean, verify: VerifyFunction) {
super();
this.name = 'headerapikey';
this.passReqToCallback = passReqToCallback;
this.verify = verify;
}
authenticate(req: Request): void {
const headerKey = req.headers['x-api-key'] as string | undefined;
const queryKey = req.query['x-api-key'] as string | undefined;
const apiKey = headerKey ?? queryKey;
if (!apiKey) {
return this.fail({ message: 'Missing API Key' }, null);
}
const verified: VerifyCallback = (err, user?, info?) => {
if (err) {
return this.error(err);
}
if (!user) {
return this.fail(info, null);
}
this.success(user, info);
};
if (this.passReqToCallback) {
this.verify(apiKey, verified, req);
} else {
this.verify(apiKey, verified);
}
}
}
+2
View File
@@ -12,6 +12,7 @@ export class ApiKeyAuthMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
if (this.auth instanceof NoAuth) {
delete req.query['x-api-key'];
next();
return;
}
@@ -31,6 +32,7 @@ export class ApiKeyAuthMiddleware implements NestMiddleware {
return;
}
req.user = user;
delete req.query['x-api-key'];
next();
})(req, res, next);
}
+6 -4
View File
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { IApiKeyAuth } from '@waha/core/auth/auth';
import { SessionActions } from '@waha/core/auth/casl.types';
import { HeaderAPIKeyStrategy } from 'passport-headerapikey';
import { HeaderOrQueryApiKeyStrategy } from '@waha/core/auth/HeaderOrQueryApiKeyStrategy';
import { ApiKeyAuthService } from './ApiKeyAuthService';
export interface User {
@@ -19,13 +19,15 @@ function AdminUser(): User {
}
@Injectable()
export class ApiKeyStrategy extends PassportStrategy(HeaderAPIKeyStrategy) {
export class ApiKeyStrategy extends PassportStrategy(
HeaderOrQueryApiKeyStrategy,
) {
constructor(
private auth: IApiKeyAuth,
private apiKeyService: ApiKeyAuthService,
) {
// @ts-ignore
super({ header: 'X-Api-Key', prefix: '' }, true, (apikey, done) => {
// @ts-ignore — PassportStrategy mixin doesn't forward constructor arg types
super(true, (apikey, done) => {
return this.validate(apikey, done);
});
}
+12 -1
View File
@@ -1,4 +1,8 @@
import { ValidationPipe, ValidationPipeOptions } from '@nestjs/common';
import {
ArgumentMetadata,
ValidationPipe,
ValidationPipeOptions,
} from '@nestjs/common';
import { parseBool } from '@waha/helpers';
// So we can change it to True during development and testing
@@ -12,4 +16,11 @@ export class WAHAValidationPipe extends ValidationPipe {
options.forbidNonWhitelisted = WAHA_HTTP_STRICT_MODE;
super(options);
}
async transform(value: any, metadata: ArgumentMetadata) {
if (metadata.type === 'query' && value && typeof value === 'object') {
delete value['x-api-key'];
}
return super.transform(value, metadata);
}
}
+12
View File
@@ -79,4 +79,16 @@ export function getPinoTransport() {
};
}
export function redactUrlParams(
key: string,
url: string,
query: Record<string, string>,
): string {
const value = query[key];
if (!value) {
return url;
}
return url.replace(value, '[REDACTED]');
}
export { getNestJSLogLevels };