Compare commits

...

40 Commits

Author SHA1 Message Date
daniel31x13 047e156cfb updated version number 2024-01-17 13:02:44 -05:00
Daniel cf8e409bb3 Merge pull request #430 from linkwarden/hotfix/file-size-error
bypass error
2024-01-17 19:08:25 +03:30
daniel31x13 3565ad3e7c bypass error 2024-01-17 10:30:35 -05:00
Daniel 0c78187a10 Merge pull request #414 from linkwarden/dev
minor update to README.md
2024-01-08 10:23:52 -05:00
daniel31x13 834d25a99e minor update to README.md 2024-01-08 10:23:24 -05:00
Daniel bc46f6f64b Merge pull request #407 from linkwarden/dev
updated .env.sample
2024-01-05 14:23:16 -05:00
daniel31x13 a67980b29d updated .env.sample 2024-01-05 14:21:58 -05:00
Daniel 07eb242c26 Merge pull request #400 from linkwarden/dev
updated version
2024-01-02 15:15:41 -05:00
daniel31x13 7880551c4d updated version 2024-01-02 15:15:14 -05:00
Daniel f71acd86df Merge pull request #399 from linkwarden/dev
bug fixed + improved docker image
2024-01-02 15:12:56 -05:00
daniel31x13 98fbb5b678 bug fixed 2024-01-02 15:11:38 -05:00
Daniel 0c2c837028 Merge pull request #398 from modem7/yarn-cache
Implement docker cache mount for yarn
2024-01-02 12:41:34 -05:00
modem7 a5b166f41d implement docker cache mount for yarn 2024-01-02 17:39:50 +00:00
Daniel 89de1829c2 Merge pull request #395 from linkwarden/dev
Revert "updated README.md"
2024-01-02 07:16:56 -05:00
daniel31x13 fbca98984b Revert "updated README.md"
This reverts commit 4da2310e95.
2024-01-02 07:16:11 -05:00
Daniel 06ab784441 Merge pull request #394 from linkwarden/dev
updated README.md
2024-01-02 07:12:50 -05:00
daniel31x13 4da2310e95 updated README.md 2024-01-02 07:12:18 -05:00
Daniel a8f4072f1c Merge pull request #393 from linkwarden/dev
updated SECURITY.md
2024-01-02 07:01:28 -05:00
daniel31x13 93bcfc67fe updated SECURITY.md 2024-01-02 07:01:04 -05:00
Daniel ba49946974 Merge pull request #391 from linkwarden/dev
Dev
2024-01-01 17:13:16 -05:00
Daniel d16b296b15 Merge pull request #390 from QAComet/qacomet/worker-environment-variables
fix: load environment variables in the worker script
2024-01-01 17:12:46 -05:00
QAComet 3fc61ac5ce fix: load environment variables in the worker script 2024-01-01 15:09:55 -07:00
daniel31x13 ced51e4801 minor fix 2024-01-01 10:37:20 -05:00
Daniel 254c090605 Merge pull request #387 from linkwarden/dev
Dev
2023-12-31 16:05:41 -05:00
daniel31x13 2a83ced9d8 updated README 2023-12-31 16:05:18 -05:00
daniel31x13 52d333f085 updated README 2023-12-31 16:03:19 -05:00
Daniel fbbb97b4cd Merge pull request #385 from linkwarden/dev
added an extra environment variable
2023-12-31 10:46:50 -05:00
daniel31x13 4e29330472 added an extra environment variable 2023-12-31 10:46:09 -05:00
Daniel 44c82ff426 Merge pull request #384 from linkwarden/dev
replaced link outer component with <Link> tag for better accessibility
2023-12-31 10:06:06 -05:00
daniel31x13 29e0370808 replaced link outer component with <Link> tag for better accessibility 2023-12-31 10:05:30 -05:00
Daniel 74399c1708 Merge pull request #383 from linkwarden/dev
update version number
2023-12-31 08:03:36 -05:00
daniel31x13 1dde8a6088 update version number 2023-12-31 08:03:15 -05:00
daniel31x13 e872c25332 small improvement + better error handling 2023-12-31 07:55:45 -05:00
Daniel dea1e12700 Merge pull request #379 from linkwarden/dev
bugs fixed
2023-12-30 09:33:39 -05:00
daniel31x13 055869883a bugs fixed 2023-12-30 09:31:53 -05:00
Daniel a5d3926d84 Merge pull request #376 from linkwarden/dev
hotfix
2023-12-30 00:05:30 -05:00
daniel31x13 eee6a807da hotfix 2023-12-30 00:03:45 -05:00
Daniel e24ae15a73 Merge pull request #375 from linkwarden/dev
Dev
2023-12-29 23:59:34 -05:00
daniel31x13 f1dadf1546 throw error if a link was blocking the queue for too long 2023-12-29 23:59:00 -05:00
daniel31x13 ee6dcdcc5b added prettier settings 2023-12-29 12:21:22 -05:00
60 changed files with 883 additions and 596 deletions
+13 -13
View File
@@ -1,22 +1,22 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the // For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node // README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{ {
"name": "Node.js & TypeScript", "name": "Node.js & TypeScript",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye", "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye",
// Features to add to the dev container. More info: https://containers.dev/features. // Features to add to the dev container. More info: https://containers.dev/features.
// "features": {}, // "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally. // Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [], // "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created. // Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install", // "postCreateCommand": "yarn install",
// Configure tool-specific properties. // Configure tool-specific properties.
// "customizations": {}, // "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
"remoteUser": "root" "remoteUser": "root"
} }
+3 -1
View File
@@ -1,5 +1,5 @@
NEXTAUTH_SECRET=very_sensitive_secret NEXTAUTH_SECRET=very_sensitive_secret
NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_URL=http://localhost:3000/api/v1/auth
# Manual installation database settings # Manual installation database settings
DATABASE_URL=postgresql://user:password@localhost:5432/linkwarden DATABASE_URL=postgresql://user:password@localhost:5432/linkwarden
@@ -18,6 +18,8 @@ RE_ARCHIVE_LIMIT=
NEXT_PUBLIC_MAX_FILE_SIZE= NEXT_PUBLIC_MAX_FILE_SIZE=
MAX_LINKS_PER_USER= MAX_LINKS_PER_USER=
ARCHIVE_TAKE_COUNT= ARCHIVE_TAKE_COUNT=
BROWSER_TIMEOUT=
IGNORE_UNAUTHORIZED_CA=
# AWS S3 Settings # AWS S3 Settings
SPACES_KEY= SPACES_KEY=
+12 -10
View File
@@ -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 | # Reporting Security Issues
| ------- | --------- |
| 1.x.x | ✅ |
## 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.
+11
View File
@@ -0,0 +1,11 @@
node_modules
.next
public
*.lock
*.log
.github
data
pgdata
+4
View File
@@ -0,0 +1,4 @@
{
"trailingComma": "es5",
"tabWidth": 2
}
+1 -1
View File
@@ -9,7 +9,7 @@ WORKDIR /data
COPY ./package.json ./yarn.lock ./playwright.config.ts ./ COPY ./package.json ./yarn.lock ./playwright.config.ts ./
# Increase timeout to pass github actions arm64 build # 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 && \ RUN npx playwright install-deps && \
apt-get clean && \ apt-get clean && \
+13 -7
View File
@@ -17,7 +17,9 @@
## Intro & motivation ## 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. 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" /> <img src="./assets/dashboard.png" />
<div align="center"> <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> </div>
<details> <details>
Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 654 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 KiB

After

Width:  |  Height:  |  Size: 786 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 634 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 664 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 657 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 945 KiB

