Skip to content

Commit

Permalink
feat(ee-auth): Use clerk in the enterprise version (#5755)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChmaraX authored Jul 10, 2024
1 parent eae30ad commit d03da1b
Show file tree
Hide file tree
Showing 128 changed files with 4,786 additions and 2,089 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/on-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,11 @@ jobs:
strategy:
# The order is important for ee to be first, otherwise outputs not work correctly
matrix:
name: ['novu/api-ee', 'novu/api']
name: ['novu/api-ee', 'novu/api', 'novu/api-ee-clerk']
uses: ./.github/workflows/reusable-api-e2e.yml
with:
ee: ${{ contains (matrix.name,'-ee') }}
ee-clerk: ${{ contains (matrix.name,'-ee-clerk') }}
test-e2e-affected: ${{ contains(fromJson(needs.get-affected.outputs.test-e2e), '@novu/api') }}
test-e2e-ee-affected: ${{ contains(fromJson(needs.get-affected.outputs.test-e2e-ee), '@novu/api') }}
job-name: ${{ matrix.name }}
Expand Down
28 changes: 26 additions & 2 deletions .github/workflows/reusable-api-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ on:
required: false
default: false
type: boolean
ee-clerk:
description: 'use the clerk ee version of api'
required: false
default: false
type: boolean
job-name:
description: 'job name [options: novu/api-ee, novu/api]'
description: 'job name [options: novu/api-ee, novu/api, novu/api-ee-clerk]'
required: true
type: string

Expand Down Expand Up @@ -97,7 +102,7 @@ jobs:
cd apps/api && pnpm test:e2e
- name: Run E2E EE tests
if: ${{ needs.check_submodule_token.outputs.has_token == 'true' && inputs.ee }}
if: ${{ needs.check_submodule_token.outputs.has_token == 'true' && inputs.ee && !inputs.ee-clerk }}
env:
LAUNCH_DARKLY_SDK_KEY: ${{ secrets.LAUNCH_DARKLY_SDK_KEY }}
GOOGLE_OAUTH_CLIENT_ID: ${{ secrets.GOOGLE_OAUTH_CLIENT_ID }}
Expand All @@ -106,6 +111,25 @@ jobs:
run: |
cd apps/api && pnpm test:e2e:ee
- name: Run E2E EE Clerk tests
if: ${{ needs.check_submodule_token.outputs.has_token == 'true' && inputs.ee && inputs.ee-clerk }}
env:
LAUNCH_DARKLY_SDK_KEY: ${{ secrets.LAUNCH_DARKLY_SDK_KEY }}
GOOGLE_OAUTH_CLIENT_ID: ${{ secrets.GOOGLE_OAUTH_CLIENT_ID }}
GOOGLE_OAUTH_CLIENT_SECRET: ${{ secrets.GOOGLE_OAUTH_CLIENT_SECRET }}
CI_EE_TEST: true
CLERK_ENABLED: true
CLERK_ISSUER_URL: ${{ vars.CLERK_ISSUER_URL }}
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
CLERK_PRIVATE_KEY_BASE64: ${{ secrets.CLERK_PRIVATE_KEY_BASE64 }}
CLERK_PEM_PUBLIC_KEY_BASE64: ${{ secrets.CLERK_PEM_PUBLIC_KEY_BASE64 }}
WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }}
CLERK_LONG_LIVED_TOKEN: ${{ secrets.CLERK_LONG_LIVED_TOKEN }}
run: |
export CLERK_PEM_PUBLIC_KEY=$(echo $CLERK_PEM_PUBLIC_KEY_BASE64 | base64 -d)
export CLERK_PRIVATE_KEY=$(echo $CLERK_PRIVATE_KEY_BASE64 | base64 -d)
cd apps/api && pnpm test:e2e:clerk
- name: Kill port for worker 1342 for unit tests
run: sudo kill -9 $(sudo lsof -t -i:1342)

