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; +}