Description
Developing a dashboard using Django channels, two different consumers are implemented, UserConsumer and RequestConsumer.
In UserConsumer methods like login, logout and get_user are implemented. The user Authentication is being done using OTP code sent to user mobile so that a user_send_code method is in charge of sending SMS while the user_login method verifies the code sent by user. UserConsumer is implemented as follow:
class UserConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
await self.accept()
async def receive_json(self, content=None, **kwargs):
action = content.get("action", "user.get_user")
if action == "user.login":
await self.user_login(content)
elif action == "user.send_code":
await self.user_send_code(content)
elif action == "user.logout":
await self.user_logout()
else:
await self.user_get_user()
async def user_send_code(self, content):
await self.send("we will send sms to: {}".format(content['mobile']))
if check_mobile_pattern(content["mobile"]):
code = random.randint(1111, 9999)
self.scope[VERIFY_CODE] = code
self.scope[MOBILE] = content["mobile"]
result = await send_sms(content["mobile"], code)
if result:
await self.send_json(send_code(200))
else:
await self.send_json(send_code(400))
else:
await self.send_json(send_code(SystemErrorCodes.InvalidMobilePattern))
async def user_get_user(self):
if self.scope['user'].is_authenticated:
await self.send_json({"id": self.scope['user'].id})
else:
await self.send_json(send_code(SystemMessageCodes.AnonymousUserMessage))
async def user_login(self, content):
verify_code = self.scope.get("verify_code")
code = content.get("code")
mobile = self.scope.get(MOBILE)
if mobile is not None and verify_code is not None and code == verify_code:
user = await load_user_by_mobile(mobile)
del self.scope[VERIFY_CODE]
# login the user to this session.
await login(self.scope, user)
# save the session (if the session backend does not access the db you can use `sync_to_async`)
await database_sync_to_async(self.scope["session"].save)()
await self.send_json(send_code(200))
else:
await self.send_json(send_code(SystemErrorCodes.InvalidVerifyCode))
after sending the right code to web service, user logins and user_get_user method returns the userID. However when I try to open a new Socket of class RequestConsumer, the user is not set in the scope and socket closed. Here is the code:
class RequestConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.user = self.scope['user']
if self.user.is_authenticated:
await self.accept()
else:
await self.close()
my asgi application is implemented as follow:
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter(
[
path("user/", UserConsumer.as_asgi()),
path('requests/', RequestConsumer.as_asgi()),
]
)
)
),
})
so both consumer are using same AuthMiddlewareStack middleware. And here is some parts of my settings.py file:
INSTALLED_APPS = [
'daphne',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'file_process.apps.FileProcessConfig',
'channels',
'users_app.apps.UsersAppConfig',
'requests_apps.apps.RequestsAppConfig'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # Default session storage
SESSION_COOKIE_NAME = 'sessionid' # Default cookie name
SESSION_COOKIE_HTTPONLY = True # Prevent JavaScript access
SESSION_COOKIE_SECURE = False # Set to True if using HTTPS
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_SAVE_EVERY_REQUEST = True
and finally this is a sample test script I've provided for the client side:
<script>
const socket1 = new WebSocket("ws://localhost:8000/user/");
socket1.onopen = () => {
console.log("Login WebSocket Connected");
};
socket1.onerror = (error) => {
console.error("WebSocket Error: ", error);
};
socket1.onclose = () => {
console.log("Login WebSocket Closed");
};
socket1.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Message from server: ", data);
if (data.code === 200) {
const socket2 = new WebSocket("ws://localhost:8000/requests/");
socket2.onopen = () => {
console.log("Login WebSocket Connected");
};
socket2.onerror = (error) => {
console.error("WebSocket Error: ", error);
};
socket2.onclose = () => {
console.log("Login WebSocket Closed");
};
} else {
alert("Login Failed!");
}
};
document.getElementById("loginForm").addEventListener("submit", (e) => {
e.preventDefault();
const code = document.getElementById("code").value;
const message = {
action: "user.login",
code: code,
};
socket1.send(JSON.stringify(message));
});
</script>
I couldn't find any cookie being sent to my browser if there should be one there.
This is my first webservice developed by django-channels so I'm not sure if I have done everything properly.