admin token view

master
Chakib Benziane 5 years ago
parent 9ecdef717a
commit bd9a980133

@ -0,0 +1,34 @@
package api
import (
"net/http"
"git.sp4ke.com/sp4ke/bit4sat/storage"
"git.sp4ke.com/sp4ke/bit4sat/utils"
"github.com/gin-gonic/gin"
)
func adminGetInfo(c *gin.Context) {
adminToken := c.Param("adminToken")
// Get upload payments
//upInfo, err := storage.GetUploadInfoByToken(adminToken)
//if err != nil {
//utils.JSONErrPriv(c, http.StatusInternalServerError, err)
//return
//}
// Sum of payments
paySum, err := storage.GetMsatBalanceByAdminToken(adminToken)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, gin.H{
"msat_avail": paySum,
})
return
}

@ -43,7 +43,16 @@ func download(c *gin.Context) {
return
}
dlSess := session.Values["session-id"]
dlSessVal := session.Values["session-id"]
var dlSess string
if dlSess, ok = dlSessVal.(string); dlSessVal != nil && !ok {
log.Printf("error parsing session dlSessVal")
utils.JSONErr(c, http.StatusExpectationFailed,
"start a new download session session")
return
}
dlIdVal := session.Values["dlId"]
var sessDlId string
@ -66,7 +75,7 @@ func download(c *gin.Context) {
// If the stored dlId is different than the
// current dl id it means we're downloading a different
// file
if dlSess == nil || (sessDlId != dlId) {
if dlSessVal == nil || (sessDlId != dlId) {
// This is a new download session
log.Println("new download session")
@ -182,23 +191,35 @@ func download(c *gin.Context) {
return
}
// If invoice paid send the files
// If invoice paid create an authorized download link linked to session
// The link will expire after after one hour
// Also bump up the expirey of the DlSession to one hour
if invoice.Settled {
log.Println("dl invoice paid, sending")
// set response type to zip mime type
c.Header("Content-Type", "application/zip")
c.Header("Content-Disposition", "attachment; filename=download.zip")
err := storage.ZipFiles(upFilesMeta, c.Writer)
downKey, err := storage.GetShortId()
if err != nil {
log.Fatal(err)
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
key := fmt.Sprintf("session_%s_download_key_%s", dlSess, downKey)
// We store the upload id in the value to use it when retreiving
// files
db.SetKeyVal(key, upId)
db.ExpireKey(key, MaxAgeDlKeySession)
// Session will also expire in 1 hour
session.Options.MaxAge = MaxAgeDlKeySession
err = session.Save(c.Request, c.Writer)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
//c.JSON(http.StatusOK, gin.H{
//"download": "ok",
//"invoice": invoice,
//})
c.JSON(http.StatusOK, gin.H{
"download_key": downKey,
"invoice": invoice,
})
return
}
@ -211,6 +232,77 @@ func download(c *gin.Context) {
return
}
func getFiles(c *gin.Context) {
downKey := c.Param("dlKey")
// Get the session
session, err := SessionStore.Get(c.Request, DlSessionKey)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
dlSessVal := session.Values["session-id"]
var dlSess string
var ok bool
if dlSess, ok = dlSessVal.(string); dlSessVal != nil && !ok {
log.Printf("error parsing session dlSessVal")
utils.JSONErr(c, http.StatusExpectationFailed,
"user session lost, start a new download session session")
return
}
if dlSessVal == nil {
utils.JSONErr(c, http.StatusGone, "the download session has expired")
return
}
key := fmt.Sprintf("session_%s_download_key_%s", dlSess, downKey)
// Check if download key exists in db
exists, err := db.Exists(key)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
if !exists {
utils.JSONErr(c, http.StatusGone, "the download session has expired")
return
}
// We should now allow the user to get the files
// We retreive the files from the upId stored on the session download key
var upId string
err = db.GetFromKey(key, &upId)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// Get files metadata for upload
var upFilesMeta []storage.FileUpload
upFilesMeta, err = storage.GetUploadFilesMeta(upId)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
c.Header("Content-Type", "application/zip")
c.Header("Content-Disposition", "attachment; filename=download.zip")
err = storage.ZipFiles(upFilesMeta, c.Writer)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
return
}
func TestDownHandler(c *gin.Context) {
sess := sessions.Default(c)

@ -119,19 +119,20 @@ func pollInvoice(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"invoice": invoice,
})
return
case err := <-errorChan:
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// We stopped waiting for the invoice, it must have expired
c.JSON(http.StatusGone, gin.H{
"invoice": invoice,
})
return
}
return
}
// Was used by ln-charge
@ -152,7 +153,7 @@ func invoiceCbHandler(c *gin.Context) {
// get upload id related to invoice
var uploadId string
invoiceUploadKey := fmt.Sprintf("invoice_%s_uploadid", invoice.RHash)
invoiceUploadKey := fmt.Sprintf("invoice_%s_upload_id", invoice.RHash)
err := db.DB.Redis.Do(radix.FlatCmd(&uploadId, "GET", invoiceUploadKey))
if err != nil {

@ -39,8 +39,16 @@ func (api *API) Run() {
downRoute := v1.Group("/d")
{
// Download
downRoute.GET(":dlId", download)
// Query download
downRoute.GET("/q/:dlId", download)
// Get file
downRoute.GET("/g/:dlKey", getFiles)
}
adminRoute := v1.Group("/a")
{
adminRoute.GET("/info/:adminToken", adminGetInfo)
}
// test rout

@ -3,8 +3,9 @@ package api
import "github.com/boj/redistore"
const (
UpSessionKey = "bit4sat-up"
DlSessionKey = "bit4sat-dl"
UpSessionKey = "bit4sat-up"
DlSessionKey = "bit4sat-dl"
MaxAgeDlKeySession = 3600 // max age for a paid and authorized download key
)
var (

@ -65,7 +65,7 @@ func (ctrl UploadCtrl) New(c *gin.Context) {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
up.DownloadID = dlId
up.DownloadId = dlId
err = up.TxWrite(tx)

@ -11,3 +11,14 @@ func GetFromKey(key string, target interface{}) error {
func SetKeyVal(key string, val interface{}) error {
return DB.Redis.Do(radix.FlatCmd(nil, "SET", key, val))
}
func ExpireKey(key string, seconds int) error {
return DB.Redis.Do(radix.FlatCmd(nil, "EXPIRE", key, seconds))
}
func Exists(key string) (bool, error) {
var exists bool
err := DB.Redis.Do(radix.Cmd(&exists, "EXISTS", key))
return exists, err
}

@ -1,100 +0,0 @@
version: "3.4"
volumes:
redis-db:
postgresql:
file-storage:
gocache:
#sqlite:
#maria-conf:
services:
api:
image: sp4ke/bit4sat
container_name: bit4sat-api
build:
context: .
dockerfile: ./docker/Dockerfile
environment:
- GO111MODULE=on
- API_HOST=bit4sat-api
- LND_GRPC_HOST=lnd
- LND_GRPC_PORT=10009
- BIT4SAT_STORAGE_PATH=/storage
- GOPATH=/go
- SQL_DB_HOST=postgres
- SQL_DB_USER=bit4sat
- SQL_DB_PASS=bit4sat
- LN_CHARGE_API=ln-charge-test:9112
- LN_CHARGE_TOKEN=3emU3Fy8VasHCzMaMXHSVJYpQSqH3yXQj8N5cQFBbq3botrudJuR7zQkBBmFSbAmgXs9GD4j4U3J4R2sMfgqPo8q
- SESSION_SECRET=Ai7fCy36UE5cb9wcmdAxxRXwYyQDsDMr6rYocA6Eava7pdiB29EusLbb9sTYWS1e
- GRPC_SSL_CIPHER_SUITES="HIGH+ECDSA"
- SHORT_ID_SALT=Czp6NtlGpt0ebzG1DuUND1nMftLUR77c
- ENV=dev
# Used in case of ssl problems
- HTTP_PROXY=http://tinyproxy:8888
#deploy:
#replicas: 1
#
ports:
- "8880:8880"
depends_on:
- redis
- postgres
volumes:
- $PWD:/src
- gocache:/go
#- /fastData/go:/go
#- ./db-storage:/sqlite
- file-storage:/storage
working_dir: /src
networks:
- btc-test-overlay
- btc-overlay
- default
#maria:
#image: mariadb:latest
#environment:
#- MYSQL_ROOT_PASSWORD=pass
#- MYSQL_DATABASE=bit4sat
#- MYSQL_USER=bit4sat
#- MYSQL_PASSWORD=bit4sat
#volumes:
#- db:/var/lib/mysql
#- maria-conf:/etc/mysql
postgres:
image: postgres:11.2
environment:
- POSTGRES_PASSWORD=bit4sat
- POSTGRES_USER=bit4sat
- POSTGRES_DB=bit4sat
volumes:
- postgresql:/var/lib/postgresql/data
redis:
image: redis:alpine
volumes:
- redis-db:/data
command:
- redis-server
- --appendonly yes
networks:
btc-test-overlay:
external: true
btc-overlay:
external: true

@ -142,6 +142,7 @@ func InvoiceFromLndIn(lndInvoice *lnrpc.Invoice) *Invoice {
RHash: hex.EncodeToString(lndInvoice.RHash),
PreImage: hex.EncodeToString(lndInvoice.RPreimage),
CreatedAt: timestamp(time.Unix(lndInvoice.CreationDate, 0)),
PaidAt: timestamp(time.Unix(lndInvoice.SettleDate, 0)),
Status: InvoiceStatus[UnPaid],
}

@ -234,7 +234,7 @@ type FileUpload struct {
type Upload struct {
UploadId string `db:"upload_id"`
DownloadID string `db:"download_id"`
DownloadId string `db:"download_id"`
AskFee bool `db:"ask_fee"`
Created time.Time `db:"created"`
AskCurrency string `db:"ask_currency"`
@ -242,7 +242,18 @@ type Upload struct {
InvoiceRhash string `db:"invoice_rhash"` // used as id
Settled bool `db:"settled"`
UploadStatus uint32 `db:"status"` // upload flag status
AdminToekn string `db:"admin_token"`
AdminToken string `db:"admin_token"`
}
type UploadPayments struct {
PaymentId string `db:"payment_id_rhash"`
MsatAmount float64 `db:"msat_amount"`
PayCreated time.Time `db:"payment_created"`
}
type UploadInfo struct {
Upload
UploadPayments
}
func (u Upload) MarshalBinary() ([]byte, error) {
@ -358,7 +369,7 @@ func GetUploadInvoice(uploadId string) (*ln.Invoice, error) {
return nil, err
}
log.Printf("GetUploadInvoice returned %#v")
//log.Printf("GetUploadInvoice returned %#v", invoice)
return &invoice, nil
}
@ -393,6 +404,36 @@ func GetUploadFilesMeta(uploadId string) ([]FileUpload, error) {
}
// Get all payments linked to an admin token
func GetUploadPaymentsByToken(adminToken string) ([]*UploadInfo, error) {
upInfo := []*UploadInfo{}
query := `SELECT
uploads.upload_id,
uploads.download_id,
uploads.ask_fee,
uploads.ask_currency,
uploads.ask_amount,
uploads.admin_token,
upload_payments.msat_amount,
upload_payments.created AS payment_created
FROM uploads, upload_payments
WHERE uploads.admin_token = $1
`
err := DB.Sql.Select(&upInfo, query, adminToken)
return upInfo, err
}
func GetMsatBalanceByAdminToken(adminToken string) (float64, error) {
var balance float64
query := `SELECT SUM(upload_payments.msat_amount)
FROM uploads, upload_payments
WHERE uploads.upload_id = upload_payments.upload_id
AND uploads.admin_token = $1`
err := DB.Sql.Get(&balance, query, adminToken)
return balance, err
}
func GetUploadById(id string) (*Upload, error) {
key := fmt.Sprintf("upload_%s", id)
up := Upload{}

@ -127,12 +127,12 @@ func handleSettledInvoice(invoice *lnrpc.Invoice) {
log.Printf("error handleSettledInvoice: %s", err)
return
}
log.Printf("stored invoice %#v", storedInvoice)
//log.Printf("stored invoice %#v", storedInvoice)
// Update stored invoice fields
newInvoice := ln.UpdateInvoiceFromLnd(storedInvoice, invoice)
log.Printf("new invoice %#v", newInvoice)
//log.Printf("new invoice %#v", newInvoice)
// Set invoice for upload
err = storage.SetUploadInvoice(uploadId, newInvoice)
@ -164,7 +164,7 @@ func handleSettledInvoice(invoice *lnrpc.Invoice) {
// Get upload_id related to this invoice
key = fmt.Sprintf("invoice_id_%s_upload_id", storedInvoice.RHash)
log.Printf("looking for %s", key)
//log.Printf("looking for %s", key)
var uploadId string
err := db.DB.Redis.Do(radix.FlatCmd(&uploadId, "GET", key))
if err != nil {
@ -173,7 +173,7 @@ func handleSettledInvoice(invoice *lnrpc.Invoice) {
}
// Add payment to the corresponding upload_id (user account)
log.Printf("updating payment for upload %s", uploadId)
//log.Printf("updating payment for upload %s", uploadId)
err = storage.AddPaymentToUpload(uploadId, storedInvoice.RHash,
storedInvoice.Msatoshi)
if err != nil {

@ -20,6 +20,11 @@ rewrite /d {
to /
}
## Redeem view
rewrite /r {
to /
}
## test
proxy /t localhost:8880 {
transparent

Binary file not shown.

@ -0,0 +1,32 @@
<template>
<div id="admin">
<p v-show="apiDone">Current balance: {{balance}}</p>
<textarea name="payreqRedeem"></textarea>
<button name="redeem">Redeem</button>
</div>
</template>
<script>
import Api from './api.js'
export default {
name: 'AdminView',
data(){
return {
balance: 0,
apiDone: false,
}
},
props: ['adminToken'],
created(){
let self = this
Api.adminInfo(this.adminToken)
.then((res)=>{
res.json().then((data)=>{
self.balance = data.msat_avail
self.apiDone = true
})
})
}
}
</script>

@ -15,6 +15,7 @@ const Worker = GetWorker('main');
import { mapState, mapGetters } from 'vuex'
const dlUrlRegex = /d\/(\w+)\/?/
const adminUrlRegex = /r\/(\w+)\/?/
export default {
@ -45,6 +46,15 @@ export default {
params: {dlId}
})
}
if (loc.pathname.match(adminUrlRegex)) {
let adminToken = adminUrlRegex.exec(loc.pathname)[1]
history.replaceState("", `admin`, '/' )
this.$router.replace({
name: 'admin',
replace: true,
params: {adminToken}
})
}
},
}

@ -47,31 +47,43 @@ export default {
this.$store.commit('setFiles', data.files)
Api.pollInvoice(data.invoice.rhash)
.then((res)=>{
// invoice paid we can try again to download
if(res.ok){
//Api.download(self.dlId)
console.log("calling set download")
self.setDownload()
}
})
.then((res)=>{
// invoice paid we can try again to download
if(res.ok){
self.fetchDownloadKey()
}
})
.catch((e)=>{console.log(e)})
// download
}) .catch((e)=>{console.log(e)})
} else {
console.log("calling set download")
self.setDownload()
res.json().then((data)=>{
console.log(data)
self.setDownload(data.download_key)
})
}
})
},
methods: {
setDownload(){
fetchDownloadKey() {
Api.download(this.dlId)
.then((res)=>{
if (!res.ok ) {
this.error = true
this.errMsg = "an error occured, try reloading the page"
} else {
res.json().then((data)=>{
this.setDownload(data.download_key)
})
}
})
},
setDownload(downloadKey){
this.showDl = true;
this.dlLink = Api.endPoints.download + '/' + this.dlId
this.dlLink = Api.endPoints.getFiles + '/' + downloadKey
}
},

@ -126,6 +126,7 @@ export default {
</script>
<style>
img {
width: var(--qrcode-width);
height: var(--qrcode-width);

@ -1,6 +1,9 @@
<template>
<div id="upload-view" class="flex flex-column items-center justify-center">
<pay :objectId="uploadId" :invoice="invoice"></pay>
<p v-show="!paid && !expired" class="dn f6 mb4 mid-gray">To avoid spam you are asked to make a one-time payment equivalent to the
fee you ask for your link</p>
<pay v-show="!paid" :objectId="uploadId" :invoice="invoice"></pay>
<div class="hr"></div>
<form id="accepted" class="flex flex-column mt5 w-100" v-if="accepted" >
@ -102,6 +105,10 @@ export default {
status: state => state.upload.status,
invoice: state => state.base.invoice,
}),
...mapGetters([
'paid',
'expired',
]),
downloadLink(){
let loc = window.location;
return loc.host + '/d/' + this.downloadId

@ -9,11 +9,23 @@ const endPoints = {
session: '/api/v1/session',
pollupstatus: '/api/v1/u/poll',
checkstatus: '/api/v1/u/check',
download: '/api/v1/d',
pollinvoice: '/api/v1/pollinvoice'
download: '/api/v1/d/q', // query download
getFiles: '/api/v1/d/g', // get file
pollinvoice: '/api/v1/pollinvoice',
adminToken: '/api/v1/a/info'
}
export function adminInfo(adminToken){
let req = new Request(endPoints.adminToken + '/' + adminToken,{
method: 'GET',
credentials: 'same-origin'
})
console.log(req)
return fetch(req).catch((e)=>{console.error(e)})
}
export async function download(dlId){
@ -144,4 +156,5 @@ export default {
checkUploadStatus: checkUploadStatus,
download: download,
pollInvoice: pollInvoice,
adminInfo: adminInfo
}

@ -9,6 +9,7 @@ import Home from './Home.vue'
import App from './App.vue'
import UploadView from './UploadView.vue'
import DownloadView from './DownloadView.vue'
import AdminView from './AdminView.vue'
import GetWorker from './workerInterface.js'
import Router from 'vue-router'
import Api from './api.js'
@ -37,9 +38,10 @@ const router = new Router({
props: true,
},
{
path:'/a/:adminToken',
// Redeem view
path:'/r/:adminToken',
name: 'admin',
//component: AdminView,
component: AdminView,
props: true,
}

Loading…
Cancel
Save