Skip to content

Commit c170277

Browse files
committed
[Issue #68] Session tokens must always contain clientKey, userId (if any) and the list of allowed permissions
1 parent a371048 commit c170277

File tree

16 files changed

+381
-281
lines changed

16 files changed

+381
-281
lines changed

config/circleci.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
{
2+
"adminClientKey": "8eaf12451134b24e",
3+
"adminClientSecret": "faf0a67a00ab7b5477149b96d9c07e32",
24
"adminPass": "1.TestPassword.1",
3-
"adminSessionSecret": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
4-
"env": "test",
5-
"port": 8080,
65
"db": {
76
"host": "127.0.0.1",
87
"port": 5432,
98
"name": "circle_test",
109
"user": "ubuntu",
1110
"password": ""
1211
},
12+
"env": "test",
13+
"port": 8080,
14+
"sessionSecret": "b7dde0ef7952697c2613f76c1b5e0503",
1315
"userAuth": {
1416
"sessionSecret": "v3erY secr€t",
1517
"facebook": {

config/sample.json

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
11
{
2+
"adminClientKey": "default",
3+
"adminClientSecret": "default",
24
"adminPass": "default",
3-
"adminSessionSecret": "default",
4-
"env": "dev",
5-
"port": 8080,
6-
"publicHost": "http://localhost:8080",
75
"db": {
86
"host": "localhost",
97
"port": 5432,
108
"name": "sensorweb",
119
"user": "postgres",
1210
"password": "default"
1311
},
14-
"userAuth": {
15-
"sessionSecret": "default",
16-
"facebook": {
17-
"clientID": "",
18-
"clientSecret": ""
19-
}
20-
},
12+
"env": "dev",
13+
"port": 8080,
14+
"publicHost": "http://localhost:8080",
2115
"sensorthings": {
2216
"server": "https://pg-api.sensorup.com",
2317
"path": "/st-playground/proxy/v1.0",
@@ -26,5 +20,13 @@
2620
"value": "a8654152-74c0-41dd-a954-bcab50ff99d4"
2721
}
2822
},
23+
"sessionSecret": "default",
24+
"userAuth": {
25+
"sessionSecret": "default",
26+
"facebook": {
27+
"clientID": "",
28+
"clientSecret": ""
29+
}
30+
},
2931
"version": "v1.0"
3032
}

config/test.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
{
2+
"adminClientKey": "3bfff46e04c7ed52",
3+
"adminClientSecret": "2e221f61493297318678218c9ade9b4a",
24
"adminPass": "1.TestPassword.1",
3-
"adminSessionSecret": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
4-
"env": "test",
5-
"port": 8080,
6-
"publicHost": "http://localhost:8080",
75
"db": {
86
"host": "localhost",
97
"port": 5432,
108
"name": "sensorwebtest",
119
"user": "postgres",
1210
"password": "default"
1311
},
12+
"env": "test",
13+
"port": 8080,
14+
"permissions": ["admin", "dummy"],
15+
"publicHost": "http://localhost:8080",
16+
"sessionSecret": "bf5f0df1abfd6c4e10327a0ad965fe54",
1417
"userAuth": {
1518
"sessionSecret": "v3erY secr€t",
1619
"facebook": {

doc/API.md

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ This document provides protocol-level details of the SensorWeb API.
1010

1111
All requests will be to URLs of the form:
1212

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

1515
Note that:
1616

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

2121
## Request Format
@@ -39,7 +39,7 @@ Use the JWT with this header:
3939
For example:
4040

4141
```curl
42-
curl 'http://localhost:3000/api/v1/clients' \
42+
curl 'http://localhost:3000/v1.0/clients' \
4343
-H 'Accept: application/json' \
4444
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJraWQiOm51bGwsImFsZyI6IkhTMjU2In0.eyJpZCI6MiwibmFtZSI6ImFkbWluIn0.JNtvokupDl2hdqB+vER15y89qigPc4FviZfJOSR1Vso'
4545
```
@@ -86,33 +86,40 @@ SHOULD NOT be repeated.
8686
# API Endpoints
8787

8888
* Login
89-
* [POST /auth/basic](#post-authbasic)
90-
* [GET /auth/facebook](#get-authfacebook)
89+
* [GET /auth/basic](#post-authbasic) :lock: (client signed token required)
90+
* [GET /auth/facebook](#get-authfacebook) :lock: (client signed token required)
9191
* API clients management
9292
* [POST /clients](#post-clients) :lock: (admin scope required)
9393
* [GET /clients](#get-clients) :lock: (admin scope required)
9494
* [DELETE /clients/:key](#delete-clientskey) :lock: (admin scope required)
9595
* Permissions
9696
* [GET /permissions](#get-permissions) :lock: (admin scope required)
9797

98-
## POST /auth/basic
99-
Authenticates a user using Basic authentication. So far only an admin user is
98+
## GET /auth/basic
99+
Authenticates a user using username and password. So far only an admin user is
100100
allowed.
101101
### Request
102-
Requests must include a [basic authorization header]
103-
(https://en.wikipedia.org/wiki/Basic_access_authentication#Client_side)
104-
with `username:password` encoded in Base64.
102+
Requests must include a JWT signed with a valid client secret as the
103+
`authToken` query parameter.
104+
105105
```ssh
106-
POST /api/auth/basic HTTP/1.1
107-
Authorization: Basic YWRtaW46QXZhbGlkUGFzc3dvcmQuMA==
106+
GET /v1.0/auth/basic?authToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbn
107+
RJZCI6IjhlYWYxMjQ1MTEzNGIyNGUiLCJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxLkxv
108+
bmdhZG1pbnBhc3MuMSIsInNjb3BlcyI6ImFkbWluIn0.foaQeXQGt5_8wFmW5mH9wdQLE3VKHwH9oD
109+
clmUroWRk HTTP/1.1
108110
```
111+
112+
The payload of the signed JWT must include the following information:
113+
* `clientKey`: client identifier, aka his key.
114+
* `scopes`: the list of permissions the client is asking for for this token.
115+
109116
### Response
110-
Successful requests will produce a "201 Created" response with a session token
117+
Successful requests will produce a 200 response with a session token
111118
in the form of a [JWT](https://jwt.io/) with the following data:
112119
```json
113120
{
114-
"id": "admin",
115-
"scope": "admin"
121+
"clientKey": "8eaf12451134b24e",
122+
"scopes": ["admin"]
116123
}
117124
```
118125

@@ -124,9 +131,9 @@ Content-Length: 156
124131
Content-Type: application/json; charset=utf-8
125132
Date: Fri, 23 Sep 2016 16:22:39 GMT
126133
{
127-
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImFk
128-
bWluIiwic2NvcGUiOiJhZG1pbiIsImlhdCI6MTQ3NDY0Nzc1O
129-
X0.R1vQOLVg8A-6i5QaZQVOGAzImiPvgAdkWiODYhYiNn4"
134+
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRJZCI6IjhlYWYxMjQ1MTE
135+
zNGIyNGUiLCJzY29wZXMiOlsiYWRtaW4iXSwiaWF0IjoxNDc0NjQ3NzU5fQ.ZxnRCbuw
136+
yCypJMnAHHhpwSL_-y19Q4DSioA1cnB9JyY"
130137
}
131138
```
132139

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

139146
```ssh
140-
POST /api/auth/facebook?authToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb
147+
GET /v1.0/auth/facebook?authToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb
141148
GllbnRJZCI6IjEyMzQ1Njc4OTAiLCJzY29wZXMiOlsidXNlci1mYXZvcml0ZXMiXSwiYXV0aFJlZ
142149
GlyZWN0VXJscyI6WyJodHRwczovL2RvbWFpbi5vcmcvYXV0aC9zdWNjZXNzIl0sImF1dGhGYWlsd
143150
XJlVXJscyI6WyJodHRwczovL2RvbWFpbi5vcmcvYXV0aC9lcnJvciJdfQ.e7rYEZsQNLG0aTjDRH
144151
sQ2xembu3fyVe-B9bm8mFprwQ HTTP/1.1
145152
```
146153

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

173180
```json
174181
{
175-
"id": {
176-
"opaqueId": "facebook_id",
177-
"provider": "facebook",
178-
"clientKey": "02e9c791d7"
179-
},
180-
"scope": "user"
182+
"clientKey": "02e9c791d7",
183+
"scopes": ["sensorthings"]
181184
}
182185
```
183186

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

193196
```ssh
194-
POST /api/clients HTTP/1.1
197+
POST /v1.0/clients HTTP/1.1
195198
Content-Type: application/json
196199
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImFkbWluIiwic2NvcGUiOiJhZG1pbiIsImlhdCI6MTQ3NDY0Nzc1OX0.R1vQOLVg8A-6i5QaZQVOGAzImiPvgAdkWiODYhYiNn4
197200
{
@@ -222,7 +225,7 @@ Get the list of registered API clients.
222225

223226
### Request
224227
```ssh
225-
GET /api/clients HTTP/1.1
228+
GET /v1.0/clients HTTP/1.1
226229
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImFkbWluIiwic2NvcGUiOiJhZG1pbiIsImlhdCI6MTQ3NDY0Nzc1OX0.R1vQOLVg8A-6i5QaZQVOGAzImiPvgAdkWiODYhYiNn4
227230
```
228231

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

251254
### Request
252255
```ssh
253-
DELETE /api/clients/766a06dab7358b6aec17891df1fe8555 HTTP/1.1
256+
DELETE /v1.0/clients/766a06dab7358b6aec17891df1fe8555 HTTP/1.1
254257
Host: localhost:8080
255258
```
256259

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

263266
### Request
264267
```ssh
265-
GET /api/permissions HTTP/1.1
268+
GET /v1.0/permissions HTTP/1.1
266269
Host: localhost:8080
267270
```
268271

src/config.js

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import fs from 'fs';
33
import owasp from 'owasp-password-strength-test';
44
import path from 'path';
55

6+
import { validator } from 'express-validator';
7+
68
const defaultValue = 'default';
79

810
const avoidDefault = value => {
@@ -20,21 +22,25 @@ const password = value => {
2022
}
2123
};
2224

25+
const hex = size => {
26+
return val => {
27+
const error = new Error('Admin client key must be an 8 chars hex string');
28+
if (size && val.length < size) {
29+
throw error;
30+
}
31+
32+
if (!validator.isHexadecimal(val)) {
33+
throw error;
34+
}
35+
};
36+
};
37+
2338
convict.addFormat({
2439
name: 'dbport',
2540
validate: val => (val === null || val >= 0 && val <= 65535),
2641
coerce: val => (val === null ? null : parseInt(val))
2742
});
2843

29-
convict.addFormat({
30-
name: 'hex',
31-
validate: function(val) {
32-
if (/[^a-fA-F0-9]/.test(val)) {
33-
throw new Error('must be a hex key');
34-
}
35-
}
36-
});
37-
3844
convict.addFormat({
3945
name: 'arrayOfStrings',
4046
validate: val => (
@@ -44,16 +50,21 @@ convict.addFormat({
4450

4551
// Note: Alphabetically ordered, please.
4652
const conf = convict({
53+
adminClientKey: {
54+
doc: 'Admin client key',
55+
format: hex(16),
56+
default: ''
57+
},
58+
adminClientSecret: {
59+
doc: 'Admin client secret',
60+
format: hex(32),
61+
default: ''
62+
},
4763
adminPass: {
4864
doc: 'The password for the admin user. Follow OWASP guidelines for passwords',
4965
format: password,
5066
default: 'invalid'
5167
},
52-
adminSessionSecret: {
53-
doc: 'Secret to sign admin session tokens',
54-
format: avoidDefault,
55-
default: defaultValue
56-
},
5768
behindProxy: {
5869
doc: `Set this to true if the server runs behind a reverse proxy. This is
5970
especially important if the proxy implements HTTPS with
@@ -129,6 +140,11 @@ const conf = convict({
129140
}
130141
}
131142
},
143+
sessionSecret: {
144+
doc: 'Secret to sign session tokens',
145+
format: hex(32),
146+
default: defaultValue
147+
},
132148
userAuth: {
133149
cookieSecure: {
134150
doc: `This configures whether the cookie should be set and sent for
@@ -148,7 +164,7 @@ const conf = convict({
148164
},
149165
clientSecret: {
150166
doc: 'Facebook clientSecret',
151-
format: 'hex'
167+
format: hex()
152168
},
153169
},
154170
},

0 commit comments

Comments
 (0)