Add default rely on Cloudflare for IP addresses

This commit is contained in:
Tuan Dang
2023-06-07 00:50:25 +01:00
parent a8ed187443
commit 5667e47b31
17 changed files with 906 additions and 842 deletions

1657
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -31,15 +31,14 @@
"libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21",
"mongoose": "^6.10.5",
"node-cache": "^5.1.2",
"nanoid": "^3.3.6",
"node-cache": "^5.1.2",
"nodemailer": "^6.8.0",
"passport": "^0.6.0",
"passport-google-oauth20": "^2.0.0",
"posthog-node": "^2.6.0",
"query-string": "^7.1.3",
"rate-limit-mongo": "^2.3.2",
"request-ip": "^3.3.0",
"rimraf": "^3.0.2",
"stripe": "^10.7.0",
"swagger-autogen": "^2.22.0",

View File

@@ -126,13 +126,13 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({
user,
ip: req.ip,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.ip,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});
@@ -153,7 +153,7 @@ export const login2 = async (req: Request, res: Response) => {
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
ipAddress: req.realIP
});
// return (access) token in response
@@ -210,7 +210,7 @@ export const logout = async (req: Request, res: Response) => {
userId: req.user._id,
actions: [logoutAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
ipAddress: req.realIP
});
} catch (err) {

View File

@@ -130,7 +130,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
workspaceId,
environment,
channel: channel ? channel : 'cli',
ipAddress: req.ip
ipAddress: req.realIP
});
key = await Key.findOne({
@@ -199,7 +199,7 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => {
workspaceId,
environment,
channel: 'cli',
ipAddress: req.ip
ipAddress: req.realIP
});
key = {

View File

@@ -151,14 +151,14 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({
user,
ip: req.ip,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.ip,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});
@@ -215,7 +215,7 @@ export const login2 = async (req: Request, res: Response) => {
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
ipAddress: req.realIP
});
return res.status(200).send(response);
@@ -296,14 +296,14 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
await checkUserDevice({
user,
ip: req.ip,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.ip,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});
@@ -363,7 +363,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
ipAddress: req.realIP
});
return res.status(200).send(resObj);

View File

@@ -296,7 +296,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId),
actions,
channel,
ipAddress: req.ip,
ipAddress: req.realIP,
});
}
@@ -562,7 +562,7 @@ export const createSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId),
actions: [addAction],
channel,
ipAddress: req.ip,
ipAddress: req.realIP,
}));
// (EE) take a secret snapshot
@@ -784,7 +784,7 @@ export const getSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId as string),
actions: [readAction],
channel,
ipAddress: req.ip,
ipAddress: req.realIP,
}));
const postHogClient = await TelemetryService.getPostHogClient();
@@ -1019,7 +1019,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(key),
actions: [updateAction],
channel,
ipAddress: req.ip,
ipAddress: req.realIP,
}));
// (EE) take a secret snapshot
@@ -1157,7 +1157,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(key),
actions: [deleteAction],
channel,
ipAddress: req.ip,
ipAddress: req.realIP,
}));
// (EE) take a secret snapshot

View File

@@ -117,7 +117,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.ip,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});
@@ -250,7 +250,7 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.ip,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});

View File

@@ -70,7 +70,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
environment,
secrets,
channel: channel ? channel : 'cli',
ipAddress: req.ip
ipAddress: req.realIP
});
await pushKeys({
@@ -145,7 +145,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
workspaceId,
environment,
channel: channel ? channel : 'cli',
ipAddress: req.ip
ipAddress: req.realIP
});
if (channel !== 'cli') {

View File

@@ -179,14 +179,14 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({
user,
ip: req.ip,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.ip,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});
@@ -243,7 +243,7 @@ export const login2 = async (req: Request, res: Response) => {
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
ipAddress: req.realIP
});
return res.status(200).send(response);

View File

@@ -138,7 +138,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.ip,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});

View File

