Refactor auth module.

- Switch from form to text field for passphrase input.
- Refactor error dialog format.
- Send dialog on incorrect parsing of private key.
- Refactor block sync service to take parameters.
This commit is contained in:
Spencer Ofwiti 2021-03-16 13:08:18 +03:00
parent 9fa9852ded
commit 12f4d328bf
15 changed files with 82 additions and 75 deletions

View File

@ -4,6 +4,10 @@ An angular admin web client for managing users and transactions in the CIC netwo
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.2.0.
## Angular CLI
Run `npm install -g @angular/cli` to install the angular CLI.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.

View File

@ -16,16 +16,13 @@ export class ErrorInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
return next.handle(request).pipe(catchError((err: HttpErrorResponse) => {
const data = {
this.errorDialogService.openDialog({
message: err.error.message || err.statusText,
reason: err && err.error && err.error.reason ? err.error.reason : '',
status: err.status
};
this.errorDialogService.openDialog(data);
});
if ([401, 403].indexOf(err.status) !== -1) {
location.reload(true);
}
// const error = err.error.message || err.statusText;
return throwError(err);
}));
}

View File

@ -1,10 +1,11 @@
import { Injectable } from '@angular/core';
import {MutableKeyStore, MutablePgpKeyStore} from '@app/_helpers';
import { hobaParseChallengeHeader } from '@src/assets/js/hoba.js';
import { signChallenge } from '@src/assets/js/hoba-pgp.js';
import {environment} from '@src/environments/environment';
import {LoggingService} from '@app/_services/logging.service';
import {HttpWrapperService} from '@app/_services/http-wrapper.service';
import {MutableKeyStore, MutablePgpKeyStore} from '@app/_pgp';
import {ErrorDialogService} from '@app/_services/error-dialog.service';
import {first} from 'rxjs/operators';
@Injectable({
@ -18,7 +19,8 @@ export class AuthService {
constructor(
private httpWrapperService: HttpWrapperService,
private loggingService: LoggingService
private loggingService: LoggingService,
private errorDialogService: ErrorDialogService
) {
if (sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'))) {
this.sessionToken = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'));
@ -29,7 +31,7 @@ export class AuthService {
}
setState(s): void {
(document.getElementById('state') as HTMLInputElement).value = s;
document.getElementById('state').innerHTML = s;
}
getWithToken(): void {
@ -44,7 +46,7 @@ export class AuthService {
throw new Error('login rejected');
}
this.sessionLoginCount++;
this.setState('click to perform login ' + this.sessionLoginCount + ' with token ' + this.sessionToken);
this.setState('Click button to perform login ' + this.sessionLoginCount + ' with token ' + this.sessionToken);
return;
});
xhr.send();
@ -64,7 +66,7 @@ export class AuthService {
this.sessionToken = xhr.getResponseHeader('Token');
sessionStorage.setItem(btoa('CICADA_SESSION_TOKEN'), this.sessionToken);
this.sessionLoginCount++;
this.setState('click to perform login ' + this.sessionLoginCount + ' with token ' + this.sessionToken);
this.setState('Click button to perform login ' + this.sessionLoginCount + ' with token ' + this.sessionToken);
return;
});
xhr.send();
@ -95,8 +97,7 @@ export class AuthService {
}
} else {
try {
const o = this.getChallenge();
return true;
this.getChallenge();
} catch (e) {
this.loggingService.sendErrorLevelMessage('Login challenge failed', this, {error: e});
}
@ -113,15 +114,19 @@ export class AuthService {
loginView(): void {
document.getElementById('one').style.display = 'none';
document.getElementById('two').style.display = 'block';
this.setState('click to log in with PGP key ' + this.mutableKeyStore.getPrivateKeyId());
this.setState('Click button to log in with PGP key ' + this.mutableKeyStore.getPrivateKeyId());
}
async setKey(privateKeyArmored): Promise<boolean> {
try {
await this.mutableKeyStore.importPrivateKey(privateKeyArmored);
localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored);
} catch (e) {
this.loggingService.sendErrorLevelMessage('Failed setting key', this, {error: e});
} catch (err) {
this.loggingService.sendErrorLevelMessage('Failed setting key', this, {error: err});
this.errorDialogService.openDialog({
message: `Failed to set key, Enter your private key again. Reason: ${err.error.message || err.statusText}`,
status: err.status
});
return false;
}
this.loginView();
@ -134,10 +139,10 @@ export class AuthService {
}
async getPublicKeys(): Promise<void> {
this.httpWrapperService.get(`${environment.publicKeysUrl}/keys.asc`).pipe(first()).subscribe(async res => {
this.httpWrapperService.get(`${environment.publicKeysUrl}`).pipe(first()).subscribe(async res => {
await this.mutableKeyStore.importPublicKey(res.body);
}, error => {
this.loggingService.sendErrorLevelMessage('There was an error!', this, {error});
this.loggingService.sendErrorLevelMessage('There was an error fetching public keys!', this, {error});
});
if (this.privateKey !== undefined) {
await this.mutableKeyStore.importPrivateKey(this.privateKey);

View File

@ -21,7 +21,8 @@ export class BlockSyncService {
private loggingService: LoggingService
) { }
blockSync(): any {
blockSync(address: string = null, offset: number = 0, limit: number = 100): any {
this.transactionService.resetTransactionsList();
const settings = new Settings(this.scan);
const provider = environment.web3Provider;
const readyStateElements = { network: 2 };
@ -40,13 +41,13 @@ export class BlockSyncService {
};
settings.registry.onload = (addressReturned: number): void => {
this.loggingService.sendInfoLevelMessage(`Loaded network contracts ${addressReturned}`);
this.readyStateProcessor(settings, readyStateElements.network);
this.readyStateProcessor(settings, readyStateElements.network, address, offset, limit);
};
settings.registry.load();
}
readyStateProcessor(settings, bit): void {
readyStateProcessor(settings: Settings, bit: number, address: string, offset: number, limit: number): void {
this.readyState |= bit;
if (this.readyStateTarget === this.readyState && this.readyStateTarget) {
const wHeadSync = new Worker('./../assets/js/block-sync/head.js');
@ -56,7 +57,15 @@ export class BlockSyncService {
wHeadSync.postMessage({
w3_provider: settings.w3.provider,
});
this.fetcher(settings);
if (address === null) {
this.transactionService.getAllTransactions(offset, limit).pipe(first()).subscribe(res => {
this.fetcher(settings, res.body);
});
} else {
this.transactionService.getAddressTransactions(address, offset, limit).pipe(first()).subscribe(res => {
this.fetcher(settings, res.body);
});
}
}
}
@ -93,17 +102,15 @@ export class BlockSyncService {
});
}
fetcher(settings: any, offset: number = 0, limit: number = 100): void {
this.transactionService.getAllTransactions(offset, limit).pipe(first()).subscribe(res => {
const blockFilterBinstr = window.atob(res.body.block_filter);
const bOne = new Uint8Array(blockFilterBinstr.length);
bOne.map((e, i, v) => v[i] = blockFilterBinstr.charCodeAt(i));
fetcher(settings: Settings, transactionsInfo: any): void {
const blockFilterBinstr = window.atob(transactionsInfo.block_filter);
const bOne = new Uint8Array(blockFilterBinstr.length);
bOne.map((e, i, v) => v[i] = blockFilterBinstr.charCodeAt(i));
const blocktxFilterBinstr = window.atob(res.body.blocktx_filter);
const bTwo = new Uint8Array(blocktxFilterBinstr.length);
bTwo.map((e, i, v) => v[i] = blocktxFilterBinstr.charCodeAt(i));
const blocktxFilterBinstr = window.atob(transactionsInfo.blocktx_filter);
const bTwo = new Uint8Array(blocktxFilterBinstr.length);
bTwo.map((e, i, v) => v[i] = blocktxFilterBinstr.charCodeAt(i));
settings.scanFilter(settings, res.body.low, res.body.high, bOne, bTwo, res.body.filter_rounds);
});
settings.scanFilter(settings, transactionsInfo.low, transactionsInfo.high, bOne, bTwo, transactionsInfo.filter_rounds);
}
}

View File

@ -41,6 +41,7 @@ export class HttpWrapperService {
return Observable.create((observer: any) => {
const requestBeginTime = moment();
this.http.request(new HttpRequest(method, url, body, options)).subscribe((response) => {
this.loggingService.sendInfoLevelMessage(response);
this.logTime(requestBeginTime, `${url}`, method);
observer.next(response);
observer.complete();

View File

@ -6,3 +6,4 @@ export * from '@app/_services/block-sync.service';
export * from '@app/_services/location.service';
export * from '@app/_services/logging.service';
export * from '@app/_services/http-wrapper.service';
export * from '@app/_services/error-dialog.service';

View File

@ -1,10 +1,11 @@
import { Injectable } from '@angular/core';
import {environment} from '@src/environments/environment';
import {BehaviorSubject, Observable} from 'rxjs';
import {HttpGetter, Registry, TokenRegistry} from '@app/_helpers';
import {HttpGetter} from '@app/_helpers';
import {CICRegistry} from 'cic-client';
import Web3 from 'web3';
import {HttpWrapperService} from '@app/_services/http-wrapper.service';
import {Registry, TokenRegistry} from '@app/_eth';
@Injectable({
providedIn: 'root'

View File

@ -11,10 +11,10 @@ import {Tx} from '@src/assets/js/ethtx/dist';
import {toValue} from '@src/assets/js/ethtx/dist/tx';
import * as secp256k1 from 'secp256k1';
import {AuthService} from '@app/_services/auth.service';
import {Registry} from '@app/_helpers';
import {defaultAccount} from '@app/_models';
import {LoggingService} from '@app/_services/logging.service';
import {HttpWrapperService} from '@app/_services/http-wrapper.service';
import {Registry} from '@app/_eth';
const Web3 = require('web3');
const vCard = require('vcard-parser');
@ -45,22 +45,22 @@ export class TransactionService {
}
async setTransaction(transaction, cacheSize: number): Promise<void> {
if (this.transactions.find(cachedTx => cachedTx.tx.txHash === transaction.tx.txHash)) { return; }
transaction.value = Number(transaction.value);
transaction.type = 'transaction';
if (this.transactions.find(cachedTx => cachedTx.tx.txHash === transaction.tx.txHash)) { return; }
try {
this.userService.getAccountDetailsFromMeta(await User.toKey(transaction.from)).pipe(first()).subscribe(async (res) => {
this.userService.getAccountDetailsFromMeta(await User.toKey(transaction.from)).pipe(first()).subscribe((res) => {
transaction.sender = this.getAccountInfo(res.body);
}, error => {
transaction.sender = defaultAccount;
});
this.userService.getAccountDetailsFromMeta(await User.toKey(transaction.to)).pipe(first()).subscribe(async (res) => {
this.userService.getAccountDetailsFromMeta(await User.toKey(transaction.to)).pipe(first()).subscribe((res) => {
transaction.recipient = this.getAccountInfo(res.body);
}, error => {
transaction.recipient = defaultAccount;
});
} finally {
await this.addTransaction(transaction, cacheSize);
this.addTransaction(transaction, cacheSize);
}
}
@ -70,17 +70,17 @@ export class TransactionService {
conversion.fromValue = Number(conversion.fromValue);
conversion.toValue = Number(conversion.toValue);
try {
this.userService.getAccountDetailsFromMeta(await User.toKey(conversion.trader)).pipe(first()).subscribe(async (res) => {
this.userService.getAccountDetailsFromMeta(await User.toKey(conversion.trader)).pipe(first()).subscribe((res) => {
conversion.sender = conversion.recipient = this.getAccountInfo(res.body);
}, error => {
conversion.sender = conversion.recipient = defaultAccount;
});
} finally {
await this.addTransaction(conversion, cacheSize);
this.addTransaction(conversion, cacheSize);
}
}
async addTransaction(transaction, cacheSize: number): Promise<void> {
addTransaction(transaction, cacheSize: number): void {
this.transactions.unshift(transaction);
if (this.transactions.length > cacheSize) {
this.transactions.length = cacheSize;
@ -88,6 +88,11 @@ export class TransactionService {
this.transactionList.next(this.transactions);
}
resetTransactionsList(): void {
this.transactions = [];
this.transactionList.next(this.transactions);
}
getAccountInfo(account: string): any {
let accountInfo = Envelope.fromJSON(JSON.stringify(account)).unwrap().m.data;
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));

View File

@ -3,11 +3,13 @@ import {BehaviorSubject, Observable} from 'rxjs';
import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
import {environment} from '@src/environments/environment';
import {first} from 'rxjs/operators';
import {AccountIndex, MutableKeyStore, MutablePgpKeyStore, PGPSigner, Registry, Signer} from '@app/_helpers';
import {ArgPair, Envelope, Syncable, User} from 'cic-client-meta';
import {MetaResponse} from '@app/_models';
import {LoggingService} from '@app/_services/logging.service';
import {HttpWrapperService} from '@app/_services/http-wrapper.service';
import {TokenService} from '@app/_services/token.service';
import {AccountIndex, Registry} from '@app/_eth';
import {MutableKeyStore, MutablePgpKeyStore, PGPSigner, Signer} from '@app/_pgp';
const vCard = require('vcard-parser');
@Injectable({
@ -35,7 +37,8 @@ export class UserService {
constructor(
private http: HttpClient,
private httpWrapperService: HttpWrapperService,
private loggingService: LoggingService
private loggingService: LoggingService,
private tokenService: TokenService
) {
}
@ -145,7 +148,7 @@ export class UserService {
}
getAccountDetailsFromMeta(userKey: string): Observable<any> {
return this.httpWrapperService.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers });
return this.http.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers });
}
getUser(userKey: string): any {
@ -169,16 +172,18 @@ export class UserService {
});
}
async loadAccounts(limit: number, offset: number = 0): Promise<void> {
async loadAccounts(limit: number = 100, offset: number = 0): Promise<void> {
this.resetAccountsList();
const accountIndexAddress = await this.registry.addressOf('AccountRegistry');
const accountIndexQuery = new AccountIndex(accountIndexAddress);
const accountAddresses = await accountIndexQuery.last(await accountIndexQuery.totalAccounts());
this.loggingService.sendInfoLevelMessage(accountAddresses);
for (const accountAddress of accountAddresses.slice(offset, offset + limit)) {
this.getAccountDetailsFromMeta(await User.toKey(accountAddress)).pipe(first()).subscribe(res => {
this.getAccountDetailsFromMeta(await User.toKey(accountAddress)).pipe(first()).subscribe(async res => {
const account = Envelope.fromJSON(JSON.stringify(res)).unwrap();
this.accountsMeta.push(account);
const accountInfo = account.m.data;
accountInfo.balance = await this.tokenService.getTokenBalance(accountInfo.identities.evm['bloxberg:8996'][0]);
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
this.accounts.unshift(accountInfo);
if (this.accounts.length > limit) {

View File

@ -17,8 +17,8 @@
<mat-form-field appearance="outline" class="full-width">
<mat-label>Private Key</mat-label>
<textarea matInput style="height: 30rem" name="privateKeyAsc" id="privateKeyAsc" formControlName="key"
placeholder="Enter your private key..." [errorStateMatcher]="matcher"></textarea>
<textarea matInput style="height: 30rem" formControlName="key" placeholder="Enter your private key..."
[errorStateMatcher]="matcher"></textarea>
<div *ngIf="submitted && keyFormStub.key.errors" class="invalid-feedback">
<mat-error *ngIf="keyFormStub.key.errors.required">Private Key is required.</mat-error>
</div>
@ -34,23 +34,10 @@
<div id="two" style="display: none" class="card-body p-4 align-items-center">
<div class="text-center w-75 m-auto">
<h4 class="text-dark-50 text-center font-weight-bold">Enter Passphrase</h4>
<h4 id="state" class="text-dark-50 text-center font-weight-bold"></h4>
<button mat-raised-button matRipple color="primary" type="submit" (click)="login()"> Login </button>
</div>
<form [formGroup]="stateForm" (ngSubmit)="login()">
<mat-form-field appearance="outline" class="full-width">
<mat-label>Login State</mat-label>
<input matInput name="state" id="state" formControlName="state" readonly disabled>
</mat-form-field>
<div class="form-group mb-0 text-center">
<button mat-raised-button matRipple color="primary" type="submit">
Login
</button>
</div>
</form>
<div class="row mt-3">
<div class="col-12 text-center">
<p class="text-muted">Change private key? <a (click)="switchWindows()" class="text-muted ml-1"><b>Enter private key</b></a></p>

View File

@ -12,7 +12,6 @@ import {Router} from '@angular/router';
})
export class AuthComponent implements OnInit {
keyForm: FormGroup;
stateForm: FormGroup;
submitted: boolean = false;
loading: boolean = false;
matcher = new CustomErrorStateMatcher();
@ -27,14 +26,11 @@ export class AuthComponent implements OnInit {
this.keyForm = this.formBuilder.group({
key: ['', Validators.required],
});
this.stateForm = this.formBuilder.group({
state: '',
});
if (this.authService.privateKey !== undefined ) {
this.authService.setKey(this.authService.privateKey).then(r => {
if (this.authService.sessionToken !== undefined) {
this.authService.setState(
'click to perform login ' + this.authService.sessionLoginCount + ' with token ' + this.authService.sessionToken);
'Click button to perform login ' + this.authService.sessionLoginCount + ' with token ' + this.authService.sessionToken);
}
});
}
@ -54,6 +50,7 @@ export class AuthComponent implements OnInit {
login(): void {
const loginStatus = this.authService.login();
console.log(loginStatus);
if (loginStatus) {
this.router.navigate(['/home']);
}

View File

@ -3,9 +3,6 @@
<p>
Message: {{ data.message }}
</p>
<p>
Reason: {{ data.reason }}
</p>
<p>
Status: {{ data.status }}
</p>

View File

@ -9,6 +9,6 @@ import {MAT_DIALOG_DATA} from '@angular/material/dialog';
})
export class ErrorDialogComponent {
constructor(@Inject(MAT_DIALOG_DATA) public data: string) { }
constructor(@Inject(MAT_DIALOG_DATA) public data: any) { }
}

View File

@ -6,9 +6,9 @@ export const environment = {
level: NgxLoggerLevel.OFF,
serverLogLevel: NgxLoggerLevel.ERROR,
loggingUrl: 'http://localhost:8000',
cicAuthUrl: 'https://meta.dev.grassrootseconomics.net',
cicAuthUrl: 'https://meta.dev.grassrootseconomics.net:80',
cicMetaUrl: 'http://localhost:63380',
publicKeysUrl: 'http://localhost:8000',
publicKeysUrl: 'http://localhost:8000/keys.asc',
cicCacheUrl: 'http://localhost:63313',
cicScriptsUrl: 'http://localhost:9999',
web3Provider: 'ws://localhost:63546',

View File

@ -10,9 +10,9 @@ export const environment = {
level: NgxLoggerLevel.TRACE,
serverLogLevel: NgxLoggerLevel.ERROR,
loggingUrl: 'http://localhost:8000',
cicAuthUrl: 'https://meta.dev.grassrootseconomics.net',
cicAuthUrl: 'https://meta.dev.grassrootseconomics.net:80',
cicMetaUrl: 'http://localhost:63380',
publicKeysUrl: 'http://localhost:8000',
publicKeysUrl: 'http://localhost:8000/keys.asc',
cicCacheUrl: 'http://localhost:63313',
cicScriptsUrl: 'http://localhost:9999',
web3Provider: 'ws://localhost:63546',