Skip to content

Commit 4493d80

Browse files
author
Lionel Laské
committed
Merge branch 'dev'
2 parents 9bfb805 + 93cebc7 commit 4493d80

36 files changed

+16715
-2236
lines changed

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
engine-strict=true

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7+
## [1.4.0] - 2021-11-21
8+
### Added
9+
- Two factor authentication
10+
- Expose presence and server on the same port #232
11+
- Add privacy settings
12+
13+
### Changed
14+
- node.js minimal version is now 10+
15+
16+
### Fixed
17+
- Unit tests are broken for activities #273
18+
- Warning: Accessing non-existent property with node.js #272
19+
- Error retrieving content when stored in UTF-16 #286
20+
21+
722
## [1.3.0] - 2020-10-11
823
### Added
924
- Provide a way to remove multiple users at the same time #217

README.md

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
[Sugarizer](https://github.com/llaske/sugarizer) is the open source learning platform based on Sugar that began in the famous One Laptop Per Child project.
66

7-
Sugarizer Server allows the deployment of Sugarizer on a local server, for example on a school server, so expose locally Sugarizer as a Web Application. Sugarizer Server can also be used to provide collaboration features for Sugarizer Application on the network. Sugarizer Server could be deployed in a Docker container or on any computer with Node.js 6+ and MongoDB 2.6+.
7+
Sugarizer Server allows the deployment of Sugarizer on a local server, for example on a school server, so expose locally Sugarizer as a Web Application. Sugarizer Server can also be used to provide collaboration features for Sugarizer Application on the network. Sugarizer Server could be deployed in a Docker container or on any computer with Node.js 10+ and MongoDB 2.6+.
88

99

1010
## Running Sugarizer Server
@@ -40,13 +40,20 @@ Following is the typical content of Sugarizer Server settings file:
4040
port = 8080
4141

4242
[security]
43-
min_password_size = 4
44-
max_age = 172800000
45-
https = false
46-
certificate_file = ../server.crt
47-
key_file = ../server.key
48-
strict_ssl = false
49-
no_signup_mode = false
43+
min_password_size = 4
44+
max_age = 172800000
45+
max_age_TFA = 180000
46+
https = false
47+
certificate_file = ../server.crt
48+
key_file = ../server.key
49+
strict_ssl = false
50+
no_signup_mode = false
51+
service_name = Sugarizer Server
52+
secret = super.sugarizer.server.key
53+
54+
[privacy]
55+
consent_need = false
56+
policy = https://sugarizer.org/policy.html
5057

5158
[client]
5259
path = ../sugarizer/
@@ -84,12 +91,16 @@ The **[information]** section is for describing your server. It could be useful
8491

8592
The **[web]** section describes the settings of the node.js process. By default, the web server is on the port 8080.
8693

87-
The **[security]** section regroup security settings. `min_password_size` is the minimum number of characters for the password. `max_age` is the expiration time in milliseconds of a session with the client. At the expiration of the session, the client should reenter its password. Default time is 172800000 (48 hours). Parameters `https`, `certificate_file`, `key_file` and `strict_ssl` are explain above.
94+
The **[security]** section regroup security settings. `min_password_size` is the minimum number of characters for the password. `max_age` is the expiration time in milliseconds of a session with the client. At the expiration of the session, the client should reenter its password. Default time is 172800000 (48 hours). Similarly, `max_age_TFA` is is the expiration time in milliseconds of a session with the client. At the expiration of the session, the client should reenter its password. The default time is 180000 (30 mins).Parameters `https`, `certificate_file`, `key_file` and `strict_ssl` are explain above.
8895
It `no_signup_mode` is true, account creation is allowed only by an administrator or a teacher (no direct sign-up allowed by a student).
96+
The `service_name` is the issuer parameter, a string value indicating the provider or service this account is associated with, URL-encoded according to [RFC 3986](http://tools.ietf.org/html/rfc3986).
97+
The `secret` is the JWT Secret which is used to encrypt JSON Web Token. It should be replaced with a unique value to keep the SSP Server secure.
98+
99+
The **[privacy]** section describe privacy settings. When `consent_need` is set to true, the Sugarizer client will ask a consent to user before they will be allowed to do their first connection to the server. `policy` is the URL that Sugarizer client shown in consent popup displayed to user.
89100

90101
The **[client]** indicate the place where is located Sugarizer Client. Sugarizer Client is need by the server.
91102

92-
The **[presence]** section describes the settings of the presence server. By default, a web socket is created on port 8039. You need to change this value if you want to use another port.
103+
The **[presence]** section describes the settings of the presence server. By default, a web socket is created on port 8039. You need to change this value if you want to use another port. You could use the same value than the one in the `web` port.
93104

94105
The **[database]** and **[collections]** sections are for MongoDB settings. You could update the server name (by default MongoDB run locally) and the server port. Names of the database and collections had no reason to be changed. The `waitdb` parameter allow you to force server to wait for the database. Optionally, the `replicaset` parameter can be set to `true` to enable MongoDB Replicaset support, in this case the server name becomes the replicaset connection string.
95106

@@ -142,6 +153,7 @@ To implement the above functionalities, the sugarizer backend exposes an API. Th
142153
#### USERS ROUTES
143154

144155
[POST] /auth/login
156+
[POST] /auth/verify2FA
145157
[POST] /auth/signup
146158
[GET] /api/v1/users
147159
[GET] /api/v1/users?name=tarun
@@ -198,6 +210,12 @@ To implement the above functionalities, the sugarizer backend exposes an API. Th
198210
[POST] /api/v1/stats
199211
[DELETE] /api/v1/stats
200212

213+
#### TWO FACTOR AUTHENTICATION ROUTES
214+
215+
[GET] /api/v1/dashboard/profile/enable2FA
216+
[PUT] /api/v1/dashboard/profile/enable2FA
217+
[PUT] /api/v1/dashboard/profile/disable2FA
218+
201219

202220
A full documentation of the API is available in http://127.0.0.1:8080/docs.
203221

@@ -290,11 +308,7 @@ Then launch Grunt task to minify Sugarizer JavaScript files:
290308

291309
grunt -v
292310

293-
After minification, the `build` directory will contain the optimized version of each file in the same directory as the initial one, so you could just copy files:
294-
295-
cp -r build/* .
296-
297-
Then navigate to Sugarizer-Server directory install the specific component for Sugarizer-Server by running:
311+
Now navigate to Sugarizer-Server directory install the specific component for Sugarizer-Server by running:
298312

299313
npm install
300314

api/controller/auth.js

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
var jwt = require('jwt-simple'),
22
users = require('./users.js'),
33
mongo = require('mongodb'),
4+
otplib = require('otplib'),
45
common = require('../../dashboard/helper/common');
56

67
var security;
8+
var secret;
79

810
// Init settings
911
exports.init = function(settings) {
1012
security = settings.security;
13+
secret = settings.security.secret;
1114
};
1215

1316
/**
@@ -110,9 +113,15 @@ exports.login = function(req, res) {
110113
//take the first user incase of multple matches
111114
user = users[0];
112115

113-
// If authentication is success, we will generate a token and dispatch it to the client
114116
var maxAge = req.iniconfig.security.max_age;
115-
res.send(genToken(user, maxAge));
117+
var maxAgeTfa = req.iniconfig.security.max_age_TFA;
118+
// If authentication is success, we will generate a token and dispatch it to the client
119+
if (user.tfa === false || typeof user.tfa === "undefined") {
120+
res.send(genToken(user, maxAge, false));
121+
} else {
122+
delete user.deployments;
123+
res.send(genToken(user, maxAgeTfa, true)); //give users a buffer of 30 mins to verify.
124+
}
116125
} else {
117126
res.status(401).send({
118127
'error': "Invalid credentials",
@@ -122,6 +131,62 @@ exports.login = function(req, res) {
122131
return;
123132
});
124133
};
134+
exports.verify2FA = function(req, res) {
135+
136+
if (!req.body.userToken) {
137+
return res.status(401).send({
138+
'error': 'User Token not defined',
139+
'code': 31
140+
});
141+
}
142+
143+
//token that the user entered
144+
var uniqueToken = req.body.userToken;
145+
146+
var uid = req.user._id; // unique uid.
147+
148+
//find user by user id.
149+
users.getAllUsers({
150+
_id: new mongo.ObjectID(uid),
151+
verified: {
152+
$ne: false
153+
}
154+
}, {enableSecret: true}, function(users) {
155+
if (users && users.length > 0) {
156+
157+
//take the first user incase of multple matches
158+
var user = users[0];
159+
var uniqueSecret = user.uniqueSecret;
160+
try {
161+
var isValid = otplib.authenticator.check(uniqueToken, uniqueSecret);
162+
} catch (err) {
163+
res.status(401).send({
164+
'error': 'Could not verify OTP error in otplib',
165+
'code': 32
166+
});
167+
}
168+
169+
var maxAge = req.iniconfig.security.max_age;
170+
var maxAgeTfa = req.iniconfig.security.max_age_TFA;
171+
172+
if (isValid === true) {
173+
delete user.uniqueSecret;
174+
// refresh the user token and set partial to false.
175+
res.send(genToken(user, maxAge, false));
176+
} else {
177+
delete user.deployments;
178+
delete user.uniqueSecret;
179+
res.send(genToken(user, maxAgeTfa, true));
180+
}
181+
} else {
182+
res.status(401).send({
183+
'error': "User not found",
184+
'code': 1
185+
});
186+
}
187+
return;
188+
});
189+
};
125190

126191
/**
127192
* @api {post} auth/signup/ Signup User
@@ -216,7 +281,7 @@ function validateUsername(name, callback) {
216281
callback(false);
217282
}
218283
});
219-
};
284+
}
220285

221286
exports.validateUser = function(uid, callback) {
222287

@@ -307,16 +372,18 @@ exports.checkAdminOrLocal = function(req, res, next) {
307372
};
308373

309374
// private method
310-
function genToken(user, age) {
375+
function genToken(user, age, partial) {
311376
var expires = expiresIn(age);
312377
var token = jwt.encode({
378+
partial: partial,
313379
exp: expires
314-
}, require('../../config/secret')());
380+
}, secret);
315381

316382
return {
317383
token: token,
318384
expires: expires,
319-
user: user
385+
user: user,
386+
partial: partial
320387
};
321388
}
322389

api/controller/journal.js

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,46 @@ var sugarizerVersion = null;
1818
var gridfsbucket, CHUNKS_COLL, FILES_COLL;
1919

2020
//- Utility functions
21+
// Extract from https://gist.github.com/kongchen/941a652882d89bb96f87
22+
function _toUTF8(str) {
23+
var utf8 = [];
24+
for (var i=0; i < str.length; i++) {
25+
var charcode = str.charCodeAt(i);
26+
if (charcode < 0x80) utf8.push(charcode);
27+
else if (charcode < 0x800) {
28+
utf8.push(0xc0 | (charcode >> 6),
29+
0x80 | (charcode & 0x3f));
30+
}
31+
else if (charcode < 0xd800 || charcode >= 0xe000) {
32+
utf8.push(0xe0 | (charcode >> 12),
33+
0x80 | ((charcode>>6) & 0x3f),
34+
0x80 | (charcode & 0x3f));
35+
}
36+
// surrogate pair
37+
else {
38+
i++;
39+
// UTF-16 encodes 0x10000-0x10FFFF by
40+
// subtracting 0x10000 and splitting the
41+
// 20 bits of 0x0-0xFFFFF into two halves
42+
charcode = 0x10000 + (((charcode & 0x3ff)<<10)
43+
| (str.charCodeAt(i) & 0x3ff))
44+
utf8.push(0xf0 | (charcode >>18),
45+
0x80 | ((charcode>>12) & 0x3f),
46+
0x80 | ((charcode>>6) & 0x3f),
47+
0x80 | (charcode & 0x3f));
48+
}
49+
}
50+
return utf8;
51+
}
52+
function _toUTF16(input) {
53+
var i, str = '';
54+
55+
for (i = 0; i < input.length; i++) {
56+
str += '%' + ('0' + input[i].toString(16)).slice(-2);
57+
}
58+
str = decodeURIComponent(str);
59+
return str;
60+
}
2161

2262
// Init database
2363
exports.init = function(settings, database) {
@@ -395,7 +435,7 @@ exports.findJournalContent = function(req, res) {
395435
if (resCount == reqCount) {
396436
try {
397437
var textObject = JSON.parse(items[ind].text);
398-
items[ind].text = textObject.text;
438+
items[ind].text = textObject.encoding ? _toUTF16(textObject.text) : textObject.text;
399439
} catch (e) {
400440
return res.status(500).send({'error': 'Invalid text value', 'code': 12});
401441
}
@@ -662,10 +702,13 @@ exports.addEntryInJournal = function(req, res) {
662702
// Add a new entry
663703
if (journal.text) {
664704
var text = journal.text;
705+
var utftext = _toUTF8(journal.text);
706+
var isUtf16 = (journal.text.length != utftext.length);
665707
var filename = mongo.ObjectId();
666708
var textContent = JSON.stringify({
667709
text_type: typeof journal.text,
668-
text: journal.text
710+
text: isUtf16 ? utftext : journal.text,
711+
encoding: isUtf16
669712
});
670713

671714
streamifier.createReadStream(textContent)

0 commit comments

Comments
 (0)