Browse Source

Initial commit

master
Brandon Cornejo 6 years ago
commit
845d5dd3ed
47 changed files with 25143 additions and 0 deletions
  1. +5
    -0
      .gitignore
  2. +0
    -0
     
  3. +4
    -0
      app.py
  4. +66
    -0
      app/__init__.py
  5. +8
    -0
      app/admin.py
  6. +8
    -0
      app/forms.py
  7. +55
    -0
      app/models.py
  8. +96
    -0
      app/static/css/app.css
  9. BIN
     
  10. BIN
     
  11. BIN
     
  12. BIN
     
  13. BIN
     
  14. BIN
     
  15. BIN
     
  16. BIN
     
  17. BIN
     
  18. BIN
     
  19. BIN
     
  20. +7643
    -0
      app/static/lib/uikit/css/uikit.almost-flat.css
  21. +3
    -0
      app/static/lib/uikit/css/uikit.almost-flat.min.css
  22. +6890
    -0
      app/static/lib/uikit/css/uikit.css
  23. +7759
    -0
      app/static/lib/uikit/css/uikit.gradient.css
  24. +3
    -0
      app/static/lib/uikit/css/uikit.gradient.min.css
  25. +3
    -0
      app/static/lib/uikit/css/uikit.min.css
  26. BIN
     
  27. BIN
     
  28. BIN
     
  29. BIN
     
  30. +1780
    -0
      app/static/lib/uikit/js/uikit.js
  31. +3
    -0
      app/static/lib/uikit/js/uikit.min.js
  32. +14
    -0
      app/templates/_macros.html
  33. +84
    -0
      app/templates/confirm_purchase.html
  34. +130
    -0
      app/templates/dashboard.html
  35. +26
    -0
      app/templates/editticket.html
  36. +9
    -0
      app/templates/error.html
  37. +88
    -0
      app/templates/index.html
  38. +66
    -0
      app/templates/layout.html
  39. +26
    -0
      app/templates/newticket.html
  40. +24
    -0
      app/templates/purchase.html
  41. +23
    -0
      app/templates/security/change_password.html
  42. +25
    -0
      app/templates/security/forgot_password.html
  43. +31
    -0
      app/templates/security/login_user.html
  44. +31
    -0
      app/templates/security/register_user.html
  45. +22
    -0
      app/templates/security/reset_password.html
  46. +17
    -0
      app/templates/viewticket.html
  47. +201
    -0
      app/views.py

+ 5
- 0
.gitignore View File

@@ -0,0 +1,5 @@
*.pyc
*.db
*.log
venv/


+ 0
- 0
View File


+ 4
- 0
app.py View File

@@ -0,0 +1,4 @@
#!venv/bin/python
from app import app

app.run(host='0.0.0.0', debug=True)

+ 66
- 0
app/__init__.py View File

@@ -0,0 +1,66 @@
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask_mail import Mail
from flask.ext.security import SQLAlchemyUserDatastore, Security, user_registered
from flask.ext.admin import Admin
from flask.ext.admin.contrib.sqla import ModelView
from config import ADMINS, MAILCONF, SECURITY_EMAIL_SENDER

app = Flask(__name__)
app.config.from_object('config')

# Setup SQL database and ORM
db = SQLAlchemy(app)

# Initialize Flask-Security
from models import User, Role, Ticket, Invoice
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
mail = Mail(app)

# Initialize Flask-Admin
from app import admin
admin = Admin(app, name='PacketCrypt', index_view=admin.AdminIndex())
admin.add_view(ModelView(User, db.session))
admin.add_view(ModelView(Ticket, db.session))
admin.add_view(ModelView(Invoice, db.session))

@app.before_first_request
def initialize():
try:
db.create_all()
user = user_datastore.find_user(email='br4n@atr0phy.net')
if not user:
user = user_datastore.create_user(email='br4n@atr0phy.net', password='packetcrypt')
user_datastore.add_role_to_user(user, 'Admin')
app.logger.info("First run, create default admin user")
for role in ('Admin', 'User'):
user_datastore.create_role(name=role)
db.session.commit()
except Exception, e:
app.logger.error(str(e))

@user_registered.connect_via(app)
def on_user_registered(sender, **extra):
default_role = user_datastore.find_role("User")
user_datastore.add_role_to_user(user, default_role)
db.session.commit()

# Import views
from app import views

if not app.debug:
import logging
from logging.handlers import SMTPHandler, RotatingFileHandler
credentials = None
if MAILCONF['MAIL_USERNAME'] or MAILCONF['MAIL_PASSWORD']:
credentials = (MAILCONF['MAIL_USERNAME'], MAILCONF['MAIL_PASSWORD'])
mail_handler = SMTPHandler((MAILCONF['MAIL_SERVER'], MAILCONF['MAIL_PORT']), SECURITY_EMAIL_SENDER, ADMINS, 'PacketCrypt failure', credentials)
mail_handler.setLevel(logging.ERROR)
app.logger.addHandler(mail_handler)
file_handler = RotatingFileHandler('app.log', 'a', 1 * 1024 * 1024, 10)
file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
app.logger.setLevel(logging.INFO)
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.info('PacketCrypt startup')

+ 8
- 0
app/admin.py View File

@@ -0,0 +1,8 @@
from flask.ext.security import current_user
from flask.ext.admin import AdminIndexView, BaseView, expose

class AdminIndex(AdminIndexView):
def is_accessible(self):
return current_user.has_role('Admin')



+ 8
- 0
app/forms.py View File

