summaryrefslogtreecommitdiff
path: root/app/routes
diff options
context:
space:
mode:
Diffstat (limited to 'app/routes')
-rw-r--r--app/routes/auth.register.tsx68
1 files changed, 42 insertions, 26 deletions
diff --git a/app/routes/auth.register.tsx b/app/routes/auth.register.tsx
index cc4e6a9..c064eff 100644
--- a/app/routes/auth.register.tsx
+++ b/app/routes/auth.register.tsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { Link, redirect } from "react-router";
+import { redirect } from "react-router";
import {
generateRegistrationOptions,
verifyRegistrationResponse,
@@ -8,14 +8,13 @@ import {
} from "@simplewebauthn/server";
import type { Route } from "./+types/auth.register";
import { getSession, commitSession } from "~/lib/session.server";
-import { listCredentials, saveCredential } from "~/lib/db.server";
+import { saveCredential } from "~/lib/db.server";
import { rpID, rpName, origin } from "~/lib/passkey.server";
export async function loader({ request }: Route.LoaderArgs) {
const session = await getSession(request.headers.get("Cookie"));
if (session.get("authenticated")) return redirect("/");
- const hasCredentials = listCredentials().length > 0;
- return { hasCredentials };
+ return {};
}
export async function action({ request }: Route.ActionArgs) {
@@ -24,6 +23,12 @@ export async function action({ request }: Route.ActionArgs) {
const session = await getSession(request.headers.get("Cookie"));
if (intent === "options") {
+ const body = await request.json() as { token?: string };
+ const registerToken = process.env.REGISTER_TOKEN;
+ if (!registerToken || body.token !== registerToken) {
+ return Response.json({ error: "トークンが違います" }, { status: 403 });
+ }
+
const options = await generateRegistrationOptions({
rpName,
rpID,
@@ -74,8 +79,8 @@ export async function action({ request }: Route.ActionArgs) {
return Response.json({ error: "不正なリクエスト" }, { status: 400 });
}
-export default function Register({ loaderData }: Route.ComponentProps) {
- const { hasCredentials } = loaderData;
+export default function Register() {
+ const [token, setToken] = useState("");
const [status, setStatus] = useState<"idle" | "loading" | "error">("idle");
const [errorMsg, setErrorMsg] = useState("");
@@ -85,8 +90,15 @@ export default function Register({ loaderData }: Route.ComponentProps) {
try {
const { startRegistration } = await import("@simplewebauthn/browser");
- const optRes = await fetch("/auth/register?intent=options", { method: "POST" });
- if (!optRes.ok) throw new Error("オプション取得に失敗しました");
+ const optRes = await fetch("/auth/register?intent=options", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ token }),
+ });
+ if (!optRes.ok) {
+ const err = await optRes.json();
+ throw new Error(err.error ?? "オプション取得に失敗しました");
+ }
const options = await optRes.json();
const regResponse = await startRegistration({ optionsJSON: options });
@@ -115,24 +127,28 @@ export default function Register({ loaderData }: Route.ComponentProps) {
</header>
<div className="auth-box">
<h2>パスキーを登録</h2>
- {hasCredentials ? (
- <>
- <p>すでにパスキーが登録されています。</p>
- <Link to="/auth/login" className="btn" style={{ textDecoration: "none" }}>
- ログインへ
- </Link>
- </>
- ) : (
- <>
- <p>
- このデバイスの生体認証(Touch ID / Face ID など)でパスキーを作成します。
- 一度登録すると、次回からはパスキーでログインできます。
- </p>
- <button className="btn" onClick={handleRegister} disabled={status === "loading"}>
- {status === "loading" ? "登録中…" : "パスキーを登録"}
- </button>
- {status === "error" && <p className="error-msg" style={{ marginTop: ".75rem" }}>{errorMsg}</p>}
- </>
+ <p>登録トークンを入力してパスキーを作成します。</p>
+ <input
+ type="password"
+ placeholder="登録トークン"
+ value={token}
+ onChange={(e) => setToken(e.target.value)}
+ onKeyDown={(e) => e.key === "Enter" && handleRegister()}
+ style={{
+ width: "100%",
+ marginBottom: ".75rem",
+ padding: ".5rem .75rem",
+ border: "1px solid #d1d5db",
+ borderRadius: "4px",
+ fontSize: ".875rem",
+ fontFamily: "ui-monospace, monospace",
+ }}
+ />
+ <button className="btn" onClick={handleRegister} disabled={status === "loading" || !token}>
+ {status === "loading" ? "登録中…" : "パスキーを登録"}
+ </button>
+ {status === "error" && (
+ <p className="error-msg" style={{ marginTop: ".75rem" }}>{errorMsg}</p>
)}
</div>
</div>