Merge branch 'spencer/refactor-http-requests' into 'master'

Replace XMLHttpRequests with Fetch API.

See merge request grassrootseconomics/cic-staff-client!22
This commit is contained in:
Spencer Ofwiti 2021-05-17 06:19:03 +00:00
commit 51f4f052c7
11 changed files with 633 additions and 2395 deletions

View File

@ -32,14 +32,11 @@
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss",
"node_modules/datatables.net-dt/css/jquery.dataTables.css",
"node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"scripts": [
"node_modules/jquery/dist/jquery.js",
"node_modules/datatables.net/js/jquery.dataTables.js",
"node_modules/bootstrap/dist/js/bootstrap.js",
"node_modules/block-syncer/dist/worker_ondemand.js"
"node_modules/bootstrap/dist/js/bootstrap.js"
]
},
"configurations": {

2858
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -33,30 +33,19 @@
"@angular/platform-browser-dynamic": "~10.2.0",
"@angular/router": "~10.2.0",
"@angular/service-worker": "~10.2.0",
"@cicnet/schemas-data-validator": "*",
"@popperjs/core": "^2.5.4",
"angular-datatables": "^9.0.2",
"block-syncer": "^0.2.4",
"bootstrap": "^4.5.3",
"chart.js": "^2.9.4",
"cic-client": "0.1.4",
"cic-client-meta": "0.0.7-alpha.6",
"cic-schemas-data-validator": "^1.0.0-alpha.3",
"datatables.net": "^1.10.22",
"datatables.net-dt": "^1.10.22",
"ethers": "^5.0.31",
"http-server": "^0.12.3",
"jquery": "^3.5.1",
"mocha": "^8.2.1",
"moolb": "^0.1.0",
"ng2-charts": "^2.4.2",
"ngx-logger": "^4.2.1",
"openpgp": "^4.10.10",
"popper.js": "^1.16.1",
"rxjs": "~6.6.0",
"sha3": "^2.1.4",
"tslib": "^2.0.0",
"vcard-parser": "^1.0.0",
"vcards-js": "^2.10.0",
"web3": "^1.3.0",
"zone.js": "~0.10.2"
},

View File

@ -87,3 +87,10 @@ export class GlobalErrorHandler extends ErrorHandler {
return isWarning;
}
}
export function rejectBody(error): { status: any; statusText: any } {
return {
status: error.status,
statusText: error.statusText,
};
}

View File

