-
Notifications
You must be signed in to change notification settings - Fork 4k
feat(dashboard,api-service): add self-hosted-auth #7755
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
73 commits
Select commit
Hold shift + click to select a range
7d1d507
feat(dashboard,api-service): add self-hosted-auth
djabarovgeorge 5806874
fix:
scopsy 2b694ce
fix: opted in status
scopsy 92a8620
Merge branch 'next' into self-hosted-auth
djabarovgeorge 03c53a4
refactor(api-service,dashaboard): integrate SystemOrganizationService…
djabarovgeorge 15a96d4
refactor(api-service): rename variable 'admins' to 'users' for clarit…
djabarovgeorge 4b699ab
refactor(dashboard): update self-hosted configuration and remove unus…
djabarovgeorge f085a27
refactor(dashboard): export OrganizationSwitcher from self-hosted uti…
djabarovgeorge e48c021
refactor(dashboard): export UserButton from self-hosted utils and cle…
djabarovgeorge 0fe32b2
refactor(dashboard, api): enhance error handling in content-templates…
djabarovgeorge 9716462
refactor(dashboard): implement navigation for self-hosted environment…
djabarovgeorge d6df4e5
refactor(dashboard): update workflow row and general settings for sel…
djabarovgeorge d9685f5
refactor(api, dashboard): enhance organization creation with API serv…
djabarovgeorge 19ca2db
refactor(dashboard): update activity filters to use API service level…
djabarovgeorge c7b3150
Merge branch 'next' into self-hosted-auth
djabarovgeorge 1652325
fix(dashboard): validate Clerk Publishable Key only when not self-hosted
djabarovgeorge 0d91285
refactor(dashboard): streamline organization switcher component and e…
djabarovgeorge 4030708
fix(dashboard): improve tooltip content and enhance avatar animation …
djabarovgeorge c16270d
feat(dashboard): add UserAvatar component and integrate it into UserB…
djabarovgeorge 6fdbc74
feat(dashboard): enhance workflow row with tooltip for unsupported wo…
djabarovgeorge 1266c9e
fix(dashboard): update NovuBrandingSwitch to handle self-hosted scena…
djabarovgeorge 77f3328
fix(dashboard): update FreeTierState and NovuBrandingSwitch for self-…
djabarovgeorge da6f1fa
refactor(dashboard): standardize SVG attributes and enhance SideNavig…
djabarovgeorge a1501f0
fix(dashboard): update FreeTierState component to enhance messaging a…
djabarovgeorge 82bb22e
fix(dashboard): handle undefined subscription in UsageCard to prevent…
djabarovgeorge 8c1da05
refactor(dashboard): remove unused imports in restricted-switch and o…
djabarovgeorge 2ac18d1
refactor(root): remove unused imports and dependencies for cleaner co…
djabarovgeorge 2915109
feat(docker): add Dockerfile and update package.json for Docker build…
merrcury 802c72a
Merge branch 'self-hosted-auth' of github.com:novuhq/novu into self-h…
merrcury a7dd8e5
Merge branch 'self-hosted-auth' of github.com:novuhq/novu into self-h…
djabarovgeorge b058e0d
refactor(code-snippets): enhance server URL handling for self-hosted …
djabarovgeorge d7bb330
refactor(auth): rename SystemOrganizationService to CommunityEditionS…
djabarovgeorge efefe2d
refactor(auth): typo
djabarovgeorge fe533c5
fix(auth): update COMMUNITY_EDITION_USER_EMAIL to use example domain …
djabarovgeorge 0d00312
refactor(dashboard): replace RestrictedSwitch with Popover and LinkBu…
djabarovgeorge aa4d70c
refactor(dashboard): update NovuBrandingSwitch to include self-hosted…
djabarovgeorge b604655
refactor(dashboard): enhance organization and user buttons with self-…
djabarovgeorge 3c289e5
Merge branch 'next' into self-hosted-auth
djabarovgeorge 9a70325
fix(dashboard): after next merge
djabarovgeorge 4ec6bbf
Merge branch 'next' into self-hosted-auth
djabarovgeorge a555ff9
refactor(organization-switcher): update button styles and text for im…
djabarovgeorge 80c1d57
refactor(dashboard): implement self-hosted redirection for welcome an…
djabarovgeorge cb8a9c5
refactor(header-navigation): update customer support button to handle…
djabarovgeorge 60517af
refactor(user-button): enhance dropdown menu with new self-hosted upg…
djabarovgeorge be8f315
refactor(workflow-row): update self-hosted upgrade link to use dynami…
djabarovgeorge 1540978
refactor(auth): replace CommunityEditionService with SelfHostUsecase …
djabarovgeorge 145cf23
refactor(auth): update SelfHostUsecase to use CommunityOrganizationRe…
djabarovgeorge 2e31aed
refactor(api): simplify isMember check by passing organizationId dire…
djabarovgeorge d95ad6f
refactor(root): after pr comments
djabarovgeorge 3aa7b4b
refactor(user-button): remove user email display from dropdown menu f…
djabarovgeorge 4899262
feat(dashboard): update after pr comments
djabarovgeorge ba2cc2f
Merge branch 'next' into self-hosted-auth
djabarovgeorge 623f319
feat(root, dashboard): new dockerfile for the dashboard service (#8221)
merrcury eef014d
fix(dashboard): remove email as user name
djabarovgeorge 2299dce
Merge branch 'self-hosted-auth' of github.com:novuhq/novu into self-h…
djabarovgeorge 198c861
feat(api): implement self-host secret guard and update auth controlle…
djabarovgeorge f4c539f
Merge branch 'next' into self-hosted-auth
djabarovgeorge 14a245e
refactor(dashboard): modularize demo workflow initialization and clea…
djabarovgeorge 8e10051
Merge branch 'next' into self-hosted-auth
djabarovgeorge 4a22cb4
fix(api): adjust max snooze duration logic based on NOVU_ENTERPRISE e…
djabarovgeorge 92f60d7
refactor(auth): rename self-host secret key to self-host token and up…
djabarovgeorge 6e262f5
chore(docker): add SELF_HOSTED_TOKEN and VITE_SELF_HOSTED_TOKEN to do…
djabarovgeorge e137711
fix(auth): update error messages in SelfHostSecretGuard for clarity
djabarovgeorge 6b6f1f3
fix(dashboard): add bell for test page
djabarovgeorge 690730a
Merge branch 'next' into self-hosted-auth
djabarovgeorge a6eb8b4
Merge branch 'next' into self-hosted-auth
djabarovgeorge c00e159
fix(dashboard): remove TypeScript error suppression in maily-config
djabarovgeorge e579651
fix(api-service): ci run
djabarovgeorge 1482cc9
fix(api): update test command and simplify maxSnoozeDurationHours logic
djabarovgeorge 08b78dc
Merge branch 'next' into self-hosted-auth
djabarovgeorge 4a9a17e
refactor(api): replace hardcoded COMMUNITY_EDITION_NAME with helper f…
djabarovgeorge fa7a6d6
fix(api-service): ci test
djabarovgeorge f0439a2
fix(api-service): ci tests
djabarovgeorge File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -733,6 +733,8 @@ | |
"oklch", | ||
"Duplicable", | ||
"Radek", | ||
"automators", | ||
"VITE", | ||
"snooze", | ||
"unsnooze", | ||
"snoozed", | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; | ||
import { HttpRequestHeaderKeysEnum } from '@novu/application-generic'; | ||
|
||
@Injectable() | ||
export class SelfHostSecretGuard implements CanActivate { | ||
canActivate(context: ExecutionContext): boolean { | ||
const secretKey = process.env.SELF_HOSTED_TOKEN; | ||
if (!secretKey) return true; | ||
|
||
const request = context.switchToHttp().getRequest(); | ||
const headerKey = request.headers[HttpRequestHeaderKeysEnum.NOVU_SELF_HOSTED_TOKEN.toLowerCase()]; | ||
|
||
if (!headerKey) { | ||
throw new UnauthorizedException('Missing self-hosted token'); | ||
} | ||
|
||
if (headerKey !== secretKey) { | ||
throw new UnauthorizedException( | ||
'Invalid self-hosted token, please validate that the API and Dashboard have the same token' | ||
); | ||
} | ||
|
||
return true; | ||
} | ||
} |
131 changes: 131 additions & 0 deletions
131
apps/api/src/app/auth/services/system-organization.service.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; | ||
import { CommunityOrganizationRepository, UserEntity, UserRepository } from '@novu/dal'; | ||
import { PinoLogger } from '@novu/application-generic'; | ||
import { ApiServiceLevelEnum } from '@novu/shared'; | ||
import { CreateOrganization } from '../../organization/usecases/create-organization/create-organization.usecase'; | ||
import { CreateOrganizationCommand } from '../../organization/usecases/create-organization/create-organization.command'; | ||
import { UserRegister } from '../usecases/register/user-register.usecase'; | ||
import { UserRegisterCommand } from '../usecases/register/user-register.command'; | ||
import { COMMUNITY_EDITION_NAME, getSelfHostedFindQuery } from '../../shared/helpers/self-hosted'; | ||
|
||
@Injectable() | ||
export class CommunityEditionService implements OnModuleInit { | ||
private readonly E11000_DUPLICATE_KEY_ERROR_CODE = 'E11000'; | ||
private readonly COMMUNITY_EDITION_USER_EMAIL = '[email protected]'; | ||
|
||
constructor( | ||
@Inject('ORGANIZATION_REPOSITORY') | ||
private organizationRepository: CommunityOrganizationRepository, | ||
private createOrganizationUsecase: CreateOrganization, | ||
private userRegisterUsecase: UserRegister, | ||
private userRepository: UserRepository, | ||
private logger: PinoLogger | ||
) {} | ||
|
||
async onModuleInit() { | ||
try { | ||
await this.initializeCommunityEditionOrganization(); | ||
} catch (error) { | ||
this.logger.error({ err: error }, 'Failed to initialize Self-Hosted Community Edition Setup during module init'); | ||
throw error; | ||
} | ||
} | ||
|
||
private async initializeCommunityEditionOrganization(): Promise<void> { | ||
await this.organizationRepository.withTransaction(async () => { | ||
let communityEditionOrg = await this.organizationRepository.findOne(getSelfHostedFindQuery()); | ||
|
||
if (communityEditionOrg) { | ||
this.logger.info( | ||
'Self Hosted is already initialized, skipping Community Edition creation. ' + | ||
`Organization already exists with ID: ${communityEditionOrg._id}` | ||
); | ||
|
||
return; | ||
} | ||
|
||
this.logger.info('Community Edition not found, creating it'); | ||
|
||
try { | ||
let user = await this.userRepository.findByEmail(this.COMMUNITY_EDITION_USER_EMAIL); | ||
if (!user) { | ||
user = await this.createCommunityEditionUser(); | ||
} | ||
|
||
this.logger.debug(`Retrieved Community User with ID: ${user._id}`); | ||
|
||
const organization = await this.createOrganizationUsecase.execute( | ||
CreateOrganizationCommand.create({ | ||
userId: user._id, | ||
name: COMMUNITY_EDITION_NAME, | ||
apiServiceLevel: ApiServiceLevelEnum.UNLIMITED, | ||
}) | ||
); | ||
|
||
this.logger.debug(`Retrieved Community Edition with ID: ${organization?._id}`); | ||
} catch (error) { | ||
const isDuplicateKeyError = | ||
error instanceof Error && | ||
error.message.includes(this.E11000_DUPLICATE_KEY_ERROR_CODE) && | ||
error.message.includes(COMMUNITY_EDITION_NAME); | ||
|
||
if (!isDuplicateKeyError) { | ||
throw error; | ||
} | ||
|
||
this.logger.warn('Duplicate key error, another instance may have created the Community Edition'); | ||
communityEditionOrg = await this.organizationRepository.findOne(getSelfHostedFindQuery()); | ||
if (!communityEditionOrg) { | ||
this.logger.error('Failed to retrieve Community Edition after duplicate key error'); | ||
throw error; | ||
} | ||
|
||
this.logger.info(`Retrieved Community Edition created by another instance with ID: ${communityEditionOrg._id}`); | ||
} | ||
}); | ||
} | ||
|
||
private async createCommunityEditionUser(): Promise<UserEntity> { | ||
try { | ||
const { user } = await this.userRegisterUsecase.execute( | ||
UserRegisterCommand.create({ | ||
email: this.COMMUNITY_EDITION_USER_EMAIL, | ||
firstName: 'Community', | ||
lastName: 'User', | ||
password: 'communityUser1q@W#', | ||
}) | ||
); | ||
|
||
if (!user?._id) { | ||
throw new Error('Failed to create community user'); | ||
} | ||
|
||
return user; | ||
} catch (error) { | ||
const isDuplicateKeyDatabaseError = | ||
error instanceof Error && | ||
error.message.includes(this.E11000_DUPLICATE_KEY_ERROR_CODE) && | ||
error.message.includes(this.COMMUNITY_EDITION_USER_EMAIL); | ||
const isUserAlreadyExistsUsecaseError = error.message.includes('User already exists'); | ||
|
||
if (!isDuplicateKeyDatabaseError && !isUserAlreadyExistsUsecaseError) { | ||
throw error; | ||
} | ||
|
||
this.logger.warn('Duplicate key error, another instance may have created the Community User'); | ||
const user = await this.userRepository.findByEmail(this.COMMUNITY_EDITION_USER_EMAIL); | ||
if (!user) { | ||
this.logger.error('Failed to retrieve Community User after duplicate key error'); | ||
throw error; | ||
} | ||
|
||
this.logger.info(`Retrieved Community User created by another instance with ID: ${user._id}`); | ||
|
||
if (!user?._id) { | ||
throw new Error('Failed to create Community user'); | ||
} | ||
|
||
return user; | ||
} | ||
} | ||
} |
Empty file.
37 changes: 37 additions & 0 deletions
37
apps/api/src/app/auth/usecases/self-host/self-host.usecase.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Inject, NotFoundException } from '@nestjs/common'; | ||
import { CommunityOrganizationRepository, MemberRepository } from '@novu/dal'; | ||
import { SwitchOrganization } from '../switch-organization/switch-organization.usecase'; | ||
import { SwitchOrganizationCommand } from '../switch-organization'; | ||
import { getSelfHostedFindQuery } from '../../../shared/helpers/self-hosted'; | ||
|
||
export class SelfHostUsecase { | ||
constructor( | ||
@Inject('ORGANIZATION_REPOSITORY') | ||
private organizationRepository: CommunityOrganizationRepository, | ||
private memberRepository: MemberRepository, | ||
private readonly switchOrganizationUsecase: SwitchOrganization | ||
) {} | ||
|
||
async execute() { | ||
const communityEditionOrg = await this.organizationRepository.findOne(getSelfHostedFindQuery()); | ||
|
||
if (!communityEditionOrg) { | ||
throw new NotFoundException('Community Edition not found'); | ||
} | ||
|
||
const users = await this.memberRepository.getOrganizationMembers(communityEditionOrg._id); | ||
|
||
if (!users || users.length === 0) { | ||
throw new NotFoundException('No admin users found for Community Edition'); | ||
} | ||
|
||
const token = await this.switchOrganizationUsecase.execute( | ||
SwitchOrganizationCommand.create({ | ||
newOrganizationId: communityEditionOrg._id!, | ||
userId: users[0]._userId, | ||
}) | ||
); | ||
|
||
return { token }; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.