Implementing Search User Feature for Admins

The users tab of admin panel of SUSI.AI provides a list of all users registered on SUSI. This helps admins to get an overview of users and also provides the admins option like change user roles and delete accounts. The list of users is displayed in a table which also uses pagination to handle larger number of users. But to find a particular user can be a difficult task if the user base is large. Therefore a feature to search for users by their email has been implement which searches for users on SUSI server and then sends the searched users to the client. In this post we will discuss both about the server and client side implementation of this feature.

Continue ReadingImplementing Search User Feature for Admins

Implementing User Stats for SUSI.AI Admin Panel

SUSI.AI has an admin panel where users with roles operator or above get an overview of various stats and manage other users and skills. The admin panel allows the system admins to get a list of all users registered on susi and also provide an option to change their user roles as well. The admin tab provides us with the statistics of its users. In this post we will discuss how this is implemented on susi server.

Continue ReadingImplementing User Stats for SUSI.AI Admin Panel

How to Change a Password and Forgot Password Feature in the SUSI.AI Server

The accounting system of SUSI.AI provides its users the option to change the password of their accounts. This features gives us two options, either we can change our password by entering the older password or if the user forgot the password, they are provided a link on their email address through which we can change our password. Using either option, the user has to authenticate themselves before they can actually change their passwords. If the user has the current password, it is considered as a parameter of authentication. In other case the user has to check their email account for the link which also confirms the authenticity of user. In this post we will discuss how both options works on SUSI.

Continue ReadingHow to Change a Password and Forgot Password Feature in the SUSI.AI Server

Delete User Account Service using LoginService.java API in SUSI.AI

SUSI.AI has an api to handle all account related services called accounts.susi.ai. This API provides services like change password and delete account. the main goal of accounts.susi.ai is to centralise the accounts’ related settings from web, android and iOS clients into a single place like other accounting services. In this post we will discuss one of these features which is the delete user account from SUSI.AI.

The api accounts.susi.ai has a react file DeleteAccount.react.js. This file handle the delete account service and interacts with the LoginServce.java api, which is the core file for authentication. I will discuss in details how these two interact with each other and with other files.

Continue ReadingDelete User Account Service using LoginService.java API in SUSI.AI

Fetching Info of All Users and their connected devices for the SUSI.AI Admin Panel

Fetching the data of all users is required for displaying the list of users on the SUSI.AI Admin panel. It was also required to fetch the information of connected devices of the user along with the other user data. The right to fetch the data of all users should only be permitted to user roles “OPERATOR” and above. This blog post explains how the data of connected devices of all users is fetched, which can then be used in the Admin panel.

How is user data stored on the server?

All the personal accounting information of any user is stored in the user’s accounting object. This is stored in the “accounting.json” file. The structure of this file is as follows:

{
  "email:akjn11@gmail.com": {
    "devices": {
      "8C-39-45-23-D8-95": {
        "name": "Device 2",
        "room": "Room 2",
        "geolocation": {
          "latitude": "54.34567",
          "longitude": "64.34567"
        }
      }
    },
    "lastLoginIP": "127.0.0.1"
  },
  "email:akjn22@gmail.com": {
    "devices": {
      "1C-29-46-24-D3-55": {
        "name": "Device 2",
        "room": "Room 2",
        "geolocation": {
          "latitude": "54.34567",
          "longitude": "64.34567"
        }
      }
    },
    "lastLoginIP": "127.0.0.1"
  }
}

 

As can be seen from the above sample content of the “accounting.json” file, we need to fetch this data so that it can then be used to display the list of users along with their connected devices on the Admin panel.

Implementing API to fetch user data and their connected devices

The endpoint of the servlet is “/aaa/getUsers.json” and the minimum user role for this servlet is “OPERATOR”. This is implemented as follows:

   @Override
    public String getAPIPath() {
        return "/aaa/getUsers.json";
    }

    @Override
    public UserRole getMinimalUserRole() {
        return UserRole.OPERATOR;
    }

 

