Skip to content

Commit

Permalink
Merge pull request #355 from Swetrix/feature/organisations
Browse files Browse the repository at this point in the history
(feature) Organisations
  • Loading branch information
Blaumaus authored Dec 14, 2024
2 parents b55b795 + 0676b2d commit 098cd72
Show file tree
Hide file tree
Showing 347 changed files with 10,748 additions and 9,386 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export enum ActionTokenType {
PROJECT_SHARE,
ADDING_PROJECT_SUBSCRIBER,
TRANSFER_PROJECT,
ORGANISATION_INVITE,
}

@Entity()
Expand Down
12 changes: 6 additions & 6 deletions backend/apps/cloud/src/action-tokens/action-tokens.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,22 @@ export class ActionTokensService {
user: User,
action: ActionTokenType,
newValue: string = null,
): Promise<ActionToken> {
) {
return this.actionTokensRepository.save({ user, action, newValue })
}

async find(id: string): Promise<ActionToken> {
async find(id: string) {
return this.actionTokensRepository.findOneOrFail({
where: { id },
relations: ['user'],
})
}

async delete(id: string): Promise<void> {
async delete(id: string) {
await this.actionTokensRepository.delete(id)
}

public async createActionToken(
async createActionToken(
userId: string,
action: ActionTokenType,
newValue?: string,
Expand All @@ -51,14 +51,14 @@ export class ActionTokensService {
})
}

public async findActionToken(token: string) {
async findActionToken(token: string) {
return this.actionTokensRepository.findOne({
where: { id: token },
relations: ['user'],
})
}

public async deleteActionToken(token: string) {
async deleteActionToken(token: string) {
await this.actionTokensRepository.delete(token)
}

Expand Down
48 changes: 36 additions & 12 deletions backend/apps/cloud/src/alert/alert.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,32 +46,56 @@ export class AlertController {
) {}

@ApiBearerAuth()
@Get('/')
@Get('/:alertId')
@UseGuards(JwtAccessTokenGuard, RolesGuard)
@Roles(UserType.ADMIN, UserType.CUSTOMER)
@ApiResponse({ status: 200, type: Alert })
async getAllAlerts(
async getAlert(
@CurrentUserId() userId: string,
@Param('alertId') alertId: string,
) {
this.logger.log({ userId, alertId }, 'GET /alert/:alertId')

const alert = await this.alertService.findOne({
where: { id: alertId },
relations: ['project'],
})

if (_isEmpty(alert)) {
throw new NotFoundException('Alert not found')
}

const project = await this.projectService.getFullProject(alert.project.id)

this.projectService.allowedToView(project, userId)

return _omit(alert, ['project'])
}

@ApiBearerAuth()
@Get('/project/:projectId')
@UseGuards(JwtAccessTokenGuard, RolesGuard)
@Roles(UserType.ADMIN, UserType.CUSTOMER)
@ApiResponse({ status: 200, type: Alert })
async getProjectAlerts(
@CurrentUserId() userId: string,
@Param('projectId') projectId: string,
@Query('take') take: number | undefined,
@Query('skip') skip: number | undefined,
) {
this.logger.log({ userId, take, skip }, 'GET /alert')
this.logger.log({ userId, projectId, take, skip }, 'GET /alert/:projectId')

const projects = await this.projectService.find({
where: {
admin: { id: userId },
},
})
const project = await this.projectService.getFullProject(projectId)

if (_isEmpty(projects)) {
return []
if (_isEmpty(project)) {
throw new NotFoundException('Project not found')
}

const pids = _map(projects, project => project.id)
this.projectService.allowedToView(project, userId)

const result = await this.alertService.paginate(
{ take, skip },
{ project: In(pids) },
{ project: { id: projectId } },
['project'],
)

Expand Down
2 changes: 2 additions & 0 deletions backend/apps/cloud/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { IntegrationsModule } from './integrations/integrations.module'
import { HealthModule } from './health/health.module'
import { AppController } from './app.controller'
import { isPrimaryNode, isPrimaryClusterNode } from './common/utils'
import { OrganisationModule } from './organisation/organisation.module'

const modules = [
SentryModule.forRoot(),
Expand Down Expand Up @@ -91,6 +92,7 @@ const modules = [
CaptchaModule,
OgImageModule,
HealthModule,
OrganisationModule,
]

@Module({
Expand Down
28 changes: 20 additions & 8 deletions backend/apps/cloud/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
SSOProviders,
} from './dtos'
import { JwtAccessTokenGuard, JwtRefreshTokenGuard, RolesGuard } from './guards'
import { ProjectService } from '../project/project.service'

const OAUTH_RATE_LIMIT = 15

Expand All @@ -71,6 +72,7 @@ export class AuthController {
constructor(
private readonly userService: UserService,
private readonly authService: AuthService,
private readonly projectService: ProjectService,
) {}

@ApiOperation({ summary: 'Register a new user' })
Expand All @@ -85,7 +87,7 @@ export class AuthController {
@I18n() i18n: I18nContext,
@Headers() headers: unknown,
@Ip() requestIp: string,
): Promise<RegisterResponseDto> {
) {
const ip = getIPFromHeaders(headers) || requestIp || ''

await checkRateLimit(ip, 'register', 5)
Expand Down Expand Up @@ -121,6 +123,7 @@ export class AuthController {
return {
...jwtTokens,
user: this.userService.omitSensitiveData(newUser),
totalMonthlyEvents: 0,
}
}

Expand All @@ -142,7 +145,7 @@ export class AuthController {

await checkRateLimit(ip, 'login', 10, 1800)

let user = await this.authService.validateUser(body.email, body.password)
let user = await this.authService.getBasicUser(body.email, body.password)

if (!user) {
throw new ConflictException(i18n.t('auth.invalidCredentials'))
Expand All @@ -153,20 +156,29 @@ export class AuthController {
!user.isTwoFactorAuthenticationEnabled,
)

const meta = {
totalMonthlyEvents: undefined,
}

await this.authService.sendTelegramNotification(user.id, headers, ip)

console.log('before:', user)
if (user.isTwoFactorAuthenticationEnabled) {
// @ts-expect-error
user = _pick(user, ['isTwoFactorAuthenticationEnabled', 'email'])
} else {
user = await this.authService.getSharedProjectsForUser(user)
const [sharedProjects, organisationMemberships] = await Promise.all([
this.authService.getSharedProjectsForUser(user.id),
this.authService.getOrganisationsForUser(user.id),
])

user.sharedProjects = sharedProjects
user.organisationMemberships = organisationMemberships
meta.totalMonthlyEvents = await this.projectService.getRedisCount(user.id)
}

console.log('after:', user)

return {
...jwtTokens,
...meta,
user: this.userService.omitSensitiveData(user as User),
}
}
Expand Down Expand Up @@ -260,7 +272,7 @@ export class AuthController {
throw new UnauthorizedException()
}

const isPasswordValid = await this.authService.validateUser(
const isPasswordValid = await this.authService.getBasicUser(
user.email,
body.oldPassword,
)
Expand Down Expand Up @@ -323,7 +335,7 @@ export class AuthController {
throw new UnauthorizedException()
}

const isPasswordValid = await this.authService.validateUser(
const isPasswordValid = await this.authService.getBasicUser(
user.email,
body.password,
)
Expand Down
2 changes: 2 additions & 0 deletions backend/apps/cloud/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
JwtRefreshTokenStrategy,
} from './strategies'
import { Message } from '../integrations/telegram/entities/message.entity'
import { OrganisationModule } from '../organisation/organisation.module'

@Module({
imports: [
Expand All @@ -26,6 +27,7 @@ import { Message } from '../integrations/telegram/entities/message.entity'
ActionTokensModule,
ProjectModule,
TypeOrmModule.forFeature([Message]),
OrganisationModule,
],
controllers: [AuthController],
providers: [
Expand Down
62 changes: 53 additions & 9 deletions backend/apps/cloud/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { TelegramService } from '../integrations/telegram/telegram.service'
import { SSOProviders } from './dtos'
import { UserGoogleDTO } from '../user/dto/user-google.dto'
import { UserGithubDTO } from '../user/dto/user-github.dto'
import { OrganisationService } from '../organisation/organisation.service'

const REDIS_SSO_SESSION_TIMEOUT = 60 * 5 // 5 minutes
const getSSORedisKey = (uuid: string) => `${REDIS_SSO_UUID}:${uuid}`
Expand Down Expand Up @@ -77,6 +78,7 @@ export class AuthService {
private readonly jwtService: JwtService,
private readonly projectService: ProjectService,
private readonly telegramService: TelegramService,
private readonly organisationService: OrganisationService,
) {
this.oauth2Client = new Auth.OAuth2Client(
this.configService.get('GOOGLE_OAUTH2_CLIENT_ID'),
Expand Down Expand Up @@ -180,7 +182,7 @@ export class AuthService {
)
}

public async validateUser(
public async getBasicUser(
email: string,
password: string,
): Promise<User | null> {
Expand All @@ -201,17 +203,32 @@ export class AuthService {
return null
}

public async getSharedProjectsForUser(user: User): Promise<User> {
const sharedProjects = await this.projectService.findShare({
async getSharedProjectsForUser(userId: string) {
return this.projectService.findShare({
where: {
user: { id: user.id },
user: { id: userId },
},
relations: ['project'],
})
}

user.sharedProjects = sharedProjects

return user
async getOrganisationsForUser(userId: string) {
return this.organisationService.findMemberships({
where: {
user: { id: userId },
},
relations: ['organisation'],
select: {
id: true,
role: true,
confirmed: true,
created: true,
organisation: {
id: true,
name: true,
},
},
})
}

private async comparePassword(
Expand Down Expand Up @@ -492,15 +509,28 @@ export class AuthService {
!user.isTwoFactorAuthenticationEnabled,
)

const meta = {
totalMonthlyEvents: undefined,
}

if (user.isTwoFactorAuthenticationEnabled) {
// @ts-expect-error
user = _pick(user, ['isTwoFactorAuthenticationEnabled', 'email'])
} else {
user = await this.getSharedProjectsForUser(user)
const [sharedProjects, organisationMemberships] = await Promise.all([
this.getSharedProjectsForUser(user.id),
this.getOrganisationsForUser(user.id),
])

user.sharedProjects = sharedProjects
user.organisationMemberships = organisationMemberships

meta.totalMonthlyEvents = await this.projectService.getRedisCount(user.id)
}

return {
...jwtTokens,
...meta,
user: this.userService.omitSensitiveData(user),
}
}
Expand Down Expand Up @@ -539,6 +569,7 @@ export class AuthService {
return {
...jwtTokens,
user: this.userService.omitSensitiveData(user),
totalMonthlyEvents: 0,
}
}

Expand Down Expand Up @@ -929,6 +960,7 @@ export class AuthService {
return {
...jwtTokens,
user: this.userService.omitSensitiveData(user),
totalMonthlyEvents: 0,
}
}

Expand All @@ -944,15 +976,27 @@ export class AuthService {
!user.isTwoFactorAuthenticationEnabled,
)

const meta = {
totalMonthlyEvents: undefined,
}

if (user.isTwoFactorAuthenticationEnabled) {
// @ts-expect-error
user = _pick(user, ['isTwoFactorAuthenticationEnabled', 'email'])
} else {
user = await this.getSharedProjectsForUser(user)
const [sharedProjects, organisationMemberships] = await Promise.all([
this.getSharedProjectsForUser(user.id),
this.getOrganisationsForUser(user.id),
])

user.sharedProjects = sharedProjects
user.organisationMemberships = organisationMemberships
meta.totalMonthlyEvents = await this.projectService.getRedisCount(user.id)
}

return {
...jwtTokens,
...meta,
user: this.userService.omitSensitiveData(user),
}
}
Expand Down
5 changes: 5 additions & 0 deletions backend/apps/cloud/src/auth/dtos/register.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,9 @@ export class RegisterResponseDto {
description: 'User entity',
})
user: object

@ApiProperty({
description: 'Total used monthly events',
})
totalMonthlyEvents: number
}
Loading

0 comments on commit 098cd72

Please sign in to comment.