Skip to content
This repository was archived by the owner on Nov 23, 2022. It is now read-only.

Commit 2245333

Browse files
authored
Merge pull request #5 from exoframejs/develop
Merge branch 'develop', prepare v0.21.0
2 parents a5a42f2 + 5c37b9d commit 2245333

File tree

5 files changed

+183
-7
lines changed

5 files changed

+183
-7
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
.nyc_output
33
coverage
4+
test/fixtures/auth.db

src/auth/index.js

+55-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const uuid = require('uuid');
1010
// our packages
1111
const {auth} = require('../../config');
1212
const {getConfig} = require('../config');
13-
const {reqCollection} = require('../db');
13+
const {reqCollection, getTokenCollection} = require('../db');
1414

1515
// promisify readfile
1616
const readFile = promisify(fs.readFile);
@@ -22,7 +22,15 @@ const publicKeysPath = join(keysFolder, 'authorized_keys');
2222

2323
// validation function
2424
const validate = (request, decodedToken, callback) => {
25-
const {user, loggedIn} = decodedToken;
25+
const {user, loggedIn, deploy, tokenName} = decodedToken;
26+
27+
// if it's a deployment token - check if it's still in db
28+
if (deploy) {
29+
const existingToken = getTokenCollection().findOne({tokenName, user: user.username});
30+
if (!existingToken) {
31+
return callback(null, false, user);
32+
}
33+
}
2634

2735
if (!user || !loggedIn) {
2836
return callback(null, false, user);
@@ -65,19 +73,58 @@ module.exports = server =>
6573
});
6674