@@ -0,0 +1,8 @@
from flask.ext.wtf import Form
from wtforms import TextField, SubmitField, TextAreaField
from wtforms.validators import Required

class TicketForm(Form):
subject = TextField('Subject', validators = [Required()])
body = TextAreaField('Message', validators = [Required()])


+ 55
- 0
app/models.py View File

@@ -0,0 +1,55 @@
from app import db
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.security import UserMixin, RoleMixin

roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))

class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))

class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic'))
tickets = db.relationship('Ticket', backref='creator', lazy='dynamic')
invoices = db.relationship('Invoice', backref='customer', lazy='dynamic')

def __repr__(self):
return '<User %r>' % (self.email)

class Ticket(db.Model):
id = db.Column(db.Integer, primary_key=True)
subject = db.Column(db.String(140), unique=True)
body = db.Column(db.String(2000))
created = db.Column(db.DateTime)
timestamp = db.Column(db.DateTime)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

def __repr__(self):
return '<Ticket %r>' % (self.subject)

class Invoice(db.Model):
id = db.Column(db.Integer, primary_key=True)
is_confirmed = db.Column(db.Boolean())
paid = db.Column(db.Boolean())
datepaid = db.Column(db.DateTime)
datecreated = db.Column(db.DateTime)
dateends = db.Column(db.DateTime)
total_btc = db.Column(db.Float)
exchange_rate_when_paid = db.Column(db.Float)
address = db.Column(db.String(34))
confirmations = db.Column(db.Integer)
transaction_hash = db.Column(db.String)
input_transaction_hash = db.Column(db.String)
value_paid = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

def __repr__(self):
return '<Invoice %r>' % (self.id)

+ 96
- 0
app/static/css/app.css View File

@@ -0,0 +1,96 @@
body {
}

#top-bar {
position: absolute;
width:100%;
top:0;
left:0;
background-color: rgba(255,255,255,0.85);
}

#top-bar div:last-child {
margin: 1.0%;
}

#big-top-bar {
background: url('/static/img/binary.jpg') no-repeat;
min-height: 400px;
position:relative;
}

.pc-market-block {
max-width: 60%;
margin-top: 2em;
}

#tech-images > img {
border-radius: 1.0;
padding: 40px 30px;
display: inline-block;
width: 100px;
}

#pc-dashboard-wrapper {
max-width:60%;
}

#footer {
padding-top:4em;
}

#footer-social > img {
border-radius: 100px;;
padding: 0 8px;
display: inline-block;
width: 60px;
}
#three-fold-container {
max-width: 80%;
}
#two-fold-container {
padding-top:6em;
max-width:80%;
}

#container {
}

#form-container {
margin-top:20px;
}

#form-container .uk-form-row > textarea {
width: 100%;
height: 200px;
}

#bitcoin-logo {
bottom: 10px;
position: absolute;
right: 10px;
width: 200px;
}

#btc_qr {
margin: 0 auto;
display:block
}

.pc-form-row {
margin-bottom: 2em;
}

#form-info {
width:55%;
}
#form-info > .uk-button {
margin-top:2em;
}
#form-container > form {
width:40%;
display:inline-block;
}
.pc-form-row > input {
width:100%;
}

BIN
View File


BIN
View File


BIN
View File


BIN
View File


BIN
View File


BIN
View File


BIN
View File


BIN
View File


BIN
View File


BIN
View File


BIN
View File


+ 7643
- 0
app/static/lib/uikit/css/uikit.almost-flat.css
File diff suppressed because it is too large
View File


+ 3
- 0
app/static/lib/uikit/css/uikit.almost-flat.min.css
File diff suppressed because it is too large
View File


+ 6890
- 0
app/static/lib/uikit/css/uikit.css
File diff suppressed because it is too large
View File


+ 7759
- 0
app/static/lib/uikit/css/uikit.gradient.css
File diff suppressed because it is too large
View File


+ 3
- 0
app/static/lib/uikit/css/uikit.gradient.min.css
File diff suppressed because it is too large
View File


+ 3
- 0
app/static/lib/uikit/css/uikit.min.css
File diff suppressed because it is too large
View File


BIN
View File


BIN
View File


BIN
View File


BIN
View File


+ 1780
- 0
app/static/lib/uikit/js/uikit.js
File diff suppressed because it is too large
View File


+ 3
- 0
app/static/lib/uikit/js/uikit.min.js
File diff suppressed because it is too large
View File


+ 14
- 0
app/templates/_macros.html View File

@@ -0,0 +1,14 @@
{% macro render_field_with_label(field) %}
{% set css_class=kwargs.pop('class', '') %}
<span class="uk-text-small uk-text-bold">{{ field.label }}:</span>
<div class="uk-form-row pc-form-row">{{ field(class=css_class, **kwargs)|safe }}</div>
{% endmacro %}

{% macro render_field_with_label_oneline(field) %}
{% set css_class=kwargs.pop('class', '') %}
<div class="uk-form-row">{{field.label}} {{field(class=css_class, **kwargs)|safe }}</div>
{% endmacro %}

{% macro render_field(field) %}
<div class="uk-form-row">{{ field(class=css_class, **kwargs)|safe }}</div>
{% endmacro %}

+ 84
- 0
app/templates/confirm_purchase.html View File

@@ -0,0 +1,84 @@
{% extends "layout.html" %}

{% block title %}Confirming purchase - packetcrypt{% endblock %}

