diff --git a/cloudflare-workers/src/index.ts b/cloudflare-workers/src/index.ts
index 3ac0af3a..7510eefc 100644
--- a/cloudflare-workers/src/index.ts
+++ b/cloudflare-workers/src/index.ts
@@ -13,11 +13,10 @@
 
 import { AutoRouter, cors, error } from 'itty-router';
 import {
-	Commenter,
 	DeleteCommentIDParam,
 	GetCommentBody,
 	GetCommentRespBody,
-	ModifiedCommentBody,
+	JWTPayload,
 	OAuthState,
 	PatchCommentBody,
 	PatchCommentIDBody,
@@ -27,105 +26,21 @@ import {
 	ResponseBody,
 } from './types';
 import { deleteComment, getComment, getUserOfComment, modifyComment, postComment, registerUser } from './db';
-import {
-	validateSecret,
-	setCommitHash,
-	compareCommitHash,
-	modifyComments,
-	renameComments,
-	sendCommentUpdateToTelegram,
-} from './administration';
+import { setCommitHash, compareCommitHash, modifyComments, renameComments, sendCommentUpdateToTelegram } from './administration';
 import { matchCommentCache, purgeAllCommentCache, purgeCommentCache, putCommentCache } from './cache';
-import { signJWT, verifyAndDecodeJWT } from './utils';
+import { signJWT } from './utils';
 import { getAccessToken, getUserInfo } from './oauth';
-
-function validatePath(path: string | undefined): boolean {
-	if (path === undefined) {
-		return false;
-	}
-
-	if (!path.startsWith('/')) {
-		return false;
-	}
-
-	return true;
-}
-
-function validateAndDecodePath(path: string | undefined): string | null {
-	if (path === undefined) {
-		return null;
-	}
-
-	path = decodeURIComponent(path);
-
-	if (!path.startsWith('/')) {
-		return null;
-	}
-
-	return path;
-}
-
-function validateDiff(diff: ModifiedCommentBody['diff']): boolean {
-	return diff != undefined && diff instanceof Array === true && diff.length !== 0;
-}
-
-function validateOffset(offset: PostCommentBody['offset']): boolean {
-	return offset.start >= 0 && offset.end >= 0 && offset.start < offset.end;
-}
-
-function validateComment(comment: PostCommentBody['comment'] | undefined): boolean {
-	return comment != undefined && comment.length >= 1 && comment.length <= 65535;
-}
-
-async function validateAndDecodeAuthorizationToken(env: Env, req: Request): Promise<JWTPayload | null> {
-	const authorization = req.headers.get('Authorization');
-
-	if (!authorization) {
-		return null;
-	}
-
-	const [scheme, secret] = authorization.split(' ');
-
-	if (scheme !== 'Bearer' || !secret) {
-		return null;
-	}
-
-	let token;
-	try {
-		token = (await verifyAndDecodeJWT(secret, env.OAUTH_JWT_SECRET)) as JWTPayload;
-	} catch (e) {
-		return null;
-	}
-
-	return token;
-}
-
-function validateAdministratorSecret(env: Env, req: Request): boolean {
-	const authorization = req.headers.get('Authorization');
-
-	if (!authorization) {
-		return false;
-	}
-
-	const [scheme, secret] = authorization.split(' ');
-
-	if (scheme !== 'Bearer' || !secret) {
-		return false;
-	}
-
-	return validateSecret(env, secret);
-}
-
-function validateCommitHash(hash: string | undefined): boolean {
-	return hash != undefined && hash.length > 0;
-}
-
-function isSameCommenter(commenter: Commenter | null, token: JWTPayload | null): boolean {
-	if (commenter === null || token === null) {
-		return false;
-	}
-	return commenter.oauth_provider === token.provider && commenter.oauth_user_id === token.id;
-}
+import {
+	isSameCommenter,
+	validateAdministratorSecret,
+	validateAndDecodeAuthorizationToken,
+	validateAndDecodePath,
+	validateComment,
+	validateCommitHash,
+	validateDiff,
+	validateOffset,
+	validatePath,
+} from './validation';
 
 const { preflight, corsify } = cors({
 	origin: [
@@ -153,12 +68,6 @@ const router = AutoRouter({
 	finally: [corsify],
 });
 
-type JWTPayload = {
-	provider: string;
-	id: string;
-	name: string;
-};
-
 router.post('/comment/:path', async (req, env, ctx) => {
 	const params = req.params as GetCommentBody;
 
diff --git a/cloudflare-workers/src/types.ts b/cloudflare-workers/src/types.ts
index 3effc0e6..db2d4bba 100644
--- a/cloudflare-workers/src/types.ts
+++ b/cloudflare-workers/src/types.ts
@@ -104,3 +104,9 @@ export type Commenter = {
 	oauth_user_id: string;
 	name: string;
 };
+
+export type JWTPayload = {
+	provider: string;
+	id: string;
+	name: string;
+};
diff --git a/cloudflare-workers/src/validation.ts b/cloudflare-workers/src/validation.ts
new file mode 100644
index 00000000..1f68ec2c
--- /dev/null
+++ b/cloudflare-workers/src/validation.ts
@@ -0,0 +1,91 @@
+import { validateSecret } from './administration';
+import { Commenter, JWTPayload, ModifiedCommentBody, PostCommentBody } from './types';
+import { verifyAndDecodeJWT } from './utils';
+
+export function validatePath(path: string | undefined): boolean {
+	if (path === undefined) {
+		return false;
+	}
+
+	if (!path.startsWith('/')) {
+		return false;
+	}
+
+	return true;
+}
+
+export function validateAndDecodePath(path: string | undefined): string | null {
+	if (path === undefined) {
+		return null;
+	}
+
+	path = decodeURIComponent(path);
+
+	if (!path.startsWith('/')) {
+		return null;
+	}
+
+	return path;
+}
+
+export function validateDiff(diff: ModifiedCommentBody['diff']): boolean {
+	return diff != undefined && diff instanceof Array === true && diff.length !== 0;
+}
+
+export function validateOffset(offset: PostCommentBody['offset']): boolean {
+	return offset.start >= 0 && offset.end >= 0 && offset.start < offset.end;
+}
+
+export function validateComment(comment: PostCommentBody['comment'] | undefined): boolean {
+	return comment != undefined && comment.length >= 1 && comment.length <= 65535;
+}
+
+export async function validateAndDecodeAuthorizationToken(env: Env, req: Request): Promise<JWTPayload | null> {
+	const authorization = req.headers.get('Authorization');
+
+	if (!authorization) {
+		return null;
+	}
+
+	const [scheme, secret] = authorization.split(' ');
+
+	if (scheme !== 'Bearer' || !secret) {
+		return null;
+	}
+
+	let token;
+	try {
+		token = (await verifyAndDecodeJWT(secret, env.OAUTH_JWT_SECRET)) as JWTPayload;
+	} catch (e) {
+		return null;
+	}
+
+	return token;
+}
+
+export function validateAdministratorSecret(env: Env, req: Request): boolean {
+	const authorization = req.headers.get('Authorization');
+
+	if (!authorization) {
+		return false;
+	}
+
+	const [scheme, secret] = authorization.split(' ');
+
+	if (scheme !== 'Bearer' || !secret) {
+		return false;
+	}
+
+	return validateSecret(env, secret);
+}
+
+export function validateCommitHash(hash: string | undefined): boolean {
+	return hash != undefined && hash.length > 0;
+}
+
+export function isSameCommenter(commenter: Commenter | null, token: JWTPayload | null): boolean {
+	if (commenter === null || token === null) {
+		return false;
+	}
+	return commenter.oauth_provider === token.provider && commenter.oauth_user_id === token.id;
+}