|
|
|
@ -1,6 +1,15 @@ |
|
|
|
import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service'; |
|
|
|
import { PortfolioSnapshotValue } from '@ghostfolio/api/app/portfolio/interfaces/snapshot-value.interface'; |
|
|
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; |
|
|
|
import { UserService } from '@ghostfolio/api/app/user/user.service'; |
|
|
|
import { ApiService } from '@ghostfolio/api/services/api/api.service'; |
|
|
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; |
|
|
|
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; |
|
|
|
import { |
|
|
|
PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW, |
|
|
|
PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME, |
|
|
|
PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS |
|
|
|
} from '@ghostfolio/common/config'; |
|
|
|
import { AuthDeviceDto } from '@ghostfolio/common/dtos'; |
|
|
|
import { |
|
|
|
AssertionCredentialJSON, |
|
|
|
@ -29,6 +38,7 @@ import { |
|
|
|
VerifyRegistrationResponseOpts |
|
|
|
} from '@simplewebauthn/server'; |
|
|
|
import { isoBase64URL, isoUint8Array } from '@simplewebauthn/server/helpers'; |
|
|
|
import { isPast } from 'date-fns'; |
|
|
|
import ms from 'ms'; |
|
|
|
|
|
|
|
@Injectable() |
|
|
|
@ -36,9 +46,12 @@ export class WebAuthService { |
|
|
|
private readonly logger = new Logger(WebAuthService.name); |
|
|
|
|
|
|
|
public constructor( |
|
|
|
private readonly apiService: ApiService, |
|
|
|
private readonly configurationService: ConfigurationService, |
|
|
|
private readonly deviceService: AuthDeviceService, |
|
|
|
private readonly jwtService: JwtService, |
|
|
|
private readonly portfolioSnapshotService: PortfolioSnapshotService, |
|
|
|
private readonly redisCacheService: RedisCacheService, |
|
|
|
private readonly userService: UserService, |
|
|
|
@Inject(REQUEST) private readonly request: RequestWithUser |
|
|
|
) {} |
|
|
|
@ -155,6 +168,9 @@ export class WebAuthService { |
|
|
|
throw new Error('Device not found'); |
|
|
|
} |
|
|
|
|
|
|
|
// Compute in the background during the biometric authentication
|
|
|
|
void this.warmUpPortfolioSnapshot({ userId: device.userId }); |
|
|
|
|
|
|
|
const opts: GenerateAuthenticationOptionsOpts = { |
|
|
|
allowCredentials: [], |
|
|
|
rpID: this.rpID, |
|
|
|
@ -233,4 +249,57 @@ export class WebAuthService { |
|
|
|
|
|
|
|
throw new Error(); |
|
|
|
} |
|
|
|
|
|
|
|
private async isPortfolioSnapshotExpired(portfolioSnapshotKey: string) { |
|
|
|
try { |
|
|
|
const { expiration }: PortfolioSnapshotValue = JSON.parse( |
|
|
|
await this.redisCacheService.get(portfolioSnapshotKey) |
|
|
|
); |
|
|
|
|
|
|
|
return isPast(new Date(expiration)); |
|
|
|
} catch { |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private async warmUpPortfolioSnapshot({ userId }: { userId: string }) { |
|
|
|
try { |
|
|
|
const user = await this.userService.user({ id: userId }); |
|
|
|
|
|
|
|
if (!user) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const userSettings = user.settings.settings; |
|
|
|
|
|
|
|
const filters = this.apiService.buildFiltersFromUserSettings({ |
|
|
|
userSettings |
|
|
|
}); |
|
|
|
|
|
|
|
const portfolioSnapshotKey = |
|
|
|
this.redisCacheService.getPortfolioSnapshotKey({ filters, userId }); |
|
|
|
|
|
|
|
if (await this.isPortfolioSnapshotExpired(portfolioSnapshotKey)) { |
|
|
|
await this.portfolioSnapshotService.addJobToQueue({ |
|
|
|
data: { |
|
|
|
filters, |
|
|
|
userId, |
|
|
|
calculationType: userSettings.performanceCalculationType, |
|
|
|
userCurrency: userSettings.baseCurrency |
|
|
|
}, |
|
|
|
name: PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME, |
|
|
|
opts: { |
|
|
|
...PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS, |
|
|
|
jobId: portfolioSnapshotKey, |
|
|
|
priority: PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
this.logger.error( |
|
|
|
`Portfolio snapshot of user '${userId}' could not be warmed up`, |
|
|
|
error |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|