Browse Source

Refactor JwtStrategy

pull/1764/head
Thomas 3 years ago
parent
commit
94da578e10
  1. 36
      apps/api/src/app/admin/admin.service.ts
  2. 27
      apps/api/src/app/auth/jwt.strategy.ts
  3. 4
      apps/api/src/app/info/info.service.ts
  4. 6
      apps/api/src/app/user/user.controller.ts
  5. 6
      apps/api/src/app/user/user.service.ts
  6. 2
      apps/api/src/interceptors/transform-data-source-in-request.interceptor.ts
  7. 4
      apps/api/src/interceptors/transform-data-source-in-response.interceptor.ts
  8. 13
      apps/client/src/app/components/admin-users/admin-users.html

36
apps/api/src/app/admin/admin.service.ts

@ -231,12 +231,27 @@ export class AdminService {
} }
private async getUsersWithAnalytics(): Promise<AdminData['users']> { private async getUsersWithAnalytics(): Promise<AdminData['users']> {
const usersWithAnalytics = await this.prismaService.user.findMany({ let orderBy: any = {
orderBy: { createdAt: 'desc'
};
let where;
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
orderBy = {
Analytics: { Analytics: {
updatedAt: 'desc' updatedAt: 'desc'
} }
}, };
where = {
NOT: {
Analytics: null
}
};
}
const usersWithAnalytics = await this.prismaService.user.findMany({
orderBy,
where,
select: { select: {
_count: { _count: {
select: { Account: true, Order: true } select: { Account: true, Order: true }
@ -252,19 +267,16 @@ export class AdminService {
id: true, id: true,
Subscription: true Subscription: true
}, },
take: 30, take: 30
where: {
NOT: {
Analytics: null
}
}
}); });
return usersWithAnalytics.map( return usersWithAnalytics.map(
({ _count, Analytics, createdAt, id, Subscription }) => { ({ _count, Analytics, createdAt, id, Subscription }) => {
const daysSinceRegistration = const daysSinceRegistration =
differenceInDays(new Date(), createdAt) + 1; differenceInDays(new Date(), createdAt) + 1;
const engagement = Analytics.activityCount / daysSinceRegistration; const engagement = Analytics
? Analytics.activityCount / daysSinceRegistration
: undefined;
const subscription = this.configurationService.get( const subscription = this.configurationService.get(
'ENABLE_FEATURE_SUBSCRIPTION' 'ENABLE_FEATURE_SUBSCRIPTION'
@ -278,8 +290,8 @@ export class AdminService {
id, id,
subscription, subscription,
accountCount: _count.Account || 0, accountCount: _count.Account || 0,
country: Analytics.country, country: Analytics?.country,
lastActivity: Analytics.updatedAt, lastActivity: Analytics?.updatedAt,
transactionCount: _count.Order || 0 transactionCount: _count.Order || 0
}; };
} }

27
apps/api/src/app/auth/jwt.strategy.ts

@ -1,33 +1,46 @@
import { UserService } from '@ghostfolio/api/app/user/user.service'; import { UserService } from '@ghostfolio/api/app/user/user.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { HEADER_KEY_TIMEZONE } from '@ghostfolio/common/config';
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport'; import { PassportStrategy } from '@nestjs/passport';
import * as countriesAndTimezones from 'countries-and-timezones';
import { ExtractJwt, Strategy } from 'passport-jwt'; import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable() @Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
public constructor( public constructor(
readonly configurationService: ConfigurationService, private readonly configurationService: ConfigurationService,
private readonly prismaService: PrismaService, private readonly prismaService: PrismaService,
private readonly userService: UserService private readonly userService: UserService
) { ) {
super({ super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
passReqToCallback: true,
secretOrKey: configurationService.get('JWT_SECRET_KEY') secretOrKey: configurationService.get('JWT_SECRET_KEY')
}); });
} }
public async validate({ id }: { id: string }) { public async validate(request: Request, { id }: { id: string }) {
try { try {
const timezone = request.headers[HEADER_KEY_TIMEZONE.toLowerCase()];
const user = await this.userService.user({ id }); const user = await this.userService.user({ id });
if (user) { if (user) {
await this.prismaService.analytics.upsert({ if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
create: { User: { connect: { id: user.id } } }, const country =
update: { activityCount: { increment: 1 }, updatedAt: new Date() }, countriesAndTimezones.getCountryForTimezone(timezone)?.id;
where: { userId: user.id }
}); await this.prismaService.analytics.upsert({
create: { country, User: { connect: { id: user.id } } },
update: {
country,
activityCount: { increment: 1 },
updatedAt: new Date()
},
where: { userId: user.id }
});
}
return user; return user;
} else { } else {

4
apps/api/src/app/info/info.service.ts

@ -59,9 +59,7 @@ export class InfoService {
} }
if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) { if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) {
if ( if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') === true
) {
info.fearAndGreedDataSource = encodeDataSource( info.fearAndGreedDataSource = encodeDataSource(
ghostfolioFearAndGreedIndexDataSource ghostfolioFearAndGreedIndexDataSource
); );

6
apps/api/src/app/user/user.controller.ts

@ -1,5 +1,4 @@
import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { HEADER_KEY_TIMEZONE } from '@ghostfolio/common/config';
import { User, UserSettings } from '@ghostfolio/common/interfaces'; import { User, UserSettings } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types'; import type { RequestWithUser } from '@ghostfolio/common/types';
@ -66,9 +65,7 @@ export class UserController {
} }
@Post() @Post()
public async signupUser( public async signupUser(): Promise<UserItem> {
@Headers(HEADER_KEY_TIMEZONE.toLowerCase()) timezone
): Promise<UserItem> {
const isUserSignupEnabled = const isUserSignupEnabled =
await this.propertyService.isUserSignupEnabled(); await this.propertyService.isUserSignupEnabled();
@ -82,7 +79,6 @@ export class UserController {
const hasAdmin = await this.userService.hasAdmin(); const hasAdmin = await this.userService.hasAdmin();
const { accessToken, id, role } = await this.userService.createUser({ const { accessToken, id, role } = await this.userService.createUser({
timezone,
data: { role: hasAdmin ? 'USER' : 'ADMIN' } data: { role: hasAdmin ? 'USER' : 'ADMIN' }
}); });

6
apps/api/src/app/user/user.service.ts

@ -16,7 +16,6 @@ import {
} from '@ghostfolio/common/permissions'; } from '@ghostfolio/common/permissions';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Prisma, Role, User } from '@prisma/client'; import { Prisma, Role, User } from '@prisma/client';
import * as countriesAndTimezones from 'countries-and-timezones';
import { sortBy } from 'lodash'; import { sortBy } from 'lodash';
const crypto = require('crypto'); const crypto = require('crypto');
@ -233,11 +232,9 @@ export class UserService {
} }
public async createUser({ public async createUser({
data, data
timezone
}: { }: {
data: Prisma.UserCreateInput; data: Prisma.UserCreateInput;
timezone?: string;
}): Promise<User> { }): Promise<User> {
if (!data?.provider) { if (!data?.provider) {
data.provider = 'ANONYMOUS'; data.provider = 'ANONYMOUS';
@ -266,7 +263,6 @@ export class UserService {
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
await this.prismaService.analytics.create({ await this.prismaService.analytics.create({
data: { data: {
country: countriesAndTimezones.getCountryForTimezone(timezone)?.id,
User: { connect: { id: user.id } } User: { connect: { id: user.id } }
} }
}); });

2
apps/api/src/interceptors/transform-data-source-in-request.interceptor.ts

@ -24,7 +24,7 @@ export class TransformDataSourceInRequestInterceptor<T>
const http = context.switchToHttp(); const http = context.switchToHttp();
const request = http.getRequest(); const request = http.getRequest();
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') === true) { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
if (request.body.dataSource) { if (request.body.dataSource) {
request.body.dataSource = decodeDataSource(request.body.dataSource); request.body.dataSource = decodeDataSource(request.body.dataSource);
} }

4
apps/api/src/interceptors/transform-data-source-in-response.interceptor.ts

@ -26,9 +26,7 @@ export class TransformDataSourceInResponseInterceptor<T>
): Observable<any> { ): Observable<any> {
return next.handle().pipe( return next.handle().pipe(
map((data: any) => { map((data: any) => {
if ( if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') === true
) {
data = redactAttributes({ data = redactAttributes({
options: [ options: [
{ {

13
apps/client/src/app/components/admin-users/admin-users.html

@ -28,7 +28,13 @@
> >
<ng-container i18n>Engagement per Day</ng-container> <ng-container i18n>Engagement per Day</ng-container>
</th> </th>
<th class="mat-header-cell px-1 py-2" i18n>Last Request</th> <th
*ngIf="hasPermissionForSubscription"
class="mat-header-cell px-1 py-2"
i18n
>
Last Request
</th>
<th class="mat-header-cell px-1 py-2"></th> <th class="mat-header-cell px-1 py-2"></th>
</tr> </tr>
</thead> </thead>
@ -86,7 +92,10 @@
[value]="userItem.engagement" [value]="userItem.engagement"
></gf-value> ></gf-value>
</td> </td>
<td class="mat-cell px-1 py-2"> <td
*ngIf="hasPermissionForSubscription"
class="mat-cell px-1 py-2"
>
{{ formatDistanceToNow(userItem.lastActivity) }} {{ formatDistanceToNow(userItem.lastActivity) }}
</td> </td>
<td class="mat-cell px-1 py-2"> <td class="mat-cell px-1 py-2">

Loading…
Cancel
Save