家計簿アプリを作る #35:パスワード変更機能
家計簿アプリ作成シリーズの第35回です。ログイン中のユーザーが自分のパスワードを変更できる機能を実装します。
実装方針
Better Auth の changePassword API を使ってパスワードを変更します。src/routes/(auth)/settings/ に専用ページを作成し、(auth)/+layout.server.ts の認証チェックを利用します。
+page.server.ts の実装
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
import { createAuth } from '$lib/server/auth';
export const actions: Actions = {
changePassword: async ({ request, platform, cookies }) => {
const formData = await request.formData();
const currentPassword = String(formData.get('currentPassword') ?? '');
const newPassword = String(formData.get('newPassword') ?? '');
const errors: Record<string, string> = {};
if (!currentPassword) {
errors.currentPassword = '現在のパスワードを入力してください';
}
if (!newPassword) {
errors.newPassword = '新しいパスワードを入力してください';
}
if (Object.keys(errors).length > 0) {
return fail(400, { errors, values: { currentPassword, newPassword } });
}
const auth = createAuth(platform!.env.DB);
try {
await auth.api.changePassword({
body: { currentPassword, newPassword },
headers: request.headers,
});
} catch (e) {
return fail(400, { error: 'パスワードの変更に失敗しました' });
}
// セキュリティのためログアウトしてログインページへ
cookies.delete('better-auth.session_token', { path: '/' });
redirect(303, '/login');
}
};
ハマりポイント①:auth という固定エクスポートはない
$lib/server/auth.ts は createAuth(d1) 関数をエクスポートしており、auth という変数はそのままでは存在しません。D1 を渡してインスタンスを生成する必要があります。
// ❌ authという固定エクスポートはない
import { auth } from '$lib/server/auth';
// ✅ createAuthを呼び出してインスタンスを作る
import { createAuth } from '$lib/server/auth';
const auth = createAuth(platform!.env.DB);
ハマりポイント②:formData.get() は null を含む
// ❌ FormDataEntryValue | null のまま渡すと型エラーになる
const currentPassword = formData.get('currentPassword');
// ✅ Stringでキャストする
const currentPassword = String(formData.get('currentPassword') ?? '');
ハマりポイント③:changePassword のレスポンスに ok はない
changePassword は成功時にユーザー情報を直接返し、fetch のような Response オブジェクトではありません。エラー時は例外がスローされるため try/catch で処理します。
// ❌ okプロパティは存在しない
const res = await auth.api.changePassword({ ... });
if (!res.ok) { ... }
// ✅ try/catchで例外を捕捉する
try {
await auth.api.changePassword({ ... });
} catch (e) {
return fail(400, { error: 'パスワードの変更に失敗しました' });
}
パスワード変更後はログアウトさせる
セキュリティの観点から、パスワード変更後はセッションを削除してログインページに戻すようにしました。
cookies.delete('better-auth.session_token', { path: '/' });
redirect(303, '/login');
+page.svelte の実装
登録・編集フォームと同じパターンでバリデーションエラーを表示します。
{#if form?.errors?.currentPassword}
<Helper class="mt-2" color="red">
<span class="font-medium">{form.errors.currentPassword}</span>
</Helper>
{/if}
form?.errors.xxx のように ?. を途中で切らさず、form?.errors?.xxx まで連鎖させる必要があります。form が null のときに errors へアクセスするとエラーになるためです。
一覧ページへのリンク追加
<A href="/settings" class="hover:underline">パスワードの変更</A>
まとめ
| ポイント | 内容 |
|---|---|
| Better Authのインスタンス化 | createAuth(platform!.env.DB) で生成する |
| FormDataの型 | String(formData.get(...) ?? '') でキャストする |
| エラーハンドリング | changePassword は try/catch で例外を捕捉する |
| 変更後の挙動 | セッションを削除してログインページにリダイレクト |
| オプショナルチェーン | form?.errors?.xxx まで連鎖させる |