Feature/setup internet identity (#1080)
* Setup Internet Identity as social login provider * Update changelog
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { MatCheckboxChange } from '@angular/material/checkbox';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { Router } from '@angular/router';
|
||||
import { InternetIdentityService } from '@ghostfolio/client/services/internet-identity.service';
|
||||
import {
|
||||
STAY_SIGNED_IN,
|
||||
SettingsStorageService
|
||||
} from '@ghostfolio/client/services/settings-storage.service';
|
||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-login-with-access-token-dialog',
|
||||
@@ -16,7 +19,10 @@ export class LoginWithAccessTokenDialog {
|
||||
public constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
public dialogRef: MatDialogRef<LoginWithAccessTokenDialog>,
|
||||
private settingsStorageService: SettingsStorageService
|
||||
private internetIdentityService: InternetIdentityService,
|
||||
private router: Router,
|
||||
private settingsStorageService: SettingsStorageService,
|
||||
private tokenStorageService: TokenStorageService
|
||||
) {}
|
||||
|
||||
ngOnInit() {}
|
||||
@@ -31,4 +37,14 @@ export class LoginWithAccessTokenDialog {
|
||||
public onClose() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
public async onLoginWithInternetIdentity() {
|
||||
try {
|
||||
const { authToken } = await this.internetIdentityService.login();
|
||||
|
||||
this.tokenStorageService.saveToken(authToken);
|
||||
this.dialogRef.close();
|
||||
this.router.navigate(['/']);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
@@ -5,16 +5,7 @@
|
||||
></gf-dialog-header>
|
||||
|
||||
<div mat-dialog-content>
|
||||
<div>
|
||||
<ng-container *ngIf="data.hasPermissionToUseSocialLogin">
|
||||
<div class="text-center">
|
||||
<a color="accent" href="/api/v1/auth/google" mat-flat-button
|
||||
><ion-icon class="mr-1" name="logo-google"></ion-icon
|
||||
><span i18n>Sign in with Google</span></a
|
||||
>
|
||||
</div>
|
||||
<div class="my-3 text-center text-muted" i18n>or</div>
|
||||
</ng-container>
|
||||
<div class="align-items-center d-flex flex-column">
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Security Token</mat-label>
|
||||
<textarea
|
||||
@@ -24,6 +15,29 @@
|
||||
[(ngModel)]="data.accessToken"
|
||||
></textarea>
|
||||
</mat-form-field>
|
||||
<ng-container *ngIf="data.hasPermissionToUseSocialLogin">
|
||||
<div class="my-3 text-center text-muted" i18n>or</div>
|
||||
<div class="d-flex flex-column">
|
||||
<button
|
||||
class="mb-2"
|
||||
mat-stroked-button
|
||||
(click)="onLoginWithInternetIdentity()"
|
||||
>
|
||||
<img
|
||||
class="mr-2"
|
||||
src="./assets/icons/internet-computer.svg"
|
||||
style="height: 0.75rem"
|
||||
/><span i18n>Sign in with Internet Identity</span>
|
||||
</button>
|
||||
<a href="/api/v1/auth/google" mat-stroked-button
|
||||
><img
|
||||
class="mr-2"
|
||||
src="./assets/icons/google.svg"
|
||||
style="height: 1rem"
|
||||
/><span i18n>Sign in with Google</span></a
|
||||
>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
|
@@ -12,4 +12,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mat-form-field {
|
||||
::ng-deep {
|
||||
.mat-form-field-wrapper {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Router } from '@angular/router';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { InternetIdentityService } from '@ghostfolio/client/services/internet-identity.service';
|
||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
||||
import { InfoItem } from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
@@ -34,6 +35,7 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
|
||||
private dataService: DataService,
|
||||
private deviceService: DeviceDetectorService,
|
||||
private dialog: MatDialog,
|
||||
private internetIdentityService: InternetIdentityService,
|
||||
private router: Router,
|
||||
private tokenStorageService: TokenStorageService
|
||||
) {
|
||||
@@ -62,6 +64,14 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
public async onLoginWithInternetIdentity() {
|
||||
try {
|
||||
const { authToken } = await this.internetIdentityService.login();
|
||||
this.tokenStorageService.saveToken(authToken);
|
||||
this.router.navigate(['/']);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
public openShowAccessTokenDialog(
|
||||
accessToken: string,
|
||||
authToken: string,
|
||||
|
@@ -28,16 +28,25 @@
|
||||
Create Account
|
||||
</button>
|
||||
<ng-container *ngIf="hasPermissionForSocialLogin">
|
||||
<div
|
||||
class="m-3 text-muted"
|
||||
i18n
|
||||
[ngClass]="{'d-inline': deviceType !== 'mobile' }"
|
||||
<div class="my-3 text-muted" i18n>or</div>
|
||||
<button
|
||||
class="d-block mb-2"
|
||||
mat-stroked-button
|
||||
(click)="onLoginWithInternetIdentity()"
|
||||
>
|
||||
or
|
||||
</div>
|
||||
<a color="accent" href="/api/v1/auth/google" mat-flat-button
|
||||
><ion-icon class="mr-1" name="logo-google"></ion-icon
|
||||
><span i18n>Continue with Google</span></a
|
||||
<img
|
||||
class="mr-2"
|
||||
src="./assets/icons/internet-computer.svg"
|
||||
style="height: 0.75rem"
|
||||
/>
|
||||
<span i18n>Continue with Internet Identity</span>
|
||||
</button>
|
||||
<a class="d-block" href="/api/v1/auth/google" mat-stroked-button
|
||||
><img
|
||||
class="mr-2"
|
||||
src="./assets/icons/google.svg"
|
||||
style="height: 1rem"
|
||||
/><span i18n>Continue with Google</span></a
|
||||
>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
@@ -23,6 +23,7 @@ import {
|
||||
Export,
|
||||
Filter,
|
||||
InfoItem,
|
||||
OAuthResponse,
|
||||
PortfolioChart,
|
||||
PortfolioDetails,
|
||||
PortfolioInvestments,
|
||||
@@ -368,7 +369,9 @@ export class DataService {
|
||||
}
|
||||
|
||||
public loginAnonymous(accessToken: string) {
|
||||
return this.http.get<any>(`/api/v1/auth/anonymous/${accessToken}`);
|
||||
return this.http.get<OAuthResponse>(
|
||||
`/api/v1/auth/anonymous/${accessToken}`
|
||||
);
|
||||
}
|
||||
|
||||
public postAccess(aAccess: CreateAccessDto) {
|
||||
|
55
apps/client/src/app/services/internet-identity.service.ts
Normal file
55
apps/client/src/app/services/internet-identity.service.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { AuthClient } from '@dfinity/auth-client';
|
||||
import { OAuthResponse } from '@ghostfolio/common/interfaces';
|
||||
import { EMPTY, Subject } from 'rxjs';
|
||||
import { catchError, takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class InternetIdentityService implements OnDestroy {
|
||||
private unsubscribeSubject = new Subject<void>();
|
||||
|
||||
public constructor(private http: HttpClient) {}
|
||||
|
||||
public async login(): Promise<OAuthResponse> {
|
||||
const authClient = await AuthClient.create({
|
||||
idleOptions: {
|
||||
disableDefaultIdleCallback: true,
|
||||
disableIdle: true
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
authClient.login({
|
||||
onError: async () => {
|
||||
return reject();
|
||||
},
|
||||
onSuccess: () => {
|
||||
const principalId = authClient.getIdentity().getPrincipal();
|
||||
|
||||
this.http
|
||||
.get<OAuthResponse>(
|
||||
`/api/v1/auth/internet-identity/${principalId.toText()}`
|
||||
)
|
||||
.pipe(
|
||||
catchError(() => {
|
||||
reject();
|
||||
return EMPTY;
|
||||
}),
|
||||
takeUntil(this.unsubscribeSubject)
|
||||
)
|
||||
.subscribe((response) => {
|
||||
resolve(response);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.unsubscribeSubject.next();
|
||||
this.unsubscribeSubject.complete();
|
||||
}
|
||||
}
|
1
apps/client/src/assets/icons/google.svg
Normal file
1
apps/client/src/assets/icons/google.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 128 128" id="Social_Icons" version="1.1" viewBox="0 0 128 128" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="_x31__stroke"><g id="Google"><rect clip-rule="evenodd" fill="none" fill-rule="evenodd" height="128" width="128"/><path clip-rule="evenodd" d="M27.585,64c0-4.157,0.69-8.143,1.923-11.881L7.938,35.648 C3.734,44.183,1.366,53.801,1.366,64c0,10.191,2.366,19.802,6.563,28.332l21.558-16.503C28.266,72.108,27.585,68.137,27.585,64" fill="#FBBC05" fill-rule="evenodd"/><path clip-rule="evenodd" d="M65.457,26.182c9.031,0,17.188,3.2,23.597,8.436L107.698,16 C96.337,6.109,81.771,0,65.457,0C40.129,0,18.361,14.484,7.938,35.648l21.569,16.471C34.477,37.033,48.644,26.182,65.457,26.182" fill="#EA4335" fill-rule="evenodd"/><path clip-rule="evenodd" d="M65.457,101.818c-16.812,0-30.979-10.851-35.949-25.937 L7.938,92.349C18.361,113.516,40.129,128,65.457,128c15.632,0,30.557-5.551,41.758-15.951L86.741,96.221 C80.964,99.86,73.689,101.818,65.457,101.818" fill="#34A853" fill-rule="evenodd"/><path clip-rule="evenodd" d="M126.634,64c0-3.782-0.583-7.855-1.457-11.636H65.457v24.727 h34.376c-1.719,8.431-6.397,14.912-13.092,19.13l20.474,15.828C118.981,101.129,126.634,84.861,126.634,64" fill="#4285F4" fill-rule="evenodd"/></g></g></svg>
|
After Width: | Height: | Size: 1.4 KiB |
28
apps/client/src/assets/icons/internet-computer.svg
Normal file
28
apps/client/src/assets/icons/internet-computer.svg
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 358.8 179.8" style="enable-background:new 0 0 358.8 179.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#SVGID_1_);}
|
||||
.st1{fill:url(#SVGID_2_);}
|
||||
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#29ABE2;}
|
||||
</style>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="224.7853" y1="257.7536" x2="348.0663" y2="133.4581" gradientTransform="matrix(1 0 0 -1 0 272)">
|
||||
<stop offset="0.21" style="stop-color:#F15A24"/>
|
||||
<stop offset="0.6841" style="stop-color:#FBB03B"/>
|
||||
</linearGradient>
|
||||
<path class="st0" d="M271.6,0c-20,0-41.9,10.9-65,32.4c-10.9,10.1-20.5,21.1-27.5,29.8c0,0,11.2,12.9,23.5,26.8
|
||||
c6.7-8.4,16.2-19.8,27.3-30.1c20.5-19.2,33.9-23.1,41.6-23.1c28.8,0,52.2,24.2,52.2,54.1c0,29.6-23.4,53.8-52.2,54.1
|
||||
c-1.4,0-3-0.2-5-0.6c8.4,3.9,17.5,6.7,26,6.7c52.8,0,63.2-36.5,63.8-39.1c1.5-6.7,2.4-13.7,2.4-20.9C358.6,40.4,319.6,0,271.6,0z"/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="133.9461" y1="106.4262" x2="10.6653" y2="230.7215" gradientTransform="matrix(1 0 0 -1 0 272)">
|
||||
<stop offset="0.21" style="stop-color:#ED1E79"/>
|
||||
<stop offset="0.8929" style="stop-color:#522785"/>
|
||||
</linearGradient>
|
||||
<path class="st1" d="M87.1,179.8c20,0,41.9-10.9,65-32.4c10.9-10.1,20.5-21.1,27.5-29.8c0,0-11.2-12.9-23.5-26.8
|
||||
c-6.7,8.4-16.2,19.8-27.3,30.1c-20.5,19-34,23.1-41.6,23.1c-28.8,0-52.2-24.2-52.2-54.1c0-29.6,23.4-53.8,52.2-54.1
|
||||
c1.4,0,3,0.2,5,0.6c-8.4-3.9-17.5-6.7-26-6.7C13.4,29.6,3,66.1,2.4,68.8C0.9,75.5,0,82.5,0,89.7C0,139.4,39,179.8,87.1,179.8z"/>
|
||||
<path class="st2" d="M127.3,59.7c-5.8-5.6-34-28.5-61-29.3C18.1,29.2,4,64.2,2.7,68.7C12,29.5,46.4,0.2,87.2,0
|
||||
c33.3,0,67,32.7,91.9,62.2c0,0,0.1-0.1,0.1-0.1c0,0,11.2,12.9,23.5,26.8c0,0,14,16.5,28.8,31c5.8,5.6,33.9,28.2,60.9,29
|
||||
c49.5,1.4,63.2-35.6,63.9-38.4c-9.1,39.5-43.6,68.9-84.6,69.1c-33.3,0-67-32.7-92-62.2c0,0.1-0.1,0.1-0.1,0.2
|
||||
c0,0-11.2-12.9-23.5-26.8C156.2,90.8,142.2,74.2,127.3,59.7z M2.7,69.1c0-0.1,0-0.2,0.1-0.3C2.7,68.9,2.7,69,2.7,69.1z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
Reference in New Issue
Block a user