Commit da30c65a authored by Thomas Mignot's avatar Thomas Mignot

token auth and auto login

parent c6cbb160
......@@ -134,4 +134,6 @@ STATIC_ROOT = 'static/'
# ryzom settings
DDP_URLPATTERNS = 'todos.routing'
SERVER_METHODS = 'todos.methods'
SERVER_METHODS = [
'todos.methods'
]
......@@ -8,15 +8,22 @@ import importlib
import json
from channels.generic.websocket import JsonWebsocketConsumer
from channels.auth import get_user
from channels.auth import get_user, login, logout
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from asgiref.sync import async_to_sync
from django.conf import settings
from ryzom.models import Clients, Subscriptions, Publications
from ryzom.methods import Methods
from ryzom.models import Clients, Subscriptions, Publications, Tokens
ddp_urlpatterns = importlib.import_module(settings.DDP_URLPATTERNS).urlpatterns
server_methods = importlib.import_module(settings.SERVER_METHODS).Methods
for module in settings.SERVER_METHODS:
importlib.import_module(module)
'''
Import all user defined server methods from the SERVER_METHODS settings
'''
class Consumer(JsonWebsocketConsumer, object):
......@@ -31,12 +38,20 @@ class Consumer(JsonWebsocketConsumer, object):
access from the channel layer.
sends back a 'Connected' message to the client
'''
self.accept()
user = async_to_sync(get_user)(self.scope)
token = self.scope['query_string']
if token:
user_token = Tokens.objects.filter(token=token.decode()).last()
if user_token:
user = user_token.user
async_to_sync(login)(self.scope, user)
self.scope['session'].save()
Clients.objects.create(
channel=self.channel_name,
user=user if isinstance(user, User) else None
)
self.accept()
self.send(json.dumps({'type': 'Connected'}))
def disconnect(self, close_code):
......@@ -82,13 +97,13 @@ class Consumer(JsonWebsocketConsumer, object):
}))
return
if msg_type in ['subscribe', 'unsubscribe', 'method', 'geturl']:
if msg_type in [
'subscribe', 'unsubscribe',
'method', 'geturl',
'login', 'logout']:
func = getattr(self, f'recv_{msg_type}', None)
if func:
try:
func(data)
except KeyError as e:
print(e)
if not data.get('params', None):
self.send(json.dumps({
'_id': data.get('_id'),
'type': 'Error',
......@@ -97,6 +112,8 @@ class Consumer(JsonWebsocketConsumer, object):
'message': '"params" key not found'
}
}))
else:
func(data)
else:
self.send(json.dumps({
'_id': data['_id'],
......@@ -107,6 +124,39 @@ class Consumer(JsonWebsocketConsumer, object):
}
}))
def recv_login(self, data):
params = data['params']
username = params.get('username', None)
password = params.get('password', None)
if username and password:
user = authenticate(username=username, password=password)
if user:
async_to_sync(login)(self.scope, user)
self.scope['session'].save()
client = Clients.objects.get(channel=self.channel_name)
client.user = user
client.save()
token, created = Tokens.objects.get_or_create(user=user)
self.send(json.dumps({
'_id': data['_id'],
'type': 'Success',
'params': {
'token': f'{token.token}'
}
}))
else:
self.send(json.dumps({
'_id': data['_id'],
'type': 'Error',
'params': {
'name': 'Credentials mismatch',
'message': 'Wrong username/password combination'
}
}))
def recv_logout(self, data):
pass
def recv_geturl(self, data):
'''
geturl message handler.
......@@ -151,7 +201,7 @@ class Consumer(JsonWebsocketConsumer, object):
'''
to_send = {'_id': data['_id']}
params = data['params']
method = getattr(server_methods, params['name'], None)
method = Methods.get(params['name'])
if method is None:
to_send.update({
'type': 'Error',
......@@ -161,7 +211,8 @@ class Consumer(JsonWebsocketConsumer, object):
}
})
else:
ret = method(params['params'])
user = async_to_sync(get_user)(self.scope)
ret = method(user, params['params'])
if ret:
to_send.update({
'type': 'Success',
......
# Generated by Django 2.1.7 on 2019-03-11 16:35
# Generated by Django 2.1.7 on 2019-03-15 15:55
from django.conf import settings
import django.contrib.postgres.fields
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
import django.db.models.deletion
import secrets
class Migration(migrations.Migration):
......@@ -46,4 +47,12 @@ class Migration(migrations.Migration):
('publication', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ryzom.Publications')),
],
),
migrations.CreateModel(
name='Tokens',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.CharField(default=secrets.token_urlsafe, max_length=255, unique=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
......@@ -3,6 +3,7 @@ This file defines the models needed for ryzom pub/sub system.
They're not intended to be used by end-user.
'''
import importlib
import secrets
from django.db import models
from django.contrib.auth.models import User
......@@ -11,6 +12,13 @@ from django.contrib.postgres.aggregates import ArrayAgg
from ryzom.ddp import send_insert
class Tokens(models.Model):
'''
'''
token = models.CharField(default=secrets.token_urlsafe, max_length=255, unique=True)
user = models.OneToOneField(User, models.CASCADE)
class Clients(models.Model):
'''
Clients are the representation of connected Clients
......
......@@ -162,25 +162,13 @@
initialized = true;
};
call = function(name, params) {
ws_send({
type: 'method',
params: {
name: name,
params: params
}
}, function(r, e) {
if (e)
console.log(e);
});
};
var ws;
ws_connect = function(reconnecting) {
ws = new WebSocket(
'ws://' + window.location.host + '/ws/ddp/'
);
ws_path = 'ws://' + window.location.host + '/ws/ddp/'
if (token = localStorage.getItem('auth_token'))
ws_path += '?' + token
ws = new WebSocket(ws_path);
if (reconnecting) {
ws.onopen = function() {
......@@ -234,5 +222,34 @@
route(current_url, query_string);
});
ryzom = {
login: function(credentials) {
ws_send({
type: 'login',
params: credentials
}, function(r, e) {
if (e)
console.log(e);
else
localStorage.setItem('auth_token', r.params.token)
});
},
logout: function() {
},
call: function(name, params) {
ws_send({
type: 'method',
params: {
name: name,
params: params
}
}, function(r, e) {
if (e)
console.log(e);
});
}
};
</script>
</html>
......@@ -7,7 +7,7 @@ class Task(Li):
content = [
Span(task_object.about),
Button('Done', {'class': 'btn float-right'}, {
'click': f'call("remove_task", {{id: {task_object.id}}})'
'click': f'ryzom.call("remove_task", {{id: {task_object.id}}})'
})
]
super().__init__(content, attr, _id=f'task_{task_object.id}')
......@@ -44,7 +44,7 @@ class Taskform(Div):
'class': 'btn btn-primary'
},
events={
'click': 'call("insert_task", { \
'click': 'ryzom.call("insert_task", { \
about: $("#task_input").value \
}); $("#task_input").value = ""'
}
......
from django.contrib.auth.models import User
from ryzom.methods import Methods
from todos.models import Tasks
class Methods():
def create_user(user, params):
username = params.get('username', None)
password = params.get('username', None)
email = params.get('username', None)
def insert_task(params):
if 'about' in params:
t = Tasks(about=params['about'])
t.save()
if username and password and email:
user, created = User.objects.get_or_create(
username=username, email=email)
if created:
user.set_password(password)
user.save()
return user.id
else:
return {
'type': 'Error',
'params': {
'name': 'Forbidden',
'message': 'User already exists in Database'
}
}
else:
return {
'type': 'Error',
'params': {
'name': 'Bad request',
'message': 'You must provide a unique username, \
a valid email address and a password'
}
}
def insert_task(user, params):
if 'about' in params:
user = user if user.id else None
t = Tasks(user=user, about=params['about'])
t.save()
return True
return False
def remove_task(user, params):
if 'id' in params:
t = Tasks.objects.get(id=params['id'])
if t:
t.delete()
return True
return False
def remove_task(params):
if 'id' in params:
t = Tasks.objects.get(id=params['id'])
if t:
t.delete()
return True
return False
return False
Methods.add({
'create_user': create_user,
'insert_task': insert_task,
'remove_task': remove_task
})
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment