The MMO Project Part 10: Square pegs in round holes
I had an idea for dealing with password login, some web dev stuff. I do believe Godot has POST requests available to it so I will write an endpoint on the Django server that will create a token for the client after authenticating it via form data submitted via the POST request from the login interface. The endpoint will set the token in the account's game_token field in the DB and send back the same token to the Auth Server The SSL should secure the password barring advanced MITM between client and webserver. I am of course operating under the assumption that the POST request will be encrypted via SSL.
So then the client logs in via the gateway, The gateway could fire the post request even. Either way the request goes to an endpoint that sends a token to the DB and to the AUTH server. Then the client connects to the auth server, which will identify and verify the token and the username match, then send the important account information over to the game server (For character lookup) then the client will make it over to the game server and based on their auth token, be hooked up with the character data for that account.
This will require me to write another interface on the auth server for the Django server to talk to... or I could just have the auth server read the token from the DB after the login system has created it somewhere in the code that runs on peer connect.
I'll be honest though, I am not too confident in my thinking right now because I have not been sleeping much lately. Damn cat woke me up early today and I was trying to sleep for once. Hopefully I am not making a huge mistake with this auth system because I just came up with it in the shower before breakfast.
I'll draw up a kind of check list and diagram so I can focus on getting it done while I try to get myself to wake up a bit... hopefully seeing any potential errors in it.
Well, research shows the HTTP Requests in Godot have SSL available. That's good. It seems like you might be able to have a payload so that's good too. I considered for a moment Web RTC (Weird because my brain translates that to Real Time Clock) but I think that would just over-complicate things and I do have a habbit of doing that, don't I? Trying to avoid that for today.
I think I will try sending a payload with a GET request on the client for login, that way I can easily send back the token to store on the client for the login process, then delete it from the client after auth and clear it on the DB after 30 seconds from login start. So if you don't make it to the game server in 30 seconds something went wrong anyway and you should time out.
Edit: Instead of GET, I used POST. There was a fear that POST would not bother with returned data but nope. It's there. We're good.
So It goes Client -[GET(username+pass)]->Django(Authenticates U+P, stores token in DB) -[Token]-> Client -[Username + Token]-> Gateway -> Auth -[Username + Token]->DB -[Token + Username Match?]-> Auth -[Account ID]-> Game Server -[Account ID]-> DB -[Characters]-> Game Server -> Client
And now we're in game(assuming successful auth). At least, in theory.
Alright, trying to include the body in the GET request seemed to go nowhere so we are operating with POST requests.
With that we now generate a token. The question is... does sending a post request in Godot bother with getting the response? I guess we find out now.
Hello.
Here I was all worried it wouldn't bother with the response after the post. Phew. This almost got way more complicated than it needed to be.
Alright now I need to rewrite the C++ DB wrapper a little bit to allow us to use this token. Then I need to modify the auth server to use the token for authenticating with the DB. Then I need to add a timer to auth that will (every 30 seconds) call the clear tokens endpoint on the Django server which will basically just run a query on all accounts wiping the tokens. This might lead to some problems with logging in if you manage to get halfway logged in right as the auth server calls for the wipe but it should be rare/negligible. (Inb4 it's not)
Now we got that working it is Lunch time, then the wiring up begins.
Alright now I am passing the data along through the gateway to the auth server. Now I need to lookup the account with the DB wrapper where username and token match and return the results or if they don't just return None or something to that effect.
Alright, the lookup works in the wrapper now. Returns account ID which during testing is an integer, will be a UUID later just in case. For now though... the bits are all working it's just putting them together. But first the doggo needs to go out.
Alright, finally got the new auth system in.
The character information is now populated from the ACTUAL database and can be interacted with in the Django Admin. That took all week, didn't it? Well it was something I had never done before... actually I am not sure anyone would have done exactly this before.
Here's the character in the admin:
I'm almost proud of myself. Now I will have to get some shit worked out.
For those that are interested the auth endpoint looks like:
def client_auth_request(request):
# The client connects via request, check the username and password against the DB.
# If they match, set a token in the game_token fields of the account.
# Return the token in JSON format to the client.
data = {
"token": "None"
}
if request.method == "POST":
print(request.body)
received_data = json.loads(request.body)
print("POST recieved.")
print(received_data)
username = received_data['username']
password = received_data['password']
if username and password:
print("Authenticating user and pass")
user = authenticate(request, username=username, password=password)
print(user)
if user is not None:
#This is where we set the token on the user's account and return a token in JSON format.
user_entry = User.objects.get(username=username)
# Generate a 256 bit token and store it in the account.
token = secrets.token_hex(32)
user_entry.game_token = token
user_entry.save()
data = {
"token": token
}
return JsonResponse(data)
else:
return JsonResponse(data)
else:
return JsonResponse(data)
else:
return JsonResponse(data)
I'm sure I could improve it but it's good enough for what I am up to right now. There will be much refactoring once I have everything working. I'ma get some tea.
Okay. With that I now have to setup the endpoint to clear all tokens and then the timed function on the auth server to fire it. Alternately I can make a secondary process or use a task scheduler that runs independent of Django and just fires a SQL statement every X seconds. I avoid the multi-threading libs like the plague because they aren't true multi-threading which can be frustrating. I prefer to spool up a secondary process and just use IPC. It's faster as far as I can see. I wonder where in the Django app I should write this since it's better than exposing an endpoint that could be easily hit by some asshat trying to mess shit up(even if it was secured I still don't feel comfortable with it).
Celery. I really don't like using Celery. It always goes terribly for me. Django Background Tasks hasn't been updated in almost a year so that's out. My laptop is barely hanging on as it is with the 4 servers and the client... adding another layer to this could be disastrous. I could start using the R710 but... mmm, bills. I wish I had more Pi 4s to use as servers.
I guess I can have a closer look at Django-Q. redis is pretty lightweight I guess and I have it installed anyway, could use it as a broker. Was planning to use it eventually for taking some load off the game server with location tracking but that wont be til after I have a grasp of GDNative. There really needs to be more documentation for that, I don't want to spend my days crawling through the source trying fo figure out what everything does and what to use in certain situations.
# Setting up Tasks for Django Q
def clear_game_tokens():
#For all Users, clear game_token to a blank string.
print("Cleaning up tokens!")
users = User.objects.all()
for user in users:
user.game_token = ""
user.save()
pass
schedule(clear_game_tokens, schedule_type=Schedule.MINUTES,
minutes=5)
It was just that easy. Well I mean I had to start redis and run the task runner but I just put this in the view and Robert's your father's brother.
It runs every 5 minutes instead of every 30 seconds because I made it a bit... janky. I could use SQL Alchemy here and just fire a single SQL statement but I think keeping it pythonic and readable during the early dev will pay off. I wonder if I could run raw queries from django anyway. Yes, yes I can. So who needs SQL Alchemy? (I still do for other projects)
It would look something like
Anyway that's the new login system and proper database all hooked up. I was worried I would not manage to finish it this week. It's 4pm now, so I even got done early. I think I will go through all... 7 projects? Let's count them. Client, Game server, Auth server, Gateway server, Web server, Character DB wrapper, Account DB wrapper. Yeah, wow this project is more complex than I expected it to be when I started out on it.
Anyway I should make my commits.
There we go, all nice and commited. I think I might take a bit of time and work on Tamakai for a couple days at least next week now that I have managed to tackle the giant cyclops. I need to work on something else for a bit. I might work on Mantilogs though, depends on if I get any requests from Mew. I still gotta write the documentation for it and probably create a docker container for deployment which will be a whole can of worms I have been ducking and weaving for a while. I kinda want to get to milestone 2 first.
It's useful that I write these as I work as a kind of stream of consious thing. I can see my thought process and where I have made grave mistakes later. I feel like I will be looking at this in a couple months thinking "WHAT THE HELL IS WRONG WITH ME!? LOOK AT ALL THIS WORK I MADE FOR MYSELF!" Though I feel that way about everything, machines are like genies, code is like a wish. You tell the machine what you want to happen and then it twists it into something melevolent and throws it back in your face and kicks you in the ghoulies.
Well, I guess I will head out of the office half an hour early today as well. This time victorious. Start my weekend just a little early. Next week I will make sure the game server is saving the character data to the database properly and start adding game systems, maybe work on figuring out server-side collision detection and pay some technical debt.
Cheers.