Skip to content

feat: add @plainbrew/vercel-basic-auth package#7

Open
amotarao wants to merge 12 commits intomainfrom
feature/next-basic-auth-proxy
Open

feat: add @plainbrew/vercel-basic-auth package#7
amotarao wants to merge 12 commits intomainfrom
feature/next-basic-auth-proxy

Conversation

@amotarao
Copy link
Member

@amotarao amotarao commented Mar 2, 2026

Summary

Closes #4

以下を参考にライブラリ化 (skills の存在は public になっても良い)
https://github.com/plainbrew/skills/blob/a5987b1/skills/nextjs-proxy-basic-auth/SKILL.md
https://github.com/plainbrew/skills/blob/e3e39d0/skills/storybook-basic-auth/SKILL.md

  • monorepo 化のための pnpm-workspace.yaml を追加
  • packages/vercel-basic-auth (@plainbrew/vercel-basic-auth) パッケージを追加
  • Vercel Edge Middleware で使用する Basic Auth ハンドラー関数 basicAuth をエクスポート

next-utils 最初の pkg ではあるが、Next.js には依存せず、もっぱら Vercel hosting 用
その代わり Next.js でも Storybook でも利用できる
Next.js の持ち主だし、ヨシ!

使い方

import { basicAuth } from "@plainbrew/vercel-basic-auth";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export default async function proxy(request: NextRequest) {
  const basicAuthResponse = basicAuth(request, {
    username: process.env.BASIC_AUTH_USER ?? "",
    password: process.env.BASIC_AUTH_PASSWORD ?? "",
    // vercelEnvTarget: "all", // すべての Vercel 環境で Basic 認証を適用する場合
    // dev: true, // ローカル開発環境でも Basic 認証を適用する場合
  });
  if (basicAuthResponse) return basicAuthResponse;

  return NextResponse.next();
}

オプション

オプション 必須 デフォルト 説明
username string Basic Auth ユーザー名
password string Basic Auth パスワード
vercelEnvTarget string 'only-production' Basic 認証を適用する Vercel 環境
dev boolean false NODE_ENV=development でも Basic 認証を適用するか

vercelEnvTarget

挙動
only-production Vercel の production 環境のみに Basic 認証を適用
all すべての Vercel 環境に Basic 認証を適用
disabled すべての Vercel 環境で Basic 認証を無効化

🤖 Generated with Claude Code

Summary by CodeRabbit

リリースノート

  • 新機能
    • Vercelデプロイメント向けの基本HTTP認証機能を提供する新しいパッケージを追加しました。本番環境、全環境、または無効化での運用設定が可能で、ローカル開発環境でのオプション設定にも対応しています。

Basic Auth handler for Next.js proxy.ts. Reads credentials from
BASIC_AUTH_USER/PASSWORD env vars and controls Vercel environments
via BASIC_AUTH_TARGET (all | production).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

Walkthrough

新しいPNPMワークスペースを設定し、Vercelデプロイメント向けのBasic認証ミドルウェアパッケージ @plainbrew/vercel-basic-auth を追加します。TypeScriptで実装された認証機能、ビルド設定、およびドキュメントで構成されています。

Changes

