显示图像(jpg)在使用nextauth 4.22.1进行身份验证时。

huangapple go评论69阅读模式
英文:

Show Images (jpg) against authentication with nextauth 4.22.1

问题

Sorry, but I can't provide the translation you're looking for.

英文:

Good day everybody,

as mentioned in the title, I'm trying to show images against authentication using nextauth 4.22.1. I'm using the new /app directory, which is placed in the /src directory. Sometimes it's working but most times it doesn't.

Here is my code:
\src\app\fotos\page.js:

import Link from "next/link";
import { getFolders } from "./getFolders";

export default function Fotos() {
    const folderNames = getFolders();
    return (
        <main>
            <h2>Bestellung</h2>
            <p>Hier kommen Fotos hin.</p>
            <h3>Veranstaltungen:</h3>
            <ul>
                {folderNames.map((folderName) => (
                    <li key={folderName}>
                        <Link href={`/fotos/${folderName}`}>{folderName}</Link>
                    </li>
                ))}
            </ul>
        </main>
    );
}

\src\app\fotos\getFolders.js:

import fs from "fs";
import path from "path";

export function getFolders() {
    const directoryPath = path.join(process.cwd(), "/src/app/fotos/[course]");
    const fileNames = fs.readdirSync(directoryPath);
    return fileNames.filter((e) => {
        return e !== ".gitignore" && e !== "page.js" && e !== "FotoPage.js";
    });
}

\src\app\fotos\[course]\page.js:

"use client";

import { SessionProvider } from "next-auth/react";
import FotoPage from "./FotoPage";

export default function Show(props) {
    return (
        <SessionProvider>
            <FotoPage props={props} />
        </SessionProvider>
    );
}

\src\app\fotos\[course]\FotoPage.js:

"use client";

import { getSession } from "next-auth/react";
import { redirect } from "next/navigation";
import Link from "next/link";
import UWImage from "@/components/UWImage";

export default async function FotoPage({ props }) {
    const session = await getSession();
    const folderListPromise = await fetch("/api/getfotofoldercontent");
    const folderList = await folderListPromise.json();
    const course = props.params.course;
    if (folderList.folderNames.includes(course)) {
        if (session?.user.name === course) {
            const fileNamesPromise = await fetch(`/api/getfotofoldercontent/${course}`);
            const fileNamesList = await fileNamesPromise.json();
            return (
                <main>
                    <h2>Fotos aus Kurs {course}</h2>
                    <div style={{ display: "flex", flexWrap: "wrap", gap: "10px", justifyContent: "center" }}>
                        {fileNamesList.fileNames.map((fileName) => (
                            <div key={fileName} style={{ textAlign: "center" }}>
                                <strong>{fileName}</strong>
                                <UWImage course={course} fileName={fileName} />
                            </div>
                        ))}
                    </div>
                </main>
            );
        } else { // not logged in as correct user
            redirect(`/api/auth/signin?callbackUrl=/fotos/${course}`);
        }
    } else {
        return (
            <main>
                <h1>
                    Den Kurs "{course}" gibt es nicht.{" "}
                    <Link href="/fotos" style={{ textDecoration: "underline" }}>
                        Hier
                    </Link>{" "}
                    geht es zu den Kursen.
                </h1>
            </main>
        );
    }
}

\src\components\UWImage.js:

"use client";

import Image from "next/image";

export default function UWImage({ course, fileName }) {
    return (
        <div>
            <Image
                src={`/api/getimage?folderName=${course}&fileName=${fileName}`}
                width={400}
                height={400}
                style={{ width: "auto", height: "30vh", borderRadius: "3%" }}
                alt={fileName}
            />
        </div>
    );
}

\src\app\api\getimage\route.js:

import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { NextResponse } from "next/server";
import { readFileSync } from "fs";
import { join } from "path";

export async function GET(request) {
    const url = new URL(request.url);
    const folderName = url.searchParams.get("folderName");
    const session = await getServerSession(authOptions);
    if (session && session.user.name === folderName) {
        const fileName = url.searchParams.get("fileName");
        const imagePath = join(process.cwd(), "src/app/fotos/[course]/", folderName, fileName);
        const image = readFileSync(imagePath);
        return new NextResponse(image, { "Cache-Control": "private" });
    } else {
        return new NextResponse(null, { status: 401 });
    }
}

