diff --git a/backend/app.js b/backend/app.js index dd4b096..e51a2cd 100644 --- a/backend/app.js +++ b/backend/app.js @@ -2,6 +2,7 @@ const express = require('express'); const app = express(); const bodyparser = require('body-parser'); const cors = require('cors'); +const crypto = require('crypto'); const postmodel = require('./models/post'); const usermodel = require('./models/user'); const ownermodel = require('./models/owner'); @@ -12,6 +13,8 @@ const txmodel = require('./models/tx'); const mongoose = require('mongoose'); const stdrpc = require('stdrpc'); const CoinGecko = require('coingecko-api'); +var URLSafeBase64 = require('urlsafe-base64'); +var Buffer = require('buffer/').Buffer; var db = require('./config/db'); mongoose.connect('mongodb://'+db.user+':'+db.password+'@'+db.server+'/'+db.database).then(() => { @@ -74,16 +77,24 @@ function hexToString(hexString) { return str; } +function sendPin(pin, address) { + //var memo = URLSafeBase64.encode(Buffer.from('ZGO pin: '.concat(pin))); + var memo = Buffer.from('ZGO pin: '.concat(pin)).toString('hex'); + //console.log(typeof(memo)); + var amounts = [ + { + address: address, + amount: 0.00000001, + memo: memo + } + ]; + rpc.z_sendmany(fullnode.addr, amounts).catch((err) => { + console.log('Sendmany', err); + }); +} + var blockInterval = setInterval( function() { console.log('Node periodic Zcash scan'); - //usermodel.find({}, function (err, docs) { - //if (err) { - //console.log(err); - //} else { - //console.log(session, blocktime); - //console.log(docs); - //} - //}); rpc.z_listreceivedbyaddress(fullnode.addr, 1).then(txs => { var re = /.*ZGO::(.*)\sReply-To:\s(z\w+)/; async.each (txs, function(txData, callback) { @@ -95,17 +106,31 @@ var blockInterval = setInterval( function() { var address = match[2]; var session = match[1]; var blocktime = txData.blocktime; + var amount = txData.amount; + var expiration = blocktime; //console.log(' ', session, blocktime); if (txData.confirmations >= 10 ) { usermodel.findOne({address: address, session: session, blocktime: blocktime}).then(function(doc){ if (doc != null) { console.log('Found user'); } else { - console.log('User not found', session, blocktime); + console.log('User not found', session, blocktime, amount); + if (amount >= 0.001 && amount < 0.005){ + expiration = blocktime + 3600; + } else if (amount >= 0.005){ + expiration = blocktime + 24*3600; + } + console.log('exp', expiration); + const n = crypto.randomInt(0, 10000000); + const pin = n.toString().padStart(6, '0'); + sendPin(pin, address); var user = new usermodel({ address: address, session: session, - blocktime: blocktime + blocktime: blocktime, + expiration: expiration, + pin: pin, + validated: false }); user.save(function(error) { if (error) { @@ -181,6 +206,10 @@ app.use((req, res, next) => { }); +app.get('/api/test', (req, res, next) => { + sendPin('12345678', 'zs1w6nkameazc5gujm69350syl5w8tgvyaphums3pw8eytzy5ym08x7dvskmykkatmwrucmgv3er8e'); + res.status(200).send('Endpoint triggered'); +}); app.get('/api/users', (req, res, next) => { console.log('Get: /api/users'); @@ -223,8 +252,7 @@ app.get('/api/pending', (req, res, next) => { app.get('/api/getuser', (req, res, next) => { console.log('Get: /api/getuser/', req.query.session); var today = new Date().getTime() / 1000; - var expiration = today - (24*3600); - usermodel.find({'session': req.query.session, 'blocktime': { $gt: expiration }}). + usermodel.find({'session': req.query.session, 'expiration': { $gt: today }}). then((documents) => { if(documents.length > 0){ //console.log(documents); @@ -253,8 +281,6 @@ app.get('/api/blockheight', (req, res, next) => { }); }); - - app.get('/api/txs', (req, res, next) => { console.log('Get: /api/txs'); rpc.z_listreceivedbyaddress(fullnode.addr, 10).then(txs => { @@ -301,6 +327,21 @@ app.post('/api/addowner', (req, res, next) => { }); }); +app.post('/api/validateuser', (req, res, next) => { + console.log('Post: /api/validateuser'); + usermodel.findByIdAndUpdate(req.body.user._id, req.body.user, + function(err, docs) { + if (err) { + console.log(err); + } else { + res.status(201).json({ + message: 'User Validated', + user: docs + }); + } + }); +}); + app.post('/api/updateowner', (req, res, next) => { console.log('Post: /api/updateowner'); ownermodel.findByIdAndUpdate(req.body.owner._id, req.body.owner, diff --git a/backend/models/user.js b/backend/models/user.js index a40171e..83ae866 100644 --- a/backend/models/user.js +++ b/backend/models/user.js @@ -3,7 +3,10 @@ const mongoose = require('mongoose'); const userSchema = mongoose.Schema({ address: {type: String, required:true}, session: {type: String, required:true}, - blocktime: {type: Number, required:true} + blocktime: {type: Number, required:true}, + expiration: {type: Number, required:true, default:0}, + pin: {type: String, required:true}, + validated: {type: Boolean, required:true} }); module.exports = mongoose.model('User', userSchema); diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index cff4598..464931c 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -11,7 +11,7 @@ const routes: Routes = [ //{ path: 'create', component: PostCreateComponent, canActivate: [AuthGuardService]}, { path: 'shop', component: ViewerComponent, canActivate: [AuthGuardService]}, { path: 'orders', component: ListOrdersComponent, canActivate: [AuthGuardService]}, - { path: 'login', component: LoginComponent} + { path: 'login', component: LoginComponent, resolve: { response: NodeResolverService}} ]; @NgModule({ diff --git a/src/app/app.component.css b/src/app/app.component.css index 150f53d..435af29 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -1,5 +1,3 @@ main{ - margin-top: 16px; - width: 80%; margin: auto; } diff --git a/src/app/checkout/checkout.component.html b/src/app/checkout/checkout.component.html index 7034aea..3458441 100644 --- a/src/app/checkout/checkout.component.html +++ b/src/app/checkout/checkout.component.html @@ -1,5 +1,5 @@
-

Scan to make payment

+

Scan to make payment

diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html index 6ea406e..5f2346e 100644 --- a/src/app/header/header.component.html +++ b/src/app/header/header.component.html @@ -1,14 +1,10 @@ - - Z Go! - -

Last block seen: {{heightUpdate | async}}

+
- +

Last block:

+

{{heightUpdate | async}}

diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts index 7d828cf..4e88167 100644 --- a/src/app/header/header.component.ts +++ b/src/app/header/header.component.ts @@ -3,7 +3,6 @@ import { MatDialog, MatDialogConfig} from '@angular/material/dialog'; import {FullnodeService} from '../fullnode.service'; import { UserService } from '../user.service'; import {Subscription, Observable} from 'rxjs'; -import { SettingsComponent } from '../settings/settings.component'; import {Owner} from '../owner.model'; @@ -42,28 +41,6 @@ export class HeaderComponent implements OnInit, OnDestroy { ngOnDestroy(){ } - shortenZaddr(address:string) { - var addr = address; - var end = addr.length; - var last = end - 5; - return addr.substring(0,5).concat('...').concat(addr.substring(last, end)); - } - openSettings() { - - const dialogConfig = new MatDialogConfig(); - - dialogConfig.disableClose = true; - dialogConfig.autoFocus = true; - dialogConfig.data = this.owner; - - const dialogRef = this.dialog.open(SettingsComponent, dialogConfig); - dialogRef.afterClosed().subscribe((val) => { - if (val != null) { - console.log('Saving settings'); - this.userService.updateOwner(val); - } - }); - } } diff --git a/src/app/listorders/listorders.component.css b/src/app/listorders/listorders.component.css index 121ab70..a9d0799 100644 --- a/src/app/listorders/listorders.component.css +++ b/src/app/listorders/listorders.component.css @@ -20,3 +20,6 @@ img.icon{ .total{ font-size: large; } +img.total{ + margin-bottom:-2px; +} diff --git a/src/app/listorders/listorders.component.html b/src/app/listorders/listorders.component.html index f6fa94a..e14b581 100644 --- a/src/app/listorders/listorders.component.html +++ b/src/app/listorders/listorders.component.html @@ -1,6 +1,6 @@
-

{{(ownerUpdate | async)!.name}}

+

{{(ownerUpdate | async)!.name}}

@@ -11,11 +11,11 @@

Today's Total:

-

{{todayTotal}}

+

{{todayTotal | number: '1.0-6'}}

Overall Total:

-

{{total}}

+

{{total | number: '1.0-6'}}

@@ -25,13 +25,14 @@ - {{order.totalZec}} (@{{order.price | currency: 'USD'}}) + {{order.totalZec}} - {{order.timestamp | date: 'medium'}} + {{order.timestamp | date: 'short'}}

Order: {{order._id}}

+

Zcash price: {{order.price | currency: 'USD'}}

{{item.qty}} x {{item.name}} diff --git a/src/app/login/login.component.css b/src/app/login/login.component.css index 0177792..1e0275a 100644 --- a/src/app/login/login.component.css +++ b/src/app/login/login.component.css @@ -4,6 +4,7 @@ mat-card.coolcard{ background-color: #FF5722; color: #FFFFFF; + margin: 5px; } .icon{ font-family: 'Material Icons'; @@ -14,3 +15,7 @@ mat-card.coolcard{ font-size: 120%; padding: 3px; } +.alert-success{ + margin: 5px; + background-color: #BBFFBB +} diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html index d571d2d..222274e 100644 --- a/src/app/login/login.component.html +++ b/src/app/login/login.component.html @@ -1,61 +1,62 @@
-

-
- __||__   _____       _ 
-|___  /  / ____|     | |
-   / /  | |  __  ___ | |
-  / /   | | |_ |/ _ \| |
- / /__  | |__| | (_) |_|
-/__  _|  \_____|\___/(_)
-   ||                   
-		
-

+ + + + + + + + + + + +

Last block seen: {{ heightUpdate | async }}

- - - - - - - - - -
- -

A non-custodial point-of-sale application, powered by Zcash!

-
    -
  • Your Zcash shielded address is your login.
  • -
  • Your customer pays directly to your wallet.
  • -
-
-
- -

Login received!

- - It has {{tx.confirmations}} confirmations, needs 10. - -
- -
- - Session length - - - {{ticket.viewValue}} - - - - - - -
-
-
+ +

A non-custodial point-of-sale application, powered by Zcash!

+

Your Zcash shielded address is your login.

+

Your customer pays directly to your wallet.

+
+ +

+ Check your wallet +

+ + PIN + + + + + +
+ +

Login received!

+ + It needs {{10 - tx.confirmations}} more confirmations. + +
+ +
+ + Session length + + + {{ticket.viewValue}} + + + + + + +
+
diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts index 274d2e9..b54edf0 100644 --- a/src/app/login/login.component.ts +++ b/src/app/login/login.component.ts @@ -6,6 +6,7 @@ import { UserService } from '../user.service'; import { FullnodeService } from '../fullnode.service'; import { ScanComponent} from '../scan/scan.component'; import { Tx } from '../tx.model'; +import {User} from '../user.model'; import { Subscription, Observable } from 'rxjs'; import { v4 as uuidv4 } from 'uuid'; var QRCode = require('easyqrcodejs'); @@ -25,10 +26,19 @@ export class LoginComponent implements OnInit { nodeAddress: string = ''; localToken: string | null = ''; selectedValue: number = 0.001; + public user:User = { + address: '', + session: '', + blocktime: 0, + expiration: 0, + pin: '', + validated: false + }; private FullnodeSub: Subscription = new Subscription(); private UserSub: Subscription = new Subscription(); public heightUpdate: Observable; public uZaddrUpdate: Observable; + public userUpdate:Observable; public txsUpdate: Observable; tickets = [ { @@ -39,8 +49,10 @@ export class LoginComponent implements OnInit { viewValue: 'One day' } ]; + prompt: boolean = false; entryForm: FormGroup; + pinForm: FormGroup; constructor( private fb: FormBuilder, @@ -55,8 +67,15 @@ export class LoginComponent implements OnInit { this.entryForm = fb.group({ selectedSession: [0.001, Validators.required] }); + this.pinForm = fb.group({ + pinValue: [null, Validators.required] + }); this.heightUpdate = fullnodeService.heightUpdate; this.uZaddrUpdate = userService.uZaddrUpdate; + this.userUpdate = userService.userUpdate; + this.userUpdate.subscribe((user) => { + this.user = user; + }); this.txsUpdate = userService.txUpdate; this.txsUpdate.subscribe((txs) => { this.txs = txs; @@ -76,26 +95,7 @@ export class LoginComponent implements OnInit { localStorage.setItem('s4z_token', token); this.localToken = token; } - this.userService.findUser(); - this.userService.uZaddrUpdate. - subscribe((userAddr: string) => { - if (userAddr.length != 0) { - console.log('Log in found!'); - this.router.navigate(['/shop']); - } else { - console.log('No login for existing token found'); - //console.log('Showing QR code for login'); - ////console.log(URLSafeBase64.encode(Buffer.from('S4ZEC::'.concat(localToken)))); - //var codeString = `zcash:${this.nodeAddress}?amount=0.005&memo=${URLSafeBase64.encode(Buffer.from('ZGO::'.concat(this.localToken!)))}`; - //console.log(codeString); - //var qrcode = new QRCode(document.getElementById("qrcode"), { - //text: codeString, - //logo: "/assets/zcash.png", - //logoWidth: 80, - //logoHeight: 80 - //}); - } - }); + this.loginCheck(); }); this.intervalHolder = setInterval(() => { this.fullnodeService.getHeight(); @@ -106,21 +106,25 @@ export class LoginComponent implements OnInit { } loginCheck(){ + var today = new Date().getTime() / 1000; this.userService.findUser(); this.userService.findPending(); this.txsUpdate.subscribe((txs) => { this.txs = txs; }); - this.uZaddrUpdate.subscribe((userAddr: string) => { - if (userAddr.length != 0) { + this.userUpdate.subscribe((user) => { + if (user.expiration > today) { + this.prompt = true; console.log('Log in found in blockchain!'); - this.router.navigate(['/shop']); + if (user.validated) { + this.router.navigate(['/shop']); + } } }); } login() { - console.log('Dropdown:', this.entryForm.value.selectedSession); + //console.log('Dropdown:', this.entryForm.value.selectedSession); const dialogConfig = new MatDialogConfig(); dialogConfig.disableClose = true; @@ -137,6 +141,13 @@ export class LoginComponent implements OnInit { }); } + confirmPin(){ + if (this.user.pin === this.pinForm.value.pinValue) { + this.userService.validateUser(); + this.router.navigate(['/shop']); + } + } + ngOnDestroy(){ this.FullnodeSub.unsubscribe(); this.UserSub.unsubscribe(); diff --git a/src/app/order/order.service.ts b/src/app/order/order.service.ts index 69334b6..8d69190 100644 --- a/src/app/order/order.service.ts +++ b/src/app/order/order.service.ts @@ -16,7 +16,10 @@ export class OrderService { user:{ address: '', session: '', - blocktime: 0 + blocktime: 0, + expiration: 0, + pin: '', + validated: false }, order: { address: '', diff --git a/src/app/tx.model.ts b/src/app/tx.model.ts index 9e8d9b8..69b43f5 100644 --- a/src/app/tx.model.ts +++ b/src/app/tx.model.ts @@ -3,4 +3,5 @@ export interface Tx { address: string; session: string; confirmations: number; + amount: number; } diff --git a/src/app/user.model.ts b/src/app/user.model.ts index ff6ebca..bf43ae3 100644 --- a/src/app/user.model.ts +++ b/src/app/user.model.ts @@ -3,4 +3,7 @@ export interface User { address: string; session: string; blocktime: number; + expiration: number; + pin: string; + validated: boolean; } diff --git a/src/app/user.service.ts b/src/app/user.service.ts index f8a403f..524b5bb 100644 --- a/src/app/user.service.ts +++ b/src/app/user.service.ts @@ -13,7 +13,10 @@ export class UserService{ user: { address: '', session: '', - blocktime: 0 + blocktime: 0, + expiration: 0, + pin: '', + validated: false }, owner: { address: '', @@ -99,6 +102,15 @@ export class UserService{ } } + validateUser(){ + var validatedUser: User = this.dataStore.user; + validatedUser.validated = true; + this.http.post<{message: string, user: User}>(this.beUrl+'api/validateuser', {user: validatedUser}, {headers: this.reqHeaders}). + subscribe((responseData) => { + console.log(responseData.message); + }); + } + addOwner(address: string) { const owner: Owner={_id: '', address: address, name: 'Zgo-'.concat(address.substring(0,5))}; let obs = this.http.post<{message: string}>(this.beUrl+'api/addowner', {address: owner.address, name: owner.name}, {headers: this.reqHeaders}); diff --git a/src/app/viewer/viewer.component.css b/src/app/viewer/viewer.component.css index f6d0e97..f4e7eaf 100644 --- a/src/app/viewer/viewer.component.css +++ b/src/app/viewer/viewer.component.css @@ -1,3 +1,9 @@ * { font-family: 'Roboto Mono', monospace; } +.icon{ + font-family: 'Material Icons'; +} +.small{ + font-size: x-small; +} diff --git a/src/app/viewer/viewer.component.html b/src/app/viewer/viewer.component.html index e4cd943..0ef72f0 100644 --- a/src/app/viewer/viewer.component.html +++ b/src/app/viewer/viewer.component.html @@ -1,15 +1,13 @@
-

{{(ownerUpdate | async)!.name}}

+

{{(ownerUpdate | async)!.name}}

+

{{ shortenZaddr((ownerUpdate | async)!.address) }}

+ + +
- - - - - -
- - - -
+ + diff --git a/src/app/viewer/viewer.component.ts b/src/app/viewer/viewer.component.ts index fbbb81c..a13abe9 100644 --- a/src/app/viewer/viewer.component.ts +++ b/src/app/viewer/viewer.component.ts @@ -5,8 +5,10 @@ import { UserService } from '../user.service'; import { FullnodeService } from '../fullnode.service'; import { ItemService } from '../items/items.service'; import { Subscription, Observable } from 'rxjs'; +import { SettingsComponent } from '../settings/settings.component'; import {Owner} from '../owner.model'; +import {User} from '../user.model'; @Component({ @@ -16,9 +18,20 @@ import {Owner} from '../owner.model'; }) export class ViewerComponent implements OnInit { + intervalHolder: any; public message: string = "Welcome to the inside!"; + public user: User = { + address: '', + session: '', + blocktime: 0, + expiration: 0, + pin: '', + validated: false + }; + private owner: Owner= {_id:'', address: 'none', name:''}; public addrUpdate: Observable; public ownerUpdate: Observable; + public userUpdate: Observable; constructor( public fullnodeService: FullnodeService, @@ -28,15 +41,57 @@ export class ViewerComponent implements OnInit { ){ this.addrUpdate = fullnodeService.addrUpdate; this.ownerUpdate = userService.ownerUpdate; + this.ownerUpdate.subscribe((owner) => { + this.owner = owner; + }); + this.userUpdate = userService.userUpdate; + this.userUpdate.subscribe((user) => { + this.user = user; + }); } ngOnInit(){ this.ownerUpdate.subscribe((owner) => { this.message = owner.name; }); + this.loginCheck(); + this.intervalHolder = setInterval(() => { + this.loginCheck(); + }, 60000); } ngOnDestroy(){ } + shortenZaddr(address:string) { + var addr = address; + var end = addr.length; + var last = end - 5; + return addr.substring(0,5).concat('...').concat(addr.substring(last, end)); + } + openSettings() { + + const dialogConfig = new MatDialogConfig(); + + dialogConfig.disableClose = true; + dialogConfig.autoFocus = true; + dialogConfig.data = this.owner; + + const dialogRef = this.dialog.open(SettingsComponent, dialogConfig); + dialogRef.afterClosed().subscribe((val) => { + if (val != null) { + console.log('Saving settings'); + this.userService.updateOwner(val); + } + }); + } + + loginCheck(){ + var today = new Date().getTime() / 1000; + console.log('User check', this.user.validated); + if (this.user.expiration < today || !this.user.validated) { + console.log('Log in expired!'); + this.router.navigate(['/login']); + } + } } diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..5c1d436 Binary files /dev/null and b/src/assets/logo.png differ