Comment all the things and remove unnecessary files

This commit is contained in:
Garrett Mills 2021-04-10 07:26:42 -05:00
parent 6ba038e4bf
commit f0570a6101
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246
16 changed files with 88 additions and 43 deletions

View File

@ -12,10 +12,15 @@ export interface FirebaseResourceItem {
seqID: number; seqID: number;
} }
/**
* An asynchronous iterable wrapper that enables us to use AsyncCollection
* to interact with the Firebase realtime database.
*/
@Injectable() @Injectable()
export class FirebaseResource<T extends FirebaseResourceItem> extends Iterable<T> { export class FirebaseResource<T extends FirebaseResourceItem> extends Iterable<T> {
protected refName!: RTDBRef protected refName!: RTDBRef
/** Get the Reference for this resource. */
ref(): firebase.database.Reference { ref(): firebase.database.Reference {
return Application.getApplication().make<FirebaseUnit>(FirebaseUnit).ref(this.refName) return Application.getApplication().make<FirebaseUnit>(FirebaseUnit).ref(this.refName)
} }

View File

@ -6,21 +6,30 @@ export default {
gpg: { gpg: {
key: { key: {
// Contents of the SERVER's GPG public key, armored.
public: fs.readFileSync(env('GPG_KEY_PUB')).toString('utf-8'), public: fs.readFileSync(env('GPG_KEY_PUB')).toString('utf-8'),
// Contents of the SERVER's GPG private key, armored.
private: fs.readFileSync(env('GPG_KEY_PRIV')).toString('utf-8'), private: fs.readFileSync(env('GPG_KEY_PRIV')).toString('utf-8'),
}, },
}, },
firebase: { firebase: {
// Contents of the Firebase service account credentials file.
credentials: JSON.parse( credentials: JSON.parse(
fs.readFileSync(env('FIREBASE_CREDENTIALS')) fs.readFileSync(env('FIREBASE_CREDENTIALS'))
.toString('utf-8') .toString('utf-8')
), ),
// Name of the HTTP header to check for the firebase auth token
api_auth_header: env('FIREBASE_API_AUTH_HEADER', 'X-Auth-Token'), api_auth_header: env('FIREBASE_API_AUTH_HEADER', 'X-Auth-Token'),
rtdb: { rtdb: {
// URL of the realtime-database this app should use
default: env('FIREBASE_DEFAULT_RTDB', 'https://loc-chain-default-rtdb.firebaseio.com'), default: env('FIREBASE_DEFAULT_RTDB', 'https://loc-chain-default-rtdb.firebaseio.com'),
// Mapping of ref-shortname to actual database reference
// If you add a value here, also add it to the RTDBRef type alias
refs: { refs: {
peers: 'chain/server/peers', peers: 'chain/server/peers',
transaction: 'chain/pending/transactions', transaction: 'chain/pending/transactions',

View File

@ -1,5 +0,0 @@
import { env } from "@extollo/lib"
export default {
app_name: env('APP_NAME', 'Extollo'),
}

View File

@ -1,6 +0,0 @@
import {env} from '@extollo/lib'
export default {
welcome_to_extollo: 'Welcome to Extollo!',
viewed_page_num_times: 'You have viewed this page :num: times.',
}

View File

@ -37,7 +37,8 @@ export default {
middleware: { middleware: {
global: { global: {
pre: ['LogRequest'], pre: [],
post: [],
}, },
}, },
} }

View File

@ -1,5 +1,5 @@
import {Controller} from "@extollo/lib" import {Controller} from "@extollo/lib"
import {Inject, Injectable} from "@extollo/di" import {Injectable} from "@extollo/di"
import {TransactionResource, TransactionResourceItem} from "../../../rtdb/TransactionResource" import {TransactionResource, TransactionResourceItem} from "../../../rtdb/TransactionResource"
import {one} from "@extollo/util" import {one} from "@extollo/util"
@ -10,7 +10,9 @@ import {one} from "@extollo/util"
*/ */
@Injectable() @Injectable()
export class Blockchain extends Controller { export class Blockchain extends Controller {
/**
* Post a new transaction to the blockchain. This is only intended for testing.
*/
public async postTransaction() { public async postTransaction() {
const item: TransactionResourceItem = { const item: TransactionResourceItem = {
firebaseID: '', firebaseID: '',
@ -25,5 +27,4 @@ export class Blockchain extends Controller {
await (<TransactionResource> this.make(TransactionResource)).push(item) await (<TransactionResource> this.make(TransactionResource)).push(item)
return one(item) return one(item)
} }
} }

View File

@ -0,0 +1,20 @@
import {Config, error, Middleware} from "@extollo/lib"
import {Inject, Injectable} from "@extollo/di"
import {HTTPStatus} from "@extollo/util"
/**
* DebugOnly Middleware
* --------------------------------------------
* Only allows the request to proceed if the app is in debug mode.
*/
@Injectable()
export class DebugOnly extends Middleware {
@Inject()
protected readonly config!: Config
public async apply() {
if ( !this.config.get('server.debug', false) ) {
return error('Not found.', HTTPStatus.NOT_FOUND)
}
}
}

View File

@ -1,12 +0,0 @@
import {json, Logging, Middleware} from "@extollo/lib";
import {Inject, Injectable} from "@extollo/di";
@Injectable()
export class LogRequest extends Middleware {
@Inject()
protected readonly logging!: Logging
public async apply() {
this.logging.info(`Incoming request: ${this.request.method} @ ${this.request.path}`)
}
}

View File

@ -16,16 +16,19 @@ export class FirebaseUserOnly extends Middleware {
@Inject() @Inject()
protected readonly config!: Config protected readonly config!: Config
/** Get the name of the API token header. */
get headerName(): string { get headerName(): string {
return String(this.config.get('app.firebase.api_auth_header')) return String(this.config.get('app.firebase.api_auth_header'))
} }
/** Get the API token from the request header. */
getAuthHeader(): string { getAuthHeader(): string {
const tokens = this.request.getHeader(this.headerName) const tokens = this.request.getHeader(this.headerName)
if ( Array.isArray(tokens) ) return tokens[0] if ( Array.isArray(tokens) ) return tokens[0]
return String(tokens) return String(tokens)
} }
/** Check if the request contains a valid Firebase API token. */
public async apply() { public async apply() {
const token = this.getAuthHeader() const token = this.getAuthHeader()

View File

@ -5,7 +5,8 @@ import {HTTPStatus} from "@extollo/util";
/** /**
* ValidateEncounterTransaction Middleware * ValidateEncounterTransaction Middleware
* -------------------------------------------- * --------------------------------------------
* Put some description here. * Errors out the request if it is missing any fields required to create
* a new encounter transaction on the blockchain.
*/ */
@Injectable() @Injectable()
export class ValidateEncounterTransaction extends Middleware { export class ValidateEncounterTransaction extends Middleware {

View File

@ -2,5 +2,6 @@ import {Route} from "@extollo/lib"
Route.group('/api/v1', () => { Route.group('/api/v1', () => {
Route.post('/encounter', 'api:Blockchain.postTransaction') Route.post('/encounter', 'api:Blockchain.postTransaction')
.pre('DebugOnly')
.pre('api:ValidateEncounterTransaction') .pre('api:ValidateEncounterTransaction')
}) })

View File

@ -1,6 +1,5 @@
html html
head head
title !{locale('app_name')} title loc-chain
body body
h1 !{locale('welcome_to_extollo')} h1 Welcome to Loc-Chain
h2 !{locale('viewed_page_num_times', { interp: { num: app_visits } })}

View File

@ -23,6 +23,7 @@ export interface BlockInfectionTransaction {
/** Union type of all possible block transactions. */ /** Union type of all possible block transactions. */
export type BlockTransaction = BlockInfectionTransaction | BlockEncounterTransaction export type BlockTransaction = BlockInfectionTransaction | BlockEncounterTransaction
/** Returns true if the item is a valid BlockEncounterTransaction. */
export function isBlockEncounterTransaction(what: any): what is BlockEncounterTransaction { export function isBlockEncounterTransaction(what: any): what is BlockEncounterTransaction {
return ( return (
what what
@ -32,6 +33,7 @@ export function isBlockEncounterTransaction(what: any): what is BlockEncounterTr
) )
} }
/** Returns true if the item is a valid BlockInfectionTransaction. */
export function isBlockInfectionTransaction(what: any): what is BlockInfectionTransaction { export function isBlockInfectionTransaction(what: any): what is BlockInfectionTransaction {
return ( return (
what what
@ -40,6 +42,7 @@ export function isBlockInfectionTransaction(what: any): what is BlockInfectionTr
) )
} }
/** Returns true if the item is a valid BlockTransaction. */
export function isBlockTransaction(what: any): what is BlockTransaction { export function isBlockTransaction(what: any): what is BlockTransaction {
return isBlockEncounterTransaction(what) || isBlockInfectionTransaction(what) return isBlockEncounterTransaction(what) || isBlockInfectionTransaction(what)
} }
@ -48,14 +51,17 @@ export function isBlockTransaction(what: any): what is BlockTransaction {
* Interface representing a single block in the chain. * Interface representing a single block in the chain.
*/ */
export interface BlockResourceItem extends FirebaseResourceItem { export interface BlockResourceItem extends FirebaseResourceItem {
uuid: string; uuid: string; // Globally unique ID
transactions: BlockTransaction[]; transactions: BlockTransaction[]; // Transactions validated by this block
lastBlockHash: string; lastBlockHash: string; // The combined sha256 hash of the previous block
lastBlockUUID: string; lastBlockUUID: string; // the UUID of the previous block
proof: string; proof: string; // the generated proof-of-work string
timestamp: number; timestamp: number; // millisecond unix timestamp when this block was created
} }
/**
* A Firebase realtime database resource for blocks in the chain.
*/
@Injectable() @Injectable()
export class BlockResource extends FirebaseResource<BlockResourceItem> { export class BlockResource extends FirebaseResource<BlockResourceItem> {
public static collect(): AsyncCollection<BlockResourceItem> { public static collect(): AsyncCollection<BlockResourceItem> {

View File

@ -7,13 +7,16 @@ import {AsyncCollection} from "@extollo/util"
* Interface representing a client-submitted encounter transaction. * Interface representing a client-submitted encounter transaction.
*/ */
export interface TransactionResourceItem extends FirebaseResourceItem { export interface TransactionResourceItem extends FirebaseResourceItem {
combinedHash: string; combinedHash: string; // The salted and hashed combination of the client IDs
timestamp: number; timestamp: number; // the unix-time in milliseconds when the interaction occurred
encodedGPSLocation: string; encodedGPSLocation: string; // Encoded GPS location data
partnerPublicKey: string; partnerPublicKey: string; // The public key of the other client
validationSignature: string; validationSignature: string; // The transaction validation data
} }
/**
* A Firebase realtime-database resource for managing encounter transactions.
*/
@Injectable() @Injectable()
export class TransactionResource extends FirebaseResource<TransactionResourceItem> { export class TransactionResource extends FirebaseResource<TransactionResourceItem> {
public static collect(): AsyncCollection<TransactionResourceItem> { public static collect(): AsyncCollection<TransactionResourceItem> {

View File

@ -19,9 +19,11 @@ export class Block implements BlockResourceItem {
lastBlockHash: string lastBlockHash: string
lastBlockUUID: string lastBlockUUID: string
proof: string proof: string
get config(): Config { get config(): Config {
return Application.getApplication().make(Config) return Application.getApplication().make(Config)
} }
constructor(rec: BlockResourceItem) { constructor(rec: BlockResourceItem) {
this.firebaseID = rec.firebaseID this.firebaseID = rec.firebaseID
this.seqID = rec.seqID this.seqID = rec.seqID

View File

@ -8,10 +8,13 @@ import { Blockchain } from "../Blockchain"
/** /**
* Transaction Unit * Transaction Unit
* --------------------------------------- * ---------------------------------------
* Put some description here. * This unit listens for transactions created on the realtime database.
* When new ones come through, it matches them up, validates them, and pushes
* them onto this server's blockchain.
*/ */
@Singleton() @Singleton()
export class Transaction extends Unit { export class Transaction extends Unit {
/** True if currently processing transactions. */
private processing: boolean = false private processing: boolean = false
@Inject() @Inject()
@ -23,6 +26,7 @@ export class Transaction extends Unit {
@Inject() @Inject()
protected readonly logging!: Logging protected readonly logging!: Logging
/** Claim the right to process transactions. Returns true if the right was granted. */
claim() { claim() {
if ( !this.processing ) { if ( !this.processing ) {
this.processing = true this.processing = true
@ -32,10 +36,17 @@ export class Transaction extends Unit {
return false return false
} }
/** Release the right to claim transactions. */
release() { release() {
this.processing = false this.processing = false
} }
/**
* Given two transactions, determine whether the came from a valid interaction.
* That is, do the two transactions vouch for each-other cryptographically.
* @param transaction1
* @param transaction2
*/
public async compareTransactions(transaction1: TransactionResourceItem, transaction2: TransactionResourceItem) { public async compareTransactions(transaction1: TransactionResourceItem, transaction2: TransactionResourceItem) {
// verify signature // verify signature
const result1 = await openpgp.verify({ const result1 = await openpgp.verify({
@ -59,6 +70,9 @@ export class Transaction extends Unit {
return (await result1.signatures[0].verified) && (await result2.signatures[0].verified) return (await result1.signatures[0].verified) && (await result2.signatures[0].verified)
} }
/**
* Subscribe to the transactions reference and wait for new transactions to be added.
*/
public async up() { public async up() {
this.firebase.ref("transaction").on("child_added", async () => { this.firebase.ref("transaction").on("child_added", async () => {
this.logging.debug('Received child_added event for transactions reference.') this.logging.debug('Received child_added event for transactions reference.')
@ -110,6 +124,9 @@ export class Transaction extends Unit {
}) })
} }
/**
* Release listeners and resources before shutdown.
*/
public async down() { public async down() {
// Release all subscriptions before shutdown // Release all subscriptions before shutdown
this.firebase.ref("transaction").off() this.firebase.ref("transaction").off()