Initial version

This commit is contained in:
Kumi 2020-11-22 11:10:32 +01:00
commit 419a958bb7
23 changed files with 473 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
__pycache__/
*.pyc
venv/
db.sqlite3

0
frontend/__init__.py Normal file
View file

3
frontend/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
frontend/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class FrontendConfig(AppConfig):
name = 'frontend'

59
frontend/consumers.py Normal file
View file

@ -0,0 +1,59 @@
import json
from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncWebsocketConsumer
from django.contrib.auth.models import AnonymousUser
class ChatConsumer(AsyncWebsocketConsumer):
def authenticate(self):
return self.scope["user"]
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Authenticate user
user = await database_sync_to_async(self.authenticate)()
if isinstance(user, AnonymousUser) or not user:
await self.close(code=4003)
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message
}))

View file

3
frontend/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

7
frontend/routing.py Normal file
View file

@ -0,0 +1,7 @@
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/room/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
]

3
frontend/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

9
frontend/urls.py Normal file
View file

@ -0,0 +1,9 @@
from django.urls import path
from frontend.views import DemoView, RoomView
urlpatterns = [
path('', DemoView.as_view()),
path('room/<str:room_name>/', RoomView.as_view(), name='room'),
]

16
frontend/views.py Normal file
View file

@ -0,0 +1,16 @@
from django.shortcuts import render
from django.views.generic import TemplateView
# Create your views here.
class DemoView(TemplateView):
template_name = "frontend/demo.html"
class RoomView(TemplateView):
template_name = "frontend/room.html"
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['room_name'] = kwargs["room_name"]
return context

22
manage.py Executable file
View file

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pmessage.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

0
pmessage/__init__.py Normal file
View file

30
pmessage/asgi.py Normal file
View file

@ -0,0 +1,30 @@
"""
ASGI config for pmessage project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
"""
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
import frontend.routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pmessage.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter(
frontend.routing.websocket_urlpatterns
)
)
),
})

137
pmessage/settings.py Normal file
View file

@ -0,0 +1,137 @@
"""
Django settings for pmessage project.
Generated by 'django-admin startproject' using Django 3.1.3.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'e4rgo)yp&*g)#hm@4g6n30&vyqkm8(#-(y&3*mw$%*ju-w7w&#'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'channels',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'frontend',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'pmessage.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / "templates"],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'pmessage.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
BASE_DIR / "static",
]
ASGI_APPLICATION = 'pmessage.asgi.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}

22
pmessage/urls.py Normal file
View file

@ -0,0 +1,22 @@
"""pmessage URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include("frontend.urls")),
]

16
pmessage/wsgi.py Normal file
View file