+1 -3
View File
@@ -22,9 +22,7 @@ export default function FilterSearchDropdown({
role="button" role="button"
className="btn btn-sm btn-square btn-ghost" className="btn btn-sm btn-square btn-ghost"
> >
<i <i className="bi-funnel text-neutral text-2xl"></i>
className="bi-funnel text-neutral text-2xl"
></i>
</div> </div>
<ul className="dropdown-content z-[30] menu shadow bg-base-200 border border-neutral-content rounded-box w-44 mt-1"> <ul className="dropdown-content z-[30] menu shadow bg-base-200 border border-neutral-content rounded-box w-44 mt-1">
<li> <li>
+66 -59
View File
@@ -81,68 +81,75 @@ export default function LinkGrid({ link, count, className }: Props) {
ref={ref} ref={ref}
className="border border-solid border-neutral-content bg-base-200 shadow-md hover:shadow-none duration-100 rounded-2xl relative" 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"> <Link
{previewAvailable(link) ? ( href={link.url || ""}
<Image target="_blank"
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true`} className="rounded-2xl cursor-pointer"
width={1280} >
height={720} <div className="relative rounded-t-2xl h-40 overflow-hidden">
alt="" {previewAvailable(link) ? (
className="rounded-t-2xl select-none object-cover z-10 h-40 w-full shadow opacity-80 scale-105" <Image
style={{ filter: "blur(2px)" }} src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true`}
draggable="false" width={1280}
onError={(e) => { height={720}
const target = e.target as HTMLElement; alt=""
target.style.display = "none"; 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"
) : link.preview === "unavailable" ? ( onError={(e) => {
<div className="bg-gray-50 duration-100 h-40 bg-opacity-80"></div> const target = e.target as HTMLElement;
) : ( target.style.display = "none";
<div className="duration-100 h-40 bg-opacity-80 skeleton rounded-none"></div> }}
)} />
<div ) : link.preview === "unavailable" ? (
style={ <div className="bg-gray-50 duration-100 h-40 bg-opacity-80"></div>
{ ) : (
// background: <div className="duration-100 h-40 bg-opacity-80 skeleton rounded-none"></div>
// "radial-gradient(circle, rgba(255, 255, 255, 0.5), transparent)", )}
<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"
className="absolute top-0 left-0 right-0 bottom-0 rounded-t-2xl flex items-center justify-center shadow rounded-md" >
> <LinkIcon link={link} />
<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>
</div> </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> </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 ? ( {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"> <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 ( return (
<div <div
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.preventDefault();
router.push(`/collections/${link.collection.id}`); router.push(`/collections/${link.collection.id}`);
}} }}
className="flex items-center gap-1 max-w-full w-fit hover:opacity-70 duration-100" className="flex items-center gap-1 max-w-full w-fit hover:opacity-70 duration-100"
@@ -1,7 +1,9 @@
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
import React from "react"; import React from "react";
export default function LinkDate({ link }: { export default function LinkDate({
link,
}: {
link: LinkIncludingShortenedCollectionAndTags; link: LinkIncludingShortenedCollectionAndTags;
}) { }) {
const formattedDate = new Date(link.createdAt as string).toLocaleString( const formattedDate = new Date(link.createdAt as string).toLocaleString(
@@ -10,7 +12,7 @@ export default function LinkDate({ link }: {
year: "numeric", year: "numeric",
month: "short", month: "short",
day: "numeric", day: "numeric",
}, }
); );
return ( return (
+4 -3
View File
@@ -55,8 +55,9 @@ export default function LinkCardCompact({ link, count, className }: Props) {
!showInfo ? "hover:bg-base-300" : "" !showInfo ? "hover:bg-base-300" : ""
} duration-200 rounded-lg`} } duration-200 rounded-lg`}
> >
<div <Link
onClick={() => link.url && window.open(link.url || "", "_blank")} href={link.url || ""}
target="_blank"
className="flex items-center cursor-pointer py-3 px-3" className="flex items-center cursor-pointer py-3 px-3"
> >
<div className="shrink-0"> <div className="shrink-0">
@@ -91,7 +92,7 @@ export default function LinkCardCompact({ link, count, className }: Props) {
<LinkDate link={link} /> <LinkDate link={link} />
</div> </div>
</div> </div>
</div> </Link>
<LinkActions <LinkActions
link={link} link={link}
+2 -2
View File
@@ -49,7 +49,7 @@ export default function DeleteLinkModal({ onClose, activeLink }: Props) {
<p>Are you sure you want to delete this Link?</p> <p>Are you sure you want to delete this Link?</p>
<div role="alert" className="alert alert-warning"> <div role="alert" className="alert alert-warning">
<i className="bi-exclamation-triangle text-xl"/> <i className="bi-exclamation-triangle text-xl" />
<span> <span>
<b>Warning:</b> This action is irreversible! <b>Warning:</b> This action is irreversible!
</span> </span>
@@ -64,7 +64,7 @@ export default function DeleteLinkModal({ onClose, activeLink }: Props) {
className={`ml-auto btn w-fit text-white flex items-center gap-2 duration-100 bg-red-500 hover:bg-red-400 hover:dark:bg-red-600 cursor-pointer`} className={`ml-auto btn w-fit text-white flex items-center gap-2 duration-100 bg-red-500 hover:bg-red-400 hover:dark:bg-red-600 cursor-pointer`}
onClick={deleteLink} onClick={deleteLink}
> >
<i className="bi-trash text-xl"/> <i className="bi-trash text-xl" />
Delete Delete
</button> </button>
</div> </div>
@@ -227,10 +227,10 @@ export default function EditCollectionSharingModal({
e.canCreate && e.canUpdate && e.canDelete e.canCreate && e.canUpdate && e.canDelete
? "Admin" ? "Admin"
: e.canCreate && !e.canUpdate && !e.canDelete : e.canCreate && !e.canUpdate && !e.canDelete
? "Contributor" ? "Contributor"
: !e.canCreate && !e.canUpdate && !e.canDelete : !e.canCreate && !e.canUpdate && !e.canDelete
? "Viewer" ? "Viewer"
: undefined; : undefined;
return ( return (
<> <>
+7 -7
View File
@@ -86,7 +86,7 @@ export default function EditLinkModal({ onClose, activeLink }: Props) {
title={link.url} title={link.url}
target="_blank" target="_blank"
> >
<i className="bi-link-45deg text-xl"/> <i className="bi-link-45deg text-xl" />
<p>{shortendURL}</p> <p>{shortendURL}</p>
</Link> </Link>
) : undefined} ) : undefined}
@@ -116,13 +116,13 @@ export default function EditLinkModal({ onClose, activeLink }: Props) {
defaultValue={ defaultValue={
link.collection.id link.collection.id
? { ? {
value: link.collection.id, value: link.collection.id,
label: link.collection.name, label: link.collection.name,
} }
: { : {
value: null as unknown as number, value: null as unknown as number,
label: "Unorganized", label: "Unorganized",
} }
} }
/> />
) : null} ) : null}
@@ -69,11 +69,13 @@ export default function PreservedFormatsModal({ onClose, activeLink }: Props) {
const isReady = () => { const isReady = () => {
return ( return (
collectionOwner.archiveAsScreenshot ===
(link && link.pdf && link.pdf !== "pending") &&
collectionOwner.archiveAsPDF ===
(link && link.pdf && link.pdf !== "pending") &&
link && link &&
(collectionOwner.archiveAsScreenshot === true
? link.pdf && link.pdf !== "pending"
: true) &&
(collectionOwner.archiveAsPDF === true
? link.pdf && link.pdf !== "pending"
: true) &&
link.readable && link.readable &&
link.readable !== "pending" link.readable !== "pending"
); );
+3 -3
View File
@@ -102,9 +102,9 @@ export default function PreservedFormatRow({
) : undefined} ) : undefined}
<Link <Link
href={`${isPublic ? "/public" : ""}/preserved/${ href={`${
link?.id isPublic ? "/public" : ""
}?format=${format}`} }/preserved/${link?.id}?format=${format}`}
target="_blank" target="_blank"
className="btn btn-sm btn-square" className="btn btn-sm btn-square"
> >
+2 -2
View File
@@ -47,11 +47,11 @@ export default function SearchBar({ placeholder }: Props) {
"/public/collections/" + "/public/collections/" +
router.query.id + router.query.id +
"?q=" + "?q=" +
encodeURIComponent(searchQuery || ""), encodeURIComponent(searchQuery || "")
); );
} else { } else {
return router.push( return router.push(
"/search?q=" + encodeURIComponent(searchQuery), "/search?q=" + encodeURIComponent(searchQuery)
); );
} }
} }
+1 -1
View File
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
export default function SettingsSidebar({ className }: { className?: string }) { export default function SettingsSidebar({ className }: { className?: string }) {
const LINKWARDEN_VERSION = "v2.4.0"; const LINKWARDEN_VERSION = "v2.4.9";
const { collections } = useCollectionStore(); const { collections } = useCollectionStore();
+2 -1
View File
@@ -11,7 +11,8 @@ services:
environment: environment:
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres
restart: always 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: ports:
- 3000:3000 - 3000:3000
volumes: volumes:
+2 -2
View File
@@ -8,7 +8,7 @@ import { SetStateAction, useEffect } from "react";
type Props< type Props<
T extends T extends
| CollectionIncludingMembersAndLinkCount | CollectionIncludingMembersAndLinkCount
| LinkIncludingShortenedCollectionAndTags | LinkIncludingShortenedCollectionAndTags,
> = { > = {
sortBy: Sort; sortBy: Sort;
@@ -19,7 +19,7 @@ type Props<
export default function useSort< export default function useSort<
T extends T extends
| CollectionIncludingMembersAndLinkCount | CollectionIncludingMembersAndLinkCount
| LinkIncludingShortenedCollectionAndTags | LinkIncludingShortenedCollectionAndTags,
>({ sortBy, data, setData }: Props<T>) { >({ sortBy, data, setData }: Props<T>) {
useEffect(() => { useEffect(() => {
const dataArray = [...data]; const dataArray = [...data];
+230 -201
View File
@@ -17,232 +17,261 @@ type LinksAndCollectionAndOwner = Link & {
}; };
}; };
const BROWSER_TIMEOUT = Number(process.env.BROWSER_TIMEOUT) || 5;
export default async function archiveHandler(link: LinksAndCollectionAndOwner) { export default async function archiveHandler(link: LinksAndCollectionAndOwner) {
const browser = await chromium.launch(); const browser = await chromium.launch();
const context = await browser.newContext(devices["Desktop Chrome"]); const context = await browser.newContext(devices["Desktop Chrome"]);
const page = await context.newPage(); const page = await context.newPage();
const timeoutPromise = new Promise((_, reject) => {
setTimeout(
() =>
reject(
new Error(
`Browser has been open for more than ${BROWSER_TIMEOUT} minutes.`
)
),
BROWSER_TIMEOUT * 60000
);
});
try { try {
const validatedUrl = link.url ? await validateUrlSize(link.url) : undefined; await Promise.race([
(async () => {
const validatedUrl = link.url
? 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"); const contentType = validatedUrl?.get("content-type");
let linkType = "url"; let linkType = "url";
let imageExtension = "png"; let imageExtension = "png";
if (!link.url) linkType = link.type; 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")) { else if (contentType?.startsWith("image")) {
linkType = "image"; linkType = "image";
if (contentType === "image/jpeg") imageExtension = "jpeg"; if (contentType.includes("image/jpeg")) imageExtension = "jpeg";
else if (contentType === "image/png") imageExtension = "png"; else if (contentType.includes("image/png")) imageExtension = "png";
}
const user = link.collection?.owner;
// send to archive.org
if (user.archiveAsWaybackMachine && link.url) sendToWayback(link.url);
const targetLink = await prisma.link.update({
where: { id: link.id },
data: {
type: linkType,
image:
user.archiveAsScreenshot && !link.image?.startsWith("archive")
? "pending"
: undefined,
pdf:
user.archiveAsPDF && !link.pdf?.startsWith("archive")
? "pending"
: undefined,
readable: !link.readable?.startsWith("archive") ? "pending" : undefined,
preview: !link.readable?.startsWith("archive") ? "pending" : undefined,
lastPreserved: new Date().toISOString(),
},
});
if (linkType === "image" && !link.image?.startsWith("archive")) {
await imageHandler(link, imageExtension); // archive image (jpeg/png)
return;
} else if (linkType === "pdf" && !link.pdf?.startsWith("archive")) {
await pdfHandler(link); // archive pdf
return;
} else if (link.url) {
// archive url
await page.goto(link.url, { waitUntil: "domcontentloaded" });
const content = await page.content();
// TODO single file
// const session = await page.context().newCDPSession(page);
// const doc = await session.send("Page.captureSnapshot", {
// format: "mhtml",
// });
// const saveDocLocally = (doc: any) => {
// console.log(doc);
// return createFile({
// data: doc,
// filePath: `archives/${targetLink.collectionId}/${link.id}.mhtml`,
// });
// };
// saveDocLocally(doc.data);
// Readability
const window = new JSDOM("").window;
const purify = DOMPurify(window);
const cleanedUpContent = purify.sanitize(content);
const dom = new JSDOM(cleanedUpContent, { url: link.url || "" });
const article = new Readability(dom.window.document).parse();
const articleText = article?.textContent
.replace(/ +(?= )/g, "") // strip out multiple spaces
.replace(/(\r\n|\n|\r)/gm, " "); // strip out line breaks
if (
articleText &&
articleText !== "" &&
!link.readable?.startsWith("archive")
) {
await createFile({
data: JSON.stringify(article),
filePath: `archives/${targetLink.collectionId}/${link.id}_readability.json`,
});
await prisma.link.update({
where: { id: link.id },
data: {
readable: `archives/${targetLink.collectionId}/${link.id}_readability.json`,
textContent: articleText,
},
});
}
// Preview
const ogImageUrl = await page.evaluate(() => {
const metaTag = document.querySelector('meta[property="og:image"]');
return metaTag ? (metaTag as any).content : null;
});
createFolder({
filePath: `archives/preview/${link.collectionId}`,
});
if (ogImageUrl) {
console.log("Found og:image URL:", ogImageUrl);
// Download the image
const imageResponse = await page.goto(ogImageUrl);
// Check if imageResponse is not null
if (imageResponse && !link.preview?.startsWith("archive")) {
const buffer = await imageResponse.body();
// Check if buffer is not null
if (buffer) {
// Load the image using Jimp
Jimp.read(buffer, async (err, image) => {
if (image && !err) {
image?.resize(1280, Jimp.AUTO).quality(20);
const processedBuffer = await image?.getBufferAsync(
Jimp.MIME_JPEG
);
createFile({
data: processedBuffer,
filePath: `archives/preview/${link.collectionId}/${link.id}.jpeg`,
}).then(() => {
return prisma.link.update({
where: { id: link.id },
data: {
preview: `archives/preview/${link.collectionId}/${link.id}.jpeg`,
},
});
});
}
}).catch((err) => {
console.error("Error processing the image:", err);
});
} else {
console.log("No image data found.");
}
} }
await page.goBack(); const user = link.collection?.owner;
} else if (!link.preview?.startsWith("archive")) {
console.log("No og:image found"); // send to archive.org
await page if (user.archiveAsWaybackMachine && link.url) sendToWayback(link.url);
.screenshot({ type: "jpeg", quality: 20 })
.then((screenshot) => { const targetLink = await prisma.link.update({
return createFile({ where: { id: link.id },
data: screenshot, data: {
filePath: `archives/preview/${link.collectionId}/${link.id}.jpeg`, type: linkType,
image:
user.archiveAsScreenshot && !link.image?.startsWith("archive")
? "pending"
: undefined,
pdf:
user.archiveAsPDF && !link.pdf?.startsWith("archive")
? "pending"
: undefined,
readable: !link.readable?.startsWith("archive")
? "pending"
: undefined,
preview: !link.readable?.startsWith("archive")
? "pending"
: undefined,
lastPreserved: new Date().toISOString(),
},
});
if (linkType === "image" && !link.image?.startsWith("archive")) {
await imageHandler(link, imageExtension); // archive image (jpeg/png)
return;
} else if (linkType === "pdf" && !link.pdf?.startsWith("archive")) {
await pdfHandler(link); // archive pdf
return;
} else if (link.url) {
// archive url
await page.goto(link.url, { waitUntil: "domcontentloaded" });
const content = await page.content();
// TODO single file
// const session = await page.context().newCDPSession(page);
// const doc = await session.send("Page.captureSnapshot", {
// format: "mhtml",
// });
// const saveDocLocally = (doc: any) => {
// console.log(doc);
// return createFile({
// data: doc,
// filePath: `archives/${targetLink.collectionId}/${link.id}.mhtml`,
// });
// };
// saveDocLocally(doc.data);
// Readability
const window = new JSDOM("").window;
const purify = DOMPurify(window);
const cleanedUpContent = purify.sanitize(content);
const dom = new JSDOM(cleanedUpContent, { url: link.url || "" });
const article = new Readability(dom.window.document).parse();
const articleText = article?.textContent
.replace(/ +(?= )/g, "") // strip out multiple spaces
.replace(/(\r\n|\n|\r)/gm, " "); // strip out line breaks
if (
articleText &&
articleText !== "" &&
!link.readable?.startsWith("archive")
) {
await createFile({
data: JSON.stringify(article),
filePath: `archives/${targetLink.collectionId}/${link.id}_readability.json`,
}); });
})
.then(() => { await prisma.link.update({
return prisma.link.update({
where: { id: link.id }, where: { id: link.id },
data: { data: {
preview: `archives/preview/${link.collectionId}/${link.id}.jpeg`, readable: `archives/${targetLink.collectionId}/${link.id}_readability.json`,
textContent: articleText,
}, },
}); });
}
// Preview
const ogImageUrl = await page.evaluate(() => {
const metaTag = document.querySelector('meta[property="og:image"]');
return metaTag ? (metaTag as any).content : null;
}); });
}
// Screenshot/PDF createFolder({
await page.evaluate( filePath: `archives/preview/${link.collectionId}`,
autoScroll, });
Number(process.env.AUTOSCROLL_TIMEOUT) || 30
);
// Check if the user hasn't deleted the link by the time we're done scrolling if (ogImageUrl) {
const linkExists = await prisma.link.findUnique({ console.log("Found og:image URL:", ogImageUrl);
where: { id: link.id },
});
if (linkExists) {
const processingPromises = [];
if (user.archiveAsScreenshot && !link.image?.startsWith("archive")) { // Download the image
processingPromises.push( const imageResponse = await page.goto(ogImageUrl);
page.screenshot({ fullPage: true }).then((screenshot) => {
return createFile({ // Check if imageResponse is not null
data: screenshot, if (imageResponse && !link.preview?.startsWith("archive")) {
filePath: `archives/${linkExists.collectionId}/${link.id}.png`, const buffer = await imageResponse.body();
});
}) // Check if buffer is not null
); if (buffer) {
} // Load the image using Jimp
if (user.archiveAsPDF && !link.pdf?.startsWith("archive")) { Jimp.read(buffer, async (err, image) => {
processingPromises.push( if (image && !err) {
page image?.resize(1280, Jimp.AUTO).quality(20);
.pdf({ const processedBuffer = await image?.getBufferAsync(
width: "1366px", Jimp.MIME_JPEG
height: "1931px", );
printBackground: true,
margin: { top: "15px", bottom: "15px" }, createFile({
}) data: processedBuffer,
.then((pdf) => { filePath: `archives/preview/${link.collectionId}/${link.id}.jpeg`,
}).then(() => {
return prisma.link.update({
where: { id: link.id },
data: {
preview: `archives/preview/${link.collectionId}/${link.id}.jpeg`,
},
});
});
}
}).catch((err) => {
console.error("Error processing the image:", err);
});
} else {
console.log("No image data found.");
}
}
await page.goBack();
} else if (!link.preview?.startsWith("archive")) {
console.log("No og:image found");
await page
.screenshot({ type: "jpeg", quality: 20 })
.then((screenshot) => {
return createFile({ return createFile({
data: pdf, data: screenshot,
filePath: `archives/${linkExists.collectionId}/${link.id}.pdf`, filePath: `archives/preview/${link.collectionId}/${link.id}.jpeg`,
}); });
}) })
.then(() => {
return prisma.link.update({
where: { id: link.id },
data: {
preview: `archives/preview/${link.collectionId}/${link.id}.jpeg`,
},
});
});
}
// Screenshot/PDF
await page.evaluate(
autoScroll,
Number(process.env.AUTOSCROLL_TIMEOUT) || 30
); );
// Check if the user hasn't deleted the link by the time we're done scrolling
const linkExists = await prisma.link.findUnique({
where: { id: link.id },
});
if (linkExists) {
const processingPromises = [];
if (
user.archiveAsScreenshot &&
!link.image?.startsWith("archive")
) {
processingPromises.push(
page.screenshot({ fullPage: true }).then((screenshot) => {
return createFile({
data: screenshot,
filePath: `archives/${linkExists.collectionId}/${link.id}.png`,
});
})
);
}
if (user.archiveAsPDF && !link.pdf?.startsWith("archive")) {
processingPromises.push(
page
.pdf({
width: "1366px",
height: "1931px",
printBackground: true,
margin: { top: "15px", bottom: "15px" },
})
.then((pdf) => {
return createFile({
data: pdf,
filePath: `archives/${linkExists.collectionId}/${link.id}.pdf`,
});
})
);
}
await Promise.allSettled(processingPromises);
await prisma.link.update({
where: { id: link.id },
data: {
image: user.archiveAsScreenshot
? `archives/${linkExists.collectionId}/${link.id}.png`
: undefined,
pdf: user.archiveAsPDF
? `archives/${linkExists.collectionId}/${link.id}.pdf`
: undefined,
},
});
}
} }
await Promise.allSettled(processingPromises); })(),
await prisma.link.update({ timeoutPromise,
where: { id: link.id }, ]);
data: {
image: user.archiveAsScreenshot
? `archives/${linkExists.collectionId}/${link.id}.png`
: undefined,
pdf: user.archiveAsPDF
? `archives/${linkExists.collectionId}/${link.id}.pdf`
: undefined,
},
});
}
}
} catch (err) { } catch (err) {
console.log(err); console.log(err);
console.log("Failed Link details:", link); console.log("Failed Link details:", link);
@@ -14,7 +14,7 @@ export default async function getDashboardData(
else if (query.sort === Sort.DescriptionZA) order = { description: "desc" }; else if (query.sort === Sort.DescriptionZA) order = { description: "desc" };
const pinnedLinks = await prisma.link.findMany({ const pinnedLinks = await prisma.link.findMany({
take: 6, take: 8,
where: { where: {
AND: [ AND: [
{ {
+2 -5
View File
@@ -62,14 +62,11 @@ export default async function postLink(
link.description && link.description !== "" link.description && link.description !== ""
? link.description ? link.description
: link.url : link.url
? await getTitle(link.url) ? await getTitle(link.url)
: undefined; : undefined;
const validatedUrl = link.url ? await validateUrlSize(link.url) : undefined; 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"); const contentType = validatedUrl?.get("content-type");
let linkType = "url"; let linkType = "url";
let imageExtension = "png"; let imageExtension = "png";
@@ -5,6 +5,9 @@ import Stripe from "stripe";
import { DeleteUserBody } from "@/types/global"; import { DeleteUserBody } from "@/types/global";
import removeFile from "@/lib/api/storage/removeFile"; 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( export default async function deleteUserById(
userId: number, userId: number,
body: DeleteUserBody 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) // 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( const isPasswordValid = bcrypt.compareSync(
body.password, body.password,
user.password as string user.password as string
@@ -23,6 +23,7 @@ export default async function updateUserById(
id: userId, id: userId,
}, },
}); });
if (ssoUser) { if (ssoUser) {
// deny changes to SSO-defined properties // deny changes to SSO-defined properties
if (data.email !== user?.email) { if (data.email !== user?.email) {
@@ -49,7 +50,7 @@ export default async function updateUserById(
status: 400, status: 400,
}; };
} }
if (data.image !== "") { if (data.image?.startsWith("data:image/jpeg;base64")) {
return { return {
response: "SSO Users cannot change their avatar.", response: "SSO Users cannot change their avatar.",
status: 400, status: 400,
+4 -1
View File
@@ -40,7 +40,10 @@ async function emptyS3Directory(bucket: string, dir: string) {
export default async function removeFolder({ filePath }: { filePath: string }) { export default async function removeFolder({ filePath }: { filePath: string }) {
if (s3Client) { if (s3Client) {
try { try {
await emptyS3Directory(process.env.SPACES_BUCKET_NAME as string, filePath); await emptyS3Directory(
process.env.SPACES_BUCKET_NAME as string,
filePath
);
} catch (err) { } catch (err) {
console.log("Error", err); console.log("Error", err);
} }
+12 -1
View File
@@ -1,6 +1,17 @@
import fetch from "node-fetch";
import https from "https";
export default async function validateUrlSize(url: string) { export default async function validateUrlSize(url: string) {
try { 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 = const totalSizeMB =
Number(response.headers.get("content-length")) / Math.pow(1024, 2); Number(response.headers.get("content-length")) / Math.pow(1024, 2);
+2 -1
View File
@@ -42,7 +42,8 @@ export default async function verifyUser({
return null; return null;
} }
if (!user.username && !ssoUser) { // SSO users don't need a username if (!user.username && !ssoUser) {
// SSO users don't need a username
res.status(401).json({ res.status(401).json({
response: "Username not found.", response: "Username not found.",
}); });
+10 -1
View File
@@ -1,6 +1,15 @@
import fetch from "node-fetch";
import https from "https";
export default async function getTitle(url: string) { export default async function getTitle(url: string) {
try { 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(); const text = await response.text();
// regular expression to find the <title> tag // regular expression to find the <title> tag
+6 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "linkwarden", "name": "linkwarden",
"version": "2.4.0", "version": "2.4.9",
"main": "index.js", "main": "index.js",
"repository": "https://github.com/linkwarden/linkwarden.git", "repository": "https://github.com/linkwarden/linkwarden.git",
"author": "Daniel31X13 <daniel31x13@gmail.com>", "author": "Daniel31X13 <daniel31x13@gmail.com>",
@@ -15,7 +15,8 @@
"worker:prod": "ts-node --transpile-only --skip-project scripts/worker.ts", "worker:prod": "ts-node --transpile-only --skip-project scripts/worker.ts",
"start": "concurrently \"next start\" \"yarn worker:prod\"", "start": "concurrently \"next start\" \"yarn worker:prod\"",
"build": "next build", "build": "next build",
"lint": "next lint" "lint": "next lint",
"format": "prettier --write \"**/*.{ts,tsx,js,json,md}\""
}, },
"dependencies": { "dependencies": {
"@auth/prisma-adapter": "^1.0.1", "@auth/prisma-adapter": "^1.0.1",
@@ -38,6 +39,7 @@
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"csstype": "^3.1.2", "csstype": "^3.1.2",
"dompurify": "^3.0.6", "dompurify": "^3.0.6",
"dotenv": "^16.3.1",
"eslint": "8.46.0", "eslint": "8.46.0",
"eslint-config-next": "13.4.9", "eslint-config-next": "13.4.9",
"formidable": "^3.5.1", "formidable": "^3.5.1",
@@ -64,11 +66,13 @@
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/dompurify": "^3.0.4", "@types/dompurify": "^3.0.4",
"@types/jsdom": "^21.1.3", "@types/jsdom": "^21.1.3",
"@types/node-fetch": "^2.6.10",
"@types/shelljs": "^0.8.15", "@types/shelljs": "^0.8.15",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"daisyui": "^4.4.2", "daisyui": "^4.4.2",
"nodemon": "^3.0.2", "nodemon": "^3.0.2",
"postcss": "^8.4.26", "postcss": "^8.4.26",
"prettier": "3.1.1",
"prisma": "^5.1.0", "prisma": "^5.1.0",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
+1 -1
View File
@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import "@/styles/globals.css"; import "@/styles/globals.css";
import 'bootstrap-icons/font/bootstrap-icons.css'; import "bootstrap-icons/font/bootstrap-icons.css";
import { SessionProvider } from "next-auth/react"; import { SessionProvider } from "next-auth/react";
import type { AppProps } from "next/app"; import type { AppProps } from "next/app";
import Head from "next/head"; import Head from "next/head";
+5 -2
View File
@@ -249,7 +249,8 @@ if (process.env.NEXT_PUBLIC_AUTHENTIK_ENABLED === "true") {
profile: (profile) => { profile: (profile) => {
return { return {
id: profile.sub, id: profile.sub,
name: profile.name ?? profile.preferred_username, username: profile.preferred_username,
name: profile.name || "",
email: profile.email, email: profile.email,
image: profile.picture, image: profile.picture,
}; };
@@ -589,13 +590,15 @@ if (process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED === "true") {
profile: (profile) => { profile: (profile) => {
return { return {
id: profile.sub, id: profile.sub,
name: profile.name ?? profile.preferred_username, username: profile.preferred_username,
name: profile.name || "",
email: profile.email, email: profile.email,
image: profile.picture, image: profile.picture,
}; };
}, },
}) })
); );
const _linkAccount = adapter.linkAccount; const _linkAccount = adapter.linkAccount;
adapter.linkAccount = (account) => { adapter.linkAccount = (account) => {
const { "not-before-policy": _, refresh_expires_in, ...data } = account; const { "not-before-policy": _, refresh_expires_in, ...data } = account;
+400 -230
View File
@@ -1,238 +1,408 @@
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from "next";
import * as process from "process"; import * as process from "process";
export type ResponseData = { export type ResponseData = {
credentialsEnabled: string|undefined credentialsEnabled: string | undefined;
emailEnabled: string|undefined emailEnabled: string | undefined;
registrationDisabled: string|undefined registrationDisabled: string | undefined;
buttonAuths: { buttonAuths: {
method: string method: string;
name: string name: string;
}[] }[];
} };
export default function handler(req: NextApiRequest, res: NextApiResponse<ResponseData>) { export default function handler(
res.json(getLogins()); req: NextApiRequest,
res: NextApiResponse<ResponseData>
) {
res.json(getLogins());
} }
export function getLogins() { export function getLogins() {
const buttonAuths = [] const buttonAuths = [];
// 42 School // 42 School
if (process.env.NEXT_PUBLIC_FORTYTWO_ENABLED === 'true') { if (process.env.NEXT_PUBLIC_FORTYTWO_ENABLED === "true") {
buttonAuths.push({method: '42-school', name: process.env.FORTYTWO_CUSTOM_NAME ?? '42 School'}); buttonAuths.push({
} method: "42-school",
// Apple name: process.env.FORTYTWO_CUSTOM_NAME ?? "42 School",
if (process.env.NEXT_PUBLIC_APPLE_ENABLED === 'true') { });
buttonAuths.push({method: 'apple', name: process.env.APPLE_CUSTOM_NAME ?? 'Apple'}); }
} // Apple
// Atlassian if (process.env.NEXT_PUBLIC_APPLE_ENABLED === "true") {
if (process.env.NEXT_PUBLIC_ATLASSIAN_ENABLED === 'true') { buttonAuths.push({
buttonAuths.push({method: 'atlassian', name: process.env.ATLASSIAN_CUSTOM_NAME ?? 'Atlassian'}); method: "apple",
} name: process.env.APPLE_CUSTOM_NAME ?? "Apple",
// Auth0 });
if (process.env.NEXT_PUBLIC_AUTH0_ENABLED === 'true') { }
buttonAuths.push({method: 'auth0', name: process.env.AUTH0_CUSTOM_NAME ?? 'Auth0'}); // Atlassian
} if (process.env.NEXT_PUBLIC_ATLASSIAN_ENABLED === "true") {
// Authentik buttonAuths.push({
if (process.env.NEXT_PUBLIC_AUTHENTIK_ENABLED === 'true') { method: "atlassian",
buttonAuths.push({method: 'authentik', name: process.env.AUTHENTIK_CUSTOM_NAME ?? 'Authentik'}); name: process.env.ATLASSIAN_CUSTOM_NAME ?? "Atlassian",
} });
// Battle.net }
if (process.env.NEXT_PUBLIC_BATTLENET_ENABLED === 'true') { // Auth0
buttonAuths.push({method: 'battlenet', name: process.env.BATTLENET_CUSTOM_NAME ?? 'Battle.net'}); if (process.env.NEXT_PUBLIC_AUTH0_ENABLED === "true") {
} buttonAuths.push({
// Box method: "auth0",
if (process.env.NEXT_PUBLIC_BOX_ENABLED === 'true') { name: process.env.AUTH0_CUSTOM_NAME ?? "Auth0",
buttonAuths.push({method: 'box', name: process.env.BOX_CUSTOM_NAME ?? 'Box'}); });
} }
// Cognito // Authentik
if (process.env.NEXT_PUBLIC_COGNITO_ENABLED === 'true') { if (process.env.NEXT_PUBLIC_AUTHENTIK_ENABLED === "true") {
buttonAuths.push({method: 'cognito', name: process.env.COGNITO_CUSTOM_NAME ?? 'Cognito'}); buttonAuths.push({
} method: "authentik",
// Coinbase name: process.env.AUTHENTIK_CUSTOM_NAME ?? "Authentik",
if (process.env.NEXT_PUBLIC_COINBASE_ENABLED === 'true') { });
buttonAuths.push({method: 'coinbase', name: process.env.COINBASE_CUSTOM_NAME ?? 'Coinbase'}); }
} // Battle.net
// Discord if (process.env.NEXT_PUBLIC_BATTLENET_ENABLED === "true") {
if (process.env.NEXT_PUBLIC_DISCORD_ENABLED === 'true') { buttonAuths.push({
buttonAuths.push({method: 'discord', name: process.env.DISCORD_CUSTOM_NAME ?? 'Discord'}); method: "battlenet",
} name: process.env.BATTLENET_CUSTOM_NAME ?? "Battle.net",
// Dropbox });
if (process.env.NEXT_PUBLIC_DROPBOX_ENABLED === 'true') { }
buttonAuths.push({method: 'dropbox', name: process.env.DROPBOX_CUSTOM_NAME ?? 'Dropbox'}); // Box
} if (process.env.NEXT_PUBLIC_BOX_ENABLED === "true") {
// Duende IdentityServer6 buttonAuths.push({
if (process.env.NEXT_PUBLIC_DUENDE_IDS6_ENABLED === 'true') { method: "box",
buttonAuths.push({method: 'duende-identityserver6', name: process.env.DUENDE_IDS6_CUSTOM_NAME ?? 'DuendeIdentityServer6'}); name: process.env.BOX_CUSTOM_NAME ?? "Box",
} });
// EVE Online }
if (process.env.NEXT_PUBLIC_EVEONLINE_ENABLED === 'true') { // Cognito
buttonAuths.push({method: 'eveonline', name: process.env.EVEONLINE_CUSTOM_NAME ?? 'EVE Online'}); if (process.env.NEXT_PUBLIC_COGNITO_ENABLED === "true") {
} buttonAuths.push({
// Facebook method: "cognito",
if (process.env.NEXT_PUBLIC_FACEBOOK_ENABLED === 'true') { name: process.env.COGNITO_CUSTOM_NAME ?? "Cognito",
buttonAuths.push({method: 'facebook', name: process.env.FACEBOOK_CUSTOM_NAME ?? 'Facebook'}); });
} }
// FACEIT // Coinbase
if (process.env.NEXT_PUBLIC_FACEIT_ENABLED === 'true') { if (process.env.NEXT_PUBLIC_COINBASE_ENABLED === "true") {
buttonAuths.push({method: 'faceit', name: process.env.FACEIT_CUSTOM_NAME ?? 'FACEIT'}); buttonAuths.push({
} method: "coinbase",
// Foursquare name: process.env.COINBASE_CUSTOM_NAME ?? "Coinbase",
if (process.env.NEXT_PUBLIC_FOURSQUARE_ENABLED === 'true') { });
buttonAuths.push({method: 'foursquare', name: process.env.FOURSQUARE_CUSTOM_NAME ?? 'Foursquare'}); }
} // Discord
// Freshbooks if (process.env.NEXT_PUBLIC_DISCORD_ENABLED === "true") {
if (process.env.NEXT_PUBLIC_FRESHBOOKS_ENABLED === 'true') { buttonAuths.push({
buttonAuths.push({method: 'freshbooks', name: process.env.FRESHBOOKS_CUSTOM_NAME ?? 'Freshbooks'}); method: "discord",
} name: process.env.DISCORD_CUSTOM_NAME ?? "Discord",
// FusionAuth });
if (process.env.NEXT_PUBLIC_FUSIONAUTH_ENABLED === 'true') { }
buttonAuths.push({method: 'fusionauth', name: process.env.FUSIONAUTH_CUSTOM_NAME ?? 'FusionAuth'}); // Dropbox
} if (process.env.NEXT_PUBLIC_DROPBOX_ENABLED === "true") {
// GitHub buttonAuths.push({
if (process.env.NEXT_PUBLIC_GITHUB_ENABLED === 'true') { method: "dropbox",
buttonAuths.push({method: 'github', name: process.env.GITHUB_CUSTOM_NAME ?? 'GitHub'}); name: process.env.DROPBOX_CUSTOM_NAME ?? "Dropbox",
} });
// GitLab }
if (process.env.NEXT_PUBLIC_GITLAB_ENABLED === 'true') { // Duende IdentityServer6
buttonAuths.push({method: 'gitlab', name: process.env.GITLAB_CUSTOM_NAME ?? 'GitLab'}); if (process.env.NEXT_PUBLIC_DUENDE_IDS6_ENABLED === "true") {
} buttonAuths.push({
// Google method: "duende-identityserver6",
if (process.env.NEXT_PUBLIC_GOOGLE_ENABLED === 'true') { name: process.env.DUENDE_IDS6_CUSTOM_NAME ?? "DuendeIdentityServer6",
buttonAuths.push({method: 'google', name: process.env.GOOGLE_CUSTOM_NAME ?? 'Google'}); });
} }
// HubSpot // EVE Online
if (process.env.NEXT_PUBLIC_HUBSPOT_ENABLED === 'true') { if (process.env.NEXT_PUBLIC_EVEONLINE_ENABLED === "true") {
buttonAuths.push({method: 'hubspot', name: process.env.HUBSPOT_CUSTOM_NAME ?? 'HubSpot'}); buttonAuths.push({
} method: "eveonline",
// IdentityServer4 name: process.env.EVEONLINE_CUSTOM_NAME ?? "EVE Online",
if (process.env.NEXT_PUBLIC_IDS4_ENABLED === 'true') { });
buttonAuths.push({method: 'identity-server4', name: process.env.IDS4_CUSTOM_NAME ?? 'IdentityServer4'}); }
} // Facebook
// Kakao if (process.env.NEXT_PUBLIC_FACEBOOK_ENABLED === "true") {
if (process.env.NEXT_PUBLIC_KAKAO_ENABLED === 'true') { buttonAuths.push({
buttonAuths.push({method: 'kakao', name: process.env.KAKAO_CUSTOM_NAME ?? 'Kakao'}); method: "facebook",
} name: process.env.FACEBOOK_CUSTOM_NAME ?? "Facebook",
// Keycloak });
if (process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED === 'true') { }
buttonAuths.push({method: 'keycloak', name: process.env.KEYCLOAK_CUSTOM_NAME ?? 'Keycloak'}); // FACEIT
} if (process.env.NEXT_PUBLIC_FACEIT_ENABLED === "true") {
// LINE buttonAuths.push({
if (process.env.NEXT_PUBLIC_LINE_ENABLED === 'true') { method: "faceit",
buttonAuths.push({method: 'line', name: process.env.LINE_CUSTOM_NAME ?? 'LINE'}); name: process.env.FACEIT_CUSTOM_NAME ?? "FACEIT",
} });
// LinkedIn }
if (process.env.NEXT_PUBLIC_LINKEDIN_ENABLED === 'true') { // Foursquare
buttonAuths.push({method: 'linkedin', name: process.env.LINKEDIN_CUSTOM_NAME ?? 'LinkedIn'}); if (process.env.NEXT_PUBLIC_FOURSQUARE_ENABLED === "true") {
} buttonAuths.push({
// MailChimp method: "foursquare",
if (process.env.NEXT_PUBLIC_MAILCHIMP_ENABLED === 'true') { name: process.env.FOURSQUARE_CUSTOM_NAME ?? "Foursquare",
buttonAuths.push({method: 'mailchimp', name: process.env.MAILCHIMP_CUSTOM_NAME ?? 'Mailchimp'}); });
} }
// Mail.ru // Freshbooks
if (process.env.NEXT_PUBLIC_MAILRU_ENABLED === 'true') { if (process.env.NEXT_PUBLIC_FRESHBOOKS_ENABLED === "true") {
buttonAuths.push({method: 'mailru', name: process.env.MAILRU_CUSTOM_NAME ?? 'Mail.ru'}); buttonAuths.push({
} method: "freshbooks",
// Naver name: process.env.FRESHBOOKS_CUSTOM_NAME ?? "Freshbooks",
if (process.env.NEXT_PUBLIC_NAVER_ENABLED === 'true') { });
buttonAuths.push({method: 'naver', name: process.env.NAVER_CUSTOM_NAME ?? 'Naver'}); }
} // FusionAuth
// Netlify if (process.env.NEXT_PUBLIC_FUSIONAUTH_ENABLED === "true") {
if (process.env.NEXT_PUBLIC_NETLIFY_ENABLED === 'true') { buttonAuths.push({
buttonAuths.push({method: 'netlify', name: process.env.NETLIFY_CUSTOM_NAME ?? 'Netlify'}); method: "fusionauth",
} name: process.env.FUSIONAUTH_CUSTOM_NAME ?? "FusionAuth",
// Okta });
if (process.env.NEXT_PUBLIC_OKTA_ENABLED === 'true') { }
buttonAuths.push({method: 'okta', name: process.env.OKTA_CUSTOM_NAME ?? 'Okta'}); // GitHub
} if (process.env.NEXT_PUBLIC_GITHUB_ENABLED === "true") {
// OneLogin buttonAuths.push({
if (process.env.NEXT_PUBLIC_ONELOGIN_ENABLED === 'true') { method: "github",
buttonAuths.push({method: 'onelogin', name: process.env.ONELOGIN_CUSTOM_NAME ?? 'OneLogin'}); name: process.env.GITHUB_CUSTOM_NAME ?? "GitHub",
} });
// Osso }
if (process.env.NEXT_PUBLIC_OSSO_ENABLED === 'true') { // GitLab
buttonAuths.push({method: 'osso', name: process.env.OSSO_CUSTOM_NAME ?? 'Osso'}); if (process.env.NEXT_PUBLIC_GITLAB_ENABLED === "true") {
} buttonAuths.push({
// osu! method: "gitlab",
if (process.env.NEXT_PUBLIC_OSU_ENABLED === 'true') { name: process.env.GITLAB_CUSTOM_NAME ?? "GitLab",
buttonAuths.push({method: 'osu', name: process.env.OSU_CUSTOM_NAME ?? 'Osu!'}); });
} }
// Patreon // Google
if (process.env.NEXT_PUBLIC_PATREON_ENABLED === 'true') { if (process.env.NEXT_PUBLIC_GOOGLE_ENABLED === "true") {
buttonAuths.push({method: 'patreon', name: process.env.PATREON_CUSTOM_NAME ?? 'Patreon'}); buttonAuths.push({
} method: "google",
// Pinterest name: process.env.GOOGLE_CUSTOM_NAME ?? "Google",
if (process.env.NEXT_PUBLIC_PINTEREST_ENABLED === 'true') { });
buttonAuths.push({method: 'pinterest', name: process.env.PINTEREST_CUSTOM_NAME ?? 'Pinterest'}); }
} // HubSpot
// Pipedrive if (process.env.NEXT_PUBLIC_HUBSPOT_ENABLED === "true") {
if (process.env.NEXT_PUBLIC_PIPEDRIVE_ENABLED === 'true') { buttonAuths.push({
buttonAuths.push({method: 'pipedrive', name: process.env.PIPEDRIVE_CUSTOM_NAME ?? 'Pipedrive'}); method: "hubspot",
} name: process.env.HUBSPOT_CUSTOM_NAME ?? "HubSpot",
// Reddit });
if (process.env.NEXT_PUBLIC_REDDIT_ENABLED === 'true') { }
buttonAuths.push({method: 'reddit', name: process.env.REDDIT_CUSTOM_NAME ?? 'Reddit'}); // IdentityServer4
} if (process.env.NEXT_PUBLIC_IDS4_ENABLED === "true") {
// Salesforce buttonAuths.push({
if (process.env.NEXT_PUBLIC_SALESFORCE_ENABLED === 'true') { method: "identity-server4",
buttonAuths.push({method: 'salesforce', name: process.env.SALESFORCE_CUSTOM_NAME ?? 'Salesforce'}); name: process.env.IDS4_CUSTOM_NAME ?? "IdentityServer4",
} });
// Slack }
if (process.env.NEXT_PUBLIC_SLACK_ENABLED === 'true') { // Kakao
buttonAuths.push({method: 'slack', name: process.env.SLACK_CUSTOM_NAME ?? 'Slack'}); if (process.env.NEXT_PUBLIC_KAKAO_ENABLED === "true") {
} buttonAuths.push({
// Spotify method: "kakao",
if (process.env.NEXT_PUBLIC_SPOTIFY_ENABLED === 'true') { name: process.env.KAKAO_CUSTOM_NAME ?? "Kakao",
buttonAuths.push({method: 'spotify', name: process.env.SPOTIFY_CUSTOM_NAME ?? 'Spotify'}); });
} }
// Strava // Keycloak
if (process.env.NEXT_PUBLIC_STRAVA_ENABLED === 'true') { if (process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED === "true") {
buttonAuths.push({method: 'strava', name: process.env.STRAVA_CUSTOM_NAME ?? 'Strava'}); buttonAuths.push({
} method: "keycloak",
// Todoist name: process.env.KEYCLOAK_CUSTOM_NAME ?? "Keycloak",
if (process.env.NEXT_PUBLIC_TODOIST_ENABLED === 'true') { });
buttonAuths.push({method: 'todoist', name: process.env.TODOIST_CUSTOM_NAME ?? 'Todoist'}); }
} // LINE
// Twitch if (process.env.NEXT_PUBLIC_LINE_ENABLED === "true") {
if (process.env.NEXT_PUBLIC_TWITCH_ENABLED === 'true') { buttonAuths.push({
buttonAuths.push({method: 'twitch', name: process.env.TWITCH_CUSTOM_NAME ?? 'Twitch'}); method: "line",
} name: process.env.LINE_CUSTOM_NAME ?? "LINE",
// United Effects });
if (process.env.NEXT_PUBLIC_UNITED_EFFECTS_ENABLED === 'true') { }
buttonAuths.push({method: 'united-effects', name: process.env.UNITED_EFFECTS_CUSTOM_NAME ?? 'United Effects'}); // LinkedIn
} if (process.env.NEXT_PUBLIC_LINKEDIN_ENABLED === "true") {
// VK buttonAuths.push({
if (process.env.NEXT_PUBLIC_VK_ENABLED === 'true') { method: "linkedin",
buttonAuths.push({method: 'vk', name: process.env.VK_CUSTOM_NAME ?? 'VK'}); name: process.env.LINKEDIN_CUSTOM_NAME ?? "LinkedIn",
} });
// Wikimedia }
if (process.env.NEXT_PUBLIC_WIKIMEDIA_ENABLED === 'true') { // MailChimp
buttonAuths.push({method: 'wikimedia', name: process.env.WIKIMEDIA_CUSTOM_NAME ?? 'Wikimedia'}); if (process.env.NEXT_PUBLIC_MAILCHIMP_ENABLED === "true") {
} buttonAuths.push({
// Wordpress.com method: "mailchimp",
if (process.env.NEXT_PUBLIC_WORDPRESS_ENABLED === 'true') { name: process.env.MAILCHIMP_CUSTOM_NAME ?? "Mailchimp",
buttonAuths.push({method: 'wordpress', name: process.env.WORDPRESS_CUSTOM_NAME ?? 'WordPress.com'}); });
} }
// Yandex // Mail.ru
if (process.env.NEXT_PUBLIC_YANDEX_ENABLED === 'true') { if (process.env.NEXT_PUBLIC_MAILRU_ENABLED === "true") {
buttonAuths.push({method: 'yandex', name: process.env.YANDEX_CUSTOM_NAME ?? 'Yandex'}); buttonAuths.push({
} method: "mailru",
// Zitadel name: process.env.MAILRU_CUSTOM_NAME ?? "Mail.ru",
if (process.env.NEXT_PUBLIC_ZITADEL_ENABLED === 'true') { });
buttonAuths.push({method: 'zitadel', name: process.env.ZITADEL_CUSTOM_NAME ?? 'ZITADEL'}); }
} // Naver
// Zoho if (process.env.NEXT_PUBLIC_NAVER_ENABLED === "true") {
if (process.env.NEXT_PUBLIC_ZOHO_ENABLED === 'true') { buttonAuths.push({
buttonAuths.push({method: 'zoho', name: process.env.ZOHO_CUSTOM_NAME ?? 'Zoho'}); method: "naver",
} name: process.env.NAVER_CUSTOM_NAME ?? "Naver",
// Zoom });
if (process.env.NEXT_PUBLIC_ZOOM_ENABLED === 'true') { }
buttonAuths.push({method: 'zoom', name: process.env.ZOOM_CUSTOM_NAME ?? 'Zoom'}); // Netlify
} if (process.env.NEXT_PUBLIC_NETLIFY_ENABLED === "true") {
return { buttonAuths.push({
credentialsEnabled: (process.env.NEXT_PUBLIC_CREDENTIALS_ENABLED === 'true' || process.env.NEXT_PUBLIC_CREDENTIALS_ENABLED === undefined) ? "true" : "false", method: "netlify",
emailEnabled: (process.env.NEXT_PUBLIC_EMAIL_PROVIDER === 'true' ? 'true' : 'false'), name: process.env.NETLIFY_CUSTOM_NAME ?? "Netlify",
registrationDisabled: (process.env.NEXT_PUBLIC_DISABLE_REGISTRATION === 'true' ? 'true' : 'false'), });
buttonAuths: buttonAuths }
}; // Okta
if (process.env.NEXT_PUBLIC_OKTA_ENABLED === "true") {
buttonAuths.push({
method: "okta",
name: process.env.OKTA_CUSTOM_NAME ?? "Okta",
});
}
// OneLogin
if (process.env.NEXT_PUBLIC_ONELOGIN_ENABLED === "true") {
buttonAuths.push({
method: "onelogin",
name: process.env.ONELOGIN_CUSTOM_NAME ?? "OneLogin",
});
}
// Osso
if (process.env.NEXT_PUBLIC_OSSO_ENABLED === "true") {
buttonAuths.push({
method: "osso",
name: process.env.OSSO_CUSTOM_NAME ?? "Osso",
});
}
// osu!
if (process.env.NEXT_PUBLIC_OSU_ENABLED === "true") {
buttonAuths.push({
method: "osu",
name: process.env.OSU_CUSTOM_NAME ?? "Osu!",
});
}
// Patreon
if (process.env.NEXT_PUBLIC_PATREON_ENABLED === "true") {
buttonAuths.push({
method: "patreon",
name: process.env.PATREON_CUSTOM_NAME ?? "Patreon",
});
}
// Pinterest
if (process.env.NEXT_PUBLIC_PINTEREST_ENABLED === "true") {
buttonAuths.push({
method: "pinterest",
name: process.env.PINTEREST_CUSTOM_NAME ?? "Pinterest",
});
}
// Pipedrive
if (process.env.NEXT_PUBLIC_PIPEDRIVE_ENABLED === "true") {
buttonAuths.push({
method: "pipedrive",
name: process.env.PIPEDRIVE_CUSTOM_NAME ?? "Pipedrive",
});
}
// Reddit
if (process.env.NEXT_PUBLIC_REDDIT_ENABLED === "true") {
buttonAuths.push({
method: "reddit",
name: process.env.REDDIT_CUSTOM_NAME ?? "Reddit",
});
}
// Salesforce
if (process.env.NEXT_PUBLIC_SALESFORCE_ENABLED === "true") {
buttonAuths.push({
method: "salesforce",
name: process.env.SALESFORCE_CUSTOM_NAME ?? "Salesforce",
});
}
// Slack
if (process.env.NEXT_PUBLIC_SLACK_ENABLED === "true") {
buttonAuths.push({
method: "slack",
name: process.env.SLACK_CUSTOM_NAME ?? "Slack",
});
}
// Spotify
if (process.env.NEXT_PUBLIC_SPOTIFY_ENABLED === "true") {
buttonAuths.push({
method: "spotify",
name: process.env.SPOTIFY_CUSTOM_NAME ?? "Spotify",
});
}
// Strava
if (process.env.NEXT_PUBLIC_STRAVA_ENABLED === "true") {
buttonAuths.push({
method: "strava",
name: process.env.STRAVA_CUSTOM_NAME ?? "Strava",
});
}
// Todoist
if (process.env.NEXT_PUBLIC_TODOIST_ENABLED === "true") {
buttonAuths.push({
method: "todoist",
name: process.env.TODOIST_CUSTOM_NAME ?? "Todoist",
});
}
// Twitch
if (process.env.NEXT_PUBLIC_TWITCH_ENABLED === "true") {
buttonAuths.push({
method: "twitch",
name: process.env.TWITCH_CUSTOM_NAME ?? "Twitch",
});
}
// United Effects
if (process.env.NEXT_PUBLIC_UNITED_EFFECTS_ENABLED === "true") {
buttonAuths.push({
method: "united-effects",
name: process.env.UNITED_EFFECTS_CUSTOM_NAME ?? "United Effects",
});
}
// VK
if (process.env.NEXT_PUBLIC_VK_ENABLED === "true") {
buttonAuths.push({
method: "vk",
name: process.env.VK_CUSTOM_NAME ?? "VK",
});
}
// Wikimedia
if (process.env.NEXT_PUBLIC_WIKIMEDIA_ENABLED === "true") {
buttonAuths.push({
method: "wikimedia",
name: process.env.WIKIMEDIA_CUSTOM_NAME ?? "Wikimedia",
});
}
// Wordpress.com
if (process.env.NEXT_PUBLIC_WORDPRESS_ENABLED === "true") {
buttonAuths.push({
method: "wordpress",
name: process.env.WORDPRESS_CUSTOM_NAME ?? "WordPress.com",
});
}
// Yandex
if (process.env.NEXT_PUBLIC_YANDEX_ENABLED === "true") {
buttonAuths.push({
method: "yandex",
name: process.env.YANDEX_CUSTOM_NAME ?? "Yandex",
});
}
// Zitadel
if (process.env.NEXT_PUBLIC_ZITADEL_ENABLED === "true") {
buttonAuths.push({
method: "zitadel",
name: process.env.ZITADEL_CUSTOM_NAME ?? "ZITADEL",
});
}
// Zoho
if (process.env.NEXT_PUBLIC_ZOHO_ENABLED === "true") {
buttonAuths.push({
method: "zoho",
name: process.env.ZOHO_CUSTOM_NAME ?? "Zoho",
});
}
// Zoom
if (process.env.NEXT_PUBLIC_ZOOM_ENABLED === "true") {
buttonAuths.push({
method: "zoom",
name: process.env.ZOOM_CUSTOM_NAME ?? "Zoom",
});
}
return {
credentialsEnabled:
process.env.NEXT_PUBLIC_CREDENTIALS_ENABLED === "true" ||
process.env.NEXT_PUBLIC_CREDENTIALS_ENABLED === undefined
? "true"
: "false",
emailEnabled:
process.env.NEXT_PUBLIC_EMAIL_PROVIDER === "true" ? "true" : "false",
registrationDisabled:
process.env.NEXT_PUBLIC_DISABLE_REGISTRATION === "true"
? "true"
: "false",
buttonAuths: buttonAuths,
};
} }
+3 -4
View File
@@ -3,7 +3,7 @@ import TextInput from "@/components/TextInput";
import CenteredForm from "@/layouts/CenteredForm"; import CenteredForm from "@/layouts/CenteredForm";
import { signIn } from "next-auth/react"; import { signIn } from "next-auth/react";
import Link from "next/link"; import Link from "next/link";
import { useState, FormEvent } from "react"; import React, { useState, FormEvent } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { getLogins } from "./api/v1/logins"; import { getLogins } from "./api/v1/logins";
import { InferGetServerSidePropsType } from "next"; import { InferGetServerSidePropsType } from "next";
@@ -131,18 +131,17 @@ export default function Login({
const Buttons: any = []; const Buttons: any = [];
availableLogins.buttonAuths.forEach((value, index) => { availableLogins.buttonAuths.forEach((value, index) => {
Buttons.push( Buttons.push(
<> <React.Fragment key={index}>
{index !== 0 ? <div className="divider my-1">OR</div> : undefined} {index !== 0 ? <div className="divider my-1">OR</div> : undefined}
<AccentSubmitButton <AccentSubmitButton
key={index}
type="button" type="button"
onClick={() => loginUserButton(value.method)} onClick={() => loginUserButton(value.method)}
label={`Sign in with ${value.name}`} label={`Sign in with ${value.name}`}
className=" w-full text-center" className=" w-full text-center"
loading={submitLoader} loading={submitLoader}
/> />
</> </React.Fragment>
); );
}); });
return Buttons; return Buttons;
+5 -4
View File
@@ -5,6 +5,9 @@ import CenteredForm from "@/layouts/CenteredForm";
import { signOut, useSession } from "next-auth/react"; import { signOut, useSession } from "next-auth/react";
import Link from "next/link"; 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() { export default function Delete() {
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [comment, setComment] = useState<string>(); 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."); return toast.error("Please fill the required fields.");
} }
@@ -57,9 +60,7 @@ export default function Delete() {
href="/settings/account" href="/settings/account"
className="absolute top-4 left-4 btn btn-ghost btn-square btn-sm" className="absolute top-4 left-4 btn btn-ghost btn-square btn-sm"
> >
<i <i className="bi-chevron-left text-neutral text-xl"></i>
className="bi-chevron-left text-neutral text-xl"
></i>
</Link> </Link>
<div className="flex items-center gap-2 w-full rounded-md h-8"> <div className="flex items-center gap-2 w-full rounded-md h-8">
<p className="text-red-500 dark:text-red-500 truncate w-full text-3xl text-center"> <p className="text-red-500 dark:text-red-500 truncate w-full text-3xl text-center">
+10 -10
View File
@@ -1,4 +1,4 @@
import { defineConfig, devices } from '@playwright/test'; import { defineConfig, devices } from "@playwright/test";
/** /**
* Read environment variables from file. * Read environment variables from file.
@@ -10,7 +10,7 @@ import { defineConfig, devices } from '@playwright/test';
* See https://playwright.dev/docs/test-configuration. * See https://playwright.dev/docs/test-configuration.
*/ */
export default defineConfig({ export default defineConfig({
testDir: './e2e', testDir: "./e2e",
/* Run tests in files in parallel */ /* Run tests in files in parallel */
fullyParallel: true, fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */ /* Fail the build on CI if you accidentally left test.only in the source code. */
@@ -20,31 +20,31 @@ export default defineConfig({
/* Opt out of parallel tests on CI. */ /* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined, workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html', reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: { use: {
/* Base URL to use in actions like `await page.goto('/')`. */ /* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://127.0.0.1:3000', // baseURL: 'http://127.0.0.1:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry', trace: "on-first-retry",
}, },
/* Configure projects for major browsers */ /* Configure projects for major browsers */
projects: [ projects: [
{ {
name: 'chromium', name: "chromium",
use: { ...devices['Desktop Chrome'] }, use: { ...devices["Desktop Chrome"] },
}, },
{ {
name: 'firefox', name: "firefox",
use: { ...devices['Desktop Firefox'] }, use: { ...devices["Desktop Firefox"] },
}, },
{ {
name: 'webkit', name: "webkit",
use: { ...devices['Desktop Safari'] }, use: { ...devices["Desktop Safari"] },
}, },
/* Test against mobile viewports. */ /* Test against mobile viewports. */
+1 -1
View File
@@ -3,4 +3,4 @@ module.exports = {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
} };
Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

+1
View File
@@ -1,3 +1,4 @@
import 'dotenv/config';
import { Collection, Link, User } from "@prisma/client"; import { Collection, Link, User } from "@prisma/client";
import { prisma } from "../lib/api/db"; import { prisma } from "../lib/api/db";
import archiveHandler from "../lib/api/archiveHandler"; import archiveHandler from "../lib/api/archiveHandler";
+2 -2
View File
@@ -1,9 +1,9 @@
import { create } from "zustand"; import { create } from "zustand";
import {ViewMode} from "@/types/global"; import { ViewMode } from "@/types/global";
type LocalSettings = { type LocalSettings = {
theme?: string; theme?: string;
viewMode?: string viewMode?: string;
}; };
type LocalSettingsStore = { type LocalSettingsStore = {
+1
View File
@@ -12,6 +12,7 @@ declare global {
NEXT_PUBLIC_MAX_FILE_SIZE?: string; NEXT_PUBLIC_MAX_FILE_SIZE?: string;
MAX_LINKS_PER_USER?: string; MAX_LINKS_PER_USER?: string;
ARCHIVE_TAKE_COUNT?: string; ARCHIVE_TAKE_COUNT?: string;
IGNORE_UNAUTHORIZED_CA?: string;
SPACES_KEY?: string; SPACES_KEY?: string;
SPACES_SECRET?: string; SPACES_SECRET?: string;
+18
View File
@@ -1795,6 +1795,14 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca"
integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== 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": "@types/node@*", "@types/node@>=8.1.0":
version "20.4.4" version "20.4.4"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.4.tgz#c79c7cc22c9d0e97a7944954c9e663bcbd92b0cb" 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" resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.6.tgz#925ebd576d54a9531b5d76f0a5bef32548351dae"
integrity sha512-ilkD8YEnnGh1zJ240uJsW7AzE+2qpbOUYjacomn3AvJ6J4JhKGSZ2nh4wUIXPZrEPppaCLx5jFe8T89Rk8tQ7w== 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: ecc-jsbn@~0.1.1:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" 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" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== 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: pretty-format@^3.8.0:
version "3.8.0" version "3.8.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"