{% block content %}
<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center">
<h2>Invoice Created</h2>
Please send {{ invoice.total_btc - invoice.value_paid}} BTC to: <a href="bitcoin:{{invoice.address}}?amount={{invoice.total_btc}}">{{ invoice.address }}</a>
<br/><br/>
<img src="https://chart.googleapis.com/chart?cht=qr&chs=300x300&chl=bitcoin:{{invoice.address}}?amount={{invoice.total_btc}}&callback=?" id="btc_qr" />
<button class="uk-button uk-button-primary uk-button-expand" id="confirm">Confirm Payment <i class="uk-icon-spinner"></i></button>
<form action="{{url_for('confirm_purchase', invoice_id=invoice.id)}}" method="POST" name="delete_ticket" style="width:100%;">
<button type="submit" class="uk-button uk-button-expand" id="cancel">Cancel Payment</button>
</form>
</div>

<div id="modal" class="uk-modal">
<div class="uk-modal-dialog uk-modal-dialog-slide">
<a class="uk-modal-close uk-close"></a>
<h1>Thank you!</h1>
<p>Your payment of <label id="valuepaid"></label> <i class="uk-icon-btc"></i> has been confirmed.</p>
<p id="paid">Your VPN service is being configured and should be available from your dashboard in the next 5-10 minutes.</p>
<p id="notpaid">
However, this does not cover the total cost for your PacketCrypt service.
Please send <label id="remaining"></label> <i class="uk-icon-btc"></i> to: <a id="btcaddy" >{{invoice.address}}</a>
</p>
<p>Transaction: <a id="hashlink"></a></p>
<a class="uk-button uk-button-success uk-button-expand" href="{{url_for('dashboard')}}">Return to Dashboard</a>
</div>
</div>
{% endblock %}

{% block postscript %}
<script>
var amountPaidOnLoad = {{invoice.value_paid}};
$(document).ready(function(){
var confirmButton = $('#confirm');
var cancelButton = $('#cancel');

confirmButton.click(checkPayment);

checkPayment();

});
var checkPayment = function (arg){
$('#confirm > i').addClass('uk-icon-spin');
$.post('/invoice_status', {
invoice_id: {{invoice.id}}
}).done(function(d){
var paidInFull = (d['value_paid'] >= d['total_btc']);
var transactionHash = d['input_transaction_hash'];
var confirmations = d['confirmations'];

window.setTimeout(stopSpin, 3500);

if(confirmations == null || amountPaidOnLoad == d['value_paid']){
//$('#flash-wrapper').children(":first").append("<div class='uk-alert uk-alert-warning' data-uk-alert><a class='uk-alert-close uk-close'></a>Unable to confirm payment. Wait a few seconds and try again.</div>");
window.setTimeout(checkPayment, 10000);
} else {
// Setup the modal before display
var priceLeft = (d['total_btc'] - d['value_paid']);
var btcLink = "bitcoin:{{invoice.address}}?amount="+priceLeft;
var hashLink = "https://blockchain.info/tx/"+transactionHash;
$('#valuepaid').html(d['value_paid']);
$('#remaining').html(d['total_btc'] - d['value_paid']);
(paidInFull) ? $('#notpaid').css('display', 'none') : $('#paid').css('display', 'none');
$('#btcaddy').attr('href', btcLink);
$('#hashlink').attr('href', hashLink).html(transactionHash);

// Display teh modal
var modal = new $.UIkit.modal.Modal('#modal');
modal.show();
}
}).fail(function(){
$('#flash-wrapper').children(":first").append("<div class='uk-alert uk-alert-danger' data-uk-alert><a href='' class='uk-alert-close uk-close'></a>An error occured while trying to confirm your payment.</div>");
});
};

var stopSpin = function (arg){
$('#confirm > i').removeClass('uk-icon-spin');
};
</script>
{% endblock %}


+ 130
- 0
app/templates/dashboard.html View File

@@ -0,0 +1,130 @@
{% extends "layout.html" %}

{% block title %} Dashboard - packetcrypt {% endblock %}

{% block content %}
<div class="uk-container-center" id="pc-dashboard-wrapper">
<div class="uk-grid">
<div class="uk-width-1-1">
<h4 class="uk-float-right">Hello, {{ user.email }}!</h4>
<br/>
</div>
<div class="uk-width-1-2">
<div class="uk-panel">
<h2 class="uk-panel-title">Account Information <i class="uk-icon-user"></i> </h2>
<ul>
<li>Email Address: {{ user.email }}</li>
<li>Last Payment Date: {{ lastpaid }}</li>
<li>Plan Expires: {{ expires }}</li>
<li>Number of Support Tickets: {{ user.tickets.all() | length }} </li>
<li class="uk-text-muted">Traffic This Month: 000Mb (00%)</li>
<li class="uk-text-muted">Number of Referred Signups: 0</li>
</ul>
</div>
</div>
<div class="uk-width-1-2">
<div class="uk-panel uk-panel-box uk-panel-box-secondary">
<h2 class="uk-panel-title">Controls <i class="uk-icon-cogs uk-float-right"></i> </h2>
<hr class="uk-panel-divider">
<ul class="uk-nav">
<li><a href="">Logout</a></li>
<li><a href="">Change Password</a></li>
<li><a href="{{ url_for('newticket') }}">Open Support Ticket</a></li>
</ul>
<div class="uk-panel" style="margin-top:1em;">
{% if latest %}
<a href="{{url_for('purchase')}}" class="uk-button uk-button-success uk-align-left uk-button-small">Renew</a>
{% else %}
<a href="{{url_for('purchase')}}" class="uk-button uk-button-success uk-align-left uk-button-small">Purchase</a>
{% endif %}


