42e7bedea4
The honeyDue Go API no longer owns identity — Ory Kratos at
NEXT_PUBLIC_KRATOS_URL does. Rewrite the web app's auth layer to use Kratos
browser self-service flows and the ory_kratos_session cookie.
- Kratos client (src/lib/kratos/): flow init/fetch/submit, whoami, logout,
message helpers, and the useKratosFlow lifecycle hook.
- Generic flow renderer (src/components/auth/): KratosFlowForm renders
ui.nodes (inputs, oidc social buttons, hidden csrf), KratosMessages
surfaces flow-level messages, AuthGate guards /app via whoami.
- Auth pages (login/register/forgot-password/verify-email/reset-password)
rewritten as Kratos login/registration/recovery/verification/settings
flows. Password change in settings now uses the Kratos settings flow.
- Proxy + serverFetch forward the ory_kratos_session cookie to the Go API
instead of "Authorization: Token". Deleted /api/auth/{login,logout,me}.
- Middleware does a cheap ory_kratos_session cookie pre-filter; AuthGate's
whoami call is authoritative.
- auth store rewritten around whoami + GET /auth/me; removed dead auth API
functions, types/auth, validations/auth, code-input.
- Added NEXT_PUBLIC_KRATOS_URL to config (.env.example) and CLAUDE.md.
npm run build passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
100 lines
3.1 KiB
TypeScript
100 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { Suspense } from "react";
|
|
import Link from "next/link";
|
|
import { Loader2 } from "lucide-react";
|
|
|
|
import { AuthFormWrapper } from "@/components/forms/auth-form-wrapper";
|
|
import { KratosFlowForm } from "@/components/auth/kratos-flow-form";
|
|
import { KratosMessages } from "@/components/auth/kratos-messages";
|
|
import { useKratosFlow } from "@/lib/kratos/use-kratos-flow";
|
|
import type { KratosFlow, KratosUiNode } from "@/lib/kratos";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Reset password — Ory Kratos `settings` browser self-service flow
|
|
// ---------------------------------------------------------------------------
|
|
// Kratos does not have a standalone "reset password" flow. After completing
|
|
// the `recovery` flow, Kratos issues a privileged (recovery) session and
|
|
// redirects the browser here. The `settings` flow then lets the user set a
|
|
// new password.
|
|
//
|
|
// This page only renders the `password` group of the settings flow so it
|
|
// reads as a focused "set new password" screen. The full settings flow
|
|
// (profile, etc.) lives under /app/settings.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function ResetPasswordForm() {
|
|
const { flow, loading, error, setFlow } = useKratosFlow("settings");
|
|
|
|
function handleResult(result: { status: number; ok: boolean; data: unknown }) {
|
|
if (result.data && typeof result.data === "object" && "ui" in result.data) {
|
|
const next = result.data as KratosFlow;
|
|
setFlow(next);
|
|
// Kratos sets the flow state to "success" once the password is updated.
|
|
if (next.state === "success") {
|
|
// Give the success message a beat, then send the user into the app.
|
|
setTimeout(() => {
|
|
window.location.href = "/app";
|
|
}, 1200);
|
|
}
|
|
return;
|
|
}
|
|
if (result.ok) {
|
|
window.location.href = "/app";
|
|
}
|
|
}
|
|
|
|
// Only render password-related nodes (csrf/method markers included).
|
|
const passwordOnlyFlow: KratosFlow | null = flow
|
|
? {
|
|
...flow,
|
|
ui: {
|
|
...flow.ui,
|
|
nodes: flow.ui.nodes.filter(
|
|
(n: KratosUiNode) => n.group === "password" || n.group === "default",
|
|
),
|
|
},
|
|
}
|
|
: null;
|
|
|
|
return (
|
|
<AuthFormWrapper
|
|
title="Set new password"
|
|
subtitle="Choose a new password for your account"
|
|
footer={
|
|
<p>
|
|
<Link href="/login" className="text-primary hover:underline">
|
|
Back to login
|
|
</Link>
|
|
</p>
|
|
}
|
|
>
|
|
<div className="flex flex-col gap-4">
|
|
<KratosMessages flow={flow} error={error} />
|
|
|
|
{loading && !flow && (
|
|
<div className="flex items-center justify-center py-6 text-muted-foreground">
|
|
<Loader2 className="animate-spin" />
|
|
</div>
|
|
)}
|
|
|
|
{passwordOnlyFlow && (
|
|
<KratosFlowForm
|
|
flow={passwordOnlyFlow}
|
|
onResult={handleResult}
|
|
submitLabel="Update password"
|
|
/>
|
|
)}
|
|
</div>
|
|
</AuthFormWrapper>
|
|
);
|
|
}
|
|
|
|
export default function ResetPasswordPage() {
|
|
return (
|
|
<Suspense>
|
|
<ResetPasswordForm />
|
|
</Suspense>
|
|
);
|
|
}
|