Let us go over the main method serviceImpl() of the servlet:

  • We need to traverse through the user data of all authorized users. This is done by getting the data using DAO.getAuthorizedClients() and storing them in a Collection. Then we extract all the keys from this collection, which is then used to traverse into the Collection and fetch the user data. The implementation is as follows:

    Collection<ClientIdentity> authorized = DAO.getAuthorizedClients();
    List<String> keysList = new ArrayList<String>();
    authorized.forEach(client -> keysList.add(client.toString()));

    for (Client client : authorized) {
        // code           
    }

 

  • Then we traverse through each client and generate a client identity to get the user role of the client. This is done using the DAO.getAuthorization() method. The user role of the client is also put in the final object which we want to return. This is implemented as follows:

    JSONObject json = client.toJSON();
    ClientIdentity identity = new 
    ClientIdentity(ClientIdentity.Type.email, client.getName());
    Authorization authorization = DAO.getAuthorization(identity);
    UserRole userRole = authorization.getUserRole();
    json.put("userRole", userRole.toString().toLowerCase());

 

  • Then the client credentials are generated and it is checked whether the user is verified or not. If the user is verified, then in the final object, “confirmed” is set to true, else it is set to false.

    ClientCredential clientCredential = new ClientCredential (ClientCredential.Type.passwd_login, identity.getName());
    Authentication authentication = DAO.getAuthentication(clientCredential);

    json.put("confirmed", authentication.getBoolean("activated", false));

 

  • Then we fetch the accounting object of the user using DAO.getAccounting(), and extract all the user data and put them in separate key value pairs in the final object which we want to return. As the information of all connected devices of a user is also stored in the user’s accounting object, that info is also extracted the same way and put into the final object.

    Accounting accounting = DAO.getAccounting(authorization.getIdentity());
    if (accounting.getJSON().has("lastLoginIP")) {
        json.put("lastLoginIP", accounting.getJSON().getString("lastLoginIP"));
    } else {
        json.put("lastLoginIP", "");
    }

    if(accounting.getJSON().has("signupTime")) {
        json.put("signupTime", accounting.getJSON().getString("signupTime"));
    } else {
        json.put("signupTime", "");
    }

    if(accounting.getJSON().has("lastLoginTime")) {
        json.put("lastLoginTime", accounting.getJSON().getString("lastLoginTime"));
    } else {
        json.put("lastLoginTime", "");
    }

    if(accounting.getJSON().has("devices")) {
        json.put("devices", accounting.getJSON().getJSONObject("devices"));
    } else {
        json.put("devices", "");
    }
    accounting.commit();

 

This is how the data of all users is fetched by any Admin or higher user role, and is then used to display the user list on the Admin panel.

Resources

Continue ReadingFetching Info of All Users and their connected devices for the SUSI.AI Admin Panel

How to integrate SUSI.AI with Google Sign In API

Google Sign-In manages the OAuth 2.0 flow and token lifecycle, simplifying our integration with Google APIs. A user always has the option to revoke access to an application at any time. Users can see a list of all the apps which they have given permission to access their account details and are using the login API. In this blog post I will discuss how to integrate this API with susi server API to enhance our AAA system. More on Google API here.

Continue ReadingHow to integrate SUSI.AI with Google Sign In API

Using SUSI.AI Accounting Model to store Device information on Server

Just like with Google Home devices and Amazon Alexa devices, SUSI.AI Users should have a way to add and store the information of their devices (Smart Speakers) on SUSI Server, so that it could be displayed to them on various clients. Hence, we needed a Servlet which could add and store the User data on the Server.

Implementation of Servlets in SUSI.AI

All servlets in SUSI extend AbstractAPIHandler class and implement APIHandler. All servlets have 4 methods, which we overwrite depending on what we want the servlet to do. They are as follows :

@Override
    public String getAPIPath() {
        return null;
    }

@Override
    public BaseUserRole getMinimalBaseUserRole() {
        return null;
    }

@Override
    public JSONObject getDefaultPermissions(BaseUserRole baseUserRole) {
        return null;
    }