<div class="uk-button-group uk-align-right" style="">
<button class="uk-button uk-button-disabled uk-button-small" type="submit"><i class="uk-icon-folder-open-alt"></i> Download</button>
<div data-uk-dropdown>
<a href="" class="uk-button uk-button-small"><i class="uk-icon-caret-down"></i></a>
<div class="uk-dropdown uk-dropdown-small">
<ul class="uk-nav uk-nav-dropdown">
<li><a href="">Windows</a></li>
<li><a href="">OSX</a></li>
<li><a href="">Android</a></li>
<li><a href="">iOS</a></li>
</ul>
</div>
</div>
</div> <!-- end button-group -->
</div>
</div>
</div>
<div class="uk-width-1-1">
<table class="uk-table uk-table-hover uk-table-striped">
<caption>Support Tickets <div class="uk-badge" style="margin-left:1em;">{{ g.user.tickets.all() | length }}</div></caption>
<thead>
<tr><th>Date</th><th>Subject</th><th>Status</th><th>Last Updated</th></tr>
</thead>
<tbody>
{% if g.user.tickets.all() %}
{% for ticket in g.user.tickets.all() %}
<tr class="ticket-row">
<td>{{ ticket.timestamp |date }}</td>
<td><a href="{{ url_for('viewticket', tid=ticket.id) }}"></a>{{ ticket.subject }}</td>
<td>N/A</td>
<td>N/A</td>
</tr>
{% endfor %}
{% else %}
<tr><td>You have no support ticket history</td><td></td><td></td><td></td></tr>
{% endif %}
</tbody>
</table>
<br/><br/>
<table class="uk-table uk-table-hover uk-table-striped">
<caption>Invoices <div class="uk-badge uk-badge-danger" style="margin-left:1em;">{{ g.user.invoices.all() | length }}</div></caption>
<thead>
<tr><th>Date Paid</th><th>Amount Paid</th><th>Payment Address</th><th>Confirmed</th></tr>
</thead>
<tbody>
{% if g.user.invoices.all() %}
{% for invoice in g.user.invoices.all() %}
<tr class="invoice-row">
{% if invoice.datepaid %}
<td>{{invoice.datepaid | date}}</td>
{% else %}
<td>Unpaid</td>
{% endif %}
<td>{{invoice.value_paid}}</td>
<td>{{invoice.address}}</td>
{% if invoice.is_confirmed %}
<td><a href="{{url_for('confirm_purchase', invoice_id=invoice.id)}}"><i class="uk-icon-ok"></i></a></td>
{% else %}
{% if invoice.paid %}
<td><a href="{{url_for('confirm_purchase', invoice_id=invoice.id)}}"><i class="uk-icon-ban-circle"></i></a></td>
{% else %}
<td><a href="{{url_for('confirm_purchase', invoice_id=invoice.id)}}" class="uk-button">Pay <i class="uk-icon-btc"></i></a></td>
{% endif %}
{% endif %}
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

{% block postscript %}
<script type="text/javascript">
$(window).load(function(){
$(".ticket-row").click(function(){
var tid = $(this).find("a").attr("href");
window.location = tid
});
$(".invoice-row").click(function(){
var invoice_id = $(this).find("a").attr("href");
window.location = invoice_id
});
});

</script>
{% endblock %}

+ 26
- 0
app/templates/editticket.html View File

@@ -0,0 +1,26 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_field_with_label, render_field %}

{% block title %}Update Support Ticket - packetcrypt{% endblock %}
{% block flash %}
{% for field in form.errors %}
{% for error in form.errors[field] %}
<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div>
{% endfor %}
{% endfor %}
{% endblock %}

{% block content %}
<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center">
<form action="{{ url_for('editticket', tid=ticket.id) }}" method="POST" name="form" class="uk-form" style="width:100%">
{{ form.hidden_tag() }}
{{ render_field_with_label(form.subject) }}
{{ render_field_with_label(form.body) }}
<div class="uk-form-row">
<button class="uk-button uk-button-success" type="submit">Update Ticket</button>
<a href="{{url_for('dashboard')}}" class="uk-button uk-button-primary">Close</a>
<a href="{{url_for('deleteticket', tid=ticket.id)}}" class="uk-button uk-button-danger">Delete Ticket</a>
</div>
</form>
</div>
{% endblock %}

+ 9
- 0
app/templates/error.html View File

@@ -0,0 +1,9 @@
{% extends "layout.html" %}

{% block content %}
<div id="form-container" class="uk-width-1-4 uk-panel uk-panel-box uk-container-center">
<h1> The file was not found and/or an unexpected error occurred.</h1>
<p>The administrator has been notified. Sorry for the inconvenience!</p>
<p><a href="{{url_for('index')}}" class="uk-button">Back</a></p>
</div>
{% endblock %}

+ 88
- 0
app/templates/index.html View File

@@ -0,0 +1,88 @@
{% extends "layout.html" %}

{% block title %}PacketCrypt - Secure, anonymous VPN service for humans{% endblock %}

