Show pageOld revisionsBacklinksBack to top This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== HomeCtrl: a touch of security ====== {{tag>dev iot raspberry}} In our [[blog:2020:0630_homectrl_project|previous article]] we discussed the initial construction of the HomeCtrl project. The result of that is that currently, anyone sending a post request to the correct URL mentioned in that other article could basically trigger the opening of our entrance gate. [**OOooopss...**] Clearly, from a security perspective, that is not so good ;-) So I was thinking we could try to do something about it here... ====== ====== ===== Sending a user and password as part of the post data ===== In fact, there is no reason to make this too complex: what we really want is to be able to identify who is sending the request to activate the entrance gate. To achieve this result it would be enough to send a user name and password as part of the post request data, and compare those elements with an array we keep inside the server process. => Nothing too fancy, no need for a database or anything like that here. Let's give this a try and see how it works! As an authentication scheme I will (for now) simply concatenate a user name with its password. Since this POST data is sent over https, this will be secured already. As a result, now I need this kind of request, otherwise the authentication will fail: <code>curl -H "Content-Type: application/json" -d '{"device": "entrance", "action":"trigger", "auth":"manu_5678"}' -X POST https://api.nervtech.org/homectrl/trigger</code> <note>[Side note lol] To check how long a process has been running on linux, one can first retrieve the **process ID** and then use the command **ls -ld /proc/<pid>** to find out when the process was started.</note> * Adding a new page to enter the user credentials on the ionic app: <code>nv_cmd ionic generate page login</code> * To store our username/password locally on the user device, we can use the [[https://ionicframework.com/docs/angular/storage|ionic storage component]] * So we install the ionic storage package: <code>npm install --save @ionic/storage</code> * Then we inject the module in the app root module: <sxh typescript; highlight: [4]>@NgModule({ declarations: [AppComponent], entryComponents: [], imports: [BrowserModule, HttpClientModule, IonicModule.forRoot(), IonicStorageModule.forRoot(), AppRoutingModule], providers: [ StatusBar, SplashScreen, { provide: RouteReuseStrategy, useClass: IonicRouteStrategy } ], bootstrap: [AppComponent] })</sxh> * And then we use it on our login page to save/restore the user provided values: <sxh typescript; highlight: []>import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { Storage } from '@ionic/storage'; @Component({ selector: 'app-login', templateUrl: './login.page.html', styleUrls: ['./login.page.scss'], }) export class LoginPage implements OnInit { protected username: string = ""; protected password: string = ""; constructor(public router: Router, private sto: Storage) { } public gotoHome() { this.router.navigate(['/home']) } public onUsernameUpdated(val) { // console.log("Username updated to: '"+val+"'") this.username = val this.sto.set("username", val) } public onPasswordUpdated(val) { // console.log("Password updated to: '"+val+"'") this.password = val this.sto.set("password", val) } ngOnInit() { // Assign the saved username and password: this.sto.get('username').then((val) => { // console.log("Using saved username: "+val) this.username = val }); this.sto.get('password').then((val) => { // console.log("Using saved password: "+val) this.password = val }); } } </sxh> * Finally, I will now also use the stored username/password values when sending an activation request from the home page: <sxh typescript; highlight: []> public activateEntranceGate() { console.log("Requesting entrance gate activation..."); if (this.toast != null) { this.toast.dismiss(); this.toast = null; } this.requestSent = true; // When about to send a request we should retrieve the saved username and password: let username:string = ""; let password:string = ""; this.sto.get("username") .then((val) => { username = val return this.sto.get("password") }) .then((val) => { password = val // let addr = "http://192.168.1.2/trigger" let addr = "https://api.nervtech.org/homectrl/trigger" let body = {device: 'entrance', action: 'trigger', auth: username+"_"+password} this.http.post<any>(addr, body).subscribe(data => { console.log("Received reply: "+JSON.stringify(data)) this.requestSent = false; if(data['status'] == 'OK') { this.presentToast("Gate activated successfully.", "success"); } else { this.presentToast(data['message'], "warning"); } }) }) }</sxh> => And after rebuilding/re-installing the android application, this seems to work just fine: I can enter my credentials on the login page, and they are used as expected on the home page. If I close and restart the app, then the previously entered values are retrieved from [some form of] local storage, so we don't have to enter them again. All good! ===== Handling authentication data on the server side ===== On the server side, I also added a couple of helper functions in the main script: <sxh python; highlight: []>def printMsg(msg): print(msg, file=sys.stderr) def readConfigFile(): cfgFile=os.path.join(dirname, "homectrl.json") printMsg("Reading config file: %s" % cfgFile) with open(cfgFile) as json_file: data = json.load(json_file) return data def getAuthenticatedUser(cfg, auth): users = cfg['users'] for uname in users.keys(): if auth == "%s_%s" % (uname, users[uname]): return uname return None</sxh> And then, I use those functions to check if the activation requests I reveice are from properly authenticated users: <sxh python; highlight: [17-19]>@app.route("/trigger", methods = ['POST']) def on_trigger(): if request.method == 'POST': # data = request.form # a multidict containing POST data data = request.json printMsg("Received post data: %s" % data) dev = data.get('device') act = data.get('action') auth = data.get('auth') # Here we should also read the json config file: cfg = readConfigFile() # check if we have one authentization code corresponding to the provided auth: user = getAuthenticatedUser(cfg, auth) if user == None: printMsg("Cannot authenticate user with auth: %s" % auth) return jsonify(status = "Error", message = "Cannot authenticate user.") # dev = data['device'] # act = data['action'] if dev == 'entrance' and act == "trigger": now = datetime.now() printMsg('%s: Entrance gate activation requested by %s' % (now, user)) triggerGate() return jsonify(status = "OK") else: return jsonify(status = "Error", message = "Invalid device/action: dev=%s, act=%s" % (dev, act)) else: printMsg("not supported request method: %s" % request.method) return jsonify(status = "Error", message = "Invalid request method %s" % request.method)</sxh> The very nice thing here is that the user credentials are just stored in a JSON file, with for instance the following content: <code>{ "users": { "flo": "1234", "manu": "5678" } } </code> And that file is re-read for each request we receive. So from there, it will be very easy to add/remove users dynamically since I have a direct access to that file with my samba shared folder ;-). <note>Of course this means we are not handling any kind of "real password" here since I will be the one defining them for each user, but hey, we really don't need more than that, and I definitely want to know who has the right to open my home entrance gate at any time anyway.</note> blog/2020/0701_homectrl_authentication_handling.txt Last modified: 2020/07/10 12:11by 127.0.0.1