@Override
    public ServiceResponse serviceImpl(Query post, HttpServletResponse response, Authorization rights, JsonObjectWithDefault permissions) throws APIException {
        Return null;
    }

 

How these 4 methods work together?

  1. First method is getAPIPath(). It returns the endpoint of the servlet.
  2. The second method is getMinimalBaseUserRole(). It returns the minimum privilege level required to access the endpoint.
  3. The third method is getDefaultPermissions(). It gets the Default Permissions of a UserRole in SUSI Server. Different UserRoles have different permissions defined in SUSI Server.
  4. Whenever the endpoint defined in the getAPIPath() method is called properly, it responds with whatever is defined in the fourth method, which is serviceImpl().

How is User data stored on SUSI Server?

Before we move on to the actual implementation of the API required to store the device information, we should get a brief idea of how exactly User data is stored on SUSI Server.

Every SUSI User has an Accounting Object. It is a JSONObject which stores the Settings of the particular User on the Server. For example, every time you change some setting on the Web Client https://chat.susi.ai, an API call is made to aaa/changeUserSettings.json with appropriate parameters with information of the changed setting, and the User settings are stored to the Server in the Accounting Object of the User.

Implementation of a Servlet to store Device information on Server

The task of this servlet is to store the information of a new device (Smart speaker) whenever it is initially set up using the Android/iOS app.

This is the implementation of the 4 methods of a servlet which is used to store information of connected devices:

    @Override
    public String getAPIPath() {
        return "/aaa/addNewDevice.json";
    }

    @Override
    public UserRole getMinimalUserRole() {
        return UserRole.USER;
    }

    @Override
    public JSONObject getDefaultPermissions(UserRole baseUserRole) {
        return null;
    }

    @Override
   public ServiceResponse serviceImpl(Query query, HttpServletResponse response, Authorization authorization, JsonObjectWithDefault permissions) throws APIException {

           JSONObject value = new JSONObject();
           
           String key = query.get("macid", null);
           String name = query.get("name", null);
           String device = query.get("device", null);
               
           if (key == null || name == null || device == null) {
               throw new APIException(400, "Bad service call, missing arguments");
           } else {
               value.put(name, device);
           }
           
           if (authorization.getIdentity() == null) {
               throw new APIException(400, "Specified user data not found, ensure you are logged in");
           } 
           else {
                Accounting accounting = DAO.getAccounting(authorization.getIdentity());
                if (accounting.getJSON().has("devices")) {
                       accounting.getJSON().getJSONObject("devices").put(key, value);
                   } 
                else {
                       JSONObject jsonObject = new JSONObject();
                       jsonObject.put(key, value);
                       accounting.getJSON().put("devices", jsonObject);
                   }
               
               JSONObject result = new JSONObject(true);
               result.put("accepted", true);
               result.put("message", "You have successfully added the device!");
               return new ServiceResponse(result);
           }
    }

 

As it can be seen from the above code, the endpoint for this servlet is /aaa/addNewDevice.json and it accepts 3 parameters –

  • macid : Mac Address of the device
  • name : Name of the device
  • device : Additional information which you want to send about the device (subject to changes)

As the main task of this servlet is user specific, and should only be accessible to the particular user, hence we returned UserRole as USER in the getMinimalUserRole() method.

In the serviceImpl() method, first we extract the value of the URL query parameters and store them in variables. If any of the parameters is missing, we display an error response code of 400 with error message “Bad service call, missing arguments”. If query parameters are fine, we store the name and device values in a new JSONObject, value in this case.

An if-else statement then checks for whether the User is logged in or not, using the authorization.getIdentity(), which is a function which returns the identity of the User. The implementation of this function is in Accounting.java file, and is as follows :

    public ClientIdentity getIdentity() {
        return identity;
    }

 

If the User is logged in, getAccounting() function of DAO.java file is called which returns an Accounting object according to the following implementation of the function :

    public static Accounting getAccounting(@Nonnull ClientIdentity identity) {
        return new Accounting(identity, accounting);
    }

 

