Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 047e156cfb | |||
| cf8e409bb3 | |||
| 3565ad3e7c | |||
| 0c78187a10 | |||
| 834d25a99e | |||
| bc46f6f64b | |||
| a67980b29d | |||
| 07eb242c26 | |||
| 7880551c4d | |||
| f71acd86df | |||
| 98fbb5b678 | |||
| 0c2c837028 | |||
| a5b166f41d | |||
| 89de1829c2 | |||
| fbca98984b | |||
| 06ab784441 | |||
| 4da2310e95 | |||
| a8f4072f1c | |||
| 93bcfc67fe | |||
| ba49946974 | |||
| d16b296b15 | |||
| 3fc61ac5ce | |||
| ced51e4801 | |||
| 254c090605 | |||
| 2a83ced9d8 | |||
| 52d333f085 | |||
| fbbb97b4cd | |||
| 4e29330472 | |||
| 44c82ff426 | |||
| 29e0370808 | |||
| 74399c1708 | |||
| 1dde8a6088 | |||
| e872c25332 | |||
| dea1e12700 | |||
| 055869883a |
@@ -1,5 +1,5 @@
|
||||
NEXTAUTH_SECRET=very_sensitive_secret
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
NEXTAUTH_URL=http://localhost:3000/api/v1/auth
|
||||
|
||||
# Manual installation database settings
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/linkwarden
|
||||
@@ -19,6 +19,7 @@ NEXT_PUBLIC_MAX_FILE_SIZE=
|
||||
MAX_LINKS_PER_USER=
|
||||
ARCHIVE_TAKE_COUNT=
|
||||
BROWSER_TIMEOUT=
|
||||
IGNORE_UNAUTHORIZED_CA=
|
||||
|
||||
# AWS S3 Settings
|
||||
SPACES_KEY=
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
# Security Policy
|
||||
# Security
|
||||
|
||||
## Supported Versions
|
||||
The Linkwarden team and community take security bugs in Linkwarden seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | --------- |
|
||||
| 1.x.x | ✅ |
|
||||
# Reporting Security Issues
|
||||
|
||||
## Reporting a Vulnerability
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
First off, we really appreciate the time you spent!
|
||||
Instead, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/linkwarden/linkwarden/security/advisories/new) tab.
|
||||
|
||||
If you found a vulnerability, these are the ways you can reach us:
|
||||
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message:
|
||||
[security@linkwarden.app](mailto:security@linkwarden.app)
|
||||
|
||||
Email: [security@linkwarden.app](mailto:security@linkwarden.app)
|
||||
|
||||
Or you can directly DM me via Twitter: [@daniel31x13](https://twitter.com/Daniel31X13).
|
||||
After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
|
||||
|
||||
# Preferred Languages
|
||||
|
||||
We prefer all communications to be in English.
|
||||
@@ -9,7 +9,7 @@ WORKDIR /data
|
||||
COPY ./package.json ./yarn.lock ./playwright.config.ts ./
|
||||
|
||||
# Increase timeout to pass github actions arm64 build
|
||||
RUN yarn install --network-timeout 10000000
|
||||
RUN --mount=type=cache,sharing=locked,target=/usr/local/share/.cache/yarn yarn install --network-timeout 10000000
|
||||
|
||||
RUN npx playwright install-deps && \
|
||||
apt-get clean && \
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
|
||||
## Intro & motivation
|
||||
|
||||
**Linkwarden is a self-hosted, open-source collaborative bookmark manager to collect, organize and archive webpages.** The objective is to organize useful webpages and articles you find across the web in one place, and since useful webpages can go away (see the inevitability of [Link Rot](https://www.howtogeek.com/786227/what-is-link-rot-and-how-does-it-threaten-the-web/)), Linkwarden also saves a copy of each webpage as a Screenshot and PDF, ensuring accessibility even if the original content is no longer available.
|
||||
**Linkwarden is a self-hosted, open-source collaborative bookmark manager to collect, organize and archive webpages.**
|
||||
|
||||
The objective is to organize useful webpages and articles you find across the web in one place, and since useful webpages can go away (see the inevitability of [Link Rot](https://www.howtogeek.com/786227/what-is-link-rot-and-how-does-it-threaten-the-web/)), Linkwarden also saves a copy of each webpage as a Screenshot and PDF, ensuring accessibility even if the original content is no longer available.
|
||||
|
||||
Additionally, Linkwarden is designed with collaboration in mind, sharing links with the public and/or allowing multiple users to work together seamlessly.
|
||||
|
||||
@@ -27,17 +29,21 @@ Additionally, Linkwarden is designed with collaboration in mind, sharing links w
|
||||
<img src="./assets/dashboard.png" />
|
||||
|
||||
<div align="center">
|
||||
<img src="./assets/all_links.png" width="32%" />
|
||||
<img src="./assets/all_links.jpg" width="23%" />
|
||||
|
||||
<img src="./assets/all_collections.png" width="32%" />
|
||||
<img src="./assets/list_view.jpg" width="23%" />
|
||||
|
||||
<img src="./assets/manage_team.png" width="32%" />
|
||||
<img src="./assets/all_collections.jpg" width="23%" />
|
||||
|
||||
<img src="./assets/readable_view.png" width="32%" />
|
||||
<img src="./assets/manage_team.jpg" width="23%" />
|
||||
|
||||
<img src="./assets/public_page.png" width="32%" />
|
||||
<img src="./assets/readable_view.jpg" width="23%" />
|
||||
|
||||
<img src="./assets/light_mode.png" width="32%" />
|
||||
<img src="./assets/preserved_formats.jpg" width="23%" />
|
||||
|
||||
<img src="./assets/public_page.jpg" width="23%" />
|
||||
|
||||
<img src="./assets/light_dashboard.jpg" width="23%" />
|
||||
</div>
|
||||
|
||||
<details>
|
||||
|
||||
|
After Width: | Height: | Size: 251 KiB |
|
Before Width: | Height: | Size: 654 KiB |
|
After Width: | Height: | Size: 564 KiB |
|
Before Width: | Height: | Size: 799 KiB |
|
Before Width: | Height: | Size: 369 KiB After Width: | Height: | Size: 786 KiB |
|
After Width: | Height: | Size: 471 KiB |
|
Before Width: | Height: | Size: 785 KiB |
|
Before Width: | Height: | Size: 634 KiB |
|
After Width: | Height: | Size: 394 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 664 KiB |
|
After Width: | Height: | Size: 301 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
Before Width: | Height: | Size: 657 KiB |
|
After Width: | Height: | Size: 345 KiB |
|
Before Width: | Height: | Size: 945 KiB |
@@ -81,68 +81,75 @@ export default function LinkGrid({ link, count, className }: Props) {
|
||||
ref={ref}
|
||||
className="border border-solid border-neutral-content bg-base-200 shadow-md hover:shadow-none duration-100 rounded-2xl relative"
|
||||
>
|
||||
<div className="relative rounded-t-2xl h-40 overflow-hidden">
|
||||
{previewAvailable(link) ? (
|
||||
<Image
|
||||
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true`}
|
||||
width={1280}
|
||||
height={720}
|
||||
alt=""
|
||||
className="rounded-t-2xl select-none object-cover z-10 h-40 w-full shadow opacity-80 scale-105"
|
||||
style={{ filter: "blur(2px)" }}
|
||||
draggable="false"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
target.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
) : link.preview === "unavailable" ? (
|
||||
<div className="bg-gray-50 duration-100 h-40 bg-opacity-80"></div>
|
||||
) : (
|
||||
<div className="duration-100 h-40 bg-opacity-80 skeleton rounded-none"></div>
|
||||
)}
|
||||
<div
|
||||
style={
|
||||
{
|
||||
// background:
|
||||
// "radial-gradient(circle, rgba(255, 255, 255, 0.5), transparent)",
|
||||
<Link
|
||||
href={link.url || ""}
|
||||
target="_blank"
|
||||
className="rounded-2xl cursor-pointer"
|
||||
>
|
||||
<div className="relative rounded-t-2xl h-40 overflow-hidden">
|
||||
{previewAvailable(link) ? (
|
||||
<Image
|
||||
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true`}
|
||||
width={1280}
|
||||
height={720}
|
||||
alt=""
|
||||
className="rounded-t-2xl select-none object-cover z-10 h-40 w-full shadow opacity-80 scale-105"
|
||||
style={{ filter: "blur(2px)" }}
|
||||
draggable="false"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
target.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
) : link.preview === "unavailable" ? (
|
||||
<div className="bg-gray-50 duration-100 h-40 bg-opacity-80"></div>
|
||||
) : (
|
||||
<div className="duration-100 h-40 bg-opacity-80 skeleton rounded-none"></div>
|
||||
)}
|
||||
<div
|
||||
style={
|
||||
{
|
||||
// background:
|
||||
// "radial-gradient(circle, rgba(255, 255, 255, 0.5), transparent)",
|
||||
}
|
||||
}
|
||||
}
|
||||
className="absolute top-0 left-0 right-0 bottom-0 rounded-t-2xl flex items-center justify-center shadow rounded-md"
|
||||
>
|
||||
<LinkIcon link={link} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="divider my-0 last:hidden border-t border-neutral-content h-[1px]" />
|
||||
|
||||
<div className="p-3 mt-1">
|
||||
<p className="truncate w-full pr-8 text-primary">
|
||||
{unescapeString(link.name || link.description) || link.url}
|
||||
</p>
|
||||
|
||||
<Link
|
||||
href={link.url || ""}
|
||||
target="_blank"
|
||||
title={link.url || ""}
|
||||
className="w-fit"
|
||||
>
|
||||
<div className="flex gap-1 item-center select-none text-neutral mt-1 hover:opacity-60 duration-100">
|
||||
<i className="bi-link-45deg text-lg mt-[0.15rem] leading-none"></i>
|
||||
<p className="text-sm truncate">{shortendURL}</p>
|
||||
className="absolute top-0 left-0 right-0 bottom-0 rounded-t-2xl flex items-center justify-center shadow rounded-md"
|
||||
>
|
||||
<LinkIcon link={link} />
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<hr className="divider mt-2 mb-1 last:hidden border-t border-neutral-content h-[1px]" />
|
||||
<div className="flex justify-between text-xs text-neutral px-3 pb-1">
|
||||
<div className="cursor-pointer w-fit">
|
||||
{collection ? (
|
||||
<LinkCollection link={link} collection={collection} />
|
||||
) : undefined}
|
||||
</div>
|
||||
<LinkDate link={link} />
|
||||
</div>
|
||||
|
||||
<hr className="divider my-0 last:hidden border-t border-neutral-content h-[1px]" />
|
||||
|
||||
<div className="p-3 mt-1">
|
||||
<p className="truncate w-full pr-8 text-primary">
|
||||
{unescapeString(link.name || link.description) || link.url}
|
||||
</p>
|
||||
|
||||
<Link
|
||||
href={link.url || ""}
|
||||
target="_blank"
|
||||
title={link.url || ""}
|
||||
className="w-fit"
|
||||
>
|
||||
<div className="flex gap-1 item-center select-none text-neutral mt-1">
|
||||
<i className="bi-link-45deg text-lg mt-[0.15rem] leading-none"></i>
|
||||
<p className="text-sm truncate">{shortendURL}</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<hr className="divider mt-2 mb-1 last:hidden border-t border-neutral-content h-[1px]" />
|
||||
|
||||
<div className="flex justify-between text-xs text-neutral px-3 pb-1">
|
||||
<div className="cursor-pointer w-fit">
|
||||
{collection ? (
|
||||
<LinkCollection link={link} collection={collection} />
|
||||
) : undefined}
|
||||
</div>
|
||||
<LinkDate link={link} />
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{showInfo ? (
|
||||
<div className="p-3 absolute z-30 top-0 left-0 right-0 bottom-0 bg-base-200 rounded-2xl fade-in overflow-y-auto">
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function LinkCollection({
|
||||
return (
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
router.push(`/collections/${link.collection.id}`);
|
||||
}}
|
||||
className="flex items-center gap-1 max-w-full w-fit hover:opacity-70 duration-100"
|
||||
|
||||
@@ -55,8 +55,9 @@ export default function LinkCardCompact({ link, count, className }: Props) {
|
||||
!showInfo ? "hover:bg-base-300" : ""
|
||||
} duration-200 rounded-lg`}
|
||||
>
|
||||
<div
|
||||
onClick={() => link.url && window.open(link.url || "", "_blank")}
|
||||
<Link
|
||||
href={link.url || ""}
|
||||
target="_blank"
|
||||
className="flex items-center cursor-pointer py-3 px-3"
|
||||
>
|
||||
<div className="shrink-0">
|
||||
@@ -91,7 +92,7 @@ export default function LinkCardCompact({ link, count, className }: Props) {
|
||||
<LinkDate link={link} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<LinkActions
|
||||
link={link}
|
||||
|
||||
@@ -69,11 +69,13 @@ export default function PreservedFormatsModal({ onClose, activeLink }: Props) {
|
||||
|
||||
const isReady = () => {
|
||||
return (
|
||||
collectionOwner.archiveAsScreenshot ===
|
||||
(link && link.pdf && link.pdf !== "pending") &&
|
||||
collectionOwner.archiveAsPDF ===
|
||||
(link && link.pdf && link.pdf !== "pending") &&
|
||||
link &&
|
||||
(collectionOwner.archiveAsScreenshot === true
|
||||
? link.pdf && link.pdf !== "pending"
|
||||
: true) &&
|
||||
(collectionOwner.archiveAsPDF === true
|
||||
? link.pdf && link.pdf !== "pending"
|
||||
: true) &&
|
||||
link.readable &&
|
||||
link.readable !== "pending"
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
export default function SettingsSidebar({ className }: { className?: string }) {
|
||||
const LINKWARDEN_VERSION = "v2.4.0";
|
||||
const LINKWARDEN_VERSION = "v2.4.9";
|
||||
|
||||
const { collections } = useCollectionStore();
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ services:
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres
|
||||
restart: always
|
||||
image: ghcr.io/linkwarden/linkwarden:latest
|
||||
# build: . # uncomment this line to build from source
|
||||
image: ghcr.io/linkwarden/linkwarden:latest # comment this line to build from source
|
||||
ports:
|
||||
- 3000:3000
|
||||
volumes:
|
||||
|
||||
@@ -43,18 +43,19 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) {
|
||||
? await validateUrlSize(link.url)
|
||||
: undefined;
|
||||
|
||||
if (validatedUrl === null) throw "File is too large to be stored.";
|
||||
if (validatedUrl === null)
|
||||
throw "Something went wrong while retrieving the file size.";
|
||||
|
||||
const contentType = validatedUrl?.get("content-type");
|
||||
let linkType = "url";
|
||||
let imageExtension = "png";
|
||||
|
||||
if (!link.url) linkType = link.type;
|
||||
else if (contentType === "application/pdf") linkType = "pdf";
|
||||
else if (contentType?.includes("application/pdf")) linkType = "pdf";
|
||||
else if (contentType?.startsWith("image")) {
|
||||
linkType = "image";
|
||||
if (contentType === "image/jpeg") imageExtension = "jpeg";
|
||||
else if (contentType === "image/png") imageExtension = "png";
|
||||
if (contentType.includes("image/jpeg")) imageExtension = "jpeg";
|
||||
else if (contentType.includes("image/png")) imageExtension = "png";
|
||||
}
|
||||
|
||||
const user = link.collection?.owner;
|
||||
|
||||
@@ -14,7 +14,7 @@ export default async function getDashboardData(
|
||||
else if (query.sort === Sort.DescriptionZA) order = { description: "desc" };
|
||||
|
||||
const pinnedLinks = await prisma.link.findMany({
|
||||
take: 6,
|
||||
take: 8,
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
|
||||
@@ -67,9 +67,6 @@ export default async function postLink(
|
||||
|
||||
const validatedUrl = link.url ? await validateUrlSize(link.url) : undefined;
|
||||
|
||||
if (validatedUrl === null)
|
||||
return { response: "File is too large to be stored.", status: 400 };
|
||||
|
||||
const contentType = validatedUrl?.get("content-type");
|
||||
let linkType = "url";
|
||||
let imageExtension = "png";
|
||||
|
||||
@@ -5,6 +5,9 @@ import Stripe from "stripe";
|
||||
import { DeleteUserBody } from "@/types/global";
|
||||
import removeFile from "@/lib/api/storage/removeFile";
|
||||
|
||||
const keycloakEnabled = process.env.KEYCLOAK_CLIENT_SECRET;
|
||||
const authentikEnabled = process.env.AUTHENTIK_CLIENT_SECRET;
|
||||
|
||||
export default async function deleteUserById(
|
||||
userId: number,
|
||||
body: DeleteUserBody
|
||||
@@ -22,7 +25,7 @@ export default async function deleteUserById(
|
||||
}
|
||||
|
||||
// Then, we check if the provided password matches the one stored in the database (disabled in Keycloak integration)
|
||||
if (!process.env.KEYCLOAK_CLIENT_SECRET) {
|
||||
if (!keycloakEnabled && !authentikEnabled) {
|
||||
const isPasswordValid = bcrypt.compareSync(
|
||||
body.password,
|
||||
user.password as string
|
||||
|
||||
@@ -23,6 +23,7 @@ export default async function updateUserById(
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (ssoUser) {
|
||||
// deny changes to SSO-defined properties
|
||||
if (data.email !== user?.email) {
|
||||
@@ -49,7 +50,7 @@ export default async function updateUserById(
|
||||
status: 400,
|
||||
};
|
||||
}
|
||||
if (data.image !== "") {
|
||||
if (data.image?.startsWith("data:image/jpeg;base64")) {
|
||||
return {
|
||||
response: "SSO Users cannot change their avatar.",
|
||||
status: 400,
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import fetch from "node-fetch";
|
||||
import https from "https";
|
||||
|
||||
export default async function validateUrlSize(url: string) {
|
||||
try {
|
||||
const response = await fetch(url, { method: "HEAD" });
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized:
|
||||
process.env.IGNORE_UNAUTHORIZED_CA === "true" ? false : true,
|
||||
});
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "HEAD",
|
||||
agent: httpsAgent,
|
||||
});
|
||||
|
||||
const totalSizeMB =
|
||||
Number(response.headers.get("content-length")) / Math.pow(1024, 2);
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import fetch from "node-fetch";
|
||||
import https from "https";
|
||||
export default async function getTitle(url: string) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized:
|
||||
process.env.IGNORE_UNAUTHORIZED_CA === "true" ? false : true,
|
||||
});
|
||||
|
||||
const response = await fetch(url, {
|
||||
agent: httpsAgent,
|
||||
});
|
||||
const text = await response.text();
|
||||
|
||||
// regular expression to find the <title> tag
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "linkwarden",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.9",
|
||||
"main": "index.js",
|
||||
"repository": "https://github.com/linkwarden/linkwarden.git",
|
||||
"author": "Daniel31X13 <daniel31x13@gmail.com>",
|
||||
@@ -39,6 +39,7 @@
|
||||
"crypto-js": "^4.2.0",
|
||||
"csstype": "^3.1.2",
|
||||
"dompurify": "^3.0.6",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "8.46.0",
|
||||
"eslint-config-next": "13.4.9",
|
||||
"formidable": "^3.5.1",
|
||||
@@ -65,6 +66,7 @@
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/dompurify": "^3.0.4",
|
||||
"@types/jsdom": "^21.1.3",
|
||||
"@types/node-fetch": "^2.6.10",
|
||||
"@types/shelljs": "^0.8.15",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"daisyui": "^4.4.2",
|
||||
|
||||
@@ -249,7 +249,8 @@ if (process.env.NEXT_PUBLIC_AUTHENTIK_ENABLED === "true") {
|
||||
profile: (profile) => {
|
||||
return {
|
||||
id: profile.sub,
|
||||
name: profile.name ?? profile.preferred_username,
|
||||
username: profile.preferred_username,
|
||||
name: profile.name || "",
|
||||
email: profile.email,
|
||||
image: profile.picture,
|
||||
};
|
||||
@@ -589,13 +590,15 @@ if (process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED === "true") {
|
||||
profile: (profile) => {
|
||||
return {
|
||||
id: profile.sub,
|
||||
name: profile.name ?? profile.preferred_username,
|
||||
username: profile.preferred_username,
|
||||
name: profile.name || "",
|
||||
email: profile.email,
|
||||
image: profile.picture,
|
||||
};
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const _linkAccount = adapter.linkAccount;
|
||||
adapter.linkAccount = (account) => {
|
||||
const { "not-before-policy": _, refresh_expires_in, ...data } = account;
|
||||
|
||||
@@ -3,7 +3,7 @@ import TextInput from "@/components/TextInput";
|
||||
import CenteredForm from "@/layouts/CenteredForm";
|
||||
import { signIn } from "next-auth/react";
|
||||
import Link from "next/link";
|
||||
import { useState, FormEvent } from "react";
|
||||
import React, { useState, FormEvent } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { getLogins } from "./api/v1/logins";
|
||||
import { InferGetServerSidePropsType } from "next";
|
||||
@@ -131,18 +131,17 @@ export default function Login({
|
||||
const Buttons: any = [];
|
||||
availableLogins.buttonAuths.forEach((value, index) => {
|
||||
Buttons.push(
|
||||
<>
|
||||
<React.Fragment key={index}>
|
||||
{index !== 0 ? <div className="divider my-1">OR</div> : undefined}
|
||||
|
||||
<AccentSubmitButton
|
||||
key={index}
|
||||
type="button"
|
||||
onClick={() => loginUserButton(value.method)}
|
||||
label={`Sign in with ${value.name}`}
|
||||
className=" w-full text-center"
|
||||
loading={submitLoader}
|
||||
/>
|
||||
</>
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
return Buttons;
|
||||
|
||||
@@ -5,6 +5,9 @@ import CenteredForm from "@/layouts/CenteredForm";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import Link from "next/link";
|
||||
|
||||
const keycloakEnabled = process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED === "true";
|
||||
const authentikEnabled = process.env.NEXT_PUBLIC_AUTHENTIK_ENABLED === "true";
|
||||
|
||||
export default function Delete() {
|
||||
const [password, setPassword] = useState("");
|
||||
const [comment, setComment] = useState<string>();
|
||||
@@ -23,7 +26,7 @@ export default function Delete() {
|
||||
},
|
||||
};
|
||||
|
||||
if (process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED !== "true" && password == "") {
|
||||
if (!keycloakEnabled && !authentikEnabled && password == "") {
|
||||
return toast.error("Please fill the required fields.");
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 71 KiB |
@@ -1,3 +1,4 @@
|
||||
import 'dotenv/config';
|
||||
import { Collection, Link, User } from "@prisma/client";
|
||||
import { prisma } from "../lib/api/db";
|
||||
import archiveHandler from "../lib/api/archiveHandler";
|
||||
|
||||
@@ -12,7 +12,7 @@ declare global {
|
||||
NEXT_PUBLIC_MAX_FILE_SIZE?: string;
|
||||
MAX_LINKS_PER_USER?: string;
|
||||
ARCHIVE_TAKE_COUNT?: string;
|
||||
NEXT_PUBLIC_MAX_FILE_SIZE?: string;
|
||||
IGNORE_UNAUTHORIZED_CA?: string;
|
||||
|
||||
SPACES_KEY?: string;
|
||||
SPACES_SECRET?: string;
|
||||
|
||||
@@ -1795,6 +1795,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca"
|
||||
integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==
|
||||
|
||||
"@types/node-fetch@^2.6.10":
|
||||
version "2.6.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.10.tgz#ff5c1ceacab782f2b7ce69957d38c1c27b0dc469"
|
||||
integrity sha512-PPpPK6F9ALFTn59Ka3BaL+qGuipRfxNE8qVgkp0bVixeiR2c2/L+IVOiBdu9JhhT22sWnQEp6YyHGI2b2+CMcA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
form-data "^4.0.0"
|
||||
|
||||
"@types/node@*", "@types/node@>=8.1.0":
|
||||
version "20.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.4.tgz#c79c7cc22c9d0e97a7944954c9e663bcbd92b0cb"
|
||||
@@ -2706,6 +2714,11 @@ dompurify@^3.0.6:
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.6.tgz#925ebd576d54a9531b5d76f0a5bef32548351dae"
|
||||
integrity sha512-ilkD8YEnnGh1zJ240uJsW7AzE+2qpbOUYjacomn3AvJ6J4JhKGSZ2nh4wUIXPZrEPppaCLx5jFe8T89Rk8tQ7w==
|
||||
|
||||
dotenv@^16.3.1:
|
||||
version "16.3.1"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
|
||||
integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
|
||||
|
||||
ecc-jsbn@~0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
||||
@@ -4751,6 +4764,11 @@ prelude-ls@^1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
prettier@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.1.tgz#6ba9f23165d690b6cbdaa88cb0807278f7019848"
|
||||
integrity sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==
|
||||
|
||||
pretty-format@^3.8.0:
|
||||
version "3.8.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
|
||||
|
||||