{% block content %}
<div class="uk-grid">
<div class="uk-width-1-1">
<div class="uk-panel uk-container-center pc-market-block uk-text-center">
<h1>We handle the security, you browse the web.</h1>
<span class="uk-text-large">
PacketCrypt offers the the quickest way available to keep your web traffic safe.
We follow financial industry standards to ensure your data is protected no matter where you're grabbing a connection.
PacketCrypt makes it simple to encrypt your internet trafic and go about your day.
</span>
<div id="tech-images">
<img src={{ url_for('static', filename='img/windows.png') }} />
<img src={{ url_for('static', filename='img/openvpn.png') }} />
<img src={{ url_for('static', filename='img/android.png') }} />
<img src={{ url_for('static', filename='img/ios.png') }} />
<img src={{ url_for('static', filename='img/osx.png') }} />
</div>
</div>
</div>
<div class="uk-width-1-1">
<div id="three-fold-container" class="uk-container-center">
<div class="uk-grid" data-uk-grid-match="{target:'.uk-panel'}">
<div class="uk-width-large-1-3 uk-width-medium-1-1">
<div class="uk-panel uk-panel-box uk-panel-box-primary">
<h3 class="uk-panel-title uk-text-center"><i class="uk-icon-lock uk-icon-large"></i> Secure</h3>
We don't take chances with your data or ours.
All of PacketCrypt's services rely on OpenVPN, open source security software trusted by over 5 million users.
Run on the safest platform in the world: FreeBSD. Our servers are stable and secured to make sure we're always there for you.
</div>
</div>
<div class="uk-width-large-1-3 uk-width-medium-1-1">
<div class="uk-panel uk-panel-box uk-panel-box-primary">
<h3 class="uk-panel-title uk-text-center"><i class="uk-icon-download-alt uk-icon-large"></i> Fast</h3>
Unlike other anonymous VPN providers, you don't have to sacrifice speed for safety with PacketCrypt.
All of our servers worldwide are on a Gigabit pipeline, so you can download as fast as your local connection can handle.
Stream movies or download large files fast, and without leaving a trace.
</div>
</div>
<div class="uk-width-large-1-3 uk-width-medium-1-1">
<div class="uk-panel uk-panel-box uk-panel-box-primary">
<h3 class="uk-panel-title uk-text-center"><i class="uk-icon-group uk-icon-large"></i> Anonymous</h3>
The first rule of security is <em>Trust No One</em>, and that includes us.
PacketCrypt keeps no logs, and doesn't track your login sessions.
You pay us in Bitcoin and all we ask for is an email address.
Not only does PacketCrypt protect you from the rest of the internet, we protect you from ourselves.
</div>
</div>
</div>
</div>
</div>
<div class="uk-width-1-1">
<div id="two-fold-container" class="uk-container-center">
<div class="uk-grid" data-uk-grid-match="{target:'.uk-panel'}">
<div class="uk-width-1-3">
<div class="uk-panel uk-align-left uk-text-center">
<h2>Sign Up Now!</h2>
<ul class="uk-list uk-list-striped">
<li>No personal information required</li>
<li>Only <strong id="btcprice">0</strong><i class="uk-icon-btc"></i> BTC</li>
<li>Supports all major platforms</li>
<li>Unthrottled Gigabit connection</li>
<li>1000Gb of transfer per month!</li>
</ul>
</div>
</div>
<div class="uk-width-2-3">
<div class="uk-panel">
<h2>Getting started is easy:</h2>
<p>
All you need to register is an email address! Bitcoin transactions are processed instantly,
and your access will be available within 10 minutes of purchase. Use our custom VPN client
for an install-and-go experience or download your client key and use the tools you're
familiar with! Click below to sign up for an account and let Packetcrypt secure your web traffic.
</p>
</span>
<a href="{{url_for('purchase')}}" class="uk-button uk-button-primary uk-button-large uk-align-right">Buy Now!</a>
</div>
</div>
</div>
</div>
</div>
</div>

{% endblock %}

+ 66
- 0
app/templates/layout.html View File

@@ -0,0 +1,66 @@
<html>
<head>
<!-- Stylesheets -->
<link rel="stylesheet" href="{{ url_for('static', filename='lib/uikit/css/uikit.gradient.min.css') }}" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}" />

<!-- Javascript -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="{{ url_for('static', filename='lib/uikit/js/uikit.min.js') }}"></script>

<title>{% block title %}{% endblock %}</title>
</head>
<body>
<div id="container" class="uk-grid">
<div id="big-top-bar" class="uk-width-1-1">
<div id="top-bar" class="">
<div class="uk-float-left"><img src="http://placehold.it/250x65" /></div>
<div class="uk-float-right uk-button-group">
<a class="uk-button" href="{{ url_for('index') }}">Home</a>
<a class="uk-button" href=" {{ url_for('blog') }}">Blog</a>
{% if g.user.is_authenticated() %}
<a class="uk-button uk-button-success" href="{{ url_for_security('logout') }}">Logoff</a>
<a class="uk-button uk-button-primary" href=" {{ url_for('dashboard') }}">Dashboard</a>
{% else %}
<a class="uk-button uk-button-success" href="{{ url_for_security('login') }}">Login</a>
<a class="uk-button uk-button-primary" href="{{ url_for_security('register') }}">Signup</a>
{% endif %}
{% if g.user.has_role('Admin') %}
<a class="uk-button uk-button-danger" href="/admin/">Admin Panel</a>
{% endif %}
</div>
</div>
<a href="http://bitcoin.org"><img id="bitcoin-logo" src="{{ url_for('static', filename='img/bitcoins.png') }}" /></a>
</div>
<div id="flash-wrapper" class="uk-width-1-1">
<div class="uk-panel uk-width-1-2 uk-container-center">
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="uk-alert uk-alert-success" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ message }}</div>
{% endfor %}
{% endif %}
{% block flash %}{% endblock %}
{% endwith %}
</div>
</div>
<div id="content" class="uk-width-1-1">
{% block content %}{% endblock %}
</div>
<div id="footer" class="uk-width-1-1">
<hr/>
<div class="uk-float-left">
&copy; 2013 PacketCrypt, Inc. All Rights Reserved. PacketCrypt&trade; is property of PacketCrypt, Inc.<br/> All other marks are the property of their respective owners.
<br />
<a href="">Terms</a> | <a href="">Privacy Policy</a> | <a href="">Contact us</a>
</div>
<div class="uk-float-right" id="footer-social">
<img src="{{ url_for('static', filename='img/reddit.png') }}"/>
<img src="{{ url_for('static', filename='img/twitter.png') }}"/>
<img src="{{ url_for('static', filename='img/facebook.png') }}"/>
</div>
</div>
</div>
{% block postscript %}{% endblock %}
</body>
</html>

