os-js / osjs-server

OS.js Server Module

Home Page:https://manual.os-js.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Auto logout of an inactive user after a timeout specified in server config

mahsashadi opened this issue · comments

Hi

After a defined period of time the user doesn't interact with OSjs, I need him/her to be logged out automatically and login page should be rendered again.

Since the security is the goal, client-side auto logout is not sufficient. The server must kill the session after timeout of inactivity.

We consider a timeout in server config, and interactions can be tracked by mouse move, mouse click, scroll, or keyboard input.

How and where do you recommend to implement this scenario in server-side OSjs?

This sounds like a client setting. The server does not listen for client interactions with exception of the HTTP API.

We consider a timeout in server config, and interactions can be tracked by mouse move, mouse click, scroll, or keyboard input.

If you just need this, create some global event listeners to reset a variable whenever an interaction occurs, and a timeout on the side that triggers core.make('osjs/auth').logout().

This sounds like a client setting. The server does not listen for client interactions with exception of the HTTP API.

So you mean that I can not implement it server-side in any way, true?

create some global event listeners to reset a variable whenever an interaction occurs, and a timeout on the side that triggers core.make('osjs/auth').logout()

So where do you mean to write these codes? In which files?

So you mean that I can not implement it server-side in any way, true?

You could, but this would not detect any mouse interactions etc. only the filesystem and other API methods. So it will not be as reliable as doing it in the client.

So where do you mean to write these codes? In which files?

Doesn't really matter where, but I would recommend creating a UserTimeoutService service provider or something like that with something like this:

class UserTimeoutService {
  constructor(core) {
    this.core = core
    this.interval = null
    this.lastInteraction = new Date()
    this.onSomeUserInteraction = this.onSomeUserInteraction.bind(this)
  }

  init() {
    this.interval = setInterval(checkTimeout, 10 * 1000)
    document.addEventListener('mousemove', this.onSomeUserInteraction)
    document.addEventListener('keydown', this.onSomeUserInteraction)
  }

  destroy() {
    clearInterval(this.interval)
    document.removeEventListener('mousemove', this.onSomeUserInteraction)
    document.removeEventListener('keydown', this.onSomeUserInteraction)
  }

  checkTimeout() {
    const now = new Date()
    const delta = this.lastInteraction - now
    const timeout = 1000 * 60 * 5 // 5 minutes

    if (delta > timeout) {
      this.core.make('osjs/auth').logout()
    }
  }

  onSomeUserInteraction() {
    this.lastInteraction = new Date()
  }
}

If you want to debounce the interactions you can simply do this. I would probably recommend doing this when you listen on mouse moves etc.:

class UserTimeoutService {
  constructor(core) {
    this.lastInteractionBounce = null
  }

  onSomeUserInteraction() {
    clearTimeout(this.lastInteractionBounce)
    this.lastInteractionBounce = setTimeout(() => {
      this.lastInteraction = new Date()
    }, 100)
  }
}

You could, but this would not detect any mouse interactions etc. only the filesystem and other API methods. So it will not be as reliable as doing it in the client.

How about creating two serviceProviders, both in client-side and server-side?
every time an interaction is detected, client-side serviceProvider send a request to server-side serviceProvider with a timestamp, and then everything can be done serve-side (which can be more security wise)

I'm not sure if that will help here. Because no matter how this is solved, it is the client that has to do the logout action.

Also:

  • This will increase bandwidth usage
  • The server would have to send a signal to the client, and if this signal fails the user will not get logged out :)

Another thing that came to mind: the user session has an expiration date on the server side, but this gets refreshed whenever a user makes a server request. There is a mechanic here that does a ping/pong to keep this alive for as long as possible. This could have some potential to mess things up if the inactivity was detected server-side (depending om implementation).

Because no matter how this is solved, it is the client that has to do the logout action.

we aim that timeout value (defined in server config) and the code managing that can not be seen and changed client-side.

the user session has an expiration date on the server side, but this gets refreshed whenever a user makes a server request.

The timeout can only be updated by user activity through mouse and keyboard, and if the timeout is faced, the session will be destroyed and the logout will be called.

we aim that timeout value (defined in server config) and the code managing that can not be seen and changed client-side.

Might I ask why this needs to be hidden ?

As for being changed, that is not really possible on the client-side unless it's exposed directly as a service accesible via .make() (and that it is not blacklisted).

In any case, if you really need this to be a server feature then just:

  1. Create a client service that just listens for a signal that calls the logout
    • This service also pings the server with user activity
  2. Create a server service that listens for these pings
    • Extract the username from the request session and store in a map
    • Have a timer that goes over this map and broadcast a signal to the appropriate user
    • Make sure that this map is cleared when user logs out manually

I would recommend creating a UserTimeoutService service provider or something like that with something like this:

Writing a service provider client-side, I don't know why I can not access to my class properties via setInterval and addEventListener.
For example in code below, I have access to this.core property in init with code lines tagged by 1,2,3. But with 4 and 5 tagged lines, it prints undefined.

export default class MyServiceProvider {

  constructor(core, options = {}) {
    this.core = core;
    this.options = options;
    this.interval = null
  }

  provides() {
    return [
      'namespace/api'
    ];
  }

  init() {
    //1
    this.core.singleton( 'namespace/api', () => ({
      getMyVar: () => this.myfunc(),
    }));
    //2
    this.myfunc()
    //3
    setTimeout(() => {
      this.myfunc()
    }, 5000);

    //4
    this.interval = setInterval(this.myfunc, 10 * 1000)
    //5
    document.addEventListener('mousemove', this.myfunc)

  }

  myfunc(){
    console.log('i am in myfunc', this.core)
  }
}

Because no matter how this is solved, it is the client that has to do the logout action

I did not consider this one, you are right, finally the main task is done client-side.

As for being changed, that is not really possible on the client-side unless it's exposed directly as a service accesible via .make() (and that it is not blacklisted).

It solves my concerns if it is not possible to change the client-side codes, but how?
Codes in osjs.js (main file created by webpack in dist directory) can be seen and changed in user's browser as he/she wishes, Is it false?

For example in code below, I have access to this.core property in init with code lines tagged by 1,2,3. But with 4 and 5 tagged lines, it prints undefined.

This is because when you pass around a function (the prototype of the class) it needs to be explicitly bound to a context in order to access your instance (this):

export default class MyServiceProvider {

  constructor(core, options = {}) {
    this.myfunc = this.myfunc.bind(this) // Bind the function!
  }

  init() {
    this.interval = setInterval(this.myfunc, 10 * 1000)
  }

  myfunc(){
    console.log('i am in myfunc', this.core)
  }
}

Codes in osjs.js (main file created by webpack in dist directory) can be seen and changed in user's browser as he/she wishes, Is it false?

Yes, the bundle can be changed from within devtools. But this is usually not a problem and has some limitations.

Yes, the bundle can be changed from within devtools. But this is usually not a problem and has some limitations.

As you recommended I developed it in a client-side service provider. I hope it will be secure.
Thanks a lot for your help.

No problem :)

If you're all done, please close this issue.