\src\app\api\getfotofoldercontent\[[...folder]]\route.js:

import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { NextResponse } from "next/server";
import { getFolders } from "@/app/fotos/getFolders";
import { getFileNames } from "@/app/fotos/getFileNames";

export async function GET(params) {
    const folderName = new URL(params.url).pathname.split("/")[3];
    if (!folderName) {
        return NextResponse.json({ folderNames: getFolders()});
    }
    const session = await getServerSession(authOptions);
    if (session && session.user.name === folderName){
        return NextResponse.json({ fileNames: getFileNames(folderName)});
    }
    return new NextResponse(null, { status: 401 });
}

\src\app\fotos\getFileNames.js:

import fs from "fs";
import path from "path";

export function getFileNames(folderName) {
    const directoryPath = path.join(process.cwd(), "/src/app/fotos/[course]", folderName);
    const fileNames = fs.readdirSync(directoryPath);
    return fileNames;
}

\src\app\api\auth\signin\page.js:

import LoginForm from "@/components/LoginForm";

export default function LoginPage() {
    return (
        <main style={{ display: "flex", justifyContent: "center" }}>
            <div className="LoginContainer">
                <br />
                <div className="LoginBackgroundImage">
                    <h1 className="LoginHeading">Login</h1>
                </div>
                <br />
                <LoginForm />
            </div>
        </main>
    );
}

\src\components\LoginForm.js:

"use client";

import { signIn } from "next-auth/react";
import { useSearchParams, useRouter } from "next/navigation";
import { useState } from "react";

export default function LoginForm() {
    const router = useRouter();
    const callbackUrl = useSearchParams().get("callbackUrl") || "/";
    let [loading, setLoading] = useState(false);
    let [course, setCourse] = useState(callbackUrl.split("/").pop());
    let [password, setPassword] = useState("");
    const [error, setError] = useState("");

    async function onSubmit(e) {
        e.preventDefault();
        try {
            setLoading(true);
            const res = await signIn("credentials", {
                redirect: false,
                courseName: course,
                password: password,
                callbackUrl
            });
            setLoading(false);
            if (!res?.error) {
                setError("");
                router.push(callbackUrl);
            } else {
                setError("falsches Passwort");
            }
        } catch (error) {
            setLoading(false);
            setError(error);
        }
    }

    const handleChange = (event) => {
        event.preventDefault();
        const { name, value } = event.target;
        if (name === "course") {
            setCourse(value);
        } else if (name === "password") {
            setPassword(value);
        }
    };

    return (
        <form onSubmit={onSubmit}>
            {error ? <strong className="w3-text-red">{error}</strong> : ""}
            <div className="mb-6">
                <input
                    style={{ margin: 5 }}
                    required
                    type="text"
                    className="input-box"
                    name="course"
                    value={course}
                    onChange={handleChange}
                    placeholder="KURS32XYZ"
                />
            </div>
            <div className="mb-6">
                <input
                    style={{ margin: 5 }}
                    required
                    type="password"
                    className="input-box"
                    name="password"
                    value={password}
                    onChange={handleChange}
                    placeholder="Passwort"
                />
            </div>
            <button style={{ margin: 5, padding: 2 }} type="submit" disabled={loading}>
                {loading ? "lädt..." : "Anmelden"}
            </button>
        </form>
    );
}

\src\app\api\auth\[...nextauth]\route.js:

import { authOptions } from "@/lib/auth";
import NextAuth from "next-auth";

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

\src\lib\auth.js:

import CredentialsProvider from "next-auth/providers/credentials";

// TODO: replace with real passwords
const passwordList = {
    "5CR33N5H0T5": "12345",
    W4LLP4P3R5: "54321",
    FR4NC3: "france",
    QW3RTZ: "qwertz",
    V1T450L: "vitasol",
    "3RINN3RUNG": "erinnerung",
    H4LL0W: "halloween",
    MILRI4: "milria"
};

function isCorrectCredentials(credentials) {
    return passwordList[credentials.courseName] === credentials.password;
}

export const authOptions = {
    session: {
        strategy: "jwt"
    },
    providers: [
        CredentialsProvider({
            name: "credentials",
            credentials: {
                courseName: {
                    label: "Kursname",
                    type: "text",
                    placeholder: "KURS32XYZ"
                },
                password: { label: "Passwort", type: "password" }
            },
            async authorize(credentials) {
                return isCorrectCredentials(credentials) ? { id: "1", name: credentials.courseName } : null;
            }
        })
    ],
    pages: {
        signIn: "api/auth/signin"
    }
};

