Skip to content

Commit 3f2622e

Browse files
committed
feat(plugins) Split trusted-bot feature in a dedicated npm package
Signed-off-by: Pierre PÉRONNET <pierre.peronnet@datadoghq.com>
1 parent 159daf8 commit 3f2622e

29 files changed

+3382
-465
lines changed

plugins/cloudflare-auto-session/functions/_middleware.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,37 @@ export const onRequestGet: PagesPluginFunction<
1414
const { request, pluginArgs, next } = context;
1515

1616
// Get the arguments given to the Plugin by the developer
17-
const { cookieName, cookieSecret, formAsset, isValid } =
17+
const { cookieName, cookieSecret, formAsset, isValid, byPass } =
1818
withDefaults(pluginArgs);
1919

2020
const session = new Session(cookieName, cookieSecret, isValid);
2121

22-
const cookie = session.getCookie(request);
23-
if (cookie === undefined) {
24-
return serveForm(formAsset)(context);
25-
}
22+
return [
23+
byPass,
24+
async (request: Request): Promise<boolean> => {
25+
const cookie = session.getCookie(request);
26+
if (cookie === undefined) {
27+
return false;
28+
}
29+
30+
if (!session.valid(cookie)) {
31+
console.info('Invalid cookie');
32+
return false;
33+
}
2634

27-
if (!session.valid(cookie)) {
28-
console.info('Invalid cookie');
29-
return serveForm(formAsset)(context).then(session.end.bind(session));
30-
}
35+
return true;
36+
},
37+
]
38+
.map(fn => fn(request))
39+
.reduce((acc, curr) => acc.then(acc => acc || curr), Promise.resolve(false))
40+
.then(trusted => {
41+
if (!trusted) {
42+
return serveForm(formAsset)(context);
43+
}
3144

32-
// Cookie is valid
33-
// Continue to the next middleware
34-
return next();
45+
// Continue to the next middleware
46+
return next();
47+
});
3548
};
3649

3750
export const onRequestPost: PagesPluginFunction<
@@ -41,6 +54,7 @@ export const onRequestPost: PagesPluginFunction<
4154
PluginArgs<CookieData>
4255
> = async ({ request, pluginArgs }) => {
4356
// Get the arguments given to the Plugin by the developer
57+
// AllowedBot is not used for POST requests
4458
const { cookieName, cookieSecret, login, isValid } = withDefaults(pluginArgs);
4559

4660
const session = new Session(cookieName, cookieSecret, isValid);

plugins/cloudflare-auto-session/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@natbienetre/cloudflare-auto-session",
33
"main": "dist/index.js",
44
"types": "index.d.ts",
5-
"version": "1.0.5",
5+
"version": "2.1.1",
66
"license": "MPL-2.0",
77
"funding": "https://github.com/sponsors/holyhope",
88
"type": "module",
@@ -12,7 +12,7 @@
1212
"tsconfig.json"
1313
],
1414
"scripts": {
15-
"build": "npx wrangler pages functions build --plugin --outdir=dist",
15+
"build": "npx wrangler pages functions build --plugin --outdir=dist --sourcemap --minify",
1616
"prepare": "npm run build",
1717
"format": "prettier --check --write \"**/*.{ts,js,mjs,json,md}\"",
1818
"prettier": "prettier --check \"**/*.{ts,js,mjs,json,md}\"",
@@ -39,5 +39,6 @@
3939
"eslint-plugin-promise": "^6.0.0",
4040
"prettier": "^3.4.2",
4141
"typescript": "*"
42-
}
42+
},
43+
"packageManager": "yarn@3.6.3+sha512.d432ab8b22b89cb8b46649ebaf196159b0c1870904224771d75fdf8b55490aaa5de4d9f028deb0425f1513bff7843e97b9d117103795a23b12d2e7c1cedcf50f"
4344
}

plugins/cloudflare-auto-session/src/args.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ export interface PluginArgsWithDefaults<Data extends CookieData> {
44
cookieName: string;
55
cookieSecret: string;
66
formAsset: string;
7+
byPass(request: Request): Promise<boolean>;
78
login: (request: Request) => Promise<SessionSpec<Data>>;
89
isValid: (data: Data) => boolean;
910
}
1011

