GithubHelp home page GithubHelp logo

apiko's People

Contributors

alewiahmed avatar borovik-denis avatar ilabacheuski avatar kasp1 avatar newset avatar prog-rajkamal avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

apiko's Issues

Owner exception in ender.checkRestrictions()

This function checks whether the user requesting the endpoint meets the endpoint restrictions and if not, it will terminate the request with an error.

Restrictions (if any) are in the endpoint's restrict property, which may contain either true to restrict the endpoint only for logged-in users or it may contain a string with comma-separated list of user roles that the user has to possess in order to be able to perform the request.

We should modify this function if there is a role restriction to behave differently in case of GET, GET-one, PUT and DELETE request methods:

  1. Check if the user meets role requirements and proceed if yes, if not:
  2. Check if the endpoint contains ownership: true option and terminate the request if not, if yes:
  3. Check if a database collection with the name in URL exists var collection = g.ender.endFromReq(req).split('/')[1] and terminate the request if not, if yes:
  4. Check if the user's ID (from req.session) equals the ID that was set in the owner property of the record that's being affected by the request and terminate the request if not. If yes, proceed.

The above process applies for GET-one, PUT and DELETE. POST shouldn't be affected by ownership at all, because since data are created with POST, the ownership is yet being created during a POST reqeust.

In case of GET-many, the generic handler should only return records where the session ID matches the owner ID.

The long story

Regular application with any user content usually needs to:

  1. Assign the ownership of created content to its creator (user) and limit other users in manipulating with foreign content.
  2. Allow administrators to manipulate with any content.

Apiko can help with the second point, because while POST (CREATE) endpoint of a database collection can be open to anyone, GET (READ), PUT (UPDATE) and DELETE endpoints can be restricted to certain user roles, like "admin".

Restricting GET, PUT and DELETE endpoints to specific user roles will on the other side prevent anyone else from using these endpoints, including the content creator if they don't possess the specified user roles.

A solution is to create custom endpoint handlers for each of the mentioned restricted endpoints that would also check whether the user, who is trying perform the request, is also the creator of the requested content and such custom handler would allow them to successfully perform the request in that case.

This common practice could be further automated with Apiko in the following way:

  1. All database collections/tables would have an "owner" property/column in a similar way they now all have the "id" property/column.

  2. The generic POST (CREATE) handler would be modified to check if there is a user ID associated with the currently active session upon a request and if yes, the user ID would be considered the "creator/owner" ID of the content that's being created using the POST request. If there is no session ID specified, 0 would be put to the "owner" property instead. Note that if any Apiko server would need to have every single record of a user content assigned with a user, then the specific POST endpoind could be already restricted to logged-in users only.

  3. If any of the other endpoints (GET, GET-one, PUT, DELETE) would be restricted by any user role, the generic handlers would automatically check if there is the "ownership: true" setting mentioned in the given endpoint's settings/properties and if yes, it would further check if the requesting client is logged in -> if the user's ID == to the owner ID -> it would allow to successfully perform the endpoint request even if the user doesn't possess the required user roles.

Problem using ProxyPass Apache

Hello!
I have multiple webserver instances running on same ip, and using ProxyPass from apache to handle this.
For example I have domain bth.joelmandell.se that is a documentroot in apache and mwb-api.joelmandell.se that is the proxypass. I get an error when trying to reach the endpoint users in apiko: DNS lookup failure for: 192.168.0.108:5000users.
What can I do to fix this?

"owner" property in all collections

All collections should have the "owner" property by default, where user ID or 0 should be stored.

Similar to how all collections have the "id" property at the moment.

The long story

Regular application with any user content usually needs to:

  1. Assign the ownership of created content to its creator (user) and limit other users in manipulating with foreign content.
  2. Allow administrators to manipulate with any content.

Apiko can help with the second point, because while POST (CREATE) endpoint of a database collection can be open to anyone, GET (READ), PUT (UPDATE) and DELETE endpoints can be restricted to certain user roles, like "admin".

