Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP - [Issue #68] Session tokens must always contain clientKey, userId (if a… #80

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions config/circleci.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
{
"adminClientKey": "8eaf12451134b24e",
"adminClientSecret": "faf0a67a00ab7b5477149b96d9c07e32",
"adminPass": "1.TestPassword.1",
"adminSessionSecret": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
"env": "test",
"port": 8080,
"db": {
"host": "127.0.0.1",
"port": 5432,
"name": "circle_test",
"user": "ubuntu",
"password": ""
},
"env": "test",
"port": 8080,
"sessionSecret": "b7dde0ef7952697c2613f76c1b5e0503",
"userAuth": {
"sessionSecret": "v3erY secr€t",
"facebook": {
Expand Down
24 changes: 13 additions & 11 deletions config/sample.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
{
"adminClientKey": "default",
"adminClientSecret": "default",
"adminPass": "default",
"adminSessionSecret": "default",
"env": "dev",
"port": 8080,
"publicHost": "http://localhost:8080",
"db": {
"host": "localhost",
"port": 5432,
"name": "sensorweb",
"user": "postgres",
"password": "default"
},
"userAuth": {
"sessionSecret": "default",
"facebook": {
"clientID": "",
"clientSecret": ""
}
},
"env": "dev",
"port": 8080,
"publicHost": "http://localhost:8080",
"sensorthings": {
"server": "https://pg-api.sensorup.com",
"path": "/st-playground/proxy/v1.0",
Expand All @@ -26,5 +20,13 @@
"value": "a8654152-74c0-41dd-a954-bcab50ff99d4"
}
},
"sessionSecret": "default",
"userAuth": {
"sessionSecret": "default",
"facebook": {
"clientID": "",
"clientSecret": ""
}
},
"version": "v1.0"
}
11 changes: 7 additions & 4 deletions config/test.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
{
"adminClientKey": "3bfff46e04c7ed52",
"adminClientSecret": "2e221f61493297318678218c9ade9b4a",
"adminPass": "1.TestPassword.1",
"adminSessionSecret": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
"env": "test",
"port": 8080,
"publicHost": "http://localhost:8080",
"db": {
"host": "localhost",
"port": 5432,
"name": "sensorwebtest",
"user": "postgres",
"password": "default"
},
"env": "test",
"port": 8080,
"permissions": ["admin", "dummy"],
"publicHost": "http://localhost:8080",
"sessionSecret": "bf5f0df1abfd6c4e10327a0ad965fe54",
"userAuth": {
"sessionSecret": "v3erY secr€t",
"facebook": {
Expand Down
65 changes: 34 additions & 31 deletions doc/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ This document provides protocol-level details of the SensorWeb API.

All requests will be to URLs of the form:

https://<host-url>/api/v1/<api-endpoint>
https://<host-url>/<api-version>/<api-endpoint>

Note that:

* All API access must be over a properly-validated HTTPS connection.
* The URL embeds a version identifier "v1"; future revisions of this API may
* The URL embeds a version identifier "v1.0"; future revisions of this API may
introduce new version numbers.

## Request Format
Expand All @@ -39,7 +39,7 @@ Use the JWT with this header:
For example:

```curl
curl 'http://localhost:3000/api/v1/clients' \
curl 'http://localhost:3000/v1.0/clients' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJraWQiOm51bGwsImFsZyI6IkhTMjU2In0.eyJpZCI6MiwibmFtZSI6ImFkbWluIn0.JNtvokupDl2hdqB+vER15y89qigPc4FviZfJOSR1Vso'
```
Expand Down Expand Up @@ -86,33 +86,40 @@ SHOULD NOT be repeated.
# API Endpoints

* Login
* [POST /auth/basic](#post-authbasic)
* [GET /auth/facebook](#get-authfacebook)
* [GET /auth/basic](#post-authbasic) :lock: (client signed token required)
* [GET /auth/facebook](#get-authfacebook) :lock: (client signed token required)
* API clients management
* [POST /clients](#post-clients) :lock: (admin scope required)
* [GET /clients](#get-clients) :lock: (admin scope required)
* [DELETE /clients/:key](#delete-clientskey) :lock: (admin scope required)
* Permissions
* [GET /permissions](#get-permissions) :lock: (admin scope required)

## POST /auth/basic
Authenticates a user using Basic authentication. So far only an admin user is
## GET /auth/basic
Authenticates a user using username and password. So far only an admin user is
allowed.
### Request
Requests must include a [basic authorization header]
(https://en.wikipedia.org/wiki/Basic_access_authentication#Client_side)
with `username:password` encoded in Base64.
Requests must include a JWT signed with a valid client secret as the
`authToken` query parameter.

```ssh
POST /api/auth/basic HTTP/1.1
Authorization: Basic YWRtaW46QXZhbGlkUGFzc3dvcmQuMA==
GET /v1.0/auth/basic?authToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbn
RJZCI6IjhlYWYxMjQ1MTEzNGIyNGUiLCJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxLkxv
bmdhZG1pbnBhc3MuMSIsInNjb3BlcyI6ImFkbWluIn0.foaQeXQGt5_8wFmW5mH9wdQLE3VKHwH9oD
clmUroWRk HTTP/1.1
```

The payload of the signed JWT must include the following information:
* `clientKey`: client identifier, aka his key.
* `scopes`: the list of permissions the client is asking for for this token.

### Response
Successful requests will produce a "201 Created" response with a session token
Successful requests will produce a 200 response with a session token
in the form of a [JWT](https://jwt.io/) with the following data:
```json
{
"id": "admin",
"scope": "admin"
"clientKey": "8eaf12451134b24e",
"scopes": ["admin"]
}
```

Expand All @@ -124,9 +131,9 @@ Content-Length: 156
Content-Type: application/json; charset=utf-8
Date: Fri, 23 Sep 2016 16:22:39 GMT
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImFk
bWluIiwic2NvcGUiOiJhZG1pbiIsImlhdCI6MTQ3NDY0Nzc1O
X0.R1vQOLVg8A-6i5QaZQVOGAzImiPvgAdkWiODYhYiNn4"
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRJZCI6IjhlYWYxMjQ1MTE
zNGIyNGUiLCJzY29wZXMiOlsiYWRtaW4iXSwiaWF0IjoxNDc0NjQ3NzU5fQ.ZxnRCbuw
yCypJMnAHHhpwSL_-y19Q4DSioA1cnB9JyY"
}
```

Expand All @@ -137,16 +144,16 @@ Requests must include a JWT signed with a valid client secret as the
`authToken` query parameter.

```ssh
POST /api/auth/facebook?authToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb
GET /v1.0/auth/facebook?authToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb
GllbnRJZCI6IjEyMzQ1Njc4OTAiLCJzY29wZXMiOlsidXNlci1mYXZvcml0ZXMiXSwiYXV0aFJlZ
GlyZWN0VXJscyI6WyJodHRwczovL2RvbWFpbi5vcmcvYXV0aC9zdWNjZXNzIl0sImF1dGhGYWlsd
XJlVXJscyI6WyJodHRwczovL2RvbWFpbi5vcmcvYXV0aC9lcnJvciJdfQ.e7rYEZsQNLG0aTjDRH
sQ2xembu3fyVe-B9bm8mFprwQ HTTP/1.1
```

The payload of the signed JWT must include the following information:
* `id`: client identifier, aka his key.
* `scope`: just `client` for now.
* `clientKey`: client identifier, aka his key.
* `scopes`: the list of permissions the client is asking for for this token.
* `redirectUrl`: the URL you would like to be redirected after a
successful login. This URL needs to be associated with your client
information first. It will gets the user's JWT as a query parameter `token`.
Expand All @@ -172,12 +179,8 @@ with the following data:

```json
{
"id": {
"opaqueId": "facebook_id",
"provider": "facebook",
"clientKey": "02e9c791d7"
},
"scope": "user"
"clientKey": "02e9c791d7",
"scopes": ["sensorthings"]
}
```

Expand All @@ -191,7 +194,7 @@ ___Parameters___
* permissions (optional) - List of permissions the client is allowed to request.

```ssh
POST /api/clients HTTP/1.1
POST /v1.0/clients HTTP/1.1
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImFkbWluIiwic2NvcGUiOiJhZG1pbiIsImlhdCI6MTQ3NDY0Nzc1OX0.R1vQOLVg8A-6i5QaZQVOGAzImiPvgAdkWiODYhYiNn4
{
Expand Down Expand Up @@ -222,7 +225,7 @@ Get the list of registered API clients.

### Request
```ssh
GET /api/clients HTTP/1.1
GET /v1.0/clients HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImFkbWluIiwic2NvcGUiOiJhZG1pbiIsImlhdCI6MTQ3NDY0Nzc1OX0.R1vQOLVg8A-6i5QaZQVOGAzImiPvgAdkWiODYhYiNn4
```

Expand Down Expand Up @@ -250,7 +253,7 @@ Deletes a registered API client given its identifier.

### Request
```ssh
DELETE /api/clients/766a06dab7358b6aec17891df1fe8555 HTTP/1.1
DELETE /v1.0/clients/766a06dab7358b6aec17891df1fe8555 HTTP/1.1
Host: localhost:8080
```

Expand All @@ -262,7 +265,7 @@ Get the list of client permissions.

### Request
```ssh
GET /api/permissions HTTP/1.1
GET /v1.0/permissions HTTP/1.1
Host: localhost:8080
```

Expand Down
46 changes: 31 additions & 15 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import fs from 'fs';
import owasp from 'owasp-password-strength-test';
import path from 'path';

import { validator } from 'express-validator';

const defaultValue = 'default';

const avoidDefault = value => {
Expand All @@ -20,21 +22,25 @@ const password = value => {
}
};

const hex = size => {
return val => {
const error = new Error('Admin client key must be an 8 chars hex string');
if (size && val.length < size) {
throw error;
}

if (!validator.isHexadecimal(val)) {
throw error;
}
};
};

convict.addFormat({
name: 'dbport',
validate: val => (val === null || val >= 0 && val <= 65535),
coerce: val => (val === null ? null : parseInt(val))
});

convict.addFormat({
name: 'hex',
validate: function(val) {
if (/[^a-fA-F0-9]/.test(val)) {
throw new Error('must be a hex key');
}
}
});

convict.addFormat({
name: 'arrayOfStrings',
validate: val => (
Expand All @@ -44,16 +50,21 @@ convict.addFormat({

// Note: Alphabetically ordered, please.
const conf = convict({
adminClientKey: {
doc: 'Admin client key',
format: hex(16),
default: ''
},
adminClientSecret: {
doc: 'Admin client secret',
format: hex(32),
default: ''
},
adminPass: {
doc: 'The password for the admin user. Follow OWASP guidelines for passwords',
format: password,
default: 'invalid'
},
adminSessionSecret: {
doc: 'Secret to sign admin session tokens',
format: avoidDefault,
default: defaultValue
},
behindProxy: {
doc: `Set this to true if the server runs behind a reverse proxy. This is
especially important if the proxy implements HTTPS with
Expand Down Expand Up @@ -129,6 +140,11 @@ const conf = convict({
}
}
},
sessionSecret: {
doc: 'Secret to sign session tokens',
format: hex(32),
default: defaultValue
},
userAuth: {
cookieSecure: {
doc: `This configures whether the cookie should be set and sent for
Expand All @@ -148,7 +164,7 @@ const conf = convict({
},
clientSecret: {
doc: 'Facebook clientSecret',
format: 'hex'
format: hex()
},
},
},
Expand Down
Loading