Our device information is then stored in this Accounting object in the following format :

{
  "lastLoginIP": "162.158.166.19",
  "accepted": true,
  "message": "Success: Showing user data",
  "session": {"identity": {
    "type": "email",
    "name": "myemail@gmail.com",
    "anonymous": false
  }},
  "settings": {
    "customThemeValue": "4285f4,f5f4f6,fff,f5f4f6,fff,4285f4,",
    "theme": "light"
  },
  "devices": {
    "MacID2": {"MyDevice": "SmartSpeaker"},
    "MacID1": {"Name of device": "SmartSpeaker"}
  }
}

 

Resources

Continue ReadingUsing SUSI.AI Accounting Model to store Device information on Server

Store User’s Personal Information with SUSI

In this blog, I discuss how SUSI.AI stores personal information of it’s users. This personal information is mostly about usernames/links to different websites like LinkedIn, GitHub, Facebook, Google/Gmail etc. To store such details, we have a dedicated API. Endpoint is :

https://api.susi.ai/aaa/storePersonalInfo.json

In this API/Servlet, storing the details and getting the details, both the aspects are covered. At the time of making the API call, user has an option either to ask the server for a list of available store names along with their values or request the server to store the value for a particular store name. If a store name already exists and a client makes a call with new/updated value, The servlet will update the value for that particular store name.

The reason you are looking at minimal user role as USER is quite obvious, i.e. these details correspond to a particular user. Hence neither we want someone writing such information anonymously nor we want this information to be visible to anonymous user until allowed by the user.

In the next steps, we start evaluating the API call made by the client. We look at the combination of the parameters present in the request. If the request is to fetch list of available stores, server first checks if Accounting object even has a JSONObject for “stores” or not. If not found, it sends an error message “No personal information is added yet.” and error code 420. Prior to all these steps, server first generates an accounting object for the user. If found, details are encoded as JSONObject’s parameter. Look at the code below to understand things fairly.

Accounting accounting = DAO.getAccounting(authorization.getIdentity());
        if(post.get("fetchDetails", false)) {
            if(accounting.getJSON().has("stores")){
                JSONObject jsonObject = accounting.getJSON().getJSONObject("stores");
                json.put("stores", jsonObject);
                json.put("accepted", true);
                json.put("message", "details fetched successfully.");
                return new ServiceResponse(json);
            } else {
                throw new APIException(420, "No personal information is added yet.");
            }
        }

If the request was not to fetch the list of available stores, It means client wants server to save a new field or update a previous value for that of a store name. A combination of If-else evaluates whether the call even contains required parameters.

if (post.get(“storeName”, null) == null) {
throw new APIException(422, “Bad store name encountered!”);
}

String storeName = post.get(“storeName”, null);
if (post.get(“value”, null) == null) {
throw new APIException(422, “Bad store name value encountered!”);
}

If request contains all the required data, then store name & value are extracted as key-value pair from the request.

In the next steps, since the server is expected to store list of the store names for a particular user, First the identity is gathered from the already present authorization object in “serviceImpl” method. If the server finds a “null” identity, It throws an error with error code 400 and error message “Specified User Setting not found, ensure you are logged in”.

Else, server first checks if a JSONObject with key “stores” exists or not. If not, It will create an object and will put the key value pair in the new JSONObject. Otherwise it would anyways do so.

Since these details are for a particular account (i.e. for a particular user), these are placed in the Accounting.json file. For better knowledge, Look at the code snippet below.

if (accounting.getJSON().has("stores")) {
                accounting.getJSON().getJSONObject("stores").put(storeName, value);
            } else {
                JSONObject jsonObject = new JSONObject(true);
                jsonObject.put(storeName, value);
                accounting.getJSON().put("stores", jsonObject);
            }

            json.put("accepted", true);
            json.put("message", "You successfully updated your account information!");
            return new ServiceResponse(json);

Additional Resources :

Continue ReadingStore User’s Personal Information with SUSI