add wireguard, update lcore api

master
Alice 5 years ago
parent d51f3232a0
commit e67a61a315

@ -1,6 +1,6 @@
from django.conf import settings from django.conf import settings
from ccvpn.common import get_client_ip from ccvpn.common import get_client_ip
from lambdainst.core import is_vpn_gateway from django_lcore import is_vpn_gateway
def some_settings(request): def some_settings(request):

@ -42,6 +42,7 @@ INSTALLED_APPS = (
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.humanize', 'django.contrib.humanize',
'django_countries', 'django_countries',
'django_lcore',
'lambdainst', 'lambdainst',
'payments', 'payments',
'tickets', 'tickets',
@ -85,6 +86,7 @@ TEMPLATES = [
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'ccvpn.context_processors.some_settings', 'ccvpn.context_processors.some_settings',
'constance.context_processors.config',
], ],
}, },
}, },
@ -278,6 +280,12 @@ CONSTANCE_CONFIG = {
'NOTIFY_DAYS_BEFORE': ("3, 1", "When to send account expiration notifications. In number of days before, separated y commas", 'NOTIFY_DAYS_BEFORE': ("3, 1", "When to send account expiration notifications. In number of days before, separated y commas",
'integer_list'), 'integer_list'),
'KB_MAX_TOP_ENTRIES': (10, "Maximum number of categories to show in the short list"), 'KB_MAX_TOP_ENTRIES': (10, "Maximum number of categories to show in the short list"),
'WIREGUARD': (False, "Enable WireGuard support"),
'WIREGUARD_MAX_PEERS': (10, "Maximum number of WireGuard peers registered per user.", int),
'WIREGUARD_PUBKEY': ("", "WireGuard server public key"),
'WIREGUARD_PSK': ("", "WireGuard pre-shared key (optional)"),
'WIREGUARD_DNS': ("10.128.0.1", "WireGuard DNS server IP address"),
} }
CONSTANCE_ADDITIONAL_FIELDS = { CONSTANCE_ADDITIONAL_FIELDS = {
@ -302,6 +310,16 @@ TINYMCE_DEFAULT_CONFIG = {
'browser_spellcheck': True, 'browser_spellcheck': True,
} }
VPN_DOMAIN = "204vpn.net"
VPN_FULL_NAME = "CCrypto VPN"
VPN_SHORT_NAME = "ccvpn"
OPENVPN_CONFIG_HEADER = """\
# +----------------------------+
# | Cognitive Cryptography VPN |
# | https://vpn.ccrypto.org/ |
# +----------------------------+
"""
# Local settings # Local settings
try: try:

@ -2,6 +2,7 @@ from django.urls import path, include
from django.contrib import admin from django.contrib import admin
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
import django_lcore
from lambdainst import views as account_views from lambdainst import views as account_views
@ -11,9 +12,10 @@ urlpatterns = [
path('admin/status', account_views.admin_status, name='admin_status'), path('admin/status', account_views.admin_status, name='admin_status'),
path('admin/referrers', account_views.admin_ref, name='admin_ref'), path('admin/referrers', account_views.admin_ref, name='admin_ref'),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('vpn/', include(django_lcore.urls)),
path('api/locations', account_views.api_locations), path('api/locations', account_views.api_locations),
path('api/auth', account_views.api_auth), path('api/auth', django_lcore.views.api_auth),
path('', views.index, name='index'), path('', views.index, name='index'),
path('ca.crt', account_views.ca_crt), path('ca.crt', account_views.ca_crt),

@ -2,17 +2,20 @@ import string
from django.shortcuts import resolve_url from django.shortcuts import resolve_url
from django import forms from django import forms
from django.contrib import admin from django.contrib import admin
from django.contrib import messages
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.html import format_html
import django_lcore
import lcoreapi
from lambdainst.models import VPNUser, GiftCode, GiftCodeUser from lambdainst.models import VPNUser, GiftCode, GiftCodeUser
from . import core
def make_user_link(user): def make_user_link(user):
change_url = resolve_url('admin:auth_user_change', user.id) change_url = resolve_url('admin:auth_user_change', user.id)
return '<a href="%s">%s</a>' % (change_url, user.username) return format_html('<a href="{}">{}</a>', change_url, user.username)
class GiftCodeAdminForm(forms.ModelForm): class GiftCodeAdminForm(forms.ModelForm):
@ -32,8 +35,8 @@ class VPNUserInline(admin.StackedInline):
fk_name = 'user' fk_name = 'user'
fields = ('notes', 'expiration', 'last_expiry_notice', 'notify_expiration', fields = ('notes', 'expiration', 'last_expiry_notice', 'notify_expiration',
'trial_periods_given', 'referrer_a', 'campaign', 'last_vpn_auth') 'trial_periods_given', 'referrer_a', 'campaign', 'last_vpn_auth', 'last_core_sync')
readonly_fields = ('referrer_a', 'last_vpn_auth', 'campaign') readonly_fields = ('referrer_a', 'last_vpn_auth', 'last_core_sync', 'campaign')
def referrer_a(self, object): def referrer_a(self, object):
if not object.referrer: if not object.referrer:
@ -63,13 +66,11 @@ class GiftCodeUserAdmin(admin.TabularInline):
def user_link(self, object): def user_link(self, object):
return make_user_link(object.user) return make_user_link(object.user)
user_link.allow_tags = True
user_link.short_description = 'User' user_link.short_description = 'User'
def code_link(self, object): def code_link(self, object):
change_url = resolve_url('admin:lambdainst_giftcode_change', object.code.id) change_url = resolve_url('admin:lambdainst_giftcode_change', object.code.id)
return '<a href="%s">%s</a>' % (change_url, object.code.code) return format_html('<a href="{}">{}</a>', change_url, object.code.code)
code_link.allow_tags = True
code_link.short_description = 'Code' code_link.short_description = 'Code'
def has_add_permission(self, request): def has_add_permission(self, request):
@ -90,6 +91,7 @@ class UserAdmin(UserAdmin):
'groups', 'user_permissions')}), 'groups', 'user_permissions')}),
) )
readonly_fields = ('last_login', 'date_joined', 'links') readonly_fields = ('last_login', 'date_joined', 'links')
actions = (django_lcore.core_sync_action, )
def is_paid(self, object): def is_paid(self, object):
return object.vpnuser.is_paid return object.vpnuser.is_paid
@ -97,24 +99,26 @@ class UserAdmin(UserAdmin):
is_paid.short_description = _("Is paid?") is_paid.short_description = _("Is paid?")
def links(self, object): def links(self, object):
fmt = '<a href="%s?user__id__exact=%d">%s</a>'
payments_url = resolve_url('admin:payments_payment_changelist') payments_url = resolve_url('admin:payments_payment_changelist')
tickets_url = resolve_url('admin:tickets_ticket_changelist') tickets_url = resolve_url('admin:tickets_ticket_changelist')
s = fmt % (payments_url, object.id, "Payments") fmt = '<a href="{}?user__id__exact={}">{}</a>'
s += ' - ' + fmt % (tickets_url, object.id, "Tickets") return format_html(fmt + " - " + fmt,
return s payments_url, object.id, "Payments",
links.allow_tags = True tickets_url, object.id, "Tickets",
)
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change) super().save_model(request, obj, form, change)
# Notify core # Notify core
if change and core.VPN_AUTH_STORAGE == 'core': if change:
core.update_user_expiration(obj) django_lcore.sync_user(obj.vpnuser)
def delete_model(self, request, obj): def delete_model(self, request, obj):
if core.VPN_AUTH_STORAGE == 'core': try:
core.delete_user(obj.username) django_lcore.api.get_user(obj.username).delete()
except lcoreapi.APIError as e:
messages.error(request, "failed to delete vpn user: %r" % e)
super().delete_model(request, obj) super().delete_model(request, obj)

@ -1,177 +0,0 @@
from datetime import timedelta, datetime
import lcoreapi
from django.conf import settings
import logging
cluster_messages = settings.LAMBDAINST_CLUSTER_MESSAGES
lcore_settings = settings.LCORE
LCORE_BASE_URL = lcore_settings.get('BASE_URL')
LCORE_API_KEY = lcore_settings['API_KEY']
LCORE_API_SECRET = lcore_settings['API_SECRET']
LCORE_SOURCE_ADDR = lcore_settings.get('SOURCE_ADDRESS')
LCORE_INST_SECRET = lcore_settings['INST_SECRET']
LCORE_TIMEOUT = lcore_settings.get('TIMEOUT', 10)
# The default is to log the exception and only raise it if we cannot show
# the previous value or a default value instead.
LCORE_RAISE_ERRORS = bool(lcore_settings.get('RAISE_ERRORS', False))
LCORE_CACHE_TTL = lcore_settings.get('CACHE_TTL', 60)
if isinstance(LCORE_CACHE_TTL, int):
LCORE_CACHE_TTL = timedelta(seconds=LCORE_CACHE_TTL)
assert isinstance(LCORE_CACHE_TTL, timedelta)
VPN_AUTH_STORAGE = settings.VPN_AUTH_STORAGE
assert VPN_AUTH_STORAGE in ('core', 'inst')
core_api = lcoreapi.API(LCORE_API_KEY, LCORE_API_SECRET, LCORE_BASE_URL,
timeout=LCORE_TIMEOUT)
class APICache:
""" Cache data for a time, try to update and silence errors.
Outdated data is not a problem.
"""
def __init__(self, ttl=None, initial=None):
self.cache_date = datetime.fromtimestamp(0)
self.ttl = ttl or LCORE_CACHE_TTL
self.has_cached_value = initial is not None
self.cached = initial() if initial else None
def query(self, wrapped, *args, **kwargs):
try:
return wrapped(*args, **kwargs)
except lcoreapi.APIError:
logger = logging.getLogger('django.request')
logger.exception("core api error")
if LCORE_RAISE_ERRORS:
raise
if not self.has_cached_value:
# We only return a default value if we were given one.
# Prevents returning an unexpected None.
raise
# Return previous value
return self.cached
def __call__(self, wrapped):
def wrapper(*args, **kwargs):
if self.cache_date > (datetime.now() - self.ttl):
return self.cached
self.cached = self.query(wrapped, *args, **kwargs)
# New results *and* errors are cached
self.cache_date = datetime.now()
return self.cached
return wrapper
@APICache(initial=lambda: 0)
def current_active_sessions():
return core_api.get(core_api.info['current_instance'] + '/sessions', active=True)['total_count']
@APICache(initial=lambda: [])
def get_locations():
gateways = core_api.get('gateways/', enabled=True)
locations = {}
for gw in gateways.list_iter():
cc = gw['cluster_name']
if cc not in locations:
locations[cc] = dict(
servers=0,
bandwidth=0,
hostname='gw.' + cc + '.204vpn.net',
country_code=cc,
message=cluster_messages.get(cc),
)
locations[cc]['servers'] += 1
locations[cc]['bandwidth'] += gw['bandwidth'] or 0
locations = sorted(locations.items(), key=lambda x: x[1]['country_code'])
return locations
@APICache(initial=lambda: [])
def get_gateway_exit_ips():
gateways = core_api.get('gateways/', enabled=True)
ipv4_list = []
ipv6_list = []
for gw in gateways.list_iter():
ma = gw['main_addr']
if ma.get('ipv4'):
ipv4_list.append(ma['ipv4'])
if ma.get('ipv6'):
ipv6_list.append(ma['ipv6'])
# TODO: IPv6 support
return ipv4_list
def is_vpn_gateway(ip):
addresses = get_gateway_exit_ips()
return ip in addresses
def create_user(username, cleartext_password):
""" The password will be hashed and stored safely on the core,
so we have to send it clearly here.
"""
path = core_api.info['current_instance'] + '/users/'
core_api.post(path, data={
'username': username,
'password': cleartext_password,
'expiration_date': datetime(1, 1, 1).isoformat(), # Expired.
})
def update_user_expiration(user):
path = core_api.info['current_instance'] + '/users/' + user.username
try:
if not user.is_active:
core_api.patch(path, data={
'expiration_date': datetime(1, 1, 1).isoformat(), # Expired.
})
return
core_api.patch(path, data={
'expiration_date': user.vpnuser.expiration,
})
except lcoreapi.APIError:
# User can't do anything to this, we should just report it
logger = logging.getLogger('django.request')
logger.exception("core api error, missing user (exp update)")
def update_user_password(user, cleartext_password):
path = core_api.info['current_instance'] + '/users/' + user.username
try:
core_api.patch(path, data={
'password': cleartext_password,
})
except lcoreapi.APINotFoundError:
# This time we can try fix it!
create_user(user.username, cleartext_password)
except lcoreapi.APIError:
# and maybe fail.
logger = logging.getLogger('django.request')
logger.exception("core api error (password update)")
def delete_user(username):
path = core_api.info['current_instance'] + '/users/' + username
core_api.delete(path)

