feat: refactored login

This commit is contained in:
Sebastian Hierholzer 2023-12-03 21:01:28 +01:00
parent 0f40578ca9
commit ffc927759e
No known key found for this signature in database
GPG Key ID: E4E9291D12E1CE60
7 changed files with 1637 additions and 219 deletions

View File

@ -13,45 +13,88 @@ export default async function updateUserById(
userId: number, userId: number,
data: AccountSettings data: AccountSettings
) { ) {
if (emailEnabled && !data.email) const ssoUser = await prisma.account.findFirst({
return {
response: "Email invalid.",
status: 400,
};
else if (!data.username)
return {
response: "Username invalid.",
status: 400,
};
if (data.newPassword && data.newPassword?.length < 8)
return {
response: "Password must be at least 8 characters.",
status: 400,
};
// Check email (if enabled)
const checkEmail =
/^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
if (emailEnabled && !checkEmail.test(data.email?.toLowerCase() || ""))
return {
response: "Please enter a valid email.",
status: 400,
};
const checkUsername = RegExp("^[a-z0-9_-]{3,31}$");
if (!checkUsername.test(data.username.toLowerCase()))
return {
response:
"Username has to be between 3-30 characters, no spaces and special characters are allowed.",
status: 400,
};
const userIsTaken = await prisma.user.findFirst({
where: { where: {
id: { not: userId }, userId: userId,
OR: emailEnabled },
? [ });
const user = await prisma.user.findUnique({
where: {
id: userId,
},
});
if (ssoUser) { // deny changes to SSO-defined properties
if (data.email !== user?.email) {
return {
response: "SSO users cannot change their email.",
status: 400,
};
}
if (data.newPassword) {
return {
response: "SSO Users cannot change their password.",
status: 400,
};
}
if (data.name !== user?.name) {
return {
response: "SSO Users cannot change their name.",
status: 400,
};
}
if (data.username !== user?.username) {
return {
response: "SSO Users cannot change their username.",
status: 400,
};
}
if (data.image !== "") {
return {
response: "SSO Users cannot change their avatar.",
status: 400,
};
}
} else { // verify only for non-SSO users
// SSO users cannot change their email, password, name, username, or avatar
if (emailEnabled && !data.email)
return {
response: "Email invalid.",
status: 400,
};
else if (!data.username)
return {
response: "Username invalid.",
status: 400,
};
if (data.newPassword && data.newPassword?.length < 8)
return {
response: "Password must be at least 8 characters.",
status: 400,
};
// Check email (if enabled)
const checkEmail =
/^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
if (emailEnabled && !checkEmail.test(data.email?.toLowerCase() || ""))
return {
response: "Please enter a valid email.",
status: 400,
};
const checkUsername = RegExp("^[a-z0-9_-]{3,31}$");
if (!checkUsername.test(data.username.toLowerCase()))
return {
response:
"Username has to be between 3-30 characters, no spaces and special characters are allowed.",
status: 400,
};
const userIsTaken = await prisma.user.findFirst({
where: {
id: { not: userId },
OR: emailEnabled
? [
{ {
username: data.username.toLowerCase(), username: data.username.toLowerCase(),
}, },
@ -59,66 +102,70 @@ export default async function updateUserById(
email: data.email?.toLowerCase(), email: data.email?.toLowerCase(),
}, },
] ]
: [ : [
{ {
username: data.username.toLowerCase(), username: data.username.toLowerCase(),
}, },
], ],
}, },
}); });
if (userIsTaken) {
if (data.email?.toLowerCase().trim() === userIsTaken.email?.trim())
return {
response: "Email is taken.",
status: 400,
};
else if (
data.username?.toLowerCase().trim() === userIsTaken.username?.trim()
)
return {
response: "Username is taken.",
status: 400,
};
if (userIsTaken) {
if (data.email?.toLowerCase().trim() === userIsTaken.email?.trim())
return { return {
response: "Email is taken.", response: "Username/Email is taken.",
status: 400,
};
else if (
data.username?.toLowerCase().trim() === userIsTaken.username?.trim()
)
return {
response: "Username is taken.",
status: 400,
};
return {
response: "Username/Email is taken.",
status: 400,
};
}
// Avatar Settings
if (data.image?.startsWith("data:image/jpeg;base64")) {
if (data.image.length < 1572864) {
try {
const base64Data = data.image.replace(/^data:image\/jpeg;base64,/, "");
createFolder({ filePath: `uploads/avatar` });
await createFile({
filePath: `uploads/avatar/${userId}.jpg`,
data: base64Data,
isBase64: true,
});
} catch (err) {
console.log("Error saving image:", err);
}
} else {
console.log("A file larger than 1.5MB was uploaded.");
return {
response: "A file larger than 1.5MB was uploaded.",
status: 400, status: 400,
}; };
} }
} else if (data.image == "") {
removeFile({ filePath: `uploads/avatar/${userId}.jpg` }); // Avatar Settings
if (data.image?.startsWith("data:image/jpeg;base64")) {
if (data.image.length < 1572864) {
try {
const base64Data = data.image.replace(/^data:image\/jpeg;base64,/, "");
createFolder({ filePath: `uploads/avatar` });
await createFile({
filePath: `uploads/avatar/${userId}.jpg`,
data: base64Data,
isBase64: true,
});
} catch (err) {
console.log("Error saving image:", err);
}
} else {
console.log("A file larger than 1.5MB was uploaded.");
return {
response: "A file larger than 1.5MB was uploaded.",
status: 400,
};
}
} else if (data.image == "") {
removeFile({ filePath: `uploads/avatar/${userId}.jpg` });
}
} }
const previousEmail = ( const previousEmail = (
await prisma.user.findUnique({ where: { id: userId } }) await prisma.user.findUnique({ where: { id: userId } })
)?.email; )?.email;
// Other settings // Other settings
const saltRounds = 10; const saltRounds = 10;
@ -130,7 +177,7 @@ export default async function updateUserById(
}, },
data: { data: {
name: data.name, name: data.name,
username: data.username.toLowerCase().trim(), username: data.username?.toLowerCase().trim(),
email: data.email?.toLowerCase().trim(), email: data.email?.toLowerCase().trim(),
isPrivate: data.isPrivate, isPrivate: data.isPrivate,
image: data.image ? `uploads/avatar/${userId}.jpg` : "", image: data.image ? `uploads/avatar/${userId}.jpg` : "",

View File

@ -31,13 +31,18 @@ export default async function verifyUser({
subscriptions: true, subscriptions: true,
}, },
}); });
const ssoUser = await prisma.account.findFirst({
where: {
userId: userId,
},
});
if (!user) { if (!user) {
res.status(404).json({ response: "User not found." }); res.status(404).json({ response: "User not found." });
return null; return null;
} }
if (!user.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.",
}); });

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import * as process from "process";
export type ResponseData = {
credentialsEnabled: string|undefined
emailEnabled: string|undefined
registrationDisabled: string|undefined
buttonAuths: {
method: string
name: string
}[]
}
export default function handler(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
res.json(getLogins());
}
export function getLogins() {
const buttonAuths = []
if (process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED === 'true') {
buttonAuths.push({method: 'keycloak', name: 'Keycloak'});
}
if (process.env.NEXT_PUBLIC_AUTHENTIK_ENABLED === 'true') {
buttonAuths.push({method: 'authentik', name: process.env.AUTHENTIK_CUSTOM_NAME ?? 'Authentik'});
}
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,
registrationDisabled: process.env.NEXT_PUBLIC_DISABLE_REGISTRATION,
buttonAuths: buttonAuths
};
}