1112
const Defaults = {
13+
byPass: (_: Request): Promise<boolean> => Promise.resolve(false),
1214
cookieName: 'cloudflare-auto-session',
1315
cookieSecret: 'secret',
1416
login: async (request: Request): Promise<SessionSpec<CookieData>> => {

plugins/cloudflare-auto-session/src/session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export const serveForm = (
139139

140140
url.pathname = assetPath;
141141

142-
console.debug('Redirecting to login form', url.toString());
142+
console.info('Serving the login form', url.toString());
143143

144144
return env.ASSETS.fetch(new Request(url, request));
145145
};

plugins/cloudflare-auto-session/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface PluginArgs<Data extends CookieData> {
2121
cookieName: string;
2222
cookieSecret: string;
2323
formAsset: string;
24+
byPass(request: Request): Promise<boolean>;
2425
login: (request: Request) => Promise<SessionSpec<Data>>;
2526
isValid: (session: Data) => boolean;
2627
}

plugins/cloudflare-env-var-password/functions/_middleware.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export const onRequest: PagesPluginFunction<
1616
getEnvVarName,
1717
missingPasswordCallback,
1818
session,
19-
allowedBots,
2019
} = withDefaults(context.pluginArgs);
2120
const passwordHash = context.env[getEnvVarName(context)];
2221

@@ -32,8 +31,7 @@ export const onRequest: PagesPluginFunction<
3231
context.request,
3332
passwordHash,
3433
passwordEncodingMethod,
35-
passwordFieldName,
36-
allowedBots
34+
passwordFieldName
3735
);
3836

3937
return autoSession<CookieData>({

plugins/cloudflare-env-var-password/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@natbienetre/cloudflare-env-var-password",
33
"main": "dist/index.js",
44
"types": "index.d.ts",
5-
"version": "0.2.4",
5+
"version": "1.1.1",
66
"license": "MPL-2.0",
77
"funding": "https://github.com/sponsors/holyhope",
88
"type": "module",
@@ -12,7 +12,7 @@
1212
"tsconfig.json"
1313
],
1414
"scripts": {
15-
"build": "npx wrangler pages functions build --plugin --outdir=dist",
15+
"build": "npx wrangler pages functions build --plugin --outdir=dist --sourcemap --minify",
1616
"prepare": "npm run build",
1717
"format": "prettier --check --write \"**/*.{ts,js,mjs,json,md}\"",
1818
"prettier": "prettier --check \"**/*.{ts,js,mjs,json,md}\"",
@@ -21,8 +21,7 @@
2121
"dev": "npm run build"
2222
},
2323
"dependencies": {
24-
"@natbienetre/cloudflare-auto-session": "1.0.5",
25-
"cidr-tools": "^11.0.2"
24+
"@natbienetre/cloudflare-auto-session": "2.1.1"
2625
},
2726
"devDependencies": {
2827
"@cloudflare/workers-types": "^4.20241230.0",
@@ -40,5 +39,6 @@
4039
"eslint-plugin-promise": "^6.0.0",
4140
"prettier": "^3.4.2",
4241
"typescript": "*"
43-
}
42+
},
43+
"packageManager": "yarn@3.6.3+sha512.d432ab8b22b89cb8b46649ebaf196159b0c1870904224771d75fdf8b55490aaa5de4d9f028deb0425f1513bff7843e97b9d117103795a23b12d2e7c1cedcf50f"
4444
}

plugins/cloudflare-env-var-password/src/args.ts

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import { GoogleBot } from './google';
21
import type {
32
PluginArgs,
43
AutoSessionArgs,
5-
AllowedBots,
64
PasswordEncodingMethod,
75
} from './types';
86

@@ -25,7 +23,6 @@ export interface PluginArgsWithDefaults {
2523
Record<string, unknown>
2624
>
2725
) => Promise<Response>;
28-
allowedBots: AllowedBots;
2926
}
3027

3128
export const Defaults = {
@@ -46,33 +43,6 @@ export const Defaults = {
4643
): Promise<Response> => {
4744
throw new Error(`Missing password for ${context.request.url}`);
4845
},
49-
allowedBots: {
50-
google: new Map<GoogleBot, boolean>([
51-
[GoogleBot.Googlebot, true],
52-
[GoogleBot.GooglebotImage, true],
53-
[GoogleBot.GooglebotVideo, true],
54-
[GoogleBot.StoreBot, true],
55-
[GoogleBot.InspectionTool, true],
56-
[GoogleBot.Other, false],
57-
[GoogleBot.OtherImage, false],
58-
[GoogleBot.OtherVideo, false],
59-
[GoogleBot.CloudVertexBot, false],
60-
[GoogleBot.Extended, false],
61-
[GoogleBot.APIsGoogle, false],
62-
[GoogleBot.AdsBot, false],
63-
[GoogleBot.AdsBotMobile, false],
64-
[GoogleBot.AdSense, false],
65-
[GoogleBot.Safety, true],
66-
[GoogleBot.AutoTriggeredFeedFetcher, false],
67-
[GoogleBot.AutoTriggeredPublisherCenter, false],
68-
[GoogleBot.AutoTriggeredReadAloud, false],
69-
[GoogleBot.AutoTriggeredSiteVerifier, false],
70-
[GoogleBot.UserTriggeredFeedFetcher, false],
71-
[GoogleBot.UserTriggeredPublisherCenter, false],
72-
[GoogleBot.UserTriggeredReadAloud, false],
73-
[GoogleBot.UserTriggeredSiteVerifier, false],
74-
]),
75-
},
7646
};
7747