@ -62,3 +62,17 @@ class ReqEmailForm(forms.Form, FormPureRender):
) )
def wg_pk_validator(s):
try:
data = base64.b64decode(s, validate=True)
except:
raise forms.ValidationError("Invalid public key format")
if len(data) != 32:
raise forms.ValidationError("Invalid public key length")
class WgPeerForm(forms.Form):
public_key = forms.CharField(min_length=3, max_length=100, strip=True, required=False, validators=[
wg_pk_validator
])
name = forms.CharField(max_length=21, required=False)

@ -1,11 +0,0 @@
from django.core.management.base import BaseCommand
from lambdainst.core import core_api
class Command(BaseCommand):
help = "Get informations about core API"
def handle(self, *args, **options):
for k, v in core_api.info.items():
print("%s: %s" % (k, v))

@ -0,0 +1,18 @@
# Generated by Django 2.2.8 on 2020-03-24 04:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('lambdainst', '0002_vpnuser_campaign'),
]
operations = [
migrations.AddField(
model_name='vpnuser',
name='last_core_sync',
field=models.DateTimeField(blank=True, null=True),
),
]

@ -3,12 +3,11 @@ from datetime import timedelta
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils import timezone
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from constance import config as site_config from constance import config as site_config
from django_lcore.core import LcoreUserProfileMethods, setup_sync_hooks
from . import core
from ccvpn.common import get_trial_period_duration from ccvpn.common import get_trial_period_duration
from payments.models import Subscription from payments.models import Subscription
@ -20,7 +19,7 @@ def random_gift_code():
return ''.join([prng.choice(charset) for n in range(10)]) return ''.join([prng.choice(charset) for n in range(10)])
class VPNUser(models.Model): class VPNUser(models.Model, LcoreUserProfileMethods):
class Meta: class Meta:
verbose_name = _("VPN User") verbose_name = _("VPN User")
verbose_name_plural = _("VPN Users") verbose_name_plural = _("VPN Users")
@ -35,45 +34,13 @@ class VPNUser(models.Model):
trial_periods_given = models.IntegerField(default=0) trial_periods_given = models.IntegerField(default=0)
last_vpn_auth = models.DateTimeField(blank=True, null=True) last_vpn_auth = models.DateTimeField(blank=True, null=True)
last_core_sync = models.DateTimeField(blank=True, null=True)
referrer = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL, referrer = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL,
related_name='referrals') related_name='referrals')
referrer_used = models.BooleanField(default=False) referrer_used = models.BooleanField(default=False)
campaign = models.CharField(blank=True, null=True, max_length=64) campaign = models.CharField(blank=True, null=True, max_length=64)
@property
def is_paid(self):
if self.get_subscription():
return True
if not self.expiration:
return False
return self.expiration > timezone.now()
@property
def time_left(self):
return self.expiration - timezone.now()
def add_paid_time(self, time):
now = timezone.now()
if not self.expiration or self.expiration < now:
self.expiration = now
self.expiration += time
# Propagate update to core
if core.VPN_AUTH_STORAGE == 'core':
core.update_user_expiration(self.user)
def remove_paid_time(self, time):
now = timezone.now()
if self.expiration < now:
return
self.expiration -= time
if core.VPN_AUTH_STORAGE == 'core':
core.update_user_expiration(self.user)
def give_trial_period(self): def give_trial_period(self):
self.add_paid_time(get_trial_period_duration()) self.add_paid_time(get_trial_period_duration())
self.trial_periods_given += 1 self.trial_periods_given += 1
@ -111,6 +78,7 @@ class VPNUser(models.Model):
def __str__(self): def __str__(self):
return self.user.username return self.user.username
setup_sync_hooks(User, VPNUser)
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def create_vpnuser(sender, instance, created, **kwargs): def create_vpnuser(sender, instance, created, **kwargs):

