Merge pull request #332 from linkwarden/feat/refactor-login

Feat/refactor login
This commit is contained in:
Daniel 2023-12-08 01:14:51 +03:30 committed by GitHub
commit ab83d1d0c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 2194 additions and 269 deletions

View File

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

View File

@ -13,106 +13,154 @@ export default async function updateUserById(
userId: number,
data: AccountSettings
) {
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({
const ssoUser = await prisma.account.findFirst({
where: {
id: { not: userId },
OR: emailEnabled
? [
{
username: data.username.toLowerCase(),
},
{
email: data.email?.toLowerCase(),
},
]
: [
{
username: data.username.toLowerCase(),
},
],
userId: userId,
},
});
if (userIsTaken) {
if (data.email?.toLowerCase().trim() === userIsTaken.email?.trim())
const user = await prisma.user.findUnique({
where: {
id: userId,
},
});
if (ssoUser) {
// deny changes to SSO-defined properties
if (data.email !== user?.email) {
return {
response: "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.",
response: "SSO users cannot change their email.",
status: 400,
};
}
} else if (data.image == "") {
removeFile({ filePath: `uploads/avatar/${userId}.jpg` });
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(),
},
{
email: data.email?.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,
};
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,
};
}
} else if (data.image == "") {
removeFile({ filePath: `uploads/avatar/${userId}.jpg` });
}
}
const previousEmail = (
@ -130,15 +178,13 @@ export default async function updateUserById(
},
data: {
name: data.name,
username: data.username.toLowerCase().trim(),
username: data.username?.toLowerCase().trim(),
email: data.email?.toLowerCase().trim(),
isPrivate: data.isPrivate,
image: data.image ? `uploads/avatar/${userId}.jpg` : "",
archiveAsScreenshot: data.archiveAsScreenshot,
archiveAsPDF: data.archiveAsPDF,
archiveAsWaybackMachine: data.archiveAsWaybackMachine,
displayLinkIcons: data.displayLinkIcons,
blurredFavicons: data.blurredFavicons,
password:
data.newPassword && data.newPassword !== ""
? newHashedPassword

View File

@ -31,13 +31,18 @@ export default async function verifyUser({
subscriptions: true,
},
});
const ssoUser = await prisma.account.findFirst({
where: {
userId: userId,
},
});
if (!user) {
res.status(404).json({ response: "User not found." });
return null;
}
if (!user.username) {
if (!user.username && !ssoUser) { // SSO users don't need a username
res.status(401).json({
response: "Username not found.",
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,238 @@
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 = []
// 42 School
if (process.env.NEXT_PUBLIC_FORTYTWO_ENABLED === 'true') {
buttonAuths.push({method: '42-school', name: process.env.FORTYTWO_CUSTOM_NAME ?? '42 School'});
}
// Apple
if (process.env.NEXT_PUBLIC_APPLE_ENABLED === 'true') {
buttonAuths.push({method: 'apple', name: process.env.APPLE_CUSTOM_NAME ?? 'Apple'});
}
// Atlassian
if (process.env.NEXT_PUBLIC_ATLASSIAN_ENABLED === 'true') {
buttonAuths.push({method: 'atlassian', name: process.env.ATLASSIAN_CUSTOM_NAME ?? 'Atlassian'});
}
// Auth0
if (process.env.NEXT_PUBLIC_AUTH0_ENABLED === 'true') {
buttonAuths.push({method: 'auth0', name: process.env.AUTH0_CUSTOM_NAME ?? 'Auth0'});
}
// Authentik
if (process.env.NEXT_PUBLIC_AUTHENTIK_ENABLED === 'true') {
buttonAuths.push({method: 'authentik', name: process.env.AUTHENTIK_CUSTOM_NAME ?? 'Authentik'});
}
// Battle.net
if (process.env.NEXT_PUBLIC_BATTLENET_ENABLED === 'true') {
buttonAuths.push({method: 'battlenet', name: process.env.BATTLENET_CUSTOM_NAME ?? 'Battle.net'});
}
// Box
if (process.env.NEXT_PUBLIC_BOX_ENABLED === 'true') {
buttonAuths.push({method: 'box', name: process.env.BOX_CUSTOM_NAME ?? 'Box'});
}
// Cognito
if (process.env.NEXT_PUBLIC_COGNITO_ENABLED === 'true') {
buttonAuths.push({method: 'cognito', name: process.env.COGNITO_CUSTOM_NAME ?? 'Cognito'});
}
// Coinbase
if (process.env.NEXT_PUBLIC_COINBASE_ENABLED === 'true') {
buttonAuths.push({method: 'coinbase', name: process.env.COINBASE_CUSTOM_NAME ?? 'Coinbase'});
}
// Discord
if (process.env.NEXT_PUBLIC_DISCORD_ENABLED === 'true') {
buttonAuths.push({method: 'discord', name: process.env.DISCORD_CUSTOM_NAME ?? 'Discord'});
}
// Dropbox
if (process.env.NEXT_PUBLIC_DROPBOX_ENABLED === 'true') {
buttonAuths.push({method: 'dropbox', name: process.env.DROPBOX_CUSTOM_NAME ?? 'Dropbox'});
}
// Duende IdentityServer6
if (process.env.NEXT_PUBLIC_DUENDE_IDS6_ENABLED === 'true') {
buttonAuths.push({method: 'duende-identityserver6', name: process.env.DUENDE_IDS6_CUSTOM_NAME ?? 'DuendeIdentityServer6'});
}
// EVE Online
if (process.env.NEXT_PUBLIC_EVEONLINE_ENABLED === 'true') {
buttonAuths.push({method: 'eveonline', name: process.env.EVEONLINE_CUSTOM_NAME ?? 'EVE Online'});
}
// Facebook
if (process.env.NEXT_PUBLIC_FACEBOOK_ENABLED === 'true') {
buttonAuths.push({method: 'facebook', name: process.env.FACEBOOK_CUSTOM_NAME ?? 'Facebook'});
}
// FACEIT
if (process.env.NEXT_PUBLIC_FACEIT_ENABLED === 'true') {
buttonAuths.push({method: 'faceit', name: process.env.FACEIT_CUSTOM_NAME ?? 'FACEIT'});
}
// Foursquare
if (process.env.NEXT_PUBLIC_FOURSQUARE_ENABLED === 'true') {
buttonAuths.push({method: 'foursquare', name: process.env.FOURSQUARE_CUSTOM_NAME ?? 'Foursquare'});
}
// Freshbooks
if (process.env.NEXT_PUBLIC_FRESHBOOKS_ENABLED === 'true') {
buttonAuths.push({method: 'freshbooks', name: process.env.FRESHBOOKS_CUSTOM_NAME ?? 'Freshbooks'});
}
// FusionAuth
if (process.env.NEXT_PUBLIC_FUSIONAUTH_ENABLED === 'true') {
buttonAuths.push({method: 'fusionauth', name: process.env.FUSIONAUTH_CUSTOM_NAME ?? 'FusionAuth'});
}
// GitHub
if (process.env.NEXT_PUBLIC_GITHUB_ENABLED === 'true') {
buttonAuths.push({method: 'github', name: process.env.GITHUB_CUSTOM_NAME ?? 'GitHub'});
}
// GitLab
if (process.env.NEXT_PUBLIC_GITLAB_ENABLED === 'true') {
buttonAuths.push({method: 'gitlab', name: process.env.GITLAB_CUSTOM_NAME ?? 'GitLab'});
}
// Google
if (process.env.NEXT_PUBLIC_GOOGLE_ENABLED === 'true') {
buttonAuths.push({method: 'google', name: process.env.GOOGLE_CUSTOM_NAME ?? 'Google'});
}
// HubSpot
if (process.env.NEXT_PUBLIC_HUBSPOT_ENABLED === 'true') {
buttonAuths.push({method: 'hubspot', name: process.env.HUBSPOT_CUSTOM_NAME ?? 'HubSpot'});
}
// IdentityServer4
if (process.env.NEXT_PUBLIC_IDS4_ENABLED === 'true') {
buttonAuths.push({method: 'identity-server4', name: process.env.IDS4_CUSTOM_NAME ?? 'IdentityServer4'});
}
// Kakao
if (process.env.NEXT_PUBLIC_KAKAO_ENABLED === 'true') {
buttonAuths.push({method: 'kakao', name: process.env.KAKAO_CUSTOM_NAME ?? 'Kakao'});
}
// Keycloak
if (process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED === 'true') {
buttonAuths.push({method: 'keycloak', name: process.env.KEYCLOAK_CUSTOM_NAME ?? 'Keycloak'});
}
// LINE
if (process.env.NEXT_PUBLIC_LINE_ENABLED === 'true') {
buttonAuths.push({method: 'line', name: process.env.LINE_CUSTOM_NAME ?? 'LINE'});
}
// LinkedIn
if (process.env.NEXT_PUBLIC_LINKEDIN_ENABLED === 'true') {
buttonAuths.push({method: 'linkedin', name: process.env.LINKEDIN_CUSTOM_NAME ?? 'LinkedIn'});
}
// MailChimp
if (process.env.NEXT_PUBLIC_MAILCHIMP_ENABLED === 'true') {
buttonAuths.push({method: 'mailchimp', name: process.env.MAILCHIMP_CUSTOM_NAME ?? 'Mailchimp'});
}
// Mail.ru
if (process.env.NEXT_PUBLIC_MAILRU_ENABLED === 'true') {
buttonAuths.push({method: 'mailru', name: process.env.MAILRU_CUSTOM_NAME ?? 'Mail.ru'});
}
// Naver
if (process.env.NEXT_PUBLIC_NAVER_ENABLED === 'true') {
buttonAuths.push({method: 'naver', name: process.env.NAVER_CUSTOM_NAME ?? 'Naver'});
}
// Netlify
if (process.env.NEXT_PUBLIC_NETLIFY_ENABLED === 'true') {
buttonAuths.push({method: 'netlify', name: process.env.NETLIFY_CUSTOM_NAME ?? 'Netlify'});
}
// 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
};
}

View File

@ -5,17 +5,22 @@ import { signIn } from "next-auth/react";
import Link from "next/link";
import { useState, FormEvent } from "react";
import { toast } from "react-hot-toast";
import { getLogins } from "./api/v1/logins";
import { InferGetServerSidePropsType } from "next";
interface FormData {
username: string;
password: string;
}
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 const getServerSideProps = () => {
const availableLogins = getLogins();
return { props: { availableLogins } };
};
export default function Login() {
export default function Login({
availableLogins,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
const [submitLoader, setSubmitLoader] = useState(false);
const [form, setForm] = useState<FormData>({
@ -49,118 +54,123 @@ export default function Login() {
}
}
async function loginUserKeycloak() {
async function loginUserButton(method: string) {
setSubmitLoader(true);
const load = toast.loading("Authenticating...");
const res = await signIn("keycloak", {});
const res = await signIn(method, {});
toast.dismiss(load);
setSubmitLoader(false);
}
async function loginUserAuthentik() {
setSubmitLoader(true);
function displayLoginCredential() {
if (availableLogins.credentialsEnabled === "true") {
return (
<>
<p className="text-3xl text-black dark:text-white text-center font-extralight">
Enter your credentials
</p>
<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>
const load = toast.loading("Authenticating...");
<TextInput
autoFocus={true}
placeholder="johnny"
value={form.username}
className="bg-base-100"
onChange={(e) => setForm({ ...form, username: e.target.value })}
/>
</div>
<div className="w-full">
<p className="text-sm text-black dark:text-white w-fit font-semibold mb-1">
Password
</p>
const res = await signIn("authentik", {});
<TextInput
type="password"
placeholder="••••••••••••••"
value={form.password}
className="bg-base-100"
onChange={(e) => setForm({ ...form, password: e.target.value })}
/>
{availableLogins.emailEnabled === "true" && (
<div className="w-fit ml-auto mt-1">
<Link
href={"/forgot"}
className="text-gray-500 dark:text-gray-400 font-semibold"
>
Forgot Password?
</Link>
</div>
)}
</div>
<AccentSubmitButton
type="submit"
label="Login"
className=" w-full text-center"
loading={submitLoader}
/>
toast.dismiss(load);
{availableLogins.buttonAuths.length > 0 ? (
<div className="divider my-1">OR</div>
) : undefined}
</>
);
}
}
function displayLoginExternalButton() {
const Buttons: any = [];
availableLogins.buttonAuths.forEach((value, index) => {
Buttons.push(
<>
{index !== 0 ? <div className="divider my-1">OR</div> : undefined}
setSubmitLoader(false);
<AccentSubmitButton
key={index}
type="button"
onClick={() => loginUserButton(value.method)}
label={`Sign in with ${value.name}`}
className=" w-full text-center"
loading={submitLoader}
/>
</>
);
});
return Buttons;
}
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"}
className="block text-black dark:text-white font-semibold"
>
Sign Up
</Link>
</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-base-200 rounded-2xl shadow-md border border-neutral-content">
{process.env.NEXT_PUBLIC_DISABLE_LOGIN !== "true" ? (
<>
<p className="text-3xl text-center font-extralight">
Enter your credentials
</p>
<div className="divider my-0"></div>
<div>
<p className="text-sm w-fit font-semibold mb-1">
Username
{emailEnabled ? " or Email" : undefined}
</p>
<TextInput
autoFocus={true}
placeholder="johnny"
value={form.username}
className="bg-base-100"
onChange={(e) =>
setForm({ ...form, username: e.target.value })
}
/>
</div>
<div className="w-full">
<p className="text-sm w-fit font-semibold mb-1">Password</p>
<TextInput
type="password"
placeholder="••••••••••••••"
value={form.password}
className="bg-base-100"
onChange={(e) =>
setForm({ ...form, password: e.target.value })
}
/>
{emailEnabled && (
<div className="w-fit ml-auto mt-1">
<Link
href={"/forgot"}
className="text-neutral font-semibold"
>
Forgot Password?
</Link>
</div>
)}
</div>
<AccentSubmitButton
type="submit"
label="Login"
className="w-full text-center"
loading={submitLoader}
/>
</>
) : undefined}
{process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED === "true" ? (
<AccentSubmitButton
type="button"
onClick={loginUserKeycloak}
label="Sign in with Keycloak"
className="w-full text-center"
loading={submitLoader}
/>
) : undefined}
{process.env.NEXT_PUBLIC_AUTHENTIK_ENABLED === "true" ? (
<AccentSubmitButton
type="button"
onClick={loginUserAuthentik}
label="Sign in with Authentiks"
className="w-full text-center"
loading={submitLoader}
/>
) : undefined}
{process.env.NEXT_PUBLIC_DISABLE_REGISTRATION ===
"true" ? undefined : (
<div className="flex items-baseline gap-1 justify-center">
<p className="w-fit text-neutral">New here?</p>
<Link href={"/register"} className="block font-semibold">
Sign Up
</Link>
</div>
)}
<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>
</form>
</CenteredForm>

View File

@ -7,7 +7,7 @@ import CenteredForm from "@/layouts/CenteredForm";
import TextInput from "@/components/TextInput";
import AccentSubmitButton from "@/components/AccentSubmitButton";
const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER;
const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER === "true";
type FormData = {
name: string;

View File

@ -59,7 +59,7 @@ export default function Api() {
<div className="divider my-3"></div>
<div className="flex flex-col gap-3">
<div className="badge badge-warning rounded-md w-fit p-4">
<div className="badge badge-warning rounded-md w-fit">
Status: Under Development
</div>

View File

@ -84,7 +84,7 @@ export default function Subscribe() {
Billed {plan === Plan.monthly ? "Monthly" : "Yearly"}
</p>
<fieldset className="w-full flex-col flex justify-evenly px-4 pb-4 pt-1 rounded-md border border-neutral-content">
<legend className="w-fit font-extralight px-2 border border-neutral rounded-md text-xl">
<legend className="w-fit font-extralight px-2 border border-neutral-content rounded-md text-xl">
Total
</legend>

367
types/enviornment.d.ts vendored
View File

@ -18,10 +18,8 @@ declare global {
SPACES_REGION?: string;
SPACES_FORCE_PATH_STYLE?: string;
NEXT_PUBLIC_KEYCLOAK_ENABLED?: string;
KEYCLOAK_ISSUER?: string;
KEYCLOAK_CLIENT_ID?: string;
KEYCLOAK_CLIENT_SECRET?: string;
NEXT_PUBLIC_CREDENTIALS_ENABLED?: string;
DISABLE_NEW_SSO_USERS?: string;
NEXT_PUBLIC_EMAIL_PROVIDER?: string;
EMAIL_FROM?: string;
@ -34,6 +32,367 @@ declare global {
NEXT_PUBLIC_STRIPE_BILLING_PORTAL_URL?: string;
NEXT_PUBLIC_TRIAL_PERIOD_DAYS?: 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;
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;
KAKAO_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;
}
}
}