7848
export function withDefaults(args: PluginArgs): PluginArgsWithDefaults {

plugins/cloudflare-env-var-password/src/authenticator.ts

Lines changed: 36 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,24 @@ import {
33
type CookieSpec,
44
} from '@natbienetre/cloudflare-auto-session';
55

6-
import type { AllowedBots, PasswordEncodingMethod, CookieData } from './types';
7-
import { allBots } from './google';
6+
import type { PasswordEncodingMethod, CookieData } from './types';
87

98
export class Auth {
109
readonly passwordEncodingMethod: PasswordEncodingMethod;
1110
readonly passwordFieldName: string;
1211
readonly expectedPasswordHash: string;
1312
readonly url: URL;
14-
readonly verifiers: Array<(req: Request) => Promise<boolean>>;
1513

1614
constructor(
1715
request: Request,
1816
passwordHash: string,
1917
passwordEncodingMethod: PasswordEncodingMethod,
20-
passwordFieldName: string,
21-
allowedBots: AllowedBots
18+
passwordFieldName: string
2219
) {
2320
this.url = new URL(request.url);
2421
this.passwordEncodingMethod = passwordEncodingMethod;
2522
this.passwordFieldName = passwordFieldName;
2623
this.expectedPasswordHash = passwordHash;
27-
this.verifiers = [...allowedBots.google]
28-
.filter(value => value[1])
29-
.map(
30-
value =>
31-
allBots.get(value[0]) ??
32-
(async (_: Request): Promise<boolean> => false)
33-
);
34-
}
35-
36-
async verify(req: Request): Promise<boolean> {
37-
console.debug('Checking if client is a trusted bot', req.cf?.botManagement);
38-
return this.verifiers
39-
.map(verif => verif(req))
40-
.reduce(
41-
async (acc, curr) => (await acc) || (await curr),
42-
Promise.resolve(false)
43-
);
4424
}
4525

4626
isValid(data: CookieData): boolean {
@@ -76,67 +56,53 @@ export class Auth {
7656
}
7757

7858
async sessionData(request: Request): Promise<SessionSpec<CookieData>> {
79-
return this.verify(request).then(verified => {
80-
if (verified) {
81-
console.info('Trusted bot detected');
59+
return request.formData().then(async formData => {
60+
const password = formData.get(this.passwordFieldName);
61+
62+
if (password === null) {
63+
console.warn('No password provided');
8264

8365
return {
84-
authenticated: true,
85-
allowed: true,
66+
authenticated: false,
67+
allowed: false,
8668
cookie: this.cookieSpec({
87-
source: 'trusted-bot',
69+
source: 'no-password',
8870
}),
8971
};
9072
}
9173

92-
return request.formData().then(async formData => {
93-
const password = formData.get(this.passwordFieldName);
94-
95-
if (password === null) {
96-
console.warn('No password provided');
97-
98-
return {
99-
authenticated: false,
100-
allowed: false,
101-
cookie: this.cookieSpec({
102-
source: 'no-password',
103-
}),
104-
};
105-
}
106-
107-
return this.hashPassword(password)
108-
.then(hashedPassword => this.expectedPasswordHash === hashedPassword)
109-
.then(passwordMatch => {
110-
if (!passwordMatch) {
111-
console.warn(
112-
`Password mismatch, expected ${this.expectedPasswordHash}`
113-
);
114-
115-
return {
116-
authenticated: true,
117-
allowed: false,
118-
cookie: this.cookieSpec({
119-
source: 'invalid-password',
120-
}),
121-
};
122-
}
123-
124-
console.info('Password match');
125-
126-
// Remove the password from the form data
127-
// before storing it in the cookie
128-
formData.delete(this.passwordFieldName);
74+
return this.hashPassword(password)
75+
.then(hashedPassword => this.expectedPasswordHash === hashedPassword)
76+
.then(passwordMatch => {
77+
if (!passwordMatch) {
78+
console.warn(
79+
`Password mismatch, expected ${this.expectedPasswordHash}`
80+
);
12981

13082
return {
13183
authenticated: true,
132-
allowed: true,
84+
allowed: false,
13385
cookie: this.cookieSpec({
134-
source: 'user-form',
135-
userData: formData,
86+
source: 'invalid-password',
13687
}),
13788
};
138-
});
139-
});
89+
}
90+
91+
console.info('Password match');
92+
93+
// Remove the password from the form data
94+
// before storing it in the cookie
95+
formData.delete(this.passwordFieldName);
96+
97+
return {
98+
authenticated: true,
99+
allowed: true,
100+
cookie: this.cookieSpec({
101+
source: 'user-form',
102+
userData: formData,
103+
}),
104+
};
105+
});
140106
});
141107
}
142108
}

0 commit comments

Comments
 (0)