@ -1,5 +1,6 @@
from django.urls import path from django.urls import path
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
import django_lcore
from . import views from . import views
@ -12,8 +13,10 @@ urlpatterns = [
path('signup', views.signup, name='signup'), path('signup', views.signup, name='signup'),
path('settings', views.settings), path('settings', views.settings),
path('config_dl', views.config_dl),
path('config', views.config), path('config', views.config),
path('config_dl', django_lcore.views.openvpn_dl),
path('wireguard', views.wireguard),
path('wireguard/new', views.wireguard_new, name='wireguard_new'),
path('logs', views.logs), path('logs', views.logs),
path('gift_code', views.gift_code), path('gift_code', views.gift_code),
path('trial', views.trial), path('trial', views.trial),

@ -1,41 +1,34 @@
import requests
import io
import zipfile
import hmac
import base64 import base64
import hmac
from datetime import datetime, timedelta
from hashlib import sha256 from hashlib import sha256
from urllib.parse import urlencode, parse_qsl from urllib.parse import parse_qsl, urlencode
from datetime import timedelta, datetime
import requests
from django.http import ( from constance import config as site_config
HttpResponse, JsonResponse,
HttpResponseRedirect,
HttpResponseNotFound, HttpResponseForbidden
)
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.admin.sites import site
from django.contrib import messages
from django.utils.translation import ugettext as _
from django.utils import timezone
from django.conf import settings as project_settings from django.conf import settings as project_settings
from django.views.decorators.csrf import csrf_exempt from django.contrib import auth, messages
from django.db.models import Count from django.contrib.admin.sites import site
from django.contrib import auth from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import Count
from django.http import (HttpResponse,
HttpResponseNotFound, HttpResponseRedirect,
JsonResponse)
from django.shortcuts import redirect, render
from django.utils import timezone
from django.utils.translation import ugettext as _
from django.template.loader import render_to_string
from django_countries import countries from django_countries import countries
from constance import config as site_config import qrcode
import django_lcore
import lcoreapi import lcoreapi
from ccvpn.common import get_client_ip, get_price_float from ccvpn.common import get_client_ip, get_price_float
from payments.models import ACTIVE_BACKENDS from payments.models import ACTIVE_BACKENDS
from .forms import SignupForm, ReqEmailForm from .forms import SignupForm, ReqEmailForm, WgPeerForm
from .models import GiftCode, VPNUser from .models import GiftCode, VPNUser
from .core import core_api
from . import core
from . import graphs from . import graphs
from . import openvpn
def get_locations(): def get_locations():
@ -43,13 +36,21 @@ def get_locations():
that depends on the request that depends on the request
""" """
countries_d = dict(countries) countries_d = dict(countries)
locations = core.get_locations() locations = django_lcore.get_clusters()
for k, v in locations: for (_), loc in locations:
cc = v['country_code'].upper() code = loc['country_code'].upper()
v['country_name'] = countries_d.get(cc, cc) loc['country_name'] = countries_d.get(code, code)
return locations return locations
def log_errors(request, form):
errors = []
for field in form:
for e in field.errors:
errors.append(e)
messages.add_message(request, messages.ERROR, ", ".join(errors))
def ca_crt(request): def ca_crt(request):
return HttpResponse(content=project_settings.OPENVPN_CA, return HttpResponse(content=project_settings.OPENVPN_CA,
content_type='application/x-x509-ca-cert') content_type='application/x-x509-ca-cert')
@ -78,9 +79,6 @@ def signup(request):
form.cleaned_data['password']) form.cleaned_data['password'])
user.save() user.save()
if core.VPN_AUTH_STORAGE == 'core':
core.create_user(form.cleaned_data['username'], form.cleaned_data['password'])
try: try:
user.vpnuser.referrer = User.objects.get(id=request.session.get('referrer')) user.vpnuser.referrer = User.objects.get(id=request.session.get('referrer'))
except User.DoesNotExist: except User.DoesNotExist:
@ -89,6 +87,7 @@ def signup(request):
user.vpnuser.campaign = request.session.get('campaign') user.vpnuser.campaign = request.session.get('campaign')
user.vpnuser.save() user.vpnuser.save()
django_lcore.sync_user(user.vpnuser, True)
user.backend = 'django.contrib.auth.backends.ModelBackend' user.backend = 'django.contrib.auth.backends.ModelBackend'
auth.login(request, user) auth.login(request, user)
@ -234,10 +233,7 @@ def settings(request):
messages.error(request, _("Passwords do not match")) messages.error(request, _("Passwords do not match"))
else: else:
request.user.set_password(pw) request.user.set_password(pw)
django_lcore.sync_user(request.user.vpnuser)
if core.VPN_AUTH_STORAGE == 'core':
core.update_user_password(request.user, pw)
messages.success(request, _("OK!")) messages.success(request, _("OK!"))
email = request.POST.get('email') email = request.POST.get('email')
@ -274,10 +270,10 @@ def logs(request):
page = int(request.GET.get('page', 0)) page = int(request.GET.get('page', 0))
offset = page * page_size offset = page * page_size
base = core_api.info['current_instance'] base = django_lcore.api.info['current_instance']
path = '/users/' + request.user.username + '/sessions/' path = '/users/' + request.user.username + '/sessions/'
try: try:
l = core_api.get(base + path, offset=offset, limit=page_size) l = django_lcore.api.get(base + path, offset=offset, limit=page_size)
total_count = l['total_count'] total_count = l['total_count']
items = l['items'] items = l['items']
except lcoreapi.APINotFoundError: except lcoreapi.APINotFoundError:
@ -297,105 +293,12 @@ def logs(request):
def config(request): def config(request):
return render(request, 'lambdainst/config.html', dict( return render(request, 'lambdainst/config.html', dict(
title=_("Config"), title=_("Config"),
config_os=openvpn.CONFIG_OS, config_os=django_lcore.openvpn.CONFIG_OS,
config_countries=(c for _, c in get_locations()), config_countries=(c for _, c in get_locations()),
config_protocols=openvpn.PROTOCOLS, config_protocols=django_lcore.openvpn.PROTOCOLS,
)) ))
@login_required
def config_dl(request):
allowed_cc = [cc for (cc, _) in get_locations()]
os = request.GET.get('client_os')
common_options = {
'username': request.user.username,
'protocol': request.GET.get('protocol'),
'os': os,
'http_proxy': request.GET.get('http_proxy'),
'ipv6': 'enable_ipv6' in request.GET,
}
# Should be validated since it's used in the filename
# other common options are only put in the config file
protocol = common_options['protocol']
if protocol not in ('udp', 'udpl', 'tcp'):
return HttpResponseNotFound()
location = request.GET.get('gateway')
if location == 'all':
# Multiple gateways in a zip archive
f = io.BytesIO()
z = zipfile.ZipFile(f, mode='w')
for gw_name in allowed_cc + ['random']:
if os == 'chromeos':
filename = 'ccrypto-%s-%s.onc' % (gw_name, protocol)
else:
filename = 'ccrypto-%s-%s.ovpn' % (gw_name, protocol)
config = openvpn.make_config(gw_name=gw_name, **common_options)
z.writestr(filename, config.encode('utf-8'))
z.close()
r = HttpResponse(content=f.getvalue(), content_type='application/zip')
r['Content-Disposition'] = 'attachment; filename="%s.zip"' % filename
return r
else:
# Single gateway
if location[3:] in allowed_cc:
gw_name = location[3:]
else:
gw_name = 'random'
if os == 'chromeos':
filename = 'ccrypto-%s-%s.onc' % (gw_name, protocol)
else:
filename = 'ccrypto-%s-%s.ovpn' % (gw_name, protocol)
config = openvpn.make_config(gw_name=gw_name, **common_options)
if 'plain' in request.GET:
return HttpResponse(content=config, content_type='text/plain')
else:
if os == 'chromeos':
r = HttpResponse(content=config, content_type='application/x-onc')
else:
r = HttpResponse(content=config, content_type='application/x-openvpn-profile')
r['Content-Disposition'] = 'attachment; filename="%s"' % filename
return r
@csrf_exempt
def api_auth(request):
if request.method != 'POST':
return HttpResponseNotFound()
if core.VPN_AUTH_STORAGE != 'inst':
return HttpResponseNotFound()
username = request.POST.get('username')
password = request.POST.get('password')
secret = request.POST.get('secret')
if secret != core.LCORE_INST_SECRET:
return HttpResponseForbidden(content="Invalid secret")
user = authenticate(username=username, password=password)
if not user or not user.is_active:
return JsonResponse(dict(status='fail', message="Invalid credentials"))
if not user.vpnuser.is_paid:
return JsonResponse(dict(status='fail', message="Not allowed to connect"))
user.vpnuser.last_vpn_auth = timezone.now()
user.vpnuser.save()
return JsonResponse(dict(status='ok'))
def api_locations(request): def api_locations(request):
def format_loc(cc, l): def format_loc(cc, l):
msg = ' [%s]' % l['message'] if l['message'] else '' msg = ' [%s]' % l['message'] if l['message'] else ''
@ -415,8 +318,8 @@ def status(request):
ctx = { ctx = {
'title': _("Status"), 'title': _("Status"),
'n_users': VPNUser.objects.filter(expiration__gte=timezone.now()).count(), 'n_users': VPNUser.objects.filter(expiration__gte=timezone.now()).count(),
'n_sess': core.current_active_sessions(), 'n_sess': django_lcore.count_active_sessions(),
'n_gws': sum(l['servers'] for cc, l in locations), 'n_gws': sum(len(l['servers']) for cc, l in locations),
'n_countries': len(set(cc for cc, l in locations)), 'n_countries': len(set(cc for cc, l in locations)),
'total_bw': sum(l['bandwidth'] for cc, l in locations), 'total_bw': sum(l['bandwidth'] for cc, l in locations),
'locations': locations, 'locations': locations,
@ -444,8 +347,10 @@ def admin_status(request):
payment_status = ((b, b.get_info()) for b in ACTIVE_BACKENDS.values()) payment_status = ((b, b.get_info()) for b in ACTIVE_BACKENDS.values())
payment_status = ((b, i) for (b, i) in payment_status if i) payment_status = ((b, i) for (b, i) in payment_status if i)
lcore_keys = {'core_name', 'core_now', 'core_version', 'current_instance', 'key_public'}
ctx = { ctx = {
'api_status': {k: str(v) for k, v in core_api.info.items()}, 'api_status': {k: str(v) for k, v in django_lcore.api.info.items() if k in lcore_keys},
'payment_backends': sorted(ACTIVE_BACKENDS.values(), key=lambda x: x.backend_id), 'payment_backends': sorted(ACTIVE_BACKENDS.values(), key=lambda x: x.backend_id),
'payment_status': payment_status, 'payment_status': payment_status,
} }
@ -475,6 +380,59 @@ def admin_ref(request):
return render(request, 'lambdainst/admin_ref.html', ctx) return render(request, 'lambdainst/admin_ref.html', ctx)
@login_required
def wireguard(request):
api = django_lcore.api
if request.method == 'POST':
action = request.POST.get('action')
if action == 'delete_key':
key = api.get_wg_peer(request.user.username, request.POST.get('peer_id'))
if key:
key.delete()
elif action == 'set_name':
key = api.get_wg_peer(request.user.username, request.POST.get('peer_id'))
if key:
name = request.POST.get('name')
if name:
name = name[:21]
key.rename(name)
return redirect(request.path)
try:
keys = api.get_wg_peers(request.user.username)
except lcoreapi.APINotFoundError:
django_lcore.sync_user(request.user.vpnuser)
keys = []
context = dict(
can_create_key=len(keys) < int(site_config.WIREGUARD_MAX_PEERS),
menu_item='wireguard',
enabled=request.user.vpnuser.is_paid,
config_countries=[(k, v) for (k, v) in django_lcore.get_clusters()],
keys=keys,
)
return render(request, 'lambdainst/wireguard.html', context)
@login_required
def wireguard_new(request):
api = django_lcore.api
if request.method == 'POST':
action = request.POST.get('action')
form = WgPeerForm(request.POST)
if action == 'add_key':
if form.is_valid():
api.create_wg_peer(
request.user.username,
public_key=form.cleaned_data['public_key'],
name=form.cleaned_data['name'],
)
else:
log_errors(request, form)
return redirect('/account/wireguard')
context = dict(
menu_item='wireguard',
enabled=request.user.vpnuser.is_paid,
)
return render(request, 'lambdainst/wireguard_new.html', context)

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-23 11:55+0000\n" "POT-Creation-Date: 2020-03-27 18:32+0000\n"
"PO-Revision-Date: 2016-04-07 01:32+0000\n" "PO-Revision-Date: 2016-04-07 01:32+0000\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
@ -35,39 +35,39 @@ msgstr "Chat Live"
msgid "Download {} v{}" msgid "Download {} v{}"
msgstr "Télécharger {} v{}" msgstr "Télécharger {} v{}"
#: lambdainst/admin.py:23 #: lambdainst/admin.py:25
msgid "Code must be [a-zA-Z0-9]" msgid "Code must be [a-zA-Z0-9]"
msgstr "" msgstr ""
#: lambdainst/admin.py:25 #: lambdainst/admin.py:27
msgid "Code must be between 1 and 32 characters" msgid "Code must be between 1 and 32 characters"
msgstr "" msgstr ""
#: lambdainst/admin.py:44 #: lambdainst/admin.py:46
msgid "(rewarded)" msgid "(rewarded)"
msgstr "" msgstr ""
#: lambdainst/admin.py:46 #: lambdainst/admin.py:48
msgid "(not rewarded)" msgid "(not rewarded)"
msgstr "" msgstr ""
#: lambdainst/admin.py:49 #: lambdainst/admin.py:51
msgid "Referrer" msgid "Referrer"
msgstr "" msgstr ""
#: lambdainst/admin.py:54 lambdainst/admin.py:97 #: lambdainst/admin.py:56 lambdainst/admin.py:100
msgid "Is paid?" msgid "Is paid?"
msgstr "Est payé?" msgstr "Est payé?"
#: lambdainst/admin.py:88 #: lambdainst/admin.py:90
msgid "Important dates" msgid "Important dates"
msgstr "Dates importantes" msgstr "Dates importantes"
#: lambdainst/admin.py:89 #: lambdainst/admin.py:91
msgid "Permissions" msgid "Permissions"
msgstr "Permissions" msgstr "Permissions"
#: lambdainst/admin.py:134 tickets/admin.py:44 #: lambdainst/admin.py:139 tickets/admin.py:44
msgid "Comment" msgid "Comment"
msgstr "Notes" msgstr "Notes"
@ -106,27 +106,27 @@ msgstr "E-Mail"
msgid "Passwords are not the same" msgid "Passwords are not the same"
msgstr "Les mots de passe de correspondent pas" msgstr "Les mots de passe de correspondent pas"
#: lambdainst/models.py:25 #: lambdainst/models.py:24
msgid "VPN User" msgid "VPN User"
msgstr "VPN User" msgstr "VPN User"
#: lambdainst/models.py:26 #: lambdainst/models.py:25
msgid "VPN Users" msgid "VPN Users"
msgstr "VPN Users" msgstr "VPN Users"
#: lambdainst/models.py:123 #: lambdainst/models.py:91
msgid "Gift Code" msgid "Gift Code"
msgstr "Code cadeau" msgstr "Code cadeau"
#: lambdainst/models.py:124 #: lambdainst/models.py:92
msgid "Gift Codes" msgid "Gift Codes"
msgstr "Codes cadeau" msgstr "Codes cadeau"
#: lambdainst/models.py:169 #: lambdainst/models.py:137
msgid "Gift Code User" msgid "Gift Code User"
msgstr "Utilisateur de codes" msgstr "Utilisateur de codes"
#: lambdainst/models.py:170 #: lambdainst/models.py:138
msgid "Gift Code Users" msgid "Gift Code Users"
msgstr "Utilisateurs de codes" msgstr "Utilisateurs de codes"
@ -213,50 +213,50 @@ msgstr ""
msgid "%s Pbps" msgid "%s Pbps"
msgstr "" msgstr ""
#: lambdainst/views.py:157 #: lambdainst/views.py:156
msgid "Awesome VPN! 3€ per month, with a free 7 days trial!" msgid "Awesome VPN! 3€ per month, with a free 7 days trial!"
msgstr "" msgstr ""
#: lambdainst/views.py:173 templates/account_layout.html:10 #: lambdainst/views.py:172 templates/account_layout.html:10
#: templates/lambdainst/account.html:17 #: templates/lambdainst/account.html:17
msgid "Account" msgid "Account"
msgstr "Compte" msgstr "Compte"
#: lambdainst/views.py:218 lambdainst/views.py:241 lambdainst/views.py:266 #: lambdainst/views.py:217 lambdainst/views.py:237 lambdainst/views.py:262
msgid "OK!" msgid "OK!"
msgstr "OK!" msgstr "OK!"
#: lambdainst/views.py:220 #: lambdainst/views.py:219
msgid "Invalid captcha" msgid "Invalid captcha"
msgstr "Captcha invalide" msgstr "Captcha invalide"
#: lambdainst/views.py:234 #: lambdainst/views.py:233
msgid "Passwords do not match" msgid "Passwords do not match"
msgstr "Les mots de passe ne correspondent pas" msgstr "Les mots de passe ne correspondent pas"
#: lambdainst/views.py:251 templates/account_layout.html:22 #: lambdainst/views.py:247 templates/account_layout.html:28
#: templates/lambdainst/settings.html:6 #: templates/lambdainst/settings.html:6
msgid "Settings" msgid "Settings"
msgstr "Options" msgstr "Options"
#: lambdainst/views.py:262 #: lambdainst/views.py:258
msgid "Gift code not found or already used." msgid "Gift code not found or already used."
msgstr "Code inconnu ou déjà utilisé." msgstr "Code inconnu ou déjà utilisé."
#: lambdainst/views.py:264 #: lambdainst/views.py:260
msgid "Gift code only available to free accounts." msgid "Gift code only available to free accounts."
msgstr "Code uniquement disponible pour les nouveaux comptes." msgstr "Code uniquement disponible pour les nouveaux comptes."
#: lambdainst/views.py:292 templates/account_layout.html:30 #: lambdainst/views.py:288 templates/account_layout.html:36
#: templates/lambdainst/logs.html:6 #: templates/lambdainst/logs.html:6
msgid "Logs" msgid "Logs"
msgstr "Logs" msgstr "Logs"
#: lambdainst/views.py:299 templates/lambdainst/config.html:7 #: lambdainst/views.py:295 templates/lambdainst/config.html:7
msgid "Config" msgid "Config"
msgstr "Config" msgstr "Config"
#: lambdainst/views.py:416 payments/backends/bitcoin.py:90 #: lambdainst/views.py:319 payments/backends/bitcoin.py:90
#: payments/backends/bitcoin.py:92 templates/admin/index.html:53 #: payments/backends/bitcoin.py:92 templates/admin/index.html:53
#: templates/admin/index.html:56 templates/lambdainst/admin_ref.html:16 #: templates/admin/index.html:56 templates/lambdainst/admin_ref.html:16
#: templates/lambdainst/admin_status.html:16 templates/payments/list.html:13 #: templates/lambdainst/admin_status.html:16 templates/payments/list.html:13
@ -426,10 +426,14 @@ msgid "Overview"
msgstr "Vue d'ensemble" msgstr "Vue d'ensemble"
#: templates/account_layout.html:18 #: templates/account_layout.html:18
msgid "Config Download" msgid "OpenVPN Config"
msgstr "Configuration" msgstr "Config OpenVPN"
#: templates/account_layout.html:26 templates/payments/list.html:6 #: templates/account_layout.html:23
msgid "WireGuard"
msgstr ""
#: templates/account_layout.html:32 templates/payments/list.html:6
msgid "Payments" msgid "Payments"
msgstr "Paiements" msgstr "Paiements"
@ -657,7 +661,7 @@ msgstr "N'importe quoi entre 1 et 256 caractères."
msgid "Same password." msgid "Same password."
msgstr "Le même mot de passe." msgstr "Le même mot de passe."
#: templates/ccvpn/signup.html:33 #: templates/ccvpn/signup.html:33 templates/lambdainst/wireguard_new.html:30
msgid "Optional." msgid "Optional."
msgstr "Optionnel." msgstr "Optionnel."
@ -845,6 +849,7 @@ msgid "Gateway"
msgstr "Serveur" msgstr "Serveur"
#: templates/lambdainst/config.html:22 #: templates/lambdainst/config.html:22
#: templates/lambdainst/wireguard_peer.html:26
msgid "Random" msgid "Random"
msgstr "Aléatoire" msgstr "Aléatoire"
@ -881,6 +886,7 @@ msgid "Requires TCP."
msgstr "Nécéssite TCP." msgstr "Nécéssite TCP."
#: templates/lambdainst/config.html:56 #: templates/lambdainst/config.html:56
#: templates/lambdainst/wireguard_new.html:38
msgid "Enable IPv6?" msgid "Enable IPv6?"
msgstr "Activer l'IPv6?" msgstr "Activer l'IPv6?"
@ -941,6 +947,7 @@ msgid "repeat"
msgstr "répétez" msgstr "répétez"
#: templates/lambdainst/settings.html:27 #: templates/lambdainst/settings.html:27
#: templates/lambdainst/wireguard.html:158
msgid "Save" msgid "Save"
msgstr "Enregistrer" msgstr "Enregistrer"
@ -994,6 +1001,74 @@ msgstr "Nom"
msgid "Servers" msgid "Servers"
msgstr "Serveurs" msgstr "Serveurs"
#: templates/lambdainst/wireguard.html:10
msgid ""
"This page lets you manage WireGuard peers. Each can only have one concurrent "
"connection."
msgstr ""
"Gérez vos appareils et clés WireGuard. Chaque clé permet une connexion "
"simultanée."
#: templates/lambdainst/wireguard.html:18
msgid "Your Devices"
msgstr "Vos appareils"
#: templates/lambdainst/wireguard.html:23
#: templates/lambdainst/wireguard_new.html:5
#: templates/lambdainst/wireguard_new.html:7
msgid "New Device"
msgstr "Nouvel appareil"
#: templates/lambdainst/wireguard.html:32
msgid "Key"
msgstr "Clé"
#: templates/lambdainst/wireguard.html:33
#: templates/lambdainst/wireguard_new.html:15
#: templates/lambdainst/wireguard_new.html:17
msgid "Name"
msgstr "Nom"
#: templates/lambdainst/wireguard.html:34
msgid "Actions"
msgstr "Actions"
#: templates/lambdainst/wireguard.html:46
msgid "Download"
msgstr "Télécharger"
#: templates/lambdainst/wireguard.html:51
msgid "Show QR Code"
msgstr "Afficher le code QR"
#: templates/lambdainst/wireguard.html:56
msgid "Edit"
msgstr "Éditer"
#: templates/lambdainst/wireguard.html:65
msgid "Delete"
msgstr "Supprimer"
#: templates/lambdainst/wireguard_new.html:20
msgid "Used to identify the device in your account."
msgstr "Utilisé pour l'identifier dans votre compte."
#: templates/lambdainst/wireguard_new.html:25
msgid "Public Key"
msgstr "Clé publique"
#: templates/lambdainst/wireguard_new.html:27
msgid "Public key (base64)"
msgstr "Clé publique (base64)"
#: templates/lambdainst/wireguard_new.html:31
msgid "Use an existing public key. Leave empty to generate a new one."
msgstr "Pour générer votre propre clé privée. Laissez vide sinon."
#: templates/lambdainst/wireguard_new.html:42
msgid "Create Key"
msgstr "Ajouter"
#: templates/layout.html:26 #: templates/layout.html:26
msgid "Service Status" msgid "Service Status"
msgstr "État des services" msgstr "État des services"

210
poetry.lock generated

@ -37,8 +37,8 @@ description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\"" marker = "sys_platform == \"win32\""
name = "colorama" name = "colorama"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.4.1" version = "0.4.3"
[[package]] [[package]]
category = "main" category = "main"
@ -46,7 +46,7 @@ description = "A high-level Python Web framework that encourages rapid developme
name = "django" name = "django"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
version = "2.2.8" version = "2.2.11"
[package.dependencies] [package.dependencies]
pytz = "*" pytz = "*"
@ -100,18 +100,35 @@ description = "JSONField for django models"
name = "django-jsonfield" name = "django-jsonfield"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.3.1" version = "1.4.0"
[package.dependencies] [package.dependencies]
Django = ">=1.9" Django = ">=1.11"
six = "*"
[[package]]
category = "main"
description = "a reusable django app for common lambdacore instance code"
name = "django-lcore"
optional = false
python-versions = "*"
version = "1.1.0"
[package.dependencies]
django = ">=2.2"
lcoreapi = "*"
[package.source]
reference = "94ef8c9467d1d71c86d95439404b7c40202fa1ec"
type = "git"
url = "https://git.packetimpact.net/lvpn/django-lcore.git"
[[package]] [[package]]
category = "main" category = "main"
description = "Pickled object field for Django" description = "Pickled object field for Django"
name = "django-picklefield" name = "django-picklefield"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "2.0" version = "2.1.1"
[package.dependencies] [package.dependencies]
Django = ">=1.11" Django = ">=1.11"
@ -137,7 +154,7 @@ description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna" name = "idna"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.8" version = "2.9"
[[package]] [[package]]
category = "dev" category = "dev"
@ -175,13 +192,13 @@ description = "lambdacore client api"
name = "lcoreapi" name = "lcoreapi"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.0" version = "1.1.1"
[package.dependencies] [package.dependencies]
requests = "*" requests = "*"
[package.source] [package.source]
reference = "d72f38a6c3658ac856358d53f22fbddae6503ff7" reference = "df771a7dbd1a2166a9a873539f976865f6f6a630"
type = "git" type = "git"
url = "https://git.packetimpact.net/lvpn/lcoreapi.git" url = "https://git.packetimpact.net/lvpn/lcoreapi.git"
[[package]] [[package]]
@ -233,8 +250,8 @@ category = "main"
description = "Pygments is a syntax highlighting package written in Python." description = "Pygments is a syntax highlighting package written in Python."
name = "pygments" name = "pygments"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=3.5"
version = "2.5.2" version = "2.6.1"
[[package]] [[package]]
category = "dev" category = "dev"
@ -250,6 +267,33 @@ colorama = "*"
isort = ">=4.2.5,<5" isort = ">=4.2.5,<5"
mccabe = ">=0.6,<0.7" mccabe = ">=0.6,<0.7"
[[package]]
category = "dev"
description = "A Pylint plugin to help Pylint understand the Django web framework"
name = "pylint-django"
optional = false
python-versions = "*"
version = "2.0.14"
[package.dependencies]
pylint = ">=2.0"
pylint-plugin-utils = ">=0.5"
[package.extras]
for_tests = ["coverage", "django-tables2", "factory-boy", "pytest"]
with_django = ["django"]
[[package]]
category = "dev"
description = "Utilities and helpers for writing Pylint plugins"
name = "pylint-plugin-utils"
optional = false
python-versions = "*"
version = "0.6"
[package.dependencies]
pylint = ">=1.7"
[[package]] [[package]]
category = "main" category = "main"
description = "The Swiss Army Knife of the Bitcoin protocol." description = "The Swiss Army Knife of the Bitcoin protocol."
@ -283,8 +327,8 @@ category = "main"
description = "YAML parser and emitter for Python" description = "YAML parser and emitter for Python"
name = "pyyaml" name = "pyyaml"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "5.2" version = "5.3.1"
[[package]] [[package]]
category = "main" category = "main"
@ -292,16 +336,16 @@ description = "Python HTTP for Humans."
name = "requests" name = "requests"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.22.0" version = "2.23.0"
[package.dependencies] [package.dependencies]
certifi = ">=2017.4.17" certifi = ">=2017.4.17"
chardet = ">=3.0.2,<3.1.0" chardet = ">=3.0.2,<4"
idna = ">=2.5,<2.9" idna = ">=2.5,<3"
urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
[package.extras] [package.extras]
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
[[package]] [[package]]
@ -309,8 +353,8 @@ category = "main"
description = "Python 2 and 3 compatibility utilities" description = "Python 2 and 3 compatibility utilities"
name = "six" name = "six"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
version = "1.13.0" version = "1.14.0"
[[package]] [[package]]
category = "main" category = "main"
@ -318,7 +362,7 @@ description = "Non-validating SQL parser"
name = "sqlparse" name = "sqlparse"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.3.0" version = "0.3.1"
[[package]] [[package]]
category = "main" category = "main"
@ -326,7 +370,7 @@ description = "Python bindings for the Stripe API"
name = "stripe" name = "stripe"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.41.0" version = "2.44.0"
[package.dependencies] [package.dependencies]
[package.dependencies.requests] [package.dependencies.requests]
@ -340,15 +384,15 @@ marker = "implementation_name == \"cpython\" and python_version < \"3.8\""
name = "typed-ast" name = "typed-ast"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.4.0" version = "1.4.1"
[[package]] [[package]]
category = "main" category = "main"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
name = "urllib3" name = "urllib3"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
version = "1.25.7" version = "1.25.8"
[package.extras] [package.extras]
brotli = ["brotlipy (>=0.6.0)"] brotli = ["brotlipy (>=0.6.0)"]
@ -364,7 +408,7 @@ python-versions = "*"
version = "1.11.2" version = "1.11.2"
[metadata] [metadata]
content-hash = "c00450bd56aa5a669d833686086c1839e5f67dd1ed271fa7a1898d88d6a9ae10" content-hash = "6484d8b6029f1cbbeaa6013676535b59801aef7f1e9bd03bf3f06a7dd8acd37a"
python-versions = "^3.5" python-versions = "^3.5"
[metadata.files] [metadata.files]
@ -381,12 +425,12 @@ chardet = [
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
] ]
colorama = [ colorama = [
{file = "colorama-0.4.1-py2.py3-none-any.whl", hash = "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"}, {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
{file = "colorama-0.4.1.tar.gz", hash = "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d"}, {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
] ]
django = [ django = [
{file = "Django-2.2.8-py3-none-any.whl", hash = "sha256:fa98ec9cc9bf5d72a08ebf3654a9452e761fbb8566e3f80de199cbc15477e891"}, {file = "Django-2.2.11-py3-none-any.whl", hash = "sha256:b51c9c548d5c3b3ccbb133d0bebc992e8ec3f14899bce8936e6fdda6b23a1881"},
{file = "Django-2.2.8.tar.gz", hash = "sha256:a4ad4f6f9c6a4b7af7e2deec8d0cbff28501852e5010d6c2dc695d3d1fae7ca0"}, {file = "Django-2.2.11.tar.gz", hash = "sha256:65e2387e6bde531d3bb803244a2b74e0253550a9612c64a60c8c5be267b30f50"},
] ]
django-admin-list-filter-dropdown = [ django-admin-list-filter-dropdown = [
{file = "django-admin-list-filter-dropdown-1.0.3.tar.gz", hash = "sha256:07cd37b6a9be1b08f11d4a92957c69b67bc70b1f87a2a7d4ae886c93ea51eb53"}, {file = "django-admin-list-filter-dropdown-1.0.3.tar.gz", hash = "sha256:07cd37b6a9be1b08f11d4a92957c69b67bc70b1f87a2a7d4ae886c93ea51eb53"},
@ -401,20 +445,21 @@ django-countries = [
{file = "django_countries-5.3.3-py2.py3-none-any.whl", hash = "sha256:e4eaaec9bddb9365365109f833d1fd0ecc0cfee3348bf5441c0ccefb2d6917cd"}, {file = "django_countries-5.3.3-py2.py3-none-any.whl", hash = "sha256:e4eaaec9bddb9365365109f833d1fd0ecc0cfee3348bf5441c0ccefb2d6917cd"},
] ]
django-jsonfield = [ django-jsonfield = [
{file = "django-jsonfield-1.3.1.tar.gz", hash = "sha256:df2a0cefa4daeb56b074cf178b59ced0d1b4f31e6bbfdfb488755507eabfbf93"}, {file = "django-jsonfield-1.4.0.tar.gz", hash = "sha256:9b3dac9f7352a6d37e9cfe0126c5b58ac7abf1cb20c0da294a00269a725223f1"},
{file = "django_jsonfield-1.3.1-py2.py3-none-any.whl", hash = "sha256:431caef3d6a93ad2b1844d26520cd104c474c7dd9dacf3dcb2f72888bf17e284"}, {file = "django_jsonfield-1.4.0-py2.py3-none-any.whl", hash = "sha256:7bdd0ea75ad842b9e33decdf343398c91fbb7bd664fde0648ef83e78b0453b6e"},
] ]
django-lcore = []
django-picklefield = [ django-picklefield = [
{file = "django-picklefield-2.0.tar.gz", hash = "sha256:f1733a8db1b6046c0d7d738e785f9875aa3c198215de11993463a9339aa4ea24"}, {file = "django-picklefield-2.1.1.tar.gz", hash = "sha256:67a5e156343e3b032cac2f65565f0faa81635a99c7da74b0f07a0f5db467b646"},
{file = "django_picklefield-2.0-py2.py3-none-any.whl", hash = "sha256:9052f2dcf4882c683ce87b4356f29b4d014c0dad645b6906baf9f09571f52bc8"}, {file = "django_picklefield-2.1.1-py2.py3-none-any.whl", hash = "sha256:e03cb181b7161af38ad6b573af127e4fe9b7cc2c455b42c1ec43eaad525ade0a"},
] ]
django-tinymce4-lite = [ django-tinymce4-lite = [
{file = "django-tinymce4-lite-1.7.5.tar.gz", hash = "sha256:f0958117ddacc72596e80746729e02a727264413ab54b799f3b697a44e054e87"}, {file = "django-tinymce4-lite-1.7.5.tar.gz", hash = "sha256:f0958117ddacc72596e80746729e02a727264413ab54b799f3b697a44e054e87"},
{file = "django_tinymce4_lite-1.7.5-py2.py3-none-any.whl", hash = "sha256:e6776bc5b2c7237705fea18668574bc1c4dff36babc90c99a2bb7b5d636eb5e8"}, {file = "django_tinymce4_lite-1.7.5-py2.py3-none-any.whl", hash = "sha256:e6776bc5b2c7237705fea18668574bc1c4dff36babc90c99a2bb7b5d636eb5e8"},
] ]
idna = [ idna = [
{file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"},
{file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"},
] ]
isort = [ isort = [
{file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"},
@ -494,13 +539,21 @@ pygal = [
{file = "pygal-2.4.0.tar.gz", hash = "sha256:9204f05380b02a8a32f9bf99d310b51aa2a932cba5b369f7a4dc3705f0a4ce83"}, {file = "pygal-2.4.0.tar.gz", hash = "sha256:9204f05380b02a8a32f9bf99d310b51aa2a932cba5b369f7a4dc3705f0a4ce83"},
] ]
pygments = [ pygments = [
{file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"}, {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"},
{file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"}, {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"},
] ]
pylint = [ pylint = [
{file = "pylint-2.4.4-py3-none-any.whl", hash = "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"}, {file = "pylint-2.4.4-py3-none-any.whl", hash = "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"},
{file = "pylint-2.4.4.tar.gz", hash = "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd"}, {file = "pylint-2.4.4.tar.gz", hash = "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd"},
] ]
pylint-django = [
{file = "pylint-django-2.0.14.tar.gz", hash = "sha256:c9bbcff6b87ee8466fae274fd7aae3d2d3d4c4d1ea20c48cbce673e837e36048"},
{file = "pylint_django-2.0.14-py3-none-any.whl", hash = "sha256:3a4cc19dd6301fc2d36c9fb6e15163001a6d12723c1f7f8c2249223c2a8c68f0"},
]
pylint-plugin-utils = [
{file = "pylint-plugin-utils-0.6.tar.gz", hash = "sha256:57625dcca20140f43731311cd8fd879318bf45a8b0fd17020717a8781714a25a"},
{file = "pylint_plugin_utils-0.6-py3-none-any.whl", hash = "sha256:2f30510e1c46edf268d3a195b2849bd98a1b9433229bb2ba63b8d776e1fc4d0a"},
]
python-bitcoinlib = [ python-bitcoinlib = [
{file = "python-bitcoinlib-0.10.2.tar.gz", hash = "sha256:bdb270ded594b8dead58fd6830ad14f880c25ec1fd2ca1be24e9e85decefce04"}, {file = "python-bitcoinlib-0.10.2.tar.gz", hash = "sha256:bdb270ded594b8dead58fd6830ad14f880c25ec1fd2ca1be24e9e85decefce04"},
{file = "python_bitcoinlib-0.10.2-py2-none-any.whl", hash = "sha256:5df2777f4b47dc4f3e63ee02fd92ac3cc06d79a9fd6a2284c8d345cd8e750d25"}, {file = "python_bitcoinlib-0.10.2-py2-none-any.whl", hash = "sha256:5df2777f4b47dc4f3e63ee02fd92ac3cc06d79a9fd6a2284c8d345cd8e750d25"},
@ -515,59 +568,60 @@ pytz = [
{file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"},
] ]
pyyaml = [ pyyaml = [
{file = "PyYAML-5.2-cp27-cp27m-win32.whl", hash = "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc"}, {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"},
{file = "PyYAML-5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"}, {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"},
{file = "PyYAML-5.2-cp35-cp35m-win32.whl", hash = "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15"}, {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"},
{file = "PyYAML-5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075"}, {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"},
{file = "PyYAML-5.2-cp36-cp36m-win32.whl", hash = "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31"}, {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"},
{file = "PyYAML-5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc"}, {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"},
{file = "PyYAML-5.2-cp37-cp37m-win32.whl", hash = "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04"}, {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"},
{file = "PyYAML-5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd"}, {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"},
{file = "PyYAML-5.2-cp38-cp38-win32.whl", hash = "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f"}, {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"},
{file = "PyYAML-5.2-cp38-cp38-win_amd64.whl", hash = "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803"}, {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"},
{file = "PyYAML-5.2.tar.gz", hash = "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c"}, {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"},
] ]
requests = [ requests = [
{file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"},
{file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"},
] ]
six = [ six = [
{file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"}, {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
{file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"}, {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
] ]
sqlparse = [ sqlparse = [
{file = "sqlparse-0.3.0-py2.py3-none-any.whl", hash = "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177"}, {file = "sqlparse-0.3.1-py2.py3-none-any.whl", hash = "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e"},
{file = "sqlparse-0.3.0.tar.gz", hash = "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"}, {file = "sqlparse-0.3.1.tar.gz", hash = "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"},
] ]
stripe = [ stripe = [
{file = "stripe-2.41.0-py2.py3-none-any.whl", hash = "sha256:50900b01a6db4f4f8db0f02a5125db0b2d732252c7683f73187abd08b7d238f3"}, {file = "stripe-2.44.0-py2.py3-none-any.whl", hash = "sha256:c16f92ae001e415340fffd73c97ec29b2e1f6591b905e8741c4ff02eab35e104"},
{file = "stripe-2.41.0.tar.gz", hash = "sha256:2f0ec677136985ece9cca232f106c2a87193261cac1fe58d4e959215310a0da8"}, {file = "stripe-2.44.0.tar.gz", hash = "sha256:b15ce50cac5961ce63873906eac1492a12ed01a6ba3606aca831a1741b724a29"},
] ]
typed-ast = [ typed-ast = [
{file = "typed_ast-1.4.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e"}, {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
{file = "typed_ast-1.4.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b"}, {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
{file = "typed_ast-1.4.0-cp35-cp35m-win32.whl", hash = "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4"}, {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
{file = "typed_ast-1.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"}, {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
{file = "typed_ast-1.4.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631"}, {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
{file = "typed_ast-1.4.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233"}, {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
{file = "typed_ast-1.4.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1"}, {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
{file = "typed_ast-1.4.0-cp36-cp36m-win32.whl", hash = "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a"}, {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
{file = "typed_ast-1.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c"}, {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
{file = "typed_ast-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a"}, {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
{file = "typed_ast-1.4.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e"}, {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
{file = "typed_ast-1.4.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d"}, {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
{file = "typed_ast-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36"}, {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
{file = "typed_ast-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0"}, {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
{file = "typed_ast-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66"}, {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
{file = "typed_ast-1.4.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2"}, {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
{file = "typed_ast-1.4.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47"}, {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
{file = "typed_ast-1.4.0-cp38-cp38-win32.whl", hash = "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161"}, {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
{file = "typed_ast-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e"}, {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
{file = "typed_ast-1.4.0.tar.gz", hash = "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34"}, {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
{file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
] ]
urllib3 = [ urllib3 = [
{file = "urllib3-1.25.7-py2.py3-none-any.whl", hash = "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293"}, {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"},
{file = "urllib3-1.25.7.tar.gz", hash = "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"}, {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"},
] ]
wrapt = [ wrapt = [
{file = "wrapt-1.11.2.tar.gz", hash = "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"}, {file = "wrapt-1.11.2.tar.gz", hash = "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"},

@ -16,15 +16,17 @@ pytz = "^2019.1"
python-bitcoinlib = "^0.10.1" python-bitcoinlib = "^0.10.1"
stripe = "^2.24" stripe = "^2.24"
django-constance = {version = "=2.4",extras = ["database"]} django-constance = {version = "=2.4",extras = ["database"]}
lcoreapi = {git = "https://git.packetimpact.net/lvpn/lcoreapi.git"} lcoreapi = {git = "https://git.packetimpact.net/lvpn/lcoreapi.git", tag = "v1.1.1"}
pygments = "^2.3" pygments = "^2.3"
psycopg2-binary = "^2.8" psycopg2-binary = "^2.8"
python-frontmatter = "^0.4.5" python-frontmatter = "^0.4.5"
django-tinymce4-lite = "^1.7" django-tinymce4-lite = "^1.7"
django-admin-list-filter-dropdown = "^1.0" django-admin-list-filter-dropdown = "^1.0"
django-lcore = {git = "https://git.packetimpact.net/lvpn/django-lcore.git", tag = "v1.1.0"}
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pylint = "^2.3" pylint = "^2.3"
pylint-django = "^2.0.14"
[build-system] [build-system]
requires = ["poetry>=0.12"] requires = ["poetry>=0.12"]

@ -725,6 +725,87 @@ a.home-signup-button {
} }
/***************************************************/
/********************* WireGuard */
.wireguard-peer-actions a {
margin: 0 0.25em;
}
.wireguard-qr {
text-align: center;
}
.wireguard-qr img {
width: 305px;
}
.wireguard-peer-info td {
padding: 0.25em 0.5em;
}
.wireguard-key {
font-family: monospace;
font-size: 1.1em;
background: #ddd;
padding: 5px 3px 0 3px;
}
.wireguard-list-header h3 {
display: inline-block;
}
.wireguard-list-header a {
float: right;
}
.page-wg-title {
display: flex;
flex-direction: row;
}
.page-wg-title h3 {
flex-grow: 1;
}
.page-wg-title p {
margin: 0;
}
.page-wg-table {
width: 100%;
margin-top: 0.5em;
}
.page-wg-table__action {
text-align: center;
}
.page-wg-shadow {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.75);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.page-wg-shadow__loading {
width: 100%;
height: 100%;
background: url("../img/spinner.gif") center no-repeat;
}
.page-wg-shadow canvas, .page-wg-shadow img {
border: 3px solid white;
}
td.page-wg-table__action {
padding: 0.5em 0.27em;
}
td.page-wg-table__action--more {
border-left: 0;
}
/***************************************************/ /***************************************************/
/********************* Tickets */ /********************* Tickets */
@ -879,6 +960,28 @@ div.ticket-message-private {
/***************************************************/
/********************* Icons */
.icon {
display: inline-block;
background-position: center;
background-size: contain;
background-repeat: no-repeat;
}
.icon-wireguard { background-image: url(../img/icon-wireguard.png); }
.icon-wireguard {
/* fix for wireguard at least */
height: 0.9em;
position: relative;
bottom: -1px;
}
@media screen and (max-width: 64em) { @media screen and (max-width: 64em) {
.content { .content {
padding: 0em 1em 3em 1em; padding: 0em 1em 3em 1em;

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

@ -0,0 +1,614 @@
/**
* @fileoverview
* - Using the 'QRCode for Javascript library'
* - Fixed dataset of 'QRCode for Javascript library' for support full-spec.
* - this library has no dependencies.
*
* @author davidshimjs
* @see <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a>
* @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a>
*/
var QRCode;
(function () {
//---------------------------------------------------------------------
// QRCode for JavaScript
//
// Copyright (c) 2009 Kazuhiko Arase
//
// URL: http://www.d-project.com/
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license.php
//
// The word "QR Code" is registered trademark of
// DENSO WAVE INCORPORATED
// http://www.denso-wave.com/qrcode/faqpatent-e.html
//
//---------------------------------------------------------------------
function QR8bitByte(data) {
this.mode = QRMode.MODE_8BIT_BYTE;
this.data = data;
this.parsedData = [];
// Added to support UTF-8 Characters
for (var i = 0, l = this.data.length; i < l; i++) {
var byteArray = [];
var code = this.data.charCodeAt(i);
if (code > 0x10000) {
byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18);
byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12);
byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6);
byteArray[3] = 0x80 | (code & 0x3F);
} else if (code > 0x800) {
byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12);
byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6);
byteArray[2] = 0x80 | (code & 0x3F);
} else if (code > 0x80) {
byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6);
byteArray[1] = 0x80 | (code & 0x3F);
} else {
byteArray[0] = code;
}
this.parsedData.push(byteArray);
}
this.parsedData = Array.prototype.concat.apply([], this.parsedData);
if (this.parsedData.length != this.data.length) {
this.parsedData.unshift(191);
this.parsedData.unshift(187);
this.parsedData.unshift(239);
}
}
QR8bitByte.prototype = {
getLength: function (buffer) {
return this.parsedData.length;
},
write: function (buffer) {
for (var i = 0, l = this.parsedData.length; i < l; i++) {
buffer.put(this.parsedData[i], 8);
}
}
};
function QRCodeModel(typeNumber, errorCorrectLevel) {
this.typeNumber = typeNumber;
this.errorCorrectLevel = errorCorrectLevel;
this.modules = null;
this.moduleCount = 0;
this.dataCache = null;
this.dataList = [];
}
QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+","+col);}
return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}
this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=7){this.setupTypeNumber(test);}
if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}
this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}}
return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}
return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}
this.modules[r][6]=(r%2==0);}
for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}
this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}
for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;}
for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}}
for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}}
this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex<data.length){dark=(((data[byteIndex]>>>bitIndex)&1)==1);}
var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}
this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}
row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}
var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}
if(buffer.getLengthInBits()>totalDataCount*8){throw new Error("code length overflow. ("
+buffer.getLengthInBits()
+">"
+totalDataCount*8
+")");}
if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}
while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}
while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;}
buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;}
buffer.put(QRCodeModel.PAD1,8);}
return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}
offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}
var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}
var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}
for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}
return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}
return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}
return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}
return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}
return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error("mode:"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error("mode:"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error("mode:"+mode);}}else{throw new Error("type:"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}
for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}
if(r==0&&c==0){continue;}
if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}
if(sameCount>5){lostPoint+=(3+sameCount-5);}}}
for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}
for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}
for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}
var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}
var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error("glog("+n+")");}
return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}
while(n>=256){n-=255;}
return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<<i;}
for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}
for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}
function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+"/"+shift);}
var offset=0;while(offset<num.length&&num[offset]==0){offset++;}
this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}
QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}
return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}
var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}
for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}
return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}
QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+errorCorrectLevel);}
var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}
return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}
QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);}
if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}
this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];
function _isSupportCanvas() {
return typeof CanvasRenderingContext2D != "undefined";
}
// android 2.x doesn't support Data-URI spec
function _getAndroid() {
var android = false;
var sAgent = navigator.userAgent;
if (/android/i.test(sAgent)) { // android
android = true;
var aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i);
if (aMat && aMat[1]) {
android = parseFloat(aMat[1]);
}
}
return android;
}
var svgDrawer = (function() {
var Drawing = function (el, htOption) {
this._el = el;
this._htOption = htOption;
};
Drawing.prototype.draw = function (oQRCode) {
var _htOption = this._htOption;
var _el = this._el;
var nCount = oQRCode.getModuleCount();
var nWidth = Math.floor(_htOption.width / nCount);
var nHeight = Math.floor(_htOption.height / nCount);
this.clear();
function makeSVG(tag, attrs) {
var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
for (var k in attrs)
if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);
return el;
}
var svg = makeSVG("svg" , {'viewBox': '0 0 ' + String(nCount) + " " + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight});
svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
_el.appendChild(svg);
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorLight, "width": "100%", "height": "100%"}));
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorDark, "width": "1", "height": "1", "id": "template"}));
for (var row = 0; row < nCount; row++) {
for (var col = 0; col < nCount; col++) {
if (oQRCode.isDark(row, col)) {
var child = makeSVG("use", {"x": String(col), "y": String(row)});
child.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#template")
svg.appendChild(child);
}
}
}
};
Drawing.prototype.clear = function () {
while (this._el.hasChildNodes())
this._el.removeChild(this._el.lastChild);
};
return Drawing;
})();
var useSVG = document.documentElement.tagName.toLowerCase() === "svg";
// Drawing in DOM by using Table tag
var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () {
var Drawing = function (el, htOption) {
this._el = el;
this._htOption = htOption;
};
/**
* Draw the QRCode
*
* @param {QRCode} oQRCode
*/
Drawing.prototype.draw = function (oQRCode) {
var _htOption = this._htOption;
var _el = this._el;
var nCount = oQRCode.getModuleCount();
var nWidth = Math.floor(_htOption.width / nCount);
var nHeight = Math.floor(_htOption.height / nCount);
var aHTML = ['<table style="border:0;border-collapse:collapse;">'];
for (var row = 0; row < nCount; row++) {
aHTML.push('<tr>');
for (var col = 0; col < nCount; col++) {
aHTML.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';"></td>');
}
aHTML.push('</tr>');
}
aHTML.push('</table>');
_el.innerHTML = aHTML.join('');
// Fix the margin values as real size.
var elTable = _el.childNodes[0];
var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2;
var nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2;
if (nLeftMarginTable > 0 && nTopMarginTable > 0) {
elTable.style.margin = nTopMarginTable + "px " + nLeftMarginTable + "px";
}
};
/**
* Clear the QRCode
*/
Drawing.prototype.clear = function () {
this._el.innerHTML = '';
};
return Drawing;
})() : (function () { // Drawing in Canvas
function _onMakeImage() {
this._elImage.src = this._elCanvas.toDataURL("image/png");
this._elImage.style.display = "block";
this._elCanvas.style.display = "none";
}
// Android 2.1 bug workaround
// http://code.google.com/p/android/issues/detail?id=5141
if (this._android && this._android <= 2.1) {
var factor = 1 / window.devicePixelRatio;
var drawImage = CanvasRenderingContext2D.prototype.drawImage;
CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
if (("nodeName" in image) && /img/i.test(image.nodeName)) {
for (var i = arguments.length - 1; i >= 1; i--) {
arguments[i] = arguments[i] * factor;
}
} else if (typeof dw == "undefined") {
arguments[1] *= factor;
arguments[2] *= factor;
arguments[3] *= factor;
arguments[4] *= factor;
}
drawImage.apply(this, arguments);
};
}
/**
* Check whether the user's browser supports Data URI or not
*
* @private
* @param {Function} fSuccess Occurs if it supports Data URI
* @param {Function} fFail Occurs if it doesn't support Data URI
*/
function _safeSetDataURI(fSuccess, fFail) {
var self = this;
self._fFail = fFail;
self._fSuccess = fSuccess;
// Check it just once
if (self._bSupportDataURI === null) {
var el = document.createElement("img");
var fOnError = function() {
self._bSupportDataURI = false;
if (self._fFail) {
self._fFail.call(self);
}
};
var fOnSuccess = function() {
self._bSupportDataURI = true;
if (self._fSuccess) {
self._fSuccess.call(self);
}
};
el.onabort = fOnError;
el.onerror = fOnError;
el.onload = fOnSuccess;
el.src = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // the Image contains 1px data.
return;
} else if (self._bSupportDataURI === true && self._fSuccess) {
self._fSuccess.call(self);
} else if (self._bSupportDataURI === false && self._fFail) {
self._fFail.call(self);
}
};
/**
* Drawing QRCode by using canvas
*
* @constructor
* @param {HTMLElement} el
* @param {Object} htOption QRCode Options
*/
var Drawing = function (el, htOption) {
this._bIsPainted = false;
this._android = _getAndroid();
this._htOption = htOption;
this._elCanvas = document.createElement("canvas");
this._elCanvas.width = htOption.width;
this._elCanvas.height = htOption.height;
el.appendChild(this._elCanvas);
this._el = el;
this._oContext = this._elCanvas.getContext("2d");
this._bIsPainted = false;
this._elImage = document.createElement("img");
this._elImage.alt = "Scan me!";
this._elImage.style.display = "none";
this._el.appendChild(this._elImage);
this._bSupportDataURI = null;
};
/**
* Draw the QRCode
*
* @param {QRCode} oQRCode
*/
Drawing.prototype.draw = function (oQRCode) {
var _elImage = this._elImage;
var _oContext = this._oContext;
var _htOption = this._htOption;
var nCount = oQRCode.getModuleCount();
var nWidth = _htOption.width / nCount;
var nHeight = _htOption.height / nCount;
var nRoundedWidth = Math.round(nWidth);
var nRoundedHeight = Math.round(nHeight);
_elImage.style.display = "none";
this.clear();
for (var row = 0; row < nCount; row++) {
for (var col = 0; col < nCount; col++) {
var bIsDark = oQRCode.isDark(row, col);
var nLeft = col * nWidth;
var nTop = row * nHeight;
_oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
_oContext.lineWidth = 1;
_oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
_oContext.fillRect(nLeft, nTop, nWidth, nHeight);
// 안티 앨리어싱 방지 처리
_oContext.strokeRect(
Math.floor(nLeft) + 0.5,
Math.floor(nTop) + 0.5,
nRoundedWidth,
nRoundedHeight
);
_oContext.strokeRect(
Math.ceil(nLeft) - 0.5,
Math.ceil(nTop) - 0.5,
nRoundedWidth,
nRoundedHeight
);
}
}
this._bIsPainted = true;
};
/**
* Make the image from Canvas if the browser supports Data URI.
*/
Drawing.prototype.makeImage = function () {
if (this._bIsPainted) {
_safeSetDataURI.call(this, _onMakeImage);
}
};
/**
* Return whether the QRCode is painted or not
*
* @return {Boolean}
*/
Drawing.prototype.isPainted = function () {
return this._bIsPainted;
};
/**
* Clear the QRCode
*/
Drawing.prototype.clear = function () {
this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height);
this._bIsPainted = false;
};
/**
* @private
* @param {Number} nNumber
*/
Drawing.prototype.round = function (nNumber) {
if (!nNumber) {
return nNumber;
}
return Math.floor(nNumber * 1000) / 1000;
};
return Drawing;
})();
/**
* Get the type by string length
*
* @private
* @param {String} sText
* @param {Number} nCorrectLevel
* @return {Number} type
*/
function _getTypeNumber(sText, nCorrectLevel) {
var nType = 1;
var length = _getUTF8Length(sText);
for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) {
var nLimit = 0;
switch (nCorrectLevel) {
case QRErrorCorrectLevel.L :
nLimit = QRCodeLimitLength[i][0];
break;
case QRErrorCorrectLevel.M :
nLimit = QRCodeLimitLength[i][1];
break;
case QRErrorCorrectLevel.Q :
nLimit = QRCodeLimitLength[i][2];
break;
case QRErrorCorrectLevel.H :
nLimit = QRCodeLimitLength[i][3];
break;
}
if (length <= nLimit) {
break;
} else {
nType++;
}
}
if (nType > QRCodeLimitLength.length) {
throw new Error("Too long data");
}
return nType;
}
function _getUTF8Length(sText) {
var replacedText = encodeURI(sText).toString().replace(/\%[0-9a-fA-F]{2}/g, 'a');
return replacedText.length + (replacedText.length != sText ? 3 : 0);
}
/**
* @class QRCode
* @constructor
* @example
* new QRCode(document.getElementById("test"), "http://jindo.dev.naver.com/collie");
*
* @example
* var oQRCode = new QRCode("test", {
* text : "http://naver.com",
* width : 128,
* height : 128
* });
*
* oQRCode.clear(); // Clear the QRCode.
* oQRCode.makeCode("http://map.naver.com"); // Re-create the QRCode.
*
* @param {HTMLElement|String} el target element or 'id' attribute of element.
* @param {Object|String} vOption
* @param {String} vOption.text QRCode link data
* @param {Number} [vOption.width=256]
* @param {Number} [vOption.height=256]
* @param {String} [vOption.colorDark="#000000"]
* @param {String} [vOption.colorLight="#ffffff"]
* @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H]
*/
QRCode = function (el, vOption) {
this._htOption = {
width : 256,
height : 256,
typeNumber : 4,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRErrorCorrectLevel.H
};
if (typeof vOption === 'string') {
vOption = {
text : vOption
};
}
// Overwrites options
if (vOption) {
for (var i in vOption) {
this._htOption[i] = vOption[i];
}
}
if (typeof el == "string") {
el = document.getElementById(el);
}
if (this._htOption.useSVG) {
Drawing = svgDrawer;
}
this._android = _getAndroid();
this._el = el;
this._oQRCode = null;
this._oDrawing = new Drawing(this._el, this._htOption);
if (this._htOption.text) {
this.makeCode(this._htOption.text);
}
};
/**
* Make the QRCode
*
* @param {String} sText link data
*/
QRCode.prototype.makeCode = function (sText) {
this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel);
this._oQRCode.addData(sText);
this._oQRCode.make();
this._el.title = sText;
this._oDrawing.draw(this._oQRCode);
this.makeImage();
};
/**
* Make the Image from Canvas element
* - It occurs automatically
* - Android below 3 doesn't support Data-URI spec.
*
* @private
*/
QRCode.prototype.makeImage = function () {
if (typeof this._oDrawing.makeImage == "function" && (!this._android || this._android >= 3)) {
this._oDrawing.makeImage();
}
};
/**
* Clear the QRCode
*/
QRCode.prototype.clear = function () {
this._oDrawing.clear();
};
/**
* @name QRCode.CorrectLevel
*/
QRCode.CorrectLevel = QRErrorCorrectLevel;
})();