Restricting GET, PUT and DELETE endpoints to specific user roles will on the other side prevent anyone else from using these endpoints, including the content creator if they don't possess the specified user roles.

A solution is to create custom endpoint handlers for each of the mentioned restricted endpoints that would also check whether the user, who is trying perform the request, is also the creator of the requested content and such custom handler would allow them to successfully perform the request in that case.

This common practice could be further automated with Apiko in the following way:

  1. All database collections/tables would have an "owner" property/column in a similar way they now all have the "id" property/column.

  2. The generic POST (CREATE) handler would be modified to check if there is a user ID associated with the currently active session upon a request and if yes, the user ID would be considered the "creator/owner" ID of the content that's being created using the POST request. If there is no session ID specified, 0 would be put to the "owner" property instead. Note that if any Apiko server would need to have every single record of a user content assigned with a user, then the specific POST endpoind could be already restricted to logged-in users only.

  3. If any of the other endpoints (GET, GET-one, PUT, DELETE) would be restricted by any user role, the generic handlers would automatically check if there is the "ownership: true" setting mentioned in the given endpoint's settings/properties and if yes, it would further check if the requesting client is logged in -> if the user's ID == to the owner ID -> it would allow to successfully perform the endpoint request even if the user doesn't possess the required user roles.

DELETE /users has no handler

We have endpoint that has no handler. Apiko always complains warning to it.
And it's marked as critical error. I think warning should go at least to second (1) lvl of log, not the highest (0) lvl

maintainBrowserTab configuration option

Originally I was thinking that the server side of Apiko could open a browser tab on startup with the dev UI in development mode, which would make it easier to get started with Apiko in general.

Then someone mentioned that Apiko could have a CLI tool to make things easier, so I've made it too see how will it turn out.

The problem with Apiko CLI is that it doesn't know when the server runs in "development" environment and when does it run in "production". Because environments are not strictly defined in Apiko, anyone can choose their way to define environments, here's what it looks like in the starter template for example:

if (process.argv[2] === 'prod') {
  // protects the server with the secret generated on the first run (see apiko.json)
}

if (process.argv[2] === 'dev') {
  config.protect = false, // if true, protects /dev/ UI with the secret generated on the first run (see apiko.json), use for production
  config.verbosity = 2 // 0 - 3
}

As you can see, a configuration set for an environment may have any name the user may choose. So the Apiko CLI tool can't predict which one is "development". Given the main file included in package.json, the most simplicity Apiko CLI can provide as of now is: apiko run <env_name>.

So perhaps maintaining the browser tab should be really up to the Apiko server application. We could possibly add a configuration option like maintainBrowserTab, which the user could use in their definition of "development" environment.

Finally, the apiko setup <directory> command downloads the starter repo, where the development environment is initially named "dev". So this "setup" command can be sure to use it. With the new installation instructions, a newcomer will have to only run this "setup" command and wait a while, then the dev UI will pop up to their face with no additional hassle.

checkRestrictions compare letters not roles

What this lines do?
checking restriction in ender.js
endpoint.restrict is a string
userRoles = ['string']

I think here is something totally wrong. Am I right?

var userRoles = req.session.user.role.split(',')
var hasOne = false
for (let i in endpoint.restrict) {
  if (userRoles.indexOf(endpoint.restrict[i]) >= 0) {
    hasOne = true
  }
}

All endpoints accessible with the server's secret

Any endpoint should be accessible to a client that specifies the server's secret in a request, regardless of the endpoint's restrictions. This feature will be useful for the Apiko UI for implementing collection data management.

A server secret is a random token generated when an Apiko server is started first time. It can be obtained anywhere in the server's code using g.config.secret.

Whether a client has the right to perform a request to an endpoint is being checked upon every request in the checkRestrictions() middleware function of ender.js: https://github.com/apiko-rest-api/apiko/blob/master/src/ender.js#L348-L387

