Browse Source

Initial commit

master
Brandon Cornejo 10 years ago
commit
845d5dd3ed
  1. 5
      .gitignore
  2. 0
      README.md
  3. 4
      app.py
  4. 66
      app/__init__.py
  5. 8
      app/admin.py
  6. 8
      app/forms.py
  7. 55
      app/models.py
  8. 96
      app/static/css/app.css
  9. BIN
      app/static/img/android.png
  10. BIN
      app/static/img/binary.jpg
  11. BIN
      app/static/img/bitcoins.png
  12. BIN
      app/static/img/facebook.png
  13. BIN
      app/static/img/gnupg.png
  14. BIN
      app/static/img/ios.png
  15. BIN
      app/static/img/openvpn.png
  16. BIN
      app/static/img/osx.png
  17. BIN
      app/static/img/reddit.png
  18. BIN
      app/static/img/twitter.png
  19. BIN
      app/static/img/windows.png
  20. 7643
      app/static/lib/uikit/css/uikit.almost-flat.css
  21. 3
      app/static/lib/uikit/css/uikit.almost-flat.min.css
  22. 6890
      app/static/lib/uikit/css/uikit.css
  23. 7759
      app/static/lib/uikit/css/uikit.gradient.css
  24. 3
      app/static/lib/uikit/css/uikit.gradient.min.css
  25. 3
      app/static/lib/uikit/css/uikit.min.css
  26. BIN
      app/static/lib/uikit/fonts/FontAwesome.otf
  27. BIN
      app/static/lib/uikit/fonts/fontawesome-webfont.eot
  28. BIN
      app/static/lib/uikit/fonts/fontawesome-webfont.ttf
  29. BIN
      app/static/lib/uikit/fonts/fontawesome-webfont.woff
  30. 1780
      app/static/lib/uikit/js/uikit.js
  31. 3
      app/static/lib/uikit/js/uikit.min.js
  32. 14
      app/templates/_macros.html
  33. 84
      app/templates/confirm_purchase.html
  34. 130
      app/templates/dashboard.html
  35. 26
      app/templates/editticket.html
  36. 9
      app/templates/error.html
  37. 88
      app/templates/index.html
  38. 66
      app/templates/layout.html
  39. 26
      app/templates/newticket.html
  40. 24
      app/templates/purchase.html
  41. 23
      app/templates/security/change_password.html
  42. 25
      app/templates/security/forgot_password.html
  43. 31
      app/templates/security/login_user.html
  44. 31
      app/templates/security/register_user.html
  45. 22
      app/templates/security/reset_password.html
  46. 17
      app/templates/viewticket.html
  47. 201
      app/views.py

5
.gitignore

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

0
README.md

4
app.py

@ -0,0 +1,4 @@
#!venv/bin/python
from app import app
app.run(host='0.0.0.0', debug=True)

66
app/__init__.py

@ -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
app/admin.py

@ -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
app/forms.py

@ -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
app/models.py

@ -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
app/static/css/app.css

@ -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
app/static/img/android.png

After

Width: 22  |  Height: 29  |  Size: 3.1 KiB

BIN
app/static/img/binary.jpg

After

Width: 2000  |  Height: 1500  |  Size: 470 KiB

BIN
app/static/img/bitcoins.png

After

Width: 1680  |  Height: 641  |  Size: 94 KiB

BIN
app/static/img/facebook.png

After

Width: 300  |  Height: 300  |  Size: 3.2 KiB

BIN
app/static/img/gnupg.png

After

Width: 512  |  Height: 512  |  Size: 78 KiB

BIN
app/static/img/ios.png

After

Width: 79  |  Height: 52  |  Size: 6.2 KiB

BIN
app/static/img/openvpn.png

After

Width: 256  |  Height: 256  |  Size: 6.9 KiB

BIN
app/static/img/osx.png

After

Width: 42  |  Height: 52  |  Size: 4.9 KiB

BIN
app/static/img/reddit.png

After

Width: 500  |  Height: 500  |  Size: 9.2 KiB

BIN
app/static/img/twitter.png

After

Width: 300  |  Height: 300  |  Size: 4.3 KiB

BIN
app/static/img/windows.png

After

Width: 51  |  Height: 52  |  Size: 6.4 KiB

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

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

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

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

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

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

BIN
app/static/lib/uikit/fonts/FontAwesome.otf

BIN
app/static/lib/uikit/fonts/fontawesome-webfont.eot

BIN
app/static/lib/uikit/fonts/fontawesome-webfont.ttf

BIN
app/static/lib/uikit/fonts/fontawesome-webfont.woff

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

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

14
app/templates/_macros.html

@ -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
app/templates/confirm_purchase.html

@ -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
app/templates/dashboard.html

@ -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
app/templates/editticket.html

@ -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
app/templates/error.html

@ -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
app/templates/index.html

@ -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
app/templates/layout.html

@ -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
app/templates/newticket.html

@ -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
app/templates/purchase.html

@ -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
app/templates/security/change_password.html

@ -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
app/templates/security/forgot_password.html

@ -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
app/templates/security/login_user.html

@ -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
app/templates/security/register_user.html

@ -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
app/templates/security/reset_password.html

@ -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
app/templates/viewticket.html

@ -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
app/views.py

@ -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