feat: rebuild onboarding experience
This commit is contained in:
1 parent
d091362db2
commit
e41605640b
249 files changed
+8216
-14060
No files matched your search
@@ -8,6 +8,11 @@ import Database from "better-sqlite3";
|
||||
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
||||
|
||||
import {
|
||||
getOnboardingClaimTokenFromCookieHeader,
|
||||
isClaimOnlyOnboardingAccessAllowedAsync,
|
||||
} from "@homarr/api/onboarding-claim";
|
||||
import { normalizeOnboardingStep } from "@homarr/api/onboarding-step";
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { env } from "@homarr/common/env";
|
||||
import { DB_CASING } from "@homarr/core/infrastructure/db/constants";
|
||||
@@ -75,13 +80,17 @@ const reEncryptSecrets = (tempDb: InstanceType<typeof Database>, importedKeyHex:
|
||||
const isOnboardingActiveAsync = async (): Promise<boolean> => {
|
||||
const onboardingRow = await db.query.onboarding.findFirst();
|
||||
if (!onboardingRow) return false;
|
||||
return onboardingRow.step === "start";
|
||||
return normalizeOnboardingStep(onboardingRow.step) === "start";
|
||||
};
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const session = await auth();
|
||||
const isAdmin = session?.user.permissions.includes("admin") ?? false;
|
||||
const isOnboarding = !isAdmin && (await isOnboardingActiveAsync());
|
||||
const hasOnboardingClaim = await isClaimOnlyOnboardingAccessAllowedAsync(
|
||||
db,
|
||||
getOnboardingClaimTokenFromCookieHeader(req.headers.get("cookie")),
|
||||
);
|
||||
const isOnboarding = !isAdmin && hasOnboardingClaim && (await isOnboardingActiveAsync());
|
||||
|
||||
if (!isAdmin && !isOnboarding) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import {
|
||||
claimOnboardingAsync,
|
||||
getOnboardingClaimTokenFromCookieHeader,
|
||||
onboardingClaimCookieName,
|
||||
} from "@homarr/api/onboarding-claim";
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { db } from "@homarr/db";
|
||||
|
||||
const noStoreHeaders = { "Cache-Control": "no-store" };
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const fetchSite = request.headers.get("sec-fetch-site");
|
||||
if (fetchSite && fetchSite !== "same-origin" && fetchSite !== "none") {
|
||||
return NextResponse.json({ error: "Cross-site onboarding claims are not allowed." }, { status: 403 });
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
const result = await claimOnboardingAsync(db, {
|
||||
currentToken: getOnboardingClaimTokenFromCookieHeader(request.headers.get("cookie")),
|
||||
force: session?.user.permissions.includes("admin") ?? false,
|
||||
});
|
||||
if (result.status === "finished") {
|
||||
const response = NextResponse.json({ error: "Onboarding is already complete." }, { status: 409 });
|
||||
response.cookies.delete(onboardingClaimCookieName);
|
||||
return response;
|
||||
}
|
||||
if (result.status === "forbidden") {
|
||||
return NextResponse.json({ error: "Administrator access is required." }, { status: 403 });
|
||||
}
|
||||
if (result.status === "locked") {
|
||||
return NextResponse.json(
|
||||
{ error: "Another browser has already claimed onboarding.", expiresAt: result.expiresAt },
|
||||
{ status: 423, headers: noStoreHeaders },
|
||||
);
|
||||
}
|
||||
if (result.status !== "issued" && result.status !== "active") {
|
||||
return NextResponse.json({ error: "Onboarding could not be claimed." }, { status: 409 });
|
||||
}
|
||||
|
||||
const forwardedProtocol = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
|
||||
const secure = forwardedProtocol === "https" || new URL(request.url).protocol === "https:";
|
||||
const response = NextResponse.json(
|
||||
{ status: result.status, expiresAt: result.expiresAt },
|
||||
{ headers: noStoreHeaders },
|
||||
);
|
||||
response.cookies.set(onboardingClaimCookieName, result.token, {
|
||||
httpOnly: true,
|
||||
sameSite: "strict",
|
||||
secure,
|
||||
path: "/",
|
||||
expires: new Date(result.expiresAt),
|
||||
});
|
||||
return response;
|
||||
}
|
||||
Reference in new issue
Block a user