diff --git a/.env.example b/.env.example index 9dba2e3707..1807ad1d00 100644 --- a/.env.example +++ b/.env.example @@ -69,3 +69,7 @@ NANGO_TELEMETRY_SDK=false # Getting Started configuration DEFAULT_GITHUB_CLIENT_ID= DEFAULT_GITHUB_CLIENT_SECRET= + +# Hosted Auth Configuration +WORKOS_API_KEY= +WORKOS_CLIENT_ID= diff --git a/package-lock.json b/package-lock.json index 5301e40c6f..0c51b6f980 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8814,6 +8814,14 @@ } } }, + "node_modules/@workos-inc/node": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@workos-inc/node/-/node-6.2.0.tgz", + "integrity": "sha512-nrfhsEsFUNhYzS4BW0WnuCgU+668ixANaDfGuCfz7bcSb1lrgmfMmTnqDlllsLjXmMYPT4Z4KerQjOq2+fRDoQ==", + "dependencies": { + "pluralize": "8.0.0" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "license": "BSD-3-Clause" @@ -20664,6 +20672,14 @@ "node": ">=4" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "engines": { + "node": ">=4" + } + }, "node_modules/postcss": { "version": "8.4.35", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", @@ -31128,6 +31144,7 @@ "dependencies": { "@hapi/boom": "^10.0.1", "@nangohq/shared": "^0.39.5", + "@workos-inc/node": "^6.2.0", "axios": "^1.3.4", "body-parser": "1.20.2", "connect-session-knex": "4.0.0", diff --git a/packages/server/lib/controllers/account.controller.ts b/packages/server/lib/controllers/account.controller.ts index fc2143e136..fa8f07d242 100644 --- a/packages/server/lib/controllers/account.controller.ts +++ b/packages/server/lib/controllers/account.controller.ts @@ -1,9 +1,8 @@ import type { Request, Response, NextFunction } from 'express'; import type { LogLevel } from '@nangohq/shared'; -import { accountService, userService, errorManager, LogActionEnum, createActivityLogAndLogMessage, isEnterprise, isCloud } from '@nangohq/shared'; +import { accountService, userService, errorManager, LogActionEnum, createActivityLogAndLogMessage, isCloud } from '@nangohq/shared'; import { getUserAccountAndEnvironmentFromSession } from '../utils/utils.js'; -export const AUTH_ENABLED = isCloud() || isEnterprise(); export const NANGO_ADMIN_UUID = process.env['NANGO_ADMIN_UUID']; export const AUTH_ADMIN_SWITCH_ENABLED = NANGO_ADMIN_UUID && isCloud(); export const AUTH_ADMIN_SWITCH_MS = 600 * 1000; diff --git a/packages/server/lib/controllers/auth.controller.ts b/packages/server/lib/controllers/auth.controller.ts index 2fa71779fa..fc07f8a945 100644 --- a/packages/server/lib/controllers/auth.controller.ts +++ b/packages/server/lib/controllers/auth.controller.ts @@ -1,20 +1,25 @@ import type { Request, Response, NextFunction } from 'express'; +import { WorkOS } from '@workos-inc/node'; import crypto from 'crypto'; import util from 'util'; import { resetPasswordSecret, getUserAccountAndEnvironmentFromSession } from '../utils/utils.js'; import jwt from 'jsonwebtoken'; import EmailClient from '../clients/email.client.js'; -import type { User } from '@nangohq/shared'; +import type { User, Result } from '@nangohq/shared'; import { userService, accountService, errorManager, + isOk, + resultOk, + resultErr, ErrorSourceEnum, environmentService, analytics, AnalyticsTypes, isCloud, getBaseUrl, + getBasePublicUrl, NangoError, createOnboardingProvider } from '@nangohq/shared'; @@ -26,6 +31,57 @@ export interface WebUser { name: string; } +interface InviteAccountBody { + accountId: number; +} +interface InviteAccountState extends InviteAccountBody { + token: string; +} + +let workos: WorkOS | null = null; +if (process.env['WORKOS_API_KEY'] && process.env['WORKOS_CLIENT_ID']) { + workos = new WorkOS(process.env['WORKOS_API_KEY']); +} else { + if (isCloud()) { + throw new NangoError('workos_not_configured'); + } +} + +const allowedProviders = ['GoogleOAuth']; + +const parseState = (state: string): Result => { + try { + const parsed = JSON.parse(Buffer.from(state, 'base64').toString('ascii')) as InviteAccountState; + return resultOk(parsed); + } catch { + const error = new Error('Invalid state'); + return resultErr(error); + } +}; + +const createAccountIfNotInvited = async (name: string, state?: string): Promise => { + if (!state) { + const account = await accountService.createAccount(`${name}'s Organization`); + if (!account) { + throw new NangoError('account_creation_failure'); + } + return account.id; + } + + const parsedState: Result = parseState(state); + + if (isOk(parsedState)) { + const { accountId, token } = parsedState.res; + const validToken = await userService.getInvitedUserByToken(token); + if (validToken) { + await userService.markAcceptedInvite(token); + } + return accountId; + } + + return null; +}; + class AuthController { async signin(req: Request, res: Response, next: NextFunction) { try { @@ -105,7 +161,7 @@ class AuthController { account = await accountService.getAccountById(Number(req.body['account_id'])); joinedWithToken = true; } else { - account = await environmentService.createAccount(`${name}'s Organization`); + account = await accountService.createAccount(`${name}'s Organization`); } if (account == null) { @@ -260,6 +316,158 @@ class AuthController { next(error); } } + + getManagedLogin(req: Request, res: Response, next: NextFunction) { + try { + const provider = req.query['provider'] as string; + + if (!provider || !allowedProviders.includes(provider)) { + errorManager.errRes(res, 'invalid_provider'); + return; + } + + if (!workos) { + errorManager.errRes(res, 'workos_not_configured'); + return; + } + + const oAuthUrl = workos?.userManagement.getAuthorizationUrl({ + clientId: process.env['WORKOS_CLIENT_ID'] || '', + provider, + redirectUri: `${getBaseUrl()}/api/v1/login/callback` + }); + + res.send({ url: oAuthUrl }); + } catch (err) { + next(err); + } + } + + getManagedLoginWithInvite(req: Request, res: Response, next: NextFunction) { + try { + const provider = req.query['provider'] as string; + + if (!provider || !allowedProviders.includes(provider)) { + errorManager.errRes(res, 'invalid_provider'); + return; + } + + const token = req.params['token'] as string; + + const body: InviteAccountBody = req.body as InviteAccountBody; + + if (!body || body.accountId !== undefined) { + errorManager.errRes(res, 'missing_params'); + return; + } + + if (!provider || !token) { + errorManager.errRes(res, 'missing_params'); + return; + } + + if (!workos) { + errorManager.errRes(res, 'workos_not_configured'); + return; + } + + const accountId = body.accountId; + + const inviteParams: InviteAccountState = { + accountId, + token + }; + + const oAuthUrl = workos?.userManagement.getAuthorizationUrl({ + clientId: process.env['WORKOS_CLIENT_ID'] || '', + provider, + redirectUri: `${getBaseUrl()}/api/v1/login/callback`, + state: JSON.stringify(inviteParams) + }); + + res.send({ url: oAuthUrl }); + } catch (err) { + next(err); + } + } + + async loginCallback(req: Request, res: Response, next: NextFunction) { + try { + const { code, state } = req.query; + + if (!workos) { + errorManager.errRes(res, 'workos_not_configured'); + return; + } + + if (!code) { + errorManager.errRes(res, 'missing_managed_login_callback_code'); + return; + } + + const { user: authorizedUser, organizationId } = await workos.userManagement.authenticateWithCode({ + clientId: process.env['WORKOS_CLIENT_ID'] || '', + code: code as string + }); + + const existingUser = await userService.getUserByEmail(authorizedUser.email); + + if (existingUser) { + req.login(existingUser, function (err) { + if (err) { + return next(err); + } + res.redirect(`${getBasePublicUrl()}/`); + }); + + return; + } + + const name = + authorizedUser.firstName || authorizedUser.lastName + ? `${authorizedUser.firstName || ''} ${authorizedUser.lastName || ''}` + : authorizedUser.email.split('@')[0]; + + let accountId: number | null = null; + + if (organizationId) { + // in this case we have a pre registered organization with workos + // let's make sure it exists in our system + const organization = await workos.organizations.getOrganization(organizationId); + + const account = await accountService.getOrCreateAccount(organization.name); + + if (!account) { + throw new NangoError('account_creation_failure'); + } + accountId = account.id; + } else { + if (!name) { + throw new NangoError('missing_name_for_account_creation'); + } + + accountId = await createAccountIfNotInvited(name, state as string); + + if (!accountId) { + throw new NangoError('account_creation_failure'); + } + } + + const user = await userService.createUser(authorizedUser.email, name as string, '', '', accountId); + if (!user) { + throw new NangoError('user_creation_failure'); + } + + req.login(user, function (err) { + if (err) { + return next(err); + } + res.redirect(`${getBasePublicUrl()}/`); + }); + } catch (err) { + next(err); + } + } } export default new AuthController(); diff --git a/packages/server/lib/server.ts b/packages/server/lib/server.ts index 355a403da1..43287c1110 100644 --- a/packages/server/lib/server.ts +++ b/packages/server/lib/server.ts @@ -31,13 +31,15 @@ import { setupAuth } from './clients/auth.client.js'; import publisher from './clients/publisher.client.js'; import passport from 'passport'; import environmentController from './controllers/environment.controller.js'; -import accountController, { AUTH_ENABLED } from './controllers/account.controller.js'; +import accountController from './controllers/account.controller.js'; import type { Response, Request } from 'express'; import Logger from './utils/logger.js'; import { getGlobalOAuthCallbackUrl, environmentService, getPort, + AUTH_ENABLED, + MANAGED_AUTH_ENABLED, isCloud, isEnterprise, isBasicAuthEnabled, @@ -152,6 +154,12 @@ if (AUTH_ENABLED) { app.route('/api/v1/reset-password').put(rateLimiterMiddleware, authController.resetPassword.bind(authController)); } +if (MANAGED_AUTH_ENABLED) { + app.route('/api/v1/managed/signup').post(rateLimiterMiddleware, authController.getManagedLogin.bind(authController)); + app.route('/api/v1/managed/signup/:token').post(rateLimiterMiddleware, authController.getManagedLoginWithInvite.bind(authController)); + app.route('/api/v1/login/callback').get(rateLimiterMiddleware, authController.loginCallback.bind(authController)); +} + // Webapp routes (session auth). app.route('/api/v1/meta').get(webAuth, environmentController.meta.bind(environmentController)); app.route('/api/v1/account').get(webAuth, accountController.getAccount.bind(accountController)); diff --git a/packages/server/package.json b/packages/server/package.json index 13f2863107..83c7b19739 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -24,6 +24,7 @@ "dependencies": { "@hapi/boom": "^10.0.1", "@nangohq/shared": "^0.39.5", + "@workos-inc/node": "^6.2.0", "axios": "^1.3.4", "body-parser": "1.20.2", "connect-session-knex": "4.0.0", diff --git a/packages/shared/lib/services/account.service.ts b/packages/shared/lib/services/account.service.ts index 2b8ee4cfc2..7bbac74452 100644 --- a/packages/shared/lib/services/account.service.ts +++ b/packages/shared/lib/services/account.service.ts @@ -2,6 +2,7 @@ import db from '../db/database.js'; import type { Account } from '../models/Admin'; import type { Environment } from '../models/Environment'; import { LogActionEnum } from '../models/Activity.js'; +import environmentService from './environment.service.js'; import errorManager, { ErrorSourceEnum } from '../utils/error.manager.js'; class AccountService { @@ -67,6 +68,39 @@ class AccountService { return account[0].uuid; } + + async getOrCreateAccount(name: string): Promise { + const account: Account[] = await db.knex.select('id').from(`_nango_accounts`).where({ name }); + + if (account == null || account.length == 0 || !account[0]) { + const newAccount: Account[] = await db.knex.insert({ name, created_at: new Date() }).into(`_nango_accounts`).returning('*'); + + if (!newAccount || newAccount.length == 0 || !newAccount[0]) { + throw new Error('Failed to create account'); + } + await environmentService.createDefaultEnvironments(newAccount[0]['id']); + + return newAccount[0]; + } + + return account[0]; + } + + /** + * Create Account + * @desc create a new account and assign to the default environmenets + */ + async createAccount(name: string): Promise { + const result: void | Pick = await db.knex.from(`_nango_accounts`).insert({ name: name }, ['id']); + + if (Array.isArray(result) && result.length === 1 && result[0] != null && 'id' in result[0]) { + await environmentService.createDefaultEnvironments(result[0]['id']); + + return result[0]; + } + + return null; + } } export default new AccountService(); diff --git a/packages/shared/lib/services/environment.service.ts b/packages/shared/lib/services/environment.service.ts index ffea46a797..116830a013 100644 --- a/packages/shared/lib/services/environment.service.ts +++ b/packages/shared/lib/services/environment.service.ts @@ -295,22 +295,10 @@ class EnvironmentService { return null; } - /** - * Create Account - * @desc create a new account and assign to the default environmenets - */ - async createAccount(name: string): Promise { - const result: void | Pick = await db.knex.from(`_nango_accounts`).insert({ name: name }, ['id']); - - if (Array.isArray(result) && result.length === 1 && result[0] != null && 'id' in result[0]) { - for (const defaultEnvironment of defaultEnvironments) { - await this.createEnvironment(result[0]['id'], defaultEnvironment); - } - - return result[0]; + async createDefaultEnvironments(accountId: number): Promise { + for (const environment of defaultEnvironments) { + await this.createEnvironment(accountId, environment); } - - return null; } async getEnvironmentName(id: number): Promise { diff --git a/packages/shared/lib/utils/error.ts b/packages/shared/lib/utils/error.ts index a17f5e4962..894fa3729a 100644 --- a/packages/shared/lib/utils/error.ts +++ b/packages/shared/lib/utils/error.ts @@ -532,6 +532,26 @@ export class NangoError extends Error { this.message = `The parameter ${this.payload['incorrect']} is invalid. Did you mean ${this.payload['correct']}?`; break; + case 'invalid_provider': + this.status = 400; + this.message = `The provider is not allowed. Please try again with a valid provider`; + break; + + case 'workos_not_configured': + this.status = 400; + this.message = `WorkOS is not configured. Please reach out to support to obtain valid WorkOS credentials.`; + break; + + case 'missing_managed_login_callback_code': + this.status = 400; + this.message = `Missing param 'code' for the managed login callback.`; + break; + + case 'missing_name_for_account_creation': + this.status = 400; + this.message = `Missing an account name for account login/signup.`; + break; + default: this.status = 500; this.type = 'unhandled_' + type; diff --git a/packages/shared/lib/utils/utils.ts b/packages/shared/lib/utils/utils.ts index 0a9ba74d81..5aa13d5fe2 100644 --- a/packages/shared/lib/utils/utils.ts +++ b/packages/shared/lib/utils/utils.ts @@ -34,6 +34,9 @@ export enum NodeEnv { Prod = 'production' } +export const AUTH_ENABLED = isCloud() || isEnterprise(); +export const MANAGED_AUTH_ENABLED = isCloud() || isLocal(); + export const JAVASCRIPT_AND_TYPESCRIPT_TYPES = { primitives: ['string', 'number', 'boolean', 'bigint', 'symbol', 'undefined', 'null'], aliases: ['String', 'Number', 'Boolean', 'BigInt', 'Symbol', 'Undefined', 'Null', 'bool', 'char', 'integer', 'int', 'date', 'object'], diff --git a/packages/webapp/src/components/ui/button/Auth/Google.tsx b/packages/webapp/src/components/ui/button/Auth/Google.tsx new file mode 100644 index 0000000000..7a87f40576 --- /dev/null +++ b/packages/webapp/src/components/ui/button/Auth/Google.tsx @@ -0,0 +1,84 @@ +interface Props { + text: string; + setServerErrorMessage: (message: string) => void; + invitedAccountID?: number; + token?: string; +} + +interface PostBody { + method: string; + headers: { + 'Content-Type': string; + }; + body?: string; +} + +export default function GoogleButton({ text, setServerErrorMessage, invitedAccountID, token }: Props) { + const googleLogin = async () => { + const postBody: PostBody = { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }; + + if (invitedAccountID && token) { + postBody.body = JSON.stringify({ + accountId: invitedAccountID + }); + } + const endpoint = token ? `/api/v1/managed/signup/${token}?provider=GoogleOAuth` : '/api/v1/managed/signup?provider=GoogleOAuth'; + + const res = await fetch(endpoint, postBody); + + if (res?.status === 200) { + const data = await res.json(); + const { url } = data; + window.location = url; + } else if (res != null) { + const errorMessage = (await res.json()).error; + setServerErrorMessage(errorMessage); + } + }; + return ( + + ); +} diff --git a/packages/webapp/src/index.css b/packages/webapp/src/index.css index f13a54f3b5..214884e0d1 100644 --- a/packages/webapp/src/index.css +++ b/packages/webapp/src/index.css @@ -3,7 +3,7 @@ body, #root { width: 100%; height: 100%; - background-color: #0e1014; + background-color: #010101; } @import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@300;400;500;600;700&display=swap'); diff --git a/packages/webapp/src/layout/DefaultLayout.tsx b/packages/webapp/src/layout/DefaultLayout.tsx index 3e96f71774..da2bf7cc68 100644 --- a/packages/webapp/src/layout/DefaultLayout.tsx +++ b/packages/webapp/src/layout/DefaultLayout.tsx @@ -4,11 +4,13 @@ interface DefaultLayoutI { export default function DefaultLayout({ children }: DefaultLayoutI) { return ( -
-
- Your Company +
+
+
+ Nango +
+ {children}
- {children}
); } diff --git a/packages/webapp/src/pages/ForgotPassword.tsx b/packages/webapp/src/pages/ForgotPassword.tsx index ef20f724d5..168e8363ee 100644 --- a/packages/webapp/src/pages/ForgotPassword.tsx +++ b/packages/webapp/src/pages/ForgotPassword.tsx @@ -30,23 +30,21 @@ export default function Signin() { return ( <> -
-
-

Request Password Reset

+
+
+

Request password reset

-
@@ -55,9 +53,9 @@ export default function Signin() {
{serverErrorMessage &&

{serverErrorMessage}

}
diff --git a/packages/webapp/src/pages/InviteSignup.tsx b/packages/webapp/src/pages/InviteSignup.tsx index f0182f8c8c..ace9dd7285 100644 --- a/packages/webapp/src/pages/InviteSignup.tsx +++ b/packages/webapp/src/pages/InviteSignup.tsx @@ -1,10 +1,12 @@ import { useState, useEffect } from 'react'; -import { Link, useNavigate, useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { useInviteSignupAPI, useSignupAPI } from '../utils/api'; -import { isEnterprise } from '../utils/utils'; -import { useSignin, User } from '../utils/user'; +import { MANAGED_AUTH_ENABLED, isEnterprise } from '../utils/utils'; +import { useSignin } from '../utils/user'; +import type { User } from '../utils/user'; import DefaultLayout from '../layout/DefaultLayout'; +import GoogleButton from '../components/ui/button/Auth/Google'; export default function InviteSignup() { const [serverErrorMessage, setServerErrorMessage] = useState(''); @@ -66,14 +68,11 @@ export default function InviteSignup() { return ( <> -
-
+
+

Sign up

-
-
-
@@ -128,38 +124,42 @@ export default function InviteSignup() {
{serverErrorMessage &&

{serverErrorMessage}

}
+ {MANAGED_AUTH_ENABLED && ( + <> +
+
+ or continue with +
+
+ + + )}
-
-
-

Already have an account?

- - Sign in - -
-
-
-
-

By signing up, you agree to our

- - Terms of Service - -

and

- - Privacy Policy - -

.

+
+
+

+ By signing in, you agree to our + + Terms of Service + + and + + Privacy Policy + + . +

diff --git a/packages/webapp/src/pages/ResetPassword.tsx b/packages/webapp/src/pages/ResetPassword.tsx index 769369d38b..844e979dc9 100644 --- a/packages/webapp/src/pages/ResetPassword.tsx +++ b/packages/webapp/src/pages/ResetPassword.tsx @@ -39,24 +39,18 @@ export default function Signin() { return ( <> -
-
-

Reset Password

+
+
+

Reset password

-
-
- -
-
@@ -66,7 +60,7 @@ export default function Signin() {
diff --git a/packages/webapp/src/pages/Signin.tsx b/packages/webapp/src/pages/Signin.tsx index 3b21ac2047..b41b343e81 100644 --- a/packages/webapp/src/pages/Signin.tsx +++ b/packages/webapp/src/pages/Signin.tsx @@ -2,8 +2,11 @@ import { useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { useSigninAPI } from '../utils/api'; -import { useSignin, User } from '../utils/user'; +import { useSignin } from '../utils/user'; +import type { User } from '../utils/user'; +import { MANAGED_AUTH_ENABLED } from '../utils/utils'; import DefaultLayout from '../layout/DefaultLayout'; +import GoogleButton from '../components/ui/button/Auth/Google'; export default function Signin() { const [serverErrorMessage, setServerErrorMessage] = useState(''); @@ -35,47 +38,41 @@ export default function Signin() { return ( <> -
-
-

Sign in

- +
+
+

Log in to Nango

+
-
-
-
- -
-
- + -
+
@@ -83,38 +80,47 @@ export default function Signin() {
{serverErrorMessage &&

{serverErrorMessage}

}
+ + {MANAGED_AUTH_ENABLED && ( + <> +
+
+ or continue with +
+
+ + + + )}
-
-
-

Need an account?

- - Sign up +
+
+

Don't have an account?

+ + Sign up.
-
-
-

By signing up, you agree to our

- - Terms of Service - -

and

- - Privacy Policy - -

.

+
+
+

+ By signing in, you agree to our + + Terms of Service + + and + + Privacy Policy + + . +

diff --git a/packages/webapp/src/pages/Signup.tsx b/packages/webapp/src/pages/Signup.tsx index ef3a3a3bf0..6e04cc8554 100644 --- a/packages/webapp/src/pages/Signup.tsx +++ b/packages/webapp/src/pages/Signup.tsx @@ -2,9 +2,12 @@ import { useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { useAnalyticsTrack } from '../utils/analytics'; +import { MANAGED_AUTH_ENABLED } from '../utils/utils'; import { useSignupAPI } from '../utils/api'; -import { useSignin, User } from '../utils/user'; +import type { User } from '../utils/user'; +import { useSignin } from '../utils/user'; import DefaultLayout from '../layout/DefaultLayout'; +import GoogleButton from '../components/ui/button/Auth/Google'; export default function Signup() { const [serverErrorMessage, setServerErrorMessage] = useState(''); @@ -45,14 +48,11 @@ export default function Signup() { return ( <> -
-
-

Sign up

+
+
+

Sign up to Nango

-
-
-
@@ -104,38 +101,45 @@ export default function Signup() {
{serverErrorMessage &&

{serverErrorMessage}

}
+ {MANAGED_AUTH_ENABLED && ( + <> +
+
+ or continue with +
+
+ + + )}
-
-
-

Already have an account?

- - Sign in +
+
+

Already have an account?

+ + Sign in.
-
-
-

By signing up, you agree to our

- - Terms of Service - -

and

- - Privacy Policy - -

.

+
+
+

+ By signing in, you agree to our + + Terms of Service + + and + + Privacy Policy + + . +

diff --git a/packages/webapp/src/utils/utils.tsx b/packages/webapp/src/utils/utils.tsx index 3f6ba66833..d7afdc9aa7 100644 --- a/packages/webapp/src/utils/utils.tsx +++ b/packages/webapp/src/utils/utils.tsx @@ -10,6 +10,7 @@ export const prodUrl: string = 'https://api.nango.dev'; export const syncDocs = 'https://docs.nango.dev/integrate/guides/sync-data-from-an-api'; export const AUTH_ENABLED = isCloud() || isEnterprise() || isLocal(); +export const MANAGED_AUTH_ENABLED = isCloud() || isLocal(); export function isHosted() { return process.env.REACT_APP_ENV === 'hosted'; diff --git a/packages/webapp/tailwind.config.js b/packages/webapp/tailwind.config.js index 32f6b2e7de..0573c44dab 100644 --- a/packages/webapp/tailwind.config.js +++ b/packages/webapp/tailwind.config.js @@ -10,18 +10,25 @@ module.exports = { colors: { 'bg-black': '#0E1014', 'pure-black': '#05050a', - 'bg-dark-gray': '#181B20', 'active-gray': '#161720', 'hover-gray': '#1D1F28', 'text-light-gray': '#A9A9A9', 'off-black': '#05050a', - 'text-dark-gray': '#5F5F5F', 'bg-cta-green': '#75E270', 'text-cta-green1': '#224421', 'border-gray': '#333333', 'border-blue': '#1489DF', 'text-blue': '#1489DF', 'text-light-blue': '#76C5FF', + 'dark-0': '#FFFFFF', + 'dark-100': '#F4F4F5', + 'dark-200': '#E4E4E7', + 'dark-300': '#D4D4D8', + 'dark-400': '#A1A1AA', + 'dark-500': '#71717A', + 'dark-600': '#27272A', + 'dark-700': '#18181B', + 'dark-800': '#09090B', 'bg-dark-blue': '#182633', white: '#FFFFFF' },