If the client specifies a secret parameter for a request either as a part of the URL or request body, it can be retrieved inside this checkRestrictions() function using req.all.secret.

If the value of req.all.secret equals the value of g.config.secret, the checkRestrictions() function should proceed to the next middleware function in the stack without any further checks indeed.

Apiko server not listening

Problem: apiko server is not running.

Steps to reproduce:

  1. install apiko-starter
  2. install apiko (with fix #5 )
  3. on apiko-starter, run npm run dev

More Info:
When I run npm run dev on kasp1/apiko-starter, it shows me that
run_dev__server_not_listening

Message of "server listening to port" is not shown, also apiko devui can not function due to this error.

System:
OS: Windows 8.1
node: v5.7.0
npm : v3.6.0
console: git-bash

GET user & GET users custom handlers

Getting a user row/record from the database can be achieved using GET /users/:id (for user with a specific ID) or using GET /users for many records at the moment.

These are generic handlers, such handlers are created for every database collection/table. If you create a database collection called e.g. "posts", generic handlers will be created for GET /posts, GET /posts/:id, POST /posts, PUT /posts and DELETE /posts. Of course GET /posts/:id will return the whole post's row from the database unless someone overrides this handler with their own.

In case of the users collection, these generic handlers mean that everything from the row will be sent to output, including the user's password hash and that's not optimal in the means of security, we better create a custom handler for these that will hide the password.

First thing is to add a definition of custom endpoints for both GET /users and GET /users/:id to src/core.js:

'GET /users': {
  extendable: true,
  restrict: true, // should only be accessible by a logged-in user
  handlers: {
    core: './users/get' // the handler should be in src/users/get.js
  },
  comment: 'Returns a list of users.'
},
'GET /users/:id': {
  extendable: true,
  restrict: true, // should only be accessible by a logged-in user
  handlers: {
    core: './users/getone' // the handler should be in src/users/getone.js
  },
  comment: 'Returns information about a user with the specified ID.'
},

Handlers for both of these endpoints can be copied from the generic folder, they should be modified in a way they won't return the password hash.

Please add yourself as an assignee to this issue before you start working on this, so we don't have two people working on this at the same time. Teamwork! :-)

Password reset endpoint

It would be handy to add an endpoint that would allow a user's password reset.

How could it work? Any ideas?

Database structure synchronization on setup reload

When the server setup is changed (whenever a change to endpoints or collections in the UI is made and the setup is "saved" to the server), the server triggers a reload and it is necessary to synchronize the database structure according to the new setup of collections.

What needs to be synchronized:

  • Complete CREATE/DROP of tables that are added/removed.
  • Adding/removing columns in existing tables that have been changed.
  • Changing data type of existing columns if their data type is different in the new setup.

The problem is that Sequelize.js, an awesome lib in many ways that we use for ORM in Apiko, does not fully support this kind of "programmatic migrations" yet! See this Sequelize issue.

This feature is indeed very critical for Apiko to be any useful.

What Sequlize does whenever a database structure changes in the model is that it drops and recreates the table, which dumps all data.

We need this to get to work in the sync() function of data.js. The Sequlize models can be accessed from this function as this.store, it's an object where each model is named according to the database table name, so e.g. the users model could be accessed with this.store.users. When this sync() function is called during a (re)load, the models are already updated to the new setup, so can be used as a reference of "what should the new database structure look like".

Unused verifyPassword() in data.js

While it's cool to have a function that uniformally verify password in DB, but this one is never used and contains linting errors. Unused function verifyPassword()

verifyPassword (username, password) {
      return new Promise((resolve, reject) => {
        g.data.store.models.users.findOne({ where: { username: username } }).then((user) => {
          if (bcrypt.compareSync(password, user.password)) {
            resolve()
          } else {
            reject() // !!! --->> reject requires a new Error instance
          }
        }).catch(() => {
          reject() // !!! --->> reject requires a new Error instance
        })
      })
    }

Deglobalization

As of now, the Apiko server singletons are being accessed with global.singletonName or g.singletonName, which probably makes these singletons available in user scripts and could collide with user definitions.

These singletons are only supposed to be used internally and should be only visible within Apiko.

Most of these globalizing statements can be found in index.js.

"Does user already exist?" endpoint

A regular application that incorporates registration normally wants to figure out whether a user already exists or not - for the purposes of registration form.

At the moment, a check if a user exists can be performed with the generic endpoint GET /users/:id, such handler is created of every database table and returns all data for a row it finds. Which means that this thing will return everything from the user's row, including password hash - we have to fix that in #9

These GET /users/:id and GET /users endpoints should only be accessible by a logged in user really, because otherwise any robot any easily collect user information like emails and stuff. On the registration form through, the user can't be logged in if they haven't even registered yet, but the registration form still needs a way to find out if their chosen username has already been taken or not, so there are no users with duplicate usernames.

A solution might be another endpoint dedicated to just checking whether a username exists. It may be for example GET /users/exists. In order to add such a core handler (that can be overriden by anyone who uses Apiko), we would need to add a handler definition to src/core.js:

'GET /users/exists': {
  extendable: true,
  params: {
    username: {
      required: true,
      regex: '^\\S+\\@\\S+\\.\\S+$' // default regex to match a username (as an email), can be overriden by a user endpoint
    },
  },
  handlers: {
    core: './users/exists' // the handler should be implemented in src/users/exists.js
  }
}

The generic src/generic/get.js handler may be used as a an example to modify and implement this handler.

Please add yourself as an assignee to this issue before you start working on this, so we don't have two people working on this at the same time. Teamwork! :-)

Password reset

As was discussed in #55 there is a problem when someone stole your access token.
In passwordchange api we agreed to check the current user's (or admin's) password every request. But in password change api such approach doesn't work. If you reset password it means your current password is lost somehow.
Right know what I consider is to have an e-mail information about user to send an email with random token and store this token in Session for might be hour long.
Or disable this feature.
Or figure out some other method to do it.