\src\app\layout.js:

import "./globals.css";
import "./w3.css";
import { Inter } from "next/font/google";
import Header from "@/components/Header";
import Footer from "@/components/Footer";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
    title: "AquaFotos - Unterwasser-Bilder",
    description: "Unterwasser-Fotos in bester Qualität"
};

export default function RootLayout({ children }) {
    return (
        <html lang="de">
            <body className={inter.className}>
                <Header />
                {children}
                <Footer />
            </body>
        </html>
    );
}

\package.json:

{
  "name": "aquafotos.com",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "eslint": "8.43.0",
    "eslint-config-next": "^12.0.4",
    "eslint-plugin-import": "^2.26.0",
    "eslint-plugin-jsx-a11y": "^6.5.1",
    "eslint-plugin-react": "^7.25.3",
    "eslint-plugin-react-hooks": "^4.6.0",
    "next": "^13.4.8-canary.2",
    "next-auth": "^4.22.1",
    "react": "18.2.0",
    "react-dom": "18.2.0"
  }
}

I hope, I didn't forget something important. I think, the content of Header and Footer is not relevant for the question, but if I'm wrong, I can share the content as well.
The error, I get to see often is, when I'm trying to see the Images of a folder (for example W4LLP4P3R5, I'm trying to access http://localhost:3000/fotos/W4LLP4P3R5), the Browser is trying to get the session over and over again. I can see that in the developer console as the value of the cookie "next-auth.session-token" is changing all the time. In my local console in vs code, I'm getting the message (node:28917) ExperimentalWarning: stream/web is an experimental feature. This feature could change at any time over and over again (the number in "(node:28917)" is changing).
In the browser console, I'm getting the following error sometimes:

Uncaught (in promise) TypeError: param is undefined
    FotoPage FotoPage.js:8
    describeNativeComponentFrame react-dom.development.js:2534
    describeFunctionComponentFrame react-dom.development.js:2629
    describeFiber react-dom.development.js:2708
    getStackByFiberInDevAndProd react-dom.development.js:2727
    createCapturedValueAtFiber react-dom.development.js:14060
    throwException react-dom.development.js:14550
    throwAndUnwindWorkLoop react-dom.development.js:24676
    renderRootConcurrent react-dom.development.js:24291
    performConcurrentWorkOnRoot react-dom.development.js:23343
    workLoop scheduler.development.js:261
    flushWork scheduler.development.js:230
    performWorkUntilDeadline scheduler.development.js:537
    EventHandlerNonNull* scheduler.development.js:575
    <anonymous> scheduler.development.js:638
    NextJS 4
    <anonymous> index.js:6
    NextJS 4
    <anonymous> react-dom.development.js:27
    <anonymous> react-dom.development.js:36657
    NextJS 4
    <anonymous> index.js:37
    NextJS 4
    <anonymous> client.js:3
    NextJS 4
    <anonymous> app-index.js:14
    NextJS 4
    <anonymous> app-next-dev.js:8
    appBootstrap app-bootstrap.js:58
    loadScriptsInSequence app-bootstrap.js:23
    appBootstrap app-bootstrap.js:57
    <anonymous> app-next-dev.js:7
    NextJS 7
FotoPage.js:8:39

But in some cases it's just working fine. The error above suggests that when calling http://localhost:3000/fotos/W4LLP4P3R5 (for example) the parameter is undefined, but I don't know how that can be.
Expected behaviour: Try to get the session. If I'm logged in as the user with the correct name (name of the folder, in this case "W4LLP4P3R5"), show the images, the folder contains. If I'm not logged in, or logged in as a wrong user, redirect to login-page. After login, redirect back to the callbackUrl.
You can try this with every Images-Folder. Just place the folder in \src\app\fotos\[course] and place a password-entry in \src\lib\auth.js.
Maybe I'm thinking too complicated and what I want to achieve is much easier to do. if anyone has an idea, i'm grateful for it.

答案1

得分: 0

以下是翻译好的部分:

在 \src\app\fotos[course]\FotoPage.js 中的更改:

  • 用 useRouter().push 方法替换来自 next/navigation 的重定向方法。
  • 删除包围 JSX 的
    标签。