@@ -15,6 +15,9 @@ export const apiLimiter = rateLimit({
legacyHeaders: false,
skip: (request) => {
return request.path === '/healthcheck' || request.path === '/api/status'
},
keyGenerator: (req, res) => {
return req.realIP
}
});
@@ -29,7 +32,10 @@ const authLimit = rateLimit({
windowMs: 1000 * 60 * 60,
max: 50,
standardHeaders: true,
legacyHeaders: false
legacyHeaders: false,
keyGenerator: (req, res) => {
return req.realIP
}
});
// 50 requests per 1 hour
@@ -43,7 +49,10 @@ export const passwordLimiter = rateLimit({
windowMs: 1000 * 60 * 60,
max: 50,
standardHeaders: true,
legacyHeaders: false
legacyHeaders: false,
keyGenerator: (req, res) => {
return req.realIP
}
});
export const authLimiter = (req: any, res: any, next: any) => {

View File

@@ -11,7 +11,6 @@ import swaggerUi = require("swagger-ui-express");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const swaggerFile = require("../spec.json");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const requestIp = require("request-ip");
import { apiLimiter } from "./helpers/rateLimiter";
import {
workspace as eeWorkspaceRouter,
@@ -84,8 +83,6 @@ const main = async () => {
})
);
app.use(requestIp.mw());
if ((await getNodeEnv()) === "production") {
// enable app-wide rate-limiting + helmet security
// in production
@@ -94,6 +91,13 @@ const main = async () => {
app.use(helmet());
}
app.use((req, res, next) => {
// default to IP address provided by Cloudflare
const cfIp = req.headers['cf-connecting-ip'];
req.realIP = Array.isArray(cfIp) ? cfIp[0] : (cfIp as string) || req.ip;
next();
});
// (EE) routes
app.use("/api/v1/secret", eeSecretRouter);
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);

View File

@@ -90,7 +90,7 @@ const requireAuth = ({
authMode,
authPayload, // User, ServiceAccount, ServiceTokenData
authChannel: getChannelFromUserAgent(req.headers['user-agent']),
authIP: req.ip,
authIP: req.realIP,
authUserAgent: req.headers['user-agent'] ?? 'other',
tokenVersionId: req.tokenVersionId
}

View File

@@ -42,6 +42,7 @@ declare global {
query?: any;
tokenVersionId?: Types.ObjectId;
authData: AuthData;
realIP: string;
requestData: {
[key: string]: string
};

View File

@@ -262,6 +262,7 @@ export default function UserInfoStep({
setPassword(pass);
checkPassword({
password: pass,
commonPasswords,
setErrors
});
}}
@@ -278,7 +279,10 @@ export default function UserInfoStep({
{Object.keys(errors).map((key) => {
if (errors[key as keyof Errors]) {
return (
<div className="ml-1 flex flex-row items-top justify-start">
<div
className="ml-1 flex flex-row items-top justify-start"
key={key}
>
<div>
<FontAwesomeIcon
icon={faXmark}

View File

@@ -188,7 +188,7 @@ export default function PersonalSettings() {
{Object.keys(errors).map((key) => {
if (errors[key as keyof Errors]) {
return (
<div className="ml-1 flex flex-row items-top justify-start">
<div className="ml-1 flex flex-row items-top justify-start" key={key}>
<div>
<FontAwesomeIcon
icon={faXmark}
@@ -212,6 +212,7 @@ export default function PersonalSettings() {
onButtonPressed={() => {
const errorCheck = checkPassword({
password: newPassword,
commonPasswords,
setErrors
});
if (!errorCheck) {

View File

@@ -263,6 +263,7 @@ export default function SignupInvite() {
setPassword(pass);
checkPassword({
password: pass,
commonPasswords,
setErrors
});
}}
@@ -279,7 +280,7 @@ export default function SignupInvite() {
{Object.keys(errors).map((key) => {
if (errors[key as keyof Errors]) {
return (
<div className="ml-1 flex flex-row items-top justify-start">
<div className="ml-1 flex flex-row items-top justify-start" key={key}>
<div>
<FontAwesomeIcon
icon={faXmark}