+ 26
- 0
app/templates/newticket.html View File

@@ -0,0 +1,26 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_field_with_label, render_field %}

{% block title %}Create Support Ticket - packetcrypt{% endblock %}
{% block flash %}
{% for field in form.errors %}
{% for error in form.errors[field] %}
<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div>
{% endfor %}
{% endfor %}
{% endblock %}

{% block content %}
<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center">
<form action="{{ url_for('newticket') }}" method="POST" name="form" class="uk-form" style="width:100%;">
{{ form.hidden_tag() }}
{{ render_field_with_label(form.subject) }}
{{ render_field_with_label(form.body) }}
<div class="uk-form-row">
<button class="uk-button uk-button-primary" type="Submit">Create Ticket</button>
<button class="uk-button uk-button-danger" type="reset">Reset</button>
<a class="uk-button" href="{{ url_for('dashboard') }}">Cancel</a>
</div>
</form>
</div>
{% endblock %}

+ 24
- 0
app/templates/purchase.html View File

@@ -0,0 +1,24 @@
{% extends "layout.html" %}

{% block title %}Purchase VPN Service Now - packetcrypt{% endblock %}

{% block content %}
<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center">
<h4>Purchase PacketCrypt VPN Service</h4>
<ul>
<li>Service in 5 different countries</li>
<li>4 weeks of access</li>
<li>Price (USD): ${{service_price}}</li>
{% if price %}
<li>Price (BTC): {{ price }}</li>
{% endif %}
</ul>
{% if g.user.is_authenticated() %}
<a href="{{url_for('confirm_purchase')}}" class="uk-button uk-button-success">Purchase</a>
<a href="{{url_for('dashboard')}}" class="uk-button uk-button-danger">Cancel</a>
{% else %}
<a href="{{url_for_security('login')}}" class="uk-button uk-button-primary">Login to Purchase</a>
<a href="{{url_for_security('register')}}" class="uk-button uk-button-success">Register an Account</a>
{% endif %}
</div>
{% endblock %}

+ 23
- 0
app/templates/security/change_password.html View File

@@ -0,0 +1,23 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_field_with_label, render_field %}

{% block title %}Change Password - packetcrypt{% endblock %}
{% block flash %}
{% for field in change_password_form.errors %}
{% for error in change_password_form.errors[field] %}
<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div>
{% endfor %}
{% endfor %}
{% endblock %}

{% block content %}
<div id="form-container" class="uk-width-1-4 uk-panel uk-panel-box uk-container-center">
<form action="{{ url_for_security('change_password') }}" method="POST" name="change_password_form" class="uk-form">
{{ change_password_form.hidden_tag() }}
{{ render_field_with_label(change_password_form.password) }}
{{ render_field_with_label(change_password_form.new_password) }}
{{ render_field_with_label(change_password_form.new_password_confirm) }}
{{ render_field(change_password_form.submit) }}
</form>
</div>
{% endblock %}

+ 25
- 0
app/templates/security/forgot_password.html View File

@@ -0,0 +1,25 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_field_with_label, render_field %}

{% block title %}Forgot Password - packetcrypt{% endblock %}
{% block flash %}
{% for field in forgot_password_form.errors %}
{% for error in forgot_password_form.errors[field] %}
<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div>
{% endfor %}
{% endfor %}
{% endblock %}

{% block content %}
<div id="form-container" class="uk-width-3-5 uk-panel uk-panel-box uk-container-center">
<form action="{{ url_for_security('forgot_password') }}" method="POST" name="forgot_password_form" class="uk-form">
{{ forgot_password_form.hidden_tag() }}
{{ render_field_with_label(forgot_password_form.email) }}
{{ render_field(forgot_password_form.submit, class="uk-button") }}
</form>
<div id="form-info" class="uk-align-right uk-text-info">
Enter the email-address associated with your account to the left.
Instructions on resetting your password will be emailed to you.
</div>
</div>
{% endblock %}

+ 31
- 0
app/templates/security/login_user.html View File

@@ -0,0 +1,31 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_field_with_label, render_field_with_label_oneline, render_field %}

{% block title %}Login - packetcrypt{% endblock %}
{% block flash %}
{% for field in login_user_form.errors %}
{% for error in login_user_form.errors[field] %}
<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div>
{% endfor %}
{% endfor %}
{% endblock %}

{% block content %}
<div id="form-container" class="uk-width-3-5 uk-panel uk-panel-box uk-container-center">
<form action="{{ url_for_security('login') }}" method="POST" name="login_user_form" class="uk-form">
{{ login_user_form.hidden_tag() }}
{{ render_field_with_label(login_user_form.email) }}
{{ render_field_with_label(login_user_form.password) }}
{{ render_field_with_label_oneline(login_user_form.remember) }}
{{ render_field(login_user_form.next) }}
{{ render_field(login_user_form.submit, class="uk-button uk-button-primary") }}
</form>
<div id="form-info" class="uk-align-right uk-text-info">
Log in with your Packetcrypt email and password to access your dashboard, download the VPN client, and purchase extended service!
If you are unable to remember your password, please use the button below to reset it.