Expand Down
2 changes: 1 addition & 1 deletion .source
15 changes: 15 additions & 0 deletions apps/api/e2e/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,23 @@ import sinon from 'sinon';
import chai from 'chai';

import { bootstrap } from '../src/bootstrap';
import { isClerkEnabled } from '@novu/shared';

const dalService = new DalService();

async function seedClerkMongo() {
if (isClerkEnabled()) {
const clerkClientMock = require('@novu/ee-auth')?.ClerkClientMock;

if (clerkClientMock) {
const clerkClient = new clerkClientMock();
await clerkClient.seedDatabase();
} else {
throw new Error('ClerkClientMock not found');
}
}
}

before(async () => {
/**
* disable truncating for better error messages - https://www.chaijs.com/guide/styles/#configtruncatethreshold
Expand All @@ -15,6 +29,7 @@ before(async () => {
await testServer.create(await bootstrap());

await dalService.connect(process.env.MONGO_URL);
await seedClerkMongo();
});

after(async () => {
Expand Down
11 changes: 8 additions & 3 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@
"lint:fix": "pnpm lint -- --fix",
"lint:openapi": "spectral lint http://127.0.0.1:${PORT:-3000}/openapi.yaml",
"test": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' NODE_ENV=test E2E_RUNNER=true mocha --require ts-node/register --exit --file e2e/setup.ts src/**/**/*.spec.ts",
"test:e2e": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' NODE_ENV=test E2E_RUNNER=true mocha --require ts-node/register --exit --file e2e/setup.ts src/**/*.e2e.ts",
"test:e2e": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' NODE_ENV=test E2E_RUNNER=true mocha --require ts-node/register --exit --file e2e/setup.ts src/**/*.e2e.ts ",
"test:e2e:ee": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' NODE_ENV=test E2E_RUNNER=true CI_EE_TEST=true mocha --require ts-node/register --exit --file e2e/setup.ts src/**/*.e2e-ee.ts",
"migration": "cross-env NODE_ENV=local MIGRATION=true ts-node --transpileOnly"
"test:e2e:clerk": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' NODE_ENV=test E2E_RUNNER=true CI_EE_TEST=true CLERK_ENABLED=true mocha --grep @skip-in-ee --invert --require ts-node/register --exit --file e2e/setup.ts 'src/**/*.e2e{,-ee}.ts'",
"migration": "cross-env NODE_ENV=local MIGRATION=true ts-node --transpileOnly",
"link:submodules": "pnpm link ../../enterprise/packages/auth && pnpm link ../../enterprise/packages/translation && pnpm link ../../enterprise/packages/billing",
"admin:remove-user-account": "cross-env NODE_ENV=local MIGRATION=true ts-node --transpileOnly ./admin/remove-user-account.ts",
"admin:remove-organization": "cross-env NODE_ENV=local MIGRATION=true ts-node --transpileOnly ./admin/remove-organization.ts"
},
"dependencies": {
"@godaddy/terminus": "^4.12.1",
Expand Down Expand Up @@ -53,7 +57,6 @@
"bcrypt": "^5.0.0",
"body-parser": "^1.20.0",
"bull": "^4.2.1",
"nimma": "^0.6.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"compression": "^1.7.4",
Expand All @@ -71,8 +74,10 @@
"nanoid": "^3.1.20",
"nest-raven": "^10.0.0",
"newrelic": "^9.15.0",
"nimma": "^0.6.0",
"passport": "0.6.0",
"passport-github2": "^0.1.12",
"passport-google-oauth": "^2.0.0",
"passport-headerapikey": "^1.2.2",
"passport-jwt": "^4.0.0",
"passport-oauth2": "^1.6.1",
Expand Down
3 changes: 3 additions & 0 deletions apps/api/src/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,6 @@ PR_PREVIEW_ROOT_URL=dev-web-novu.netlify.app

HUBSPOT_INVITE_NUDGE_EMAIL_USER_LIST_ID=
HUBSPOT_PRIVATE_APP_ACCESS_TOKEN=

CLERK_ISSUER_URL=
WEBHOOK_SECRET=
3 changes: 3 additions & 0 deletions apps/api/src/.env.production
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,6 @@ API_RATE_LIMIT_MAXIMUM_UNLIMITED_GLOBAL=

HUBSPOT_INVITE_NUDGE_EMAIL_USER_LIST_ID=
HUBSPOT_PRIVATE_APP_ACCESS_TOKEN=

CLERK_ISSUER_URL=
WEBHOOK_SECRET=
6 changes: 6 additions & 0 deletions apps/api/src/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,10 @@ IS_USE_MERGED_DIGEST_ID_ENABLED=true
HUBSPOT_INVITE_NUDGE_EMAIL_USER_LIST_ID=
HUBSPOT_PRIVATE_APP_ACCESS_TOKEN=

CLERK_ISSUER_URL=
CLERK_LONG_LIVED_TOKEN=

CLERK_PRIVATE_KEY=
CLERK_PEM_PUBLIC_KEY=

TUNNEL_BASE_ADDRESS=example.com
3 changes: 3 additions & 0 deletions apps/api/src/.example.env
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,7 @@ API_RATE_LIMIT_MAXIMUM_UNLIMITED_GLOBAL=
HUBSPOT_INVITE_NUDGE_EMAIL_USER_LIST_ID=
HUBSPOT_PRIVATE_APP_ACCESS_TOKEN=

CLERK_ISSUER_URL=
CLERK_LONG_LIVED_TOKEN=

TUNNEL_BASE_ADDRESS=
28 changes: 21 additions & 7 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,17 @@ import { RateLimitingModule } from './app/rate-limiting/rate-limiting.module';
import { ProductFeatureInterceptor } from './app/shared/interceptors/product-feature.interceptor';
import { AnalyticsModule } from './app/analytics/analytics.module';
import { InboxModule } from './app/inbox/inbox.module';
import { isClerkEnabled } from '@novu/shared';
import { LegacyEEAuthModule } from './app/auth/legacy-ee-auth/auth.module';

const enterpriseImports = (): Array<Type | DynamicModule | Promise<DynamicModule> | ForwardReference> => {
const modules: Array<Type | DynamicModule | Promise<DynamicModule> | ForwardReference> = [];
if (process.env.NOVU_ENTERPRISE === 'true' || process.env.CI_EE_TEST === 'true') {
if (require('@novu/ee-auth')?.EEAuthModule) {
modules.push(require('@novu/ee-auth')?.EEAuthModule);
// TODO: remove after Clerk replaces legacy EE auth
if (process.env.CLERK_ENABLED !== 'true') {
modules.push(LegacyEEAuthModule);
}

if (require('@novu/ee-bridge-api')?.BridgeModule) {
modules.push(require('@novu/ee-bridge-api')?.BridgeModule);
}
Expand All @@ -57,6 +61,12 @@ const enterpriseImports = (): Array<Type | DynamicModule | Promise<DynamicModule
}
}

if (isClerkEnabled()) {
if (require('@novu/ee-auth')?.EEAuthModule) {
modules.push(require('@novu/ee-auth')?.EEAuthModule);
}
}

return modules;
};

Expand All @@ -72,11 +82,9 @@ const enterpriseQuotaThrottlerInterceptor =
: [];

const baseModules: Array<Type | DynamicModule | Promise<DynamicModule> | ForwardReference> = [
AuthModule,
InboundParseModule,
OrganizationModule,
SharedModule,
UserModule,
AuthModule,
HealthModule,
EnvironmentsModule,
ExecutionDetailsModule,
Expand All @@ -85,10 +93,10 @@ const baseModules: Array<Type | DynamicModule | Promise<DynamicModule> | Forward
WidgetsModule,
InboxModule,
NotificationModule,
StorageModule,
NotificationGroupsModule,
InvitesModule,
ContentTemplatesModule,
OrganizationModule,
UserModule,
IntegrationModule,
ChangeModule,
SubscribersModule,
Expand All @@ -101,12 +109,18 @@ const baseModules: Array<Type | DynamicModule | Promise<DynamicModule> | Forward
TenantModule,
WorkflowOverridesModule,
RateLimitingModule,
WidgetsModule,
ProfilingModule.register(packageJson.name),
TracingModule.register(packageJson.name, packageJson.version),
];

const enterpriseModules = enterpriseImports();

if (!isClerkEnabled()) {
const communityModules = [StorageModule, InvitesModule];
baseModules.push(...communityModules);
}

const modules = baseModules.concat(enterpriseModules);

const providers: Provider[] = [
Expand Down
75 changes: 17 additions & 58 deletions apps/api/src/app/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,24 @@
import { MiddlewareConsumer, Module, NestModule, Provider, RequestMethod } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
const passport = require('passport');
import { Global, MiddlewareConsumer, Module, ModuleMetadata } from '@nestjs/common';
import { isClerkEnabled } from '@novu/shared';
import { getCommunityAuthModuleConfig, configure as configureCommunity } from './community.auth.module.config';
import { getEEModuleConfig, configure as configureEE } from './ee.auth.module.config';

import { AuthProviderEnum, PassportStrategyEnum } from '@novu/shared';
import { AuthService } from '@novu/application-generic';

import { RolesGuard } from './framework/roles.guard';
import { JwtStrategy } from './services/passport/jwt.strategy';
import { AuthController } from './auth.controller';
import { UserModule } from '../user/user.module';
import { USE_CASES } from './usecases';
import { SharedModule } from '../shared/shared.module';
import { GitHubStrategy } from './services/passport/github.strategy';
import { OrganizationModule } from '../organization/organization.module';
import { EnvironmentsModule } from '../environments/environments.module';
import { JwtSubscriberStrategy } from './services/passport/subscriber-jwt.strategy';
import { UserAuthGuard } from './framework/user.auth.guard';
import { RootEnvironmentGuard } from './framework/root-environment-guard.service';
import { ApiKeyStrategy } from './services/passport/apikey.strategy';

const AUTH_STRATEGIES: Provider[] = [JwtStrategy, ApiKeyStrategy, JwtSubscriberStrategy];

if (process.env.GITHUB_OAUTH_CLIENT_ID) {
AUTH_STRATEGIES.push(GitHubStrategy);
function getModuleConfig(): ModuleMetadata {
if (isClerkEnabled()) {
return getEEModuleConfig();
} else {
return getCommunityAuthModuleConfig();
}
}

@Module({
imports: [
OrganizationModule,
SharedModule,
UserModule,
PassportModule.register({
defaultStrategy: PassportStrategyEnum.JWT,
}),
JwtModule.register({
secretOrKeyProvider: () => process.env.JWT_SECRET as string,
signOptions: {
expiresIn: 360000,
},
}),
EnvironmentsModule,
],
controllers: [AuthController],
providers: [UserAuthGuard, ...USE_CASES, ...AUTH_STRATEGIES, AuthService, RolesGuard, RootEnvironmentGuard],
exports: [RolesGuard, RootEnvironmentGuard, AuthService, ...USE_CASES, UserAuthGuard],
})
export class AuthModule implements NestModule {
@Global()
@Module(getModuleConfig())
export class AuthModule {
public configure(consumer: MiddlewareConsumer) {
if (process.env.GITHUB_OAUTH_CLIENT_ID) {
consumer
.apply(
passport.authenticate(AuthProviderEnum.GITHUB, {
session: false,
scope: ['user:email'],
})
)
.forRoutes({
path: '/auth/github',
method: RequestMethod.GET,
});
if (isClerkEnabled()) {
configureEE(consumer);
} else {
configureCommunity(consumer);
}
}
}
Loading

0 comments on commit d03da1b

Please sign in to comment.