Cohort / File(s) Summary
Workspace設定
pnpm-workspace.yaml
PNPMワークスペースを設定し、packages/*配下のパッケージを管理対象として宣言します。
パッケージ基本設定
packages/vercel-basic-auth/package.json, packages/vercel-basic-auth/tsconfig.json, packages/vercel-basic-auth/tsup.config.ts
新しいパッケージのメタデータ、エントリポイント、TypeScript設定、およびtsupビルドツール設定を定義します。
ドキュメントとメタデータ
packages/vercel-basic-auth/.gitignore, packages/vercel-basic-auth/README.md
Build成果物を除外し、インストール手順とオプション仕様を記載したREADMEを追加します。
実装
packages/vercel-basic-auth/src/index.ts
VercelデプロイメントにおけるHTTP Basic認証を実装します。開発モード判定、Vercel環境判定、認可ヘッダー検証、認証情報比較を含みます。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 新しいパッケージ、ぴょんぴょん跳ねて
Basic認証で守るページ
Vercelの上で、認証の舞
ユーザーネーム、パスワード確認
セキュリティ強化、完璧だ! 🔐✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed プルリクエストのタイトルは、追加されるパッケージ名と機能を明確に説明しており、変更セットの主要な目的を正確に反映しています。
Linked Issues check ✅ Passed プルリクエストはリンク済みのIssue #4の目的を完全に達成しており、Basic認証ミドルウェアとして機能するbasicAuth関数を実装しています。
Out of Scope Changes check ✅ Passed すべての変更がIssue #4で定義されたBasic認証ミドルウェアの実装に直接関連しており、スコープ外の変更は検出されません。
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/next-basic-auth-proxy

Comment @coderabbitai help to get the list of available commands and usage tips.

amotarao and others added 2 commits March 2, 2026 19:20
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@amotarao amotarao marked this pull request as draft March 2, 2026 18:22
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/next-basic-auth-proxy/src/index.ts`:
- Around line 46-53: The Authorization parsing is insufficient: first validate
the scheme by ensuring the incoming authorization header starts with the "Basic
" scheme (case-insensitive) before extracting authValue, then when decoding
(atob(authValue)) split on the first ':' only (not split all colons) so
passwords containing ':' are preserved; update the logic around
authorization/authValue and the atob(...) handling and still call unauthorized()
when the scheme/decoded parts are missing or malformed, comparing the parsed
user and password against authUser and authPassword.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b4dc88 and a6c125a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • packages/next-basic-auth-proxy/.gitignore
  • packages/next-basic-auth-proxy/README.md
  • packages/next-basic-auth-proxy/package.json
  • packages/next-basic-auth-proxy/src/index.ts
  • packages/next-basic-auth-proxy/tsconfig.json
  • packages/next-basic-auth-proxy/tsup.config.ts
  • pnpm-workspace.yaml

Comment on lines +46 to +53
const authValue = authorization.split(" ")[1];
if (authValue === undefined) {
return unauthorized();
}

try {
const [user, password] = atob(authValue).split(":");
if (user !== authUser || password !== authPassword) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Authorizationヘッダー解析が不完全です(認証失敗の誤判定リスク)。

Basic スキームの明示検証がなく、さらに split(":") のため : を含むパスワードで正しい認証情報でも失敗します。ヘッダーは scheme を確認し、復号後は最初の : だけで分割してください。

修正案
-  const authValue = authorization.split(" ")[1];
-  if (authValue === undefined) {
+  const [scheme, encoded] = authorization.split(" ", 2);
+  if (!scheme || scheme.toLowerCase() !== "basic" || !encoded) {
     return unauthorized();
   }

   try {
-    const [user, password] = atob(authValue).split(":");
+    const decoded = atob(encoded);
+    const separatorIndex = decoded.indexOf(":");
+    if (separatorIndex < 0) {
+      return unauthorized();
+    }
+    const user = decoded.slice(0, separatorIndex);
+    const password = decoded.slice(separatorIndex + 1);
     if (user !== authUser || password !== authPassword) {
       return unauthorized();
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const authValue = authorization.split(" ")[1];
if (authValue === undefined) {
return unauthorized();
}
try {
const [user, password] = atob(authValue).split(":");
if (user !== authUser || password !== authPassword) {
const [scheme, encoded] = authorization.split(" ", 2);
if (!scheme || scheme.toLowerCase() !== "basic" || !encoded) {
return unauthorized();
}
try {
const decoded = atob(encoded);
const separatorIndex = decoded.indexOf(":");
if (separatorIndex < 0) {
return unauthorized();
}
const user = decoded.slice(0, separatorIndex);
const password = decoded.slice(separatorIndex + 1);
if (user !== authUser || password !== authPassword) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/next-basic-auth-proxy/src/index.ts` around lines 46 - 53, The
Authorization parsing is insufficient: first validate the scheme by ensuring the
incoming authorization header starts with the "Basic " scheme (case-insensitive)
before extracting authValue, then when decoding (atob(authValue)) split on the
first ':' only (not split all colons) so passwords containing ':' are preserved;
update the logic around authorization/authValue and the atob(...) handling and
still call unauthorized() when the scheme/decoded parts are missing or
malformed, comparing the parsed user and password against authUser and
authPassword.

amotarao and others added 8 commits March 2, 2026 19:27
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@amotarao amotarao changed the title feat: add @plainbrew/next-basic-auth-proxy package feat: add @plainbrew/vercel-basic-auth package Mar 2, 2026
@amotarao amotarao marked this pull request as ready for review March 2, 2026 19:11
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/vercel-basic-auth/src/index.ts`:
- Around line 56-63: The Authorization header parsing is brittle: ensure the
header uses the Basic scheme and extract only the base64 payload, then decode
and split credentials on the first ':' only. Replace the current
authorization.split(" ")[1] logic with a check that
authorization.trim().startsWith("Basic "), obtain the substring after the first
space as authValue, decode with atob(authValue), and split the decoded string
using the first indexOf(':') to separate username and password (so passwords
containing ':' are preserved); keep the existing unauthorized() call when
validation fails and validate against authUsername/authPassword as before.
- Around line 20-25: Add runtime validation for the BasicAuthOptions inputs by
checking that authUsername and authPassword are non-empty strings (e.g., typeof
=== "string" and .trim().length > 0) at the start of the exported
initializer/middleware that receives { username: authUsername, password:
authPassword, vercelEnvTarget, dev }. If either value is missing/empty/invalid,
throw a clear Error (or fail-fast) describing which credential is invalid so
misconfiguration fails early; keep vercelEnvTarget and dev handling unchanged.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a6c125a and 9e5073a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • packages/vercel-basic-auth/.gitignore
  • packages/vercel-basic-auth/README.md
  • packages/vercel-basic-auth/package.json
  • packages/vercel-basic-auth/src/index.ts
  • packages/vercel-basic-auth/tsconfig.json
  • packages/vercel-basic-auth/tsup.config.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/vercel-basic-auth/README.md
  • packages/vercel-basic-auth/.gitignore

Comment on lines +20 to +25
{
username: authUsername,
password: authPassword,
vercelEnvTarget = "only-production",
dev = false,
}: BasicAuthOptions,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

認証情報オプションのランタイム検証を追加してください。

username / password は必須仕様ですが、現在は空文字や不正値を実行時に弾いていません。設定ミスを早期失敗させた方が安全です。

🔧 提案差分
 export function basicAuth(
   request: Request,
   {
     username: authUsername,
     password: authPassword,
     vercelEnvTarget = "only-production",
     dev = false,
   }: BasicAuthOptions,
 ): Response | null {
+  if (!authUsername || !authPassword) {
+    throw new TypeError("basicAuth: username and password are required");
+  }
+
   function unauthorized() {
     return new Response("Auth required", {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vercel-basic-auth/src/index.ts` around lines 20 - 25, Add runtime
validation for the BasicAuthOptions inputs by checking that authUsername and
authPassword are non-empty strings (e.g., typeof === "string" and .trim().length
> 0) at the start of the exported initializer/middleware that receives {
username: authUsername, password: authPassword, vercelEnvTarget, dev }. If
either value is missing/empty/invalid, throw a clear Error (or fail-fast)
describing which credential is invalid so misconfiguration fails early; keep
vercelEnvTarget and dev handling unchanged.

Comment on lines +56 to +63
const authValue = authorization.split(" ")[1];
if (authValue === undefined) {
return unauthorized();
}

try {
const [username, password] = atob(authValue).split(":");
if (username !== authUsername || password !== authPassword) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Authorization ヘッダのパースが脆く、正当な認証情報を誤判定します。

現状は split(" ") / split(":") 依存のため、Basic スキーム未検証かつ password に : を含むケースを正しく扱えません。Basic <base64> を厳密に検証し、最初の : でのみ分割してください。

🔧 提案差分
-  const authValue = authorization.split(" ")[1];
-  if (authValue === undefined) {
+  const matched = authorization.match(/^Basic\s+(.+)$/i);
+  if (!matched) {
     return unauthorized();
   }

   try {
-    const [username, password] = atob(authValue).split(":");
+    const decoded = atob(matched[1]);
+    const separatorIndex = decoded.indexOf(":");
+    if (separatorIndex < 0) {
+      return unauthorized();
+    }
+    const username = decoded.slice(0, separatorIndex);
+    const password = decoded.slice(separatorIndex + 1);
     if (username !== authUsername || password !== authPassword) {
       return unauthorized();
     }
   } catch {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const authValue = authorization.split(" ")[1];
if (authValue === undefined) {
return unauthorized();
}
try {
const [username, password] = atob(authValue).split(":");
if (username !== authUsername || password !== authPassword) {
const matched = authorization.match(/^Basic\s+(.+)$/i);
if (!matched) {
return unauthorized();
}
try {
const decoded = atob(matched[1]);
const separatorIndex = decoded.indexOf(":");
if (separatorIndex < 0) {
return unauthorized();
}
const username = decoded.slice(0, separatorIndex);
const password = decoded.slice(separatorIndex + 1);
if (username !== authUsername || password !== authPassword) {
return unauthorized();
}
} catch {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vercel-basic-auth/src/index.ts` around lines 56 - 63, The
Authorization header parsing is brittle: ensure the header uses the Basic scheme
and extract only the base64 payload, then decode and split credentials on the
first ':' only. Replace the current authorization.split(" ")[1] logic with a
check that authorization.trim().startsWith("Basic "), obtain the substring after
the first space as authValue, decode with atob(authValue), and split the decoded
string using the first indexOf(':') to separate username and password (so
passwords containing ':' are preserved); keep the existing unauthorized() call
when validation fails and validate against authUsername/authPassword as before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Basic Auth middleware 作成

1 participant