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
+851 -806
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -31,15 +31,14 @@
"libsodium-wrappers": "^0.7.10", "libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mongoose": "^6.10.5", "mongoose": "^6.10.5",
"node-cache": "^5.1.2",
"nanoid": "^3.3.6", "nanoid": "^3.3.6",
"node-cache": "^5.1.2",
"nodemailer": "^6.8.0", "nodemailer": "^6.8.0",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "^2.0.0",
"posthog-node": "^2.6.0", "posthog-node": "^2.6.0",
"query-string": "^7.1.3", "query-string": "^7.1.3",
"rate-limit-mongo": "^2.3.2", "rate-limit-mongo": "^2.3.2",
"request-ip": "^3.3.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"stripe": "^10.7.0", "stripe": "^10.7.0",
"swagger-autogen": "^2.22.0", "swagger-autogen": "^2.22.0",
+4 -4
View File
@@ -126,13 +126,13 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.ip, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers['user-agent'] ?? ''
}); });
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.ip, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers['user-agent'] ?? ''
}); });
@@ -153,7 +153,7 @@ export const login2 = async (req: Request, res: Response) => {
userId: user._id, userId: user._id,
actions: [loginAction], actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']), channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip ipAddress: req.realIP
}); });
// return (access) token in response // return (access) token in response
@@ -210,7 +210,7 @@ export const logout = async (req: Request, res: Response) => {
userId: req.user._id, userId: req.user._id,
actions: [logoutAction], actions: [logoutAction],
channel: getChannelFromUserAgent(req.headers['user-agent']), channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip ipAddress: req.realIP
}); });
} catch (err) { } catch (err) {
@@ -130,7 +130,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
workspaceId, workspaceId,
environment, environment,
channel: channel ? channel : 'cli', channel: channel ? channel : 'cli',
ipAddress: req.ip ipAddress: req.realIP
}); });
key = await Key.findOne({ key = await Key.findOne({
@@ -199,7 +199,7 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => {
workspaceId, workspaceId,
environment, environment,
channel: 'cli', channel: 'cli',
ipAddress: req.ip ipAddress: req.realIP
}); });
key = { key = {
+6 -6
View File
@@ -151,14 +151,14 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.ip, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers['user-agent'] ?? ''
}); });
// issue tokens // issue tokens
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.ip, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers['user-agent'] ?? ''
}); });
@@ -215,7 +215,7 @@ export const login2 = async (req: Request, res: Response) => {
userId: user._id, userId: user._id,
actions: [loginAction], actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']), channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip ipAddress: req.realIP
}); });
return res.status(200).send(response); return res.status(200).send(response);
@@ -296,14 +296,14 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.ip, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers['user-agent'] ?? ''
}); });
// issue tokens // issue tokens
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.ip, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers['user-agent'] ?? ''
}); });
@@ -363,7 +363,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
userId: user._id, userId: user._id,
actions: [loginAction], actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']), channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip ipAddress: req.realIP
}); });
return res.status(200).send(resObj); return res.status(200).send(resObj);
@@ -296,7 +296,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
actions, actions,
channel, channel,
ipAddress: req.ip, ipAddress: req.realIP,
}); });
} }
@@ -562,7 +562,7 @@ export const createSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
actions: [addAction], actions: [addAction],
channel, channel,
ipAddress: req.ip, ipAddress: req.realIP,
})); }));
// (EE) take a secret snapshot // (EE) take a secret snapshot
@@ -784,7 +784,7 @@ export const getSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId as string), workspaceId: new Types.ObjectId(workspaceId as string),
actions: [readAction], actions: [readAction],
channel, channel,
ipAddress: req.ip, ipAddress: req.realIP,
})); }));
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
@@ -1019,7 +1019,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(key), workspaceId: new Types.ObjectId(key),
actions: [updateAction], actions: [updateAction],
channel, channel,
ipAddress: req.ip, ipAddress: req.realIP,
})); }));
// (EE) take a secret snapshot // (EE) take a secret snapshot
@@ -1157,7 +1157,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(key), workspaceId: new Types.ObjectId(key),
actions: [deleteAction], actions: [deleteAction],
channel, channel,
ipAddress: req.ip, ipAddress: req.realIP,
})); }));
// (EE) take a secret snapshot // (EE) take a secret snapshot
@@ -117,7 +117,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
// issue tokens // issue tokens
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.ip, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers['user-agent'] ?? ''
}); });
@@ -250,7 +250,7 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
// issue tokens // issue tokens
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.ip, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers['user-agent'] ?? ''
}); });
@@ -70,7 +70,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
environment, environment,
secrets, secrets,
channel: channel ? channel : 'cli', channel: channel ? channel : 'cli',
ipAddress: req.ip ipAddress: req.realIP
}); });
await pushKeys({ await pushKeys({
@@ -145,7 +145,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
workspaceId, workspaceId,
environment, environment,
channel: channel ? channel : 'cli', channel: channel ? channel : 'cli',
ipAddress: req.ip ipAddress: req.realIP
}); });
if (channel !== 'cli') { if (channel !== 'cli') {
+3 -3
View File
@@ -179,14 +179,14 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.ip, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers['user-agent'] ?? ''
}); });
// issue tokens // issue tokens
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.ip, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers['user-agent'] ?? ''
}); });
@@ -243,7 +243,7 @@ export const login2 = async (req: Request, res: Response) => {
userId: user._id, userId: user._id,
actions: [loginAction], actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']), channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip ipAddress: req.realIP
}); });
return res.status(200).send(response); return res.status(200).send(response);
@@ -138,7 +138,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
// issue tokens // issue tokens
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.ip, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers['user-agent'] ?? ''
}); });
+11 -2
View File
@@ -15,6 +15,9 @@ export const apiLimiter = rateLimit({
legacyHeaders: false, legacyHeaders: false,
skip: (request) => { skip: (request) => {
return request.path === '/healthcheck' || request.path === '/api/status' 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, windowMs: 1000 * 60 * 60,
max: 50, max: 50,
standardHeaders: true, standardHeaders: true,
legacyHeaders: false legacyHeaders: false,
keyGenerator: (req, res) => {
return req.realIP
}
}); });
// 50 requests per 1 hour // 50 requests per 1 hour
@@ -43,7 +49,10 @@ export const passwordLimiter = rateLimit({
windowMs: 1000 * 60 * 60, windowMs: 1000 * 60 * 60,
max: 50, max: 50,
standardHeaders: true, standardHeaders: true,
legacyHeaders: false legacyHeaders: false,
keyGenerator: (req, res) => {
return req.realIP
}
}); });
export const authLimiter = (req: any, res: any, next: any) => { export const authLimiter = (req: any, res: any, next: any) => {
+7 -3
View File
@@ -11,7 +11,6 @@ import swaggerUi = require("swagger-ui-express");
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const swaggerFile = require("../spec.json"); const swaggerFile = require("../spec.json");
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const requestIp = require("request-ip");
import { apiLimiter } from "./helpers/rateLimiter"; import { apiLimiter } from "./helpers/rateLimiter";
import { import {
workspace as eeWorkspaceRouter, workspace as eeWorkspaceRouter,
@@ -84,8 +83,6 @@ const main = async () => {
}) })
); );
app.use(requestIp.mw());
if ((await getNodeEnv()) === "production") { if ((await getNodeEnv()) === "production") {
// enable app-wide rate-limiting + helmet security // enable app-wide rate-limiting + helmet security
// in production // in production
@@ -94,6 +91,13 @@ const main = async () => {
app.use(helmet()); 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 // (EE) routes
app.use("/api/v1/secret", eeSecretRouter); app.use("/api/v1/secret", eeSecretRouter);
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter); app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
+1 -1
View File
@@ -90,7 +90,7 @@ const requireAuth = ({
authMode, authMode,
authPayload, // User, ServiceAccount, ServiceTokenData authPayload, // User, ServiceAccount, ServiceTokenData
authChannel: getChannelFromUserAgent(req.headers['user-agent']), authChannel: getChannelFromUserAgent(req.headers['user-agent']),
authIP: req.ip, authIP: req.realIP,
authUserAgent: req.headers['user-agent'] ?? 'other', authUserAgent: req.headers['user-agent'] ?? 'other',
tokenVersionId: req.tokenVersionId tokenVersionId: req.tokenVersionId
} }
+1
View File
@@ -42,6 +42,7 @@ declare global {
query?: any; query?: any;
tokenVersionId?: Types.ObjectId; tokenVersionId?: Types.ObjectId;
authData: AuthData; authData: AuthData;
realIP: string;
requestData: { requestData: {
[key: string]: string [key: string]: string
}; };
@@ -262,6 +262,7 @@ export default function UserInfoStep({
setPassword(pass); setPassword(pass);
checkPassword({ checkPassword({
password: pass, password: pass,
commonPasswords,
setErrors setErrors
}); });
}} }}
@@ -278,7 +279,10 @@ export default function UserInfoStep({
{Object.keys(errors).map((key) => { {Object.keys(errors).map((key) => {
if (errors[key as keyof Errors]) { if (errors[key as keyof Errors]) {
return ( 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> <div>
<FontAwesomeIcon <FontAwesomeIcon
icon={faXmark} icon={faXmark}
@@ -188,7 +188,7 @@ export default function PersonalSettings() {
{Object.keys(errors).map((key) => { {Object.keys(errors).map((key) => {
if (errors[key as keyof Errors]) { if (errors[key as keyof Errors]) {
return ( 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> <div>
<FontAwesomeIcon <FontAwesomeIcon
icon={faXmark} icon={faXmark}
@@ -212,6 +212,7 @@ export default function PersonalSettings() {
onButtonPressed={() => { onButtonPressed={() => {
const errorCheck = checkPassword({ const errorCheck = checkPassword({
password: newPassword, password: newPassword,
commonPasswords,
setErrors setErrors
}); });
if (!errorCheck) { if (!errorCheck) {
+2 -1
View File
@@ -263,6 +263,7 @@ export default function SignupInvite() {
setPassword(pass); setPassword(pass);
checkPassword({ checkPassword({
password: pass, password: pass,
commonPasswords,
setErrors setErrors
}); });
}} }}
@@ -279,7 +280,7 @@ export default function SignupInvite() {
{Object.keys(errors).map((key) => { {Object.keys(errors).map((key) => {
if (errors[key as keyof Errors]) { if (errors[key as keyof Errors]) {
return ( 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> <div>
<FontAwesomeIcon <FontAwesomeIcon
icon={faXmark} icon={faXmark}