Now it just generate random password if you are admin or signed in user, then send it back. So any who has token may just reset password than with help of this new one change the password.

The password change endpoint sends a 500 status code.

When trying to change the password, I've sent a POST to users/password/change/:id. My post args where old: 'old password', new: 'new password', token: 'my token'. When I send it without a token, it sends me a 401 status code, which is good.

Literal logout

Is it necessary to have an endpoint that would literally log out the currently logged in user?

Please share your opinions.

Admin password change of other users still requires the old password parameter (doesn't matter if it's correct or not)?

I have two issues,

  1. When an admin changes password of a user using /users/password/change/:id, The old password parameter is not used at all, so why should I send it with the request if it's not used at all. When I send the current password with some gibberish in it, it works, when I don't send the old password parameter it sends me {"code":1,"message":"The old parameter is required, but undefined or empty."}.
  2. When a user changes a password, the current password is required. That's great. But when an admin changes someone's password (even his/hers) the current password is not used as I've said before. So, the user changing the password is more stronger in security than the admin changing his/her's/other's password. It should be even stronger for the admin changing the password in my opinion. I'm not saying the admin should send the password of the user with the request, he/she should send his/her own password with the request for any password change.

Core endpoint for stats

There should be a way for the UI to retrieve stats from the server side.

It may be an endpoint handler for GET /apiko/stats

Perhaps it would be convenient to put the handler in /src/apiko/stats/get.js in the same way as for example the /src/apiko/core/get.js exists.

This endpoint should also be registered in the core part of server's setup (/src/core.js), extending the endpoints object with for example:

'GET /apiko/core': {
  extendable: false,
  comment: 'Stats data with optional interval parameters. If no interval is set, data for the recent 30 days will be returned.
  handlers: {
    core: './apiko/stats/get'
  },
  params: {
    start: {
      required: false,
      regex: '^\\d+$',
      comment: 'Time interval start for the requested stats as an UNIX timestamp.'
    },
    end: {
      required: false,
      regex: '^\\d+$',
      comment: 'Time interval end for the requested stats as an UNIX timestamp.'
    },
  }
}

