|
|
@ -3,7 +3,11 @@ import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard' |
|
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; |
|
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; |
|
|
import { CreateAccessDto, UpdateAccessDto } from '@ghostfolio/common/dtos'; |
|
|
import { CreateAccessDto, UpdateAccessDto } from '@ghostfolio/common/dtos'; |
|
|
import { SubscriptionType } from '@ghostfolio/common/enums'; |
|
|
import { SubscriptionType } from '@ghostfolio/common/enums'; |
|
|
import { Access, AccessSettings } from '@ghostfolio/common/interfaces'; |
|
|
import { |
|
|
|
|
|
Access, |
|
|
|
|
|
AccessSettings, |
|
|
|
|
|
CreateAccessResponse |
|
|
|
|
|
} from '@ghostfolio/common/interfaces'; |
|
|
import { permissions } from '@ghostfolio/common/permissions'; |
|
|
import { permissions } from '@ghostfolio/common/permissions'; |
|
|
import type { RequestWithUser } from '@ghostfolio/common/types'; |
|
|
import type { RequestWithUser } from '@ghostfolio/common/types'; |
|
|
|
|
|
|
|
|
@ -21,8 +25,14 @@ import { |
|
|
} from '@nestjs/common'; |
|
|
} from '@nestjs/common'; |
|
|
import { REQUEST } from '@nestjs/core'; |
|
|
import { REQUEST } from '@nestjs/core'; |
|
|
import { AuthGuard } from '@nestjs/passport'; |
|
|
import { AuthGuard } from '@nestjs/passport'; |
|
|
import { Access as AccessModel } from '@prisma/client'; |
|
|
import { |
|
|
|
|
|
AccessPermission, |
|
|
|
|
|
AccessType, |
|
|
|
|
|
Access as AccessModel |
|
|
|
|
|
} from '@prisma/client'; |
|
|
|
|
|
import { endOfDay, isBefore, parseISO } from 'date-fns'; |
|
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; |
|
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; |
|
|
|
|
|
import { omit } from 'lodash'; |
|
|
|
|
|
|
|
|
import { AccessService } from './access.service'; |
|
|
import { AccessService } from './access.service'; |
|
|
|
|
|
|
|
|
@ -46,15 +56,26 @@ export class AccessController { |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
return accessesWithGranteeUser.map( |
|
|
return accessesWithGranteeUser.map( |
|
|
({ alias, granteeUser, id, permissions, settings }) => { |
|
|
({ alias, expiresAt, granteeUser, id, permissions, settings, type }) => { |
|
|
|
|
|
if (type === AccessType.API) { |
|
|
|
|
|
return { |
|
|
|
|
|
alias, |
|
|
|
|
|
id, |
|
|
|
|
|
permissions, |
|
|
|
|
|
type, |
|
|
|
|
|
expiresAt: expiresAt ?? undefined, |
|
|
|
|
|
settings: settings as AccessSettings |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (granteeUser) { |
|
|
if (granteeUser) { |
|
|
return { |
|
|
return { |
|
|
alias, |
|
|
alias, |
|
|
id, |
|
|
id, |
|
|
permissions, |
|
|
permissions, |
|
|
|
|
|
type, |
|
|
grantee: granteeUser?.id, |
|
|
grantee: granteeUser?.id, |
|
|
settings: settings as AccessSettings, |
|
|
settings: settings as AccessSettings |
|
|
type: 'PRIVATE' |
|
|
|
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -62,9 +83,9 @@ export class AccessController { |
|
|
alias, |
|
|
alias, |
|
|
id, |
|
|
id, |
|
|
permissions, |
|
|
permissions, |
|
|
|
|
|
type, |
|
|
grantee: 'Public', |
|
|
grantee: 'Public', |
|
|
settings: settings as AccessSettings, |
|
|
settings: settings as AccessSettings |
|
|
type: 'PUBLIC' |
|
|
|
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
); |
|
|
); |
|
|
@ -75,7 +96,7 @@ export class AccessController { |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
|
|
public async createAccess( |
|
|
public async createAccess( |
|
|
@Body() data: CreateAccessDto |
|
|
@Body() data: CreateAccessDto |
|
|
): Promise<AccessModel> { |
|
|
): Promise<CreateAccessResponse> { |
|
|
if ( |
|
|
if ( |
|
|
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && |
|
|
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && |
|
|
this.request.user.subscription.type === SubscriptionType.Basic |
|
|
this.request.user.subscription.type === SubscriptionType.Basic |
|
|
@ -86,16 +107,49 @@ export class AccessController { |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const type = |
|
|
|
|
|
data.type ?? |
|
|
|
|
|
(data.granteeUserId ? AccessType.PRIVATE : AccessType.PUBLIC); |
|
|
|
|
|
const isApiAccess = type === AccessType.API; |
|
|
|
|
|
|
|
|
|
|
|
const expiresAt = data.expiresAt |
|
|
|
|
|
? endOfDay(parseISO(data.expiresAt)) |
|
|
|
|
|
: undefined; |
|
|
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
|
(type === AccessType.PRIVATE && !data.granteeUserId) || |
|
|
|
|
|
(type !== AccessType.PRIVATE && data.granteeUserId) || |
|
|
|
|
|
(!isApiAccess && expiresAt) || |
|
|
|
|
|
(expiresAt && isBefore(expiresAt, new Date())) |
|
|
|
|
|
) { |
|
|
|
|
|
throw new HttpException( |
|
|
|
|
|
getReasonPhrase(StatusCodes.BAD_REQUEST), |
|
|
|
|
|
StatusCodes.BAD_REQUEST |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
return this.accessService.createAccess({ |
|
|
const { apiToken, hashedApiToken } = isApiAccess |
|
|
|
|
|
? this.accessService.createApiToken() |
|
|
|
|
|
: { apiToken: undefined, hashedApiToken: undefined }; |
|
|
|
|
|
|
|
|
|
|
|
const access = await this.accessService.createAccess({ |
|
|
|
|
|
expiresAt, |
|
|
|
|
|
hashedApiToken, |
|
|
|
|
|
type, |
|
|
alias: data.alias || undefined, |
|
|
alias: data.alias || undefined, |
|
|
granteeUser: data.granteeUserId |
|
|
granteeUser: data.granteeUserId |
|
|
? { connect: { id: data.granteeUserId } } |
|
|
? { connect: { id: data.granteeUserId } } |
|
|
: undefined, |
|
|
: undefined, |
|
|
permissions: data.permissions, |
|
|
permissions: isApiAccess ? [AccessPermission.READ] : data.permissions, |
|
|
settings: this.accessService.buildSettings(data.filters), |
|
|
settings: this.accessService.buildSettings(data.filters), |
|
|
user: { connect: { id: this.request.user.id } } |
|
|
user: { connect: { id: this.request.user.id } } |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
|
...omit(access, 'hashedApiToken'), |
|
|
|
|
|
apiToken |
|
|
|
|
|
}; |
|
|
} catch { |
|
|
} catch { |
|
|
throw new HttpException( |
|
|
throw new HttpException( |
|
|
getReasonPhrase(StatusCodes.BAD_REQUEST), |
|
|
getReasonPhrase(StatusCodes.BAD_REQUEST), |
|
|
@ -107,7 +161,9 @@ export class AccessController { |
|
|
@Delete(':id') |
|
|
@Delete(':id') |
|
|
@HasPermission(permissions.deleteAccess) |
|
|
@HasPermission(permissions.deleteAccess) |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
|
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
|
|
public async deleteAccess(@Param('id') id: string): Promise<AccessModel> { |
|
|
public async deleteAccess( |
|
|
|
|
|
@Param('id') id: string |
|
|
|
|
|
): Promise<Omit<AccessModel, 'hashedApiToken'>> { |
|
|
const originalAccess = await this.accessService.access({ |
|
|
const originalAccess = await this.accessService.access({ |
|
|
id, |
|
|
id, |
|
|
userId: this.request.user.id |
|
|
userId: this.request.user.id |
|
|
@ -120,9 +176,11 @@ export class AccessController { |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return this.accessService.deleteAccess({ |
|
|
const access = await this.accessService.deleteAccess({ |
|
|
id |
|
|
id |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
return omit(access, 'hashedApiToken'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@HasPermission(permissions.updateAccess) |
|
|
@HasPermission(permissions.updateAccess) |
|
|
@ -131,7 +189,7 @@ export class AccessController { |
|
|
public async updateAccess( |
|
|
public async updateAccess( |
|
|
@Body() data: UpdateAccessDto, |
|
|
@Body() data: UpdateAccessDto, |
|
|
@Param('id') id: string |
|
|
@Param('id') id: string |
|
|
): Promise<AccessModel> { |
|
|
): Promise<Omit<AccessModel, 'hashedApiToken'>> { |
|
|
if ( |
|
|
if ( |
|
|
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && |
|
|
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && |
|
|
this.request.user.subscription.type === SubscriptionType.Basic |
|
|
this.request.user.subscription.type === SubscriptionType.Basic |
|
|
@ -154,18 +212,34 @@ export class AccessController { |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const isApiAccess = originalAccess.type === AccessType.API; |
|
|
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
|
(originalAccess.type === AccessType.PRIVATE && !data.granteeUserId) || |
|
|
|
|
|
(originalAccess.type !== AccessType.PRIVATE && data.granteeUserId) |
|
|
|
|
|
) { |
|
|
|
|
|
throw new HttpException( |
|
|
|
|
|
getReasonPhrase(StatusCodes.BAD_REQUEST), |
|
|
|
|
|
StatusCodes.BAD_REQUEST |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
return this.accessService.updateAccess({ |
|
|
const access = await this.accessService.updateAccess({ |
|
|
data: { |
|
|
data: { |
|
|
alias: data.alias, |
|
|
alias: data.alias, |
|
|
granteeUser: data.granteeUserId |
|
|
granteeUser: data.granteeUserId |
|
|
? { connect: { id: data.granteeUserId } } |
|
|
? { connect: { id: data.granteeUserId } } |
|
|
: { disconnect: true }, |
|
|
: undefined, |
|
|
permissions: data.permissions, |
|
|
permissions: isApiAccess ? undefined : data.permissions, |
|
|
settings: this.accessService.buildSettings(data.filters) |
|
|
settings: data.filters |
|
|
|
|
|
? this.accessService.buildSettings(data.filters) |
|
|
|
|
|
: undefined |
|
|
}, |
|
|
}, |
|
|
where: { id } |
|
|
where: { id } |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
return omit(access, 'hashedApiToken'); |
|
|
} catch { |
|
|
} catch { |
|
|
throw new HttpException( |
|
|
throw new HttpException( |
|
|
getReasonPhrase(StatusCodes.BAD_REQUEST), |
|
|
getReasonPhrase(StatusCodes.BAD_REQUEST), |
|
|
|