diff --git a/.env.sample b/.env.sample index 10ed7ee..f8fc24e 100644 --- a/.env.sample +++ b/.env.sample @@ -12,6 +12,7 @@ PAGINATION_TAKE_COUNT= STORAGE_FOLDER= AUTOSCROLL_TIMEOUT= NEXT_PUBLIC_DISABLE_REGISTRATION= +NEXT_PUBLIC_DISABLE_LOGIN= RE_ARCHIVE_LIMIT= NEXT_PUBLIC_MAX_UPLOAD_SIZE= @@ -33,3 +34,9 @@ NEXT_PUBLIC_KEYCLOAK_ENABLED= KEYCLOAK_ISSUER= KEYCLOAK_CLIENT_ID= KEYCLOAK_CLIENT_SECRET= + +# Authentik +NEXT_PUBLIC_AUTHENTIK_ENABLED= +AUTHENTIK_ISSUER= +AUTHENTIK_CLIENT_ID= +AUTHENTIK_CLIENT_SECRET= diff --git a/README.md b/README.md index 8f07c2d..6f3d31d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

Linkwarden

Discord -GitHub commits since latest release (by SemVer including pre-releases) +GitHub commits since latest release (by SemVer including pre-releases) Top Language Github Stars diff --git a/components/AccentSubmitButton.tsx b/components/AccentSubmitButton.tsx new file mode 100644 index 0000000..37f437f --- /dev/null +++ b/components/AccentSubmitButton.tsx @@ -0,0 +1,35 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { IconProp } from "@fortawesome/fontawesome-svg-core"; + +type Props = { + onClick?: Function; + icon?: IconProp; + label: string; + loading?: boolean; + className?: string; + type?: "button" | "submit" | "reset" | undefined; +}; + +export default function AccentSubmitButton({ + onClick, + icon, + label, + loading, + className, + type, +}: Props) { + return ( + + ); +} diff --git a/components/Modal/Link/PreservedFormats.tsx b/components/Modal/Link/PreservedFormats.tsx index 4017c32..de0a6de 100644 --- a/components/Modal/Link/PreservedFormats.tsx +++ b/components/Modal/Link/PreservedFormats.tsx @@ -166,7 +166,7 @@ export default function PreservedFormats() {
{link?.collection.ownerId === session.data?.user.id ? (
-
diff --git a/components/ModalContent/NewCollectionModal.tsx b/components/ModalContent/NewCollectionModal.tsx index 0773cb1..de0fc98 100644 --- a/components/ModalContent/NewCollectionModal.tsx +++ b/components/ModalContent/NewCollectionModal.tsx @@ -116,7 +116,7 @@ export default function NewCollectionModal({ onClose }: Props) {
diff --git a/components/ModalContent/PreservedFormatsModal.tsx b/components/ModalContent/PreservedFormatsModal.tsx index 951e9ca..6625d93 100644 --- a/components/ModalContent/PreservedFormatsModal.tsx +++ b/components/ModalContent/PreservedFormatsModal.tsx @@ -191,7 +191,7 @@ export default function PreservedFormatsModal({ onClose, activeLink }: Props) {
{link?.collection.ownerId === session.data?.user.id ? (
{optionsExpanded ? "Hide" : "More"} Options

-
diff --git a/components/Navbar.tsx b/components/Navbar.tsx index a493fa9..e4dbb3a 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -68,7 +68,7 @@ export default function Navbar() {
{ setNewLinkModal(true); }} - className="inline-flex gap-1 relative w-[11rem] items-center btn btn-accent text-white group" + className="inline-flex gap-1 relative w-[11rem] items-center btn btn-accent dark:border-violet-400 text-white group" > { diff --git a/lib/api/controllers/links/postLink.ts b/lib/api/controllers/links/postLink.ts index 3d28a3f..eca8a04 100644 --- a/lib/api/controllers/links/postLink.ts +++ b/lib/api/controllers/links/postLink.ts @@ -14,7 +14,7 @@ export default async function postLink( userId: number ) { try { - if (link.url) new URL(link.url); + new URL(link.url || ""); } catch (error) { return { response: diff --git a/pages/api/v1/auth/[...nextauth].ts b/pages/api/v1/auth/[...nextauth].ts index 4b5dd59..bb4f3ae 100644 --- a/pages/api/v1/auth/[...nextauth].ts +++ b/pages/api/v1/auth/[...nextauth].ts @@ -10,11 +10,13 @@ import sendVerificationRequest from "@/lib/api/sendVerificationRequest"; import { Provider } from "next-auth/providers"; import verifySubscription from "@/lib/api/verifySubscription"; import KeycloakProvider from "next-auth/providers/keycloak"; +import AuthentikProvider from "next-auth/providers/authentik"; const emailEnabled = process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false; const keycloakEnabled = process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED === "true"; +const authentikEnabled = process.env.NEXT_PUBLIC_AUTHENTIK_ENABLED === "true"; const adapter = PrismaAdapter(prisma); @@ -103,6 +105,34 @@ if (keycloakEnabled) { }; } +if (authentikEnabled) { + console.log(authentikEnabled) + providers.push( + AuthentikProvider({ + id: "authentik", + name: "Authentik", + clientId: process.env.AUTHENTIK_CLIENT_ID!, + clientSecret: process.env.AUTHENTIK_CLIENT_SECRET!, + issuer: process.env.AUTHENTIK_ISSUER, + profile: (profile) => { + console.log(profile) + return { + id: profile.sub, + username: profile.preferred_username, + name: profile.name ?? profile.preferred_username, + email: profile.email, + image: profile.picture, + }; + }, + }) + ); + const _linkAccount = adapter.linkAccount; + adapter.linkAccount = (account) => { + const { "not-before-policy": _, refresh_expires_in, ...data } = account; + return _linkAccount ? _linkAccount(data) : undefined; + }; +} + export const authOptions: AuthOptions = { adapter: adapter as Adapter, session: { diff --git a/pages/choose-username.tsx b/pages/choose-username.tsx index 39b847a..e1e6b69 100644 --- a/pages/choose-username.tsx +++ b/pages/choose-username.tsx @@ -6,6 +6,7 @@ import { useSession } from "next-auth/react"; import useAccountStore from "@/store/account"; import CenteredForm from "@/layouts/CenteredForm"; import TextInput from "@/components/TextInput"; +import AccentSubmitButton from "@/components/AccentSubmitButton"; export default function ChooseUsername() { const [submitLoader, setSubmitLoader] = useState(false); @@ -72,10 +73,10 @@ export default function ChooseUsername() {

- diff --git a/pages/dashboard.tsx b/pages/dashboard.tsx index 64438ca..0cd4bde 100644 --- a/pages/dashboard.tsx +++ b/pages/dashboard.tsx @@ -192,7 +192,7 @@ export default function Dashboard() { onClick={() => { setNewLinkModal(true); }} - className="inline-flex gap-1 relative w-[11rem] items-center btn btn-accent text-white group" + className="inline-flex gap-1 relative w-[11rem] items-center btn btn-accent dark:border-violet-400 text-white group" > -
diff --git a/pages/links/[id].tsx b/pages/links/[id].tsx index 5d98a76..e2879eb 100644 --- a/pages/links/[id].tsx +++ b/pages/links/[id].tsx @@ -244,7 +244,9 @@ export default function Index() { : undefined}

- {link?.name ?

{link?.description}

: undefined} + {link?.name ? ( +

{unescapeString(link?.description)}

+ ) : undefined}
diff --git a/pages/login.tsx b/pages/login.tsx index b9bdb3d..0bafd9b 100644 --- a/pages/login.tsx +++ b/pages/login.tsx @@ -1,4 +1,4 @@ -import SubmitButton from "@/components/SubmitButton"; +import AccentSubmitButton from "@/components/AccentSubmitButton"; import TextInput from "@/components/TextInput"; import CenteredForm from "@/layouts/CenteredForm"; import { signIn } from "next-auth/react"; @@ -13,6 +13,7 @@ interface FormData { const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER; const keycloakEnabled = process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED; +const authentikEnabled = process.env.NEXT_PUBLIC_AUTHENTIK_ENABLED; export default function Login() { const [submitLoader, setSubmitLoader] = useState(false); @@ -60,62 +61,94 @@ export default function Login() { setSubmitLoader(false); } + async function loginUserAuthentik() { + setSubmitLoader(true); + + const load = toast.loading("Authenticating..."); + + const res = await signIn("authentik", {}); + + toast.dismiss(load); + + setSubmitLoader(false); + } + return (
-

- Enter your credentials -

+ {process.env.NEXT_PUBLIC_DISABLE_LOGIN !== "true" ? ( + <> +

+ Enter your credentials +

-
+
-
-

- Username - {emailEnabled ? " or Email" : undefined} -

+
+

+ Username + {emailEnabled ? " or Email" : undefined} +

- setForm({ ...form, username: e.target.value })} - /> -
- -
-

Password

- - setForm({ ...form, password: e.target.value })} - /> - {emailEnabled && ( -
- - Forgot Password? - + + setForm({ ...form, username: e.target.value }) + } + />
- )} -
- +
+

Password

+ + + setForm({ ...form, password: e.target.value }) + } + /> + {emailEnabled && ( +
+ + Forgot Password? + +
+ )} +
+ + + + ) : undefined} {process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED === "true" ? ( - + ) : undefined} + {process.env.NEXT_PUBLIC_AUTHENTIK_ENABLED === "true" ? ( + ) : undefined} diff --git a/pages/register.tsx b/pages/register.tsx index d8b0650..d6e3bee 100644 --- a/pages/register.tsx +++ b/pages/register.tsx @@ -1,11 +1,11 @@ import Link from "next/link"; import { useState, FormEvent } from "react"; import { toast } from "react-hot-toast"; -import SubmitButton from "@/components/SubmitButton"; import { signIn } from "next-auth/react"; import { useRouter } from "next/router"; import CenteredForm from "@/layouts/CenteredForm"; import TextInput from "@/components/TextInput"; +import AccentSubmitButton from "@/components/AccentSubmitButton"; const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER; @@ -220,12 +220,12 @@ export default function Register() {
) : undefined} - + label="Sign Up" + className="w-full" + loading={submitLoader} + />

Already have an account?

diff --git a/pages/settings/account.tsx b/pages/settings/account.tsx index a3ba923..ee3e315 100644 --- a/pages/settings/account.tsx +++ b/pages/settings/account.tsx @@ -1,6 +1,10 @@ import { useState, useEffect } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faClose } from "@fortawesome/free-solid-svg-icons"; +import { + faClose, + faFileExport, + faFileImport, +} from "@fortawesome/free-solid-svg-icons"; import useAccountStore from "@/store/account"; import { AccountSettings } from "@/types/global"; import { toast } from "react-hot-toast"; @@ -247,10 +251,14 @@ export default function Account() {
- Import From + +

Import From

  • @@ -300,8 +308,12 @@ export default function Account() {

    Download your data instantly.

    -
    - Export Data +
    + +

    Export Data

    diff --git a/pages/subscribe.tsx b/pages/subscribe.tsx index 0524822..3cb4aa2 100644 --- a/pages/subscribe.tsx +++ b/pages/subscribe.tsx @@ -4,6 +4,7 @@ import { toast } from "react-hot-toast"; import { useRouter } from "next/router"; import CenteredForm from "@/layouts/CenteredForm"; import { Plan } from "@/types/global"; +import AccentSubmitButton from "@/components/AccentSubmitButton"; export default function Subscribe() { const [submitLoader, setSubmitLoader] = useState(false); @@ -47,7 +48,7 @@ export default function Subscribe() {

    -
    +
    - +
    signOut()} diff --git a/styles/globals.css b/styles/globals.css index 84eb162..709ff19 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -181,11 +181,11 @@ body { } .primary-btn-gradient { - box-shadow: inset 0px -10px 10px #0071b7; + box-shadow: inset 0px -9px 10px oklch(var(--p)); } .primary-btn-gradient:hover { - box-shadow: inset 0px -15px 10px #059bf8; + box-shadow: inset 0px -20px 40px #00436c; } .line-break * {