@ -0,0 +1,16 @@
"""
WSGI config for pmessage project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pmessage.settings')
application = get_wsgi_application()

3
requirements.txt Normal file
View file

@ -0,0 +1,3 @@
django
channels
channels_redis

View file

@ -0,0 +1,73 @@
(async () => {
// Load Worker
openpgp.initWorker({ path: '/static/frontend/openpgp/openpgp.worker.min.js' });
// Define key details
const participant1_name = "Test";
const participant1_id = "test@rooms.kumi.test";
const participant1_passphrase = "super long and hard to guess secret";
const participant2_name = "More test";
const participant2_id = "test2@rooms.kumi.test";
const participant2_passphrase = "even longer and harder to guess secret";
const curve = "ed25519";
var participant1_options = {
userIds: [{ name: participant1_name, email: participant1_id }],
curve: curve,
passphrase: participant1_passphrase
};
var participant2_options = {
userIds: [{ name: participant2_name, email: participant2_id }],
curve: curve,
passphrase: participant2_passphrase
};
// Generate key
const { privateKeyArmored: participant1_privkey, publicKeyArmored: participant1_pubkey } = await openpgp.generateKey(participant1_options);
const { privateKeyArmored: participant2_privkey, publicKeyArmored: participant2_pubkey } = await openpgp.generateKey(participant2_options);
// Read and decrypt keys
const { keys: [participant1_readkey] } = await openpgp.key.readArmored(participant1_privkey);
await participant1_readkey.decrypt(participant1_passphrase);
const { keys: [participant2_readkey] } = await openpgp.key.readArmored(participant2_privkey);
await participant2_readkey.decrypt(participant2_passphrase);
// Get public keys
const participant1_readpubkey = (await openpgp.key.readArmored(participant1_pubkey)).keys[0];
const participant2_readpubkey = (await openpgp.key.readArmored(participant2_pubkey)).keys[0];
// Encrypt message
const text = "This is encrypted text sent from participant 1 to participant 2.";
const { data: encrypted } = await openpgp.encrypt({
message: openpgp.message.fromText(text),
publicKeys: [participant2_readpubkey],
privateKeys: [participant1_readkey]
});
console.log(encrypted);
// Decrypt message
const { data: decrypted, signatures: signatures } = await openpgp.decrypt({
message: await openpgp.message.readArmored(encrypted),
publicKeys: [participant1_readpubkey],
privateKeys: [participant2_readkey]
});
console.log(decrypted);
console.log(await signatures[0].valid);
})();

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
/*! OpenPGP.js v4.10.8 - 2020-08-28 - this is LGPL licensed code, see LICENSE/our website https://openpgpjs.org/ for more information. */
!function(){return function e(n,r,t){function o(i,s){if(!r[i]){if(!n[i]){var c="function"==typeof require&&require;if(!s&&c)return c(i,!0);if(a)return a(i,!0);var f=new Error("Cannot find module '"+i+"'");throw f.code="MODULE_NOT_FOUND",f}var u=r[i]={exports:{}};n[i][0].call(u.exports,function(e){return o(n[i][1][e]||e)},u,u.exports,e,n,r,t)}return r[i].exports}for(var a="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}}()({1:[function(e,n,r){(function(e){importScripts("openpgp.min.js");var n=e.openpgp,r=[],t=6e4;n.crypto.random.randomBuffer.init(t,function(){return r.length||self.postMessage({event:"request-seed",amount:t}),new Promise(function(e){r.push(e)})}),self.onmessage=function(e){var t,s=e.data||{};switch(s.event){case"configure":t=s.config,Object.keys(t).forEach(function(e){n.config[e]=t[e]});break;case"seed-random":!function(e){e instanceof Uint8Array||(e=new Uint8Array(e));n.crypto.random.randomBuffer.set(e)}(s.buf);var c=r;r=[];for(var f=0;f<c.length;f++)c[f]();break;default:!function(e,r,t){if("clear-key-cache"===r)return Array.from(o.values()).forEach(e=>{e.isPrivate()&&e.clearPrivateParams()}),o.clear(),void i({id:e,event:"method-return"});if("function"!=typeof n[r])return void i({id:e,event:"method-return",err:"Unknown Worker Event"});n.util.restoreStreams(t),(t=n.packet.clone.parseClonedPackets(t,r)).publicKeys&&(t.publicKeys=t.publicKeys.map(a));t.privateKeys&&(t.privateKeys=t.privateKeys.map(a));n[r](t).then(function(r){i({id:e,event:"method-return",data:n.packet.clone.clonePackets(r)})}).catch(function(r){n.util.print_debug_error(r),i({id:e,event:"method-return",err:r.message,stack:r.stack})})}(s.id,s.event,s.options||{})}};const o=new Map;function a(e){const n=e.armor();return o.has(n)?o.get(n):(o.set(n,e),e)}function i(e){self.postMessage(e,n.util.getTransferables(e.data,n.config.zero_copy))}postMessage({event:"loaded"})}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1]);

View file

@ -0,0 +1,8 @@
<html>
<head>
<script src="/static/frontend/openpgp/openpgp.min.js"></script>
</head>
<body>
<script src="/static/frontend/demo/demo.js"></script>
</body>
</html>

View file

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Chat Room</title>
</head>
<body>
<textarea id="chat-log" cols="100" rows="20"></textarea><br>
<input id="chat-message-input" type="text" size="100"><br>
<input id="chat-message-submit" type="button" value="Send">
{{ room_name|json_script:"room-name" }}
<script>
const roomName = JSON.parse(document.getElementById('room-name').textContent);
const chatSocket = new WebSocket(
'ws://'
+ window.location.host
+ '/ws/room/'
+ roomName
+ '/'
);
chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
document.querySelector('#chat-log').value += (data.message + '\n');
};
chatSocket.onclose = function(e) {
console.error('Chat socket closed unexpectedly');
};
document.querySelector('#chat-message-input').focus();
document.querySelector('#chat-message-input').onkeyup = function(e) {
if (e.keyCode === 13) { // enter, return
document.querySelector('#chat-message-submit').click();
}
};
document.querySelector('#chat-message-submit').onclick = function(e) {
const messageInputDom = document.querySelector('#chat-message-input');
const message = messageInputDom.value;
chatSocket.send(JSON.stringify({
'message': message
}));
messageInputDom.value = '';
};
</script>
</body>
</html>