[core] Auth - allow using ?x-api-key as auth
This commit is contained in:
1 parent
21daf501be
commit
376676442e
6 files changed
+92
-6
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
Reference in new issue
Block a user