1 parent
3b6fa3343d
commit
75b06d3ba1
4 files changed
+135
No files matched your search
@@ -0,0 +1,30 @@
|
||||
import * as url from 'url';
|
||||
|
||||
import { validateApiKey } from './apiKey.strategy';
|
||||
|
||||
export class WebSocketAuth {
|
||||
private key: string;
|
||||
|
||||
constructor() {
|
||||
this.key = process.env.WHATSAPP_API_KEY || '';
|
||||
}
|
||||
|
||||
verifyClient = (info: any, callback: any) => {
|
||||
if (!this.key) {
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
// Do something with the info
|
||||
let query = url.parse(info.req.url, true).query;
|
||||
// case insensitive
|
||||
query = Object.keys(query).reduce((acc, key) => {
|
||||
acc[key.toLowerCase()] = query[key];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const apiKey = query['x-api-key'];
|
||||
// @ts-ignore
|
||||
const isValid = validateApiKey(apiKey, this.key);
|
||||
callback(isValid);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import * as crypto from 'crypto';
|
||||
import { HeaderAPIKeyStrategy } from 'passport-headerapikey';
|
||||
|
||||
import { WhatsappConfigService } from '../../config.service';
|
||||
|
||||
/**
|
||||
* Securely validates API key using constant-time comparison
|
||||
* @param providedKey The key provided in the request
|
||||
* @param storedKey The key stored in the configuration
|
||||
* @returns boolean indicating if the keys match
|
||||
*/
|
||||
export function validateApiKey(
|
||||
providedKey: string,
|
||||
storedKey: string | undefined,
|
||||
): boolean {
|
||||
if (!storedKey || !providedKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Convert strings to buffers for constant-time comparison
|
||||
const providedKeyBuffer = Buffer.from(providedKey);
|
||||
const storedKeyBuffer = Buffer.from(storedKey);
|
||||
|
||||
// If lengths are different, return false but use a dummy comparison to prevent timing attacks
|
||||
if (providedKeyBuffer.length !== storedKeyBuffer.length) {
|
||||
// Create a dummy buffer of the same length as the provided key
|
||||
const dummyBuffer = Buffer.alloc(providedKeyBuffer.length);
|
||||
// Perform comparison with dummy buffer to maintain constant time
|
||||
crypto.timingSafeEqual(providedKeyBuffer, dummyBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Perform constant-time comparison
|
||||
return crypto.timingSafeEqual(providedKeyBuffer, storedKeyBuffer);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ApiKeyStrategy extends PassportStrategy(HeaderAPIKeyStrategy) {
|
||||
constructor(private config: WhatsappConfigService) {
|
||||
super({ header: 'X-Api-Key', prefix: '' }, true, (apikey, done) => {
|
||||
const isValid = validateApiKey(apikey, this.config.getApiKey());
|
||||
return done(isValid);
|
||||
});
|
||||
}
|
||||
|
||||
validate(apikey: string, done: (result: boolean) => void): void {
|
||||
const isValid = validateApiKey(apikey, this.config.getApiKey());
|
||||
return done(isValid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
Injectable,
|
||||
NestMiddleware,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import * as passport from 'passport';
|
||||
|
||||
import { WhatsappConfigService } from '../../config.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthMiddleware implements NestMiddleware {
|
||||
constructor(private config: WhatsappConfigService) {}
|
||||
|
||||
use(req: any, res: any, next: () => void) {
|
||||
// No api key - skip the validation path
|
||||
if (!this.config.getApiKey()) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
passport.authenticate('headerapikey', { session: false }, (value) => {
|
||||
if (!value) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
next();
|
||||
})(req, res, next);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import * as basicAuth from 'express-basic-auth';
|
||||
|
||||
export function BasicAuthFunction(username, password, exclude: string[] = []) {
|
||||
function authFunction(req, res, next) {
|
||||
const ignore = exclude.filter((url) => req.url.startsWith(url)).length > 0;
|
||||
if (ignore) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
const auth = basicAuth({
|
||||
challenge: true,
|
||||
users: {
|
||||
[username]: password,
|
||||
},
|
||||
});
|
||||
auth(req, res, next);
|
||||
}
|
||||
|
||||
return authFunction;
|
||||
}
|
||||
Reference in new issue
Block a user