<a href="{{url_for_security('forgot_password')}}" class="uk-button">Forgot Password?</a>
</div>
</div>
{% endblock %}


+ 31
- 0
app/templates/security/register_user.html View File

@@ -0,0 +1,31 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_field_with_label, render_field %}

{% block title %}Register - packetcrypt{% endblock %}
{% block flash %}
{% for field in register_user_form.errors %}
{% for error in register_user_form.errors[field] %}
<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div>
{% endfor %}
{% endfor %}
{% endblock %}

{% block content %}
<div id="form-container" class="uk-width-3-5 uk-panel uk-panel-box uk-container-center">
<form action="{{ url_for_security('register') }}" method="POST" name="register_user_form" class="uk-form" style="display:inline-block;">
{{ register_user_form.hidden_tag() }}
{{ render_field_with_label(register_user_form.email) }}
{{ render_field_with_label(register_user_form.password) }}
{% if register_user_form.password_confirm %}
{{ render_field_with_label(register_user_form.password_confirm) }}
{% endif %}
{{ render_field(register_user_form.submit, class='uk-button uk-button-primary') }}
</form>
<div id="form-info" class="uk-align-right uk-text-info">
All we need is an email address and a password to create an account, and you'll be clicks away from browsing securely.
After registering please check your inbox for a verification email.

<br/><p><a href="{{url_for_security('login')}}" class="uk-text-bold uk-text-muted">Click here to sign in to an existing account.</a></p>
</div>
</div>
{% endblock %}

+ 22
- 0
app/templates/security/reset_password.html View File

@@ -0,0 +1,22 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_field_with_label, render_field %}

{% block title %}Reset Password - packetcrypt{% endblock %}
{% block flash %}
{% for field in reset_password_form.errors %}
{% for error in reset_password_form.errors[field] %}
<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div>
{% endfor %}
{% endfor %}
{% endblock %}

{% block content %}
<div id="form-container" class="uk-width-1-4 uk-panel uk-panel-box uk-container-center">
<form action="{{ url_for_security('reset_password') }}" method="POST" name="reset_password_form" class="uk-form">
{{ reset_password_form.hidden_tag() }}
{{ render_field_with_label(reset_password_form.password) }}
{{ render_field_with_label(reset_password_form.password_confirm) }}
{{ render_field(reset_password_form.submit) }}
</form>
</div>
{% endblock %}

+ 17
- 0
app/templates/viewticket.html View File

@@ -0,0 +1,17 @@
{% extends "layout.html" %}

{% block title %}View Support Ticket - packetcrypt{% endblock %}

{% block content %}
<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center">
<h4 class="uk-panel-title">{{ ticket.subject }}</h4>
<hr class="uk-panel-divider"/>
<span class="ticket-text">{{ ticket.body }}</span>
<br/>
<span class="uk-article-meta">{{ ticket.timestamp }}</span>
<br/><br/>
<a href="{{url_for('dashboard')}}" class="uk-button uk-button-primary" type="submit">Close</a>
<a href="{{url_for('editticket', tid=ticket.id)}}" class="uk-button uk-button-success">Edit</a>
<a href="{{url_for('deleteticket', tid=ticket.id)}}" class="uk-button uk-button-danger">Delete</a>
</div>
{% endblock %}

+ 201
- 0
app/views.py View File

@@ -0,0 +1,201 @@
from flask import render_template, flash, redirect, g, request, url_for, jsonify
from app import app, db
from models import Ticket, Invoice
from forms import TicketForm
from flask.ext.security import login_required, current_user
from config import BLOCKCHAIN_URL, SECRET_KEY, STASH_WALLET, PRICE_OF_SERVICE, CONFIRMATION_CAP
import simplejson as json
import urllib2

# Page routes
@app.route('/')
@app.route('/index')
def index():
user = g.user
return render_template("index.html", user=user)

@app.route('/blog')
def blog():
return "Da Blog!"

@app.route('/dashboard')
@login_required
def dashboard():
user = g.user
latest_invoice = user.invoices.order_by(Invoice.datepaid.desc()).first()
lastpaid = latest_invoice.datepaid if latest_invoice else "Unpurchased"
expires = latest_invoice.dateends if latest_invoice else "No service enabled"
return render_template("dashboard.html", user=user, latest=latest_invoice, lastpaid=lastpaid, expires=expires)

@app.route('/newticket', methods=['GET', 'POST'])
@login_required
def newticket():
user = g.user
form = TicketForm()
if form.validate_on_submit():
import datetime
t = Ticket()
form.populate_obj(t)
t.timestamp = datetime.datetime.utcnow()
t.created = datetime.datetime.utcnow()
t.user_id = user.id
db.session.add(t);
db.session.commit()

flash('New ticket submitted: ' + form.subject.data)
return redirect('/dashboard')
return render_template('newticket.html', form=form, user=user)

@app.route('/viewticket/<int:tid>', methods=['GET', 'POST'])
@login_required
def viewticket(tid):
user = g.user
t = Ticket.query.get(tid)
return render_template('viewticket.html', user=user, ticket=t)