Only owner data from GET (many)

If a GET (many) endpoint is restricted by user roles, the generic handler checks if the requesting user does possess the required user roles and if not, it terminates the request. Instead of terminating the request straight away, it should:

  1. Check if the endpoint contains the ownership: true option and terminate the request if not, if yes:
  2. Check if there are any records in the corresponding database collection that have owner property equalling to the user's ID from session and either:
  • Terminate the request with 404 if no suitable records were found.
  • Return only the matching rows (with 200).

The long story

Regular application with any user content usually needs to:

  1. Assign the ownership of created content to its creator (user) and limit other users in manipulating with foreign content.
  2. Allow administrators to manipulate with any content.

Apiko can help with the second point, because while POST (CREATE) endpoint of a database collection can be open to anyone, GET (READ), PUT (UPDATE) and DELETE endpoints can be restricted to certain user roles, like "admin".

Restricting GET, PUT and DELETE endpoints to specific user roles will on the other side prevent anyone else from using these endpoints, including the content creator if they don't possess the specified user roles.

A solution is to create custom endpoint handlers for each of the mentioned restricted endpoints that would also check whether the user, who is trying perform the request, is also the creator of the requested content and such custom handler would allow them to successfully perform the request in that case.

This common practice could be further automated with Apiko in the following way:

  1. All database collections/tables would have an "owner" property/column in a similar way they now all have the "id" property/column.

  2. The generic POST (CREATE) handler would be modified to check if there is a user ID associated with the currently active session upon a request and if yes, the user ID would be considered the "creator/owner" ID of the content that's being created using the POST request. If there is no session ID specified, 0 would be put to the "owner" property instead. Note that if any Apiko server would need to have every single record of a user content assigned with a user, then the specific POST endpoind could be already restricted to logged-in users only.

  3. If any of the other endpoints (GET, GET-one, PUT, DELETE) would be restricted by any user role, the generic handlers would automatically check if there is the "ownership: true" setting mentioned in the given endpoint's settings/properties and if yes, it would further check if the requesting client is logged in -> if the user's ID == to the owner ID -> it would allow to successfully perform the endpoint request even if the user doesn't possess the required user roles.

Password change endpoint

Perhaps we'll need a specific endpoint for changing passwords. It could be a e.g. POST /users/password/change/userID and it could be in the src/users/passwordchange.js file.

This endpoint should probably accept three parameteres:

  • user ID
  • old password
  • new password

If old password matches, it should be replaced in the database with the new password and the server should reply with 200 OK.

Perhaps the endpoint parameters could be named literally old and new. The endpoint will also need to receive a user ID of whom is the password being changed. The endpoint definition in core.js could look like this:

    'POST /users/password/change/:id': {
      extendable: true,
      comment: 'Attempts to change a user password (according to specified user ID).',
      ownership: true,
      restrict: 'admin', //only the user themself and an admin can change their password
      params: {
        id: {
          required: true,
          regex: '^\\d{1,10}$',
          comment: 'User ID of whom password is being changed.'
        },
        old: {
          required: true,
          regex: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,18}$',
          comment: 'The specified user\'s old password.'
        },
        new: {
          required: true,
          regex: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,18}$',
          comment: 'The specified user\'s new password.'
        }
      },
      handlers: {
        core: './users/passwordchange'
      },
      errors: {
        7: 'Incorrect old password.',
        10: 'No such user.'
      },
      "response": {}
    }

The handler function could be copied from e.g. src/users/login.js. The received user ID, old password and new password can all be retrieved from the req.all.paramName object respectively (ĂŹd, old, new`).

File update (PUT)

Apiko currently does not support updating of an existing uploaded file (PUT /files). If you would like to see this feature, +1 this issue. Thank you.

screenshot_1

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.