6775
server.route({
68-
method: 'GET',
76+
method: 'POST',
6977
path: '/deployToken',
7078
config: {auth: 'token'},
7179
handler(request, reply) {
7280
// generate new deploy token
81+
const tokenName = request.payload.tokenName;
7382
const user = request.auth.credentials;
74-
const token = jwt.sign({loggedIn: true, user, deploy: true}, auth.privateKey, {
83+
// generate new private key
84+
const token = jwt.sign({loggedIn: true, user, tokenName, deploy: true}, auth.privateKey, {
7585
algorithm: 'HS256',
7686
});
87+
// save token name to config
88+
getTokenCollection().insert({tokenName, user: user.username});
89+
// send back to user
7790
reply({token});
7891
},
7992
});
8093

94+
server.route({
95+
method: 'GET',
96+
path: '/deployToken',
97+
config: {auth: 'token'},
98+
handler(request, reply) {
99+
// generate new deploy token
100+
const user = request.auth.credentials;
101+
// save token name to config
102+
const tokens = getTokenCollection().find({user: user.username});
103+
// send back to user
104+
reply({tokens});
105+
},
106+
});
107+
108+
server.route({
109+
method: 'DELETE',
110+
path: '/deployToken',
111+
config: {auth: 'token'},
112+
handler(request, reply) {
113+
// generate new deploy token
114+
const tokenName = request.payload.tokenName;
115+
const user = request.auth.credentials;
116+
const existingToken = getTokenCollection().findOne({user: user.username, tokenName});
117+
if (!existingToken) {
118+
reply({removed: false, reason: 'Token does not exist'}).code(200);
119+
return;
120+
}
121+
// remove token from collection
122+
getTokenCollection().remove(existingToken);
123+
// send back to user
124+
reply().code(204);
125+
},
126+
});
127+
81128
server.route({
82129
method: 'GET',
83130
path: '/login',
@@ -114,7 +161,10 @@ module.exports = server =>
114161

115162
try {
116163
const publicKeysFile = await readFile(publicKeysPath);
117-
const publicKeys = publicKeysFile.toString().split('\n').filter(k => k && k.length > 0);
164+
const publicKeys = publicKeysFile
165+
.toString()
166+
.split('\n')
167+
.filter(k => k && k.length > 0);
118168
const res = await Promise.all(publicKeys.map(key => verifyWithKey({key, token, phrase: loginReq.phrase})));
119169
if (!res.some(r => r === true)) {
120170
reply({error: 'Not authorized!'}).code(401);

src/config/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ const publicKeysPath =
1919
? path.join(os.homedir(), '.ssh')
2020
: path.join(__dirname, '..', '..', 'test', 'fixtures');
2121

22+
// export paths for others
23+
exports.baseFolder = baseFolder;
24+
2225
// create base folder if doesn't exist
2326
try {
2427
fs.statSync(baseFolder);

src/db/index.js

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,44 @@
11
// npm packages
2+
const path = require('path');
23
const Loki = require('lokijs');
34

4-
// init in-memory db for login requests
5+
// our packages
6+
const {baseFolder} = require('../config');
7+
8+
// TTL for auth requests
59
const REQ_TTL =
610
process.env.NODE_ENV !== 'testing'
711
? 5 * 60 * 1000 // 5 mins for prod
812
: 0; // 0 for tests
13+
14+
// init in-memory adapter for login requests
915
const memAdapter = new Loki.LokiMemoryAdapter();
16+
const fsAdapter = new Loki.LokiFsAdapter();
17+
18+
// init in-memory requests db
1019
const db = new Loki('requests.db', {adapter: memAdapter, autoload: true});
20+
// init persistent tokens db
21+
let tokenCollection = {};
22+
const tokenDb = new Loki(path.join(baseFolder, 'auth.db'), {
23+
adapter: process.env.NODE_ENV !== 'testing' ? fsAdapter : memAdapter,
24+
autoload: true,
25+
autoloadCallback() {
26+
// get of create tokens collection
27+
tokenCollection = tokenDb.getCollection('tokens');
28+
if (tokenCollection === null) {
29+
tokenCollection = tokenDb.addCollection('tokens');
30+
}
31+
},
32+
autosave: process.env.NODE_ENV !== 'testing',
33+
});
34+
35+
// create requests collection
1136
const reqCollection = db.addCollection('requests', {
1237
ttl: REQ_TTL,
1338
ttlInterval: REQ_TTL,
1439
});
1540

1641
exports.db = db;
42+
exports.tokenDb = tokenDb;
1743
exports.reqCollection = reqCollection;
44+
exports.getTokenCollection = () => tokenCollection;

test/login.js

+96-1
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ const jwt = require('jsonwebtoken');
66

77
// our packages
88
const {auth: authConfig} = require('../config');
9+
const {getTokenCollection} = require('../src/db');
910

1011
module.exports = server =>
1112
new Promise(async resolve => {
1213
let token = '';
1314
let phrase = '';
1415
let loginReqId = '';
16+
let deployToken = '';
1517

1618
tap.test('Should get login id and login phrase', t => {
1719
const options = {
@@ -68,11 +70,14 @@ module.exports = server =>
6870

6971
tap.test('Should generate valid deploy token', t => {
7072
const options = {
71-
method: 'GET',
73+
method: 'POST',
7274
url: '/deployToken',
7375
headers: {
7476
Authorization: `Bearer ${token}`,
7577
},
78+
payload: {
79+
tokenName: 'test',
80+
},
7681
};
7782

7883
server.inject(options, response => {
@@ -84,9 +89,99 @@ module.exports = server =>
8489
const decodedUser = jwt.verify(result.token, authConfig.privateKey);
8590

8691
t.equal(decodedUser.user.username, 'admin', 'Login matches request');
92+
t.equal(decodedUser.tokenName, 'test', 'Token name matches request');
8793
t.ok(decodedUser.loggedIn, 'Is logged in');
8894
t.ok(decodedUser.deploy, 'Is logged in');
8995

96+
// store for further tests
97+
deployToken = result.token;
98+
99+
t.end();
100+
});
101+
});
102+
103+
tap.test('Should allow request with valid deploy token', t => {
104+
const options = {
105+
method: 'GET',
106+
url: '/checkToken',
107+
headers: {
108+
Authorization: `Bearer ${deployToken}`,
109+
},
110+
};
111+
112+
server.inject(options, response => {
113+
const result = response.result;
114+
115+
t.equal(response.statusCode, 200, 'Correct status code');
116+
t.ok(result.credentials, 'Has token');
117+
118+
t.equal(result.message, 'Token is valid', 'Has correct message');
119+
t.equal(result.credentials.username, 'admin', 'Login matches request');
120+
121+
t.end();
122+
});
123+
});
124+
125+
tap.test('Should list generated deploy tokens', t => {
126+
const options = {
127+
method: 'GET',
128+
url: '/deployToken',
129+
headers: {
130+
Authorization: `Bearer ${token}`,
131+
},
132+
};
133+
134+
server.inject(options, response => {
135+
const result = response.result;
136+
137+
t.equal(response.statusCode, 200, 'Correct status code');
138+
t.ok(result.tokens, 'Has token');
139+
140+
t.equal(result.tokens.length, 1, 'Should have on generated token');
141+
t.equal(result.tokens[0].tokenName, 'test', 'Should have correct token name');
142+
143+
t.end();
144+
});
145+
});
146+
147+
tap.test('Should remove generated deploy tokens', t => {
148+
const options = {
149+
method: 'DELETE',
150+
url: '/deployToken',
151+
headers: {
152+
Authorization: `Bearer ${token}`,
153+
},
154+
payload: {
155+
tokenName: 'test',
156+
},
157+
};
158+
159+
server.inject(options, response => {
160+
t.equal(response.statusCode, 204, 'Correct status code');
161+
162+
// read tokens from DB and make sure there are none
163+
const tokens = getTokenCollection().find();
164+
t.equal(tokens.length, 0, 'All tokens were deleted');
165+
166+
t.end();
167+
});
168+
});
169+
170+
tap.test('Should not allow request with removed deploy token', t => {
171+
const options = {
172+
method: 'GET',
173+
url: '/checkToken',
174+
headers: {
175+
Authorization: `Bearer ${deployToken}`,
176+
},
177+
};
178+
179+
server.inject(options, response => {
180+
const result = response.result;
181+
182+
t.equal(response.statusCode, 401, 'Correct status code');
183+
t.equal(result.error, 'Unauthorized', 'Correct error message');
184+
90185
t.end();
91186
});
92187
});

0 commit comments

Comments
 (0)