View File

@ -5,16 +5,21 @@ import { signIn } from "next-auth/react";
import Link from "next/link"; import Link from "next/link";
import { useState, FormEvent } from "react"; import { useState, FormEvent } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import {getLogins} from './api/v1/logins'
import {InferGetServerSidePropsType} from "next";
interface FormData { interface FormData {
username: string; username: string;
password: string; password: string;
} }
const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER; export const getServerSideProps = (() => {
const keycloakEnabled = process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED; const availableLogins = getLogins();
return {props: {availableLogins}}
});
export default function Login() {
export default function Login({availableLogins} : InferGetServerSidePropsType<typeof getServerSideProps>) {
const [submitLoader, setSubmitLoader] = useState(false); const [submitLoader, setSubmitLoader] = useState(false);
const [form, setForm] = useState<FormData>({ const [form, setForm] = useState<FormData>({
@ -48,96 +53,106 @@ export default function Login() {
} }
} }
async function loginUserKeycloak() { async function loginUserButton(method: string) {
setSubmitLoader(true); setSubmitLoader(true);
const load = toast.loading("Authenticating..."); const load = toast.loading("Authenticating...");
const res = await signIn("keycloak", {}); const res = await signIn(method, {});
toast.dismiss(load); toast.dismiss(load);
setSubmitLoader(false); setSubmitLoader(false);
} }
return ( function displayLoginCredential() {
<CenteredForm text="Sign in to your account"> if (availableLogins.credentialsEnabled === 'true') {
<form onSubmit={loginUser}> return (<><p className="text-3xl text-black dark:text-white text-center font-extralight">
<div className="p-4 mx-auto flex flex-col gap-3 justify-between max-w-[30rem] min-w-80 w-full bg-slate-50 dark:bg-neutral-800 rounded-2xl shadow-md border border-sky-100 dark:border-neutral-700"> Enter your credentials
<p className="text-3xl text-black dark:text-white text-center font-extralight"> </p>
Enter your credentials <hr className="border-1 border-sky-100 dark:border-neutral-700"/>
<div>
<p className="text-sm text-black dark:text-white w-fit font-semibold mb-1">
Username
{availableLogins.emailEnabled === 'true' ? " or Email" : undefined}
</p> </p>
<hr className="border-1 border-sky-100 dark:border-neutral-700" /> <TextInput
<div>
<p className="text-sm text-black dark:text-white w-fit font-semibold mb-1">
Username
{emailEnabled ? " or Email" : undefined}
</p>
<TextInput
autoFocus={true} autoFocus={true}
placeholder="johnny" placeholder="johnny"
value={form.username} value={form.username}
className="bg-white" className="bg-white"
onChange={(e) => setForm({ ...form, username: e.target.value })} onChange={(e) => setForm({...form, username: e.target.value})}/>
/> </div>
</div> <div className="w-full">
<p className="text-sm text-black dark:text-white w-fit font-semibold mb-1">
Password
</p>
<div className="w-full"> <TextInput
<p className="text-sm text-black dark:text-white w-fit font-semibold mb-1">
Password
</p>
<TextInput
type="password" type="password"
placeholder="••••••••••••••" placeholder="••••••••••••••"
value={form.password} value={form.password}
className="bg-white" className="bg-white"
onChange={(e) => setForm({ ...form, password: e.target.value })} onChange={(e) => setForm({...form, password: e.target.value})}/>
/> {availableLogins.emailEnabled === 'true' && (
{emailEnabled && (
<div className="w-fit ml-auto mt-1"> <div className="w-fit ml-auto mt-1">
<Link <Link
href={"/forgot"} href={"/forgot"}
className="text-gray-500 dark:text-gray-400 font-semibold" className="text-gray-500 dark:text-gray-400 font-semibold"
> >
Forgot Password? Forgot Password?
</Link> </Link>
</div> </div>
)} )}
</div> </div>
<SubmitButton
<SubmitButton
type="submit" type="submit"
label="Login" label="Login"
className=" w-full text-center" className=" w-full text-center"
loading={submitLoader} loading={submitLoader}/></>
/> )
{process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED === "true" ? ( }
<SubmitButton }
type="button" function displayLoginExternalButton() {
onClick={loginUserKeycloak} const Buttons: any = [];
label="Sign in with Keycloak" availableLogins.buttonAuths.forEach((value, index) => {
className=" w-full text-center" Buttons.push(<SubmitButton key={index}
loading={submitLoader} type="button"
/> onClick={() => loginUserButton(value.method)}
) : undefined} label={`Sign in with ${value.name}`}
{process.env.NEXT_PUBLIC_DISABLE_REGISTRATION === className=" w-full text-center"
"true" ? undefined : ( loading={submitLoader}
<div className="flex items-baseline gap-1 justify-center"> />);
<p className="w-fit text-gray-500 dark:text-gray-400"> });
New here? return (Buttons);
</p> }
<Link
function displayRegistration() {
if (availableLogins.registrationDisabled !== 'true') {
return (
<div className="flex items-baseline gap-1 justify-center">
<p className="w-fit text-gray-500 dark:text-gray-400">
New here?
</p>
<Link
href={"/register"} href={"/register"}
className="block text-black dark:text-white font-semibold" className="block text-black dark:text-white font-semibold"
> >
Sign Up Sign Up
</Link> </Link>
</div> </div>
)} );
}
}
return (
<CenteredForm text="Sign in to your account">
<form onSubmit={loginUser}>
<div className="p-4 mx-auto flex flex-col gap-3 justify-between max-w-[30rem] min-w-80 w-full bg-slate-50 dark:bg-neutral-800 rounded-2xl shadow-md border border-sky-100 dark:border-neutral-700">
{displayLoginCredential()}
{displayLoginExternalButton()}
{displayRegistration()}
</div> </div>
</form> </form>
</CenteredForm> </CenteredForm>

View File

@ -1,13 +1,12 @@
import Link from "next/link"; import Link from "next/link";
import { useState, FormEvent } from "react"; import { useState, FormEvent } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import SubmitButton from "@/components/SubmitButton";
import { signIn } from "next-auth/react"; import { signIn } from "next-auth/react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import CenteredForm from "@/layouts/CenteredForm"; import CenteredForm from "@/layouts/CenteredForm";
import TextInput from "@/components/TextInput"; import TextInput from "@/components/TextInput";
const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER; const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER === "true";
type FormData = { type FormData = {
name: string; name: string;

421
types/enviornment.d.ts vendored
View File

@ -1,40 +1,399 @@
declare global { declare global {
namespace NodeJS { namespace NodeJS {
interface ProcessEnv { interface ProcessEnv {
NEXTAUTH_SECRET: string; NEXTAUTH_SECRET: string;
DATABASE_URL: string; DATABASE_URL: string;
NEXTAUTH_URL: string; NEXTAUTH_URL: string;
NEXT_PUBLIC_DISABLE_REGISTRATION?: string; NEXT_PUBLIC_DISABLE_REGISTRATION?: string;
PAGINATION_TAKE_COUNT?: string; PAGINATION_TAKE_COUNT?: string;
STORAGE_FOLDER?: string; STORAGE_FOLDER?: string;
AUTOSCROLL_TIMEOUT?: string; AUTOSCROLL_TIMEOUT?: string;
RE_ARCHIVE_LIMIT?: string; RE_ARCHIVE_LIMIT?: string;
SPACES_KEY?: string; SPACES_KEY?: string;
SPACES_SECRET?: string; SPACES_SECRET?: string;
SPACES_ENDPOINT?: string; SPACES_ENDPOINT?: string;
SPACES_BUCKET_NAME?: string; SPACES_BUCKET_NAME?: string;
SPACES_REGION?: string; SPACES_REGION?: string;
SPACES_FORCE_PATH_STYLE?: string; SPACES_FORCE_PATH_STYLE?: string;
NEXT_PUBLIC_KEYCLOAK_ENABLED?: string; NEXT_PUBLIC_CREDENTIALS_ENABLED?: string;
KEYCLOAK_ISSUER?: string; DISABLE_NEW_SSO_USERS?: string;
KEYCLOAK_CLIENT_ID?: string;
KEYCLOAK_CLIENT_SECRET?: string;
NEXT_PUBLIC_EMAIL_PROVIDER?: string; NEXT_PUBLIC_EMAIL_PROVIDER?: string;
EMAIL_FROM?: string; EMAIL_FROM?: string;
EMAIL_SERVER?: string; EMAIL_SERVER?: string;
NEXT_PUBLIC_STRIPE?: string; NEXT_PUBLIC_STRIPE?: string;
STRIPE_SECRET_KEY?: string; STRIPE_SECRET_KEY?: string;
MONTHLY_PRICE_ID?: string; MONTHLY_PRICE_ID?: string;
YEARLY_PRICE_ID?: string; YEARLY_PRICE_ID?: string;
NEXT_PUBLIC_STRIPE_BILLING_PORTAL_URL?: string; NEXT_PUBLIC_STRIPE_BILLING_PORTAL_URL?: string;
NEXT_PUBLIC_TRIAL_PERIOD_DAYS?: string; NEXT_PUBLIC_TRIAL_PERIOD_DAYS?: string;
BASE_URL?: string; BASE_URL?: string;
//
// SSO Providers
//
// 42 School
NEXT_PUBLIC_FORTYTWO_ENABLED?: string;
FORTYTWO_CUSTOM_NAME?: string;
FORTYTWO_CLIENT_ID?: string;
FORTYTWO_CLIENT_SECRET?: string;
// Apple
NEXT_PUBLIC_APPLE_ENABLED?: string;
APPLE_CUSTOM_NAME?: string;
APPLE_ID?: string;
APPLE_SECRET?: string;
// Atlassian
NEXT_PUBLIC_ATLASSIAN_ENABLED?: string;
ATLASSIAN_CUSTOM_NAME?: string;
ATLASSIAN_CLIENT_ID?: string;
ATLASSIAN_CLIENT_SECRET?: string;
ATLASSIAN_SCOPE?: string;
// Auth0
NEXT_PUBLIC_AUTH0_ENABLED?: string;
AUTH0_CUSTOM_NAME?: string;
AUTH0_ISSUER?: string;
AUTH0_CLIENT_SECRET?: string;
AUTH0_CLIENT_ID?: string;
// Authentik
NEXT_PUBLIC_AUTHENTIK_ENABLED?: string;
AUTHENTIK_CUSTOM_NAME?: string;
AUTHENTIK_ISSUER?: string;
AUTHENTIK_CLIENT_ID?: string;
AUTHENTIK_CLIENT_SECRET?: string;
// TODO: Azure AD B2C
// TODO: Azure AD
// Battle.net
NEXT_PUBLIC_BATTLENET_ENABLED?: string;
BATTLENET_CUSTOM_NAME?: string;
BATTLENET_CLIENT_ID?: string;
BATTLENET_CLIENT_SECRET?: string;
BATLLENET_ISSUER?: string;
// Box
NEXT_PUBLIC_BOX_ENABLED?: string;
BOX_CUSTOM_NAME?: string;
BOX_CLIENT_ID?: string;
BOX_CLIENT_SECRET?: string;
// TODO: BoxyHQ SAML
// Bungie
NEXT_PUBLIC_BUNGIE_ENABLED?: string;
BUNGIE_CUSTOM_NAME?: string;
BUNGIE_CLIENT_ID?: string;
BUNGIE_CLIENT_SECRET?: string;
BUNGIE_API_KEY?: string;
// Cognito
NEXT_PUBLIC_COGNITO_ENABLED?: string;
COGNITO_CUSTOM_NAME?: string;
COGNITO_CLIENT_ID?: string;
COGNITO_CLIENT_SECRET?: string;
COGNITO_ISSUER?: string;
// Coinbase
NEXT_PUBLIC_COINBASE_ENABLED?: string;
COINBASE_CUSTOM_NAME?: string;
COINBASE_CLIENT_ID?: string;
COINBASE_CLIENT_SECRET?: string;
// Discord
NEXT_PUBLIC_DISCORD_ENABLED?: string;
DISCORD_CUSTOM_NAME?: string;
DISCORD_CLIENT_ID?: string;
DISCORD_CLIENT_SECRET?: string;
// Dropbox
NEXT_PUBLIC_DROPBOX_ENABLED?: string;
DROPBOX_CUSTOM_NAME?: string;
DROPBOX_CLIENT_ID?: string;
DROPBOX_CLIENT_SECRET?: string;
// DuendeIndentityServer6
NEXT_PUBLIC_DUENDE_IDS6_ENABLED?: string;
DUENDE_IDS6_CUSTOM_NAME?: string;
DUENDE_IDS6_CLIENT_ID?: string;
DUENDE_IDS6_CLIENT_SECRET?: string;
DUENDE_IDS6_ISSUER?: string;
// EVE Online
NEXT_PUBLIC_EVEONLINE_ENABLED?: string;
EVEONLINE_CUSTOM_NAME?: string;
EVEONLINE_CLIENT_ID?: string;
EVEONLINE_CLIENT_SECRET?: string;
// Facebook
NEXT_PUBLIC_FACEBOOK_ENABLED?: string;
FACEBOOK_CUSTOM_NAME?: string;
FACEBOOK_CLIENT_ID?: string;
FACEBOOK_CLIENT_SECRET?: string;
// FACEIT
NEXT_PUBLIC_FACEIT_ENABLED?: string;
FACEIT_CUSTOM_NAME?: string;
FACEIT_CLIENT_ID?: string;
FACEIT_CLIENT_SECRET?: string;
// Foursquare
NEXT_PUBLIC_FOURSQUARE_ENABLED?: string;
FOURSQUARE_CUSTOM_NAME?: string;
FOURSQUARE_CLIENT_ID?: string;
FOURSQUARE_CLIENT_SECRET?: string;
FOURSQUARE_APIVERSION?: string;
// Freshbooks
NEXT_PUBLIC_FRESHBOOKS_ENABLED?: string;
FRESHBOOKS_CUSTOM_NAME?: string;
FRESHBOOKS_CLIENT_ID?: string;
FRESHBOOKS_CLIENT_SECRET?: string;
// FusionAuth
NEXT_PUBLIC_FUSIONAUTH_ENABLED?: string;
FUSIONAUTH_CUSTOM_NAME?: string;
FUSIONAUTH_CLIENT_ID?: string;
FUSIONAUTH_CLIENT_SECRET?: string;
FUSIONAUTH_ISSUER?: string;
FUSIONAUTH_TENANT_ID?: string;
// GitHub
NEXT_PUBLIC_GITHUB_ENABLED?: string;
GITHUB_CUSTOM_NAME?: string;
GITHUB_CLIENT_ID?: string;
GITHUB_CLIENT_SECRET?: string;
// GitLab
NEXT_PUBLIC_GITLAB_ENABLED?: string;
GITLAB_CUSTOM_NAME?: string;
GITLAB_CLIENT_ID?: string;
GITLAB_CLIENT_SECRET?: string;
// Google
NEXT_PUBLIC_GOOGLE_ENABLED?: string;
NEXT_PUBLIC_GOOGLE_CUSTOM_NAME?: string;
GOOGLE_CLIENT_ID?: string;
GOOGLE_CLIENT_SECRET?: string;
// HubSpot
NEXT_PUBLIC_HUBSPOT_ENABLED?: string;
HUBSPOT_CUSTOM_NAME?: string;
HUBSPOT_CLIENT_ID?: string;
HUBSPOT_CLIENT_SECRET?: string;
// IdentityServer4
NEXT_PUBLIC_IDS4_ENABLED?: string;
IDS4_CUSTOM_NAME?: string;
IDS4_CLIENT_ID?: string;
IDS4_CLIENT_SECRET?: string;
IDS4_ISSUER?: string;
// TODO: Instagram (Doesn't return email)
// Kakao
NEXT_PUBLIC_KAKAO_ENABLED?: string;
KAOKAO_CUSTOM_NAME?: string;
KAKAO_CLIENT_ID?: string;
KAKAO_CLIENT_SECRET?: string;
// Keycloak
NEXT_PUBLIC_KEYCLOAK_ENABLED?: string;
KEYCLOAK_CUSTOM_NAME?: string;
KEYCLOAK_ISSUER?: string;
KEYCLOAK_CLIENT_ID?: string;
KEYCLOAK_CLIENT_SECRET?: string;
// LINE
NEXT_PUBLIC_LINE_ENABLED?: string;
LINE_CUSTOM_NAME?: string;
LINE_CLIENT_ID?: string;
LINE_CLIENT_SECRET?: string;
// LinkedIn
NEXT_PUBLIC_LINKEDIN_ENABLED?: string;
LINKEDIN_CUSTOM_NAME?: string;
LINKEDIN_CLIENT_ID?: string;
LINKEDIN_CLIENT_SECRET?: string;
// Mailchimp
NEXT_PUBLIC_MAILCHIMP_ENABLED?: string;
MAILCHIMP_CUSTOM_NAME?: string;
MAILCHIMP_CLIENT_ID?: string;
MAILCHIMP_CLIENT_SECRET?: string;
// Mail.ru
NEXT_PUBLIC_MAILRU_ENABLED?: string;
MAILRU_CUSTOM_NAME?: string;
MAILRU_CLIENT_ID?: string;
MAILRU_CLIENT_SECRET?: string;
// TODO: Medium (Doesn't return email)
// Naver
NEXT_PUBLIC_NAVER_ENABLED?: string;
NAVER_CUSTOM_NAME?: string;
NAVER_CLIENT_ID?: string;
NAVER_CLIENT_SECRET?: string;
// Netlify
NEXT_PUBLIC_NETLIFY_ENABLED?: string;
NETLIFY_CUSTOM_NAME?: string;
NETLIFY_CLIENT_ID?: string;
NETLIFY_CLIENT_SECRET?: string;
// Okta
NEXT_PUBLIC_OKTA_ENABLED?: string;
OKTA_CUSTOM_NAME?: string;
OKTA_CLIENT_ID?: string;
OKTA_CLIENT_SECRET?: string;
OKTA_ISSUER?: string;
// OneLogin
NEXT_PUBLIC_ONELOGIN_ENABLED?: string;
ONELOGIN_CUSTOM_NAME?: string;
ONELOGIN_CLIENT_ID?: string;
ONELOGIN_CLIENT_SECRET?: string;
ONELOGIN_ISSUER?: string;
// Osso
NEXT_PUBLIC_OSSO_ENABLED?: string;
OSSO_CUSTOM_NAME?: string;
OSSO_CLIENT_ID?: string;
OSSO_CLIENT_SECRET?: string;
OSSO_ISSUER?: string;
// osu!
NEXT_PUBLIC_OSU_ENABLED?: string;
OSU_CUSTOM_NAME?: string;
OSU_CLIENT_ID?: string;
OSU_CLIENT_SECRET?: string;
// Patreon
NEXT_PUBLIC_PATREON_ENABLED?: string;
PATREON_CUSTOM_NAME?: string;
PATREON_CLIENT_ID?: string;
PATREON_CLIENT_SECRET?: string;
// Pinterest
NEXT_PUBLIC_PINTEREST_ENABLED?: string;
PINTEREST_CUSTOM_NAME?: string;
PINTEREST_CLIENT_ID?: string;
PINTEREST_CLIENT_SECRET?: string;
// Pipedrive
NEXT_PUBLIC_PIPEDRIVE_ENABLED?: string;
PIPEDRIVE_CUSTOM_NAME?: string;
PIPEDRIVE_CLIENT_ID?: string;
PIPEDRIVE_CLIENT_SECRET?: string;
// Reddit
// TODO (1h tokens)
NEXT_PUBLIC_REDDIT_ENABLED?: string;
REDDIT_CUSTOM_NAME?: string;
REDDIT_CLIENT_ID?: string;
REDDIT_CLIENT_SECRET?: string;
// Salesforce
NEXT_PUBLIC_SALESFORCE_ENABLED?: string;
SALESFORCE_CUSTOM_NAME?: string;
SALESFORCE_CLIENT_ID?: string;
SALESFORCE_CLIENT_SECRET?: string;
// Slack
NEXT_PUBLIC_SLACK_ENABLED?: string;
SLACK_CUSTOM_NAME?: string;
SLACK_CLIENT_ID?: string;
SLACK_CLIENT_SECRET?: string;
// Spotify
NEXT_PUBLIC_SPOTIFY_ENABLED?: string;
SPOTIFY_CUSTOM_NAME?: string;
SPOTIFY_CLIENT_ID?: string;
SPOTIFY_CLIENT_SECRET?: string;
// Strava
NEXT_PUBLIC_STRAVA_ENABLED?: string;
STRAVA_CUSTOM_NAME?: string;
STRAVA_CLIENT_ID?: string;
STRAVA_CLIENT_SECRET?: string;
// Todoist
NEXT_PUBLIC_TODOIST_ENABLED?: string;
TODOIST_CUSTOM_NAME?: string;
TODOIST_CLIENT_ID?: string;
TODOIST_CLIENT_SECRET?: string;
// TODO: Trakt (Doesn't return email)
// Twitch
NEXT_PUBLIC_TWITCH_ENABLED?: string;
TWITCH_CUSTOM_NAME?: string;
TWITCH_CLIENT_ID?: string;
TWITCH_CLIENT_SECRET?: string;
// TODO: Twitter (OAuth 1.0)
// United Effects
NEXT_PUBLIC_UNITED_EFFECTS_ENABLED?: string;
UNITED_EFFECTS_CUSTOM_NAME?: string;
UNITED_EFFECTS_CLIENT_ID?: string;
UNITED_EFFECTS_CLIENT_SECRET?: string;
UNITED_EFFECTS_ISSUER?: string;
// VK
NEXT_PUBLIC_VK_ENABLED?: string;
VK_CUSTOM_NAME?: string;
VK_CLIENT_ID?: string;
VK_CLIENT_SECRET?: string;
// Wikimedia
NEXT_PUBLIC_WIKIMEDIA_ENABLED?: string;
WIKIMEDIA_CUSTOM_NAME?: string;
WIKIMEDIA_CLIENT_ID?: string;
WIKIMEDIA_CLIENT_SECRET?: string;
// Wordpress.com
NEXT_PUBLIC_WORDPRESS_ENABLED?: string;
WORDPRESS_CUSTOM_NAME?: string;
WORDPRESS_CLIENT_ID?: string;
WORDPRESS_CLIENT_SECRET?: string;
// TODO: WorkOS (Custom flow)
// Yandex
NEXT_PUBLIC_YANDEX_ENABLED?: string;
YANDEX_CUSTOM_NAME?: string;
YANDEX_CLIENT_ID?: string;
YANDEX_CLIENT_SECRET?: string;
// Zitadel
NEXT_PUBLIC_ZITADEL_ENABLED?: string;
ZITADEL_CUSTOM_NAME?: string;
ZITADEL_CLIENT_ID?: string;
ZITADEL_CLIENT_SECRET?: string;
ZITADEL_ISSUER?: string;
// Zoho
NEXT_PUBLIC_ZOHO_ENABLED?: string;
ZOHO_CUSTOM_NAME?: string;
ZOHO_CLIENT_ID?: string;
ZOHO_CLIENT_SECRET?: string;
// Zoom
NEXT_PUBLIC_ZOOM_ENABLED?: string;
ZOOM_CUSTOM_NAME?: string;
ZOOM_CLIENT_ID?: string;
ZOOM_CLIENT_SECRET?: string;
}
} }
}
} }
export {}; export {};