diff --git a/apps/api/src/app/auth-device/auth-device.controller.ts b/apps/api/src/app/auth-device/auth-device.controller.ts index 2abc694e0..5a0106c70 100644 --- a/apps/api/src/app/auth-device/auth-device.controller.ts +++ b/apps/api/src/app/auth-device/auth-device.controller.ts @@ -1,19 +1,32 @@ -import { Body, Controller, Delete, Get, HttpException, Inject, Param, Put, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + Delete, + Get, + HttpException, + Inject, + Param, + Put, + UseGuards +} from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; import { getReasonPhrase, StatusCodes } from 'http-status-codes'; import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service'; import { AuthDeviceDto } from '@ghostfolio/api/app/auth-device/auth-device.dto'; import { RequestWithUser } from '@ghostfolio/common/types'; -import { getPermissions, hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { + getPermissions, + hasPermission, + permissions +} from '@ghostfolio/common/permissions'; @Controller('auth-device') export class AuthDeviceController { public constructor( private readonly authDeviceService: AuthDeviceService, @Inject(REQUEST) private readonly request: RequestWithUser - ) { - } + ) {} @Delete(':id') @UseGuards(AuthGuard('jwt')) @@ -30,19 +43,20 @@ export class AuthDeviceController { ); } - await this.authDeviceService.deleteAuthDevice( - { - id_userId: { - id, - userId: this.request.user.id - } + await this.authDeviceService.deleteAuthDevice({ + id_userId: { + id, + userId: this.request.user.id } - ); + }); } @Put(':id') @UseGuards(AuthGuard('jwt')) - public async updateAuthDevice(@Param('id') id: string, @Body() data: AuthDeviceDto) { + public async updateAuthDevice( + @Param('id') id: string, + @Body() data: AuthDeviceDto + ) { if ( !hasPermission( getPermissions(this.request.user.role), @@ -69,19 +83,17 @@ export class AuthDeviceController { ); } - return this.authDeviceService.updateAuthDevice( - { - data: { - name: data.name - }, - where: { - id_userId: { - id, - userId: this.request.user.id - } + return this.authDeviceService.updateAuthDevice({ + data: { + name: data.name + }, + where: { + id_userId: { + id, + userId: this.request.user.id } } - ); + }); } @Get() @@ -92,7 +104,7 @@ export class AuthDeviceController { where: { userId: this.request.user.id } }); - return authDevices.map(authDevice => ({ + return authDevices.map((authDevice) => ({ createdAt: authDevice.createdAt.toISOString(), id: authDevice.id, name: authDevice.name diff --git a/apps/api/src/app/auth-device/auth-device.module.ts b/apps/api/src/app/auth-device/auth-device.module.ts index 079cf3ecd..a0e195dad 100644 --- a/apps/api/src/app/auth-device/auth-device.module.ts +++ b/apps/api/src/app/auth-device/auth-device.module.ts @@ -13,10 +13,6 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration.ser signOptions: { expiresIn: '180 days' } }) ], - providers: [ - AuthDeviceService, - ConfigurationService, - PrismaService, - ] + providers: [AuthDeviceService, ConfigurationService, PrismaService] }) export class AuthDeviceModule {} diff --git a/apps/api/src/app/auth-device/auth-device.service.ts b/apps/api/src/app/auth-device/auth-device.service.ts index 8c2f28c36..8f7d98ff6 100644 --- a/apps/api/src/app/auth-device/auth-device.service.ts +++ b/apps/api/src/app/auth-device/auth-device.service.ts @@ -5,12 +5,10 @@ import { AuthDevice, Prisma } from '@prisma/client'; @Injectable() export class AuthDeviceService { - public constructor( private readonly configurationService: ConfigurationService, private prisma: PrismaService - ) { - } + ) {} public async authDevice( where: Prisma.AuthDeviceWhereUniqueInput @@ -45,12 +43,10 @@ export class AuthDeviceService { }); } - public async updateAuthDevice( - params: { - data: Prisma.AuthDeviceUpdateInput; - where: Prisma.AuthDeviceWhereUniqueInput; - }, - ): Promise { + public async updateAuthDevice(params: { + data: Prisma.AuthDeviceUpdateInput; + where: Prisma.AuthDeviceWhereUniqueInput; + }): Promise { const { data, where } = params; return this.prisma.authDevice.update({ @@ -60,7 +56,7 @@ export class AuthDeviceService { } public async deleteAuthDevice( - where: Prisma.AuthDeviceWhereUniqueInput, + where: Prisma.AuthDeviceWhereUniqueInput ): Promise { return this.prisma.authDevice.delete({ where diff --git a/apps/api/src/app/auth/auth.controller.ts b/apps/api/src/app/auth/auth.controller.ts index dc6a54acc..befa72a1a 100644 --- a/apps/api/src/app/auth/auth.controller.ts +++ b/apps/api/src/app/auth/auth.controller.ts @@ -1,17 +1,30 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; -import { Body, Controller, Get, HttpException, Param, Post, Req, Res, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + Get, + HttpException, + Param, + Post, + Req, + Res, + UseGuards +} from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { getReasonPhrase, StatusCodes } from 'http-status-codes'; import { AuthService } from './auth.service'; import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service'; -import { AssertionCredentialJSON, AttestationCredentialJSON } from './interfaces/simplewebauthn'; +import { + AssertionCredentialJSON, + AttestationCredentialJSON +} from './interfaces/simplewebauthn'; @Controller('auth') export class AuthController { public constructor( private readonly authService: AuthService, private readonly configurationService: ConfigurationService, - private readonly webAuthService: WebAuthService, + private readonly webAuthService: WebAuthService ) {} @Get('anonymous/:accessToken') @@ -56,8 +69,13 @@ export class AuthController { @Post('webauthn/verify-attestation') @UseGuards(AuthGuard('jwt')) - public async verifyAttestation(@Body() body: { deviceName: string, credential: AttestationCredentialJSON }) { - return this.webAuthService.verifyAttestation(body.deviceName, body.credential); + public async verifyAttestation( + @Body() body: { deviceName: string; credential: AttestationCredentialJSON } + ) { + return this.webAuthService.verifyAttestation( + body.deviceName, + body.credential + ); } @Post('webauthn/generate-assertion-options') @@ -66,9 +84,14 @@ export class AuthController { } @Post('webauthn/verify-assertion') - public async verifyAssertion(@Body() body: { userId: string, credential: AssertionCredentialJSON }) { + public async verifyAssertion( + @Body() body: { userId: string; credential: AssertionCredentialJSON } + ) { try { - const authToken = await this.webAuthService.verifyAssertion(body.userId, body.credential); + const authToken = await this.webAuthService.verifyAssertion( + body.userId, + body.credential + ); return { authToken }; } catch { throw new HttpException( diff --git a/apps/api/src/app/auth/auth.module.ts b/apps/api/src/app/auth/auth.module.ts index 7ddd8b58c..a54e8b771 100644 --- a/apps/api/src/app/auth/auth.module.ts +++ b/apps/api/src/app/auth/auth.module.ts @@ -27,7 +27,7 @@ import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service'; JwtStrategy, PrismaService, UserService, - WebAuthService, + WebAuthService ] }) export class AuthModule {} diff --git a/apps/api/src/app/auth/interfaces/interfaces.ts b/apps/api/src/app/auth/interfaces/interfaces.ts index 425f8be74..0f9a9f8c1 100644 --- a/apps/api/src/app/auth/interfaces/interfaces.ts +++ b/apps/api/src/app/auth/interfaces/interfaces.ts @@ -2,7 +2,7 @@ import { Provider } from '@prisma/client'; import { AuthDeviceDto } from '@ghostfolio/api/app/auth-device/auth-device.dto'; export interface AuthDeviceDialogParams { - authDevice: AuthDeviceDto, + authDevice: AuthDeviceDto; } export interface ValidateOAuthLoginParams { diff --git a/apps/api/src/app/auth/interfaces/simplewebauthn.ts b/apps/api/src/app/auth/interfaces/simplewebauthn.ts index 1f5aecfad..4b9058e2f 100644 --- a/apps/api/src/app/auth/interfaces/simplewebauthn.ts +++ b/apps/api/src/app/auth/interfaces/simplewebauthn.ts @@ -1,93 +1,109 @@ export interface AuthenticatorAssertionResponse extends AuthenticatorResponse { - readonly authenticatorData: ArrayBuffer; - readonly signature: ArrayBuffer; - readonly userHandle: ArrayBuffer | null; + readonly authenticatorData: ArrayBuffer; + readonly signature: ArrayBuffer; + readonly userHandle: ArrayBuffer | null; } -export interface AuthenticatorAttestationResponse extends AuthenticatorResponse { - readonly attestationObject: ArrayBuffer; +export interface AuthenticatorAttestationResponse + extends AuthenticatorResponse { + readonly attestationObject: ArrayBuffer; } export interface AuthenticationExtensionsClientInputs { - appid?: string; - appidExclude?: string; - credProps?: boolean; - uvm?: boolean; + appid?: string; + appidExclude?: string; + credProps?: boolean; + uvm?: boolean; } export interface AuthenticationExtensionsClientOutputs { - appid?: boolean; - credProps?: CredentialPropertiesOutput; - uvm?: UvmEntries; + appid?: boolean; + credProps?: CredentialPropertiesOutput; + uvm?: UvmEntries; } export interface AuthenticatorSelectionCriteria { - authenticatorAttachment?: AuthenticatorAttachment; - requireResidentKey?: boolean; - residentKey?: ResidentKeyRequirement; - userVerification?: UserVerificationRequirement; + authenticatorAttachment?: AuthenticatorAttachment; + requireResidentKey?: boolean; + residentKey?: ResidentKeyRequirement; + userVerification?: UserVerificationRequirement; } export interface PublicKeyCredential extends Credential { - readonly rawId: ArrayBuffer; - readonly response: AuthenticatorResponse; - getClientExtensionResults(): AuthenticationExtensionsClientOutputs; + readonly rawId: ArrayBuffer; + readonly response: AuthenticatorResponse; + getClientExtensionResults(): AuthenticationExtensionsClientOutputs; } export interface PublicKeyCredentialCreationOptions { - attestation?: AttestationConveyancePreference; - authenticatorSelection?: AuthenticatorSelectionCriteria; - challenge: BufferSource; - excludeCredentials?: PublicKeyCredentialDescriptor[]; - extensions?: AuthenticationExtensionsClientInputs; - pubKeyCredParams: PublicKeyCredentialParameters[]; - rp: PublicKeyCredentialRpEntity; - timeout?: number; - user: PublicKeyCredentialUserEntity; + attestation?: AttestationConveyancePreference; + authenticatorSelection?: AuthenticatorSelectionCriteria; + challenge: BufferSource; + excludeCredentials?: PublicKeyCredentialDescriptor[]; + extensions?: AuthenticationExtensionsClientInputs; + pubKeyCredParams: PublicKeyCredentialParameters[]; + rp: PublicKeyCredentialRpEntity; + timeout?: number; + user: PublicKeyCredentialUserEntity; } export interface PublicKeyCredentialDescriptor { - id: BufferSource; - transports?: AuthenticatorTransport[]; - type: PublicKeyCredentialType; + id: BufferSource; + transports?: AuthenticatorTransport[]; + type: PublicKeyCredentialType; } export interface PublicKeyCredentialParameters { - alg: COSEAlgorithmIdentifier; - type: PublicKeyCredentialType; + alg: COSEAlgorithmIdentifier; + type: PublicKeyCredentialType; } export interface PublicKeyCredentialRequestOptions { - allowCredentials?: PublicKeyCredentialDescriptor[]; - challenge: BufferSource; - extensions?: AuthenticationExtensionsClientInputs; - rpId?: string; - timeout?: number; - userVerification?: UserVerificationRequirement; + allowCredentials?: PublicKeyCredentialDescriptor[]; + challenge: BufferSource; + extensions?: AuthenticationExtensionsClientInputs; + rpId?: string; + timeout?: number; + userVerification?: UserVerificationRequirement; } -export interface PublicKeyCredentialUserEntity extends PublicKeyCredentialEntity { - displayName: string; - id: BufferSource; +export interface PublicKeyCredentialUserEntity + extends PublicKeyCredentialEntity { + displayName: string; + id: BufferSource; } export interface AuthenticatorResponse { - readonly clientDataJSON: ArrayBuffer; + readonly clientDataJSON: ArrayBuffer; } export interface CredentialPropertiesOutput { - rk?: boolean; + rk?: boolean; } export interface Credential { - readonly id: string; - readonly type: string; + readonly id: string; + readonly type: string; } export interface PublicKeyCredentialRpEntity extends PublicKeyCredentialEntity { - id?: string; + id?: string; } export interface PublicKeyCredentialEntity { - name: string; -} -export declare type AttestationConveyancePreference = "direct" | "enterprise" | "indirect" | "none"; -export declare type AuthenticatorTransport = "ble" | "internal" | "nfc" | "usb"; + name: string; +} +export declare type AttestationConveyancePreference = + | 'direct' + | 'enterprise' + | 'indirect' + | 'none'; +export declare type AuthenticatorTransport = 'ble' | 'internal' | 'nfc' | 'usb'; export declare type COSEAlgorithmIdentifier = number; -export declare type UserVerificationRequirement = "discouraged" | "preferred" | "required"; +export declare type UserVerificationRequirement = + | 'discouraged' + | 'preferred' + | 'required'; export declare type UvmEntries = UvmEntry[]; -export declare type AuthenticatorAttachment = "cross-platform" | "platform"; -export declare type ResidentKeyRequirement = "discouraged" | "preferred" | "required"; +export declare type AuthenticatorAttachment = 'cross-platform' | 'platform'; +export declare type ResidentKeyRequirement = + | 'discouraged' + | 'preferred' + | 'required'; export declare type BufferSource = ArrayBufferView | ArrayBuffer; -export declare type PublicKeyCredentialType = "public-key"; +export declare type PublicKeyCredentialType = 'public-key'; export declare type UvmEntry = number[]; -export interface PublicKeyCredentialCreationOptionsJSON extends Omit { +export interface PublicKeyCredentialCreationOptionsJSON + extends Omit< + PublicKeyCredentialCreationOptions, + 'challenge' | 'user' | 'excludeCredentials' + > { user: PublicKeyCredentialUserEntityJSON; challenge: Base64URLString; excludeCredentials: PublicKeyCredentialDescriptorJSON[]; @@ -97,15 +113,21 @@ export interface PublicKeyCredentialCreationOptionsJSON extends Omit { +export interface PublicKeyCredentialRequestOptionsJSON + extends Omit< + PublicKeyCredentialRequestOptions, + 'challenge' | 'allowCredentials' + > { challenge: Base64URLString; allowCredentials?: PublicKeyCredentialDescriptorJSON[]; extensions?: AuthenticationExtensionsClientInputs; } -export interface PublicKeyCredentialDescriptorJSON extends Omit { +export interface PublicKeyCredentialDescriptorJSON + extends Omit { id: Base64URLString; } -export interface PublicKeyCredentialUserEntityJSON extends Omit { +export interface PublicKeyCredentialUserEntityJSON + extends Omit { id: string; } /** @@ -118,7 +140,11 @@ export interface AttestationCredential extends PublicKeyCredential { * A slightly-modified AttestationCredential to simplify working with ArrayBuffers that * are Base64URL-encoded in the browser so that they can be sent as JSON to the server. */ -export interface AttestationCredentialJSON extends Omit { +export interface AttestationCredentialJSON + extends Omit< + AttestationCredential, + 'response' | 'rawId' | 'getClientExtensionResults' + > { rawId: Base64URLString; response: AuthenticatorAttestationResponseJSON; clientExtensionResults: AuthenticationExtensionsClientOutputs; @@ -134,7 +160,11 @@ export interface AssertionCredential extends PublicKeyCredential { * A slightly-modified AssertionCredential to simplify working with ArrayBuffers that * are Base64URL-encoded in the browser so that they can be sent as JSON to the server. */ -export interface AssertionCredentialJSON extends Omit { +export interface AssertionCredentialJSON + extends Omit< + AssertionCredential, + 'response' | 'rawId' | 'getClientExtensionResults' + > { rawId: Base64URLString; response: AuthenticatorAssertionResponseJSON; clientExtensionResults: AuthenticationExtensionsClientOutputs; @@ -143,7 +173,11 @@ export interface AssertionCredentialJSON extends Omit { +export interface AuthenticatorAttestationResponseJSON + extends Omit< + AuthenticatorAttestationResponseFuture, + 'clientDataJSON' | 'attestationObject' + > { clientDataJSON: Base64URLString; attestationObject: Base64URLString; } @@ -151,7 +185,11 @@ export interface AuthenticatorAttestationResponseJSON extends Omit { +export interface AuthenticatorAssertionResponseJSON + extends Omit< + AuthenticatorAssertionResponse, + 'authenticatorData' | 'clientDataJSON' | 'signature' | 'userHandle' + > { authenticatorData: Base64URLString; clientDataJSON: Base64URLString; signature: Base64URLString; @@ -179,7 +217,8 @@ export declare type Base64URLString = string; * * Properties marked optional are not supported in all browsers. */ -export interface AuthenticatorAttestationResponseFuture extends AuthenticatorAttestationResponse { +export interface AuthenticatorAttestationResponseFuture + extends AuthenticatorAttestationResponse { getTransports?: () => AuthenticatorTransport[]; getAuthenticatorData?: () => ArrayBuffer; getPublicKey?: () => ArrayBuffer; diff --git a/apps/api/src/app/auth/web-auth.service.ts b/apps/api/src/app/auth/web-auth.service.ts index 4e75b0b31..f4521f625 100644 --- a/apps/api/src/app/auth/web-auth.service.ts +++ b/apps/api/src/app/auth/web-auth.service.ts @@ -1,5 +1,9 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; -import { Inject, Injectable, InternalServerErrorException } from '@nestjs/common'; +import { + Inject, + Injectable, + InternalServerErrorException +} from '@nestjs/common'; import { UserService } from '../user/user.service'; import { generateAssertionOptions, @@ -14,7 +18,10 @@ import { VerifyAttestationResponseOpts } from '@simplewebauthn/server'; import { REQUEST } from '@nestjs/core'; -import { AssertionCredentialJSON, AttestationCredentialJSON } from './interfaces/simplewebauthn'; +import { + AssertionCredentialJSON, + AttestationCredentialJSON +} from './interfaces/simplewebauthn'; import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service'; import base64url from 'base64url'; import { JwtService } from '@nestjs/jwt'; @@ -28,7 +35,7 @@ export class WebAuthService { private readonly deviceService: AuthDeviceService, private readonly jwtService: JwtService, private readonly userService: UserService, - @Inject(REQUEST) private readonly request: RequestWithUser, + @Inject(REQUEST) private readonly request: RequestWithUser ) {} get rpID() { @@ -41,7 +48,9 @@ export class WebAuthService { public async generateAttestationOptions() { const user = this.request.user; - const devices = await this.deviceService.authDevices({where: {userId: user.id}}); + const devices = await this.deviceService.authDevices({ + where: { userId: user.id } + }); const opts: GenerateAttestationOptionsOpts = { rpName: 'Ghostfolio', @@ -56,10 +65,10 @@ export class WebAuthService { * the browser if it's asked to perform an attestation when one of these ID's already resides * on it. */ - excludeCredentials: devices.map(device => ({ + excludeCredentials: devices.map((device) => ({ id: device.credentialId, type: 'public-key', - transports: ['usb', 'ble', 'nfc', 'internal'], + transports: ['usb', 'ble', 'nfc', 'internal'] })), /** * The optional authenticatorSelection property allows for specifying more constraints around @@ -67,8 +76,8 @@ export class WebAuthService { */ authenticatorSelection: { userVerification: 'preferred', - requireResidentKey: false, - }, + requireResidentKey: false + } }; const options = generateAttestationOptions(opts); @@ -79,18 +88,20 @@ export class WebAuthService { */ await this.userService.updateUser({ data: { - authChallenge: options.challenge, + authChallenge: options.challenge }, where: { - id: user.id, + id: user.id } - }) + }); return options; } - public async verifyAttestation(deviceName: string, credential: AttestationCredentialJSON): Promise { - + public async verifyAttestation( + deviceName: string, + credential: AttestationCredentialJSON + ): Promise { const user = this.request.user; const expectedChallenge = user.authChallenge; @@ -100,7 +111,7 @@ export class WebAuthService { credential, expectedChallenge, expectedOrigin: this.expectedOrigin, - expectedRPID: this.rpID, + expectedRPID: this.rpID }; verification = await verifyAttestationResponse(opts); } catch (error) { @@ -110,11 +121,15 @@ export class WebAuthService { const { verified, attestationInfo } = verification; - const devices = await this.deviceService.authDevices({where: {userId: user.id}}); + const devices = await this.deviceService.authDevices({ + where: { userId: user.id } + }); if (verified && attestationInfo) { const { credentialPublicKey, credentialID, counter } = attestationInfo; - let existingDevice = devices.find(device => device.credentialId === credentialID); + let existingDevice = devices.find( + (device) => device.credentialId === credentialID + ); if (!existingDevice) { /** @@ -126,7 +141,7 @@ export class WebAuthService { counter, name: deviceName, User: { connect: { id: user.id } } - }) + }); } return { @@ -139,26 +154,28 @@ export class WebAuthService { throw new InternalServerErrorException('An unknown error occurred'); } - public async generateAssertionOptions(userId: string){ - const devices = await this.deviceService.authDevices({where: {userId: userId}}); + public async generateAssertionOptions(userId: string) { + const devices = await this.deviceService.authDevices({ + where: { userId: userId } + }); - if(devices.length === 0){ - throw new Error('No registered auth devices found.') + if (devices.length === 0) { + throw new Error('No registered auth devices found.'); } const opts: GenerateAssertionOptionsOpts = { timeout: 60000, - allowCredentials: devices.map(dev => ({ + allowCredentials: devices.map((dev) => ({ id: dev.credentialId, type: 'public-key', - transports: ['usb', 'ble', 'nfc', 'internal'], + transports: ['usb', 'ble', 'nfc', 'internal'] })), /** * This optional value controls whether or not the authenticator needs be able to uniquely * identify the user interacting with it (via built-in PIN pad, fingerprint scanner, etc...) */ userVerification: 'preferred', - rpID: this.rpID, + rpID: this.rpID }; const options = generateAssertionOptions(opts); @@ -169,25 +186,31 @@ export class WebAuthService { */ await this.userService.updateUser({ data: { - authChallenge: options.challenge, + authChallenge: options.challenge }, where: { - id: userId, + id: userId } - }) + }); return options; } - public async verifyAssertion(userId: string, credential: AssertionCredentialJSON){ - + public async verifyAssertion( + userId: string, + credential: AssertionCredentialJSON + ) { const user = await this.userService.user({ id: userId }); const bodyCredIDBuffer = base64url.toBuffer(credential.rawId); - const devices = await this.deviceService.authDevices({where: {credentialId: bodyCredIDBuffer}}); + const devices = await this.deviceService.authDevices({ + where: { credentialId: bodyCredIDBuffer } + }); if (devices.length !== 1) { - throw new InternalServerErrorException(`Could not find authenticator matching ${credential.id}`); + throw new InternalServerErrorException( + `Could not find authenticator matching ${credential.id}` + ); } const authenticator = devices[0]; @@ -201,8 +224,8 @@ export class WebAuthService { authenticator: { credentialID: authenticator.credentialId, credentialPublicKey: authenticator.credentialPublicKey, - counter: authenticator.counter, - }, + counter: authenticator.counter + } }; verification = verifyAssertionResponse(opts); } catch (error) { @@ -218,8 +241,8 @@ export class WebAuthService { await this.deviceService.updateAuthDevice({ data: authenticator, - where: {id_userId: { id: authenticator.id, userId: user.id}} - }) + where: { id_userId: { id: authenticator.id, userId: user.id } } + }); return this.jwtService.sign({ id: user.id diff --git a/apps/api/src/services/configuration.service.ts b/apps/api/src/services/configuration.service.ts index 596e9d3ef..9a7a294e1 100644 --- a/apps/api/src/services/configuration.service.ts +++ b/apps/api/src/services/configuration.service.ts @@ -27,7 +27,7 @@ export class ConfigurationService { REDIS_HOST: str({ default: 'localhost' }), REDIS_PORT: port({ default: 6379 }), ROOT_URL: str({ default: 'http://localhost:4200' }), - WEB_AUTH_RP_ID: host({ default: 'localhost' }), + WEB_AUTH_RP_ID: host({ default: 'localhost' }) }); } diff --git a/apps/client/src/app/components/auth-device-settings/auth-device-settings.component.html b/apps/client/src/app/components/auth-device-settings/auth-device-settings.component.html index f4ef43aaf..4cd1c8b80 100644 --- a/apps/client/src/app/components/auth-device-settings/auth-device-settings.component.html +++ b/apps/client/src/app/components/auth-device-settings/auth-device-settings.component.html @@ -8,12 +8,14 @@ > Name - {{element.name}}{{element.id === currentDeviceId ? ' (current)' : ''}} + + {{ element.name }}{{ element.id === currentDeviceId ? ' (current)' : '' }} + Created at - {{element.createdAt | date}} + {{ element.createdAt | date }} @@ -39,10 +41,7 @@ - + { this.setToken(authToken, false); }); diff --git a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.module.ts b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.module.ts index 7701f4602..878211c3b 100644 --- a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.module.ts +++ b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.module.ts @@ -6,7 +6,7 @@ import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; -import { MatCheckboxModule} from "@angular/material/checkbox"; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { LoginWithAccessTokenDialog } from './login-with-access-token-dialog.component'; diff --git a/apps/client/src/app/pages/account/account-page.component.ts b/apps/client/src/app/pages/account/account-page.component.ts index 288966e69..18bbfd32a 100644 --- a/apps/client/src/app/pages/account/account-page.component.ts +++ b/apps/client/src/app/pages/account/account-page.component.ts @@ -37,7 +37,7 @@ export class AccountPageComponent implements OnDestroy, OnInit { private dialog: MatDialog, private dataService: DataService, private userService: UserService, - public webAuthnService: WebAuthnService, + public webAuthnService: WebAuthnService ) { this.dataService .fetchInfo() @@ -100,17 +100,23 @@ export class AccountPageComponent implements OnDestroy, OnInit { } public startWebAuthn() { - this.webAuthnService.startWebAuthn() + this.webAuthnService + .startWebAuthn() .pipe( - switchMap(attResp => { + switchMap((attResp) => { const dialogRef = this.dialog.open(AuthDeviceDialog, { data: { authDevice: {} } }); - return dialogRef.afterClosed().pipe(switchMap(data => { - return this.webAuthnService.verifyAttestation(attResp, data.authDevice.name) - })); + return dialogRef.afterClosed().pipe( + switchMap((data) => { + return this.webAuthnService.verifyAttestation( + attResp, + data.authDevice.name + ); + }) + ); }) ) .subscribe(() => { @@ -133,10 +139,13 @@ export class AccountPageComponent implements OnDestroy, OnInit { } }); - dialogRef.afterClosed() + dialogRef + .afterClosed() .pipe( filter(isNonNull), - switchMap(data => this.webAuthnService.updateAuthDevice(data.authDevice)) + switchMap((data) => + this.webAuthnService.updateAuthDevice(data.authDevice) + ) ) .subscribe({ next: () => { @@ -149,7 +158,7 @@ export class AccountPageComponent implements OnDestroy, OnInit { this.webAuthnService .fetchAuthDevices() .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(authDevices => { + .subscribe((authDevices) => { this.authDevices$.next(authDevices); }); } diff --git a/apps/client/src/app/pages/account/account-page.html b/apps/client/src/app/pages/account/account-page.html index ceacc4ddb..187b35632 100644 --- a/apps/client/src/app/pages/account/account-page.html +++ b/apps/client/src/app/pages/account/account-page.html @@ -76,34 +76,34 @@ -
-
-

WebAuthn devices

- +
+
+

WebAuthn devices

+ -
-
- +
+
-
-
+
+
-
diff --git a/apps/client/src/app/pages/account/account-page.module.ts b/apps/client/src/app/pages/account/account-page.module.ts index 462475d7b..32bb36b40 100644 --- a/apps/client/src/app/pages/account/account-page.module.ts +++ b/apps/client/src/app/pages/account/account-page.module.ts @@ -14,10 +14,7 @@ import { MatDialogModule } from '@angular/material/dialog'; import { AuthDeviceDialog } from '@ghostfolio/client/pages/account/auth-device-dialog/auth-device-dialog.component'; @NgModule({ - declarations: [ - AuthDeviceDialog, - AccountPageComponent, - ], + declarations: [AuthDeviceDialog, AccountPageComponent], exports: [], imports: [ AccountPageRoutingModule, @@ -31,7 +28,7 @@ import { AuthDeviceDialog } from '@ghostfolio/client/pages/account/auth-device-d MatFormFieldModule, MatInputModule, MatSelectModule, - ReactiveFormsModule, + ReactiveFormsModule ], providers: [] }) diff --git a/apps/client/src/app/pages/account/auth-device-dialog/auth-device-dialog.component.html b/apps/client/src/app/pages/account/auth-device-dialog/auth-device-dialog.component.html index 6cbe6e1df..47e1f7934 100644 --- a/apps/client/src/app/pages/account/auth-device-dialog/auth-device-dialog.component.html +++ b/apps/client/src/app/pages/account/auth-device-dialog/auth-device-dialog.component.html @@ -5,14 +5,21 @@
Name - +
- +