在 \src\app\fotos[course]\page.js 中的更改:

  • 标签包围 标签。

在 \src\app\fotos\page.js 中的更改:

  • 标签中添加 prefetch={false}。

请注意,这些更改是为了解决某个问题,可能与你的具体情况有关。如果需要进一步的帮助,请提供更多上下文信息。

英文:

Just in case anyone else faces the same problem: I got it to work reliably with the following changes:

In \src\app\fotos\[course]\FotoPage.js:

  • replace the redirect-method from next/navigation with the useRouter().push-method
  • delete the <main>-tags which surrounded the JSX
    <!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

&quot;use client&quot;;

import { getSession } from &quot;next-auth/react&quot;;
import { useRouter } from &quot;next/navigation&quot;;
import Link from &quot;next/link&quot;;
import UWImage from &quot;@/components/UWImage&quot;;

export default async function FotoPage({ props }) {
    const router = useRouter();
    const session = await getSession();
    const folderListPromise = await fetch(&quot;/api/getfotofoldercontent&quot;);
    const folderList = await folderListPromise.json();
    const course = props.params.course;
    if (folderList.folderNames.includes(course)) {
        if (session?.user.name === course) {
            const fileNamesPromise = await fetch(`/api/getfotofoldercontent/${course}`);
            const fileNamesList = await fileNamesPromise.json();
            return (
                &lt;&gt;
                    &lt;h2&gt;Fotos aus Kurs {course}&lt;/h2&gt;
                    &lt;div style={{ display: &quot;flex&quot;, flexWrap: &quot;wrap&quot;, gap: &quot;10px&quot;, justifyContent: &quot;center&quot; }}&gt;
                        {fileNamesList.fileNames.map((fileName) =&gt; (
                            &lt;div key={fileName} style={{ textAlign: &quot;center&quot; }}&gt;
                                &lt;strong&gt;{fileName}&lt;/strong&gt;
                                &lt;UWImage course={course} fileName={fileName} /&gt;
                            &lt;/div&gt;
                        ))}
                    &lt;/div&gt;
                &lt;/&gt;
            );
        } else {
            // not logged in as correct user
            router.push(`/api/auth/signin?callbackUrl=/fotos/${course}`);
        }
    } else {
        return (
            &lt;h1&gt;
                Den Kurs &amp;quot;{course}&amp;quot; gibt es nicht.{&quot; &quot;}
                &lt;Link href=&quot;/fotos&quot; style={{ textDecoration: &quot;underline&quot; }}&gt;
                    Hier
                &lt;/Link&gt;{&quot; &quot;}
                geht es zu den Kursen.
            &lt;/h1&gt;
        );
    }
}

<!-- end snippet -->

In \src\app\fotos\[course]\page.js:

  • surround &lt;Fotopage&gt;-tag with &lt;main&gt;-tag
    <!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

&quot;use client&quot;;

import { SessionProvider } from &quot;next-auth/react&quot;;
import FotoPage from &quot;./FotoPage&quot;;

export default function Show(props) {
    return (
        &lt;SessionProvider&gt;
            &lt;main&gt;
                &lt;FotoPage props={props} /&gt;
            &lt;/main&gt;
        &lt;/SessionProvider&gt;
    );
}

<!-- end snippet -->

In \src\app\fotos\page.js:

  • add prefetch={false} to the &lt;Link&gt;-tag
    <!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

import Link from &quot;next/link&quot;;
import { getFolders } from &quot;./getFolders&quot;;

export default function Fotos() {
    const folderNames = getFolders();
    return (
        &lt;main&gt;
            &lt;h2&gt;Bestellung&lt;/h2&gt;
            &lt;p&gt;Hier kommen Fotos hin.&lt;/p&gt;
            &lt;h3&gt;Veranstaltungen:&lt;/h3&gt;
            &lt;ul&gt;
                {folderNames.map((folderName) =&gt; (
                    &lt;li key={folderName}&gt;
                        &lt;Link href={`/fotos/${folderName}`} prefetch={false}&gt;{folderName}&lt;/Link&gt;
                    &lt;/li&gt;
                ))}
            &lt;/ul&gt;
        &lt;/main&gt;
    );
}

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年6月26日 00:12:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76551346.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定