@ -1,17 +1,17 @@
import { rejectBody } from '@app/_helpers/global-error-handler';
function HttpGetter(): void {}
HttpGetter.prototype.get = (filename) =>
new Promise((resolve, reject) => {
const xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.addEventListener('load', (e) => {
if (xhr.status === 200) {
resolve(xhr.responseText);
return;
fetch(filename).then((response) => {
if (response.ok) {
resolve(response.json());
} else {
reject(rejectBody(response));
}
reject('failed with status ' + xhr.status + ': ' + xhr.statusText);
return;
});
xhr.open('GET', filename);
xhr.send();
});
export { HttpGetter };

View File

@ -1,4 +1,4 @@
import { validatePerson, validateVcard } from 'cic-schemas-data-validator';
import { validatePerson, validateVcard } from '@cicnet/schemas-data-validator';
async function personValidation(person: any): Promise<void> {
const personValidationErrors: any = await validatePerson(person);

View File

@ -6,14 +6,13 @@ import { LoggingService } from '@app/_services/logging.service';
import { MutableKeyStore, MutablePgpKeyStore } from '@app/_pgp';
import { ErrorDialogService } from '@app/_services/error-dialog.service';
import { HttpClient } from '@angular/common/http';
import { HttpError } from '@app/_helpers/global-error-handler';
import { HttpError, rejectBody } from '@app/_helpers/global-error-handler';
@Injectable({
providedIn: 'root',
})
export class AuthService {
sessionToken: any;
sessionLoginCount: number = 0;
mutableKeyStore: MutableKeyStore;
constructor(
@ -39,73 +38,75 @@ export class AuthService {
document.getElementById('state').innerHTML = s;
}
getWithToken(): void {
const xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.responseType = 'text';
xhr.open('GET', environment.cicMetaUrl + window.location.search.substring(1));
xhr.setRequestHeader('Authorization', 'Bearer ' + this.sessionToken);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('x-cic-automerge', 'none');
xhr.addEventListener('load', (e) => {
if (xhr.status === 401) {
throw new Error('login rejected');
}
this.sessionLoginCount++;
this.setState('Click button to log in');
return;
getWithToken(): Promise<boolean> {
return new Promise((resolve, reject) => {
const headers = {
Authorization: 'Bearer ' + this.sessionToken,
'Content-Type': 'application/json;charset=utf-8',
'x-cic-automerge': 'none',
};
const options = {
headers,
};
fetch(environment.cicMetaUrl, options).then((response) => {
if (response.status === 401) {
return reject(rejectBody(response));
}
return resolve(true);
});
});
xhr.send();
}
// TODO rename to send signed challenge and set session. Also separate these responsibilities
sendResponse(hobaResponseEncoded: any): Promise<boolean> {
return new Promise((resolve, reject) => {
const xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.responseType = 'text';
xhr.open('GET', environment.cicMetaUrl + window.location.search.substring(1));
xhr.setRequestHeader('Authorization', 'HOBA ' + hobaResponseEncoded);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('x-cic-automerge', 'none');
xhr.addEventListener('load', (e) => {
if (xhr.status !== 200) {
const error = new HttpError(xhr.statusText, xhr.status);
return reject(error);
const headers = {
Authorization: 'HOBA ' + hobaResponseEncoded,
'Content-Type': 'application/json;charset=utf-8',
'x-cic-automerge': 'none',
};
const options = {
headers,
};
fetch(environment.cicMetaUrl, options).then((response) => {
if (response.status === 401) {
return reject(rejectBody(response));
}
this.sessionToken = xhr.getResponseHeader('Token');
this.sessionToken = response.headers.get('Token');
sessionStorage.setItem(btoa('CICADA_SESSION_TOKEN'), this.sessionToken);
this.sessionLoginCount++;
this.setState('Click button to log in');
return resolve(true);
});
xhr.send();
});
}
getChallenge(): void {
const xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.open('GET', environment.cicMetaUrl + window.location.search.substring(1));
xhr.onload = async (e) => {
if (xhr.status === 401) {
const authHeader = xhr.getResponseHeader('WWW-Authenticate');
const o = hobaParseChallengeHeader(authHeader);
this.loginResponse(o);
}
};
xhr.send();
getChallenge(): Promise<any> {
return new Promise((resolve, reject) => {
fetch(environment.cicMetaUrl).then(async (response) => {
if (response.status === 401) {
const authHeader: string = response.headers.get('WWW-Authenticate');
return resolve(hobaParseChallengeHeader(authHeader));
}
if (!response.ok) {
return reject(rejectBody(response));
}
});
});
}
login(): boolean {
async login(): Promise<boolean> {
if (this.sessionToken !== undefined) {
try {
this.getWithToken();
return true;
const response: boolean = await this.getWithToken();
return response === true;
} catch (e) {
this.loggingService.sendErrorLevelMessage('Login token failed', this, { error: e });
}
} else {
try {
this.getChallenge();
const o = await this.getChallenge();
const response: boolean = await this.loginResponse(o);
return response === true;
} catch (e) {
this.loggingService.sendErrorLevelMessage('Login challenge failed', this, { error: e });
}
@ -122,15 +123,15 @@ export class AuthService {
environment.cicMetaUrl,
this.mutableKeyStore
);
const sessionTokenResult: boolean = await this.sendResponse(r);
const response: boolean = await this.sendResponse(r);
resolve(response);
} catch (error) {
if (error instanceof HttpError) {
if (error.status === 403) {
this.errorDialogService.openDialog({
message: 'You are not authorized to use this system',
});
}
if (error.status === 401) {
} else if (error.status === 401) {
this.errorDialogService.openDialog({
message:
'Unable to authenticate with the service. ' +
@ -139,9 +140,10 @@ export class AuthService {
'staff@grassrootseconomics.net.',
});
}
} else {
// TODO define this error
this.errorDialogService.openDialog({ message: 'Incorrect key passphrase.' });
}
// TODO define this error
this.errorDialogService.openDialog({ message: 'Incorrect key passphrase.' });
resolve(false);
}
});
@ -183,6 +185,7 @@ export class AuthService {
logout(): void {
sessionStorage.removeItem(btoa('CICADA_SESSION_TOKEN'));
localStorage.removeItem(btoa('CICADA_PRIVATE_KEY'));
this.sessionToken = undefined;
window.location.reload();
}
@ -194,12 +197,14 @@ export class AuthService {
}
async getPublicKeys(): Promise<any> {
return await fetch(environment.publicKeysUrl).then((res) => {
if (!res.ok) {
// TODO does angular recommend an error interface?
throw Error(`${res.statusText} - ${res.status}`);
}
return res.text();
return new Promise((resolve, reject) => {
fetch(environment.publicKeysUrl).then((res) => {
if (!res.ok) {
// TODO does angular recommend an error interface?
return reject(rejectBody(res));
}
return resolve(res.text());
});
});
}

View File

@ -6,7 +6,6 @@ import { AppComponent } from '@app/app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { GlobalErrorHandler, MockBackendProvider } from '@app/_helpers';
import { DataTablesModule } from 'angular-datatables';
import { SharedModule } from '@app/shared/shared.module';
import { MatTableModule } from '@angular/material/table';
import { AuthGuard } from '@app/_guards';
@ -23,7 +22,6 @@ import { ServiceWorkerModule } from '@angular/service-worker';
AppRoutingModule,
BrowserAnimationsModule,
HttpClientModule,
DataTablesModule,
SharedModule,
MatTableModule,
LoggerModule.forRoot({

View File

@ -5,7 +5,6 @@ import { AccountsRoutingModule } from '@pages/accounts/accounts-routing.module';
import { AccountsComponent } from '@pages/accounts/accounts.component';
import { SharedModule } from '@app/shared/shared.module';
import { AccountDetailsComponent } from '@pages/accounts/account-details/account-details.component';
import { DataTablesModule } from 'angular-datatables';
import { CreateAccountComponent } from '@pages/accounts/create-account/create-account.component';
import { MatTableModule } from '@angular/material/table';
import { MatSortModule } from '@angular/material/sort';
@ -36,7 +35,6 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
CommonModule,
AccountsRoutingModule,
SharedModule,
DataTablesModule,
MatTableModule,
MatSortModule,
MatCheckboxModule,

View File

@ -4,7 +4,6 @@ import { CommonModule } from '@angular/common';
import { PagesRoutingModule } from '@pages/pages-routing.module';
import { PagesComponent } from '@pages/pages.component';
import { SharedModule } from '@app/shared/shared.module';
import { ChartsModule } from 'ng2-charts';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
@ -17,7 +16,6 @@ import { MatCardModule } from '@angular/material/card';
CommonModule,
PagesRoutingModule,
SharedModule,
ChartsModule,
MatButtonModule,
MatFormFieldModule,
MatSelectModule,

View File

@ -4,7 +4,6 @@ import { CommonModule } from '@angular/common';
import { TransactionsRoutingModule } from '@pages/transactions/transactions-routing.module';
import { TransactionsComponent } from '@pages/transactions/transactions.component';
import { TransactionDetailsComponent } from '@pages/transactions/transaction-details/transaction-details.component';
import { DataTablesModule } from 'angular-datatables';
import { SharedModule } from '@app/shared/shared.module';
import { MatTableModule } from '@angular/material/table';
import { MatCheckboxModule } from '@angular/material/checkbox';
@ -25,7 +24,6 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
imports: [
CommonModule,
TransactionsRoutingModule,
DataTablesModule,
SharedModule,
MatTableModule,
MatCheckboxModule,