File diff suppressed because one or more lines are too long

@ -15,8 +15,14 @@
</a></li> </a></li>
<li><a href="/account/config"> <li><a href="/account/config">
<i class="fa fa-download fa-fw" aria-hidden="true"></i>&nbsp; <i class="fa fa-download fa-fw" aria-hidden="true"></i>&nbsp;
{% trans 'Config Download' %} {% trans 'OpenVPN Config' %}
</a></li> </a></li>
{% if config.WIREGUARD %}
<li><a href="/account/wireguard">
<i class="icon icon-wireguard fa-fw" aria-hidden="true"></i>&nbsp;
{% trans 'WireGuard' %}
</a></li>
{% endif %}
<li><a href="/account/settings"> <li><a href="/account/settings">
<i class="fa fa-gears fa-fw" aria-hidden="true"></i>&nbsp; <i class="fa fa-gears fa-fw" aria-hidden="true"></i>&nbsp;
{% trans 'Settings' %} {% trans 'Settings' %}

@ -48,7 +48,7 @@
<td class="host_line"> <td class="host_line">
<span class="host_name">{{ d.hostname }}</span> <span class="host_name">{{ d.hostname }}</span>
</td> </td>
<td>{{ d.servers }}</td> <td>{{ d.servers|length }}</td>
<td>{{ d.bandwidth|bwformat }}</td> <td>{{ d.bandwidth|bwformat }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

@ -0,0 +1,168 @@
{% extends 'account_layout.html' %}
{% load static %}
{% load i18n %}
{% block title %}WireGuard{% endblock %}
{% block account_content %}
<h2>WireGuard</h2>
<p>
{% blocktrans trimmed %}
This page lets you manage WireGuard peers.
Each can only have one concurrent connection.
{% endblocktrans %}
</p>
<div class="page-wg-title">
<h3>
{% trans "Your Devices" %} <span>{{ keys|length }}/{{ config.WIREGUARD_MAX_PEERS }}</span>
</h3>
<p>
<a href="{% url 'lambdainst:wireguard_new' %}" class="pure-button pure-button-primary"
{% if not can_create_key %}disabled{% endif %}
>{% trans "New Device" %}</a>
</p>
</div>
{% if keys %}
<table class="pure-table pure-table-bordered page-wg-table">
<thead>
<tr>
<td>{% trans "Key" %}</td>
<td>{% trans "Name" %}</td>
<td colspan="4">{% trans "Actions" %}</td>
</tr>
</thead>
{% for peer in keys %}
<tr>
<td>
<span class="wireguard-key">{{ peer.public_key }}</span>
</td>
<td class="page-wg-table__name">
{{peer.name}}
</td>
<td class="page-wg-table__action">
<a href="{% url 'django_lcore:wireguard_dl' %}?id={{peer.id}}" title="{% trans 'Download' %}">
<i class="fa fa-download"></i>
</a>
</td>
<td class="page-wg-table__action page-wg-table__action--more">
<a href="#" onclick="showWgCode(event, '{% url 'django_lcore:wireguard_dl' %}?id={{peer.id}}');" title="{% trans 'Show QR Code' %}">
<i class="fa fa-qrcode"></i>
</a>
</td>
<td class="page-wg-table__action page-wg-table__action--more">
<a href="#" onclick="toggleWgEdit(event, {{ peer.id }});" title="{% trans 'Edit' %}">
<i class="fa fa-pencil"></i>
</a>
</td>
<td class="page-wg-table__action page-wg-table__action--more">
<form action="?" method="post">
{% csrf_token %}
<input type="hidden" name="action" value="delete_key" />
<input type="hidden" name="peer_id" value="{{ peer.id }}" />
<a href="#" onclick="deleteWgKey(event);" title="{% trans 'Delete' %}">
<i class="fa fa-trash"></i>
</a>
</form>
</td>
</tr>
{% endfor %}
</table>
{% endif %}
<script src="{% static 'js/qrcode.min.js' %}"></script>
<script>
function showWgCode(event, url) {
event.preventDefault();
var req = new XMLHttpRequest();
var bg = document.createElement("div");
bg.classList.add("page-wg-shadow");
bg.onclick = function() {
req.abort();
bg.remove();
};
document.body.appendChild(bg);
var spinner = document.createElement("div");
spinner.classList.add("page-wg-shadow__loading");
bg.appendChild(spinner);
var el = document.getElementById("qrcode");
req.addEventListener("load", function(e) {
spinner.remove();
console.log(req.response);
new QRCode(bg, {
text: req.response,
width: 256,
height: 256,
colorDark: "#000000",
colorLight: "#ffffff",
});
});
req.open("GET", url);
req.send();
}
function deleteWgKey(event) {
event.preventDefault();
event.target.parentNode.parentNode.submit();
}
function toggleWgEdit(event, peer_id) {
event.preventDefault();
const edit_class = 'page-wg-table__name--edit';
const tr = event.target.parentNode.parentNode.parentNode;
const nameTd = tr.querySelector(".page-wg-table__name");
// press again to save (trigger form submit)
if (tr.classList.contains(edit_class)) {
const nameForm = nameTd.querySelector("form");
if (nameForm) {
nameForm.submit();
}
return;
}
function addField(form, key, value) {
const f = document.createElement("input");
f.type = "hidden";
f.name = key;
f.value = value;
form.appendChild(f);
}
const currentName = nameTd.innerText;
nameTd.innerHTML = "";
const nameForm = document.createElement("form");
nameForm.method = "post";
addField(nameForm, "peer_id", peer_id);
addField(nameForm, "action", "set_name");
addField(nameForm, "csrfmiddlewaretoken", "{{ csrf_token }}");
const nameInput = document.createElement("input");
nameInput.type = "text";
nameInput.name = "name";
nameInput.value = currentName;
nameInput.maxLength = 21;
nameForm.appendChild(nameInput);
const nameSubmit = document.createElement("input");
nameSubmit.type = "submit";
nameSubmit.value = "{% trans "Save" %}";
nameForm.appendChild(nameSubmit);
nameTd.appendChild(nameForm);
}
function saveWgName(tr, name) {
}
</script>
{% endblock %}

@ -0,0 +1,47 @@
{% extends 'account_layout.html' %}
{% load static %}
{% load i18n %}
{% block title %}WireGuard - {% trans "New Device" %}{% endblock %}
{% block account_content %}
<h2>WireGuard - {% trans "New Device" %}</h2>
<form method="post" class="pure-form pure-form-aligned">
{% csrf_token %}
<input type="hidden" name="action" value="add_key" />
<fieldset>
<div class="pure-control-group">
<label for="name">{% trans 'Name' %}</label>
<input type="text" name="name" id="name"
placeholder="{% trans "Name" %}"
class="pure-input-1-2" />
<p class="inputinfo">
{% trans 'Used to identify the device in your account.' %}
</p>
</div>
<div class="pure-control-group">
<label for="public_key">{% trans 'Public Key' %}</label>
<input type="text" name="public_key" id="public_key"
placeholder="{% trans "Public key (base64)" %}"
class="pure-input-1-2" />
<p class="inputinfo">
<b>{% trans 'Optional.' %}</b>
{% trans 'Use an existing public key. Leave empty to generate a new one.' %}
</p>
</div>
<div class="pure-controls">
<label for="p_ipv6" class="pure-checkbox">
<input type="checkbox" name="enable_ipv6" id="p_ipv6" checked />
{% trans 'Enable IPv6?' %}
</label>
<input type="submit" class="pure-button pure-button-primary"
value="{% trans 'Create Key' %}" />
</div>
</fieldset>
</form>
{% endblock %}
Loading…
Cancel
Save