Change db table name + Modify unit tests + Update README
This commit is contained in:
@@ -11,11 +11,12 @@ RATE_LIMIT_MAX_REQUESTS=10
|
||||
|
||||
DB=foobar.db
|
||||
|
||||
INITIAL_USER_USERNAME=<your_username>
|
||||
INITIAL_USER_PASSWORD=<your_password>
|
||||
|
||||
TOKEN_SECRET=ABCD23DCAA
|
||||
ACCESS_TOKEN_EXPIRATION_TIME=10H
|
||||
REFRESH_TOKEN_EXPIRATION_TIME=2D
|
||||
|
||||
INITIAL_USER_USERNAME
|
||||
INITIAL_USER_PASSWORD
|
||||
TOKEN_SECRET
|
||||
|
||||
START_WITH_STUDIO=false
|
||||
|
||||
35
README.md
35
README.md
@@ -29,22 +29,22 @@ Usage: soul [options]
|
||||
|
||||
|
||||
Options:
|
||||
--version Show version number [boolean]
|
||||
-d, --database SQLite database file or :memory: [string] [required]
|
||||
-p, --port Port to listen on [number]
|
||||
-r, --rate-limit-enabled Enable rate limiting [boolean]
|
||||
-c, --cors CORS whitelist origins [string]
|
||||
-a, --auth Enable authentication and authorization [boolean]
|
||||
--version Show version number [boolean]
|
||||
-d, --database SQLite database file or :memory: [string] [required]
|
||||
-p, --port Port to listen on [number]
|
||||
-r, --rate-limit-enabled Enable rate limiting [boolean]
|
||||
-c, --cors CORS whitelist origins [string]
|
||||
-a, --auth Enable authentication and authorization [boolean]
|
||||
|
||||
-iuu, --initialuserusername Initial user username [string]
|
||||
-iup, --initialuserpassword Initial user password [string]
|
||||
-iuu, --initialuserusername Initial user username [string]
|
||||
-iup, --initialuserpassword Initial user password [string]
|
||||
|
||||
-ats, --accesstokensecret Access Token Secret [string]
|
||||
-atet, --accesstokenexpirationtime Access Token Expiration Time [string]
|
||||
-rts, --refreshtokensecret Refresh Token Secret [string]
|
||||
-rtet, --refreshtokenexpirationtime Refresh Token Expiration Time [string]
|
||||
-S, --studio Start Soul Studio in parallel
|
||||
--help Show help
|
||||
-ats, --accesstokensecret Access Token Secret [string]
|
||||
-atet, --accesstokenexpirationtime Access Token Expiration Time (Default: 5H) [string]
|
||||
-rts, --refreshtokensecret Refresh Token Secret [string]
|
||||
-rtet, --refreshtokenexpirationtime Refresh Token Expiration Time (Default: 1D) [string]
|
||||
-S, --studio Start Soul Studio in parallel
|
||||
--help Show help
|
||||
|
||||
```
|
||||
|
||||
@@ -62,12 +62,11 @@ To run Soul in auth mode, allowing login and signup features with authorization
|
||||
|
||||
Run the Soul command with the necessary parameters:
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
soul --d foobar.db -a -ts <your_jwt_secret_value> -atet=4H -rtet=3D -iuu=john -iup=<your_password>
|
||||
|
||||
soul --d foobar.db -a -ts <your_jwt_secret_value> -atet=4H -rtet=3D -iuu=john -iup=<your_password>
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
Note: When configuring your JWT Secret, it is recommended to use a long string value for enhanced security. It is advisable to use a secret that is at least 10 characters in length.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ const { updateSuperuser } = require('./controllers/auth');
|
||||
const { argv } = yargs;
|
||||
|
||||
const runCLICommands = () => {
|
||||
//If the updatesuperuser command is passed from the CLI execute the updatesuperuser function
|
||||
// if the updatesuperuser command is passed from the CLI execute the updatesuperuser function
|
||||
if (argv._.includes('updatesuperuser')) {
|
||||
const { id, password, is_superuser } = argv;
|
||||
|
||||
|
||||
@@ -8,6 +8,6 @@ module.exports = {
|
||||
|
||||
USER_TABLE: '_users',
|
||||
ROLE_TABLE: '_roles',
|
||||
USER_ROLES_TABLE: '_users_roles',
|
||||
USERS_ROLES_TABLE: '_users_roles',
|
||||
ROLE_PERMISSIONS_TABLE: '_roles_permissions',
|
||||
};
|
||||
|
||||
@@ -27,13 +27,11 @@ const createDefaultTables = async () => {
|
||||
|
||||
// create _users table
|
||||
if (!usersTable) {
|
||||
// create the _users table
|
||||
tableService.createTable(USER_TABLE, schema.userSchema);
|
||||
}
|
||||
|
||||
// create _users_roles table
|
||||
if (!usersRolesTable) {
|
||||
// create the _users_roles table
|
||||
tableService.createTable(
|
||||
USERS_ROLES_TABLE,
|
||||
|
||||
@@ -49,7 +47,6 @@ const createDefaultTables = async () => {
|
||||
|
||||
// create _roles table
|
||||
if (!roleTable) {
|
||||
// create the _role table
|
||||
tableService.createTable(ROLE_TABLE, schema.roleSchema);
|
||||
|
||||
// create a default role in the _roles table
|
||||
@@ -62,7 +59,6 @@ const createDefaultTables = async () => {
|
||||
|
||||
// create _roles_permissions table
|
||||
if (!rolesPermissionTable && roleId) {
|
||||
// create the _roles_permissions table
|
||||
tableService.createTable(
|
||||
ROLE_PERMISSIONS_TABLE,
|
||||
schema.rolePermissionSchema,
|
||||
|
||||
@@ -28,8 +28,13 @@ describe('Auth Endpoints', () => {
|
||||
|
||||
expect(res.status).toEqual(201);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Row Inserted');
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
|
||||
it('POST /tables/_users/rows should throw 400 error if username is not passed', async () => {
|
||||
@@ -48,6 +53,10 @@ describe('Auth Endpoints', () => {
|
||||
|
||||
expect(res.status).toEqual(400);
|
||||
expect(res.body.message).toBe('username is required');
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
|
||||
it('POST /tables/_users/rows should throw 400 error if the password is not strong', async () => {
|
||||
@@ -71,6 +80,10 @@ describe('Auth Endpoints', () => {
|
||||
expect(res.body.message).toBe(
|
||||
'This password is weak, please use another password',
|
||||
);
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
|
||||
it('POST /tables/_users/rows should throw 409 error if the username is taken', async () => {
|
||||
@@ -92,6 +105,10 @@ describe('Auth Endpoints', () => {
|
||||
|
||||
expect(res.status).toEqual(409);
|
||||
expect(res.body.message).toBe('This username is taken');
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
|
||||
it('GET /tables/_users/rows should return list of users', async () => {
|
||||
@@ -110,6 +127,10 @@ describe('Auth Endpoints', () => {
|
||||
expect(res.body.data[0]).toHaveProperty('username');
|
||||
expect(res.body.data[0]).toHaveProperty('is_superuser');
|
||||
expect(res.body.data[0]).toHaveProperty('createdAt');
|
||||
|
||||
expect(res.body.data[0]).not.toHaveProperty('password');
|
||||
expect(res.body.data[0]).not.toHaveProperty('hashed_password');
|
||||
expect(res.body.data[0]).not.toHaveProperty('salt');
|
||||
});
|
||||
|
||||
it('GET /tables/_users/rows/:id should retrive a single user', async () => {
|
||||
@@ -128,6 +149,10 @@ describe('Auth Endpoints', () => {
|
||||
expect(res.body.data[0]).toHaveProperty('username');
|
||||
expect(res.body.data[0]).toHaveProperty('is_superuser');
|
||||
expect(res.body.data[0]).toHaveProperty('createdAt');
|
||||
|
||||
expect(res.body.data[0]).not.toHaveProperty('password');
|
||||
expect(res.body.data[0]).not.toHaveProperty('hashed_password');
|
||||
expect(res.body.data[0]).not.toHaveProperty('salt');
|
||||
});
|
||||
|
||||
it('PUT /tables/_users/rows/:id should update a user', async () => {
|
||||
@@ -147,6 +172,14 @@ describe('Auth Endpoints', () => {
|
||||
});
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Row updated');
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
|
||||
it('PUT /tables/_users/rows/:id should throw a 409 error if the username is taken', async () => {
|
||||
@@ -167,6 +200,10 @@ describe('Auth Endpoints', () => {
|
||||
|
||||
expect(res.status).toEqual(409);
|
||||
expect(res.body.message).toEqual('This username is already taken');
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
|
||||
it('DELETE /tables/_users/rows/:id should remove a user', async () => {
|
||||
@@ -182,6 +219,10 @@ describe('Auth Endpoints', () => {
|
||||
|
||||
expect(res.status).toEqual(400);
|
||||
expect(res.body.message).toBe('FOREIGN KEY constraint failed');
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -200,6 +241,18 @@ describe('Auth Endpoints', () => {
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Success');
|
||||
|
||||
expect(res.headers['set-cookie']).toBeDefined();
|
||||
expect(res.headers['set-cookie']).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining('refreshToken='),
|
||||
expect.stringContaining('accessToken='),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
|
||||
it('POST /auth/token/obtain should throw a 401 error if the username does not exist in the DB', async () => {
|
||||
@@ -216,6 +269,10 @@ describe('Auth Endpoints', () => {
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Invalid username or password');
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
|
||||
it('POST /auth/token/obtain should throw a 401 error if the password is invalid', async () => {
|
||||
@@ -232,17 +289,15 @@ describe('Auth Endpoints', () => {
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Invalid username or password');
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Refresh Access Token Endpoint', () => {
|
||||
it('GET /auth/token/refresh should refresh the access and refresh tokens', async () => {
|
||||
const accessToken = await generateToken(
|
||||
{ username: 'John', userId: 1, isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
'1H',
|
||||
);
|
||||
|
||||
const refreshToken = await generateToken(
|
||||
{ username: 'John', userId: 1, isSuperuser: true },
|
||||
config.tokenSecret,
|
||||
@@ -251,15 +306,20 @@ describe('Auth Endpoints', () => {
|
||||
|
||||
const res = await requestWithSupertest
|
||||
.get('/api/auth/token/refresh')
|
||||
.set('Cookie', [
|
||||
`accessToken=${accessToken}`,
|
||||
`refreshToken=${refreshToken}`,
|
||||
]);
|
||||
.set('Cookie', [`refreshToken=${refreshToken}`]);
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Success');
|
||||
|
||||
expect(res.headers['set-cookie']).toBeDefined();
|
||||
expect(res.headers['set-cookie']).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining('refreshToken='),
|
||||
expect.stringContaining('accessToken='),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -285,6 +345,25 @@ describe('Auth Endpoints', () => {
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Password updated successfully');
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
|
||||
// check if the password is really updated
|
||||
const res2 = await requestWithSupertest
|
||||
.post('/api/auth/token/obtain')
|
||||
.send({
|
||||
fields: {
|
||||
username: testData.users.user1.username,
|
||||
password: testData.strongPassword2,
|
||||
},
|
||||
});
|
||||
|
||||
expect(res2.status).toEqual(201);
|
||||
expect(res2.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res2.body).toHaveProperty('message');
|
||||
expect(res2.body.message).toBe('Success');
|
||||
});
|
||||
|
||||
it('PUT /auth/change-password/ should throw 401 error if the current password is not valid', async () => {
|
||||
@@ -308,6 +387,10 @@ describe('Auth Endpoints', () => {
|
||||
expect(res.type).toEqual(expect.stringContaining('json'));
|
||||
expect(res.body).toHaveProperty('message');
|
||||
expect(res.body.message).toBe('Invalid current password');
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).not.toHaveProperty('hashed_password');
|
||||
expect(res.body).not.toHaveProperty('salt');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"version": "0.6.1",
|
||||
"version": "0.7.0",
|
||||
"title": "Soul API",
|
||||
"description": "API Documentation for <b>Soul</b>, a SQLite REST and realtime server. "
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user