@app.route('/editticket/<int:tid>', methods=['GET', 'POST'])
@login_required
def editticket(tid):
user = g.user
t = Ticket.query.get(tid)
form = TicketForm(subject=t.subject, body=t.body)
if form.validate_on_submit():
import datetime
form.populate_obj(t)
t.timestamp = datetime.datetime.utcnow()
flash("Updated ticket: " + t.subject)
db.session.commit()
return redirect('/dashboard')
return render_template('editticket.html', user=user, ticket=t, form=form)

@app.route('/deleteticket/<int:tid>', methods=['GET', 'POST'])
@login_required
def deleteticket(tid):
user = g.user
t = Ticket.query.get(tid)
return render_template('viewticket.html', user=user, ticket=t)

# Start Bitcoin stuff -- blockchain.info api
@app.route('/purchase')
def purchase():
user = g.user
try:
exchange_data = json.load(urllib2.build_opener().open(urllib2.Request("http://blockchain.info/tobtc?currency=USD&value=30")))
except urllib2.URLError as e:
flash('Unable to fetch current BTC exchange rate.')
return render_template('purchase.html', user=user, price=0, service_price=PRICE_OF_SERVICE)
return render_template('purchase.html', user=user, price=str(exchange_data), service_price=PRICE_OF_SERVICE)

@app.route('/confirm_purchase/', defaults={'invoice_id': None})
@app.route('/confirm_purchase/<int:invoice_id>', methods=['GET', 'POST'])
@login_required
def confirm_purchase(invoice_id):
import datetime
user = g.user
if invoice_id is None:
i = Invoice()
i.paid = False
i.datecreated = datetime.datetime.utcnow()
i.user_id = user.id
db.session.add(i)
db.session.commit()
try:
callback_url = url_for('pay_invoice', _external=True)+'?secret='+SECRET_KEY+'%26invoice_id='+str(i.id)
url = BLOCKCHAIN_URL+'?method=create&address='+STASH_WALLET+'&callback='+callback_url
xhr = urllib2.Request(url)
data = json.load(urllib2.build_opener().open(xhr))
price_data = json.load(urllib2.build_opener().open(urllib2.Request("http://blockchain.info/tobtc?currency=USD&value="+str(PRICE_OF_SERVICE))))
exchange_data = json.load(urllib2.build_opener().open(urllib2.Request("http://blockchain.info/ticker")))
app.logger.info("Sent to blockchain api: " + url)
except urllib2.URLError as e:
app.logger.error('Unable to access the blockchain.info api: ' + url)
flash('There was an error creating a new invoice. Please try again later.')
return redirect('/dashboard')
i.address = data['input_address']
i.total_btc = price_data
i.exchange_rate_when_paid = exchange_data['USD']['last']
db.session.commit()
# TODO: Generate a QR code and/or other e-z payment options for BTC services
return redirect(url_for('confirm_purchase', invoice_id=i.id))
else:
i = Invoice.query.get(invoice_id)
if request.method == 'POST':
flash('Invoice ('+i.address+') was deleted succesfully.')
db.session.delete(i)
db.session.commit()
return redirect(url_for('dashboard'))
return render_template('confirm_purchase.html', user=user, invoice=i, min_confirm=CONFIRMATION_CAP)


# AJAX Callbacks
@app.route('/invoice_status', methods=['POST'])
def invoice_status():
data = request.form
i = Invoice.query.get(data['invoice_id'])
if i is None:
return 0
return jsonify({
'confirmations': i.confirmations,
'value_paid': i.value_paid,
'total_btc': i.total_btc,
'input_transaction_hash': i.input_transaction_hash
})

@app.route('/pay_invoice', methods=['GET'])
def pay_invoice():
data = request.args
if 'test' in data:
app.logger.info('Test response recieved from Blockchain.info. return: *test*')
return "*test*"
if 'secret' in data and data['secret'] == SECRET_KEY:
import datetime
i = Invoice.query.get(data['invoice_id'])
if i is None:
# could not find invoice - do we ignore or create?
app.logger.info("Callback received for non-existant invoice. return: *error*")
return "*error*"
if not i.paid:
i.value_paid = float(data['value']) / 100000000
else:
i.value_paid += float(data['value']) / 100000000
i.datepaid = datetime.datetime.utcnow()
i.confirmations = data['confirmations']
i.transaction_hash = data['transaction_hash']
i.input_transaction_hash = data['input_transaction_hash']
if i.value_paid == i.total_btc:
app.logger.info("Invoice {} paid on {} for {} BTC.".format(i.id, i.datepaid, i.value_paid))
i.paid = True
db.session.commit()
if i.paid and i.confirmations > CONFIRMATION_CAP:
app.logger.info("Invoice {} was confirmed at {}. return: *ok*".format(i.id, i.datepaid))
i.is_confirmed = True
i.dateends = i.datepaid + datetime.timedelta(weeks=4)
return "*ok*"
app.logger.info("Callback received for invoice {}: awaiting confirmation (current: {}). return: *unconfirmed*".format(i.id, i.confirmations))
return "*unconfirmed*"
else:
app.logger.info('Payment callback with invalid secret key recieved. return: *error*')
return "*error*"

# Not routes
@app.errorhandler(404)
def internal_error(error):
return render_template('error.html'), 404

@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('error.html'), 500

@app.before_request
def before_request():
g.user = current_user

@app.template_filter('date')
def _jinja2_filter_datetime(date, fmt=None):
if not date:
return None
if fmt:
return date.strftime(fmt)
else:
return date.strftime("%m/%d/%y (%I:%M%p)")

Loading…
Cancel
Save